diff --git a/.builds/alpine.yml b/.builds/alpine.yml new file mode 100644 index 00000000..ff25a072 --- /dev/null +++ b/.builds/alpine.yml @@ -0,0 +1,28 @@ +image: alpine/latest +packages: +- build-base +- sdl2-dev +- freetype-dev +- fontconfig-dev +- opus-dev +- bzip2-dev +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + ./waf configure -8 --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + ./waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-alpine-amd64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-alpine-amd64.tar.bz2 diff --git a/.builds/debian-arm64.yml b/.builds/debian-arm64.yml new file mode 100644 index 00000000..5e8b288b --- /dev/null +++ b/.builds/debian-arm64.yml @@ -0,0 +1,31 @@ +image: debian/sid +arch: amd64 +packages: +- build-essential +- python-is-python3 +- libsdl2-dev +- libfreetype-dev +- libfontconfig-dev +- libopus-dev +- libbz2-dev +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + ./waf configure -8 --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + ./waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-debian-arm64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-debian-arm64.tar.bz2 + diff --git a/.builds/debian.yml b/.builds/debian.yml new file mode 100644 index 00000000..7c353473 --- /dev/null +++ b/.builds/debian.yml @@ -0,0 +1,30 @@ +image: debian/sid +packages: +- build-essential +- python-is-python3 +- libsdl2-dev +- libfreetype-dev +- libfontconfig-dev +- libopus-dev +- libbz2-dev +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + ./waf configure -8 --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + ./waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-debian-amd64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-debian-amd64.tar.bz2 + diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 00000000..b012fafc --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,30 @@ +image: freebsd/latest +packages: +- lang/python +- devel/pkgconf +- devel/sdl20 +- print/freetype2 +- x11-fonts/fontconfig +- audio/opus +- archivers/bzip2 +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + ./waf configure --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + ./waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-freebsd-amd64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-freebsd-amd64.tar.bz2 + diff --git a/.builds/netbsd.yml b/.builds/netbsd.yml new file mode 100644 index 00000000..5a0177af --- /dev/null +++ b/.builds/netbsd.yml @@ -0,0 +1,30 @@ +image: netbsd/latest +packages: +- python311 +- SDL2 +- freetype2 +- fontconfig +- libopus +- pkgconf +- bzip2 +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + python3.11 waf configure --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + python3.11 waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-netbsd-amd64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-netbsd-amd64.tar.bz2 + diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml new file mode 100644 index 00000000..760ccce3 --- /dev/null +++ b/.builds/openbsd.yml @@ -0,0 +1,27 @@ +image: openbsd/latest +packages: +- python3 +- sdl2 +- opus +- bzip2 +sources: +- https://git.sr.ht/~a1batross/xash3d-fwgs +tasks: +- client: | + cd xash3d-fwgs + python3 waf configure --enable-tests --enable-engine-tests build install --destdir=bin +- server: | + cd xash3d-fwgs + python3 waf configure --reconfigure --dedicated build install --destdir=bin +- run-tests: | + cd xash3d-fwgs/bin + export SDL_VIDEODRIVER=wayland + ./xash3d -runtests + ./xash -runtests + rm -rf valve/gameinfo.txt test_* +- package: | + cd xash3d-fwgs/bin + tar -cjvf xash3d-fwgs-openbsd-amd64.tar.bz2 * +artifacts: +- xash3d-fwgs/bin/xash3d-fwgs-openbsd-amd64.tar.bz2 + diff --git a/.cirrus.yml b/.cirrus.yml index 5c6b0a24..061536e8 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,35 +1,21 @@ -task: - name: freebsd-12-amd64 - freebsd_instance: - image_family: freebsd-12-3 - setup_script: - - pkg update - - pkg install -y git sdl2 python fontconfig - - git submodule update --init --recursive - test_script: - - ./scripts/cirrus/build_freebsd.sh dedicated - - ./scripts/cirrus/build_freebsd.sh full - -task: - name: freebsd-13-amd64 - freebsd_instance: - image_family: freebsd-13-1 - setup_script: - - pkg update - - pkg install -y git sdl2 python fontconfig - - git submodule update --init --recursive - test_script: - - ./scripts/cirrus/build_freebsd.sh dedicated - - ./scripts/cirrus/build_freebsd.sh full - task: name: freebsd-14-amd64 freebsd_instance: - image_family: freebsd-14-0-snap + image_family: freebsd-14-2 setup_script: - pkg update - - pkg install -y git sdl2 python fontconfig + - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace - git submodule update --init --recursive test_script: - - ./scripts/cirrus/build_freebsd.sh dedicated - - ./scripts/cirrus/build_freebsd.sh full + - ./scripts/cirrus/build_freebsd.sh + +task: + name: freebsd-15-amd64 + freebsd_instance: + image_family: freebsd-15-0-snap + setup_script: + - pkg update + - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace + - git submodule update --init --recursive + test_script: + - ./scripts/cirrus/build_freebsd.sh diff --git a/.editorconfig b/.editorconfig index 77807af0..c17cdd5d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,365 @@ -# EditorConfig is awesome: https://EditorConfig.org +# this file is just a suggestion, you might follow it, you might not root = true [*] -charset = latin1 +charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on + +[*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] + +# Visual C++ Formatting settings +cpp_indent_braces = false +cpp_indent_multi_line_relative_to = innermost_parenthesis +cpp_indent_within_parentheses = indent +cpp_indent_preserve_within_parentheses = true +cpp_indent_case_contents = true +cpp_indent_case_labels = false +cpp_indent_case_contents_when_block = false +cpp_indent_lambda_braces_when_parameter = true +cpp_indent_goto_labels = one_left +cpp_indent_preprocessor = leftmost_column +cpp_indent_access_specifiers = false +cpp_indent_namespace_contents = true +cpp_indent_preserve_comments = false +cpp_new_line_before_open_brace_namespace = new_line +cpp_new_line_before_open_brace_type = new_line +cpp_new_line_before_open_brace_function = new_line +cpp_new_line_before_open_brace_block = new_line +cpp_new_line_before_open_brace_lambda = new_line +cpp_new_line_scope_braces_on_separate_lines = true +cpp_new_line_close_brace_same_line_empty_type = false +cpp_new_line_close_brace_same_line_empty_function = false +cpp_new_line_before_catch = true +cpp_new_line_before_else = true +cpp_new_line_before_while_in_do_while = false +cpp_space_before_function_open_parenthesis = remove +cpp_space_within_parameter_list_parentheses = true +cpp_space_between_empty_parameter_list_parentheses = true +cpp_space_after_keywords_in_control_flow_statements = false +cpp_space_within_control_flow_statement_parentheses = true +cpp_space_before_lambda_open_parenthesis = false +cpp_space_within_cast_parentheses = false +cpp_space_after_cast_close_parenthesis = false +cpp_space_within_expression_parentheses = true +cpp_space_before_block_open_brace = true +cpp_space_between_empty_braces = true +cpp_space_before_initializer_list_open_brace = false +cpp_space_within_initializer_list_braces = true +cpp_space_preserve_in_initializer_list = true +cpp_space_before_open_square_bracket = false +cpp_space_within_square_brackets = false +cpp_space_before_empty_square_brackets = false +cpp_space_between_empty_square_brackets = false +cpp_space_group_square_brackets = true +cpp_space_within_lambda_brackets = false +cpp_space_between_empty_lambda_brackets = false +cpp_space_before_comma = false +cpp_space_after_comma = true +cpp_space_remove_around_member_operators = true +cpp_space_before_inheritance_colon = true +cpp_space_before_constructor_colon = true +cpp_space_remove_before_semicolon = true +cpp_space_after_semicolon = true +cpp_space_remove_around_unary_operator = true +cpp_space_around_binary_operator = insert +cpp_space_around_assignment_operator = insert +cpp_space_pointer_reference_alignment = right +cpp_space_around_ternary_operator = insert +cpp_use_unreal_engine_macro_formatting = true +cpp_wrap_preserve_blocks = one_liners + +# IDEA settings +ij_c_add_brief_tag = false +ij_c_add_getter_prefix = true +ij_c_add_setter_prefix = true +ij_c_align_dictionary_pair_values = false +ij_c_align_group_field_declarations = false +ij_c_align_init_list_in_columns = true +ij_c_align_multiline_array_initializer_expression = false +ij_c_align_multiline_assignment = false +ij_c_align_multiline_binary_operation = false +ij_c_align_multiline_chained_methods = false +ij_c_align_multiline_for = true +ij_c_align_multiline_ternary_operation = false +ij_c_array_initializer_comma_on_next_line = false +ij_c_array_initializer_new_line_after_left_brace = false +ij_c_array_initializer_right_brace_on_new_line = false +ij_c_array_initializer_wrap = off +ij_c_assignment_wrap = off +ij_c_binary_operation_sign_on_next_line = false +ij_c_binary_operation_wrap = off +ij_c_blank_lines_after_class_header = 0 +ij_c_blank_lines_after_imports = 1 +ij_c_blank_lines_around_class = 1 +ij_c_blank_lines_around_field = 0 +ij_c_blank_lines_around_field_in_interface = 0 +ij_c_blank_lines_around_method = 1 +ij_c_blank_lines_around_method_in_interface = 1 +ij_c_blank_lines_around_namespace = 0 +ij_c_blank_lines_around_properties_in_declaration = 0 +ij_c_blank_lines_around_properties_in_interface = 0 +ij_c_blank_lines_before_imports = 1 +ij_c_blank_lines_before_method_body = 0 +ij_c_block_brace_placement = next_line +ij_c_block_brace_style = next_line +ij_c_block_comment_at_first_column = true +ij_c_catch_on_new_line = false +ij_c_class_brace_style = next_line +ij_c_class_constructor_init_list_align_multiline = true +ij_c_class_constructor_init_list_comma_on_next_line = false +ij_c_class_constructor_init_list_new_line_after_colon = never +ij_c_class_constructor_init_list_new_line_before_colon = if_long +ij_c_class_constructor_init_list_wrap = normal +ij_c_copy_is_deep = false +ij_c_create_interface_for_categories = true +ij_c_declare_generated_methods = true +ij_c_description_include_member_names = true +ij_c_discharged_short_ternary_operator = false +ij_c_do_not_add_breaks = false +ij_c_do_while_brace_force = never +ij_c_else_on_new_line = false +ij_c_enum_constants_comma_on_next_line = false +ij_c_enum_constants_wrap = off +ij_c_for_brace_force = never +ij_c_for_statement_new_line_after_left_paren = false +ij_c_for_statement_right_paren_on_new_line = false +ij_c_for_statement_wrap = off +ij_c_function_brace_placement = next_line +ij_c_function_call_arguments_align_multiline = true +ij_c_function_call_arguments_align_multiline_pars = false +ij_c_function_call_arguments_comma_on_next_line = false +ij_c_function_call_arguments_new_line_after_lpar = false +ij_c_function_call_arguments_new_line_before_rpar = false +ij_c_function_call_arguments_wrap = normal +ij_c_function_non_top_after_return_type_wrap = normal +ij_c_function_parameters_align_multiline = true +ij_c_function_parameters_align_multiline_pars = false +ij_c_function_parameters_comma_on_next_line = false +ij_c_function_parameters_new_line_after_lpar = false +ij_c_function_parameters_new_line_before_rpar = false +ij_c_function_parameters_wrap = normal +ij_c_function_top_after_return_type_wrap = normal +ij_c_generate_additional_eq_operators = true +ij_c_generate_additional_rel_operators = true +ij_c_generate_class_constructor = true +ij_c_generate_comparison_operators_use_std_tie = false +ij_c_generate_instance_variables_for_properties = ask +ij_c_generate_operators_as_members = true +ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} +ij_c_if_brace_force = never +ij_c_in_line_short_ternary_operator = true +ij_c_indent_block_comment = true +ij_c_indent_c_struct_members = 4 +ij_c_indent_case_from_switch = true +ij_c_indent_class_members = 4 +ij_c_indent_directive_as_code = false +ij_c_indent_implementation_members = 0 +ij_c_indent_inside_code_block = 4 +ij_c_indent_interface_members = 0 +ij_c_indent_interface_members_except_ivars_block = false +ij_c_indent_namespace_members = 4 +ij_c_indent_preprocessor_directive = 0 +ij_c_indent_visibility_keywords = 0 +ij_c_insert_override = true +ij_c_insert_virtual_with_override = false +ij_c_introduce_auto_consts = false +ij_c_introduce_auto_vars = false +ij_c_introduce_const_params = false +ij_c_introduce_const_vars = false +ij_c_introduce_constexpr_consts = false +ij_c_introduce_generate_property = false +ij_c_introduce_generate_synthesize = true +ij_c_introduce_globals_to_header = true +ij_c_introduce_prop_to_private_category = false +ij_c_introduce_static_consts = true +ij_c_introduce_use_ns_types = false +ij_c_ivars_prefix = _ +ij_c_ivars_suffix = +ij_c_keep_blank_lines_before_end = 2 +ij_c_keep_blank_lines_before_right_brace = 2 +ij_c_keep_blank_lines_in_code = 2 +ij_c_keep_blank_lines_in_declarations = 2 +ij_c_keep_case_expressions_in_one_line = false +ij_c_keep_control_statement_in_one_line = false +ij_c_keep_directive_at_first_column = true +ij_c_keep_first_column_comment = false +ij_c_keep_line_breaks = true +ij_c_keep_nested_namespaces_in_one_line = false +ij_c_keep_simple_blocks_in_one_line = false +ij_c_keep_simple_methods_in_one_line = false +ij_c_keep_structures_in_one_line = false +ij_c_lambda_capture_list_align_multiline = false +ij_c_lambda_capture_list_align_multiline_bracket = false +ij_c_lambda_capture_list_comma_on_next_line = false +ij_c_lambda_capture_list_new_line_after_lbracket = false +ij_c_lambda_capture_list_new_line_before_rbracket = false +ij_c_lambda_capture_list_wrap = off +ij_c_line_comment_add_space = false +ij_c_line_comment_at_first_column = true +ij_c_method_brace_placement = end_of_line +ij_c_method_call_arguments_align_by_colons = true +ij_c_method_call_arguments_align_multiline = false +ij_c_method_call_arguments_special_dictionary_pairs_treatment = true +ij_c_method_call_arguments_wrap = off +ij_c_method_call_chain_wrap = off +ij_c_method_parameters_align_by_colons = true +ij_c_method_parameters_align_multiline = false +ij_c_method_parameters_wrap = off +ij_c_namespace_brace_placement = next_line +ij_c_parentheses_expression_new_line_after_left_paren = false +ij_c_parentheses_expression_right_paren_on_new_line = false +ij_c_place_assignment_sign_on_next_line = false +ij_c_property_nonatomic = true +ij_c_put_ivars_to_implementation = true +ij_c_refactor_compatibility_aliases_and_classes = true +ij_c_refactor_properties_and_ivars = true +ij_c_release_style = ivar +ij_c_retain_object_parameters_in_constructor = true +ij_c_semicolon_after_method_signature = false +ij_c_shift_operation_align_multiline = true +ij_c_shift_operation_wrap = normal +ij_c_show_non_virtual_functions = false +ij_c_space_after_colon = true +ij_c_space_after_colon_in_foreach = true +ij_c_space_after_colon_in_selector = false +ij_c_space_after_comma = true +ij_c_space_after_cup_in_blocks = false +ij_c_space_after_dictionary_literal_colon = true +ij_c_space_after_for_semicolon = true +ij_c_space_after_init_list_colon = true +ij_c_space_after_method_parameter_type_parentheses = false +ij_c_space_after_method_return_type_parentheses = false +ij_c_space_after_pointer_in_declaration = false +ij_c_space_after_quest = true +ij_c_space_after_reference_in_declaration = false +ij_c_space_after_reference_in_rvalue = false +ij_c_space_after_structures_rbrace = true +ij_c_space_after_superclass_colon = true +ij_c_space_after_type_cast = false +ij_c_space_after_visibility_sign_in_method_declaration = true +ij_c_space_before_autorelease_pool_lbrace = true +ij_c_space_before_catch_keyword = true +ij_c_space_before_catch_left_brace = true +ij_c_space_before_catch_parentheses = false +ij_c_space_before_category_parentheses = true +ij_c_space_before_chained_send_message = true +ij_c_space_before_class_left_brace = true +ij_c_space_before_colon = true +ij_c_space_before_colon_in_foreach = true +ij_c_space_before_comma = false +ij_c_space_before_dictionary_literal_colon = true +ij_c_space_before_do_left_brace = true +ij_c_space_before_else_keyword = true +ij_c_space_before_else_left_brace = true +ij_c_space_before_export_lbrace = true +ij_c_space_before_for_left_brace = true +ij_c_space_before_for_parentheses = false +ij_c_space_before_for_semicolon = false +ij_c_space_before_if_left_brace = true +ij_c_space_before_if_parentheses = false +ij_c_space_before_init_list = false +ij_c_space_before_init_list_colon = true +ij_c_space_before_method_call_parentheses = false +ij_c_space_before_method_left_brace = true +ij_c_space_before_method_parentheses = false +ij_c_space_before_namespace_lbrace = true +ij_c_space_before_pointer_in_declaration = true +ij_c_space_before_property_attributes_parentheses = false +ij_c_space_before_protocols_brackets = true +ij_c_space_before_quest = true +ij_c_space_before_reference_in_declaration = true +ij_c_space_before_superclass_colon = true +ij_c_space_before_switch_left_brace = true +ij_c_space_before_switch_parentheses = false +ij_c_space_before_template_call_lt = false +ij_c_space_before_template_declaration_lt = true +ij_c_space_before_try_left_brace = true +ij_c_space_before_while_keyword = true +ij_c_space_before_while_left_brace = true +ij_c_space_before_while_parentheses = false +ij_c_space_between_adjacent_brackets = false +ij_c_space_between_operator_and_punctuator = false +ij_c_space_within_empty_array_initializer_braces = true +ij_c_spaces_around_additive_operators = true +ij_c_spaces_around_assignment_operators = true +ij_c_spaces_around_bitwise_operators = true +ij_c_spaces_around_equality_operators = true +ij_c_spaces_around_lambda_arrow = true +ij_c_spaces_around_logical_operators = true +ij_c_spaces_around_multiplicative_operators = true +ij_c_spaces_around_pm_operators = false +ij_c_spaces_around_relational_operators = true +ij_c_spaces_around_shift_operators = true +ij_c_spaces_around_unary_operator = false +ij_c_spaces_within_array_initializer_braces = true +ij_c_spaces_within_braces = false +ij_c_spaces_within_brackets = false +ij_c_spaces_within_cast_parentheses = false +ij_c_spaces_within_catch_parentheses = true +ij_c_spaces_within_category_parentheses = false +ij_c_spaces_within_empty_braces = false +ij_c_spaces_within_empty_function_call_parentheses = true +ij_c_spaces_within_empty_function_declaration_parentheses = true +ij_c_spaces_within_empty_lambda_capture_list_bracket = false +ij_c_spaces_within_empty_template_call_ltgt = false +ij_c_spaces_within_empty_template_declaration_ltgt = false +ij_c_spaces_within_for_parentheses = true +ij_c_spaces_within_function_call_parentheses = true +ij_c_spaces_within_function_declaration_parentheses = true +ij_c_spaces_within_if_parentheses = true +ij_c_spaces_within_lambda_capture_list_bracket = false +ij_c_spaces_within_method_parameter_type_parentheses = false +ij_c_spaces_within_method_return_type_parentheses = false +ij_c_spaces_within_parentheses = true +ij_c_spaces_within_property_attributes_parentheses = false +ij_c_spaces_within_protocols_brackets = false +ij_c_spaces_within_send_message_brackets = false +ij_c_spaces_within_structured_binding_list_bracket = false +ij_c_spaces_within_switch_parentheses = true +ij_c_spaces_within_template_call_ltgt = false +ij_c_spaces_within_template_declaration_ltgt = false +ij_c_spaces_within_template_double_gt = false +ij_c_spaces_within_while_parentheses = true +ij_c_special_else_if_treatment = true +ij_c_structured_binding_list_align_multiline = false +ij_c_structured_binding_list_align_multiline_bracket = false +ij_c_structured_binding_list_comma_on_next_line = false +ij_c_structured_binding_list_new_line_after_lbracket = false +ij_c_structured_binding_list_new_line_before_rbracket = false +ij_c_structured_binding_list_wrap = off +ij_c_superclass_list_after_colon = never +ij_c_superclass_list_align_multiline = true +ij_c_superclass_list_before_colon = if_long +ij_c_superclass_list_comma_on_next_line = false +ij_c_superclass_list_wrap = on_every_item +ij_c_tag_prefix_of_block_comment = at +ij_c_tag_prefix_of_line_comment = back_slash +ij_c_template_call_arguments_align_multiline = false +ij_c_template_call_arguments_align_multiline_pars = false +ij_c_template_call_arguments_comma_on_next_line = false +ij_c_template_call_arguments_new_line_after_lt = false +ij_c_template_call_arguments_new_line_before_gt = false +ij_c_template_call_arguments_wrap = off +ij_c_template_declaration_function_body_indent = false +ij_c_template_declaration_function_wrap = split_into_lines +ij_c_template_declaration_struct_body_indent = false +ij_c_template_declaration_struct_wrap = split_into_lines +ij_c_template_parameters_align_multiline = false +ij_c_template_parameters_align_multiline_pars = false +ij_c_template_parameters_comma_on_next_line = false +ij_c_template_parameters_new_line_after_lt = false +ij_c_template_parameters_new_line_before_gt = false +ij_c_template_parameters_wrap = off +ij_c_ternary_operation_signs_on_next_line = false +ij_c_ternary_operation_wrap = off +ij_c_type_qualifiers_placement = before +ij_c_use_modern_casts = true +ij_c_use_setters_in_constructor = true +ij_c_while_brace_force = never +ij_c_while_on_new_line = false +ij_c_wrap_property_declaration = off diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..9f38e5fb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/donate.md diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index e53a8b16..798b2899 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -13,60 +13,135 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-18.04 + # Linux build specifically want oldest Ubuntu as possible + # to be crossdistribution compatible, otherwise use ubuntu-latest + - os: ubuntu-22.04 targetos: linux targetarch: amd64 - - os: ubuntu-18.04 + - os: ubuntu-22.04 targetos: linux targetarch: i386 -# - os: ubuntu-aarch64-20.04 + - os: ubuntu-22.04 + targetos: linux + targetarch: arm64 + cross: true + - os: ubuntu-22.04 + targetos: linux + targetarch: armhf + cross: true + - os: ubuntu-24.04 # riscv64 would benefit from having latest compilers + targetos: linux + targetarch: riscv64 + cross: true + - os: ubuntu-22.04 + targetos: linux + targetarch: ppc64el + cross: true +# - os: ubuntu-aarch64-22.04 # targetos: linux # targetarch: aarch64 - - - os: ubuntu-18.04 +# - os: ubuntu-latest +# targetos: linux +# targetarch: e2k-8c +# cross: true + - os: ubuntu-latest targetos: android - targetarch: 32 - - os: ubuntu-18.04 - targetos: android - targetarch: 64 - - - os: ubuntu-18.04 - targetos: motomagx - targetarch: armv6 + targetarch: multiarch +# - os: ubuntu-22.04 +# targetos: motomagx +# targetarch: armv6 + - os: ubuntu-latest + targetos: nswitch + targetarch: arm64 + - os: ubuntu-latest + targetos: psvita + targetarch: armv7hf - os: windows-latest targetos: win32 targetarch: amd64 - - os: windows-latest + - os: windows-2022 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes targetos: win32 targetarch: i386 + - os: macos-14 # arm64 as per github documentation + targetos: apple + targetarch: arm64 + - os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..) + targetos: apple + targetarch: amd64 env: - SDL_VERSION: 2.0.14 + SDL_VERSION: 2.32.8 + FFMPEG_VERSION: 7.1 GH_CPU_ARCH: ${{ matrix.targetarch }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ANDROID_SDK_TOOLS_VER: 4333796 - UPLOADTOOL_ISPRERELEASE: true + GH_CPU_OS: ${{ matrix.targetos }} + GH_CROSSCOMPILING: ${{ matrix.cross }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: recursive - - name: Checkout xash-extras - uses: actions/checkout@v2 - with: - repository: FWGS/xash-extras - path: xash-extras - - name: Install dependencies run: bash scripts/gha/deps_${{ matrix.targetos }}.sh - - name: Build engine + env: + FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }} run: bash scripts/gha/build_${{ matrix.targetos }}.sh - - - name: Upload engine (prereleases) - run: bash scripts/continious_upload.sh artifacts/* - name: Upload engine (artifacts) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} path: artifacts/* + flatpak: + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + include: + - app: su.xash.Engine.Compat.i386 + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08 + options: --privileged + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build flatpak (Compat.i386) + uses: FWGS/flatpak-github-actions/flatpak-builder@v6.5 + with: + bundle: ${{ matrix.app }}.flatpak + manifest-path: scripts/flatpak/${{ matrix.app }}.yml + release: + name: "Upload releases" + runs-on: ubuntu-latest + needs: [build, flatpak] + if: ${{ github.event_name == 'push' }} + steps: + - name: Remove old release, fetch artifacts, repackage binaries and upload new release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }} + run: | + gh release delete "$RELEASE_TAG" \ + --yes \ + --cleanup-tag \ + --repo "$GITHUB_REPOSITORY" || true + gh run download "$GITHUB_RUN_ID" \ + --dir artifacts/ \ + --repo "$GITHUB_REPOSITORY" + pushd artifacts/ + echo "Found artifacts:" + ls + for i in $(find -mindepth 1 -maxdepth 1 -type d); do + mv "$i"/* . + rm -rf "$i" + done + echo "Repackaged artifacts:" + ls -R + popd + sleep 20s + gh release create "$RELEASE_TAG" artifacts/* \ + --title "Xash3D FWGS Continuous ${{ github.ref_name }} Build" \ + --target $GITHUB_SHA \ + --repo "$GITHUB_REPOSITORY" \ + --prerelease diff --git a/.gitignore b/.gitignore index 58eb4a76..1da23701 100644 --- a/.gitignore +++ b/.gitignore @@ -54,12 +54,18 @@ CMakeFiles Makefile cmake_install.cmake install_manifest.txt +CMakeLists.txt* +CMakeScripts +Testing +compile_commands.json +_deps # makedepend Makefile.dep *.bak ALL_BUILD.* INSTALL.* ZERO_CHECK.* +CMakeLists.txt # Visual Studio *.obj @@ -216,7 +222,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj @@ -333,3 +339,18 @@ __pycache__ .history/* .cache/* enc_temp_folder/ + +# KDevelop4 +*.kdev4 + +# ccls langauge server +.ccls-* + +# JetBrains +.idea/ +cmake-build-* + +# some Android-specific build stuff +3rdparty/SDL +3rdparty/hlsdk-portable +!scripts/build-ninja.py diff --git a/.gitmodules b/.gitmodules index e2ac7c73..34b3fa18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,24 @@ [submodule "3rdparty/xash-extras"] path = 3rdparty/extras/xash-extras url = https://github.com/FWGS/xash-extras +[submodule "3rdparty/bzip2/bzip2"] + path = 3rdparty/bzip2/bzip2 + url = https://gitlab.com/bzip2/bzip2 +[submodule "3rdparty/MultiEmulator"] + path = 3rdparty/MultiEmulator + url = https://github.com/FWGS/MultiEmulator +[submodule "3rdparty/libogg/libogg"] + path = 3rdparty/libogg/libogg + url = https://github.com/xiph/ogg.git +[submodule "3rdparty/vorbis/vorbis-src"] + path = 3rdparty/vorbis/vorbis-src + url = https://github.com/xiph/vorbis.git +[submodule "3rdparty/opusfile/opusfile"] + path = 3rdparty/opusfile/opusfile + url = https://github.com/xiph/opusfile.git +[submodule "3rdparty/libbacktrace/libbacktrace"] + path = 3rdparty/libbacktrace/libbacktrace + url = https://github.com/ianlancetaylor/libbacktrace +[submodule "3rdparty/maintui"] + path = 3rdparty/maintui + url = https://github.com/numas13/xash3d-maintui.git diff --git a/3rdparty/MultiEmulator b/3rdparty/MultiEmulator new file mode 160000 index 00000000..11ff077c --- /dev/null +++ b/3rdparty/MultiEmulator @@ -0,0 +1 @@ +Subproject commit 11ff077ca58b9394a0eff0e89dd8980c2c252956 diff --git a/3rdparty/bzip2/bzip2 b/3rdparty/bzip2/bzip2 new file mode 160000 index 00000000..66c46b8c --- /dev/null +++ b/3rdparty/bzip2/bzip2 @@ -0,0 +1 @@ +Subproject commit 66c46b8c9436613fd81bc5d03f63a61933a4dcc3 diff --git a/3rdparty/bzip2/wscript b/3rdparty/bzip2/wscript new file mode 100644 index 00000000..126a3c75 --- /dev/null +++ b/3rdparty/bzip2/wscript @@ -0,0 +1,27 @@ +#! /usr/bin/env python +# encoding: utf-8 + +def options(opt): + pass + +def configure(conf): + conf.define('_GNU_SOURCE', 1) + conf.define('BZ_NO_STDIO', 1) + + if conf.env.DEST_OS == 'win32': + conf.define('BZ_LCCWIN32', 1) + else: + conf.define('BZ_UNIX', 1) + +def build(bld): + bld(features = 'subst', source = 'bzip2/bz_version.h.in', target = 'bzip2/bz_version.h', BZ_VERSION='1.1.0-fwgs', name = 'bz_version') + + bz_sources = ['bzip2/blocksort.c', 'bzip2/huffman.c', 'bzip2/crctable.c', 'bzip2/randtable.c', 'bzip2/compress.c', 'bzip2/decompress.c', 'bzip2/bzlib.c'] + + bld.stlib( + source = bz_sources, + target = 'bzip2', + use = 'bz_version', + includes = 'bzip2/', + export_includes = 'bzip2/' + ) diff --git a/3rdparty/extras/wscript b/3rdparty/extras/wscript index 9d249bac..53484e40 100644 --- a/3rdparty/extras/wscript +++ b/3rdparty/extras/wscript @@ -25,5 +25,4 @@ def build(bld): name = 'extras.pk3', files = srcdir.ant_glob('**/*'), relative_to = srcdir, - compresslevel = 0, install_path = install_path) diff --git a/3rdparty/extras/xash-extras b/3rdparty/extras/xash-extras index e16b9826..0c83a63d 160000 --- a/3rdparty/extras/xash-extras +++ b/3rdparty/extras/xash-extras @@ -1 +1 @@ -Subproject commit e16b9826c2a0960a537139737241f498f9c0e938 +Subproject commit 0c83a63d000492e65c1620125f8d9a07d36495f8 diff --git a/3rdparty/gl-wes-v2 b/3rdparty/gl-wes-v2 index 7f567393..5e5a7192 160000 --- a/3rdparty/gl-wes-v2 +++ b/3rdparty/gl-wes-v2 @@ -1 +1 @@ -Subproject commit 7f567393d4273d8f33924d98968c9a96991aba3c +Subproject commit 5e5a7192e696a856d26742079589171a0429bdfd diff --git a/3rdparty/gl4es/gl4es b/3rdparty/gl4es/gl4es index 5c28420a..a744af14 160000 --- a/3rdparty/gl4es/gl4es +++ b/3rdparty/gl4es/gl4es @@ -1 +1 @@ -Subproject commit 5c28420a384c93345a7a5d060a56a0de5f2ac871 +Subproject commit a744af14d4afbda77bf472bc53f43b9ceba39cc0 diff --git a/3rdparty/gl4es/wscript b/3rdparty/gl4es/wscript index cad76650..083f3102 100644 --- a/3rdparty/gl4es/wscript +++ b/3rdparty/gl4es/wscript @@ -13,12 +13,12 @@ def configure(conf): def build(bld): gl4es_srcdir = bld.path.find_node('gl4es/src') - + cflags = [] + if bld.env.COMPILER_CC != 'msvc': + cflags += ['-w', '-fvisibility=hidden', '-std=gnu99'] bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), target = 'gl4es', - features = 'c', includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], - defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'EXTERNAL_GETPROCADDRESS=GL4ES_GetProcAddress', 'NO_LOADER', 'STATICLIB'], - cflags = ['-w', '-fvisibility=hidden', '-std=gnu99'], - subsystem = bld.env.MSVC_SUBSYSTEM, + defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'], + cflags = cflags, export_includes = '.') diff --git a/3rdparty/libbacktrace/libbacktrace b/3rdparty/libbacktrace/libbacktrace new file mode 160000 index 00000000..79392187 --- /dev/null +++ b/3rdparty/libbacktrace/libbacktrace @@ -0,0 +1 @@ +Subproject commit 793921876c981ce49759114d7bb89bb89b2d3a2d diff --git a/3rdparty/libbacktrace/wscript b/3rdparty/libbacktrace/wscript new file mode 100644 index 00000000..2e076a56 --- /dev/null +++ b/3rdparty/libbacktrace/wscript @@ -0,0 +1,186 @@ +#! /usr/bin/env python +# encoding: utf-8 + +from waflib import TaskGen +from waflib.Tools.c_config import DEFKEYS + +FRAGMENT_ATOMIC='''int i; +int main(void) { + __atomic_load_n(&i, __ATOMIC_ACQUIRE); + __atomic_store_n(&i, 1, __ATOMIC_RELEASE); + return 0; +}''' + +FRAGMENT_SYNC='''int i; +int main (void) { + __sync_bool_compare_and_swap (&i, i, i); + __sync_lock_test_and_set (&i, 1); + __sync_lock_release (&i); + return 0; +}''' + +FRAGMENT_GETPAGESIZE='''#include +int main(void) { return getpagesize() }''' + +FRAGMENT_GETEXECNAME='''#include +int main(void) { return getexecname() != 0 }''' + +FRAGMENT_STRNLEN='''#include +int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }''' + +FRAGMENT_DL_ITERATE_PHDR='''#include <%s> +int main(void) { return dl_iterate_phdr(0, 0); }''' + +FRAGMENT_FCNTL='''#include +int main(void) { return fcntl(0, 0, 0); }''' + +FRAGMENT_GETIPINFO='''#include "unwind.h" +struct _Unwind_Context *context; +int ip_before_insn = 0; +int main(void) { return _Unwind_GetIPInfo(context, &ip_before_insn); }''' + +FRAGMENT_LOADQUERY='''#include +#include +int main(void) { return loadquery(0, 0, 0); }''' + +FRAGMENT_KERN_PROC='''#include +#if !defined(%s) || !defined(KERN_PROC_PATHNAME) +#error +#endif +int main(void) { return 0; }''' + +FRAGMENT_LSTAT='''#include +struct stat st; +int main(int argc, char **argv) { return lstat(argv[0], &st); }''' + +FRAGMENT_READLINK='''#include +char buf[100]; +int main(int argc, char **argv) { return readlink(argv[0], buf, sizeof(buf)); }''' + +def options(opt): + pass + +def configure(conf): + # add unsupported platforms here + if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']: + conf.env.DISABLE_LIBBACKTRACE = True + return + + # win32 has it's own dbghelp-based backtrace, that's why we ship PDBs + if conf.env.COMPILER_CC == 'msvc': + conf.env.DISABLE_LIBBACKTRACE = True + return + + if not conf.path.find_dir('libbacktrace') or not conf.path.find_dir('libbacktrace/config'): + conf.fatal('Can\'t find libbacktrace submodule. Run `git submodule update --init --recursive`.') + return + + conf.define('BACKTRACE_ELF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32) + conf.define('BACKTRACE_XCOFF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32) + conf.define('_ALL_SOURCE', 1) + conf.define('_GNU_SOURCE', 1) + conf.define('_POSIX_PTHREAD_SEMANTICS', 1) + conf.define('_TANDEM_SOURCE', 1) + conf.define('__EXTENSIONS__', 1) + conf.define('_DARWIN_USE_64_BIT_INODE', 1) + conf.define('_LARGE_FILES', 1) + conf.check_large_file(compiler='c', execute=False, mandatory=False) # sets _FILE_OFFSET_BITS + + conf.env.CFLAGS_EXTRAFLAGS = conf.filter_cflags(['-funwind-tables', '-g'], []) + + if conf.filter_cflags(['-frandom-seed=test'], []): + conf.env.HAVE_FRANDOM_SEED = True + + def check_header(hdr): + return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr, 'compiler': 'c'} + + def check_frag(frag, msg, define, **kw): + return dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'compiler': 'c'}, **kw) + + conf.multicheck( + check_header('dlfcn.h'), + check_header('inttypes.h'), + check_header('link.h'), + check_header('sys/link.h'), + check_header('mach-o/dyld.h'), + check_header('memory.h'), + check_header('stdint.h'), + check_header('stdlib.h'), + check_header('strings.h'), + check_header('string.h'), + check_header('sys/ldr.h'), + check_header('sys/mman.h'), + check_header('sys/stat.h'), + check_header('sys/types.h'), + check_header('tlhelp32.h'), + check_header('unistd.h'), + check_header('windows.h'), + + check_frag(FRAGMENT_ATOMIC, '__atomic extensions', 'HAVE_ATOMIC_FUNCTIONS'), + check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'), + check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'), + check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'), + check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']), + check_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']), + check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'), + check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'), + check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'), + check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC', 'KERN_PROC and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC'), + check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC_ARGS'), + check_frag(FRAGMENT_LOADQUERY, 'loadquery function', 'HAVE_LOADQUERY'), + check_frag(FRAGMENT_LSTAT, 'lstat function', 'HAVE_LSTAT'), + check_frag(FRAGMENT_READLINK, 'readlink function', 'HAVE_READLINK'), + +# {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False}, +# {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False}, +# {'lib':'zstd', 'define_name':'HAVE_ZSTD', 'uselib_store':'zstd', 'msg':'... zstd library', 'mandatory':False}, + msg='Checking for in parallel' + ) + + conf.env[DEFKEYS].sort() + + conf.write_config_header() + + conf.define('BACKTRACE_SUPPORTED', 1) + conf.define('BACKTRACE_USES_MALLOC', 0) + conf.define('BACKTRACE_SUPPORTS_THREADS', 1) + conf.define('BACKTRACE_SUPPORTS_DATA', conf.env.DEST_BINFMT in ['elf', 'mac-o']) + + conf.write_config_header('backtrace-supported.h') + +@TaskGen.feature('frandomseed') +@TaskGen.after_method('propagate_uselib_vars') +def process_frandom_seed(ctx): + tasks = getattr(ctx, 'compiled_tasks', []) + + for task in tasks: + out = task.outputs[0] + task.env.CFLAGS = list(task.env.CFLAGS) # need a copy + task.env.CFLAGS += ['-frandom-seed=%s' % out.path_from(out.ctx.bldnode)] + +def build(bld): + if bld.env.DISABLE_LIBBACKTRACE: + return + + # we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers + sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c'] + + if bld.env.DEST_BINFMT == 'pe': + sources += ['pecoff.c'] + elif bld.env.DEST_BINFMT == 'mac-o': + sources += ['macho.c'] + elif bld.env.DEST_BINFMT == 'elf': + sources += ['elf.c'] + else: + sources += ['unknown.c'] + + task = bld.stlib( + source = ['libbacktrace/' + i for i in sources], + target = 'backtrace', + features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '', + use = 'EXTRAFLAGS lzma z zstd', + includes = '. libbacktrace/', + export_defines = 'HAVE_LIBBACKTRACE=1', + export_includes = 'libbacktrace/' + ) + diff --git a/3rdparty/libogg/libogg b/3rdparty/libogg/libogg new file mode 160000 index 00000000..7cf42ea1 --- /dev/null +++ b/3rdparty/libogg/libogg @@ -0,0 +1 @@ +Subproject commit 7cf42ea17aef7bc1b7b21af70724840a96c2e7d0 diff --git a/3rdparty/libogg/wscript b/3rdparty/libogg/wscript new file mode 100644 index 00000000..54001788 --- /dev/null +++ b/3rdparty/libogg/wscript @@ -0,0 +1,50 @@ +#! /usr/bin/env python +# encoding: utf-8 + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('libogg') or not conf.path.find_dir('libogg/src'): + conf.fatal('Can\'t find libogg submodule. Run `git submodule update --init --recursive`.') + return + + conf.env.INCLUDE_INTTYPES_H = 0 + conf.env.INCLUDE_SYS_TYPES_H = 0 + conf.env.INCLUDE_STDINT_H = 0 + + if conf.check_cc(header_name='inttypes.h', mandatory = False): + conf.env.INCLUDE_INTTYPES_H = 1 + elif conf.check_cc(header_name='sys/types.h', mandatory = False): + conf.env.INCLUDE_SYS_TYPES_H = 1 + elif conf.check_cc(header_name='stdint.h', mandatory = False): + conf.env.INCLUDE_STDINT_H = 1 + +def build(bld): + sources = bld.path.ant_glob([ + 'libogg/src/*.c' + ]) + + bld( + features = 'subst', + name = 'libogg_config_types', + source = 'libogg/include/ogg/config_types.h.in', + target = 'libogg/include/ogg/config_types.h', + INCLUDE_INTTYPES_H = bld.env.INCLUDE_INTTYPES_H, + INCLUDE_SYS_TYPES_H = bld.env.INCLUDE_SYS_TYPES_H, + INCLUDE_STDINT_H = bld.env.INCLUDE_STDINT_H, + SIZE16 = 'int16_t', + USIZE16 = 'uint16_t', + SIZE32 = 'int32_t', + USIZE32 = 'uint32_t', + SIZE64 = 'int64_t', + USIZE64 = 'uint64_t' + ) + + bld.stlib( + source = sources, + target = 'ogg', + use = 'libogg_config_types', + includes = 'libogg/include/', + export_includes = 'libogg/include/' + ) diff --git a/3rdparty/maintui b/3rdparty/maintui new file mode 160000 index 00000000..4b7595b6 --- /dev/null +++ b/3rdparty/maintui @@ -0,0 +1 @@ +Subproject commit 4b7595b6fa3d52d1a30fcc7f848bf177b625d98a diff --git a/3rdparty/mainui b/3rdparty/mainui index 14c0922a..0f310046 160000 --- a/3rdparty/mainui +++ b/3rdparty/mainui @@ -1 +1 @@ -Subproject commit 14c0922af7a0dfcbb82f0979453a1fcca1d817f7 +Subproject commit 0f310046fbf807a0baa5864ab681311276c543d7 diff --git a/3rdparty/nanogl b/3rdparty/nanogl index 5a22eab5..c75f1326 160000 --- a/3rdparty/nanogl +++ b/3rdparty/nanogl @@ -1 +1 @@ -Subproject commit 5a22eab53433dcd0d27110166ae33b91d188e342 +Subproject commit c75f1326f9cd1afe0ab083741f34b21b75d62867 diff --git a/3rdparty/opus/opus b/3rdparty/opus/opus index 997fdf54..82ac57d9 160000 --- a/3rdparty/opus/opus +++ b/3rdparty/opus/opus @@ -1 +1 @@ -Subproject commit 997fdf54e781ae1c04dee42018f35388a04fe483 +Subproject commit 82ac57d9f1aaf575800cf17373348e45b7ce6c0d diff --git a/3rdparty/opus/wscript b/3rdparty/opus/wscript index 1f6c9af4..28bb5d17 100644 --- a/3rdparty/opus/wscript +++ b/3rdparty/opus/wscript @@ -3,6 +3,37 @@ import os +FRAGMENT_VLA='''int main (int argc, char **argv) { + char a[argc]; + a[sizeof( a ) - 1] = 0; + int N; + return a[0]; +}''' + +FRAGMENT_ALLOCA_H='''#include +int main (void) { + int foo=10; + int * array = alloca(foo); +}''' + +FRAGMENT_STDLIB_H='''#include +#include +int main (void) { + int foo=10; + int * array = alloca(foo); +}''' + +FRAGMENT_LRINT='''#include +#include +int main (int argc, char **argv) { + return lrint%s((%s)atof(argv[1])); +}''' + +# assuming MSVC always enables NEON +FRAGMENT_NEON='''#if !defined __ARM_NEON__ && !defined _MSC_VER +#error +#endif''' + def options(opt): pass @@ -11,6 +42,61 @@ def configure(conf): conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.') return + if conf.check_cc(fragment=FRAGMENT_LRINT % ('', 'double'), msg = 'Checking for C99 lrint', use = 'M', mandatory = False): + conf.define('HAVE_LRINT', 1) + + if conf.check_cc(fragment=FRAGMENT_LRINT % ('f', 'float'), msg = 'Checking for C99 lrintf', use = 'M', mandatory = False): + conf.define('HAVE_LRINTF', 1) + + # Check for C99 variable-size arrays, or alloca() as fallback + if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False): + conf.define('VAR_ARRAYS', 1) + elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False): + conf.define('USE_ALLOCA', 1) + conf.define('HAVE_ALLOCA_H', 1) + elif conf.check_cc(fragment=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False): + conf.define('USE_ALLOCA', 1) + + if conf.env.DEST_CPU in ['thumb', 'arm']: + if conf.check(header_name='arm_neon.h', mandatory = False): + if conf.check(fragment=FRAGMENT_NEON, msg = 'Checking if compiler enabled NEON', mandatory = False): + conf.env.HAVE_NEON = True + conf.define('OPUS_ARM_MAY_HAVE_NEON_INTR', 1) + conf.define('OPUS_ARM_PRESUME_NEON', 1) + elif conf.env.DEST_CPU.startswith('x86'): + if conf.check(header_name='xmmintrin.h', mandatory = False): + if conf.env.COMPILER_CC != 'msvc': + conf.env.CFLAGS += ['-msse'] + conf.define('OPUS_X86_MAY_HAVE_SSE', 1) + if conf.env.DEST_SIZEOF_VOID_P > 4: + conf.define('OPUS_X86_PRESUME_SSE', 1) + + if conf.check(header_name='emmintrin.h', mandatory = False): + if conf.env.COMPILER_CC != 'msvc': + conf.env.CFLAGS += ['-msse2'] + conf.define('OPUS_X86_MAY_HAVE_SSE2', 1) + if conf.env.DEST_SIZEOF_VOID_P > 4: + conf.define('OPUS_X86_PRESUME_SSE2', 1) + + # on x86_64 we enable both SSE and SSE2, so RTCD can be disabled (it's actually doesn't even build with RTCD enabled) + if conf.env.DEST_SIZEOF_VOID_P == 4: + if conf.check(header_name='cpuid.h', mandatory=False): + conf.define('CPU_INFO_BY_C', 1) + else: + conf.define('CPU_INFO_BY_ASM', 1) + + conf.define('OPUS_HAVE_RTCD', 1) + +# if conf.check(header_name='smmintrin.h', mandatory = False): +# if conf.env.COMPILER_CC != 'msvc': +# conf.env.CFLAGS += ['-msse4.1'] +# conf.define('OPUS_X86_MAY_HAVE_SSE4_1', 1) + +# if conf.check(header_name='immintrin.h', mandatory = False): +# if conf.env.COMPILER_CC != 'msvc': +# conf.env.CFLAGS += ['-mavx'] +# conf.define('OPUS_X86_MAY_HAVE_AVX', 1) + # TODO: ARM/x86 intrinsics detection # TODO: maybe call autotools/cmake/meson instead? @@ -26,8 +112,23 @@ def build(bld): 'opus/src/opus_compare.c', 'opus/celt/opus_custom_demo.c' ]) - includes = ['opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/'] - defines = ['USE_ALLOCA', 'OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.3.1"', 'CUSTOM_MODES'] + + includes = ['opus', 'opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/'] + + if bld.env.DEST_CPU in ['thumb', 'arm']: + if bld.env.HAVE_NEON: + sources += bld.path.ant_glob(['opus/silk/arm/*.c', 'opus/celt/arm/*.c']) + includes += ['opus/silk/arm', 'opus/celt/arm'] + elif bld.env.DEST_CPU.startswith('x86'): + sources += ['opus/silk/x86/x86_silk_map.c', + 'opus/celt/x86/pitch_sse.c', + 'opus/celt/x86/pitch_sse2.c', + 'opus/celt/x86/vq_sse2.c', + 'opus/celt/x86/x86_celt_map.c', + 'opus/celt/x86/x86cpu.c'] + includes += ['opus/silk/x86', 'opus/celt/x86'] + + defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES', 'ENABLE_HARDENING'] bld.stlib( source = sources, @@ -35,6 +136,5 @@ def build(bld): features = 'c', includes = includes, defines = defines, - subsystem = bld.env.MSVC_SUBSYSTEM, export_includes = ['opus/include/'] ) diff --git a/3rdparty/opusfile/opusfile b/3rdparty/opusfile/opusfile new file mode 160000 index 00000000..3ecc22aa --- /dev/null +++ b/3rdparty/opusfile/opusfile @@ -0,0 +1 @@ +Subproject commit 3ecc22aa0a4430f61c2403d57370a089536d197a diff --git a/3rdparty/opusfile/wscript b/3rdparty/opusfile/wscript new file mode 100644 index 00000000..3b81c230 --- /dev/null +++ b/3rdparty/opusfile/wscript @@ -0,0 +1,37 @@ +#! /usr/bin/env python +# encoding: utf-8 + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('opusfile') or not conf.path.find_dir('opusfile/src'): + conf.fatal('Can\'t find opusfile submodule. Run `git submodule update --init --recursive`.') + return + + if conf.env.COMPILER_CC == 'msvc': + conf.define('_CRT_SECURE_NO_WARNINGS', 1) + conf.define('_CRT_SECURE_NO_DEPRECATE', 1) + conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1) + + if conf.env.DEST_OS == 'android': + # HACKHACK: set it to 32 here because opusfile can't be built on Android SDK < 24 + # with _FILE_OFFSET_BITS 64 (which it sets automatically in src/internal.h) + # we are not (????) relying on this part of the API, so it should be harmless + conf.define('_FILE_OFFSET_BITS', 32) + +def build(bld): + sources = [ + 'opusfile/src/info.c', + 'opusfile/src/internal.c', + 'opusfile/src/opusfile.c', + 'opusfile/src/stream.c' + ] + + bld.stlib( + source = sources, + target = 'opusfile', + includes = 'opusfile/include/', + use = 'ogg opus', + export_includes = 'opusfile/include/' + ) diff --git a/3rdparty/vgui_support b/3rdparty/vgui_support index 63c134f1..36219aed 160000 --- a/3rdparty/vgui_support +++ b/3rdparty/vgui_support @@ -1 +1 @@ -Subproject commit 63c134f188e7c0891927f5a4149f4444b43b0be8 +Subproject commit 36219aedfc16d1fc982dd7ffaac2eec5ef40b8f8 diff --git a/3rdparty/vorbis/vorbis-src b/3rdparty/vorbis/vorbis-src new file mode 160000 index 00000000..bb4047de --- /dev/null +++ b/3rdparty/vorbis/vorbis-src @@ -0,0 +1 @@ +Subproject commit bb4047de4c05712bf1fd49b9584c360b8e4e0adf diff --git a/3rdparty/vorbis/wscript b/3rdparty/vorbis/wscript new file mode 100644 index 00000000..6f8755e1 --- /dev/null +++ b/3rdparty/vorbis/wscript @@ -0,0 +1,71 @@ +#! /usr/bin/env python +# encoding: utf-8 + +FRAGMENT_MEMORY_H='''#include +int main (void) { + return 0; +}''' + +FRAGMENT_ALLOCA_H='''#include +int main (void) { + int foo=10; + int * array = alloca(foo); +}''' + +def options(opt): + pass + +def configure(conf): + if not conf.path.find_dir('vorbis-src') or not conf.path.find_dir('vorbis-src/lib'): + conf.fatal('Can\'t find Vorbis submodule. Run `git submodule update --init --recursive`.') + return + + if conf.check_cc(fragment=FRAGMENT_MEMORY_H, msg = 'Checking for memory.h header', mandatory = False): + conf.define('USE_MEMORY_H', 1) + + conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False) + + if conf.env.COMPILER_CC == 'msvc': + conf.define('_CRT_SECURE_NO_WARNINGS', 1) + conf.define('_CRT_SECURE_NO_DEPRECATE', 1) + conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1) + +def build(bld): + libvorbis_sources = [ + 'vorbis-src/lib/mdct.c', + 'vorbis-src/lib/smallft.c', + 'vorbis-src/lib/block.c', + 'vorbis-src/lib/envelope.c', + 'vorbis-src/lib/window.c', + 'vorbis-src/lib/lsp.c', + 'vorbis-src/lib/lpc.c', + 'vorbis-src/lib/analysis.c', + 'vorbis-src/lib/synthesis.c', + 'vorbis-src/lib/psy.c', + 'vorbis-src/lib/info.c', + 'vorbis-src/lib/floor1.c', + 'vorbis-src/lib/floor0.c', + 'vorbis-src/lib/res0.c', + 'vorbis-src/lib/mapping0.c', + 'vorbis-src/lib/registry.c', + 'vorbis-src/lib/codebook.c', + 'vorbis-src/lib/sharedbook.c', + 'vorbis-src/lib/lookup.c', + 'vorbis-src/lib/bitrate.c' + ] + + bld.stlib( + source = libvorbis_sources, + target = 'vorbis', + includes = 'vorbis-src/include/', + use = 'ogg', + export_includes = 'vorbis-src/include/' + ) + + bld.stlib( + source = 'vorbis-src/lib/vorbisfile.c', + target = 'vorbisfile', + includes = 'vorbis-src/include/', + use = 'vorbis', + export_includes = 'vorbis-src/include/' + ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 240b95b8..ea7f397d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,3 +45,8 @@ Tags can be any: subsystem, simple feature name or even just a filename, without extension. Just keep them always same, it helps keep history clean and commit messages short. +## LLM-based tools usage. + +While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay. + +Whether 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. diff --git a/Documentation/bug-compatibility.md b/Documentation/bug-compatibility.md index 27ac9849..ab3baa9e 100644 --- a/Documentation/bug-compatibility.md +++ b/Documentation/bug-compatibility.md @@ -1,17 +1,18 @@ # Bug-compatibility in Xash3D FWGS -Xash3D 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. +Xash3D 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. At this time, we only have implemented GoldSrc bug-compatibility. It can be enabled with `-bugcomp` command line switch. +When `-bugcomp` is specified without argument, it enables everything. This behavior might be changed or removed in future versions. + +When `-bugcomp` is specified with argument, it interpreted as flags separated with `+`. This way it's possible to combine multiple levels of bug-compatibility. + ## GoldSrc bug-compatibility -### Emulated bugs - -* `pfnPEntityOfEntIndex` in GoldSrc returns NULL for last player due to incorrect player index comparison - -### Games and mods that require this - -* Counter-Strike: Condition Zero - Deleted Scenes +| Flag | Description | Games that require this flag | +| ------- | ----------- | ---------------------------- | +| `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 | +| `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol.
Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods | +| `sp_attn_none` | Makes sounds with attenuation zero spatialized, i.e. have a stereo effect. | Possibly, every game that was made for GoldSrc. | +| `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) | diff --git a/Documentation/cross-compiling-for-windows-with-wine.md b/Documentation/cross-compiling-for-windows-with-wine.md new file mode 100644 index 00000000..99a4c63f --- /dev/null +++ b/Documentation/cross-compiling-for-windows-with-wine.md @@ -0,0 +1,14 @@ +# Cross-compiling for Windows with Wine + +This can be useful to test engine in Wine without using virtual machines or dual-booting to Windows. + +0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part) +1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain +2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot` +3. Run `PKGCONFIG=/bin/false ./waf configure -T --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual. +4. .. other typical steps to build from console ... + +> [!NOTE] +> Notice the usage of PKGCONFIG=/bin/false here. We're disabling pkg-config so we don't accidentally pull +> system-wide dependencies and force building them from source. In future builds we might set custom +> directory to pull dependencies from, like ffmpeg... diff --git a/Documentation/debugging-using-minidumps.md b/Documentation/debugging-using-minidumps.md new file mode 100644 index 00000000..0f0fad44 --- /dev/null +++ b/Documentation/debugging-using-minidumps.md @@ -0,0 +1,10 @@ +# Debugging your mod using minidump files (Windows only) +Minidump 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. +There are short algorithm, explaining how to use this debugging instrument: + +1. User starts your mod with `-minidumps` startup parameter +2. 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 +3. User packs this minidump file to .zip/.7z archive, and somehow sends it to developer +4. 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 + +You 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). diff --git a/Documentation/donate.md b/Documentation/donate.md new file mode 100644 index 00000000..5a4622d2 --- /dev/null +++ b/Documentation/donate.md @@ -0,0 +1,37 @@ +# Developers donation page + +On this page you can find links where you can support each developer individually, who has provided public sponsorship information. + +## [a1batross](https://github.com/a1batross) + +Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of non-commercial Flying With Gauss organization. + +* [Boosty page](https://boosty.to/a1ba) + +## [nekonomicon](https://github.com/nekonomicon) + +[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). + +* [Boosty page](https://boosty.to/nekonomicon) + +## [Velaron](https://github.com/Velaron) + +[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). + +* [Boosty page](https://boosty.to/velaron) + +## [SNMetamorph](https://github.com/SNMetamorph) + +[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). + +* [Boosty page](https://boosty.to/snmetamorph) +* [Other donation methods](https://snmetamorph.github.io/donate) + +## [$_Vladislav](https://github.com/Vladislav4KZ) + +[YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester, +editor 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). + +Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods. + +* [Boosty page](https://boosty.to/rasstaman1337) \ No newline at end of file diff --git a/Documentation/environment-variables.md b/Documentation/environment-variables.md new file mode 100644 index 00000000..35b4d5cc --- /dev/null +++ b/Documentation/environment-variables.md @@ -0,0 +1,21 @@ +## Environment variables + +#### Xash3D FWGS + +The engine respects these environment variables: + +| Variable | Type | Description | +| --------------------- | ---------- | ----------- | +| `XASH3D_GAME` | _string_ | Overrides default game directory. Ignored if `-game` command line argument is set | +| `XASH3D_BASEDIR` | _string_ | Sets path to base (root) directory, instead of current working directory | +| `XASH3D_RODIR` | _string_ | Sets path to read-only base (root) directory. Ignored if `-rodir` command line argument is set | +| `XASH3D_EXTRAS_PAK1` | _string_ | Archive file from specified path will be added to virtual filesystem search path in the lowest possible priority | +| `XASH3D_EXTRAS_PAK2` | _string_ | Similar to `XASH3D_EXTRAS_PAK1` but next to it in priority list | + +Environment variables NOT listed in the table above are used internally, and aren't considered as stable interface. + +#### mdldec + +| Variable | Type | Description | +| --------------------- | ---------- | ----------- | +| `MDLDEC_ACT_PATH` | _string_ | If set, will read activities list from this path | diff --git a/Documentation/extensions/addon-folders.md b/Documentation/extensions/addon-folders.md new file mode 100644 index 00000000..0611c2d5 --- /dev/null +++ b/Documentation/extensions/addon-folders.md @@ -0,0 +1,16 @@ +# Addon folders in Xash3D FWGS + +Xash3D 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. + +Below is the mounts map, in order of precedence from least important to most important. + +|--------------------|------| +| Directory | Note | +|--------------------|------| +| `$game/downloaded` | Always added. Used to store server downloads.| +| `$game` | This is the game directory. | +| `$game/custom` | Always added. Used for user modifications content. | +| `$game_hd` | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. | +| `$game_addon` | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. | +| `$game_lv` | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. | +| `$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. | diff --git a/Documentation/extensions/entity-tools.md b/Documentation/extensions/entity-tools.md new file mode 100644 index 00000000..a17dbd24 --- /dev/null +++ b/Documentation/extensions/entity-tools.md @@ -0,0 +1,152 @@ +# Entity tools + +For 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. +To get more information about it, check another according page related to console scripting. + +## Commands description + +### ent_create +Create entity with specified classname and key/values. + +`ent_create ...` + +For example: + +`ent_create monster_zombie targetname zomb1` + +After creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description. + +### ent_fire + +Makes some actions on entity. + +`ent_fire ` + +#### Available commands: + +Set fields (only set entity field, does not call any functions) +* health +* gravity +* movetype +* solid +* rendermode +* rendercolor (vector) +* renderfx +* renderamt +* hullmin (vector) +* hullmax (vector) + +Actions +* rename: set entity targetname +* settarget: set entity target (only targetnames) +* setmodel: set entity model (does not update) +* set: set key/value by server library + * See game FGD to get list. + * command takes two arguments +* touch: touch entity by current player. +* use: use entity by current player. +* movehere: place entity in player fov. +* drop2floor: place entity to nearest floor surface +* moveup: move entity to 25 units up +* moveup (value): move by y axis relatively to specified value + +Flags (set/clear specified flag bit, arg is bit number): +* setflag +* clearflag +* setspawnflag +* clearspawnflag + +### ent_info +Print information about entity by identificator. + +`ent_info ` + +### ent_getvars +Set client cvars containing entity information (useful for [scripting](extensions/console-scripting.md)) and then calls ent_last_cb. + +`ent_getvars ` + +These cvars are set: +``` +ent_last_name +ent_last_num +ent_last_inst +ent_last_origin +ent_last_class +``` + +### ent_list +Print short information about antities, filtered by pattern. + +`ent_list ` + +## Syntax description + +#### \ +* !cross: entity under aim +* !\_\: instance code +* Set by ent_getvars command +* Entity index +* Targetname pattern + +#### \ + +Pattern is like identificator, but may filter many entities by classname. + +#### (vector) + +Used by ent_fire command. vector means three float values, entered without quotes. + +#### key/value + +All entities parameters may be set by specifiing key and value strings. + +Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too. + +Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters. + +If value contains spaces, it must be put in quotes: + +`ent_fire !cross set origin "0 0 0"` + +## Using with scripting + +ent_create and ent_getvars commands are setting cvars on client + +It can be used with ent_last_cb alias that is executed after setting cvars. + +Simple example: + +``` +ent_create weapon_c4 +alias ent_last_cb "ent_fire \$ent_last_inst use" +``` + +Use weapon_c4 after creating it. + +Note that you cannot use many different callbacks at the same time. + +You can set entity name by by pattern and create special script, contatning all callbacks. + +Example: + +> example.cfg +``` +alias ent_last_cb exec entity_cb.cfg +ent create \ targetname my_ent1_$name +ent_create \ targetname my_ent2_$name +``` +> entity_cb.cfg +``` +if $ent_last_name == my_ent1_$name +:(ent1 actions) +if $ent_last_name == my_ent2_$name +:(ent2 actions) +``` +Note 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. + +## Server side configuration + +To enable entity tools on server, set sv_enttools_enable to 1 + +To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number. diff --git a/Documentation/extensions/expanded-common-structures.md b/Documentation/extensions/expanded-common-structures.md new file mode 100644 index 00000000..4b72aedc --- /dev/null +++ b/Documentation/extensions/expanded-common-structures.md @@ -0,0 +1,8 @@ +# Expanded structures that used by engine and mods +To make porting and developing mods on 64-bit platforms less painful, we decided to expand size of several structures. +This 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). +| Structure name | Locates in file | Original size on 64-bit | Current size on 64-bit | +|----------------|-----------------|-------------------------|------------------------| +|`mfaceinfo_t` | `common/com_model.h` | 176 bytes | 304 bytes | +|`decal_s` | `common/com_model.h` | 72 bytes | 88 bytes | +|`mextrasurf_t` | `common/com_model.h` | 376 bytes | 504 bytes | diff --git a/Documentation/extensions/library-naming.md b/Documentation/extensions/library-naming.md index 286ab461..657db484 100644 --- a/Documentation/extensions/library-naming.md +++ b/Documentation/extensions/library-naming.md @@ -31,11 +31,17 @@ Issue #0. Inconsistency between ABI and Q_buildarch.\ Resolution: 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. Issue #1: Build-system integration.\ -Resolution: implemented as [LibraryNaming.cmake](https://github.com/FWGS/hlsdk-xash3d/blob/master/cmake/LibraryNaming.cmake) and [library_naming.py](https://github.com/FWGS/hlsdk-xash3d/blob/master/scripts/waifulib/library_naming.py) extensions, see +Resolution: 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 Issue #2(related to #0): Which ARM flavours we actually need to handle?\ Resolution: Little-endian only, as there is no known big-endian ARM platforms in the wild. Architecture is coded this way: * ```armvxy```, where `x` is ARM instruction set level and `y` is hard-float ABI presence: `hf` where hard float ABI used, otherwise `l`. +Issue #3: Some mods (like The Specialists, Tyrian, ...) already apply suffixes _i386, _i686 to the gamedll path:\ +Resolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme. + See discussion: https://github.com/FWGS/xash3d-fwgs/issues/39 + +Issue #4: When distributing game libraries on Android inside an APK, they couldn't be loaded. +Resolution: 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.. diff --git a/Documentation/extensions/mp3-loops.md b/Documentation/extensions/mp3-loops.md new file mode 100644 index 00000000..59fd6efd --- /dev/null +++ b/Documentation/extensions/mp3-loops.md @@ -0,0 +1,22 @@ +## Looping MP3 extension + +It 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. + +### Example with foobar2000 +1. Open Foobar2000 +2. Add your .mp3 file to playlist +3. Right click to newly added file and select Properties +4. In Metadata tab, at the bottom of the table, select "+add new" +5. In newly added line replace `input field name` with `LOOP_START` (without any symbols). +6. Press Tab and enter loop time point in raw samples. For example, `0` will replay sound file from beginning to end indefinitely. + +### Possible alternatives +1. 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.) +2. Vorbis looping through comment. Engine doesn't support Vorbis but this extension was highly inspired by this hack. + +### Known bugs and limitations +1. At this time using MP3 as SFX requires complete decoding. This can cause noticeable stutters, so keep MP3 file length in mind. +2. We deliberately only support modern ID3v2.3 and ID3v2.4 tags. Using ID3v1 is not possible. + + + diff --git a/Documentation/extensions/native-object.md b/Documentation/extensions/native-object.md new file mode 100644 index 00000000..91afbd24 --- /dev/null +++ b/Documentation/extensions/native-object.md @@ -0,0 +1,25 @@ +# GetNativeObject API + +To 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. + +It's defined like this: + +``` +void *pfnGetNativeObject( const char *name ); +``` + +#### Cross-platform objects + +Only these objects are guaranteed to be available on all targets. + +| Object name | Interface | +|-------------|-----------| +| `VFileSystem009` | Provides C++ interface to filesystem, binary-compatible with Valve's VFileSystem009. | +| `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`. | + +#### Android-specific objects + +| Object name | Interface | +|-------------|-----------| +| `JNIEnv` | Allows interfacing with Java Native Interface. | +| `ActivityClass` | Returns JNI object for engine Android activity class. | diff --git a/Documentation/extensions/sounds.lst.md b/Documentation/extensions/sounds.lst.md new file mode 100644 index 00000000..ad44d4c8 --- /dev/null +++ b/Documentation/extensions/sounds.lst.md @@ -0,0 +1,65 @@ +# sounds.lst.md + +Using sounds.lst located in scripts folder, modder can override some of the hardcoded sounds in temp entities and server physics. + +File format: +``` + +{ + + + +} + + +``` + +* Sounds can use any supported sound format (WAV or MP3). +* The path must be relative to the sounds/ folder in the game or base directory root, addon folder, or archive root. +* Groups can be empty or omitted from the file to load no sound. +* Groups can either list a set of files or specify a format string and a range. +* Anything after // will be considered a comment and ignored. +* Behavior is undefined if the group was listed multiple times. + +Currently supported groups are: +|Group name|Usage| +|----------|-----| +|`BouncePlayerShell`|Used for BOUNCE_SHELL tempentity hitsound| +|`BounceWeaponShell`|Used for BOUCNE_SHOTSHELL tempentity hitsound| +|`BounceConcrete`|Used for BOUNCE_CONCRETE tempentity hitsound| +|`BounceGlass`|Used for BOUCNE_GLASS| +|`BounceMetal`|Used for BOUNCE_METAL| +|`BounceFlesh`|Used for BOUNCE_FLESH| +|`BounceWood`|Used for BOUNCE_WOOD| +|`Ricochet`|Used for BOUNCE_SHRAP and ricochet tempentities| +|`Explode`|Used for tempentity explosions| +|`EntityWaterEnter`|Used for entity entering water| +|`EntityWaterExit`|Used for entity exiting water| +|`PlayerWaterEnter`|Used for player entering water| +|`PlayerWaterExit`|Used for player exiting water| + +## Example + +This example is based on defaults sounds used in Half-Life: + +``` +BouncePlayerShell "player/pl_shell%d.wav" 1 3 +BounceWeaponShell "weapons/sshell%d.wav" 1 3 +BounceConcrete "debris/concrete%d.wav" 1 3 +BounceGlass "debris/glass%d.wav" 1 4 +BounceMetal "debris/metal%d.wav" 1 6 +BounceFlesh "debris/flesh%d.wav" 1 7 +BounceWood "debris/wood%d.wav" 1 4 +Ricochet "weapons/ric%d.wav" 1 5 +Explode "weapons/explode%d.wav" 3 5 +EntityWaterEnter "player/pl_wade%d.wav" 1 4 +EntityWaterExit "player/pl_wade%d.wav" 1 4 +PlayerWaterEnter +{ + "player/pl_wade1.wav" +} +PlayerWaterExit +{ + "player/pl_wade2.wav" +} +``` diff --git a/Documentation/gameinfo.md b/Documentation/gameinfo.md new file mode 100644 index 00000000..526ce93f --- /dev/null +++ b/Documentation/gameinfo.md @@ -0,0 +1,120 @@ +# Game definition and information file + +gameinfo.txt is an essential part of any Xash3D based game. It allows basic customization for games creators, like setting game title, DLL paths, etc. + +This 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. + +## gameinfo.txt syntax + +* gameinfo.txt is a simple list of keys and values, separated by newline. +* You can add single line comments using double slashes (//). +* Keys can accept integer, float, string or boolean values. +* Boolean keys use 0 as false and 1 as true value. +* 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. + +The example: +``` +// this is a comment :) +// this is another comment +some_integer_key 123 + +this_is_float_key 13.37 // optional comment + +enable_feature 1 // boolean, 1 to enable, 0 to disable + +example_string_key string_value +example_spaces "string with spaces" +example_title "Fate\\Stay Night" // engine will parse it as Fate\Stay Night +``` + +## gameinfo.txt keys + +This is a list of all gameinfo.txt keys supported by the engine. Be aware that the engine will silently skip all unrecognized keys. + +| Key | Type | Default value | Description | +| ---------------- | ---------- | --------------- | ----------- | +| `ambient0` | string | Empty string | Automatic ambient sound | +| `ambient1` | string | Empty string | Automatic ambient sound | +| `ambient2` | string | Empty string | Automatic ambient sound | +| `ambient3` | string | Empty string | Automatic ambient sound | +| `basedir` | string | `valve` | Game base directory, used to share assets between games | +| `date` | string | Empty string | Game release date. Unused. | +| `dllpath` | string | `cl_dlls` | Game DLL path. Engine will search custom DLLs (client or menu, for example) in this directory, except `gamedll`, see below. | +| `fallback_dir` | string | Empty string | Additional game base directory | +| `gamedir` | string | Current gamedir | Game directory, ignored in FWGS, as game directory is defined by the game directory name | +| `gamedll` | string | `dlls/hl.dll` | Game server DLL for 32-bit x86 Windows (see LibraryNaming.md for details) | +| `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. | +| `icon` | string | `game.ico` | Game icon. Engine will automatically append .ico and may automatically switch to .tga icon as well | +| `max_beams` | integer | 128 | Beams limit, 64 min, 512 max | +| `max_edicts` | integer | 900 | Entities limit, 600 min, 8192 max (protocol limit). In FWGS, minimum is 64. | +| `max_particles` | integer | 4096 | Particles limit, 1024 min, 131072 max | +| `max_tempents` | integer | 500 | Temporary entities limit. 300 min, 2048 max | +| `mp_entity` | string | `info_player_deathmatch` | Entity used to mark maps as multiplayer | +| `mp_filter` | string | Empty string | When set, used to filter multiplayer maps instead of `mp_entity`.
If the map name starts with the same characters as this filter, it's considered a multiplayer map | +| `nomodels` | boolean | 0 | When set to 1, disallows changing player model in UI | +| `noskills` | boolean | 0 | When set to 1, disallows selection of game difficulty | +| `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. | +| `size` | integer | 0 | Game directory size in bytes, used in Change Game dialog only | +| `startmap` | string | `c0a0` | The name of the map used in new game | +| `sp_entity` | string | `info_player_start` | Entity used to mark map as single player. Used in map validation | +| `title` | string | `New Game` | Game title, used in window title, default server name, etc. | +| `trainmap` | string | `t0a0` | The name of the training map (Hazard Course) | +| `type` | string | Empty string | Game type, used in Change Game UI. | +| `url_info` | string | Empty string | Game homepage URL, used in Change Game UI | +| `url_update` | string | Empty string | Game updates URL, used in Settings UI | +| `version` | float | 1.0 | Game version, used in Change Game dialog and in server info | + +## FWGS-specific gameinfo.txt keys + +These strings are specific to Xash3D FWGS. + +| Key | Type | Default value | Description | +| ----------------------- | ---------- | ------------------------ | ----------- | +| `animated_title` | boolean | 0 | Use animated title in main menu (WON Half-Life logo.avi imitation from Half-Life 25-th anniversary update) +| `autosave_aged_count` | integer | 2 | Auto saves limit used in saves rotation | +| `gamedll_linux` | string | Generated from `gamedll` | Game server DLL for 32-bit x86 Linux (see LibraryNaming.md for details) | +| `gamedll_osx` | string | Generated from `gamedll` | Game server DLL for 32-bit x86 macOS (see LibraryNaming.md for details) | +| `hd_background` | boolean | 0 | Use HD background for main menu (Half-Life 25-th anniversary update) | +| `internal_vgui_support` | boolean | 0 | Only for programmers! Required to be set as 1 for PrimeXT!
When set to 1, the engine will not load vgui_support DLL, as VGUI support is done (or intentionally ignored) on the game side. | +| `render_picbutton_text` | boolean | 0 | When set to 1, the UI will not use prerendered `btns_main.bmp` and dynamically render them instead | +| `quicksave_aged_count` | integer | 2 | Quick saves limit used in saves rotation | +| `demomap` | string | Empty string | The name of the demo chapter map (Half-Life Uplink) | + +## Note on GoldSrc liblist.gam support + +As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\ +Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam. + +> [!NOTE] +> 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. + +For game creators who plan supporting only Xash3D, using this file is not recommended. + +The 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. + +| `liblist.gam` key | `gameinfo.txt` key | Note | +| ----------------- | ------------------ | ---- | +| `animated_title` | `animated_title` | | +| `edicts` | `max_edicts` | | +| `fallback_dir` | `fallback_dir` | | +| `game` | `title` | | +| `gamedir` | `gamedir` | | +| `gamedll` | `gamedll` | | +| `gamedll_linux` | `gamedll_linux` | | +| `gamedll_osx` | `gamedll_osx` | | +| `hd_background` | `hd_background` | | +| `icon` | `icon` | | +| `mpentity` | `mp_entity` | | +| `mpfilter` | `mp_filter` | | +| `nomodels` | `nomodels` | | +| `secure` | `secure` | In GoldSrc it's used to mark the multiplayer game as anti-cheat enabled.
Original Xash3D misinterprets its value for disallowing console and developer mode.
FWGS ignores this key but preserves for compatibility. | +| `startmap` | `startmap` | | +| `size` | `size` | | +| `trainingmap` | `trainmap` | | +| `trainmap` | `trainmap` | | +| `type` | `type` & `gamemode`| In `liblist.gam` this key works as both `type` and `gamemode`.
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`.
Any custom value will mark the game as both SP and MP compatible, and type is set to whatever custom value. | +| `url_dl` | `url_update` | | +| `url_info` | `url_info` | | +| `version` | `version` | | + + diff --git a/Documentation/goldsrc-protocol-support.md b/Documentation/goldsrc-protocol-support.md new file mode 100644 index 00000000..76ab4b4d --- /dev/null +++ b/Documentation/goldsrc-protocol-support.md @@ -0,0 +1,15 @@ +# Support for GoldSrc network protocol +This 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. + +For connecting to GoldSrc-based servers, use this command: +``` +connect ip:port gs +``` + +But keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion. +Without this requirement, you will just get "Steam validation rejected" error on connecting. + +That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it). + +Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be +solved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks. diff --git a/Documentation/hd-textures.md b/Documentation/hd-textures.md new file mode 100644 index 00000000..3af012f9 --- /dev/null +++ b/Documentation/hd-textures.md @@ -0,0 +1,40 @@ +### HD (external) textures support + +Xash3D supports loading texture replacements in TGA format for almost all types of models in the game, except alias models at this time. + +Textures are expected to be located at: +* `modfolder/materials/` - for a specific map +* `modfolder/materials/common` - common for all maps +* `modfolder/materials/decals` - for decals +* `modfolder/materials/models/` - for models (texture name must match the internal texture name in the model) + +Support for high-resolution textures is enabled setting `host_allow_materials` cvar to `1` or in the menu, in "Video options" section. + +#### Xash3D FWGS additions + +In addition to paths above, Xash3D FWGS checks following paths: + +* `modfolder/materials/sprites/` - for sprites, except HUD sprites + +Also, 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: + +``` +Looking for replacement... () +``` + +Status codes: +* `OK` - texture replacement file was found and loaded into GPU memory successfully +* `FAIL` - texture file was found but hasn't been parsed or loaded successfully. Refer to engine log for more details. +* `MISS` - texture file wasn't found + +Example: +``` +Looking for maps/bounce.bsp:!waterblue tex replacement...OK (materials/common/!waterblue.tga) +Looking for maps/bounce.bsp:!waterblue_luma tex replacement...MISS (not found) +Looking for {shot2 decal replacement...MISS (materials/decals/{shot2.tga) +Looking for {shot4 decal replacement...MISS (materials/decals/{shot4.tga) +Looking for {shot3 decal replacement...MISS (materials/decals/{shot3.tga) +Looking for models/gman tex replacement...FAIL (materials/models/gman/GMan_Case1.tga) +Looking for models/gman tex replacement...FAIL (materials/models/gman/inside_1.tga) +``` + diff --git a/Documentation/images/editor.jpg b/Documentation/images/editor.jpg new file mode 100644 index 00000000..3efb61be Binary files /dev/null and b/Documentation/images/editor.jpg differ diff --git a/Documentation/images/example1.jpg b/Documentation/images/example1.jpg new file mode 100644 index 00000000..fd98017f Binary files /dev/null and b/Documentation/images/example1.jpg differ diff --git a/Documentation/images/example2.jpg b/Documentation/images/example2.jpg new file mode 100644 index 00000000..0212677f Binary files /dev/null and b/Documentation/images/example2.jpg differ diff --git a/Documentation/images/example3.jpg b/Documentation/images/example3.jpg new file mode 100644 index 00000000..de4a27c5 Binary files /dev/null and b/Documentation/images/example3.jpg differ diff --git a/Documentation/images/example4.jpg b/Documentation/images/example4.jpg new file mode 100644 index 00000000..baa05f03 Binary files /dev/null and b/Documentation/images/example4.jpg differ diff --git a/Documentation/images/example5.jpg b/Documentation/images/example5.jpg new file mode 100644 index 00000000..54972c89 Binary files /dev/null and b/Documentation/images/example5.jpg differ diff --git a/Documentation/images/touch-buttons.jpg b/Documentation/images/touch-buttons.jpg new file mode 100644 index 00000000..a905538b Binary files /dev/null and b/Documentation/images/touch-buttons.jpg differ diff --git a/Documentation/images/touch-profiles.jpg b/Documentation/images/touch-profiles.jpg new file mode 100644 index 00000000..68692aed Binary files /dev/null and b/Documentation/images/touch-profiles.jpg differ diff --git a/Documentation/mod-porting-guide.md b/Documentation/mod-porting-guide.md index a2219c2c..a1c37bce 100644 --- a/Documentation/mod-porting-guide.md +++ b/Documentation/mod-porting-guide.md @@ -1,7 +1,40 @@ -# Server porting +# Self-made port +## Compatibility with RISC architectures +### Unaligned access +Unaligned access on **i386** causes only performance penalty, but on **RISC** it can cause unstable work. + +For HLSDK at least you need such patches in util.cpp: + - https://github.com/FWGS/halflife/commit/7bfefe86e35d67867ae7af830ac1fc38f2908360 + - https://github.com/FWGS/hlsdk-portable/commit/617d75545f2ecb9b2d46cc30728dc37c9eb6d35e + +### Signed chars +`char` type defined as **signed** for **x86** and as **unsigned** for **arm**. + +And sometimes such difference can break logic. + +As a solution you can use `signed char` type in code directly or use `-fsigned-char` option for gcc/clang compilers. + +For HLSDK at least you need such [fix](https://github.com/FWGS/hlsdk-portable/commit/1ca34fcb4381682bd517612b530db22a1354a795) in nodes.cpp/.h. + +## Compatibility with 64bit architectures +You need list of patches for Studio Model Render, MAKE_STRING macro and nodes: + - https://github.com/FWGS/hlsdk-portable/commit/d287ed446332e615ab5fb25ca81b99fa14d18a73 + - https://github.com/FWGS/hlsdk-portable/commit/3bce17e3a04f8af10a927a07ceb8ab0f09152ec4 + - https://github.com/FWGS/hlsdk-portable/commit/9ebfc981773ec4c7a89ffe52d9c249e1fbef9634 + - https://github.com/FWGS/hlsdk-portable/commit/00833188dab87ef5746286479ba5aeb9d83b4a0c + - https://github.com/FWGS/hlsdk-portable/commit/4661b5c1a5245b27a5532745c11e44b5540e4172 + - https://github.com/FWGS/hlsdk-portable/commit/2b61380146b1d58a8c465f0e312c061b12bda115 + - https://github.com/FWGS/hlsdk-portable/commit/8ef6cb2427ee16a763103bd3f315f38e2f01cfe2 + +## Mobility API +Xash3D FWGS has special extended interface in `mobility_int.h` which adds some new features like vibration on mobile platforms. + +## Porting server-side code Original valve's server code was compatible with linux and gcc 2.x. + Newer gcc versions have restriction which breaks build. -Now, to make it building with gcc 4.x, you need do following: + +Now, to make it building with gcc 4.x+ or clang, you need to do following: * Go to cbase.h and redefine macros as following ``` #define SetThink( a ) m_pfnThink = static_cast (&a) @@ -18,58 +51,60 @@ Now, to make it building with gcc 4.x, you need do following: * Replace all SetThink(NULL), SetTouch(NULL), setUse(NULL) and SetBlocked(NULL) by ResetThink(), ResetTouch(), ResetUse() and ResetBlocked() * Sometimes you may need to add #include if functions tolower or isspace are missing -# Client porting +## Porting client-side code -## VGUI library - -Valve's client uses vgui library which is available only on x86 systems and has big amount of mistakes in headers. The best and simplest way of porting is removing VGUI dependency at all. -Most singleplayer mods don't really use VGUI at all. It is used in multiplayer only to show score table and MOTD window - -## Porting - -### First strategy: full port - -#### Basic port (Stage 1) -* First, remove all files and headers which use vgui (contains "vgui" in file name). -* After that, try to build it and remove vgui includes. -* Remove all gViewPort usings. -* Remove all CVoiceManager usings (xash3d does not support voice) * Redefine all DLLEXPORT defines as empty field (Place it under _WIN32 macro if you want to keep windows compatibility). -* Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp +* Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp. * Fix CAPS filenames in includes (like STDIO.H, replace by stdio.h). -* Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-xash3d port (cl_util.h) -* Add ctype.h where it is need (tolower, isspace functions) -* Add string.h where it is need (memcpy, strcpy, etc) -* Use in_defs.h from hlsdk_client -* Add scoreboard_stub.cpp and input_stub.cpp from hlsdk-xash3d to fix linking. -Now your client should be able to build and work correctly. Add input_xash3d.cpp from hlsdk-xash3d project to fix input. +* Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-portable port (cl_util.h). +* Add ctype.h where is needed (tolower, isspace functions). +* Add string.h where is needed (memcpy, strcpy, etc). +* Use in_defs.h from hlsdk-portable. +* Add input_xash3d.cpp from hlsdk-portable project to fix input. -#### Multiplayer fix (Stage 2) -Look at hlsdk-xash3d project. +Now your client should be able to build and work correctly. -Main changes are: -* Add MOTD.cpp, scoreboard.cpp and input_xash3d.cpp -* Add missing functions to hud_redraw.cpp, hud.cpp and tri.cpp, fix class defination in hud.h -* Remove duplicate functions from hud.cpp and HOOK_MESSAGE's for it -* Remove +showscores/-showscores hooks from input.h -* Fix cl_util.h +# Porting mod to hlsdk-portable +Look at changes which was made. -### Second way: move mod to hlsdk-xash3d -Look at changes you made in client. +If there are not too much changes (for example, only some weapons was added), add these changes in hlsdk-portable. -If there are not much changes (for example, only some weapons was add), add these changes in hlsdk-xash3d. - -You may use diff with original HLSDK you used and apply it as patch to hlsdk-xash3d. +You can use diff with original HLSDK and apply it as patch to hlsdk-portable. +``` +NOTE: Many an old mods was made on HLSDK 2.0-2.1 and some rare mods on HLSDK 1.0. +So you need to have different versions of HLSDK to make diffs. +Plus different Spirit of Half-Life versions if mod was made on it. +Also, 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. +``` Files must have same line endings (use dos2unix on all files). -I recommend to enable ignoring space changes in diff. -### Writing Makefiles +We recommend to enable ignoring space changes in diff. -Use Makefile from hlsdk-xash3d as Makefile example. +Move all new files to separate directories. -Get .c and .cpp file lists from Visual Studio project and fill SRCS and SRCS_C variables to Makefile. +# Possible replacements for non-portable external dependencies +1. 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: + - [pfnPrimeMusicStream](https://github.com/FWGS/hlsdk-portable/blob/master/engine/cdll_int.h#L293=) engine callback; + - [miniaudio](https://github.com/mackron/miniaudio); + - [phonon](https://community.kde.org/Phonon). -Remove all files containing vgui in name from list, add missing include dirs. +2. If mod uses **OpenGL**, porting code to Xash3D render interface is recommended. + +3. If mod uses **cg.dll**, you can try to port code to [NVFX](https://github.com/tlorach/nvFX). + +4. If mod uses detours, comment code or try to find replacement somehow by yourself. + +# Additional recommendations +1. If mod uses STL, you can replace it with [MiniUTL](https://github.com/FWGS/MiniUTL). +2. Avoid to use dynamic casts to make small size binaries. +3. Avoid to use exceptions to make small size binaries. + +# Writing build scripts + +Use wscript/CMakeLists.txt files from hlsdk-portable as build scripts example. + +Get .c and .cpp file lists from Visual Studio project. + +Add missing include dirs. -Do same for Android.mk if you are building for android. diff --git a/Documentation/musl.md b/Documentation/musl.md new file mode 100644 index 00000000..4ac8f875 --- /dev/null +++ b/Documentation/musl.md @@ -0,0 +1,15 @@ +# Xash3D FWGS on `musl` + +Xash3D 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: + +``` +Host_InitError: can't initialize cl_dlls/client.so: Error relocating valve/cl_dlls/client.so: __sprintf_chk: symbol not found +``` + +... or you know that the game you're running is linked against glibc, you can try using `libgcompat`, like this: + +``` +$ LD_PRELOAD=/lib/libgcompat.so.0 ./xash3d ... +``` + +It 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. diff --git a/Documentation/nat-bypass-usage.md b/Documentation/nat-bypass-usage.md new file mode 100644 index 00000000..fabf363b --- /dev/null +++ b/Documentation/nat-bypass-usage.md @@ -0,0 +1,32 @@ +# NAT bypass feature in Xash3D FWGS +Since 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 +mitigate IPv4 addresses exhaustion. In short, they uses one IPv4 address and doing some tricks with ports to represent many other users behind this address. + +But this leads to a problem for users: they cannot accept direct +incoming 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, +and the server will not show up in servers public list. + +## Is it possible to avoid this problem? +In 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 +on NAT configurations on both server and client side, and there is no way to control it. + +First of all, server should not be behind symmetric NAT. You can check your NAT type on [this page](https://www.checkmynat.com/). +If you get "Symmetric NAT" result in this test, that means you cannot setup publicly available server with internet connection that you are using. + +Here is more detailed scheme of different NAT types compatibility, it explains are users with different NAT types can connect to each other or not. +Client NAT type on rows, server NAT type on columns: + +| *NAT Type* | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric | +| -------------------- | --------- | --------------- | -------------------- | --------- | +| Full Cone | ✔️ | ✔️ | ✔️ | ✔️ | +| Restricted Cone | ✔️ | ✔️ | ✔️ | ✔️ | +| Port Restricted Cone | ✔️ | ✔️ | ✔️ | ❌ | +| Symmetric | ✔️ | ✔️ | ❌ | ❌ | + +## How to use NAT bypass feature? +If 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. + +If 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. + +If 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, +or somehow trying manually to store it, because it has changing address and port. diff --git a/Documentation/not-supported-mod-list-and-reasons-why.md b/Documentation/not-supported-mod-list-and-reasons-why.md index f2ec68a2..d5fd6912 100644 --- a/Documentation/not-supported-mod-list-and-reasons-why.md +++ b/Documentation/not-supported-mod-list-and-reasons-why.md @@ -2,10 +2,19 @@ |Name |Version |Why not working |What was made for that |---- |------- |--------------- |---------------------- -|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift -|Counter Strike |1.6(The latest steam release) |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Sven-Coop |5.0+ |Uses custom GoldSrc engine | - +|Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works. +|Arrange Mod: Rebirth |v150 |No idea yet. | +|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift. +|Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. | +| |1.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works. +| |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845. +| |1.6(The latest steam release) |Uses vgui2 library which xash3d does not support. |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. +|Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support. |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. +|Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support. Uses new sequences code on engine-side that was never used in any other mods before. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. +|Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. +|Half-Life: Extended |Day One demo |Uses many hooks to GoldSource engine and version check. |Just wait new version or use more old version. +|Icon of Hell |Beta 0.99 |Uses outdated BSP31 map format and paranoia 2 libraries. |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. +|Paranoia 2: The Savior |All builds older 1.51 |Uses an old renderer interface and engine features. | +|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion. +|Sven-Coop |5.0+ |Uses custom GoldSrc engine. | +|Time Shadows |Beta 0.1 |Uses Direct3D renderer. | diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index 88676a33..5ee37c75 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -17,7 +17,7 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth Mirrored on github - https://github.com/nekonomicon/BattleGrounds ## Bubblemod -Download page on official site - http://www.bubblemod.org/dl_default.php (dead link!) +Download 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) Mirrored on github - https://github.com/HLSources/BubbleMod @@ -32,12 +32,21 @@ Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/c ## Cold Ice Ressurection Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir +## Counter-Life +Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code + ## Cthulhu Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK ## Deathmatch Classic Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc +## Delta Particles +Available on ModDB - https://www.moddb.com/mods/half-life-delta/downloads/delta-particles-full-sources-maps-and-c-code + +## Earth Special Forces +Alpha 2.0 - https://www.gamers-desire.de/details/2830 + ## ESHQ Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ @@ -85,6 +94,9 @@ Official gitlab repository - https://gitlab.com/Sockman/hltopdown ## Half-Life: Update Official github repository - https://github.com/Fograin/hl-subsmod-ex +## Half-Life: Rally +Official gitlab repository - https://gitlab.com/hlrally/src + ## Half-Life: Weapon Edition Available on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src @@ -115,6 +127,11 @@ Mirrored on github - https://github.com/nekonomicon/HeartOfEvil ## Ingram Chillin' Mod Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ +## Master Sword +Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic + +Official github repository of Master Sword Rebirth - https://github.com/MSRevive/MasterSwordRebirth + ## MechMod Official github repository - https://github.com/vermagav/mechmod @@ -127,22 +144,11 @@ Official github repository - https://github.com/unknownworlds/NS ## Overturn Available in mod archive on ModDB - https://www.moddb.com/mods/overturn -## Spirit of Half-Life -[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life - -## Threewave CTF -*Unfinished mod by Valve Software* - -Available in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc - -## Trinity Render -Available on ModDB: https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f - -## Tyrian: Ground Assault -Available on ModDB: https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src +## Oz Deathmatch +Mirrored on github - https://github.com/nekonomicon/OZDM ## Paranoia -Available on ModDB: https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit +Available on ModDB - https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit ## Paranoia 2: The Savior Prealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient @@ -157,12 +163,37 @@ Was found on HLFX - http://hlfx.ru/forum/showthread.php?s=2c892dfc52f72be52a89c3 ## Ricochet Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/ricochet +## Spirit of Half-Life +[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life + +## The Wastes +Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk + +## Threewave CTF +*Unfinished mod by Valve Software* + +Available in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc + +## Trinity Render +Available on ModDB - https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f + +## Tyrian: Ground Assault +Available on ModDB - https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src + +## Wasteland +Mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wasteland-sdk + ## Wizard Wars Download page on official site - http://www.thothie.com/ww/ ## XashXT Mirrored on github - https://github.com/a1batross/XashXT_original +## Xen-Warrior +*Source code is a part of Spirit of Half-Life 1.0-1.2 under XENWARRIOR macro* + +[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life + ## Zombie-X Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version @@ -179,25 +210,25 @@ Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tr OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG ## Afraid of Monsters -malortie's recreation - https://github.com/malortie/hl-aom +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-aom Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut -Reverse-engineered code, branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc +Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep -malortie's recreation - https://github.com/malortie/hl-asheep +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-asheep -Reverse-engineered code, branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep +Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly -malortie's recreation - https://github.com/malortie/hl-biglolly +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-biglolly Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly ## Black Ops -malortie's recreation - https://github.com/malortie/hl-blackops +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-blackops Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops @@ -211,7 +242,7 @@ Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Counter Strike -Reverse-engineered code of client part by a1batross - https://github.com/FWGS/cs16-client/tree/v1.32 +Reverse-engineered code of client part by a1batross - https://github.com/Velaron/cs16-client Reverse-engineered code of server part by nagist - https://github.com/nagist/cs16nd @@ -220,18 +251,28 @@ Reverse-engineered code of server part by s1lentq - https://github.com/s1lentq/R ## Counter Strike Online CSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE +## Crack-Life +Reverse-engineered code: branch **cracklife** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/cracklife + ## Crack-Life: Campaign Mode Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life -## Escape from the Darkness -malortie's recreation - https://github.com/malortie/hl-eftd +Reverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign -Reverse-engineered code, branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd +## Escape from the Darkness +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-eftd + +Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd + +## Half-Life: Black Guard +*This mod uses dlls from Cleaner's Adventures* + +Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 -Reverse-engineered code, branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift +Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction @@ -239,44 +280,44 @@ Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/ ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d -Reverse-engineered code, clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor +Reverse-engineered code: clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion -Reverse-engineered code, branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion +Reverse-engineered code: branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion ## Half-Life: Urbicide Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide ## Half-Life: Visitors -malortie's recreation - https://github.com/malortie/hl-visitors +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-visitors -Reverse-engineered code, branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors +Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret ## Night at the Office -malortie's recreation - https://github.com/malortie/hl-nato +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-nato Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice ## Poke 646 -malortie's recreation - https://github.com/malortie/hl-poke646 +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646 -Reverse-engineered code, branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 +Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta -malortie's recreation - https://github.com/malortie/hl-poke646-vendetta +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646-vendetta -Reverse-engineered code, branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta +Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life -Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point +Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point -Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point +Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Sewer Beta Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta @@ -285,19 +326,19 @@ Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate -malortie's recreation - https://github.com/malortie/hl-thegate +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-thegate -Reverse-engineered code, branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate +Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger -malortie's recreation - https://github.com/malortie/hl-theyhunger +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-theyhunger -Reverse-engineered code, branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger +Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation ## Times of Troubles -malortie's recreation - https://github.com/malortie/hl-tot +malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-tot Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot @@ -319,7 +360,7 @@ Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/d Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes ## Half-Life: Invasion -Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion +Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion @@ -335,16 +376,22 @@ Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portab Port to Linux - https://github.com/fmoraw/NS ## Spirit of Half-Life -Version 1.2, branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 +Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 ## Swiss Cheese Halloween 2002 -Just more playable version by malortie - https://github.com/malortie/hl-shall +Just more playable version by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-shall Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween +## The Wastes +Version 1.5: Port to Linux - https://git.mentality.rip/a1batross/halflife-thewastes-sdk + ## Threewave CTF Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc +## Xen-Warrior +Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 + ## Zombie-X Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x diff --git a/Documentation/ports.md b/Documentation/ports.md index fa072aba..a4482510 100644 --- a/Documentation/ports.md +++ b/Documentation/ports.md @@ -1,38 +1,38 @@ -Xash3D FWGS is intended to be easily ported to specific platform, however main issue is maintaining such ports. +Xash3D FWGS is intended to be easily portable for various platforms, however main issue is maintaining such ports. This page is about merged ports to main source tree and responsible for it developers. -For porting guidelines, look here(TODO!) +For porting guidelines, read engine-porting-guide.md. Status: -* **Supported**: active, confirmed to be fully functional. +* **Supported**: active, confirmed to be fully functional, gets built on CI. +* **Orphaned**: some work was done but not finished or actively tested due to lack of human resources. * **In progress**: active, under development. -* **Incomplete**: not active, but some work was accepted to main repository before -* **Old Engine**: port was for old engine fork -* **Not maintained**: lack of human resources -* **Not merged**: was done in third-party fork, real status unknown -* **Deprecated**: not supported anymore +* **Old Engine**: port was for old engine fork. +* **Deprecated**: not supported anymore. -Table is sorted by status. +Table is sorted by status and platform. | Platform | Status | Maintainer | Note | -------- | ------ | ---------- | ---- -| Windows | Supported | @a1batross, @SNMetamorph | +| Android | Supported | @Velaron | | *BSD | Supported | @nekonomicon | -| GNU/Linux(x86, amd64, arm) | Supported | @a1batross, @mittorn | -| Android | Supported | @a1batross, @mittorn | -| MotoMAGX | Supported | @a1batross | -| GNU/Linux(elbrus) | Supported | @a1batross, @mittorn | Rare and eventual access to e2k machine -| Haiku | Supported | not maintained | Was added by #478 and #483 -| DOS4GW | Supported | @mittorn | -| GNU/Linux(mipsel) | Supported | @mittorn | -| Switch | In progress | @fgsfdsfgs | [GitHub Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) -| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii) +| GNU/Linux | Supported | @a1batross, @mittorn | +| macOS | Supported | @sofakng | +| PSVita | Supported | @fgsfdsfgs | +| Switch | Supported | @fgsfdsfgs | +| Windows | Supported | @a1batross, @SNMetamorph | +| DOS4GW | Orphaned | N/A | Haven't been confirmed to work for a very long time +| Haiku | Orphaned | N/A | Was added by #478 and #483 +| IRIX | Orphaned | N/A | Undone, compiles but requires big endian port +| 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) +| SerenityOS | Orphaned | N/A | Works but not throughly tested +| Solaris | Orphaned | N/A | Works but not throughly tested +| WebAssembly System Interface | Orphaned | N/A | Undone, WASI is missing a lot of APIs we want to use +| Dreamcast | In progress | @maximqaxd | [GitHub Repository](https://github.com/maximqaxd/xash3d-fwgs_dc/) | PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs) -| Emscripten | Old Engine | not maintained | -| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) -| PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) -| Switch | Old Engine fork | not maintained | [GitHub Repository](https://github.com/switchports/xash3d-switch) -| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) -| macOS | Deprecated | not maintained | See GitHub issue #61 -| iOS | Deprecated | not maintained | See GitHub issue #61 +| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii) +| Emscripten | Old Engine | N/A | +| 3DS | Old Engine fork | N/A | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) +| Oculus Quest | Old Engine fork | N/A | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) +| iOS | Deprecated | N/A | See GitHub issue #61 diff --git a/Documentation/psvita.md b/Documentation/psvita.md new file mode 100644 index 00000000..8750160a --- /dev/null +++ b/Documentation/psvita.md @@ -0,0 +1,52 @@ +## PlayStation Vita port + +### Prerequisites +1. Make sure your PSVita is [set up to run homebrew applications](https://vita.hacks.guide/). +2. 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. + + 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: + ``` + *KERNEL + ux0:tai/kubridge.skprx + ``` + +3. Install `libshacccg.suprx` by following [this guide](https://cimmerian.gitbook.io/vita-troubleshooting-guide/shader-compiler/extract-libshacccg.suprx). + +### Installation +1. If you have an old vitaXash3D install, remove it. +2. Get `xash3d-fwgs-psvita.7z` from the latest [automatic build](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous). +3. Install `xash.vpk` from the 7z archive onto your PSVita. +4. Copy the `data` directory from the 7z archive to the root of your PSVita's SD card. +5. 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.** + +### Build instructions +1. Install [VitaSDK](https://vitasdk.org/). +2. Build and install [vitaGL](https://github.com/Rinnegatamante/vitaGL): + ``` + git clone https://github.com/Rinnegatamante/vitaGL.git + 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 + ``` +3. Build and install [vita-rtld](https://github.com/fgsfdsfgs/vita-rtld): + ``` + git clone https://github.com/fgsfdsfgs/vita-rtld.git && cd vita-rtld + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j2 install + ``` +4. Build and install [this SDL2 fork](https://github.com/Northfear/SDL) with vitaGL integration: + ``` + git clone https://github.com/Northfear/SDL.git && cd SDL + mkdir build && cd build + cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON .. + make -j2 install + ``` +5. Use `waf`: + ``` + ./waf configure -T release --psvita + ./waf build + ``` +6. Copy all the resulting `.so` files into a single folder: + ``` + ./waf install --destdir=xash3d + ``` +7. `xash.vpk` is located in `build/engine/`. diff --git a/Documentation/public-servers-rules-and-recommendations.md b/Documentation/public-servers-rules-and-recommendations.md deleted file mode 100644 index 55e6d805..00000000 --- a/Documentation/public-servers-rules-and-recommendations.md +++ /dev/null @@ -1,15 +0,0 @@ -As online community is growing, we don't want to keep bad or invalid servers on the Internet Games list. It's a common rules for all Xash3D server owners who are using default master server. - -1. Breaking master server or clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub. - -2. Changing settings, exploiting vulnerabilities of clients is prohibited in any way. If you found a serious bug, contact us at contact 'at' fwgs.ru or post an issue at GitHub. - -2. Agressive advertisement and spam on other servers is prohibited in any way. - -3. If your server is require downloading custom content, it should be available for everyone. Recommended way to deliver custom content is FastDL. - -4. It's recommended to keep your server is up-to-date. You may use our public Debian/Ubuntu/Arch Linux [repository](https://github.com/FWGS/xash3d/wiki/How-to-set-up-a-Xash3D-Dedicated-Server-on-Debian-or-Ubuntu) to simplify updating process. - -Just follow these rules to not get banned on our master server. Good luck, have fun! - - diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index 54f35a3f..464fdccc 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -1,4 +1,4 @@ -- [List of mods which work on Android and other non-Windows platforms without troubles](#list-of-mods-which-work-on-android-and-other-non-windows-platforms-without-troubles) +- [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) - [List of Half-life-based mods](#list-of-half-life-based-mods) - [The big singleplayer mods and mappacks](#the-big-singleplayer-mods-and-mappacks) - [Unfinished singleplayer mods and mappacks](#unfinished-singleplayer-mods-and-mappacks) @@ -6,6 +6,8 @@ - [Singleplayer mods or mappacks with a little incompatibilities](#singleplayer-mods-or-mappacks-with-a-little-incompatibilities) - [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) - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps) + - [List of mappacks for Half-Life: Blue Shift](#list-of-mappacks-for-half-life-blue-shift) + - [List of Cleaner's Adventures-based mods](#list-of-cleaners-adventures-based-mods) - [List of mods which based on Spirit of Half-Life](#list-of-mods-which-based-on-spirit-of-half-life) - [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) - [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) @@ -14,847 +16,856 @@ - [List of Opposing Force-based mods](#list-of-opposing-force-based-mods) - [The big singleplayer mods or mappacks](#the-big-singleplayer-mods-or-mappacks) - [Unfinished mods](#unfinished-mods) - - [Mods with a little incompatibilities](#mods-with-a-little-incompatibilities) - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps-1) - [List of XashXT-based mods](#list-of-xashxt-based-mods) - - [List of maps for They Hunger](#list-of-maps-for-they-hunger) + - [List of They Hunger-based mods](#list-of-they-hunger-based-mods) + - [The big singleplayers mods](#the-big-singleplayers-mods) + - [Single maps](#single-maps) + - [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) - [List of games and mods with custom gamedll](#list-of-games-and-mods-with-custom-gamedll) - - [List of mods for Counter Strike(Android only)](#list-of-mods-for-counter-strike) + - [List of Counter Strike-based mods](#list-of-counter-strike-based-mods) -# List of Half-Life-based mods -Originally this list was written by *Qwertyus*(*Qortez*) +# List of mods which work on Linux and other non-Windows platforms without troubles +Most universal way to install and run mods - place your mod folder beside valve folder and launch engine with command-line arguments `-game ` -To run mods from this category - place mod folder to folder where is **valve** folder located and launch xash3d fwgs with command-line parameter `-game `. +Also, on some platforms you can see "Custom Game" menu in game where you can choose and run any installed mod. -For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valve** and type **`map `** cmd. -## The big singleplayer mods and mappacks +For mappacks - place *.bsp files to **valve/custom/maps** folder, *.wad files to **valve/custom** and type **`map `** cmd in game. + +## List of Half-Life-based mods +Originally this list was written by @Qwertyus3D (*Qortez*) + +### The big singleplayer mods and mappacks 1. [101 grunts](https://www.runthinkshootlive.com/posts/101-grunts/) 2. [1986(Black Mesa Circa 1979,1986)](https://www.fileplanet.com/110726/110000/fileinfo/Black-Mesa-Circa-1979,-1986) -3. [A dream](https://gamebanana.com/maps/162091) aka [Map a Dream](http://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**) -4. [Adam](http://www.moddb.com/mods/adam) -5. [Affliction](http://www.moddb.com/mods/affliction) -6. [Afraid of Monsters](http://www.moddb.com/mods/afraid-of-monsters) (Windows version uses WON HL 1.1.0.7 libraries which has a bug with gibs) -7. [Agamemnon Icarus Glass](http://twhl.info/vault.php?map=1978) -8. [Aggregate Pain](https://www.runthinkshootlive.com/posts/aggregate-pain/) -9. [Alternative Way: Part 1](https://www.runthinkshootlive.com/posts/alternative-way-part-1/) -10. [Assassin Mark 2](https://www.fileplanet.com/112020/110000/fileinfo/Assassin-Mark-2) -11. [Assault on Roswell v2.0](http://www.moddb.com/mods/assault-on-roswell) -12. [Back to Xen](https://www.runthinkshootlive.com/posts/back-to-xen/) -13. [Back to Xen 2](https://www.runthinkshootlive.com/posts/back-to-xen-2/) -14. [Be Careful!](https://www.fileplanet.com/110304/110000/fileinfo/Be-Careful!) -15. [Beginnings](https://www.runthinkshootlive.com/posts/beginnings/) -16. [Betrayal](http://www.moddb.com/mods/betrayal) (this mod has inner bugs; set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) -17. [Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/) -18. [Black Mesa Energy Testing Chamber](http://twhl.info/competitions.php?results=17) -19. [Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) -20. [BlackMesa 2007( Black Mesa Missions 2007 )](http://www.moddb.com/addons/blackmesa-2007) -21. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) -22. Boom (Huknenn) v1.0 & [HL Boom: Gold Edition v1.1](http://www.moddb.com/mods/boom) -23. [BoomeNShtein3D: Episode 1](http://hl.loess.ru/?mod=1178) -24. [Brave Brain](http://www.moddb.com/mods/brave-brain) -25. [Breakdown](https://www.runthinkshootlive.com/posts/breakdown-1/) -26. [Breakdown 2: Afterwards](https://www.runthinkshootlive.com/posts/breakdown-2-afterwards/) -27. [Buddhist Wars](http://www.moddb.com/mods/buddhist-wars) -28. [C&C Tiberian Dawn v2.0](http://www.moddb.com/mods/half-life-cc-tiberian-dawn) -29. [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) -30. [Castle Disposed](http://www.moddb.com/mods/castle-disposed) -31. [Chaos Theory](https://www.runthinkshootlive.com/posts/chaos-theory/) -32. [Chernobyl](http://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) -33. [ChickenMix](http://twhl.info/vault.php?map=3118) -34. [ChickenMix 2](http://twhl.info/vault.php?map=3434) (there are a couple of minor bugs, but they are inner bugs of the mod) -35. [Confronted with Consequences](http://www.moddb.com/mods/confronted-with-consequences) -36. [Construction](https://www.runthinkshootlive.com/posts/construction/) -37. [Conundrum](https://www.runthinkshootlive.com/posts/conundrum/) -38. [Conundrum 2](https://www.runthinkshootlive.com/posts/conundrum-2/) -38. [Crack-Life](http://www.moddb.com/mods/crack-life) -39. [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) -40. [CWC Board Mappack Initiative](http://www.moddb.com/mods/cwc-pack-initiative) (the mod has inner bugs) -41. [DAV Sub](http://www.moddb.com/mods/davsub) -42. [DAV Train](http://www.moddb.com/mods/davtrain) -43. [death = power](https://www.fileplanet.com/53953/50000/fileinfo/Death=power) -44. [Death in the Dark](http://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) -45. [DejaVu v2.00](http://www.moddb.com/mods/dejavu) -46. [Destination Black Mesa](http://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) -47. [Destiny](http://twhl.info/competitions.php?results=15) -48. [Do](https://www.runthinkshootlive.com/posts/do/) -49. [Doomed-life](http://www.moddb.com/mods/doomed-life) -50. [Down Time](https://www.runthinkshootlive.com/posts/down-time/) -51. [Drug Barons](https://www.runthinkshootlive.com/posts/drug-barons/) -52. [Dungeon Death](http://darktreemedia.com/dungeondeath/) -53. [Dust Runner v1.1](https://www.runthinkshootlive.com/posts/dust-runner/) -54. [Dwell](https://www.runthinkshootlive.com/posts/dwell/) -55. [E. T. C. ( Earthquake Testing Centre )](http://www.moddb.com/mods/etc2) -56. [E. T. F.](http://www.moddb.com/mods/e-t-f) -57. [E7: Black Star](http://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) -58. [Edge of Darkness](http://www.moddb.com/mods/hl-edge-of-darkness) -59. [Edge of Death - Series 1](http://twhl.info/vault.php?map=5088) -60. [Engineering Mankind](http://twhl.info/competitions.php?results=13) -61. [Episode Power Plant and China](http://www.artpeter.net/Data/HalfLife/Hl.php) -62. [Episode Secret Weapon](http://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) -63. [Escape by Leo Ring](https://www.runthinkshootlive.com/posts/escape-by-leo-ring/) -64. [Escape from the Egyptian Tomb( RacerX Compo 12 Egyptian Museum )](http://twhl.info/vault.php?map=2368) -65. [Espionage](https://www.runthinkshootlive.com/posts/espionage/) -66. [Exodus](http://www.moddb.com/games/half-life/addons/exodus-1) -67. [Exodus 2](http://www.moddb.com/games/half-life/addons/exodus-2) -68. [Failure](http://www.moddb.com/games/half-life/addons/failure) -69. [Faraon](http://hl.loess.ru/?mod=345) -70. [Fate Reversal v1.1](http://www.moddb.com/mods/fate-reversal) -71. [Fathom 2.4](http://twhl.info/vault.php?map=4759) -72. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) -73. [Freeman's Escape](http://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) -74. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) -75. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) -76. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) -77. [Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/) -78. [Freeman's Tomb( The Crypt )](http://twhl.info/vault.php?map=3787) -79. [G-Invasion v2.5](http://www.moddb.com/mods/g-man-invasion) aka G-Man Invasion (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) -80. [G-Man House](http://hl.loess.ru/?mod=404) -81. [GameStar( Episode 4 GameStar )](https://www.runthinkshootlive.com/posts/gamestar/) -82. [Gateway](http://www.moddb.com/mods/gateway) -83. [Gateway 2](http://www.moddb.com/mods/gateway-2) -84. [Gina's Adventures](http://www.moddb.com/mods/ginas-adventures) -85. [Gman Island Part 1](https://www.runthinkshootlive.com/posts/gman-island-part-1/) -86. [Gman Island Part 2](https://www.runthinkshootlive.com/posts/gman-island-part-2/) -87. [Graber v1.1](http://www.moddb.com/mods/graber-half-life-mod) -88. [Ground Zero](http://www.moddb.com/mods/half-life-ground-zero) by *Derek 'Hellfire' McBurney* -89. [Gut Reaction](http://www.moddb.com/mods/gut-reaction) -90. [Half-Life C.A.G.E.D.](https://store.steampowered.com/app/679990/HalfLife_Caged/) (set **-num edicts 4096** in command-line parameters for successful start) -90. [Half-Life: Dreamcast v1.1](http://www.moddb.com/mods/half-life-dreamcast) -91. [Half-Life: OPS](https://www.runthinkshootlive.com/posts/half-life-ops/) -92. [Half-Life: Uplink](https://www.fileplanet.com/10360/10000/fileinfo/Half-Life:-UpLink-Demo) -92. [Half-Quake](http://www.moddb.com/mods/halfquake-amen) -93. [Half-Quake 2: Amen](http://www.moddb.com/mods/halfquake-amen) -94. [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) -95. [Hardman - In the City](https://www.runthinkshootlive.com/posts/hardman-in-the-city/) (the updated version of the mod has been tested) -96. [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**) -97. [Hazardous Materials: Episode 1 & 2](http://www.moddb.com/mods/half-life-hazardous-materials) -98. [Hazardous-Course 2](http://www.moddb.com/mods/hazardous-course-2) -99. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) -100. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) -101. [High Speed](http://www.moddb.com/games/half-life/addons/high-speed-english-version) -102. [hlife_hotdog_compo26](http://twhl.info/vault.php?map=5181) -103. [Home Alone -NOT](https://www.fileplanet.com/49895/40000/fileinfo/Home-Alone--NOT) -104. [Hour-Glass](http://www.moddb.com/mods/hour-glass) -105. [Idol Hunt](https://www.runthinkshootlive.com/posts/idol-hunt/) -106. [Independence Day](https://www.runthinkshootlive.com/posts/independence-day/) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) -107. [Infestation](https://www.runthinkshootlive.com/posts/half-life-infestation/) -108. [Infiltracja](http://unquenque.com/tenfour/) -109. [Infinite Rift](http://www.moddb.com/mods/infinite-rift) -110. [Instinct](http://www.moddb.com/mods/instinct) -111. [Insurrection](https://www.runthinkshootlive.com/posts/insurrection/) -112. [Irreality](http://www.moddb.com/mods/irreality) -113. [Ispitatel](http://www.moddb.com/mods/ispitatel) -114. [Kill All Greenpeace](http://www.moddb.com/mods/kill-all-greenpeace) -115. [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) -116. [Lands of Lore v2](http://www.moddb.com/mods/half-life-lands-of-lore) (set **gl_allow_mirrors** to **0** to avoid performance problems with last 2 maps) -117. [Last-life](https://www.vossey.com/jeu/Last-Life--i586.htm) -118. [Life's End](http://www.moddb.com/mods/lifes-end2) -119. [Macky's Adventure](http://www.moddb.com/mods/mackys-adventure-expansion-set) -120. [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) -121. [Marine Invasion: Episode 1 & 2 + Freak-Lager(OF map)](https://gamebanana.com/gamefiles/2537) -122. [Mario Keys](http://www.moddb.com/mods/mario-keys) -123. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) -124. [Medieval World](http://www.moddb.com/mods/medieval-world) -125. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) -126. [MINIMICUS](http://twhl.info/vault.php?map=163) -127. [Mission Failed](http://www.moddb.com/mods/mission-failed) -128. [Mission MC Poker](http://www.moddb.com/mods/half-life-mission-mc-poker) -129. [Mission of Mercy](https://www.runthinkshootlive.com/posts/mission-of-mercy/) -130. [Mission to Kill](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) -131. [Mission to Kill 2](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) -132. [Moonwalker](http://www.moddb.com/mods/moonwalker) -133. [Mystery House v1.0](http://www.moddb.com/mods/mystery-house) -134. [NewBlackMesaVille](https://www.runthinkshootlive.com/posts/newblackmesaville/) -135. [Night Shooting](http://www.moddb.com/games/half-life/addons/night-shooting) -136. [No Exit](http://www.moddb.com/mods/no-exit) -137. [No-Life](https://www.fileplanet.com/120501/120000/fileinfo/No-Life) -138. [Nuclear Power Plant](https://www.runthinkshootlive.com/posts/nuclear-power-plant/) -139. [Occupied Territory](http://www.moddb.com/games/half-life/addons/occupied-territory) -140. [Operacja Gargantua](https://www.runthinkshootlive.com/posts/operation-gargantua/) -141. [Operacja Mirra](http://unquenque.com/tenfour/) -142. [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) -143. [Operation: Nova](http://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) -144. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) -145. [Outrun](http://www.moddb.com/mods/outrun) -146. [Outwards (Day One)](https://drive.google.com/file/d/1mjQ8wUazYbwTq9Ocg0Vs0raq4avbTRne/view?usp=sharing) -147. [Overhaul Pack](http://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) -148. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) -149. [pagoda](http://www.moddb.com/mods/pagoda1) -150. [Peaces Like Us v1.0](https://www.runthinkshootlive.com/posts/peaces-like-us/) -151. [Phobos IV](http://www.moddb.com/mods/phobos-iv) -152. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) -153. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) -154. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) -155. [Prisoner of Event](http://wp.vondur.net/?page_id=40) -156. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) -157. [Project Quantum Leap](http://www.moddb.com/mods/project-quantum-leap) -158. [Project VIP](http://www.pcosmos.ca/mods-hl/downloads/?mod=projectVIP) -159. [Projekt Einstein](http://unquenque.com/tenfour/) -160. [Recon( Reconnaissance )](http://www.moddb.com/games/half-life/addons/reconnaissance) -161. [Red Mesa](http://www.moddb.com/mods/red-mesa-1) -162. [Red Mesa 2](http://www.moddb.com/mods/red-mesa-2) -163. [RES v1.0](https://www.fileplanet.com/29002/20000/fileinfo/RES) -164. [Rescue 9-1-Freeman](http://www.moddb.com/mods/rescue-9-1-freeman) -165. [Resistance](https://www.runthinkshootlive.com/posts/resistance/) -166. [Resistance 2](https://www.runthinkshootlive.com/posts/resistance-2/) -167. [Resistance 3](https://www.runthinkshootlive.com/posts/resistance-3/) -168. [Return](https://www.runthinkshootlive.com/posts/return/) -169. [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) -170. [Reviviscence](http://www.moddb.com/mods/reviviscence) -171. [Road of Destiny](https://www.fileplanet.com/10393/10000/fileinfo/Road-of-Destiny) -172. [Rooms: TWHL COOP Project( Rooms Half-Life or TWHL Rooms )](http://twhl.info/vault.php?map=5394) (set **gl_allow_mirrors** to **0** to prevent drops of perfomance at the map **rooms7**) -173. [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*) -174. [S.W.A.T.](http://www.moddb.com/mods/swat2) -175. [Sabotage](https://www.runthinkshootlive.com/posts/sabotage-2/) -176. [Sagharmath](https://www.moddb.com/games/half-life/addons/sagharmath) -177. [Sandscroll](https://gamebanana.com/maps/15988) -178. [Saving Bob](https://www.runthinkshootlive.com/posts/saving-bob/) -179. [Secret Base](https://www.runthinkshootlive.com/posts/secret-base/) -180. [Secret Santa 2011 for Soup Miner](http://twhl.info/vault.php?map=5724) -181. [Secret Santa 2011 for Stojke](http://twhl.info/vault.php?map=5728) -182. [Senses in Decay](http://www.moddb.com/mods/senses-in-decay) -183. [Shift-Two v1.1](http://www.moddb.com/mods/shift-two1) -184. [Sky Mesa](http://www.moddb.com/mods/sky-mesa) -185. [Sleephorst v1.5](http://www.moddb.com/mods/sleephorst-v15) -186. [Smart Decoy](http://www.moddb.com/mods/smart-decoy) -187. [Somewhere in Time](https://www.runthinkshootlive.com/posts/somewhere-in-time/) -188. [Sooper2](https://www.runthinkshootlive.com/posts/sooper-2/) -189. [Split-Second](http://twhl.info/vault.php?map=4343) -190. [Subhumanity](https://www.runthinkshootlive.com/posts/sub-humanity/) -191. [Terror Side](https://www.runthinkshootlive.com/posts/terror-side/) (set **sv_validate_changelevel** to **0** to avoid problem with level change between some maps) -192. [Test Your Skill](https://www.moddb.com/games/half-life/addons/test-your-skill) -193. [The Challenger Deep](https://www.runthinkshootlive.com/posts/challenger-deep/) -194. [The Challenger Deep 2](http://www.moddb.com/mods/the-challenger-deep-2) -195. [The Evil Thing](http://www.moddb.com/mods/the-evil-thing) -196. [The Evil World](http://www.moddb.com/mods/the-evil-world) -197. [The Haunted Lab](https://www.runthinkshootlive.com/posts/the-haunted-lab/) -198. [The Long Night](http://www.moddb.com/mods/the-long-night) -199. [The Mansion](http://twhl.info/vault.php?map=2438) -200. [The Night Things](http://twhl.info/vault.php?map=2439) -201. [The Night Things 2: Woodstock Manor](http://twhl.info/vault.php?map=2706) -202. [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) -203. [The Way Is Clear](https://www.runthinkshootlive.com/posts/the-way-is-clear/) -204. [The Way Is Clear 2](https://www.runthinkshootlive.com/posts/the-way-is-clear-2/) -205. [The Xeno Project](http://www.moddb.com/mods/xeno-project-i-ii) -206. [The Xeno Project 2](http://www.moddb.com/mods/xeno-project-i-ii) -207. [Then and Now](http://twhl.info/vault.php?map=6048) -208. [They Live( Chungo )](https://www.runthinkshootlive.com/posts/they-live/) -209. [Timefall](http://www.moddb.com/mods/hl-timefall) -210. [Timeline](http://www.moddb.com/mods/timeline-series) -211. [Timeline II: Iced Earth](http://www.moddb.com/mods/timeline-series) -212. [Todesangst](http://www.moddb.com/mods/todesangst) -213. [Tokami Island](https://www.fileplanet.com/110517/110000/fileinfo/Tokami-Island) -214. [Tower](http://hlperfectzone.narod.ru/mods.htm) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) -215. [Trespasser](http://www.moddb.com/mods/trespasser) -216. [Try, Try Again](https://www.runthinkshootlive.com/posts/try-try-again/) -217. [Tucked Up](http://www.moddb.com/games/half-life/addons/tucked-up) -218. [TWHLmix](http://twhl.info/vault.php?map=2516) -219. [Two Smoking Barrels](https://www.runthinkshootlive.com/posts/two-smoking-barrels/) -220. [Typical Disaster](http://www.moddb.com/mods/typical-disaster) -221. [Typical Disaster: The Lost Levels](https://www.runthinkshootlive.com/posts/typical-disaster-the-lost-levels/) -222. [U-Life](http://www.moddb.com/mods/u-life) -223. [Underground v2.0](http://www.isolated-design.de/half-life-mods/underground/) (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) -224. [Underground Territory](http://hl.loess.ru/?mod=1069) -225. [Undertime](http://twhl.info/vault.php?map=3906) -226. [Uplink Addon v1.2](https://www.fileplanet.com/82828/80000/fileinfo/Half-Life-Uplink-Addon-1.2) -227. [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) -228. [USS Darkstar](http://www.moddb.com/mods/uss-darkstar) (set **gl_allow_mirrors** to **0** to prevent drops of perfomance in some areas) -229. [Vengeance](http://www.moddb.com/mods/half-life-vengeance) -230. [Virtual Reality: The Real World](https://www.runthinkshootlive.com/posts/virtual-reality-the-real-world/) -231. [VLOKAM](https://www.ceskemody.cz/mapy.php?lng=1&clanek=32&razeni_hra=1&pn=vlokam-1) -232. [VLOKAM 2](https://www.ceskemody.cz/mapy.php?lng=1&clanek=33&razeni_hra=1&pn=vlokam-2) -233. [Wail of Death](https://www.runthinkshootlive.com/posts/wail-of-death/) -234. [Wail of Death 2: The Hell Master](https://www.runthinkshootlive.com/posts/wail-of-death-2-the-hell-master/) -235. [War Crimes v1.2](http://www.moddb.com/games/half-life/addons/war-crimes) -236. [Windmill v2.0](https://www.runthinkshootlive.com/posts/windmill/) -237. [World War III Missions (parts 1, 2 & 3)](http://www.moddb.com/mods/world-war-iii-missions-1-2-3) -238. [Worst Holiday](http://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) -239. [Xen](https://www.runthinkshootlive.com/posts/half-life-xen-2/) -240. [You Are in Army Now](http://www.pcosmos.ca/mods-hl/downloads/?mod=armynow) -241. [Zubben](https://www.runthinkshootlive.com/posts/zubben/) +3. [2009EB](https://hldm.ucoz.ru/forum/5-185-1) +4. [A Day in the Life of a Coward](https://www.runthinkshootlive.com/posts/a-day-in-the-life-of-a-coward/) +5. **[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**) +6. [Adam](https://www.moddb.com/mods/adam) +7. **[Affliction](https://www.moddb.com/mods/affliction)** +9. [Agamemnon Icarus Glass](https://twhl.info/vault.php?map=1978) +10. **[Aggregate Pain](https://www.runthinkshootlive.com/posts/aggregate-pain/)** +11. **[Alternative Way: Part 1](https://www.runthinkshootlive.com/posts/alternative-way-part-1/)** +12. [Assassin Mark 2](https://www.fileplanet.com/112020/110000/fileinfo/Assassin-Mark-2) +13. [Assault on Roswell v2.0](https://www.moddb.com/mods/assault-on-roswell) +14. **[Back to Xen](https://www.runthinkshootlive.com/posts/back-to-xen/)** +15. **[Back to Xen 2](https://www.runthinkshootlive.com/posts/back-to-xen-2/)** +16. [Be Careful!](https://www.fileplanet.com/110304/110000/fileinfo/Be-Careful!) +17. **[Beginnings](https://www.runthinkshootlive.com/posts/beginnings/)** +18. [Betrayal](https://www.moddb.com/mods/betrayal) (this mod has inner bugs) +19. **[Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/)** +20. [Black Mesa Energy Testing Chamber](https://twhl.info/competitions.php?results=17) +21. **[Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline)** +22. [BlackMesa 2007( Black Mesa Missions 2007 )](https://www.moddb.com/addons/blackmesa-2007) +23. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) +24. [Boom (Huknenn) v1.0](https://gamebanana.com/mods/35976) & [HL Boom: Gold Edition v1.1](https://www.moddb.com/mods/boom) +25. [BoomeNShtein3D: Episode 1](https://www.moddb.com/games/half-life/addons/boomenshtein3d-episode-1) +26. **[Brave Brain](https://www.moddb.com/mods/brave-brain)** +27. [Breakdown](https://www.runthinkshootlive.com/posts/breakdown-1/) +28. [Breakdown 2: Afterwards](https://www.runthinkshootlive.com/posts/breakdown-2-afterwards/) +29. [Buddhist Wars](https://www.moddb.com/mods/buddhist-wars) +30. **[C&C Tiberian Dawn v2.0](https://www.moddb.com/mods/half-life-cc-tiberian-dawn)** +31. [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) +32. [Castle Disposed](https://www.moddb.com/mods/castle-disposed) +33. **[Chaos Theory](https://www.runthinkshootlive.com/posts/chaos-theory/)** +34. [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) +35. [ChickenMix](https://twhl.info/vault.php?map=3118) +36. [ChickenMix 2](https://twhl.info/vault.php?map=3434) (there are a couple of minor bugs, but they are inner bugs of the mod) +37. [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) +38. [Construction](https://www.runthinkshootlive.com/posts/construction/) +39. [Conundrum](https://www.runthinkshootlive.com/posts/conundrum/) +40. [Conundrum 2](https://www.runthinkshootlive.com/posts/conundrum-2/) +41. [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) +42. [CWC Board Mappack Initiative](https://www.moddb.com/mods/cwc-pack-initiative) (the mod has inner bugs) +43. **[DAV Sub](https://www.moddb.com/mods/davsub)** +44. **[DAV Train](https://www.moddb.com/mods/davtrain)** +45. [death = power](https://www.fileplanet.com/53953/50000/fileinfo/Death=power) +46. [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) +47. [DejaVu v2.00](https://www.moddb.com/mods/dejavu) +48. **[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) +49. [Destiny](https://twhl.info/competitions.php?results=15) +50. [Deth](https://www.runthinkshootlive.com/posts/deth/) +51. [Do](https://www.runthinkshootlive.com/posts/do/) +52. [Doomed-life](https://www.moddb.com/mods/doomed-life) +53. [Down Time](https://www.runthinkshootlive.com/posts/down-time/) +54. [Drug Barons](https://www.runthinkshootlive.com/posts/drug-barons/) +55. [Dungeon Death](http://darktreemedia.com/dungeondeath/) +56. [Dust Runner v1.1](https://www.runthinkshootlive.com/posts/dust-runner/) +57. [Dwell](https://www.runthinkshootlive.com/posts/dwell/) +58. **[E. T. C. ( Earthquake Testing Centre )](https://www.moddb.com/mods/etc2)** +59. **[E. T. F.](https://www.moddb.com/mods/e-t-f)** +60. **[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) +61. **[Edge of Darkness](https://www.moddb.com/mods/hl-edge-of-darkness)** +62. [Edge of Death - Series 1](https://twhl.info/vault.php?map=5088) +63. [Engineering Mankind](https://twhl.info/competitions.php?results=13) +64. [Episode Power Plant and China](https://www.artpeter.net/Data/HalfLife/Hl.php) +65. [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) +66. **[Escape](https://www.runthinkshootlive.com/posts/escape-by-leo-ring/)** by *Leo Ring* +67. [Escape from the Egyptian Tomb( RacerX Compo 12 Egyptian Museum )](https://twhl.info/vault.php?map=2368) +68. [Espionage](https://www.runthinkshootlive.com/posts/espionage/) +69. [Exodus](https://www.moddb.com/games/half-life/addons/exodus-1) +70. [Exodus 2](https://www.moddb.com/games/half-life/addons/exodus-2) +71. [Failure](https://www.moddb.com/games/half-life/addons/failure) +72. [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)) +73. **[Fate Reversal v1.1](https://www.moddb.com/mods/fate-reversal)** +74. **[Fathom 2.4](https://twhl.info/vault.php?map=4759)** +75. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) +76. [Freeman's Escape](https://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) +77. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) +78. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) +79. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) +80. **[Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/)** +81. [Freeman's Tomb( The Crypt )](https://twhl.info/vault.php?map=3787) +82. **[G-Invasion v2.6](https://www.moddb.com/mods/g-man-invasion)** aka G-Man Invasion +83. [G-Man House](https://www.moddb.com/games/half-life/addons/g-man-house) +84. [GameStar( Episode 4 GameStar )](https://www.runthinkshootlive.com/posts/gamestar/) +85. [Gateway](https://www.moddb.com/mods/gateway) +86. [Gateway 2](https://www.moddb.com/mods/gateway-2) +87. [Gina's Adventures](https://www.moddb.com/mods/ginas-adventures) +88. **[Gman Island Part 1](https://www.runthinkshootlive.com/posts/gman-island-part-1/)** +89. **[Gman Island Part 2](https://www.runthinkshootlive.com/posts/gman-island-part-2/)** +90. **[Graber v1.1](https://www.moddb.com/mods/graber-half-life-mod)** +91. [Graf War](https://www.runthinkshootlive.com/posts/graf-war/) +92. **[Ground Zero](https://www.moddb.com/mods/half-life-ground-zero)** by *Derek 'Hellfire' McBurney* +93. **[Gut Reaction](https://www.moddb.com/mods/gut-reaction)** +94. **[Half-Life C.A.G.E.D.](https://store.steampowered.com/app/679990/HalfLife_Caged/)** +95. **[Half-Life: Dreamcast v1.1](https://www.moddb.com/mods/half-life-dreamcast)** +96. [Half-Life: OPS](https://www.runthinkshootlive.com/posts/half-life-ops/) +97. **[Half-Quake](https://www.moddb.com/mods/halfquake-amen)** +98. **[Half-Quake 2: Amen](https://www.moddb.com/mods/halfquake-amen)** +99. [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) +100. [Hardman - In the City](https://www.runthinkshootlive.com/posts/hardman-in-the-city/) (the updated version of the mod has been tested) +101. [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**) +102. [Hazardous Materials: Episode 1 & 2](https://www.moddb.com/mods/half-life-hazardous-materials) +103. **[Hazardous-Course 2](https://www.moddb.com/mods/hazardous-course-2)** +104. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) +105. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) +106. [High Speed](https://www.moddb.com/games/half-life/addons/high-speed-english-version) +107. **[hlife_hotdog_compo26](https://twhl.info/vault.php?map=5181)** +108. [Home Alone -NOT](https://www.fileplanet.com/49895/40000/fileinfo/Home-Alone--NOT) +109. **[Hour-Glass](https://www.moddb.com/mods/hour-glass)** +110. [Idol Hunt](https://www.runthinkshootlive.com/posts/idol-hunt/) +111. [Independence Day](https://www.runthinkshootlive.com/posts/independence-day/) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) +112. [Infestation](https://www.runthinkshootlive.com/posts/half-life-infestation/) +113. [Infiltracja](https://unquenque.com/tenfour/) +114. **[Infinite Rift](https://www.moddb.com/mods/infinite-rift)** +115. **[Instinct](https://www.moddb.com/mods/instinct)** +116. [Insurrection](https://www.runthinkshootlive.com/posts/insurrection/) +117. [Irreality](https://www.moddb.com/mods/irreality) +118. [Ispitatel](https://www.moddb.com/mods/ispitatel) +119. [Jailbreak](https://www.runthinkshootlive.com/posts/jailbreak/) +120. [Kill All Greenpeace](https://www.moddb.com/mods/kill-all-greenpeace) +121. [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) +122. [Lands of Lore v2](https://www.moddb.com/mods/half-life-lands-of-lore) +123. [Last-life](https://www.moddb.com/mods/hl-last-life) +124. **[Life's End](https://www.moddb.com/mods/lifes-end2)** +125. [Macky's Adventure](https://www.moddb.com/mods/mackys-adventure-expansion-set) +126. [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) +127. [Marine Invasion: Episode 1 & 2](https://gamebanana.com/gamefiles/2537) +128. **[Mario Keys](https://www.moddb.com/mods/mario-keys)** +129. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) +130. [Medieval World](https://www.moddb.com/mods/medieval-world) +131. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) +132. **[MINIMICUS](https://twhl.info/vault.php?map=163)** +133. [Misantrophy](https://www.runthinkshootlive.com/posts/misanthropy/) +134. **[Mission Failed](https://www.moddb.com/mods/mission-failed)** +135. [Mission MC Poker](https://www.moddb.com/mods/half-life-mission-mc-poker) +136. **[Mission of Mercy](https://www.runthinkshootlive.com/posts/mission-of-mercy/)** +137. [Mission to Kill](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) +138. [Mission to Kill 2](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) +139. [Mistaken Identity](https://www.runthinkshootlive.com/posts/mistaken-identity/) +140. [Mistaken Identity 2](https://www.runthinkshootlive.com/posts/mistaken-identity-part-2/) +141. **[Moonwalker](https://www.moddb.com/mods/moonwalker)** +142. [Mystery House v1.0](https://www.moddb.com/mods/mystery-house) +143. [NewBlackMesaVille](https://www.runthinkshootlive.com/posts/newblackmesaville/) +144. [Night Shooting](https://www.moddb.com/games/half-life/addons/night-shooting) +145. **[No Exit](https://www.moddb.com/mods/no-exit)** +146. [No-Life](https://www.fileplanet.com/120501/120000/fileinfo/No-Life) +147. **[Nuclear Power Plant](https://www.runthinkshootlive.com/posts/nuclear-power-plant/)** +148. [Occupied Territory](https://www.moddb.com/games/half-life/addons/occupied-territory) +149. [Office Commando](https://www.runthinkshootlive.com/posts/office-commando/) +150. **[Operacja Gargantua](https://www.runthinkshootlive.com/posts/operation-gargantua/)** +151. **[Operacja Mirra](https://unquenque.com/tenfour/)** +152. **[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) +153. **[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) +154. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) +155. [Outrun](https://www.moddb.com/mods/outrun) +156. [Outwards (Day One)](https://web.archive.org/web/20161026104825/http://visgi.info/maps/) +157. [Overhaul Pack](https://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) +158. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) +159. [pagoda](https://www.moddb.com/mods/pagoda1) +160. **[Peaces Like Us v1.0](https://www.runthinkshootlive.com/posts/peaces-like-us/)** +161. [Phobos IV](https://www.moddb.com/mods/phobos-iv) +162. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) +163. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) +164. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) +165. **[Prisoner of Event](https://wp.vondur.net/?page_id=40)** +166. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) +167. [Project Quantum Leap](https://www.moddb.com/mods/project-quantum-leap) +168. [Project VIP](https://www.pcosmos.ca/mods-hl/downloads/?mod=projectVIP) +169. **[Projekt Einstein](https://unquenque.com/tenfour/)** +170. [Recon( Reconnaissance )](https://www.moddb.com/games/half-life/addons/reconnaissance) +171. [Red Mesa](https://www.moddb.com/mods/red-mesa-1) +172. [Red Mesa 2](https://www.moddb.com/mods/red-mesa-2) +173. [RES v1.0](https://www.fileplanet.com/29002/20000/fileinfo/RES) +174. [Rescue 9-1-Freeman](https://www.moddb.com/mods/rescue-9-1-freeman) +175. [Resistance](https://www.runthinkshootlive.com/posts/resistance/) +176. [Resistance 2](https://www.runthinkshootlive.com/posts/resistance-2/) +177. [Resistance 3](https://www.runthinkshootlive.com/posts/resistance-3/) +178. [Return](https://www.runthinkshootlive.com/posts/return/) +179. [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) +180. [Reviviscence](https://www.moddb.com/mods/reviviscence) +181. [Road of Destiny](https://www.fileplanet.com/10393/10000/fileinfo/Road-of-Destiny) +182. **[Rooms: TWHL COOP Project( Rooms Half-Life or TWHL Rooms )](https://twhl.info/vault.php?map=5394)** +183. [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*) +184. [S.W.A.T.](https://www.moddb.com/mods/swat2) +185. [Sabotage](https://www.runthinkshootlive.com/posts/sabotage-2/) +186. [Sagharmath](https://www.moddb.com/games/half-life/addons/sagharmath) +187. [Sandscroll](https://gamebanana.com/maps/15988) +188. [Saving Bob](https://www.runthinkshootlive.com/posts/saving-bob/) +189. [Secret Base](https://www.runthinkshootlive.com/posts/secret-base/) +190. **[Secret Santa 2011 for Soup Miner](https://twhl.info/vault.php?map=5724)** +191. [Secret Santa 2011 for Stojke](https://twhl.info/vault.php?map=5728) +192. [Senses in Decay](https://www.moddb.com/mods/senses-in-decay) +193. [Shift-Two v1.1](https://www.moddb.com/mods/shift-two1) +194. [Sky Mesa](https://www.moddb.com/mods/sky-mesa) +195. [Sleephorst v1.5](https://www.moddb.com/mods/sleephorst-v15) +196. [Slimy Situation](https://www.moddb.com/mods/slimy-situation) +197. **[Smart Decoy](https://www.moddb.com/mods/smart-decoy)** +198. **[Somewhere in Time](https://www.runthinkshootlive.com/posts/somewhere-in-time/)** +199. [Sooper2](https://www.runthinkshootlive.com/posts/sooper-2/) +200. **[Split-Second](https://twhl.info/vault.php?map=4343)** +201. **[Subhumanity](https://www.runthinkshootlive.com/posts/sub-humanity/)** +202. Terminal Monstrosity (4 maps by Anthony Plant) +203. [Terror Side](https://www.runthinkshootlive.com/posts/terror-side/) (set **sv_validate_changelevel** to **0** to avoid problem with level change between some maps) +204. [Test Your Skill](https://www.moddb.com/games/half-life/addons/test-your-skill) +205. [The Challenger Deep](https://www.runthinkshootlive.com/posts/challenger-deep/) +206. **[The Challenger Deep 2](https://www.moddb.com/mods/the-challenger-deep-2)** +207. [The Evil Thing](https://www.moddb.com/mods/the-evil-thing) +208. [The Evil World](https://www.moddb.com/mods/the-evil-world) +209. [The Haunted Lab](https://www.runthinkshootlive.com/posts/the-haunted-lab/) +210. **[The Long Night](https://www.moddb.com/mods/the-long-night)** +211. [The Mansion](https://twhl.info/vault.php?map=2438) +212. [The Night Things](https://twhl.info/vault.php?map=2439) +213. [The Night Things 2: Woodstock Manor](https://twhl.info/vault.php?map=2706) +214. [The Returning](https://www.moddb.com/games/half-life/addons/the-returning) +215. [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) +216. [The Way Is Clear](https://www.runthinkshootlive.com/posts/the-way-is-clear/) +217. [The Way Is Clear 2](https://www.runthinkshootlive.com/posts/the-way-is-clear-2/) +218. **[The World Machine](https://www.moddb.com/mods/half-life-the-world-machine)** +219. **[The Xeno Project](https://www.moddb.com/mods/xeno-project-i-ii)** +220. **[The Xeno Project 2](https://www.moddb.com/mods/xeno-project-i-ii)** +221. [Then and Now](https://twhl.info/vault.php?map=6048) +222. [They Live( Chungo )](https://www.runthinkshootlive.com/posts/they-live/) +223. [Timefall](https://www.moddb.com/mods/hl-timefall) +224. **[Timeline](https://www.moddb.com/mods/timeline-series)** +225. **[Timeline II: Iced Earth](https://www.moddb.com/mods/timeline-series)** +226. **[Todesangst](https://www.moddb.com/mods/todesangst)** +227. **[Tokami Island](https://www.fileplanet.com/110517/110000/fileinfo/Tokami-Island)** +228. [Tower](https://hlperfectzone.narod.ru/mods.htm) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) +229. [Trespasser](https://www.moddb.com/mods/trespasser) +230. [Try, Try Again](https://www.runthinkshootlive.com/posts/try-try-again/) +231. [Tucked Up](https://www.moddb.com/games/half-life/addons/tucked-up) +232. [TWHLmix](https://twhl.info/vault.php?map=2516) +233. [Two Smoking Barrels](https://www.runthinkshootlive.com/posts/two-smoking-barrels/) +234. [Typical Disaster](https://www.moddb.com/mods/typical-disaster) +235. [Typical Disaster: The Lost Levels](https://www.runthinkshootlive.com/posts/typical-disaster-the-lost-levels/) +236. [U-Life](https://www.moddb.com/mods/u-life) +237. **[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) +238. [Underground Territory](https://www.moddb.com/games/half-life/addons/underground-territory-part-1) +239. [Undertime](https://twhl.info/vault.php?map=3906) +240. **[Uplink Addon v1.2](https://www.fileplanet.com/82828/80000/fileinfo/Half-Life-Uplink-Addon-1.2)** +241. **[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)** +242. **[Uplinked](https://www.moddb.com/mods/uplinked)** +243. **[USS Darkstar](https://www.moddb.com/mods/uss-darkstar)** +244. [Vengeance](https://www.moddb.com/mods/half-life-vengeance) +245. [Virtual Reality: The Real World](https://www.runthinkshootlive.com/posts/virtual-reality-the-real-world/) +246. [VLOKAM](https://www.ceskemody.cz/mapy.php?lng=1&clanek=32&razeni_hra=1&pn=vlokam-1) +247. [VLOKAM 2](https://www.ceskemody.cz/mapy.php?lng=1&clanek=33&razeni_hra=1&pn=vlokam-2) +248. [Wail of Death](https://www.runthinkshootlive.com/posts/wail-of-death/) +249. [Wail of Death 2: The Hell Master](https://www.runthinkshootlive.com/posts/wail-of-death-2-the-hell-master/) +250. [War Crimes v1.2](https://www.moddb.com/games/half-life/addons/war-crimes) +251. [Way to Victory](https://www.moddb.com/mods/2gn) +252. [Windmill v2.0](https://www.runthinkshootlive.com/posts/windmill/) +253. [World War III Missions (parts 1, 2 & 3)](https://www.moddb.com/mods/world-war-iii-missions-1-2-3) +254. [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) +255. [Xen](https://www.runthinkshootlive.com/posts/half-life-xen-2/) +256. [You Are in Army Now](https://www.pcosmos.ca/mods-hl/downloads/?mod=armynow) +257. [Zubben](https://www.runthinkshootlive.com/posts/zubben/) -## Unfinished singleplayer mods and mappacks -1. [Alpha Research Facility v0.3](http://www.moddb.com/games/half-life/downloads/alpha-research-facility-v-03-steam) +### Unfinished singleplayer mods and mappacks +1. [Alpha Research Facility v0.3](https://www.moddb.com/games/half-life/downloads/alpha-research-facility-v-03-steam) 2. [Back in Future Alpha version](https://www.runthinkshootlive.com/posts/back-in-future/) -3. [Biohazard 2 (unfinished mod version)](http://www.moddb.com/mods/biohazard-2-outside-of-black-mesa) -4. [Bloodreign](https://www.runthinkshootlive.com/posts/blood-reign/) (the mod has inner bugs, especially second map) -5. [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) -6. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) -7. [Cro-man's Office Mappack](http://twhl.info/vault.php?map=5547) -8. [Half-Life Episode Two Demo Alpha v1.0](http://hl.loess.ru/?mod=451) -9. [Hazardous-Course (first version of Hazardous-Course 2)](https://drive.google.com/file/d/16djJZSpNWKyScSxSyMqX-u_S-2i400rl/view?usp=sharing) -10. [High Tech v0.2](http://hl.loess.ru/?mod=499) -11. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) -12. [HLNewEnd](http://twhl.info/vault.php?map=2275) -13. [Kleiners Adventures Demo](http://www.moddb.com/mods/kleiners-adventures) -14. [Kleiners Adventures: The White Line Demo](https://www.moddb.com/mods/kleiners-adventure-siemka321-edition) -15. [LV-426: Episode 1](https://www.runthinkshootlive.com/posts/lv426-version-a/) -16. [Malevolence(1.3 and older)](http://www.moddb.com/mods/malevolence) -17. [Meth-Life Demo v1.0](https://www.gamewatcher.com/mods/half-life-mod/meth-life-mod-demo-1-0) -18. [Night-Fire Demo](http://efreeman1.narod.ru/Index.htm) -19. [On the Other Side](http://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) -20. [Orion: Part 1( Project Orion )](http://www.moddb.com/mods/orionold) -21. [Padle Mesto Demo](http://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) -22. [Panic at Black Mesa Demo](http://www.moddb.com/mods/half-life-panic-at-black-mesa) -23. [Plan B v1.1](https://www.fileplanet.com/129198/120000/fileinfo/Plan-B-SP-Mappack) -24. [Project Focus North v1.6 Demo](https://www.fileplanet.com/84637/80000/fileinfo/Project-Focus-North) -25. [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) -26. [Return to Lambdacore Demo](http://www.moddb.com/games/half-life/addons/return-to-lamdacore) -27. [Route City Beta 1](http://www.moddb.com/mods/route-city) -28. [SHAFT - Part 1](http://hl.loess.ru/?mod=913) -29. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) -30. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) -31. [Striker's Compo 26](http://twhl.info/vault.php?map=5190) (buggy) -32. [Technology Test 2](https://drive.google.com/file/d/1LfldzsMIN5j07bgtNkfY5v0Xl0S9de_G/view?usp=sharing) -33. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) -34. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) -35. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) -36. [Time-Shift](https://drive.google.com/file/d/1pK1s-2SIlSMQHVRPMLdC_94wTa__8_DX/view?usp=sharing) -37. [Train Single Beta](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (Remove **gfx.wad** from **TrainSingle** folder) -38. [WAR: The Killer Beta 0.1](http://www.moddb.com/mods/war) -39. [White Force Beta( Residual Point prototype )](http://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) -40. [Your First Mission Demo](http://twhl.info/forums.php?thread=3925) -41. [Zombieland v1.1](http://www.moddb.com/mods/zombieland) +3. [Biohazard 2 (unfinished mod version)](https://www.moddb.com/mods/biohazard-2-outside-of-black-mesa) +4. Black Meat (unfinished mappack of 3 maps) +5. [Bloodreign](https://www.runthinkshootlive.com/posts/blood-reign/) (the mod has inner bugs, especially second map) +6. [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) +7. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) +8. [Cro-man's Office Mappack](https://twhl.info/vault.php?map=5547) +9. Great Escape (unfinished mappack of 2 maps) +10. [Half-Life Episode Two Demo Alpha v1.0](https://www.moddb.com/games/half-life/downloads/half-life-episode-two-v10-demo) +11. Half-Life: Marine Demo (by EDOC32) +12. [Hazardous-Course (first version of Hazardous-Course 2)](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) +13. [High Tech v0.2](https://hl.loess.ru/?mod=499) +14. [HL: Paranormal Demo](https://gamebanana.com/mods/157378) +15. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) +16. [HLNewEnd](https://twhl.info/vault.php?map=2275) +17. [Kleiners Adventures Demo](https://www.moddb.com/mods/kleiners-adventures) +18. [Kleiners Adventures: The White Line Demo](https://www.moddb.com/mods/kleiners-adventure-siemka321-edition) +19. [LV-426: Episode 1](https://www.runthinkshootlive.com/posts/lv426-version-a/) +20. [Malevolence(1.3 and older)](https://www.moddb.com/mods/malevolence) +21. [Meth-Life Demo v1.0](https://www.gamewatcher.com/mods/half-life-mod/meth-life-mod-demo-1-0) +22. [Night-Fire Demo](https://efreeman1.narod.ru/Index.htm) +23. [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) +24. [Orion: Part 1( Project Orion )](https://www.moddb.com/mods/orionold) +25. [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) +26. [Panic at Black Mesa Demo](https://www.moddb.com/mods/half-life-panic-at-black-mesa) +27. [Plan B v1.1](https://www.fileplanet.com/129198/120000/fileinfo/Plan-B-SP-Mappack) +28. [Preview of the "Paradox"](https://www.runthinkshootlive.com/posts/half-life-preview-of-the-paradox/) +29. [Project Focus North v1.6 Demo](https://www.fileplanet.com/84637/80000/fileinfo/Project-Focus-North) +30. [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) +31. [Return to Lambdacore Demo](https://www.moddb.com/games/half-life/addons/return-to-lamdacore) +32. [Route City Beta 1](https://www.moddb.com/mods/route-city) +33. [SHAFT - Part 1](https://hl.loess.ru/?mod=913) +34. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) +35. Stargate Test (3 maps, it's earliest outlines of known Stargate mod) +36. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) +37. [Striker's Compo 26](https://twhl.info/vault.php?map=5190) (buggy) +38. [Technology Test 2](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) +39. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) +40. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) +41. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) +42. [Time-Shift](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) +43. [Train Single Beta](https://csm.dev/threads/half-strike-rezultaty.36394/) (Remove **gfx.wad** from **TrainSingle** folder) +44. [WAR: The Killer Beta 0.1](https://www.moddb.com/mods/war) +45. [White Force Beta( Residual Point prototype )](https://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) +46. [Your First Mission Demo](https://twhl.info/forums.php?thread=3925) +47. [Zombieland v1.1](https://www.moddb.com/mods/zombieland) -## Just mappacks +### Just mappacks 1. [AvsM 1 v1.2](https://www.moddb.com/mods/avsm-1-2003-version-re-release/) -2. [Cook The Headcrab Episodes 1 & 2](http://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-series) -3. [Cook The Headcrab Episode 3](http://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-3) -4. [Cook The Headcrab Episode 4](http://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-4) -5. [DAV HL Pack 1](http://www.moddb.com/games/half-life/addons/dav-level-pack-1-hlarches-and-hlattack) +2. [Cook The Headcrab Episodes 1 & 2](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-series) +3. [Cook The Headcrab Episode 3](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-3) +4. [Cook The Headcrab Episode 4](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-4) +5. **[DAV HL Pack 1](https://www.moddb.com/games/half-life/addons/dav-level-pack-1-hlarches-and-hlattack)** 6. [Discoman v2.2](https://www.fileplanet.com/117675/110000/fileinfo/Discoman-Mod) -7. [Dramatic Measures( Surprise! )](http://twhl.info/vault.php?map=5624) -8. [Funny Map Pack 1](http://www.mediafire.com/file/4iis8uhfj1ct8sa/Funny_Map_Pack_1_by_Richman.zip) -9. [Half-Life Shorts](http://twhl.info/vault.php?map=2845) -10. [Half-Starwars](http://www.pcosmos.ca/mods-hl/downloads/?mod=hlstarwars) +7. [Dramatic Measures( Surprise! )](https://twhl.info/vault.php?map=5624) +8. **[Funny Map Pack 1](https://www.mediafire.com/file/4iis8uhfj1ct8sa/Funny_Map_Pack_1_by_Richman.zip)** +9. **[Half-Life Shorts](https://twhl.info/vault.php?map=2845)** +10. [Half-Starwars](https://www.pcosmos.ca/mods-hl/downloads/?mod=hlstarwars) 11. [Japanese Episodes](https://www.runthinkshootlive.com/posts/japanese-episodes/) -12. [K7 Trouble](http://www.moddb.com/games/half-life/addons/k7-trouble) (Remove string **secure 1** from **liblist.gam**) +12. [K7 Trouble](https://www.moddb.com/games/half-life/addons/k7-trouble) (Remove string **secure 1** from **liblist.gam**) 13. [Night Shift Beta](https://www.runthinkshootlive.com/posts/night-shift/) 14. [Pillars of Pain: A Buddy & Kona Saga( PoP-BaKs )](https://www.runthinkshootlive.com/posts/pillars-of-pain/) -15. [Star Wars Half-Life](http://twhl.info/vault.php?map=274) (second map running via console using **map swpart2** cmd) +15. [Star Wars Half-Life](https://twhl.info/vault.php?map=274) (second map running via console using **map swpart2** cmd) 16. [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**) -17. [The Mansion](http://twhl.info/competitions.php?results=14) by *Unbreakable* +17. [The Mansion](https://twhl.info/competitions.php?results=14) by *Unbreakable* 18. [TS_MF](https://www.runthinkshootlive.com/posts/ts_mf/) -19. [TWHL Cubicles](http://twhl.info/vault.php?map=5499) -20. [Valve ERC Contest #1 - The martyred pop machine](https://www.runthinkshootlive.com/posts/contest-1-maps/) -21. [Valve ERC Contest #2 - Best train-ride sequence](https://www.runthinkshootlive.com/posts/contest-2-maps/) -22. [VOLCAN Beta 1.3](http://hl.loess.ru/?mods=&search=Volcan) +19. [TWHL Cubicles](https://twhl.info/vault.php?map=5499) +20. **[Valve ERC Contest #1 - The martyred pop machine](https://www.runthinkshootlive.com/posts/contest-1-maps/)** +21. **[Valve ERC Contest #2 - Best train-ride sequence](https://www.runthinkshootlive.com/posts/contest-2-maps/)** +22. [VOLCAN Beta 1.3](https://www.moddb.com/games/half-life/addons/volcan-beta13) 23. [Woodpigeon's Map Pack](https://www.fileplanet.com/63729/60000/fileinfo/Woodpigeon's-Map-Pack) -## Singleplayer mods or mappacks with a little incompatibilities -there is a same critical issue in first 9 mods - an unworking level transition from a first map to a second one; to overcome the bug, follow these steps: +### Singleplayer mods or mappacks with a little incompatibilities +1. [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**) +2. [Final Run](https://www.runthinkshootlive.com/posts/final-run/) +3. [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) +4. [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) +5. [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) +6. [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) -1) start a second map of a mod - **2009eb_map2** / **HLRE2** / **deth2** / **paranormal2** / **jail2** / **map2** / **ward** / **mistake2** / **SAJ2A** - manually via console +### Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries +1. [Citizen Arms Demo 2](https://www.moddb.com/mods/citizen-arms) (there are few inner bugs in the mod, but it still playable) +2. [DALEK unbidden](https://www.moddb.com/mods/dalek-unbidden) +3. [Fight for Life](https://www.moddb.com/mods/fight-for-life) +4. [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) +5. **[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)** +6. [Soldier](https://www.moddb.com/mods/half-life-soldier) +7. [Solo Operations](https://www.moddb.com/mods/solo-operations) +8. [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) +9. [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) +10. [Wilson Chronicles: The Unfinished Edition](https://www.gamewatcher.com/mods/half-life-mod/half-life-wilson-chronicles) -2) step back to activate a level transition to the previous map - -3) type restart into console - -4) play with working transition! - -Mods: - -1. [2009EB](http://hldm.ucoz.ru/forum/5-185-1) -2. [A Day in the Life of a Coward](https://www.runthinkshootlive.com/posts/a-day-in-the-life-of-a-coward/) -3. [Deth](https://www.runthinkshootlive.com/posts/deth/) -4. [HL: Paranormal Demo](https://gamebanana.com/maps/165193) -5. [Jailbreak](https://www.runthinkshootlive.com/posts/jailbreak/) -6. [Misantrophy](https://www.runthinkshootlive.com/posts/misanthropy/) -7. [Mistaken Identity](https://www.runthinkshootlive.com/posts/mistaken-identity/) -8. [Mistaken Identity 2](https://www.runthinkshootlive.com/posts/mistaken-identity-part-2/) -9. [The Returning](http://www.moddb.com/games/half-life/addons/the-returning) -10. [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**) -11. [Final Run](https://www.runthinkshootlive.com/posts/final-run/) -12. [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) -13. [Office Commando](https://www.runthinkshootlive.com/posts/office-commando/) (changelevel bug between 1st, 2nd and 3rd maps) -14. [Preview of the "Paradox"](https://www.runthinkshootlive.com/posts/half-life-preview-of-the-paradox/) (changelevel bug between 1st, 2nd and 3rd maps) -15. [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) -16. [Threatening Skies](http://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) - -## Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries -1. [Big Lolly](http://www.moddb.com/mods/big-lolly) (Windows version has pink HUD) -2. [Citizen Arms Demo 2](http://www.moddb.com/mods/citizen-arms) (there are few inner bugs in the mod, but it still playable) -3. [DALEK unbidden](http://www.moddb.com/mods/dalek-unbidden) -4. [Fight for Life](http://www.moddb.com/mods/fight-for-life) -5. [Half-Life Baby v1.4](http://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) -6. [Half-Secret](http://www.moddb.com/mods/half-secret) (this mod has custom **weapon_snark** code) -7. [Induction](http://www.moddb.com/mods/half-life-induction) (this mod has new item - **item_flashlight**) -8. [Lost in Black Mesa(first version without HLFX)](https://drive.google.com/file/d/1bEnm_AxJs-ly8hTEZIQBw_v2BsXdSiGa/view?usp=sharing) -9. [Soldier](http://www.moddb.com/mods/half-life-soldier) -10. [Solo Operations](http://www.moddb.com/mods/solo-operations) -11. [The Blood v1.1](http://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress) -12. [The Escape](http://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) -13. [Wilson Chronicles: The Unfinished Edition](https://www.gamewatcher.com/mods/half-life-mod/half-life-wilson-chronicles) - -## Singleplayer mods with one map or single maps -1. [3rd505th](http://hl.loess.ru/?mod=5) -2. [5 More Ways to Die](http://twhl.info/vault.php?map=3575) -3. [5 Ways to Die](http://twhl.info/vault.php?map=2984) -4. [5 Worse Ways to Die](http://twhl.info/vault.php?map=4029) -5. [A Bad Day](http://twhl.info/competitions.php?results=6) +### Singleplayer mods with one map or single maps +1. [3rd505th](https://hl.loess.ru/?mod=5) +2. [5 More Ways to Die](https://twhl.info/vault.php?map=3575) +3. [5 Ways to Die](https://twhl.info/vault.php?map=2984) +4. [5 Worse Ways to Die](https://twhl.info/vault.php?map=4029) +5. **[A Bad Day](https://twhl.info/competitions.php?results=6)** 6. [A Disgruntled Christmas](https://www.fileplanet.com/7882/0/fileinfo/A-Disgruntled-Christmas) 7. [Accidental Life Demo](https://www.runthinkshootlive.com/posts/accidental-life/) -8. [Aftermath](https://www.fileplanet.com/112021/110000/fileinfo/Aftermath) +8. **[Aftermath](https://www.fileplanet.com/112021/110000/fileinfo/Aftermath)** 9. [Ali Meyer](https://gamebanana.com/maps/178855) -10. [Alien](http://hl.loess.ru/?mods=&search=alien) (map by *Nicolas Gadenne*) +10. [Alien](https://hl.loess.ru/?mods=&search=alien) (map by *Nicolas Gadenne*) 11. [Alien Blast](https://www.fileplanet.com/136194/130000/fileinfo/Alien-Blast) -12. [Alien Survival](http://www.moddb.com/games/half-life/addons/alien-survival-map) +12. [Alien Survival](https://www.moddb.com/games/half-life/addons/alien-survival-map) 13. [American Training Facility](https://gamebanana.com/maps/146598) 14. [An Escape](https://gamebanana.com/maps/167506) 15. [Area Assault](https://www.fileplanet.com/7462/0/fileinfo/Area-Assault) 16. [Assassination](https://www.runthinkshootlive.com/posts/assassination/) -17. [Atom's Mini Compo by Tetsu0](http://twhl.info/vault.php?map=5983) -18. [Atom's Mini Compo by zeeba-G](http://twhl.info/vault.php?map=5985) -19. [B.O.G aka Black.Orange.Grenade](http://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 ) +17. [Atom's Mini Compo by Tetsu0](https://twhl.info/vault.php?map=5983) +18. [Atom's Mini Compo by zeeba-G](https://twhl.info/vault.php?map=5985) +19. [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 ) 20. [Barney's Dream Demo( B-dream Beta )](https://www.fileplanet.com/112446/110000/fileinfo/B-dream-Beta) -21. [Barrel of Grunts](http://twhl.info/vault.php?map=3390) -22. [Beach Party](http://twhl.info/vault.php?map=5572) -23. [Beastie](https://www.runthinkshootlive.com/posts/beastie/) -24. [Black Mesa South](http://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) -25. [Black Mesa Storage Facility - Bay A2](https://www.fileplanet.com/7479/0/fileinfo/Black-Mesa-Storage-Facility---Bay-A2) -26. [Black Mesa Xmas(A Black Mesa Christmas)](http://twhl.info/vault.php?map=4918) -27. [Blood and Guts](https://www.runthinkshootlive.com/posts/blood-and-guts/) -28. [Blood1](http://www.richmans-maps.ch.vu/) (link dead!) -29. [Bhop](http://twhl.info/vault.php?map=5065) -30. [Breakout](https://www.runthinkshootlive.com/posts/breakout-by-benny-blanco/) (map for *Single Mapping Competition* by *BennyBlanco*) -31. [Breakout](http://hl.loess.ru/?mods=&search=Breakout) (map by *Mediocre MapGuy*) -32. [Bullsuid Will Survive](http://www.moddb.com/games/half-life/addons/bullsuid-will-survive) -33. [C2](https://www.runthinkshootlive.com/posts/c2/) -34. [C3](https://www.runthinkshootlive.com/posts/c3/) -35. [C5](https://www.runthinkshootlive.com/posts/c5/) -36. [Caged](https://store.steampowered.com/app/679990/HalfLife_Caged/) -37. [Camera Puzzle](http://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) -38. [CataXen](http://twhl.info/vault.php?map=3429) -39. [Cause of Death](http://twhl.info/competitions.php?results=7) -40. [Challenge](http://www.snarkpit.net/index.php?s=maps&map=3266) (map by *tnkqwe*) -41. [CHALLENGE](http://twhl.info/vault.php?map=2229) aka Can You Live (map by *killer487554*; remove spaces in the map name before you play) -42. [Choices](https://www.runthinkshootlive.com/posts/choices/) -43. [Chuck - Texas Rangers](http://twhl.info/vault.php?map=4253) -44. [Coach](https://www.runthinkshootlive.com/posts/coach/) -45. [Cook The Headcrab](http://www.moddb.com/games/half-life/addons/cook-the-headcrab) -46. [Core01](http://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design competition* by *Flash*) -47. [Crawler](https://www.runthinkshootlive.com/posts/crawler/) -48. [Crysis 3](http://hl.loess.ru/?mod=215) (map for *Single Mapping Competition* by *Raid*) -49. [CUBE](http://twhl.info/competitions.php?results=7) (map by *Jobabob*) -50. [danger1](http://twhl.info/vault.php?map=2302) -51. [Data-base](https://www.runthinkshootlive.com/posts/database/) -52. [de_dust2_azabetfeN](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game) -53. [De-railed](http://twhl.info/competitions.php?results=7) -54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://drive.google.com/file/d/1uHvr8xONogTTNOy-8X6G4ehFV6Eawvbq/view?usp=sharing) -55. [Deep](http://twhl.info/vault.php?map=1669) -56. [Desert Attack](http://fyzzer.narod.ru/index.html) -57. [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) -58. [Desert Strike](http://hl.loess.ru/?mod=267) -59. [Devil Mesa](http://hosting.cecak.cz/forum-modifikace/hl1/index.php?text=mod&modifikace=dm) -60. [Disco Party v1.1](http://twhl.info/vault.php?map=1004) -61. [dissolution](http://twhl.info/vault.php?map=5494) (you need to have additional textures to play this map properly - **nw.wad** from [Nightwatch Texture Pack](http://www.moddb.com/games/half-life/addons/nightwatch-texture-pack) and **decals.wad** from Opposing Force) -62. [Doomed Demo](http://www.snarkpit.net/index.php?s=maps&map=3351) -63. [Dream1_ver3](http://twhl.info/vault.php?map=579), [Dream1_ver2](http://twhl.info/vault.php?map=371), [Dream1](http://twhl.info/vault.php?map=359) -64. [Dressed to Kill](https://www.fileplanet.com/48578/40000/fileinfo/Dressed-to-kill) -65. [Dying at Sea( Signs )](http://twhl.info/vault.php?map=5379) -66. [eif coloseum](https://www.runthinkshootlive.com/posts/eif-coloseum/) -67. [eif_room](https://www.runthinkshootlive.com/posts/eif-room/) -68. [eif_school](https://www.runthinkshootlive.com/posts/eif-school/) -69. [Emissary](http://twhl.info/competitions.php?results=15) -70. [En Route 66](http://www.moddb.com/mods/en-route-66) (set **-num edicts 4096** in command-line parameters for successful start) -71. [Enclosed Space](http://twhl.info/vault.php?map=4496) -72. [Endlevel Boss](http://twhl.info/vault.php?map=786) -73. [ES](https://cs-mapping.com.ua/forum/showthread.php?t=29017) (map for *Single Mapping Competition* by *Flash*) -74. [escape](http://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) -75. [Escape from Black Mesa v1.44](http://twhl.info/vault.php?map=) (map from *TWHL* by *Satchmo*) (wrong link!) -76. [Escape Off](http://hl.loess.ru/?mod=324) (a part of this map was used later in *Friendship 2.0* mod) -77. [Evasion](https://www.fileplanet.com/53430/50000/fileinfo/Half-life-:-Evasion) -78. [Evil Space](https://www.runthinkshootlive.com/posts/evil-space/) -79. [Experimental Problems](https://www.runthinkshootlive.com/posts/experimental-problems/) -80. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) -81. [Facility](http://twhl.info/vault.php?map=5482) -82. [Facility Escape](http://twhl.info/vault.php?map=3673) -83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) -84. [Final Assault](http://twhl.info/vault.php?map=4500) -85. [Flat](http://hl.loess.ru/?mods=&search=Flat) -86. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) -87. [Freeman's Escape](https://www.runthinkshootlive.com/posts/freemans-escape/) (map by *Dave Crabb*) -88. [Freeman's Suicide( Kill Yourself )](https://www.runthinkshootlive.com/posts/kill-yourself/) -89. [Funhouse](http://twhl.info/competitions.php?results=11) -90. [Func_breakable - The Invasion](http://hl.loess.ru/?mod=384) (map from *TWHL* by *Archie* aka *The Hunter*) -91. [Genetic Research Facility](http://twhl.info/vault.php?map=4775) -92. [Gladiator](https://cs-mapping.com.ua/forum/showthread.php?t=36140) -93. [Go to Xen Awalk(Xen Walk/SP-Offyxen)](http://www.moddb.com/mods/go-to-xen-awalk-half-life-map) -94. [goldsource](http://twhl.info/vault.php?map=5737) (link dead!) -95. [Govnomod](http://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) -96. [Govnomod: Mysterious Force](http://forums.playground.ru/half-life/g_vnomod-640221/) -97. [GruntMatch](https://www.runthinkshootlive.com/posts/grunt-match/) -98. [Grunts Domain( archiveSP01 )](https://www.runthinkshootlive.com/posts/grunts-domain/) -99. [Gunship v1](https://www.runthinkshootlive.com/posts/gunship/) -100. [Half-Life 1: Traptown E3 2003](https://gamebanana.com/maps/168032) -101. [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) -102. [Hard Way](http://hl.loess.ru/?mod=480) -103. [Headcrab Revenge](http://twhl.info/vault.php?map=3519) -104. [Headcrab-BOSS](http://www.moddb.com/games/half-life/downloads/headcrab-boss) -105. [HL Dance( Half-Dance )](http://twhl.info/vault.php?map=2991) -106. [hl_egzekucja](https://gamebanana.com/maps/50132) -107. [HLywood](http://twhl.info/vault.php?map=455) -108. [Hnaii - Office Komplex](https://www.fileplanet.com/113757/110000/fileinfo/Hnaii---Office-Komplex) -109. [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) -110. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) -111. [House](http://www.artpeter.net/Data/HalfLife/Hl.php) -112. [Impulse 101 Fun - The Train](https://drive.google.com/file/d/1jAuMCCmREH0mfAEAscqaG8FQGa2uNknb/view?usp=sharing) (map from *TWHL* by *Archie* aka *The Hunter*) -113. [In America](https://drive.google.com/file/d/1gOC8zfnUvBxRy8Rr8juNiUNbLYjoZpnn/view?usp=sharing) -114. [In the Kitchen](http://twhl.info/vault.php?map=4314) -115. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) -116. [Interactivity & Lots of Entities](http://twhl.info/vault.php?map=5744) (link dead!) -117. [Interior](https://www.runthinkshootlive.com/posts/interior/) -118. [Into the Frying Pan](https://www.runthinkshootlive.com/posts/into-the-frying-pan/) -119. [Island Bombing](http://twhl.info/competitions.php?results=13) -120. [Ivy](http://twhl.info/vault.php?map=4869) -121. [Jump Program](http://hl.loess.ru/?mod=575) -122. [Killer on the Run](https://www.runthinkshootlive.com/posts/killer-on-the-run/) -123. [Killing House for Half-Life](https://www.runthinkshootlive.com/posts/killing-house/) -124. [Kosovo 2000](http://www.moddb.com/games/half-life/addons/kosovo-2000) -125. [Kosovo II – The Second Day](http://www.moddb.com/games/half-life/addons/kosovo-2) -126. [KotiteolliSuus](http://twhl.info/vault.php?map=533) -127. [Kyo Half-Strike](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (there are few error messages at start, which can be safely skipped; also the code in the beginning is 3141) -128. [l33t Test 1: Conclusive Analysis](http://twhl.info/vault.php?map=3023) -129. [Lab](http://twhl.info/vault.php?map=1797) -130. [Lambda Station](https://www.runthinkshootlive.com/posts/lambda-station/) -131. [LC](https://www.runthinkshootlive.com/posts/lc-by-sluxe/) (map for *Fast Level Design* competition by *Slux*) -132. [LDSF( Laser Deployed Security Force )](http://twhl.info/competitions.php?results=4) -133. [Locked Up](https://www.fileplanet.com/8842/0/fileinfo/Locked-Up) -134. [Looping Stage](http://web.archive.org/web/20041104030218/http://cariad.co.za/twhl/mapvault_map.php?id=1250) -135. [Lord's Lair](https://www.fileplanet.com/7471/0/fileinfo/Lords-Lair) -136. [Losspower](http://twhl.info/vault.php?map=3919) -137. [Lost-World](https://gamebanana.com/maps/156214) -138. [Lounge](https://www.fileplanet.com/7461/0/fileinfo/The-Lounge) -139. [MatrixTrainstation](http://twhl.info/vault.php?map=2531) -140. [Maze01a](http://www.richmans-maps.ch.vu/) (link dead!) -141. [Meat, Blood, Gun](http://www.moddb.com/games/half-life/addons/meat-blood-gun) (there are 2 maps, but only first map is playable, in fact) -142. [Merry Christmas to hlife_hotdog aka *Happy Holidays*](http://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) -143. [Museum Lockdown](http://twhl.info/vault.php?map=2211) -144. [My Backyard](http://twhl.info/vault.php?map=1796) -145. [My Black Mesa](https://www.runthinkshootlive.com/posts/my-black-mesa/) (map for *Single Mapping Competition* by *Zanzer*) -146. [Nameless](https://www.runthinkshootlive.com/posts/nameless/) (map for *Single Mapping Competition* by *AGRESSOR*) -147. [Need for Energy(SP-Energy)](http://scrama.3dn.ru/load/2-1-0-2) -148. [Nightmare: A horror map](http://twhl.info/vault.php?map=2106) -149. [Nightshift_FsC](http://twhl.info/vault.php?map=2561) -150. [No Chance](https://gamebanana.com/maps/160536) -151. [No Regret](https://www.runthinkshootlive.com/posts/no-regret/) -152. [Nuclear Aftermath](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map **48h_map1_pre** by *Bluthund*) -153. [Nuke](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map **tmdnuke** by *the-middleman*) -154. [Observatory](https://www.runthinkshootlive.com/posts/observatory/) -155. [Ominous Reality: Part 1](http://twhl.info/vault.php?map=1328) -156. [Operation Rainbow](http://twhl.info/vault.php?map=3068) -157. [Operation Randomosity](http://www.moddb.com/games/half-life/addons/operation-randomosity) -158. [Orb](http://twhl.info/competitions.php?results=20) -159. [Osprey Chopper Competition](http://twhl.info/competitions.php?results=1) (map by *Andy*) -160. [Otage Beta v0.05](http://hl.loess.ru/?mods=&search=Otage) -161. [Outpost](https://www.fileplanet.com/81860/80000/fileinfo/Outpost) -162. [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**) -163. [Pac-Man](http://twhl.info/competitions.php?results=29) -164. [Parallax Beta](https://www.runthinkshootlive.com/posts/parallax/) (there are 2 maps, but only first map is playable, in fact) -165. [Postal](https://www.runthinkshootlive.com/posts/postal/) -166. [Problems in Building 2](https://gamebanana.com/maps/166032) -167. [Pulse: Demo #1](http://www.moddb.com/mods/pulse/downloads/pulse-demo-1) -168. [Quake](https://www.runthinkshootlive.com/posts/quake/) -169. [Quilted Thought Organ](https://www.runthinkshootlive.com/posts/quilited-thought-organ/) -170. [Radix](http://unquenque.com/radix.html) (this map is also a part of *Project Quantum Leap* mod) -171. [Rat Hunt: Quarters](http://twhl.info/vault.php?map=5990) (there is only 1 little issue - rats counter messages do not displayed until all rats are eliminated) -172. [Remember All](https://cs-mapping.com.ua/forum/showthread.php?t=29029) -173. [Rescue](http://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) -174. [Rescue 2](http://www.moddb.com/games/half-life/addons/rescue-2) (map by *SaCo*) -175. [Revamp](https://www.runthinkshootlive.com/posts/revamp/) -176. [Rogat](https://www.runthinkshootlive.com/posts/rogat/) -177. [Rooms](http://twhl.info/vault.php?map=24) (map by *ghost2*) -178. [Ruled by Insanity](http://twhl.info/vault.php?map=3580) -179. [Rube Goldberg](http://twhl.info/vault.php?map=5014) aka Entity Challenge 2(map by *Captain Terror*) -180. [Rube Goldberg Machine](http://twhl.info/vault.php?map=5006) (map by *TJB*) -181. [Rum](https://www.runthinkshootlive.com/posts/rum/) -182. [Rumble](http://hl.loess.ru/?mods=&search=Rumble) -183. [Run, Run, Run!](http://www.moddb.com/games/half-life/addons/run-run-run) -184. [Runaway](http://hl.loess.ru/?mods=&search=Runaway) -185. [Runder](https://www.runthinkshootlive.com/posts/runder/) -186. [s_3_LumbdaCore](http://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design* competition by *Sania3*) -187. [Saving Santa](http://twhl.info/vault.php?map=5726) ( Secret Santa 2011 for *Rimrook* by *Urby* ) -188. [sand_01](http://twhl.info/vault.php?map=3166) -189. [SciMaker 1.1 (With Kill Func)](http://twhl.info/vault.php?map=1845) -190. [Scientist Killing](http://twhl.info/vault.php?map=1969) -191. [Scientists Hideout](http://www.isolated-design.de/half-life-mods/thewall-48h-contest/) (map **48h_m01** by *ToTac*) -192. [Scramble](http://www.moddb.com/mods/scramble) -193. [Seek and Destroy](https://www.fileplanet.com/13006/10000/fileinfo/Seek-and-Destroy) -194. [SelfKill](http://www.moddb.com/games/half-life/addons/selfkill) -195. [SEMTEX](http://twhl.info/competitions.php?results=7) (map by *kol*) -196. [Sepulcher](https://www.fileplanet.com/7478/0/fileinfo/Sepulcher) -197. [Shootout Alpha 3](https://www.fileplanet.com/111687/110000/fileinfo/Shootout-ALPHA-3) -198. [Small Battle](http://twhl.info/vault.php?map=2337) -199. [Small Battle 2](http://twhl.info/vault.php?map=2393) -200. [Smash Half-Life](https://www.runthinkshootlive.com/posts/smash/) -201. [Snatch](http://jqbros.3dn.ru/load/1-1-0-9) -202. [Soft Boiled](https://www.fileplanet.com/7475/0/fileinfo/Soft-Boiled) -203. [Someplace Else](https://hylobatidae.org/minerva/parallax/someplace-else.html) (this map is also a part of *Project Quantum Leap* mod) -204. [Somewhere( Kasperg: Unique Map )](http://twhl.info/competitions.php?results=13) -205. [Southeastern Lan Party](https://www.fileplanet.com/7476/0/fileinfo/Southeastern-Lan-Party) -206. [sp_valley](https://gamebanana.com/maps/61099) -207. [Space_Lasercore](http://twhl.info/vault.php?map=1938) -208. [Spearhead](http://twhl.info/vault.php?map=1018) -209. [Spellbinder - The Summoning Tower v1.2](http://twhl.info/vault.php?map=1502) -210. [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)) -211. [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) -212. [Stacja](https://www.runthinkshootlive.com/posts/stacja/) -213. [Station17](https://gamebanana.com/maps/161063) -214. [Storage Facility](http://hl.loess.ru/?mods=&search=Storage+Facility) (map by *T.J Brosnan*) -215. [Strange Findings Part 1](http://twhl.info/vault.php?map=3617) -216. [Subway - The Longest Fall](http://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!*) -217. [Surfacerun](https://gamebanana.com/maps/156304) -218. [SwirusMap (call it "Dream")](http://twhl.info/vault.php?map=1588) -219. [Target Practice](http://twhl.info/vault.php?map=5460) -220. [Technical Problems](https://www.runthinkshootlive.com/posts/technical-problems/) -221. [Teh Hammre](https://www.runthinkshootlive.com/posts/teh-hammre/) -222. [Test Lab 16](http://twhl.info/competitions.php?results=17) -223. [Test Map](http://www.moddb.com/games/half-life/addons/test-map-update) (map by *NinjaBlack1337* aka *Guillermo_SPY*) -224. [Test of Destruction](http://twhl.info/vault.php?map=3892) -225. [The Abandon](http://half-life.ru/forum/showthread.php?threadid=14267) -226. [The Arena - Room 1](http://twhl.info/vault.php?map=3523) -227. [The Crab Lab](http://twhl.info/vault.php?map=4105) -228. [The Cupboard of Doom](http://www.moddb.com/mods/the-cupboard-of-doom) -229. [The Desert of Doom](http://hl.loess.ru/?mod=265) -230. [The Gloom](http://www.moddb.com/mods/the-gloom) -231. [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*) -232. [The House Beta 0.1](https://gamebanana.com/maps/138258) -233. [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) -234. [The Interview, Stage 1](https://www.fileplanet.com/7468/0/fileinfo/The-Interview,-stage-1) -235. [The Last Survivor](http://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) -236. [The Leech Pits](https://www.runthinkshootlive.com/posts/the-leech-pit/) -237. [The Origin of Symmetry](https://gamebanana.com/maps/56796) -238. [The Plague](http://scrama.3dn.ru/load/2-1-0-31) -239. [The Playtest](https://www.runthinkshootlive.com/posts/the-playtest/) -240. [The Poseidon Incident( USCM: Infestation Demo )](https://www.runthinkshootlive.com/posts/the-poseidon-incident/) -241. [The Run](http://twhl.info/vault.php?map=4890) -242. [The Secret Mission](http://www.geocities.ws/rawrguilds/maps.html) -243. [The Silo Station](https://www.fileplanet.com/7480/0/fileinfo/The-Silo-Station) -244. [The Stupendous Quest of the Annoying Microwave](http://twhl.info/vault.php?map=5937) -245. [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) -246. [The Transporter( Xen Transportation )](http://twhl.info/vault.php?map=3408) -247. [The_Work_Area v1.0](http://twhl.info/vault.php?map=2420) -248. [Torching the Light](http://www.moddb.com/mods/torching-the-light) -249. [Torture a Friendly NPC](http://twhl.info/vault.php?map=3672) -250. [Torture That Alien](http://twhl.info/vault.php?map=3248) -251. [Total Evasion](http://www.moddb.com/mods/total-evasion) -252. [Tower](http://twhl.info/vault.php?map=250) (Required Spirit of Half-Life for item_suit) -253. [TriggerHappy](https://www.fileplanet.com/8929/0/fileinfo/TriggerHappy) -254. [TriggerHappy2](https://www.fileplanet.com/13503/10000/fileinfo/TriggerHappy2) -255. [TriggerHappy2.5](https://www.moddb.com/games/half-life/addons/trigger-happy-v25) -256. [Trouble](https://gamebanana.com/maps/166033) -257. [Tunnels](http://twhl.info/vault.php?map=5350) -258. [Twisted Hazard Course](https://www.fileplanet.com/52919/50000/fileinfo/Twisted-Hazard-Course) -259. [Two Towers](http://twhl.info/vault.php?map=2327) -260. [Underground Facility](http://twhl.info/vault.php?map=1762) -261. [Unnamed](http://twhl.info/competitions.php?results=20) by *rowleybob* -262. [Urb's Challenge](http://twhl.info/vault.php?map=2677) -263. [USAF](http://hl.loess.ru/?mod=1085) -264. [USS Gaspra](http://biomech.itstudios.ru/gallery/uss_gaspra.htm) -265. [USSL Blue Mesa](http://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) -266. [Valve Pressure Beta v2](https://www.fileplanet.com/87528/80000/fileinfo/Valve-Pressure---Single-Player-Map) -267. [Vassy Compo](http://twhl.info/vault.php?map=771) -268. [Vilcabamba](http://www.moddb.com/games/half-life/addons/vilcabamba) (this map is also used as a first map in *Idol Hunt* mod) -269. [Vital Signs](http://twhl.info/vault.php?map=3908) -270. [Wake Up and Stay Alive](http://half-life.ru/forum/showthread.php?threadid=11956) -271. [war_coop1](http://twhl.info/vault.php?map=4991) -272. [Warehouse Firefight](http://twhl.info/vault.php?map=2279) -273. [We Got Work to Do!](http://twhl.info/vault.php?map=3107) -274. [Weird Dreams](http://twhl.info/vault.php?map=1473) (Required Spirit of Half-Life for item_suit) -275. [When the Army Came to the Office](https://www.runthinkshootlive.com/posts/when-the-army-came-to-the-office/) -276. [WTF](https://www.runthinkshootlive.com/posts/wtf/) -277. [WWE Bullsquid Royal Rumble](http://www.moddb.com/games/half-life/addons/wwe-bullsquid-royal-rumble) -278. [Wybuchowka](https://www.runthinkshootlive.com/posts/wybuchowka/) -279. [X-treme Violence](http://www.moddb.com/mods/x-treme-violence) -280. [XargoL's Entry for Vassy's Compo](http://twhl.info/vault.php?map=769) -281. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) -282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) (link dead!) -283. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) -284. [Zeeba-G's TWHL Compo 26 Entry](http://twhl.info/competitions.php?results=26) -285. [Zombies!](https://gamebanana.com/maps/160812) -286. [Zone: Map 1 (Intro)](http://www.moddb.com/mods/zone/downloads/1-map) -287. [Zone: Map 2 (Part 1)](http://www.moddb.com/mods/zone/downloads/zone-part-1) -288. [Zone: Map 3 (Part 2)](http://www.moddb.com/mods/zone/downloads/zone-map-3) +21. [Barrel of Grunts](https://twhl.info/vault.php?map=3390) +22. BDMAP1 (MoP1.bsp - map by unknown author) +23. [Beach Party](https://twhl.info/vault.php?map=5572) +24. [Beastie](https://www.runthinkshootlive.com/posts/beastie/) +25. **[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) +26. [Black Mesa Storage Facility - Bay A2](https://www.fileplanet.com/7479/0/fileinfo/Black-Mesa-Storage-Facility---Bay-A2) +27. [Black Mesa Xmas(A Black Mesa Christmas)](https://twhl.info/vault.php?map=4918) +28. Blame the Scientists (map by Incy247) +29. [Blood and Guts](https://www.runthinkshootlive.com/posts/blood-and-guts/) +30. **[Blood1](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)** +31. [Bhop](https://twhl.info/vault.php?map=5065) +32. Bow (map by Daniel Will) +33. **[Breakout](https://www.runthinkshootlive.com/posts/breakout-by-benny-blanco/)** (map for *Single Mapping Competition* by *BennyBlanco*) +34. [Breakout](https://hl.loess.ru/?mods=&search=Breakout) (map by *Mediocre MapGuy*) +35. Bridge (map by Monkey) +36. [Bullsuid Will Survive](https://www.moddb.com/games/half-life/addons/bullsuid-will-survive) +37. [C2](https://www.runthinkshootlive.com/posts/c2/) +38. [C3](https://www.runthinkshootlive.com/posts/c3/) +39. [C5](https://www.runthinkshootlive.com/posts/c5/) +40. **[Caged](https://web.archive.org/web/20081226051021/http://www.caylegeorge.com/ld_history/cg_maps.htm)** +41. [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) +42. [CataXen](https://twhl.info/vault.php?map=3429) +43. [Cause of Death](https://twhl.info/competitions.php?results=7) +44. [Challenge](https://web.archive.org/web/20231010044342/http://snarkpit.net/index.php?s=maps&map=3266) (map by *tnkqwe*) +45. [CHALLENGE](https://twhl.info/vault.php?map=2229) aka Can You Live (map by *killer487554*; remove spaces in the map name before you play) +46. [Choices](https://www.runthinkshootlive.com/posts/choices/) +47. [Chuck - Texas Rangers](https://twhl.info/vault.php?map=4253) +48. Cleaner's Adventures Begin Demo (map by InvisibleBullet) +49. [Coach](https://www.runthinkshootlive.com/posts/coach/) +50. [Cook The Headcrab](https://www.moddb.com/games/half-life/addons/cook-the-headcrab) +51. [Core01](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design competition* by *Flash*) +52. [Crawler](https://www.runthinkshootlive.com/posts/crawler/) +53. [Crysis 3](https://hl.loess.ru/?mod=215) (map for *Single Mapping Competition* by *Raid*) +54. [CUBE](https://twhl.info/competitions.php?results=7) (map by *Jobabob*) +55. [danger1](https://twhl.info/vault.php?map=2302) +56. [Data-base](https://www.runthinkshootlive.com/posts/database/) +57. [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) +58. [De-railed](https://twhl.info/competitions.php?results=7) +59. [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) +60. [Deep](https://twhl.info/vault.php?map=1669) +61. [Desert Attack](https://fyzzer.narod.ru/index.html) +62. [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) +63. [Desert Strike](https://hl.loess.ru/?mod=267) +64. [Devil Mesa](https://hosting.cecak.cz/forum-modifikace/hl1/index.php?text=mod&modifikace=dm) +65. [Disco Party v1.1](https://twhl.info/vault.php?map=1004) +66. **[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) +67. [Doomed Demo](https://web.archive.org/web/20231010122104/http://www.snarkpit.net/index.php?s=maps&map=3351) +68. [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) +69. [Dressed to Kill](https://www.fileplanet.com/48578/40000/fileinfo/Dressed-to-kill) +70. [Dying at Sea( Signs )](https://twhl.info/vault.php?map=5379) +71. [eif coloseum](https://www.runthinkshootlive.com/posts/eif-coloseum/) +72. [eif_room](https://www.runthinkshootlive.com/posts/eif-room/) +73. [eif_school](https://www.runthinkshootlive.com/posts/eif-school/) +74. [Emissary](https://twhl.info/competitions.php?results=15) +75. **[En Route 66](https://www.moddb.com/mods/en-route-66)** +76. [Enclosed Space](https://twhl.info/vault.php?map=4496) +77. **[Endlevel Boss](https://twhl.info/vault.php?map=786)** +78. [ES](https://csm.dev/threads/hl-smc-es.29017/) (map for *Single Mapping Competition* by *Flash*) +79. [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) +80. [Escape from Black Mesa v1.44](https://twhl.info/vault.php?map=) (map from *TWHL* by *Satchmo*) (wrong link!) +81. [Escape Off](https://hl.loess.ru/?mod=324) (a part of this map was used later in *Friendship 2.0* mod) +82. [Evasion](https://www.fileplanet.com/53430/50000/fileinfo/Half-life-:-Evasion) +83. [Evil Space](https://www.runthinkshootlive.com/posts/evil-space/) +84. **[Experimental Problems](https://www.runthinkshootlive.com/posts/experimental-problems/)** +85. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) +86. [Facility](https://twhl.info/vault.php?map=5482) +87. [Facility Escape](https://twhl.info/vault.php?map=3673) +88. [Fallout](https://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) +89. [Final Assault](https://twhl.info/vault.php?map=4500) +90. [Flat](https://hl.loess.ru/?mods=&search=Flat) +91. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) +92. [Freeman's Escape](https://www.runthinkshootlive.com/posts/freemans-escape/) (map by *Dave Crabb*) +93. [Freeman's Suicide( Kill Yourself )](https://www.runthinkshootlive.com/posts/kill-yourself/) +94. [Funhouse](https://twhl.info/competitions.php?results=11) +95. [Func_breakable - The Invasion](https://hl.loess.ru/?mod=384) (map from *TWHL* by *Archie* aka *The Hunter*) +96. [Genetic Research Facility](https://twhl.info/vault.php?map=4775) +97. [Gladiator](https://csm.dev/threads/hl-gladiator.36140/) +98. [Go to Xen Awalk(Xen Walk/SP-Offyxen)](https://www.moddb.com/mods/go-to-xen-awalk-half-life-map) +99. [goldsource](https://twhl.info/vault.php?map=5737) (link dead!) +100. [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!) +101. [Govnomod: Mysterious Force](http://forums.playground.ru/half-life/g_vnomod-640221/) +102. [GruntMatch](https://www.runthinkshootlive.com/posts/grunt-match/) +103. [Grunts Domain( archiveSP01 )](https://www.runthinkshootlive.com/posts/grunts-domain/) +104. [Gunship v1](https://www.runthinkshootlive.com/posts/gunship/) +105. [Half-Life 1: Traptown E3 2003](https://gamebanana.com/maps/168032) +106. [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) +107. Hard Map 2 (map from TWHL by Custom) +108. [Hard Way](https://hl.loess.ru/?mod=480) +109. [Headcrab Revenge](https://twhl.info/vault.php?map=3519) +110. [Headcrab-BOSS](https://www.moddb.com/games/half-life/downloads/headcrab-boss) +111. [HL Dance( Half-Dance )](https://twhl.info/vault.php?map=2991) +112. [hl_egzekucja](https://gamebanana.com/maps/50132) +113. **[HLywood](https://twhl.info/vault.php?map=455)** +114. **[Hnaii - Office Komplex](https://www.fileplanet.com/113757/110000/fileinfo/Hnaii---Office-Komplex)** +115. [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) +116. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) +117. [House](https://www.artpeter.net/Data/HalfLife/Hl.php) +118. [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*) +119. [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) +120. [In the Kitchen](https://twhl.info/vault.php?map=4314) +121. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) +122. [Interactivity & Lots of Entities](https://twhl.info/vault.php?map=5744) (link dead!) +123. [Interior](https://www.runthinkshootlive.com/posts/interior/) +124. [Into the Frying Pan](https://www.runthinkshootlive.com/posts/into-the-frying-pan/) +125. **[Island Bombing](https://twhl.info/competitions.php?results=13)** +126. **[Ivy](https://twhl.info/vault.php?map=4869)** +127. [Jump Program](https://hl.loess.ru/?mod=575) +128. [Killer on the Run](https://www.runthinkshootlive.com/posts/killer-on-the-run/) +129. [Killing House for Half-Life](https://www.runthinkshootlive.com/posts/killing-house/) +130. [Kosovo 2000](https://www.moddb.com/games/half-life/addons/kosovo-2000) +131. [Kosovo II – The Second Day](htstp://www.moddb.com/games/half-life/addons/kosovo-2) +132. [KotiteolliSuus](https://twhl.info/vault.php?map=533) +133. [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) +134. **[l33t Test 1: Conclusive Analysis](https://twhl.info/vault.php?map=3023)** +135. [Lab](https://twhl.info/vault.php?map=1797) +136. **[Lambda Station](https://www.runthinkshootlive.com/posts/lambda-station/)** +137. [LC](https://www.runthinkshootlive.com/posts/lc-by-sluxe/) (map for *Fast Level Design* competition by *Slux*) +138. **[LDSF( Laser Deployed Security Force )](https://twhl.info/competitions.php?results=4)** +139. [Locked Up](https://www.fileplanet.com/8842/0/fileinfo/Locked-Up) +140. [Looping Stage](https://web.archive.org/web/20041104030218/http://cariad.co.za/twhl/mapvault_map.php?id=1250) +141. [Lord's Lair](https://www.fileplanet.com/7471/0/fileinfo/Lords-Lair) +142. [Losspower](https://twhl.info/vault.php?map=3919) +143. **[Lost-World](https://gamebanana.com/maps/156214)** +144. [Lounge](https://www.fileplanet.com/7461/0/fileinfo/The-Lounge) +145. Matrixroom v0.7 (map by Joe Hunter) +146. [MatrixTrainstation](https://twhl.info/vault.php?map=2531) +147. **[Maze01a](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)** +148. [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) +149. [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) +150. Metro (map by unknown author) +151. Monster Arena v1 & Monster Arena v2 (maps by unknown author) +152. Monster Shoot (map by unknown author) +153. [Museum Lockdown](https://twhl.info/vault.php?map=2211) +154. [My Backyard](https://twhl.info/vault.php?map=1796) +155. [My Black Mesa](https://www.runthinkshootlive.com/posts/my-black-mesa/) (map for *Single Mapping Competition* by *Zanzer*) +156. **[Nameless](https://www.runthinkshootlive.com/posts/nameless/)** (map for *Single Mapping Competition* by *AGRESSOR*) +157. **[Need for Energy(SP-Energy)](https://scrama.3dn.ru/load/2-1-0-2)** +158. **[Nightmare: A horror map](https://twhl.info/vault.php?map=2106)** +159. [Nightshift_FsC](https://twhl.info/vault.php?map=2561) +160. [No Chance](https://gamebanana.com/maps/160536) +161. [No Regret](https://www.runthinkshootlive.com/posts/no-regret/) +162. [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!) +163. Nuclear Plant (map by SaCo) +164. **[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!) +165. [Observatory](https://www.runthinkshootlive.com/posts/observatory/) +166. [Ominous Reality: Part 1](https://twhl.info/vault.php?map=1328) +167. [Operation Rainbow](https://twhl.info/vault.php?map=3068) +168. [Operation Randomosity](https://www.moddb.com/games/half-life/addons/operation-randomosity) +169. **[Orb](https://twhl.info/competitions.php?results=20)** +170. [Osprey Chopper Competition](https://twhl.info/competitions.php?results=1) (map by *Andy*) +171. [Otage Beta v0.05](https://hl.loess.ru/?mods=&search=Otage) +172. [Outpost](https://www.fileplanet.com/81860/80000/fileinfo/Outpost) +173. [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**) +174. **[Pac-Man](https://twhl.info/competitions.php?results=29)** +175. **[Parallax Beta](https://www.runthinkshootlive.com/posts/parallax/)** (there are 2 maps, but only first map is playable, in fact) +176. [Postal](https://www.runthinkshootlive.com/posts/postal/) +177. [Problems in Building 2](https://gamebanana.com/maps/166032) +178. [Pulse: Demo #1](https://www.moddb.com/mods/pulse/downloads/pulse-demo-1) +179. [Quake](https://www.runthinkshootlive.com/posts/quake/) +180. [Quilted Thought Organ](https://www.runthinkshootlive.com/posts/quilited-thought-organ/) +181. **[Radix](https://unquenque.com/radix.html)** (this map is also a part of *Project Quantum Leap* mod) +182. [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) +183. [Remember All](https://csm.dev/threads/hl-smc-remember_all.29029/) +184. [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) +185. [Rescue 2](https://www.moddb.com/games/half-life/addons/rescue-2) (map by *SaCo*) +186. [Revamp](https://www.runthinkshootlive.com/posts/revamp/) +187. [Rogat](https://www.runthinkshootlive.com/posts/rogat/) +188. **[Rooms](https://twhl.info/vault.php?map=24)** (map by *ghost2*) +189. **[Ruled by Insanity](https://twhl.info/vault.php?map=3580)** +190. **[Rube Goldberg](https://twhl.info/vault.php?map=5014)** aka Entity Challenge 2(map by *Captain Terror*) +191. [Rube Goldberg Machine](https://twhl.info/vault.php?map=5006) (map by *TJB*) +192. [Rum](https://www.runthinkshootlive.com/posts/rum/) +193. [Rumble](https://hl.loess.ru/?mods=&search=Rumble) +194. [Run, Run, Run!](https://www.moddb.com/games/half-life/addons/run-run-run) +195. [Runaway](https://hl.loess.ru/?mods=&search=Runaway) +196. [Runder](https://www.runthinkshootlive.com/posts/runder/) +197. S1_INFIL, aka Infiltration (old unfinished map by *Silencer[=S7=]*) +198. [s_3_LumbdaCore](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design* competition by *Sania3*) +199. [Saving Santa](https://twhl.info/vault.php?map=5726) ( Secret Santa 2011 for *Rimrook* by *Urby* ) +200. [sand_01](https://twhl.info/vault.php?map=3166) +201. [SciMaker 1.1 (With Kill Func)](https://twhl.info/vault.php?map=1845) +202. [Scientist Killing](https://twhl.info/vault.php?map=1969) +203. [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!) +204. [Scramble](https://www.moddb.com/mods/scramble) +205. Second (map by unknown author) +206. [Seek and Destroy](https://www.fileplanet.com/13006/10000/fileinfo/Seek-and-Destroy) +207. **[SelfKill](https://www.moddb.com/games/half-life/addons/selfkill)** +208. [SEMTEX](https://twhl.info/competitions.php?results=7) (map by *kol*) +209. [Sepulcher](https://www.fileplanet.com/7478/0/fileinfo/Sepulcher) +210. [Shootout Alpha 3](https://www.fileplanet.com/111687/110000/fileinfo/Shootout-ALPHA-3) +211. [Small Battle](https://twhl.info/vault.php?map=2337) +212. [Small Battle 2](https://twhl.info/vault.php?map=2393) +213. [Smash Half-Life](https://www.runthinkshootlive.com/posts/smash/) +214. [Snatch](https://jqbros.3dn.ru/load/1-1-0-9) +215. [Soft Boiled](https://www.fileplanet.com/7475/0/fileinfo/Soft-Boiled) +216. **[Someplace Else](https://hylobatidae.org/minerva/parallax/someplace-else.html)** (this map is also a part of *Project Quantum Leap* mod) +217. [Somewhere( Kasperg: Unique Map )](https://twhl.info/competitions.php?results=13) +218. [Southeastern Lan Party](https://www.fileplanet.com/7476/0/fileinfo/Southeastern-Lan-Party) +219. **[sp_valley](https://gamebanana.com/maps/61099)** +220. **[Space_Lasercore](https://twhl.info/vault.php?map=1938)** +221. [Spearhead](https://twhl.info/vault.php?map=1018) +222. [Spellbinder - The Summoning Tower v1.2](https://twhl.info/vault.php?map=1502) +223. [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)) +224. [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) +225. [Stacja](https://www.runthinkshootlive.com/posts/stacja/) +226. [Station17](https://gamebanana.com/maps/161063) +227. [Storage Facility](https://hl.loess.ru/?mods=&search=Storage+Facility) (map by *T.J Brosnan*) +228. [Strange Findings Part 1](https://twhl.info/vault.php?map=3617) +229. [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!*) +230. **[Surfacerun](https://gamebanana.com/maps/156304)** +231. [SwirusMap (call it "Dream")](https://twhl.info/vault.php?map=1588) +232. [Target Practice](https://twhl.info/vault.php?map=5460) +233. **[Technical Problems](https://www.runthinkshootlive.com/posts/technical-problems/)** +234. [Teh Hammre](https://www.runthinkshootlive.com/posts/teh-hammre/) +235. **[Test Lab 16](https://twhl.info/competitions.php?results=17)** +236. [Test Map](https://www.moddb.com/games/half-life/addons/test-map-update) (map by *NinjaBlack1337* aka *Guillermo_SPY*) +237. [Test of Destruction](https://twhl.info/vault.php?map=3892) +238. [The Abandon](http://half-life.ru/forum/showthread.php?threadid=14267) (link dead!) +239. [The Arena - Room 1](https://twhl.info/vault.php?map=3523) +240. [The Crab Lab](https://twhl.info/vault.php?map=4105) +241. [The Cupboard of Doom](https://www.moddb.com/mods/the-cupboard-of-doom) +242. [The Desert of Doom](https://hl.loess.ru/?mod=265) +243. **[The Gloom](https://www.moddb.com/mods/the-gloom)** +244. [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*) +245. [The House Beta 0.1](https://gamebanana.com/maps/138258) +246. [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) +247. [The Interview, Stage 1](https://www.fileplanet.com/7468/0/fileinfo/The-Interview,-stage-1) +248. [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) +249. [The Leech Pits](https://www.runthinkshootlive.com/posts/the-leech-pit/) +250. [The Origin of Symmetry](https://gamebanana.com/maps/56796) +251. **[The Plague](https://scrama.3dn.ru/load/2-1-0-31)** +252. **[The Playtest](https://www.runthinkshootlive.com/posts/the-playtest/)** +253. [The Poseidon Incident( USCM: Infestation Demo )](https://www.runthinkshootlive.com/posts/the-poseidon-incident/) +254. **[The Run](https://twhl.info/vault.php?map=4890)** +255. [The Secret Mission](https://www.geocities.ws/rawrguilds/maps.html) +256. [The Silo Station](https://www.fileplanet.com/7480/0/fileinfo/The-Silo-Station) +257. [The Stupendous Quest of the Annoying Microwave](https://twhl.info/vault.php?map=5937) +258. [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) +259. [The Transporter( Xen Transportation )](https://twhl.info/vault.php?map=3408) +260. The Trap (map by Keks) +261. [The_Work_Area v1.0](https://twhl.info/vault.php?map=2420) +262. [Torching the Light](https://www.moddb.com/mods/torching-the-light) +263. [Torture a Friendly NPC](https://twhl.info/vault.php?map=3672) +264. [Torture That Alien](https://twhl.info/vault.php?map=3248) +265. [Total Evasion](https://www.moddb.com/mods/total-evasion) +266. [Tower](https://twhl.info/vault.php?map=250) (Required Spirit of Half-Life for item_suit) +267. [TriggerHappy](https://www.fileplanet.com/8929/0/fileinfo/TriggerHappy) +268. [TriggerHappy2](https://www.fileplanet.com/13503/10000/fileinfo/TriggerHappy2) +269. [TriggerHappy2.5](https://www.moddb.com/games/half-life/addons/trigger-happy-v25) +270. [Trouble](https://gamebanana.com/maps/166033) +271. [Tunnels](https://twhl.info/vault.php?map=5350) +272. **[Twisted Hazard Course](https://www.fileplanet.com/52919/50000/fileinfo/Twisted-Hazard-Course)** +273. [Two Towers](https://twhl.info/vault.php?map=2327) +274. [Underground Facility](https://twhl.info/vault.php?map=1762) +275. [Unnamed](https://twhl.info/competitions.php?results=20) by *rowleybob* +276. [Urb's Challenge](https://twhl.info/vault.php?map=2677) +277. [USAF](https://hl.loess.ru/?mod=1085) +278. **[USS Gaspra](http://biomech.itstudios.ru/gallery/uss_gaspra.htm)** (download link dead!) +279. [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) +280. [Valve Pressure Beta v2](https://www.fileplanet.com/87528/80000/fileinfo/Valve-Pressure---Single-Player-Map) +281. [Vassy Compo](https://twhl.info/vault.php?map=771) +282. [Vilcabamba](https://www.moddb.com/games/half-life/addons/vilcabamba) (this map is also used as a first map in *Idol Hunt* mod) +283. [Vital Signs](https://twhl.info/vault.php?map=3908) +284. [Wake Up and Stay Alive](http://half-life.ru/forum/showthread.php?threadid=11956) (link dead!) +285. [war_coop1](https://twhl.info/vault.php?map=4991) +286. [Warehouse Firefight](https://twhl.info/vault.php?map=2279) +287. [We Got Work to Do!](https://twhl.info/vault.php?map=3107) +288. [Weird Dreams](https://twhl.info/vault.php?map=1473) (Required Spirit of Half-Life for item_suit) +289. [When the Army Came to the Office](https://www.runthinkshootlive.com/posts/when-the-army-came-to-the-office/) +290. [WTF](https://www.runthinkshootlive.com/posts/wtf/) +291. [WWE Bullsquid Royal Rumble](https://www.moddb.com/games/half-life/addons/wwe-bullsquid-royal-rumble) +292. **[Wybuchowka](https://www.runthinkshootlive.com/posts/wybuchowka/)** +293. **[X-treme Violence](https://www.moddb.com/mods/x-treme-violence)** +294. [XargoL's Entry for Vassy's Compo](https://twhl.info/vault.php?map=769) +295. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) +296. [Xen World](https://web.archive.org/web/20150926134355/http://www.lambda-force.org/load/0-0-0-481-20) (download link dead!) +297. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) +298. [Zeeba-G's TWHL Compo 26 Entry](https://twhl.info/competitions.php?results=26) +299. [Zombies!](https://gamebanana.com/maps/160812) +300. [Zone: Map 1 (Intro)](https://www.moddb.com/mods/zone/downloads/1-map) +301. [Zone: Map 2 (Part 1)](https://www.moddb.com/mods/zone/downloads/zone-part-1) +302. [Zone: Map 3 (Part 2)](https://www.moddb.com/mods/zone/downloads/zone-map-3) -# List of mods which based on Spirit of Half-Life -Originally this list was written by *Qwertyus*(*Qortez*) +## List of mappacks for Half-Life: Blue Shift +To install - place *.bsp files to **bshift/custom/maps** folder, *.wad files to **bshift/custom** and type **`map `** cmd in game. + +1. [The Infinite Shift](https://www.fileplanet.com/archive/p-19156/The-Infinite-Shift) + +## List of Cleaner's Adventures-based mods +To 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). + +1. **[Half-Life: Black Guard](https://www.moddb.com/mods/half-lifeblack-guard)** - uses dlls copied from original Cleaner's Adventures. + +## List of mods which based on Spirit of Half-Life +Originally this list was written by @Qwertyus3D (*Qortez*) Spirit of Half-Life - extended toolkit for HL1 mappers by *Laurie Cheers*. -To run mods from this category - first of all place mod folder to folder where is **valve** folder located. +To 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). -On android install launcher from [here](http://www.moddb.com/mods/spirit-of-half-life/downloads/spirit-of-half-life-v12-android-port-v10) and run it with command-line parameter `-game ` - -On iOS use library suffix **sohl** or **Spirit** - -For Linux and OS X you must download crossbuild from [here](http://www.moddb.com/mods/spirit-of-half-life/downloads/spirit-of-half-life-12-crossplatform) and place **cl_dlls/dlls** folders from archive to your mod folder. - -## Mods which based on vanilla Spirit of Half-Life 1.2 or older(Fully playable with SoHL 1.2 libraries) -1. [Accidental Assassin](http://www.moddb.com/mods/accidental-assassin) (this mod has inner bugs; type **restart** if you stuck on changelevel) -2. [Big Scientists](http://www.moddb.com/mods/big-scientists) -3. [Black Silla Assault DEMO v1.2](http://www.moddb.com/mods/black-silla-assault) -4. [Blbej Den](http://www.moddb.com/mods/blbej-den) +### Mods which based on vanilla Spirit of Half-Life 1.2 or older(Fully playable with SoHL 1.2 libraries) +1. [Accidental Assassin](https://www.moddb.com/mods/accidental-assassin) (this mod has inner bugs; type **restart** if you stuck on changelevel) +2. **[Big Scientists](https://www.moddb.com/mods/big-scientists)** +3. [Black Silla Assault DEMO v1.2](https://www.moddb.com/mods/black-silla-assault) +4. **[Blbej Den](https://www.moddb.com/mods/blbej-den)** 5. [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) -6. [Dead Sector v1.0a](http://www.moddb.com/mods/dead-sector) -7. [Death Is Dead](http://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map) -8. [Escape from Black Mesa Alpha](http://www.moddb.com/mods/escape-from-black-mesa) -9. [ESCAPE: Trainingroom](http://www.moddb.com/mods/half-life-escape) -10. [City Crush v0.1](http://half-life.ru/forum/showthread.php?threadid=10671) -11. [Invasion 105 v2.0](http://www.moddb.com/mods/half-life-invasion-105) -12. [Issues( Project Quantum Leap 2 )](http://www.moddb.com/mods/issues) -13. [Mission Impracticable 2](http://www.moddb.com/mods/mission-impracticable-2) -14. [Prison v2.1](http://www.moddb.com/mods/half-life-prison) -15. [Prize v1.1](http://www.moddb.com/mods/prize) -16. [Radiation Alert: Episode 1 v1.1](http://www.moddb.com/mods/radiation-alert-episode-1) +6. **[Dead Sector v1.0a](https://www.moddb.com/mods/dead-sector)** +7. [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) +8. [Escape from Black Mesa Alpha](https://www.moddb.com/mods/escape-from-black-mesa) +9. [ESCAPE: Trainingroom](https://www.moddb.com/mods/half-life-escape) +10. [City Crush v0.1](http://half-life.ru/forum/showthread.php?threadid=10671) (link dead!) +11. [Invasion 105 v2.0](https://www.moddb.com/mods/half-life-invasion-105) +12. [Issues( Project Quantum Leap 2 )](https://www.moddb.com/mods/issues) +13. [Mission Impracticable 2](https://www.moddb.com/mods/mission-impracticable-2) +14. **[Prison v2.1](https://www.moddb.com/mods/half-life-prison)** +15. [Prize v1.1](https://www.moddb.com/mods/prize) +16. **[Radiation Alert: Episode 1 v1.1](https://www.moddb.com/mods/radiation-alert-episode-1)** 17. [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) -18. [Santa's Revenge](http://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) +18. **[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) 19. [Sector 6](https://www.moddb.com/mods/sector-6/) -20. [Space Prisoner v1.1](http://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) +20. [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) 21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) -22. [Timeline III: The Heart of Darkness](http://www.moddb.com/mods/timeline-series) +22. **[Timeline III: The Heart of Darkness](https://www.moddb.com/mods/timeline-series)** 23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) -## 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) -1. [Black Death](http://www.moddb.com/mods/half-life-black-death) -2. [Borderlands v0.4](http://www.moddb.com/games/half-life/addons/borderlands-v04) -3. [Dark Territory](http://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) -4. [Dead Way Alpha](https://cs-mapping.com.ua/forum/showthread.php?t=12381) +### 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) +1. [Black Death](https://www.moddb.com/mods/half-life-black-death) +2. [Borderlands v0.4](https://www.moddb.com/games/half-life/addons/borderlands-v04) +3. **[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) +4. [Dead Way Alpha](https://csm.dev/threads/hl-dead-way-1-nostalgija.12381/) 5. [Emergency](https://www.runthinkshootlive.com/posts/half-life-emergency/) -6. [ESCAPE (final)](http://www.moddb.com/mods/half-life-escape) by *DMC Interactive* -7. [ESCAPE 2](http://www.moddb.com/mods/half-life-escape) by *DMC Interactive* -8. [Force of Evil](http://www.moddb.com/mods/force-of-evil) +6. **[ESCAPE (final)](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive* +7. **[ESCAPE 2](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive* +8. [Force of Evil](https://www.moddb.com/mods/force-of-evil) 9. [Friendship: Town of half-life.ru mappers v2.0](https://www.runthinkshootlive.com/posts/friendship/) (this mod has inner bugs) -10. [Malevolence v1.4 Open Source Beta](http://www.moddb.com/mods/malevolence) -11. [Portrait of Freeman v1.1](https://www.runthinkshootlive.com/posts/portrait-of-freeman/) -12. [Project M.L.P v1.0](http://www.moddb.com/mods/project-mlp) (Remove **halflife.wad** from mod folder) -13. [Snark Planet Demo](http://www.moddb.com/mods/snark-planet) -14. [Survive in Catacombs](http://www.moddb.com/mods/survive-in-catacombs) -15. [Survive in Catacombs 2: Fear (Bloodbath)](http://www.moddb.com/mods/survive-in-catacombs-2) -16. [The Lost Hell](https://www.runthinkshootlive.com/posts/the-lost-hell/) -17. [The Trap v1.50](http://www.moddb.com/mods/the-trap) (mod by *Reaktor*) -18. [Ispitatel 4: Classic](http://www.moddb.com/mods/ispitatel-4-classic) -19. [Half-Life 2: Classic Demo](http://www.moddb.com/mods/half-life-2-classic) +10. [Malevolence v1.4 Open Source Beta](https://www.moddb.com/mods/malevolence) +11. **[Portrait of Freeman v1.1](https://www.runthinkshootlive.com/posts/portrait-of-freeman/)** +12. [Project M.L.P v1.0](https://www.moddb.com/mods/project-mlp) (Remove **halflife.wad** from mod folder) +13. [Snark Planet Demo](https://www.moddb.com/mods/snark-planet) +14. **[Survive in Catacombs](https://www.moddb.com/mods/survive-in-catacombs)** +15. **[Survive in Catacombs 2: Fear (Bloodbath)](https://www.moddb.com/mods/survive-in-catacombs-2)** +16. **[The Lost Hell](https://www.runthinkshootlive.com/posts/the-lost-hell/)** +17. **[The Trap v1.60](https://www.moddb.com/mods/the-trap) (mod by *Reaktor*)** +18. **[Ispitatel 4: Classic](https://www.moddb.com/mods/ispitatel-4-classic)** +19. [Half-Life 2: Classic Demo](https://www.moddb.com/mods/half-life-2-classic) -## 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) -1. [Before v1.0](http://www.moddb.com/mods/half-life-before) -2. [Christmas Life v1.0](http://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) -3. [COLONY 42 Alpha](http://twhl.info/vault.php?map=6055) +### 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) +1. **[Before v1.1](https://www.moddb.com/mods/half-life-before)** +2. [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) +3. [COLONY 42 Alpha](https://twhl.info/vault.php?map=6055) 4. [Crazy Crabs Demo 1 & 2](https://www.moddb.com/mods/mad-crabs) -5. [Far Crab Demo v2](http://www.moddb.com/mods/far-crab) -6. [Firefighter Demo v1.1](http://www.moddb.com/mods/firefighter-mod) -7. [Halfquake 3: Sunrise](http://www.moddb.com/mods/halfquake-amen) -8. [Prototype 98](http://www.moddb.com/mods/prototype-98) (set **fps_max** to **60**, otherwise you can get stuck in some places) -9. [Reissues v1.1](http://www.moddb.com/mods/reissues) -10. [Santa's Revenge 2: Xmas Meltdown](http://twhl.info/vault.php?map=4931) -11. [Tactical Espionage Action v1.0](http://www.moddb.com/mods/tactical-espionage-action) -12. [TWHL Tower Alpha](http://twhl.info/forums.php?thread=18670) +5. [Far Crab Demo v2](https://www.moddb.com/mods/far-crab) +6. [Firefighter Demo v1.1](https://www.moddb.com/mods/firefighter-mod) +7. **[Halfquake 3: Sunrise](https://www.moddb.com/mods/halfquake-amen)** +8. **[Prototype 98](https://www.moddb.com/mods/prototype-98)** (set **fps_max** to **60**, otherwise you can get stuck in some places) +9. **[Reissues v1.1](https://www.moddb.com/mods/reissues)** +10. **[Santa's Revenge 2: Xmas Meltdown](https://twhl.info/vault.php?map=4931)** +11. **[Tactical Espionage Action v1.1](https://www.moddb.com/mods/tactical-espionage-action)** +12. **[TWHL Tower](https://www.moddb.com/mods/twhl-tower/downloads/twhl-tower-steam-xash3d)** -## 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) -1. [Chaos Theory unfinished](http://hlfx.ru/forum/showthread.php?threadid=1772) +### 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) +1. [Chaos Theory unfinished](https://hlfx.ru/forum/showthread.php?threadid=1772) 2. [Silent Zhildor Demo](https://www.runthinkshootlive.com/posts/silent-zhildor/) -# List of Opposing Force-based mods -Originally this list was written by *Qwertyus*(*Qortez*) +## List of Opposing Force-based mods +Originally this list was written by @Qwertyus3D (*Qortez*) -To run mods from this category - first of all place mod folder and **gearbox** folder from *Opposing Force* to folder where is **valve** folder located. +To install mods from this category - first of all place mod folder and **gearbox** folder from *Opposing Force* beside **valve** folder. -On android install launcher from [here](http://www.moddb.com/games/xash3d-android/downloads/half-life-opposing-force-androidbeta) and run it with command-line parameter `-game ` +To 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). -For mappacks - place *.bsp files to **gearbox/maps** folder, *.wad files to **gearbox** and type **`map `** cmd. -## The big singleplayer mods or mappacks -1. [Aron](https://cs-mapping.com.ua/forum/showthread.php?t=37791) -2. [A Soldiers Tale](http://www.moddb.com/mods/a-soldiers-tale) +For mappacks - place *.bsp files to **gearbox/custom/maps** folder, *.wad files to **gearbox/custom** and type **`map `** cmd in game. +### The big singleplayer mods or mappacks +1. **[Aron](https://csm.dev/threads/hl-aron.37791/)** +2. **[A Soldiers Tale](https://www.moddb.com/mods/a-soldiers-tale)** 3. [Black Bag Operations](https://www.runthinkshootlive.com/posts/black-bag-operations/) -4. [Bootleg Squadrog](http://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) -5. [Camp Aign](http://twhl.info/vault.php?map=621) +4. **[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) +5. [Camp Aign](https://twhl.info/vault.php?map=621) 6. [Campaign: Part 1](https://www.runthinkshootlive.com/posts/campaign-part-1/) -7. [Double Helix](https://www.runthinkshootlive.com/posts/double-helix/) -8. [Fallback, Fire, and Ice](https://www.runthinkshootlive.com/posts/fallback-fire-and-ice/), plus simply Fallback (initial version of the mod with 5 maps) -9. [Fallen Chopper](https://www.runthinkshootlive.com/posts/fallen-chopper/) -10. [Focalpoint](http://www.moddb.com/mods/focalpoint) -11. [Foothold](http://www.moddb.com/mods/foothold) -12. [Ground Zero](http://www.snarkpit.net/index.php?s=maps&map=3423) by *Necromancer* -13. [Ground Zero 2: Fallout](http://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) -14. [Intolerable Threat](http://www.moddb.com/mods/intolerable-threat) -15. [Little Skyscraper of Horrors](http://www.moddb.com/mods/little-sky-scraper-of-horrors) -16. [Mechanized Death: An Army of None](http://www.moddb.com/games/half-life-opposing-force/addons/mechanized-death-army-of-none) -17. [Nuclear Winter](http://www.moddb.com/mods/nuclear-winter-opfor) -18. [Opposing Force Aliens Addon](https://www.fileplanet.com/82829/80000/fileinfo/Half-Life:-Opposing-Force-Aliens-Addon-1.2) -19. [Opposing Life2Life](https://www.moddb.com/games/half-life/addons/opposing-life2life-final-hd) -20. [Realms](http://www.snarkpit.net/index.php?s=maps&map=3483) -21. [Shepard's Adventures](http://www.moddb.com/mods/shepards-adventures) -22. [Snowy Rock](http://www.moddb.com/games/half-life-opposing-force/addons/snow-rock) -23. [Space Disaster](https://www.runthinkshootlive.com/posts/space-disaster/) -24. [The Alpha Unit](http://www.moddb.com/mods/na18509) -25. [The Evasion](http://www.moddb.com/mods/the-evasion) -26. [The Tower](http://www.moddb.com/mods/the-tower1) -27. [The Xen Campaigns](http://www.moddb.com/mods/the-xen-campaigns) -28. [Ultimate Attack](http://www.moddb.com/mods/half-life-ultimate-attack) -29. [Under the Blackmoon](http://www.moddb.com/mods/under-the-black-moon) -30. [Warzone](https://www.runthinkshootlive.com/posts/warzone/) +7. **[Dark Operations](https://www.moddb.com/mods/op-dark-operations)** +8. [Double Helix](https://www.runthinkshootlive.com/posts/double-helix/) +9. **[Fallback, Fire, and Ice](https://www.runthinkshootlive.com/posts/fallback-fire-and-ice/)**, plus simply Fallback (initial version of the mod with 5 maps) +10. [Fallen Chopper](https://www.runthinkshootlive.com/posts/fallen-chopper/) +11. **[Focalpoint](https://www.moddb.com/mods/focalpoint)** +12. [Foothold](https://www.moddb.com/mods/foothold) +13. [Ground Zero](https://web.archive.org/web/20231010064923/http://www.snarkpit.net/index.php?s=maps&map=3423) by *Necromancer* +14. **[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) +15. **[Intolerable Threat](https://www.moddb.com/mods/intolerable-threat)** +16. **[Little Skyscraper of Horrors](https://www.moddb.com/mods/little-sky-scraper-of-horrors)** +17. [Mechanized Death: An Army of None](https://www.moddb.com/games/half-life-opposing-force/addons/mechanized-death-army-of-none) +18. **[Military Duty v1.0.1](https://www.moddb.com/mods/military-duty)** +19. **[Nuclear Winter](https://www.moddb.com/mods/nuclear-winter-opfor)** +20. [Opposing Force Aliens Addon](https://www.fileplanet.com/82829/80000/fileinfo/Half-Life:-Opposing-Force-Aliens-Addon-1.2) +21. [Opposing Life2Life](https://www.moddb.com/games/half-life/addons/opposing-life2life-final-hd) +22. [Realms](https://web.archive.org/web/20231009194345/http://snarkpit.net/index.php?s=maps&map=3483) by *Necromancer* +23. **[Retaliation](https://www.moddb.com/mods/op-retaliation/downloads/retaliation1)** +24. **[Shepard's Adventures](https://www.moddb.com/mods/shepards-adventures)** +25. [Snowy Rock](https://www.moddb.com/games/half-life-opposing-force/addons/snow-rock) +26. [Space Disaster](https://www.runthinkshootlive.com/posts/space-disaster/) +27. **[The Alpha Unit](https://www.moddb.com/mods/na18509)** +28. **[The Evasion](https://www.moddb.com/mods/the-evasion)** +29. **[The Tower](https://www.moddb.com/mods/the-tower1)** +30. [The Xen Campaigns](https://www.moddb.com/mods/the-xen-campaigns) +31. **[Ultimate Attack](https://www.moddb.com/mods/half-life-ultimate-attack)** +32. **[Under the Blackmoon](https://www.moddb.com/mods/under-the-black-moon)** +33. [Warzone](https://www.runthinkshootlive.com/posts/warzone/) -## Unfinished mods -1. [Black Op Mission](http://www.moddb.com/mods/black-op-mission) +### Unfinished mods +1. [Black Op Mission](https://www.moddb.com/mods/black-op-mission) 2. [Mission Impossible](https://www.runthinkshootlive.com/posts/mission-impossible/) (the mod is not fully finished and changelevel to the last map doesn't work) -3. [Poisonheadcrab Nightmare Mappack](http://www.moddb.com/mods/poisonheadcrab-nightmare-mappack-for-half-life) Demo + Update (there are some bugs, but they are inner bugs of the mod) -4. [Return to Black Mesa](http://www.hl-rtbm.wbs.cz/) (link dead!) -5. [The Bounty Hunter](http://www.moddb.com/mods/half-life-the-bounty-hunter) -6. [Xen Assault](http://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**) +3. [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) +4. [Return to Black Mesa](https://web.archive.org/web/20160903151337/http://www.hl-rtbm.wbs.cz/) (download link dead!) +5. [The Bounty Hunter](https://www.moddb.com/mods/half-life-the-bounty-hunter) +6. [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**) -## Mods with a little incompatibilities -There is a same critical issue in these mods - an unworking level transition from a first map to a second one - -To overcome the bug, follow these steps: - -1) start a second map of a mod - **op2** / **ret2** - manually via console - -2) step back to activate a level transition to the previous map - -3) type restart into console - -4) play with working transition! - -Mods: - -1. [Dark Operations](http://www.moddb.com/mods/op-dark-operations) -2. [Retaliation](http://www.moddb.com/mods/op-retaliation/downloads/retaliation1) - -## Singleplayer mods with one map or single maps -1. [Alternate Points](http://twhl.info/vault.php?map=4499) +### Singleplayer mods with one map or single maps +1. [Alternate Points](https://twhl.info/vault.php?map=4499) 2. [Battle](https://www.runthinkshootlive.com/posts/battle/) -3. [Bomb Squad](http://www.snarkpit.net/index.php?s=maps&map=3471) -4. [Corruption](http://www.snarkpit.net/index.php?s=maps&map=3154) -5. [Critical Mass](http://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1) +3. [Bomb Squad](https://web.archive.org/web/20231009205618/http://snarkpit.net/index.php?s=maps&map=3471) +4. [Corruption](https://web.archive.org/web/20231009205616/http://www.snarkpit.net/index.php?s=maps&map=3154) +5. [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) 6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/) 7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/) 8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/) -9. [Guitar Star Pre-Alpha](http://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) +9. [Guitar Star Pre-Alpha](https://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) 10. [J2000](https://www.runthinkshootlive.com/posts/j2000/) 11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/) 12. [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) 13. [Little Escape](https://www.runthinkshootlive.com/posts/little-escape/) -14. [Madness 2](http://twhl.info/vault.php?map=1758) -15. [Operation: Sandblast](http://unquenque.com/sandblast.html) -16. [OpFor Postal](https://www.runthinkshootlive.com/posts/opfor-postal/) -17. [Super Nova Space Station](https://www.runthinkshootlive.com/posts/supernova-space-station/) -18. [The Red Area](https://www.runthinkshootlive.com/posts/the-red-area/) -19. [Underhalls](http://twhl.info/vault.php?map=4070) -20. [Without Doubts Beta](https://www.runthinkshootlive.com/posts/without-doubts/) -21. [X-Abaddon](http://www.moddb.com/mods/red-alert-half-life-x-pantion/addons/x-abaddon) -22. [You Die v2](https://www.runthinkshootlive.com/posts/you-die/) -23. [Zombies](https://www.runthinkshootlive.com/posts/zombies/) +14. [Marine Invasion: Freak-Lager](http://hl.gamebanana.com/gamefiles/2537) +15. [Madness 2](https://twhl.info/vault.php?map=1758) +16. **[Operation: Sandblast](https://unquenque.com/sandblast.html)** +17. [OpFor Postal](https://www.runthinkshootlive.com/posts/opfor-postal/) +18. [Super Nova Space Station](https://www.runthinkshootlive.com/posts/supernova-space-station/) +19. [The Red Area](https://www.runthinkshootlive.com/posts/the-red-area/) +20. [Underhalls](https://twhl.info/vault.php?map=4070) +21. [Without Doubts Beta](https://www.runthinkshootlive.com/posts/without-doubts/) +22. [X-Abaddon](https://www.moddb.com/mods/red-alert-half-life-x-pantion/addons/x-abaddon) +23. [You Die v2](https://www.runthinkshootlive.com/posts/you-die/) +24. [Zombies](https://www.runthinkshootlive.com/posts/zombies/) -# List of XashXT-based mods +## List of XashXT-based mods XashXT - SoHL-based extended toolkit for Xash3D mappers by *Unkle Mike*. -To run mods from this category - first of all place mod folder to folder where is **valve** folder located. +To install mods from this category - first of all place mod folder beside **valve** folder. -On **android** install launcher from [here](https://github.com/FWGS/XashXT/releases) and run it with command-line parameter `-game `. +Currently, you can try to run this mods under PrimeXT. -1. [Meanwile in Russia demo(MIR)](http://www.moddb.com/games/mir) +1. **[Meanwile in Russia demo(MIR)](https://www.moddb.com/games/mir)** 2. Monorail Quest -# List of maps for They Hunger +## List of They Hunger-based mods +To 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). + +### The big singleplayers mods +1. **[They Hunger: Rebuild](https://www.moddb.com/mods/they-hungerrebuild)** - This mod uses new **func_vehicle** entity and our **hlsdk-portable** libraries. + +### Single maps 1. [Awful changes](https://yadi.sk/d/OE6SDqbkoLy6o) by *MSteam* 2. [Dark Rock](https://yadi.sk/d/Lc4ahljfoigcQ) by *Adam 'Jeb_Radec' Brown* 3. [Hospital](https://yadi.sk/d/AAXjfvwLodkPG) by *Nod24* @@ -862,365 +873,316 @@ On **android** install launcher from [here](https://github.com/FWGS/XashXT/relea 5. [They Hunger: Lab of Horrors](https://yadi.sk/d/V_us_m78pFaMK) by *Jordan* 6. [They Hunger: Route666 Demo](https://yadi.sk/d/XKWCseAPohkG6) by *MadMapper* -# List of games and mods with custom gamedll -For mods from this category - first of all place mod folder to folder where is **valve** folder located. +## List of supported mods from mobile_hacks branch of HLSDK Portable by FWGS +Currently, 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. -On **android** need to install separate launcher with custom game libraries +1. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)** +2. **[Big Lolly](https://www.moddb.com/mods/big-lolly)** +3. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)** +4. **[Half-Secret](https://www.moddb.com/mods/half-secret)** +5. **[Case Closed](https://www.moddb.com/mods/caseclosed)** +6. [Bloody Pizza - Vendetta](https://www.moddb.com/games/half-life/addons/bloody-pizza-vendetta) +7. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04) +8. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction/downloads/half-life-induction-12)** +9. **[Redemption/Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)** +10. [Sewer beta](https://www.moddb.com/games/half-life/addons/sewer-beta) +11. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)** +12. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)** -On **iOS** need to run the game from Xash3D FWGS app with command-line parameter `-game ` and special library suffix(if it needed). +## List of games and mods with custom gamedll +For mods from this category - first of all place mod folder beside **valve** folder. -On **Linux** and **OS X** need to download special build for this OSes. +1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)** -Package for **FreeBSD** already has libraries for some mods from this list. +To 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). -NOTE: Many mods from this list was ported only on android, but you can use source code to build mod libraries manually for your platform. +2. **[Adrenaline Gamer](https://www.moddb.com/mods/adrenaline-gamer)** -1. [Adrenaline Gamer](http://www.moddb.com/mods/adrenaline-gamer) +On **x86 Linux** you can use *OpenAG* client by *YaLTeR* -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.ag), [ModDB](http://www.moddb.com/mods/adrenaline-gamer/downloads/adrenaline-gamer-66-android-port), [Github](https://github.com/nekonomicon/AdrenalineGamer-android/releases) +To 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). -On **Linux** you can use *OpenAG* client by *YaLTeR* +3. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)** + +To 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). + +4. **[Afraid of Monsters: Director's Cut](https://www.moddb.com/mods/afraid-of-monsters-dc)** + +To 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). + +5. **[Azure Sheep](https://www.moddb.com/mods/azure-sheep)** + +To 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). + +6. **[Big Lolly](https://www.moddb.com/mods/big-lolly)** + +To 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). + +7. **[Black OPS](https://www.moddb.com/mods/black-ops)** + +To 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). + +8. [Bloody Pizza: Vendetta](https://www.runthinkshootlive.com/posts/bloody-pizza-vendetta/) + +To 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). + +9. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04) + +To 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). + +10. **[Bubblemod](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)** + +This mod already has version for **x86 Linux**. + +To 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). + +11. **[Case Closed](https://www.moddb.com/mods/caseclosed)** + +To 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). + +12. **[Cleaner's Adventures](https://www.moddb.com/mods/cleaners-adventures)** + +To 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). + +13. **[Cold Ice Remastered](https://www.moddb.com/mods/cold-ice-remastered)** + +This mod already supports x86 Linux. + +Support for other platforms may be available later. + +14. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)** + +On x86 you may be need to compile client part. + +For other specific platforms you may be need to compile server part too. Source code: -Original source code: https://github.com/martinwebrant/agmod +Client: https://github.com/Velaron/cs16-client/ -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/aghl +Server: https://github.com/rehlds/ReGameDLL_CS -2. [Afraid of Monsters: Director's Cut](http://www.moddb.com/mods/afraid-of-monsters-dc) +15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.aomdc), [ModDB](http://www.moddb.com/mods/afraid-of-monsters-dc-remod/downloads/afraid-of-monsters-directors-cut-android-port), [Github](https://github.com/nekonomicon/AoMDC-android/releases) +To 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). -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/afraid-of-monsters-dc-remod/downloads/afraid-of-monsters-directors-cut-crossplatform) +16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)** -Source code: +To 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). -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/aomdc +17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)** -3. [Azure Sheep](https://www.moddb.com/mods/azure-sheep) +Steam version already supports x86 Linux. -Android launcher: [ModDB](https://www.moddb.com/mods/azure-sheeporiginal-remake/downloads/azure-sheep-android-port), [Github](https://github.com/nekonomicon/AzureSheep-android/releases) +To 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). -For **Linux** and **OS X** you can download crossbuild from [here](https://www.moddb.com/mods/azure-sheeporiginal-remake/downloads/azure-sheep-crossplatform) +18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)** -Source code: +This mod already supports x86 Linux. -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/asheep +Support for other platforms may be available later. -4. [Black OPS](http://www.moddb.com/mods/black-ops) +19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.blackops), [ModDB](http://www.moddb.com/games/xash3d-android/downloads/black-ops-android-port), [Github](https://github.com/nekonomicon/BlackOps-android/releases) +To 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). -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/games/half-life/downloads/black-ops-crossplatform) +20. **[Gang Wars](https://www.moddb.com/mods/gangwars)** -Source code: +This mod already supports x86 Linux. -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/blackops +Support for other platforms may be available later. -5. [Bubblemod](http://www.bubblemod.org/dl_default.php) +21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.blackops), [ModDB](http://www.moddb.com/games/xash3d-android/downloads/bubblemod-android-port), [Github](https://github.com/nekonomicon/Bubblemod-android/releases) +To 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). -library suffix for **iOS**: **bubblemod** +22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)** -This mod already has version for **Linux**. +Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). -Source code: +Support for other platforms may be available later. -You can find original source code into archive from **downloads** section on **bubblemod.org** +23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/commits/bubblemod +To 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). -6. [Cleaner's Adventures](http://www.moddb.com/mods/cleaners-adventures) +24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.cad), [ModDB](http://www.moddb.com/mods/cleaners-adventures/downloads/cleaners-adventures-android-port), [Github](https://github.com/nekonomicon/CleanersAdventures-android/releases) +This mod already supports x86 Linux. -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/cleaners-adventures/downloads/cleaners-adventures-crossplatform) +Support for other platforms may be available later. -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/commits/CAd - -7. [Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.cs16client) - -*On PC you can run only server.* - -Source code: - -Client: https://github.com/FWGS/cs16-client/tree/v1.32 - -Server: https://github.com/FWGS/regamedll - -8. [Crack-Life: Campaign Mode](http://www.moddb.com/mods/crack-life) - -Android launcher: [ModDB](http://www.moddb.com/games/xash3d-android/downloads/crack-life-campaign-mode-android-port-v11), [Github](https://github.com/LostGamerHL/crack_life/releases) - -Source code: - -*lostgamer's* branch: https://github.com/LostGamerHL/crack_life - -9. [Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.dmc), [ModDB](http://www.moddb.com/games/deathmatch-classic/downloads/deathmatch-classic-android-port), [Github](https://github.com/nekonomicon/DMC-android) - -Source code: - -Original source code: https://github.com/ValveSoftware/halflife/tree/master/dmc - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/dmc - -10. [Escape from the Darkness](http://www.moddb.com/mods/escape-from-the-darkness) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.eftd), [ModDB](http://www.moddb.com/mods/escape-from-the-darkness/downloads/escape-from-the-darkness-android-port), [Github](https://github.com/nekonomicon/EscapeFromTheDarkness-android/releases) - -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/escape-from-the-darkness/downloads/escape-from-the-darkness-10-crossplatform) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/eftd - -11. [Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.bshift), [ModDB](http://www.moddb.com/games/half-life-blue-shift/downloads/half-life-blue-shift-android-port-v10), [Github](https://github.com/nekonomicon/BS-android/releases) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/bshift - -12. [Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes) - -Android launcher: [ModDB](https://www.moddb.com/games/half-life/downloads/half-life-echoes-android-port), [Github](https://github.com/nekonomicon/HalfLife_Echoes-android/releases) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/echoes - -13. Half-Life: Gravgun (unfinished) by *mittorn*, *a1batross* and *Solexid* +25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid* *Support temporary abandoned* -Old launcher for android [here](https://github.com/FWGS/hlsdk-xash3d/releases) +To 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). -New launcher(leaked by *lostgamer*) for android [here](https://vk.com/xashdroid?w=wall-69384313_41512%2Fall) (Rename **gravgun.apkk** to **gravgun.apk**) +Currently, MSVC is not supported, use MinGW if you need build for Windows. -Source code: https://github.com/FWGS/hlsdk-xash3d/tree/gravgun +Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras). -14. [Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/) +26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)** -Android launcher: [ModDB](http://www.moddb.com/games/xash3d-android/downloads/half-life-opposing-force-androidbeta), [VK](https://vk.com/androidopfor) +To 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). -Source code: +27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)** -*Hings's* branch: https://github.com/H-I-N-G/hlsdk-xash3d +This mod supports x86 Linux. -*lostgamer's* branch: https://github.com/LostGamerHL/hlsdk-xash3d/tree/opfor +Support for other platforms may be available later. -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/opfor +28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)** -15. [Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs) +This mod already supports x86 Linux. -Android launcher: [ModDB](https://www.moddb.com/mods/half-life-top-down/downloads/half-life-top-down-android-port), [Github](https://github.com/nekonomicon/HLTopDown-android/releases) +To 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). -Source code: +29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/hltopdown +Steam version already supports x86 Linux. -16. [Half-Life: Visitors](http://www.moddb.com/mods/half-life-visitors) +To 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). -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.visitors), [ModDB](http://www.moddb.com/mods/half-life-visitors/downloads/half-life-visitors-android-port), [Github](https://github.com/nekonomicon/HalfLife_Visitors-android/releases) +30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)** -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/half-life-visitors/downloads/half-life-visitors-crossplatformwonsteam) +To 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). -Source code: +31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/visitors +To 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). -17. [Half-Screwed: Death and Rebirth](http://www.moddb.com/mods/half-screwed) +32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.hscrewed), [ModDB](http://www.moddb.com/games/half-life/downloads/half-screwed-death-and-rebirth-android-port), [Github](https://github.com/nekonomicon/HalfScrewed-android/releases) +To 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). -Source code: +33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)** -Original source code: https://github.com/desukuran/half-screwed +To 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). -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/half-screwed +34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)** -18. [Night at the Office](http://www.moddb.com/mods/night-at-the-office) +This mod already supports x86 Linux. -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.noffice), [ModDB](http://www.moddb.com/mods/night-at-the-office/downloads/night-at-the-office-android-port), [Github](https://github.com/nekonomicon/NightAtTheOffice-android/releases) +Support for other platforms may be available later. -Source code: +35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/noffice +To 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). -19. [Paranoia](http://www.moddb.com/mods/paranoia) +36. **[Half-Secret](https://www.moddb.com/mods/half-secret)** + +To 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). + +37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)** + +This mod already supports x86 Linux. + +Support for other platforms may be available later. + +38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)** + +To 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). + +39. **[Paranoia](https://www.moddb.com/mods/paranoia)** *Support temporary abandoned* -Test version for android you can find [here](https://vk.com/fwgs.testing?w=wall-134898471_275) (Rename **paranoia.bin** to **paranoia.apk**) +[Source code](https://github.com/FWGS/paranoia_toolkit). -Source code: https://github.com/FWGS/paranoia_toolkit +40. **[Poke646](https://poke646.com/)** -20. [Poke646](http://poke646.com/) +To 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). -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.poke646), [ModDB](http://www.moddb.com/mods/poke646/downloads/poke646-android-port-v11), [Github](https://github.com/nekonomicon/Poke646-android/releases) +41. **[Poke646: Vendetta](https://poke646.com/)** -For **Linux** you can download special build from [here](http://www.moddb.com/mods/poke646-anniversary-edition/downloads/poke646-port-for-linux-xash3d) +To 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). -Source code: +42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/) -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/poke646 +[Source code](https://github.com/SNMetamorph/PrimeXT) -21. [Poke646: Vendetta](http://poke646.com/) +43. **[Quake Remake](https://www.moddb.com/games/quake-remake)** -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.vendetta), [ModDB](http://www.moddb.com/mods/poke646-vendetta/downloads/poke646-vendetta-android-port-v11), [Github](https://github.com/nekonomicon/Poke646_Vendetta-android/releases) +*Support was fully abandoned due Quake Wrapper release.* -For **Linux** you can download special build from [here](http://www.moddb.com/mods/poke646-anniversary-edition/downloads/poke646-vendetta-for-linux-xash3d-only) +[Actual source code](https://github.com/FWGS/quakeremake) -Source code: +44. **[Rebellion](https://www.moddb.com/mods/rebellion1)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/poke646_vendetta +To 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). -22. [Quake Remake](http://www.moddb.com/games/quake-remake) +45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)** -*Support temporary abandoned* +To 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). -Old launcher for **android** you can find [here](http://www.moddb.com/games/xash3d-android/downloads/quake-remake-android-port) +46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)** -Source code: https://github.com/FWGS/QuakeRemakeDevkit +To 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). -23. [Rebellion](http://www.moddb.com/mods/rebellion1) +47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)** -Android launcher: [ModDB](http://www.moddb.com/mods/rebellion1/downloads/rebellion-10-android-port), [Github](https://github.com/nekonomicon/Rebellion-android/releases) +This mod already supports x86 Linux. -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/rebellion1/downloads/rebellion-10-crossplatform) +Support for other platforms may be available later. -Source code: +48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta) -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion +To 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). -24. [Residual Life](http://www.moddb.com/mods/hl-residual-life) +49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life) -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.rpoint), [ModDB](http://www.moddb.com/mods/hl-residual-life/downloads/residual-pointresidual-life-android-port), [Github](https://github.com/nekonomicon/ResidualPoint-android/releases) +To 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). -library suffix for **iOS**: **rp** +50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)** -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/hl-residual-life/downloads/residual-life-15-crossplatform) +To 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). -Source code: +51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)** -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +This mod already supports x86 Linux. -25. [Residual Point](http://www.moddb.com/mods/hl-residual-point/downloads/half-life-residual-point-crossplatform) +Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available. -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.rpoint), [ModDB](http://www.moddb.com/mods/hl-residual-life/downloads/residual-pointresidual-life-android-port), [Github](https://github.com/nekonomicon/ResidualPoint-android/releases) +52. **[The Gate](https://www.moddb.com/mods/the-gate)** -library suffix for **iOS**: **rp** +To 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). -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/hl-residual-point/downloads/half-life-residual-point-crossplatform) +53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)** -Source code: +To 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). -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)** -26. [Spirit of Half-Life 1.2](http://www.moddb.com/mods/spirit-of-half-life) +To 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). -Android launcher: [ModDB](http://www.moddb.com/mods/spirit-of-half-life/downloads/spirit-of-half-life-v12-android-port-v10), [Github](https://github.com/nekonomicon/SOHL-1.2-android/releases) +55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)** -library suffix for **iOS**: **sohl** / **Spirit** +To 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). -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/spirit-of-half-life/downloads/spirit-of-half-life-12-crossplatform) +56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/) -Source code: +*Support was fully abandoned due PrimeXT development* -[Logic&Trick](https://github.com/LogicAndTrick)'s [mirror](http://files.logic-and-trick.com/Home/Index/Half-Life/Spirit%20of%20Half-Life) with different SoHL versions source code. +[Source code](https://github.com/FWGS/XashXT-FWGS/) -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/sohl1.2 +57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)** -27. [Swiss Cheese Halloween](http://www.moddb.com/mods/half-life-halloween-mod) +This mod already supports x86 Linux. -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.shall), [ModDB](http://www.moddb.com/mods/half-life-halloween-mod/downloads/swiss-cheese-halloween-2002-android-port), [Github](https://github.com/nekonomicon/SHALL2002-android/releases) +Support for other platforms may be available later. -library suffix for **iOS**: **shall** +58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)** -Source code: +To 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). -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/halloween +Don't forget to enable XENWARRIOR build option!!! -28. [The Gate](http://www.moddb.com/mods/the-gate) +59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final) -Android launcher: [ModDB](http://www.moddb.com/mods/the-gate/downloads/the-gate-android-port), [Github](https://github.com/nekonomicon/TheGate-android/releases) +To 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). -library suffix for **iOS**: **thegate** / **TheGate** +## List of Counter Strike-based mods -Source code: +1. **[CSMoE](https://github.com/MoeMod/CSMoE/releases)** by *Chinese Xash3D FWGS Community* - uses custom Xash3D FWGS build. -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/thegate - -29. [They Hunger: Trilogy](http://www.moddb.com/mods/they-hunger) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.hunger), [ModDB](http://www.moddb.com/mods/they-hunger/downloads/they-hunger-android-port), [Github](https://github.com/nekonomicon/TheyHunger-android/releases) - -library suffix for **iOS**: **hunger** / **Hunger** - -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/they-hunger/downloads/they-hunger-trilogy-crossplatform) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/theyhunger - -30. [ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.dmc), [ModDB](https://www.moddb.com/games/deathmatch-classic/downloads/deathmatch-classic-android-port), [Github](https://github.com/nekonomicon/DMC-android/releases) - -For **Linux** and **OS X** you can download patch from [here](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf-crossplatform) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/dmc - -31. [Times of Troubles](http://www.moddb.com/mods/times-of-troubles) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.tot), [ModDB](http://www.moddb.com/mods/times-of-troubles/downloads/times-of-troubles-12-crossplatform), [Github](https://github.com/nekonomicon/TimesOfTroubles-android/releases) - -For **Linux** and **OS X** you can download crossbuild from [here](http://www.moddb.com/mods/times-of-troubles/downloads/times-of-troubles-12-crossplatform) - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/tot - -32. [XashXT](https://cs-mapping.com.ua/forum/showthread.php?t=38319) - -*Support temporary abandoned* - -Test version for **android** and **linux** you can find [here](https://github.com/FWGS/XashXT/releases) - -33. [Xen-Warrior](http://www.moddb.com/mods/xen-warrior) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=in.celest.xash3d.xenwar), [ModDB](http://www.moddb.com/mods/xen-warrior/downloads/xen-warrior-android-port), [Github](https://github.com/nekonomicon/XenWarrior-android/releases) - -Source code: - -Just compile SoHL 1.0-1.2 with -DXENWARRIOR flag. - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/sohl1.2 - -34. [Zombie-X](http://www.moddb.com/mods/zombie-x-10-final) - -Android launcher: [Play Market](https://play.google.com/store/apps/details?id=su.xash.xash3d.zx), [ModDB](http://www.moddb.com/games/xash3d-android/downloads/zombie-x-android-port), [Github](https://github.com/nekonomicon/ZombieX-android/releases) - -library suffix for **iOS**: **Zombie-X-DLE** - -Source code: - -*nekonomicon's* branch: https://github.com/FWGS/hlsdk-xash3d/tree/zombie-x - -# List of mods for Counter Strike -For mods from this category - place **cstrike** folder to folder where is **valve** folder located and install [CS16Client](https://play.google.com/store/apps/details?id=in.celest.xash3d.cs16client) launcher. - -1. [Counter Strike: BreakThrough Edition](https://github.com/MoeMod/CSMoE/releases) by *Chinese Xash3D FWGS Community* -2. [Counter Strike 1.6: Deathmatch](http://www.moddb.com/games/xash3d-android/downloads/cs16dm) by *Hings* -3. [DeathFortress](https://vk.com/cs_xash3d?w=wall-107446582_62350%2Fall) by *Hings* -4. [XashFortress](https://vk.com/cs_xash3d?w=wall-107446582_57507%2Fall) by *Solexid* diff --git a/Documentation/touch-controls.md b/Documentation/touch-controls.md new file mode 100644 index 00000000..ea7ea645 --- /dev/null +++ b/Documentation/touch-controls.md @@ -0,0 +1,149 @@ +# Touch controls configuring + +## Introduction + +Thanks 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. + +## Editor mode usage + +1. Launch Xash3D and start the game. +2. To enter the edit mode, click on the gear icon (command `touch_enableedit`). +3. 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`. + +*Touch controls layout editor* +![](images/editor.jpg) + +*Touch profiles window* +![](images/touch-profiles.jpg) + +*Touch buttons parameters window* +![](images/touch-buttons.jpg) + +## Layout editor features + +* **Moving buttons**: click on a button, drag it to the desired location, and release. +* **Resizing buttons**: place your first finger on the top left corner of the button, and use your second finger to resize it. +* **Hiding/showing buttons**: select the button (it will turn red), then use the menu: + * **Close**: closes the editing mode (`touch_disableedit`). + * **Reset**: resets the button to its default values. + * **Hide/Show**: hides or shows the button (`touch_hide ` / `touch_show `). + +## Working with configuration files / console + +All 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. + +### Common commands in configuration files + +``` +// Comment: lines starting with // are ignored. + +// Swipe zones for movement. Specify how far you need to swipe to speed up. +// The default values ​​are set to be optimal for a small swipe to immediately walk quickly. +touch_forwardzone "0.060000" ; touch_sidezone "0.060000" + +// Sensitivity settings (pitch - horizontal, yaw - vertical). +touch_pitch ; touch_yaw + +// Grid settings (see above). +touch_grid_count ; touch_grid_enable + +// Draws a border around the button. +touch_set_stroke +( - red; - green; - blue; - alpha/transparency) + +// Show (1) or hide (0) client buttons. +touch_setclientonly + +// Removes all buttons from the screen. +touch_removeall + +// Shows all available buttons in the configuration file. +touch_list + +// Saves the config after editing. You can specify a new file name. +touch_config_file command.cfg +``` + +*The full list of buttons in the standard `touch.cfg` file, displayed in the console when using `touch_list`.* +![](images/example2.jpg) + + +## Adding new buttons + +To add a new button, use the console command: + +``` +touch_addbutton "digits" "touch/key_1.png" "toggle_digits" 0.340000 0.782222 0.400000 0.888889 255 255 255 100 0 +``` + +### Parameters of `touch_addbutton` command + +| Parameter | Description | +| --- | --- | +| `"digits"` | Unique name of the button. | +| `"touch/key_1.png"` | Path to the icon file (`.png` format). If the icon is not needed, leave `""`. | +| `"toggle_digits"` | Command to execute after clicking (for example, `"buy"` for purchase). | +| `0.340000` | X coordinate of the upper-left corner of the button. | +| `0.782222` | Y coordinate of the upper-left corner of the button. | +| `0.400000` | X coordinate of the lower-right corner of the button. | +| `0.888889` | Y coordinate of the lower-right corner of the button. | +| `255 255 255` | Button color in RGB format. | +| `100` | Button transparency (0 - fully transparent, 255 - fully opaque). | +| `0` | Flags (see next section). | + +## List of flags + +Flags define the behavior of the button. Their values ​​are powers of two: + +| Flag | Value | Description | +| --- | --- | --- | +| `TOUCH_FL_HIDE` | 1 | Hides the button (not displayed in the game, but visible in the editor). | +| `TOUCH_FL_NOEDIT` | 2 | Disables editing of the button in the editor. | +| `TOUCH_FL_CLIENT` | 4 | The button is client-side (not saved in the main control file). | +| `TOUCH_FL_MP` | 8 | The button is displayed only in multiplayer. | +| `TOUCH_FL_SP` | 16 | The button is displayed only in singleplayer. | +| `TOUCH_FL_DEF_SHOW` | 32 | The button is always displayed on startup. | +| `TOUCH_FL_DEF_HIDE` | 64 | The button is always hidden on startup. | +| `TOUCH_FL_DRAW_ADDITIVE` | 128 | The button colors are added together in blend mode. | +| `TOUCH_FL_STROKE` | 256 | Enables outline stroke around the button. | + +Flags 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. + +*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.* +![](images/example1.jpg) + +## Useful commands + +* `touch_hide `: hides buttons by pattern. +* `touch_setcommand`: changes the command bound to a button. +* `touch_settexture`: quickly changes the button image. +* `touch_setcolor`: sets the button color. +* `touch_exportconfig`: exports the current configuration, including aspect ratio. + +## Usage examples + +* `touch_hide menu*` hides all buttons with names starting with `menu`. +* `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. + + *Result of executing this command* + ![](images/example3.jpg) + +* Example of adding a new button with a custom icon (the `lastinv` command is used - quick change between weapons) + ![](images/example4.jpg) + + *View of this button in layout editor* + ![](images/example5.jpg) + +## Tips + +* To prevent the `look` and `move` buttons from interfering with editing other elements, place them before the others in the configuration file. +* And vice versa, to make a button appear on top of others, place it at the end of the configuration file. +* You can assign commands to buttons that change other buttons, see the previous section "Usage examples". +* 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. +* To create your own icon for the button, you can use any graphics editor (for Android, Photo Editor by iudesk is suitable). Saving conditions: + * Image format - `.png` with transparency (e.g. alpha channel) + * Aspect ratio / size - 1:1 / 256x256 + * Path to the icons location - `touch/gfx` inside game directory. + +### Additional links +* [Handy palette for selecting color in RGB format](https://www.rapidtables.com/web/color/RGB_Color.html) diff --git a/README.md b/README.md index 119cf2b0..863f9f62 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,62 @@ -# Xash3D FWGS Engine -[![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) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \ -[![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) +# Xash3D FWGS Engine Xash3D FWGS icon +[![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) \ +[![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) \ +[![Download Daily Build](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) -Xash3D FWGS is a fork of Xash3D Engine by Unkle Mike with extended features and crossplatform. +Xash3D ([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. -``` -Xash3D is a game engine, aimed to provide compatibility with Half-Life Engine, -as well as to give game developers well known workflow and extend it. -Read more about Xash3D on ModDB: https://www.moddb.com/engines/xash3d-engine -``` +Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike. + +## Donate +[![Donate to FWGS button](https://img.shields.io/badge/Donate_to_FWGS-%3C3-magenta)](Documentation/donate.md) \ +If 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). ## Fork features -* HLSDK 2.4 support. -* Crossplatform: supported x86 and ARM on Windows/Linux/BSD/Android. ([see docs for more info](Documentation/ports.md)) -* Modern compilers support: say no more to MSVC6. -* Better multiplayer support: multiple master servers, headless dedicated server. -* Mobility API: allows better game integration on mobile devices(vibration, touch controls) -* Different input methods: touch, gamepad and classic mouse & keyboard. +* Steam Half-Life (HLSDK 2.5) support. +* Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md). +* Better multiplayer: multiple master servers, headless dedicated server, voice chat, [GoldSrc protocol support](Documentation/goldsrc-protocol-support.md) and IPv6 support. +* Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software. +* Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform. +* Mobility API: better game integration on mobile devices (vibration, touch controls). +* Different input methods: touch and gamepad in addition to mouse & keyboard. * TrueType font rendering, as a part of mainui_cpp. -* Multiple renderers support: OpenGL, GLESv1, GLESv2, Software -* A set of small improvements, without broken compatibility. - -## Planned fork features -* Virtual Reality support and game API -* Voice support -* Vulkan renderer +* External VGUI support module. +* PNG & KTX2 image format support. +* Ogg Vorbis (`.ogg`) & Ogg Opus (`.opus`) audio formats support. +* [A set of small improvements](Documentation/), without broken compatibility. ## Installation & Running 0) 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. 1) Copy engine binaries to some directory. 2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries. -If 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-xash3d). -This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. +If 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). +This repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods. You still needed to copy `valve` directory as all game resources located there. 3) Run the main executable (`xash3d.exe` or AppImage). For additional info, run Xash3D with `-help` command line key. +### Android +0) Install the APK file. +1) Copy `valve` directory to a folder named `xash` in the Internal storage. +2) Run games from within the app. + ## Contributing -* 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 -* Issues are accepted in both English and Russian +* 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. +* Issues are accepted in both English and Russian. * Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file. ## Build instructions -We are using Waf build system. If you have some Waf-related questions, I recommend you to read https://waf.io/book/ +We are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/). -NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using! +**NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using!** ### Prerequisites - -If your CPU is x86 compatible, we are building 32-bit code by default. This was dont for keeping compatibility with Steam releases of Half-Life and based on it's engine games. +If 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. Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! -If 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-xash3d). -This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. +If 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). +This repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods. #### Windows (Visual Studio) * Install Visual Studio. @@ -65,31 +68,52 @@ This repository contains our fork of HLSDK and restored source code for some of #### GNU/Linux ##### Debian/Ubuntu -* Enable i386 on your system, if you're compiling 32-bit engine on amd64. If not, skip this +* Only for 32-bit engine on 64-bit x86 operating system: + * Enable i386 on your system: `$ sudo dpkg --add-architecture i386`. + * Install `aptitude` ([why?](https://github.com/FWGS/xash3d-fwgs/issues/1828#issuecomment-2415131759)): `$ sudo apt update && sudo apt upgrade && sudo apt install aptitude` + * 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`. + * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig`. -`$ sudo dpkg --add-architecture i386` -* Install development tools - * For 32-bit engine on amd64: \ - `$ sudo apt install build-essential gcc-multilib g++-multilib python libsdl2-dev:i386 libfontconfig-dev:i386 libfreetype6-dev:i386` - * For everything else: \ - `$ sudo apt install build-essential python libsdl2-dev libfontconfig-dev libfreetype6-dev` -* Clone this repostory: -`$ git clone --recursive https://github.com/FWGS/xash3d-fwgs` +* For 64-bit engine on 64-bit x86 and other non-x86 systems: + * Install development tools: `$ sudo apt install git build-essential python libsdl2-dev libfreetype6-dev libopus-dev libbz2-dev libvorbis-dev libopusfile-dev libogg-dev`. + +* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. + +##### RedHat/Fedora +* Only for 32-bit engine on 64-bit x86 operating system: + * 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`. + * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`. + +* For 64-bit engine on 64-bit x86 and other non-x86 systems: + * 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`. + +* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. + +#### Android (Windows/Linux/macOS) +* Install [Android Studio](https://developer.android.com/studio) (or the command line tools). +* Install [Python](https://python.org) (at least 2.7, latest is better). +* Install [Git](https://git-scm.com/download/win). +* Install [Ninja](https://ninja-build.org/). +* Install [CMake](https://cmake.org/) (for some dependencies). + +* Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. ### Building #### Windows (Visual Studio) -0) Open command line +0) Open command line. 1) Navigate to `xash3d-fwgs` directory. -2) Carefully examine which build options are available: `waf --help` -3) Configure build: `waf configure -T release --sdl2=c:/path/to/SDL2` -4) Compile: `waf build` -5) Install: `waf install --destdir=c:/path/to/any/output/directory` +2) (optional) Examine which build options are available: `waf --help`. +3) Configure build: `waf configure --sdl2=c:/path/to/SDL2`. +4) Compile: `waf build`. +5) Install: `waf install --destdir=c:/path/to/any/output/directory`. #### Linux -If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure. +If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step is set correctly, prior to running configure. -0) Examine which build options are available: `./waf --help` -1) Configure build: `./waf configure -T release` -(You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor) -2) Compile: `./waf build` -3) Install(optional): `./waf install --destdir=/path/to/any/output/directory` +0) (optional) Examine which build options are available: `./waf --help`. +1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor). +2) Compile: `./waf build`. +3) Install: `./waf install --destdir=/path/to/any/output/directory`. + +#### Android (Windows/Linux/macOS) +You can just open the `android` folder in Android Studio and build from here, or use `gradlew` to build from command line. \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 00000000..ee4ba493 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +.gradle/ +build/ +.externalNativeBuild +.cxx/ +.idea/ +local.properties +.project +.classpath +.gradle +.settings +release/ +*.hprof +.vscode/ +*.bak \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 00000000..9f4389cc --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,140 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.time.LocalDateTime +import java.time.Month +import java.time.temporal.ChronoUnit + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "su.xash.engine" + ndkVersion = "28.2.13676358" + compileSdk = 35 + + defaultConfig { + applicationId = "su.xash.engine" + versionName = "0.21-" + getGitHash() + versionCode = getBuildNum() + minSdk = 21 + targetSdk = 35 + + externalNativeBuild { + val engineRoot = projectDir.parentFile.parent + + experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path + experimentalProperties["ninja.configure"] = "run-python" + experimentalProperties["ninja.arguments"] = setOf( + File(engineRoot, "scripts/configure-ninja.py").path, + engineRoot, + "--variant=\${ndk.variantName}", + "--abi=\${ndk.abi}", + "--configuration-dir=\${ndk.buildRoot}", + "--ndk-version=\${ndk.moduleNdkVersion}", + "--min-sdk-version=\${ndk.minPlatform}", + "--ndk-root=${android.ndkDirectory}", + // shut up, fake options + "-p:Configuration=\${ndk.variantName}", + "-p:Platform=\${ndk.abi}" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + } + } + + buildFeatures { + viewBinding = true + buildConfig = true + } + + lint { + abortOnError = false + } + +/* + androidResources { + noCompress += "" + } +*/ + + packaging { + jniLibs { + keepDebugSymbols.add("**/*.so") + useLegacyPackaging = true + } + } + + sourceSets { + getByName("main") { + assets.srcDirs("../../3rdparty/extras/xash-extras") + java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java") + } + } + + buildTypes { + debug { + isMinifyEnabled = false + isShrinkResources = false + isDebuggable = true + applicationIdSuffix = ".test" + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + } + + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" + ) + } + + register("asan") { + initWith(getByName("debug")) + } + + register("continuous") { + initWith(getByName("release")) + applicationIdSuffix = ".test" + } + } +} + +dependencies { + implementation(libs.material) + + implementation(libs.appcompat) + implementation(libs.navigation.runtime.ktx) + implementation(libs.navigation.fragment.ktx) + implementation(libs.navigation.ui.ktx) + implementation(libs.preference.ktx) + implementation(libs.swiperefreshlayout) + + implementation(libs.acra.http) +} + +fun getBuildNum(): Int { + val now = LocalDateTime.now() + val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0) + val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS) + val minuteOfDay = now.hour * 60 + now.minute + return (qBuildNum * 10000 + minuteOfDay).toInt() +} + +fun getGitHash(): String { + val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir) + .redirectErrorStream(true).start() + return process.inputStream.bufferedReader().readText().trim() +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 00000000..be2ae12c --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,116 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep class su.xash.engine.XashActivity { + java.lang.String loadAndroidID(); + java.lang.String getAndroidID(); + void saveAndroidID(java.lang.String); + java.lang.String getCallingPackage(); + java.lang.String[] getAssetsList(boolean, java.lang.String); + android.content.res.AssetManager getAssets(boolean); +} + +-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection { + void nativeCommitText(java.lang.String, int); + void nativeGenerateScancodeForUnichar(char); +} + +-keep,includedescriptorclasses class org.libsdl.app.SDLActivity { + # for some reason these aren't compatible with allowoptimization modifier + boolean supportsRelativeMouse(); + void setWindowStyle(boolean); +} + +-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity { + 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 + boolean onNativeSoftReturnKey(); + void onNativeKeyboardFocusLost(); + boolean isScreenKeyboardShown(); + android.util.DisplayMetrics getDisplayDPI(); + java.lang.String clipboardGetText(); + boolean clipboardHasText(); + void clipboardSetText(java.lang.String); + int createCustomCursor(int[], int, int, int, int); + void destroyCustomCursor(int); + android.content.Context getContext(); + boolean getManifestEnvironmentVariables(); + android.view.Surface getNativeSurface(); + void initTouch(); + boolean isAndroidTV(); + boolean isChromebook(); + boolean isDeXMode(); + boolean isTablet(); + void manualBackButton(); + int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); + void minimizeWindow(); + int openURL(java.lang.String); + void requestPermission(java.lang.String, int); + int showToast(java.lang.String, int, int, int, int); + boolean sendMessage(int, int); + boolean setActivityTitle(java.lang.String); + boolean setCustomCursor(int); + void setOrientation(int, int, boolean, java.lang.String); + boolean setRelativeMouseEnabled(boolean); + boolean setSystemCursor(int); + boolean shouldMinimizeOnFocusLoss(); + boolean showTextInput(int, int, int, int); +} + +-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { + boolean initialize(boolean, boolean); + boolean openDevice(int); + int sendOutputReport(int, byte[]); + int sendFeatureReport(int, byte[]); + boolean getFeatureReport(int, byte[]); + void closeDevice(int); +} + +-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager { + int[] getAudioOutputDevices(); + int[] getAudioInputDevices(); + int[] audioOpen(int, int, int, int, int); + void audioWriteFloatBuffer(float[]); + void audioWriteShortBuffer(short[]); + void audioWriteByteBuffer(byte[]); + void audioClose(); + int[] captureOpen(int, int, int, int, int); + int captureReadFloatBuffer(float[], boolean); + int captureReadShortBuffer(short[], boolean); + int captureReadByteBuffer(byte[], boolean); + void captureClose(); + void audioSetThreadPriority(boolean, int); + native int nativeSetupJNI(); + native void removeAudioDevice(boolean, int); + native void addAudioDevice(boolean, int); +} + +-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager { + void pollInputDevices(); + void pollHapticDevices(); + void hapticRun(int, float, int); + void hapticStop(int); +} + +# Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor. +-dontwarn javax.annotation.processing.Processor +-dontwarn javax.annotation.processing.AbstractProcessor +-dontwarn javax.annotation.processing.SupportedOptions diff --git a/android/app/run-python b/android/app/run-python new file mode 100755 index 00000000..88892d53 --- /dev/null +++ b/android/app/run-python @@ -0,0 +1,3 @@ +#!/bin/bash + +exec python $@ diff --git a/android/app/run-python.bat b/android/app/run-python.bat new file mode 100644 index 00000000..6401ceed --- /dev/null +++ b/android/app/run-python.bat @@ -0,0 +1 @@ +python %* diff --git a/android/app/src/asan/jniLibs/arm64-v8a/libclang_rt.asan-aarch64-android.so b/android/app/src/asan/jniLibs/arm64-v8a/libclang_rt.asan-aarch64-android.so new file mode 100644 index 00000000..3b5dea16 Binary files /dev/null and b/android/app/src/asan/jniLibs/arm64-v8a/libclang_rt.asan-aarch64-android.so differ diff --git a/android/app/src/asan/jniLibs/armeabi-v7a/libclang_rt.asan-arm-android.so b/android/app/src/asan/jniLibs/armeabi-v7a/libclang_rt.asan-arm-android.so new file mode 100644 index 00000000..45b90d7f Binary files /dev/null and b/android/app/src/asan/jniLibs/armeabi-v7a/libclang_rt.asan-arm-android.so differ diff --git a/android/app/src/asan/jniLibs/x86/libclang_rt.asan-i686-android.so b/android/app/src/asan/jniLibs/x86/libclang_rt.asan-i686-android.so new file mode 100644 index 00000000..6a8357cc Binary files /dev/null and b/android/app/src/asan/jniLibs/x86/libclang_rt.asan-i686-android.so differ diff --git a/android/app/src/asan/jniLibs/x86_64/libclang_rt.asan-x86_64-android.so b/android/app/src/asan/jniLibs/x86_64/libclang_rt.asan-x86_64-android.so new file mode 100644 index 00000000..0400ec9d Binary files /dev/null and b/android/app/src/asan/jniLibs/x86_64/libclang_rt.asan-x86_64-android.so differ diff --git a/android/app/src/asan/res/values/strings.xml b/android/app/src/asan/res/values/strings.xml new file mode 100644 index 00000000..fa9fca0c --- /dev/null +++ b/android/app/src/asan/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Xash3D FWGS (Test) + su.xash.engine.test.documents + \ No newline at end of file diff --git a/android/app/src/asan/resources/lib/arm64-v8a/wrap.sh b/android/app/src/asan/resources/lib/arm64-v8a/wrap.sh new file mode 100644 index 00000000..99045e69 --- /dev/null +++ b/android/app/src/asan/resources/lib/arm64-v8a/wrap.sh @@ -0,0 +1,26 @@ + +#!/system/bin/sh +HERE=$(cd "$(dirname "$0")" && pwd) +cmd=$1 +shift +# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- +# bit app running on a 64-bit device, the 64-bit getprop will fail to load +# because it will preload a 32-bit ASan runtime. +# https://github.com/android/ndk/issues/1744 +os_version=$(getprop ro.build.version.sdk) +if [ "$os_version" -eq "27" ]; then + cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" +elif [ "$os_version" -eq "28" ]; then + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" +else + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" +fi +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) +if [ -f "$HERE/libc++_shared.so" ]; then + # Workaround for https://github.com/android-ndk/ndk/issues/988. + export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" +else + export LD_PRELOAD="$ASAN_LIB" +fi +exec $cmd \ No newline at end of file diff --git a/android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh b/android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh new file mode 100644 index 00000000..99045e69 --- /dev/null +++ b/android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh @@ -0,0 +1,26 @@ + +#!/system/bin/sh +HERE=$(cd "$(dirname "$0")" && pwd) +cmd=$1 +shift +# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- +# bit app running on a 64-bit device, the 64-bit getprop will fail to load +# because it will preload a 32-bit ASan runtime. +# https://github.com/android/ndk/issues/1744 +os_version=$(getprop ro.build.version.sdk) +if [ "$os_version" -eq "27" ]; then + cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" +elif [ "$os_version" -eq "28" ]; then + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" +else + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" +fi +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) +if [ -f "$HERE/libc++_shared.so" ]; then + # Workaround for https://github.com/android-ndk/ndk/issues/988. + export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" +else + export LD_PRELOAD="$ASAN_LIB" +fi +exec $cmd \ No newline at end of file diff --git a/android/app/src/asan/resources/lib/x86/wrap.sh b/android/app/src/asan/resources/lib/x86/wrap.sh new file mode 100644 index 00000000..99045e69 --- /dev/null +++ b/android/app/src/asan/resources/lib/x86/wrap.sh @@ -0,0 +1,26 @@ + +#!/system/bin/sh +HERE=$(cd "$(dirname "$0")" && pwd) +cmd=$1 +shift +# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- +# bit app running on a 64-bit device, the 64-bit getprop will fail to load +# because it will preload a 32-bit ASan runtime. +# https://github.com/android/ndk/issues/1744 +os_version=$(getprop ro.build.version.sdk) +if [ "$os_version" -eq "27" ]; then + cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" +elif [ "$os_version" -eq "28" ]; then + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" +else + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" +fi +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) +if [ -f "$HERE/libc++_shared.so" ]; then + # Workaround for https://github.com/android-ndk/ndk/issues/988. + export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" +else + export LD_PRELOAD="$ASAN_LIB" +fi +exec $cmd \ No newline at end of file diff --git a/android/app/src/asan/resources/lib/x86_64/wrap.sh b/android/app/src/asan/resources/lib/x86_64/wrap.sh new file mode 100644 index 00000000..99045e69 --- /dev/null +++ b/android/app/src/asan/resources/lib/x86_64/wrap.sh @@ -0,0 +1,26 @@ + +#!/system/bin/sh +HERE=$(cd "$(dirname "$0")" && pwd) +cmd=$1 +shift +# This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- +# bit app running on a 64-bit device, the 64-bit getprop will fail to load +# because it will preload a 32-bit ASan runtime. +# https://github.com/android/ndk/issues/1744 +os_version=$(getprop ro.build.version.sdk) +if [ "$os_version" -eq "27" ]; then + cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" +elif [ "$os_version" -eq "28" ]; then + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" +else + cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" +fi +export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 +ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) +if [ -f "$HERE/libc++_shared.so" ]; then + # Workaround for https://github.com/android-ndk/ndk/issues/988. + export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" +else + export LD_PRELOAD="$ASAN_LIB" +fi +exec $cmd \ No newline at end of file diff --git a/android/app/src/continuous/res/values/strings.xml b/android/app/src/continuous/res/values/strings.xml new file mode 100644 index 00000000..fa9fca0c --- /dev/null +++ b/android/app/src/continuous/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Xash3D FWGS (Test) + su.xash.engine.test.documents + \ No newline at end of file diff --git a/android/app/src/debug/res/values/strings.xml b/android/app/src/debug/res/values/strings.xml new file mode 100644 index 00000000..b3ef5b7f --- /dev/null +++ b/android/app/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Xash3D FWGS (Test) + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..25112818 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/su/xash/engine/DedicatedActivity.kt b/android/app/src/main/java/su/xash/engine/DedicatedActivity.kt new file mode 100644 index 00000000..0c8d0e8c --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/DedicatedActivity.kt @@ -0,0 +1,3 @@ +package su.xash.engine + +class DedicatedActivity {} diff --git a/android/app/src/main/java/su/xash/engine/DedicatedService.kt b/android/app/src/main/java/su/xash/engine/DedicatedService.kt new file mode 100644 index 00000000..aaf67d60 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/DedicatedService.kt @@ -0,0 +1,11 @@ +package su.xash.engine + +import android.app.Service +import android.content.Intent +import android.os.IBinder + +class DedicatedService : Service() { + override fun onBind(intent: Intent?): IBinder? { + TODO("Not yet implemented") + } +} diff --git a/android/app/src/main/java/su/xash/engine/MainActivity.kt b/android/app/src/main/java/su/xash/engine/MainActivity.kt new file mode 100644 index 00000000..cecf7105 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/MainActivity.kt @@ -0,0 +1,35 @@ +package su.xash.engine + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.navigateUp +import androidx.navigation.ui.setupActionBarWithNavController +import su.xash.engine.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + private lateinit var appBarConfiguration: AppBarConfiguration + private lateinit var navController: NavController + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.toolbar) + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment + navController = navHostFragment.navController + appBarConfiguration = AppBarConfiguration(navController.graph) + setupActionBarWithNavController(navController, appBarConfiguration) + } + + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() + } +} diff --git a/android/app/src/main/java/su/xash/engine/MainApplication.kt b/android/app/src/main/java/su/xash/engine/MainApplication.kt new file mode 100644 index 00000000..4a955bd4 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/MainApplication.kt @@ -0,0 +1,27 @@ +package su.xash.engine + +import android.app.Application +import android.content.Context +import android.os.StrictMode +import org.acra.data.StringFormat +import org.acra.ktx.initAcra + +class MainApplication : Application() { + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + + if (!BuildConfig.DEBUG) { + initAcra { + buildConfigClass = BuildConfig::class.java + reportFormat = StringFormat.JSON + +// httpSender { +// uri = "http://bodis.pp.ua:5000/report" +// } + } + } else { + // enable strict mode to detect memory leaks etc. + StrictMode.enableDefaults(); + } + } +} diff --git a/android/app/src/main/java/su/xash/engine/XashActivity.java b/android/app/src/main/java/su/xash/engine/XashActivity.java new file mode 100644 index 00000000..3ccff7ee --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/XashActivity.java @@ -0,0 +1,154 @@ +package su.xash.engine; + +import android.annotation.SuppressLint; +import android.content.pm.ActivityInfo; +import android.content.res.AssetManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.Settings.Secure; +import android.util.Log; +import android.view.KeyEvent; +import android.view.WindowManager; + +import org.libsdl.app.SDLActivity; + +import su.xash.engine.util.AndroidBug5497Workaround; + +public class XashActivity extends SDLActivity { + private boolean mUseVolumeKeys; + private String mPackageName; + private static final String TAG = "XashActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + //getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES); + getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + + AndroidBug5497Workaround.assistActivity(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // Now that we don't exit from native code, we need to exit here, resetting + // application state (actually global variables that we don't cleanup on exit) + // + // When the issue with global variables will be resolved, remove that exit() call + System.exit(0); + } + + @Override + protected String[] getLibraries() { + return new String[]{"SDL2", "xash"}; + } + + @SuppressLint("HardwareIds") + private String getAndroidID() { + return Secure.getString(getContentResolver(), Secure.ANDROID_ID); + } + + @SuppressLint("ApplySharedPref") + private void saveAndroidID(String id) { + getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit(); + } + + private String loadAndroidID() { + return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", ""); + } + + @Override + public String getCallingPackage() { + if (mPackageName != null) { + return mPackageName; + } + + return super.getCallingPackage(); + } + + private AssetManager getAssets(boolean isEngine) { + AssetManager am = null; + + if (isEngine) { + am = getAssets(); + } else { + try { + am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets(); + } catch (Exception e) { + Log.e(TAG, "Unable to load mod assets!"); + e.printStackTrace(); + } + } + + return am; + } + + private String[] getAssetsList(boolean isEngine, String path) { + AssetManager am = getAssets(isEngine); + + try { + return am.list(path); + } catch (Exception e) { + e.printStackTrace(); + } + + return new String[]{}; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (SDLActivity.mBrokenLibraries) { + return false; + } + + int keyCode = event.getKeyCode(); + if (!mUseVolumeKeys) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT) { + return false; + } + } + + return getWindow().superDispatchKeyEvent(event); + } + + // TODO: REMOVE LATER, temporary launchers support? + @Override + protected String[] getArguments() { + String gamedir = getIntent().getStringExtra("gamedir"); + if (gamedir == null) gamedir = "valve"; + nativeSetenv("XASH3D_GAME", gamedir); + + String gamelibdir = getIntent().getStringExtra("gamelibdir"); + if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir); + + String pakfile = getIntent().getStringExtra("pakfile"); + if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile); + + String basedir = getIntent().getStringExtra("basedir"); + if (basedir != null) { + nativeSetenv("XASH3D_BASEDIR", basedir); + } else { + String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash"; + nativeSetenv("XASH3D_BASEDIR", rootPath); + } + + mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false); + mPackageName = getIntent().getStringExtra("package"); + + String[] env = getIntent().getStringArrayExtra("env"); + if (env != null) { + for (int i = 0; i < env.length; i += 2) + nativeSetenv(env[i], env[i + 1]); + } + + String argv = getIntent().getStringExtra("argv"); + if (argv == null) argv = "-console -log"; + return argv.split(" "); + } +} diff --git a/android/app/src/main/java/su/xash/engine/adapters/GameAdapter.kt b/android/app/src/main/java/su/xash/engine/adapters/GameAdapter.kt new file mode 100644 index 00000000..7e5ab4ef --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/adapters/GameAdapter.kt @@ -0,0 +1,70 @@ +package su.xash.engine.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.findNavController +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import su.xash.engine.R +import su.xash.engine.databinding.CardGameBinding +import su.xash.engine.model.Game +import su.xash.engine.ui.library.LibraryViewModel + + +class GameAdapter(private val libraryViewModel: LibraryViewModel) : + ListAdapter(DiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder { + val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return GameViewHolder(binding) + } + + override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) { + return holder.bind(getItem(position)) + } + + private class DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { + return oldItem.basedir.name == newItem.basedir.name + } + + override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { + return oldItem.basedir.name == newItem.basedir.name + } + + } + + inner class GameViewHolder(val binding: CardGameBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(game: Game) { + binding.apply { + gameTitle.text = game.title + + if (game.icon != null) { + gameIcon.setImageBitmap(game.icon) + } else { + gameIcon.visibility = View.GONE + } + + if (game.cover != null) { + gameCover.setImageBitmap(game.cover) + } else { + gameCover.visibility = View.GONE + } + + settingsButton.setOnClickListener { + libraryViewModel.setSelectedGame(game) + it.findNavController() + .navigate(R.id.action_libraryFragment_to_gameSettingsFragment) + } + + root.setOnClickListener { libraryViewModel.startEngine(it.context, game) } + launchButton.setOnClickListener { + libraryViewModel.startEngine(it.context, game) + } + } + } + } +} diff --git a/android/app/src/main/java/su/xash/engine/model/BackgroundBitmap.kt b/android/app/src/main/java/su/xash/engine/model/BackgroundBitmap.kt new file mode 100644 index 00000000..83e7d3ff --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/model/BackgroundBitmap.kt @@ -0,0 +1,90 @@ +package su.xash.engine.model + +import android.graphics.Bitmap +import android.graphics.Canvas +import su.xash.engine.util.TGAReader +import java.io.File +import java.io.FileInputStream +import java.util.Scanner + + +object BackgroundBitmap { + private const val BACKGROUND_ROWS = 3 + private const val BACKGROUND_COLUMNS = 4 + private const val BACKGROUND_WIDTH = 800 + private const val BACKGROUND_HEIGHT = 600 + + fun createBackground(file: File): Bitmap { + var bitmap = + Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888) + var canvas = Canvas(bitmap) + var x: Int + var y = 0 + var width: Int + var height = 0 + + val resourceFolder = File(file, "resource") + var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt") + if (!bgLayout.exists()) { + bgLayout = File(resourceFolder, "BackgroundLayout.txt") + } + + if (!bgLayout.exists()) { + val dir = File(resourceFolder, "background") + for (i in 0 until BACKGROUND_ROWS) { + x = 0 + for (j in 0 until BACKGROUND_COLUMNS) { + val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga" + val bmpFile = File(dir, filename) + val bmpImage = loadTga(bmpFile) + + canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null) + x += bmpImage.width + height = bmpImage.height + + } + y += height + } + return bitmap + } + + FileInputStream(bgLayout).use { inputStream -> + Scanner(inputStream).use { scanner -> + while (scanner.hasNext()) { + when (val str = scanner.next()) { + "resolution" -> { + width = scanner.nextInt() + height = scanner.nextInt() + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + canvas = Canvas(bitmap) + } + + else -> { + var bmpFile = file + str.split("/").forEach { bmpFile = File(bmpFile, it) } + //skip + scanner.next() + x = scanner.nextInt() + y = scanner.nextInt() + val bmp = loadTga(bmpFile) + canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null) + } + } + } + } + } + return bitmap + } + + private fun loadTga(file: File): Bitmap { + FileInputStream(file).use { + val buffer = it.readBytes() + val pixels = TGAReader.read(buffer, TGAReader.ARGB) + + val width = TGAReader.getWidth(buffer) + val height = TGAReader.getHeight(buffer) + + return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888) + } + } +} diff --git a/android/app/src/main/java/su/xash/engine/model/Game.kt b/android/app/src/main/java/su/xash/engine/model/Game.kt new file mode 100644 index 00000000..5ea6fab4 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/model/Game.kt @@ -0,0 +1,138 @@ +package su.xash.engine.model + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import su.xash.engine.XashActivity +import java.io.File +import java.io.FileInputStream + + +class Game(val ctx: Context, val basedir: File) { + private var iconName = "game.ico" + var title = "Unknown Game" + var icon: Bitmap? = null + var cover: Bitmap? = null + + private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE) + + init { + val gameInfo = File(basedir, "gameinfo.txt") + if (gameInfo.exists()) { + parseGameInfo(gameInfo) + } else { + val libListGam = File(basedir, "liblist.gam") + if (libListGam.exists()) parseGameInfo(libListGam) + } + + val iconFile = File(basedir, iconName) + if (iconFile.exists()) { + icon = BitmapFactory.decodeFile(iconFile.path) + } + + try { + cover = BackgroundBitmap.createBackground(basedir) + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun startEngine(ctx: Context) { + ctx.startActivity(Intent(ctx, XashActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + putExtra("gamedir", basedir.name) + putExtra("argv", pref.getString("arguments", "-console -log")) + putExtra("usevolume", pref.getBoolean("use_volume_buttons", false)) + putExtra("basedir", basedir.parent) + //.putExtra("gamelibdir", getGameLibDir(context)) + //.putExtra("package", getPackageName()) } + }) + } + + private fun parseGameInfo(file: File) { + FileInputStream(file).use { inputStream -> + inputStream.bufferedReader().use { reader -> + reader.forEachLine { + val tokens = it.split("\\s+".toRegex(), limit = 2) + if (tokens.size >= 2) { + val k = tokens[0] + val v = tokens[1].trim('"') + + if (k == "title" || k == "game") title = v + if (k == "icon") iconName = v + } + } + } + } + } + + private fun getPackageName(): String? { +// return if (mDbEntry != null) { +// mDbEntry.getPackageName() +// } else null + return null + } + + private fun getGameLibDir(ctx: Context): String? { + val pkgName = getPackageName() + if (pkgName != null) { + val pkgInfo: PackageInfo = try { + ctx.packageManager.getPackageInfo(pkgName, 0) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + ctx.startActivity( + Intent( + Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName") + ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + return null + } + return pkgInfo.applicationInfo?.nativeLibraryDir + } + return ctx.applicationInfo.nativeLibraryDir + } + + companion object { + fun getGames(ctx: Context, file: File): List { + val games = mutableListOf() + + if (checkIfGamedir(file)) { + games.add(Game(ctx, file)) + } else { + file.listFiles()?.forEach { + if (it.isDirectory) { + if (checkIfGamedir(it)) { + games.add(Game(ctx, it)) + } + } + } + } + + return games + } + + fun checkIfGamedir(file: File): Boolean { + if (File(file, "liblist.gam").exists()) return true + if (File(file, "gameinfo.txt").exists()) return true + + return false + } + } +} + +// Intent intent = new Intent("su.xash.engine.MOD"); +// for (ResolveInfo info : context.getPackageManager() +// .queryIntentActivities(intent, PackageManager.GET_META_DATA)) { +// String packageName = info.activityInfo.applicationInfo.packageName; +// String gameDir = info.activityInfo.applicationInfo.metaData.getString( +// "su.xash.engine.gamedir"); +// Log.d(TAG, "package = " + packageName + " gamedir = " + gameDir); +// } + +//public void startEngine(Context context) { +// 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())); +//} diff --git a/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt b/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt new file mode 100644 index 00000000..f1b2312b --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt @@ -0,0 +1,177 @@ +package su.xash.engine.ui.library + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.view.MenuProvider +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.navigation.fragment.findNavController +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import su.xash.engine.BuildConfig +import su.xash.engine.R +import su.xash.engine.adapters.GameAdapter +import su.xash.engine.databinding.FragmentLibraryBinding + + +class LibraryFragment : Fragment(), MenuProvider { + private var _binding: FragmentLibraryBinding? = null + private val binding get() = _binding!! + + private val libraryViewModel: LibraryViewModel by activityViewModels() + + private val startActivityForResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (checkStoragePermissions()) { + libraryViewModel.reloadGames(requireContext()) + } + } + + private val requiredPermissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + val granted = permissions.entries.all { it.value } + if (granted) { + libraryViewModel.reloadGames(requireContext()) + } else { + checkStoragePermissions() + } + } + + private fun checkStoragePermissions(): Boolean { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (!Environment.isExternalStorageManager()) + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.file_access_required) + setMessage(R.string.file_access_message) + setPositiveButton(android.R.string.ok) { _, _ -> + startActivityForResult.launch( + Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData( + Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) + ) + ) + } + setCancelable(false) + show() + + return false + } else { + return true + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val permissionsNeeded = requiredPermissions.filter { + ContextCompat.checkSelfPermission( + requireContext(), + it + ) != PackageManager.PERMISSION_GRANTED + }.toTypedArray() + + if (!permissionsNeeded.isEmpty()) { + val showRationale = permissionsNeeded.any { + ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it) + } + + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(R.string.external_storage_required) + setMessage(R.string.external_storage_message) + setPositiveButton(android.R.string.ok) { _, _ -> + if (showRationale) { + requestPermissionLauncher.launch(permissionsNeeded) + } else { + val intent = + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = + Uri.fromParts("package", requireContext().packageName, null) + } + startActivity(intent) + } + } + setCancelable(false) + show() + } + + return false + } else { + return true + } + } else { + return true + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentLibraryBinding.inflate(inflater, container, false) + + val adapter = GameAdapter(libraryViewModel) + binding.gamesList.adapter = adapter + + requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) } + + libraryViewModel.isReloading.observe(viewLifecycleOwner) { + binding.swipeRefresh.isRefreshing = it + } + + libraryViewModel.installedGames.observe(viewLifecycleOwner) { + (binding.gamesList.adapter as GameAdapter).submitList(it) + } + + if (checkStoragePermissions()) { + libraryViewModel.reloadGames(requireContext()) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.menu_library, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + when (menuItem.itemId) { + R.id.action_settings -> { + findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment) + } + } + + return false + } + + override fun onResume() { + super.onResume() + + if (checkStoragePermissions()) { + libraryViewModel.reloadGames(requireContext()) + } + } +} diff --git a/android/app/src/main/java/su/xash/engine/ui/library/LibraryViewModel.kt b/android/app/src/main/java/su/xash/engine/ui/library/LibraryViewModel.kt new file mode 100644 index 00000000..91a20b4f --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/library/LibraryViewModel.kt @@ -0,0 +1,55 @@ +package su.xash.engine.ui.library + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.os.Environment +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import su.xash.engine.model.Game +import java.io.File + +class LibraryViewModel(application: Application) : AndroidViewModel(application) { + val installedGames: LiveData> get() = _installedGames + private val _installedGames = MutableLiveData(emptyList()) + + val isReloading: LiveData get() = _isReloading + private val _isReloading = MutableLiveData(false) + + val selectedItem: LiveData get() = _selectedItem + private val _selectedItem = MutableLiveData() + + private val appPreferences: SharedPreferences = + application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE) + + fun reloadGames(ctx: Context) { + if (isReloading.value == true) { + return + } + _isReloading.value = true + + viewModelScope.launch { + withContext(Dispatchers.IO) { + val rootPath = appPreferences.getString("game_path", null) + ?: (Environment.getExternalStorageDirectory().absolutePath + "/xash") + val root = File(rootPath) + + _installedGames.postValue(Game.getGames(ctx, root)) + _isReloading.postValue(false) + } + } + } + + fun setSelectedGame(game: Game) { + _selectedItem.value = game + } + + fun startEngine(ctx: Context, game: Game) { + game.startEngine(ctx) + } +} diff --git a/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsFragment.kt b/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsFragment.kt new file mode 100644 index 00000000..e3bef9fd --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsFragment.kt @@ -0,0 +1,31 @@ +package su.xash.engine.ui.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import su.xash.engine.databinding.FragmentAppSettingsBinding + +class AppSettingsFragment : Fragment() { + private var _binding: FragmentAppSettingsBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentAppSettingsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + childFragmentManager.beginTransaction() + .add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit(); + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsPreferenceFragment.kt b/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsPreferenceFragment.kt new file mode 100644 index 00000000..3d61560f --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsPreferenceFragment.kt @@ -0,0 +1,12 @@ +package su.xash.engine.ui.settings + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import su.xash.engine.R + +class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.sharedPreferencesName = "app_preferences"; + setPreferencesFromResource(R.xml.app_preferences, rootKey); + } +} diff --git a/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsFragment.kt b/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsFragment.kt new file mode 100644 index 00000000..d9fbc30e --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsFragment.kt @@ -0,0 +1,57 @@ +package su.xash.engine.ui.settings + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import su.xash.engine.databinding.FragmentGameSettingsBinding +import su.xash.engine.ui.library.LibraryViewModel + +class GameSettingsFragment : Fragment() { + private var _binding: FragmentGameSettingsBinding? = null + private val binding get() = _binding!! + + private val libraryViewModel: LibraryViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentGameSettingsBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val game = libraryViewModel.selectedItem.value!! + + binding.gameCard.apply { + gameTitle.text = game.title + + if (game.icon != null) { + gameIcon.setImageBitmap(game.icon) + } else { + gameIcon.visibility = View.GONE + } + + if (game.cover != null) { + gameCover.setImageBitmap(game.cover) + } else { + gameCover.visibility = View.GONE + } + + buttonsContainer.visibility = View.GONE + } + + childFragmentManager.beginTransaction() + .add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game)) + .commit(); + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsPreferenceFragment.kt b/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsPreferenceFragment.kt new file mode 100644 index 00000000..7e765078 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsPreferenceFragment.kt @@ -0,0 +1,40 @@ +package su.xash.engine.ui.settings + +import android.os.Bundle +import androidx.preference.ListPreference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreferenceCompat +import su.xash.engine.R +import su.xash.engine.model.Game + +class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.sharedPreferencesName = game.basedir.name; + setPreferencesFromResource(R.xml.game_preferences, rootKey); + + val packageList = findPreference("package_name")!! + packageList.entries = arrayOf(getString(R.string.app_name)) + packageList.entryValues = arrayOf(requireContext().packageName) + + if (packageList.value == null) { + packageList.setValueIndex(0); + } + + val separatePackages = findPreference("separate_libraries")!! + val clientPackage = findPreference("client_package")!! + val serverPackage = findPreference("server_package")!! + separatePackages.setOnPreferenceChangeListener { _, newValue -> + if (newValue == true) { + packageList.isVisible = false + clientPackage.isVisible = true + serverPackage.isVisible = true + } else { + packageList.isVisible = true + clientPackage.isVisible = false + serverPackage.isVisible = false + } + + true + } + } +} diff --git a/android/app/src/main/java/su/xash/engine/util/AndroidBug5497Workaround.java b/android/app/src/main/java/su/xash/engine/util/AndroidBug5497Workaround.java new file mode 100644 index 00000000..27826557 --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/util/AndroidBug5497Workaround.java @@ -0,0 +1,49 @@ +package su.xash.engine.util; + +import android.app.Activity; +import android.graphics.Rect; +import android.view.View; +import android.widget.FrameLayout; + +public class AndroidBug5497Workaround { + // For more information, see https://code.google.com/p/android/issues/detail?id=5497 + // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. + + public static void assistActivity(Activity activity) { + new AndroidBug5497Workaround(activity); + } + + private View mChildOfContent; + private int usableHeightPrevious; + private FrameLayout.LayoutParams frameLayoutParams; + + private AndroidBug5497Workaround(Activity activity) { + FrameLayout content = activity.findViewById(android.R.id.content); + mChildOfContent = content.getChildAt(0); + mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent); + frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); + } + + private void possiblyResizeChildOfContent() { + int usableHeightNow = computeUsableHeight(); + if (usableHeightNow != usableHeightPrevious) { + int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); + int heightDifference = usableHeightSansKeyboard - usableHeightNow; + if (heightDifference > (usableHeightSansKeyboard / 4)) { + // keyboard probably just became visible + frameLayoutParams.height = usableHeightSansKeyboard - heightDifference; + } else { + // keyboard probably just became hidden + frameLayoutParams.height = usableHeightSansKeyboard; + } + mChildOfContent.requestLayout(); + usableHeightPrevious = usableHeightNow; + } + } + + private int computeUsableHeight() { + Rect r = new Rect(); + mChildOfContent.getWindowVisibleDisplayFrame(r); + return (r.bottom - r.top); + } +} diff --git a/android/app/src/main/java/su/xash/engine/util/TGAReader.java b/android/app/src/main/java/su/xash/engine/util/TGAReader.java new file mode 100644 index 00000000..c396315b --- /dev/null +++ b/android/app/src/main/java/su/xash/engine/util/TGAReader.java @@ -0,0 +1,589 @@ +/** + * TGAReader.java + *

+ * Copyright (c) 2014 Kenji Sasaki + * Released under the MIT license. + * https://github.com/npedotnet/TGAReader/blob/master/LICENSE + *

+ * English document + * https://github.com/npedotnet/TGAReader/blob/master/README.md + *

+ * Japanese document + * http://3dtech.jp/wiki/index.php?TGAReader + */ + +package su.xash.engine.util; + +import java.io.IOException; + +public final class TGAReader { + + public static final Order ARGB = new Order(16, 8, 0, 24); + public static final Order ABGR = new Order(0, 8, 16, 24); + + public static int getWidth(byte[] buffer) { + return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8; + } + + public static int getHeight(byte[] buffer) { + return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8; + } + + public static int[] read(byte[] buffer, Order order) throws IOException { + + // header +// int idFieldLength = buffer[0] & 0xFF; +// int colormapType = buffer[1] & 0xFF; + int type = buffer[2] & 0xFF; + int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8; + int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8; + int colormapDepth = buffer[7] & 0xFF; +// int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported +// int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported + int width = getWidth(buffer); + int height = getHeight(buffer); + int depth = buffer[16] & 0xFF; + int descriptor = buffer[17] & 0xFF; + + int[] pixels; + + // data + switch (type) { + case COLORMAP: { + int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; + pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order); + } + break; + case RGB: + pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order); + break; + case GRAYSCALE: + pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order); + break; + case COLORMAP_RLE: { + int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; + byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset); + pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order); + } + break; + case RGB_RLE: { + byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); + pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order); + } + break; + case GRAYSCALE_RLE: { + byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); + pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order); + } + break; + default: + throw new IOException("Unsupported image type: " + type); + } + + return pixels; + + } + + private static final int COLORMAP = 1; + private static final int RGB = 2; + private static final int GRAYSCALE = 3; + private static final int COLORMAP_RLE = 9; + private static final int RGB_RLE = 10; + private static final int GRAYSCALE_RLE = 11; + + private static final int RIGHT_ORIGIN = 0x10; + private static final int UPPER_ORIGIN = 0x20; + + private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) { + int elementCount = depth / 8; + byte[] elements = new byte[elementCount]; + int decodeBufferLength = elementCount * width * height; + byte[] decodeBuffer = new byte[decodeBufferLength]; + int decoded = 0; + while (decoded < decodeBufferLength) { + int packet = buffer[offset++] & 0xFF; + if ((packet & 0x80) != 0) { // RLE + for (int i = 0; i < elementCount; i++) { + elements[i] = buffer[offset++]; + } + int count = (packet & 0x7F) + 1; + for (int i = 0; i < count; i++) { + for (int j = 0; j < elementCount; j++) { + decodeBuffer[decoded++] = elements[j]; + } + } + } else { // RAW + int count = (packet + 1) * elementCount; + for (int i = 0; i < count; i++) { + decodeBuffer[decoded++] = buffer[offset++]; + } + } + } + return decodeBuffer; + } + + private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException { + int[] pixels; + int rs = order.redShift; + int gs = order.greenShift; + int bs = order.blueShift; + int as = order.alphaShift; + switch (depth) { + case 24: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 3 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * i + (width - j - 1)] = color; + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 3 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * (height - i - 1) + (width - j - 1)] = color; + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 3 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * i + j] = color; + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 3 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * (height - i - 1) + j] = color; + } + } + } + } + break; + case 32: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 4 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = palette[index + 3] & 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * i + (width - j - 1)] = color; + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 4 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = palette[index + 3] & 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * (height - i - 1) + (width - j - 1)] = color; + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 4 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = palette[index + 3] & 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * i + j] = color; + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int colormapIndex = bytes[offset + width * i + j] & + 0xFF - colormapOrigin; + int color = 0xFFFFFFFF; + if (colormapIndex >= 0) { + int index = 4 * colormapIndex + 18; + int b = palette[index] & 0xFF; + int g = palette[index + 1] & 0xFF; + int r = palette[index + 2] & 0xFF; + int a = palette[index + 3] & 0xFF; + color = (r << rs) | (g << gs) | (b << bs) | (a << as); + } + pixels[width * (height - i - 1) + j] = color; + } + } + } + } + break; + default: + throw new IOException("Unsupported depth:" + depth); + } + return pixels; + } + + private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException { + int[] pixels; + int rs = order.redShift; + int gs = order.greenShift; + int bs = order.blueShift; + int as = order.alphaShift; + switch (depth) { + case 24: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 3 * width * i + 3 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = 0xFF; + pixels[width * i + (width - j - 1)] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 3 * width * i + 3 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = 0xFF; + pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 3 * width * i + 3 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = 0xFF; + pixels[width * i + j] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 3 * width * i + 3 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = 0xFF; + pixels[width * (height - i - 1) + j] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } + } + break; + case 32: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 4 * width * i + 4 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = bytes[index + 3] & 0xFF; + pixels[width * i + (width - j - 1)] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 4 * width * i + 4 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = bytes[index + 3] & 0xFF; + pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 4 * width * i + 4 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = bytes[index + 3] & 0xFF; + pixels[width * i + j] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int index = offset + 4 * width * i + 4 * j; + int b = bytes[index] & 0xFF; + int g = bytes[index + 1] & 0xFF; + int r = bytes[index + 2] & 0xFF; + int a = bytes[index + 3] & 0xFF; + pixels[width * (height - i - 1) + j] = (r << rs) | + (g << gs) | + (b << bs) | + (a << as); + } + } + } + } + break; + default: + throw new IOException("Unsupported depth:" + depth); + } + return pixels; + } + + private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException { + int[] pixels; + int rs = order.redShift; + int gs = order.greenShift; + int bs = order.blueShift; + int as = order.alphaShift; + switch (depth) { + case 8: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + width * i + j] & 0xFF; + int a = 0xFF; + pixels[width * i + (width - j - 1)] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + width * i + j] & 0xFF; + int a = 0xFF; + pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + width * i + j] & 0xFF; + int a = 0xFF; + pixels[width * i + j] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + width * i + j] & 0xFF; + int a = 0xFF; + pixels[width * (height - i - 1) + j] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } + } + break; + case 16: + pixels = new int[width * height]; + if ((descriptor & RIGHT_ORIGIN) != 0) { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; + int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; + pixels[width * i + (width - j - 1)] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } else { + // LowerRight + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; + int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; + pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } + } else { + if ((descriptor & UPPER_ORIGIN) != 0) { + // UpperLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; + int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; + pixels[width * i + j] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } else { + // LowerLeft + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; + int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; + pixels[width * (height - i - 1) + j] = (e << rs) | + (e << gs) | + (e << bs) | + (a << as); + } + } + } + } + break; + default: + throw new IOException("Unsupported depth:" + depth); + } + return pixels; + } + + private TGAReader() { + } + + public static final class Order { + Order(int redShift, int greenShift, int blueShift, int alphaShift) { + this.redShift = redShift; + this.greenShift = greenShift; + this.blueShift = blueShift; + this.alphaShift = alphaShift; + } + + public int redShift; + public int greenShift; + public int blueShift; + public int alphaShift; + } + +} diff --git a/android/app/src/main/res/drawable/ic_baseline_add_24.xml b/android/app/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 00000000..0553ae30 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_baseline_delete_24.xml b/android/app/src/main/res/drawable/ic_baseline_delete_24.xml new file mode 100644 index 00000000..79372b1d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_delete_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml b/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml new file mode 100644 index 00000000..76fd9059 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml new file mode 100644 index 00000000..97f9a3c0 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_baseline_settings_24.xml b/android/app/src/main/res/drawable/ic_baseline_settings_24.xml new file mode 100644 index 00000000..a7c7678d --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_settings_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_baseline_terminal_24.xml b/android/app/src/main/res/drawable/ic_baseline_terminal_24.xml new file mode 100644 index 00000000..fa304167 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_baseline_terminal_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..f24e65d1 --- /dev/null +++ b/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/card_game.xml b/android/app/src/main/res/layout/card_game.xml new file mode 100644 index 00000000..339061c8 --- /dev/null +++ b/android/app/src/main/res/layout/card_game.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/edit_text_preference.xml b/android/app/src/main/res/layout/edit_text_preference.xml new file mode 100644 index 00000000..fe89013b --- /dev/null +++ b/android/app/src/main/res/layout/edit_text_preference.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_app_settings.xml b/android/app/src/main/res/layout/fragment_app_settings.xml new file mode 100644 index 00000000..73a953d8 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_app_settings.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/android/app/src/main/res/layout/fragment_game_settings.xml b/android/app/src/main/res/layout/fragment_game_settings.xml new file mode 100644 index 00000000..702dd55e --- /dev/null +++ b/android/app/src/main/res/layout/fragment_game_settings.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_library.xml b/android/app/src/main/res/layout/fragment_library.xml new file mode 100644 index 00000000..12ef63d3 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_library.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/android/app/src/main/res/layout/list_preference.xml b/android/app/src/main/res/layout/list_preference.xml new file mode 100644 index 00000000..3348546d --- /dev/null +++ b/android/app/src/main/res/layout/list_preference.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/android/app/src/main/res/layout/switch_preference.xml b/android/app/src/main/res/layout/switch_preference.xml new file mode 100644 index 00000000..51e8bfd6 --- /dev/null +++ b/android/app/src/main/res/layout/switch_preference.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/android/app/src/main/res/menu/menu_game_settings.xml b/android/app/src/main/res/menu/menu_game_settings.xml new file mode 100644 index 00000000..d2b81731 --- /dev/null +++ b/android/app/src/main/res/menu/menu_game_settings.xml @@ -0,0 +1,9 @@ +

+ + diff --git a/android/app/src/main/res/menu/menu_library.xml b/android/app/src/main/res/menu/menu_library.xml new file mode 100644 index 00000000..c38c93fe --- /dev/null +++ b/android/app/src/main/res/menu/menu_library.xml @@ -0,0 +1,8 @@ + + + diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..4df90c3c --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml new file mode 100644 index 00000000..bc3a6af3 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_monochrome.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_monochrome.xml new file mode 100644 index 00000000..98d6867d --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_monochrome.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..eb0b9248 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..5444e97c Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c9dccea5 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..d1d5ddce Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..044af09d Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..dfe09de9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..0b33b3df Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..53588d42 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f3e13b91 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..992a8b31 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/navigation/nav_graph.xml b/android/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 00000000..f24610bc --- /dev/null +++ b/android/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/android/app/src/main/res/values-es/strings.xml b/android/app/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..bacc4981 --- /dev/null +++ b/android/app/src/main/res/values-es/strings.xml @@ -0,0 +1,21 @@ + + + Xash3D FWGS + Librería + Dedicado + Ajustes + Ajustes del juego + Argumentos de la línea de comandos + Usar botones de volumen en el juego + Paquete de librerías + Librerías de paquetes separados + Paquete de cliente + Paquete de servidor + Usar iconos en lugar de fondos + Ubicación de los datos del juego + Se requiere acceso a todos los archivos + Se requiere acceso a todos los archivos para que la aplicación funcione correctamente. + Seleccionar directorio actual + Se requiere acceso al almacenamiento externo + Se requiere acceso al almacenamiento externo para que la aplicación funcione correctamente. + \ No newline at end of file diff --git a/android/app/src/main/res/values-pt-rBR/strings.xml b/android/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..760234c0 --- /dev/null +++ b/android/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,20 @@ + + + Biblioteca + Dedicado + Configurações + Configurações do jogo + Argumentos de linha de comando + Usar botões de volume no jogo + Pacote de bibliotecas + Bibliotecas de pacotes separados + Pacote do cliente + Pacote do servidor + Usar ícones em vez de fundos + Local dos dados do jogo + Acesso a todos os arquivos necessário + É necessário acesso a todos os arquivos para que o aplicativo funcione corretamente. + Selecionar diretório atual + Acesso ao armazenamento externo necessário + É necessário acesso ao armazenamento externo para que o aplicativo funcione corretamente. + diff --git a/android/app/src/main/res/values-ru/strings.xml b/android/app/src/main/res/values-ru/strings.xml new file mode 100644 index 00000000..9c5cc493 --- /dev/null +++ b/android/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,20 @@ + + + Библиотека + Выделенный + Настройки + Настройки игры + Аргументы командной строки + Использовать кнопки громкости + Пакет библиотек + Библиотеки из отдельных пакетов + Пакет клиента + Пакет сервера + Использовать иконки вместо фона + Расположение игровых данных + Требуется доступ ко всем файлам + Для правильной работы приложения требуется доступ ко всем файлам. + Выбрать текущую директорию + Требуется доступ к внешнему хранилищу + Для правильной работы приложения требуется доступ к внешнему хранилищу. + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..c692d368 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #F74843 + #FB7E14 + #000000 + #151515 + #292929 + + diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..e01ae6da --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #F74843 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..ff46f4ed --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ + + + Xash3D FWGS + Library + Dedicated + Settings + Game Settings + Command-line arguments + Use volume buttons in-game + Libraries package + Libraries from separate packages + Client package + Server package + Use icons instead of backgrounds + Game data location + All-files access required + All-files access is required for the app to function properly. + Select current directory + External storage access required + External storage access is required for the app to function properly. + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..7d7a3114 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..e3eeb1ba --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,18 @@ + + + + diff --git a/android/app/src/main/res/xml/app_preferences.xml b/android/app/src/main/res/xml/app_preferences.xml new file mode 100644 index 00000000..b5d94e96 --- /dev/null +++ b/android/app/src/main/res/xml/app_preferences.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/android/app/src/main/res/xml/game_preferences.xml b/android/app/src/main/res/xml/game_preferences.xml new file mode 100644 index 00000000..73e7a047 --- /dev/null +++ b/android/app/src/main/res/xml/game_preferences.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 00000000..78a9763a --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false +} diff --git a/android/debug.keystore b/android/debug.keystore new file mode 100644 index 00000000..41f7fccb Binary files /dev/null and b/android/debug.keystore differ diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 00000000..1533bac1 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,27 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true + + +# Enable verbose output for CMake +android.native.buildOutput=verbose diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml new file mode 100644 index 00000000..cca86651 --- /dev/null +++ b/android/gradle/libs.versions.toml @@ -0,0 +1,23 @@ +[versions] +acraHttp = "5.12.0" +agp = "8.11.1" +appcompat = "1.7.1" +kotlin = "2.2.0" +material = "1.12.0" +navigationRuntimeKtx = "2.9.1" +preferenceKtx = "1.2.1" +swiperefreshlayout = "1.1.0" + +[libraries] +acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" } +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +material = { module = "com.google.android.material:material", version.ref = "material" } +navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationRuntimeKtx" } +navigation-runtime-ktx = { module = "androidx.navigation:navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" } +navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationRuntimeKtx" } +preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } +swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..2b338a93 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..eb8a4c7d --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jul 01 19:51:06 EEST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 00000000..9ae3cf5a --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Xash3D FWGS" +include(":app") diff --git a/common/backends.h b/common/backends.h index 84156534..52f7b7e8 100644 --- a/common/backends.h +++ b/common/backends.h @@ -18,27 +18,17 @@ GNU General Public License for more details. // video backends (XASH_VIDEO) #define VIDEO_NULL 0 #define VIDEO_SDL 1 -#define VIDEO_ANDROID 2 #define VIDEO_FBDEV 3 #define VIDEO_DOS 4 - // audio backends (XASH_SOUND) #define SOUND_NULL 0 #define SOUND_SDL 1 -#define SOUND_OPENSLES 2 #define SOUND_ALSA 3 -// crash handler (XASH_CRASHHANDLER) -#define CRASHHANDLER_NULL 0 -#define CRASHHANDLER_UCONTEXT 1 -#define CRASHHANDLER_DBGHELP 2 -#define CRASHHANDLER_WIN32 3 - // input (XASH_INPUT) #define INPUT_NULL 0 #define INPUT_SDL 1 -#define INPUT_ANDROID 2 #define INPUT_EVDEV 3 // timer (XASH_TIMER) @@ -51,9 +41,8 @@ GNU General Public License for more details. // messageboxes (XASH_MESSAGEBOX) #define MSGBOX_STDERR 0 #define MSGBOX_SDL 1 -#define MSGBOX_ANDROID 2 #define MSGBOX_WIN32 3 - +#define MSGBOX_NSWITCH 4 // library loading (XASH_LIB) #define LIB_NULL 0 @@ -61,5 +50,8 @@ GNU General Public License for more details. #define LIB_WIN32 2 #define LIB_STATIC 3 +// movies (XASH_AVI) +#define AVI_NULL 0 +#define AVI_FFMPEG 1 #endif /* BACKENDS_H */ diff --git a/common/bspfile.h b/common/bspfile.h index 1fa5721a..a9c79c6a 100644 --- a/common/bspfile.h +++ b/common/bspfile.h @@ -16,8 +16,6 @@ GNU General Public License for more details. #ifndef BSPFILE_H #define BSPFILE_H -//#define SUPPORT_BSP2_FORMAT // allow to loading Darkplaces BSP2 maps (with broke binary compatibility) - /* ============================================================================== @@ -61,29 +59,19 @@ BRUSH MODELS #define LS_UNUSED 0xFE #define LS_NONE 0xFF +#define MAX_MAP_CLIPNODES_HLBSP 32767 +#define MAX_MAP_CLIPNODES_BSP2 524288 + // these limis not using by modelloader but only for displaying 'mapstats' correctly -#ifdef SUPPORT_BSP2_FORMAT #define MAX_MAP_MODELS 2048 // embedded models #define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough #define MAX_MAP_PLANES 131072 // can be increased without problems #define MAX_MAP_NODES 262144 // can be increased without problems -#define MAX_MAP_CLIPNODES 524288 // can be increased without problems +#define MAX_MAP_CLIPNODES MAX_MAP_CLIPNODES_BSP2 // can be increased without problems #define MAX_MAP_LEAFS 131072 // CRITICAL STUFF to run ad_sepulcher!!! #define MAX_MAP_VERTS 524288 // can be increased without problems #define MAX_MAP_FACES 262144 // can be increased without problems #define MAX_MAP_MARKSURFACES 524288 // can be increased without problems -#else -// increased to match PrimeXT compilers -#define MAX_MAP_MODELS 1024 // embedded models -#define MAX_MAP_ENTSTRING 0x100000 // 1 Mb should be enough -#define MAX_MAP_PLANES 65536 // can be increased without problems -#define MAX_MAP_NODES 32767 // because negative shorts are leafs -#define MAX_MAP_CLIPNODES 32767 // because negative shorts are contents -#define MAX_MAP_LEAFS 32767 // signed short limit -#define MAX_MAP_VERTS 65535 // unsigned short limit -#define MAX_MAP_FACES 65535 // unsigned short limit -#define MAX_MAP_MARKSURFACES 65535 // unsigned short limit -#endif #define MAX_MAP_ENTITIES 8192 // network limit #define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo diff --git a/common/com_image.h b/common/com_image.h index c270cf26..098a0d12 100644 --- a/common/com_image.h +++ b/common/com_image.h @@ -9,7 +9,20 @@ NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == ======================================================================== */ #define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE) -#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2 || type == PF_BC6H_SIGNED || type == PF_BC6H_UNSIGNED || type == PF_BC7) +#define ImageCompressed( type ) \ + ( type == PF_DXT1 \ + || type == PF_DXT3 \ + || type == PF_DXT5 \ + || type == PF_ATI2 \ + || type == PF_BC4_SIGNED \ + || type == PF_BC4_UNSIGNED \ + || type == PF_BC5_SIGNED \ + || type == PF_BC5_UNSIGNED \ + || type == PF_BC6H_SIGNED \ + || type == PF_BC6H_UNSIGNED \ + || type == PF_BC7_UNORM \ + || type == PF_BC7_SRGB \ + || type == PF_KTX2_RAW ) typedef enum { @@ -25,9 +38,15 @@ typedef enum PF_DXT3, // s3tc DXT3/BC2 format PF_DXT5, // s3tc DXT5/BC3 format PF_ATI2, // latc ATI2N/BC5 format + PF_BC4_SIGNED, + PF_BC4_UNSIGNED, + PF_BC5_SIGNED, + PF_BC5_UNSIGNED, PF_BC6H_SIGNED, // bptc BC6H signed FP16 format PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format - PF_BC7, // bptc BC7 format + PF_BC7_UNORM, // bptc BC7 format + PF_BC7_SRGB, + PF_KTX2_RAW, // Raw KTX2 data, used for yet unsupported KTX2 subformats PF_TOTALCOUNT, // must be last } pixformat_t; @@ -49,6 +68,8 @@ typedef enum IL_DDS_HARDWARE = BIT(4), // DXT compression is support IL_LOAD_DECAL = BIT(5), // special mode for load gradient decals IL_OVERVIEW = BIT(6), // overview required some unque operations + IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals + IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk) } ilFlags_t; // goes into rgbdata_t->encode diff --git a/common/com_model.h b/common/com_model.h index 36f2e936..febb214c 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -60,26 +60,29 @@ typedef struct vec3_t position; } mvertex_t; -typedef struct +typedef struct mclipnode32_s { - int planenum; -#ifdef SUPPORT_BSP2_FORMAT - int children[2]; // negative numbers are contents -#else - short children[2]; // negative numbers are contents -#endif -} mclipnode_t; + int planenum; + int children[2]; // negative numbers are contents +} mclipnode32_t; + +typedef struct mclipnode16_s +{ + int planenum; + short children[2]; // negative numbers are contents +} mclipnode16_t; // size is matched but representation is not -typedef struct +typedef struct medge32_s { -#ifdef SUPPORT_BSP2_FORMAT unsigned int v[2]; -#else +} medge32_t; + +typedef struct medge16_s +{ unsigned short v[2]; unsigned int cachededgeoffset; -#endif -} medge_t; +} medge16_t; typedef struct texture_s { @@ -105,7 +108,7 @@ typedef struct vec3_t mins, maxs; // terrain bounds (fill by user) - int reserved[32]; // just for future expansions or mod-makers + intptr_t reserved[32]; // just for future expansions or mod-makers } mfaceinfo_t; typedef struct @@ -127,14 +130,23 @@ typedef struct int flags; // sky or slime, no lightmap or 256 subdivision } mtexinfo_t; -typedef struct glpoly_s +// a1ba: changed size to avoid undefined behavior. Check your allocations if you take this header! +// For example: +// before: malloc( sizeof( glpoly_t ) + ( numverts - 4 ) * VERTEXSIZE * sizeof( float )) +// after (C): malloc( sizeof( glpoly_t ) + numverts * VERTEXSIZE * sizeof( float )) +// after (C++): malloc( sizeof( glpoly_t ) + ( numverts - 1 ) * VERTEXSIZE * sizeof( float )) +typedef struct glpoly2_s { - struct glpoly_s *next; - struct glpoly_s *chain; + struct glpoly2_s *next; + struct glpoly2_s *chain; int numverts; int flags; // for SURF_UNDERWATER - float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) -} glpoly_t; +#ifdef __cplusplus + float verts[1][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +#else + float verts[][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +#endif +} glpoly2_t; typedef struct mnode_s { @@ -147,13 +159,31 @@ typedef struct mnode_s // node specific mplane_t *plane; - struct mnode_s *children[2]; -#ifdef SUPPORT_BSP2_FORMAT - int firstsurface; - int numsurfaces; + +#if !XASH_64BIT + union + { + struct mnode_s *children_[2]; + struct + { + // the ordering is important + int child_0_leaf : 1; + int child_0_off : 23; + int firstsurface_1 : 8; + int child_1_leaf : 1; + int child_1_off : 23; + int numsurfaces_1 : 8; + }; + }; + unsigned short firstsurface_0; + unsigned short numsurfaces_0; #else - unsigned short firstsurface; - unsigned short numsurfaces; + // in 64-bit ABI this struct has 4 more bytes of padding, let's use it! + struct mnode_s *children_[2]; + unsigned short firstsurface_0; + unsigned short numsurfaces_0; + unsigned short firstsurface_1; + unsigned short numsurfaces_1; #endif } mnode_t; @@ -173,8 +203,8 @@ struct decal_s short entityIndex; // Entity this is attached to // Xash3D specific vec3_t position; // location of the decal center in world space. - glpoly_t *polys; // precomputed decal vertices - int reserved[4]; // just for future expansions or mod-makers + glpoly2_t *polys; // precomputed decal vertices + intptr_t reserved[4]; // just for future expansions or mod-makers }; typedef struct mleaf_s @@ -194,7 +224,6 @@ typedef struct mleaf_s int nummarksurfaces; int cluster; // helper to acess to uncompressed visdata byte ambient_sound_level[NUM_AMBIENTS]; - } mleaf_t; // surface extradata @@ -228,9 +257,20 @@ typedef struct mextrasurf_s unsigned short numverts; // world->vertexes[] int firstvertex; // fisrt look up in tr.tbn_vectors[], then acess to world->vertexes[] - int reserved[32]; // just for future expansions or mod-makers + intptr_t reserved[32]; // just for future expansions or mod-makers } mextrasurf_t; +#ifdef SUPPORT_HL25_EXTENDED_STRUCTS +// additional struct at the end of msurface_t for HL25 compatibility +typedef struct mdisplaylist_s +{ + unsigned int gl_displaylist; + int rendermode; + float scrolloffset; + int renderDetailTexture; +} mdisplaylist_t; +#endif + struct msurface_s { int visframe; // should be drawn when node is crossed @@ -246,7 +286,7 @@ struct msurface_s int light_s, light_t; // gl lightmap coordinates - glpoly_t *polys; // multiple if warped + glpoly2_t *polys; // multiple if warped struct msurface_s *texturechain; mtexinfo_t *texinfo; @@ -263,11 +303,19 @@ struct msurface_s color24 *samples; // note: this is the actual lightmap data for this surface decal_t *pdecals; + +#ifdef SUPPORT_HL25_EXTENDED_STRUCTS + mdisplaylist_t displaylist; +#endif }; typedef struct hull_s { - mclipnode_t *clipnodes; + union + { + mclipnode16_t *clipnodes16; + mclipnode32_t *clipnodes32; + }; mplane_t *planes; int firstclipnode; int lastclipnode; @@ -317,7 +365,12 @@ typedef struct model_s mvertex_t *vertexes; int numedges; - medge_t *edges; + union + { + medge16_t *edges16; + medge32_t *edges32; + }; + int numnodes; mnode_t *nodes; @@ -332,7 +385,11 @@ typedef struct model_s int *surfedges; int numclipnodes; - mclipnode_t *clipnodes; + union + { + mclipnode16_t *clipnodes16; + mclipnode32_t *clipnodes32; + }; int nummarksurfaces; msurface_t **marksurfaces; @@ -488,7 +545,8 @@ typedef struct int flags; float size; - int reserved[8]; // VBO offsets + const trivertex_t **pposeverts; // only valid during loading, used to build GL mesh + intptr_t reserved[7]; // VBO offsets int numposes; int poseverts; @@ -525,14 +583,68 @@ typedef struct #define ANIM_CYCLE 2 #define MOD_FRAMES 20 - - #define MAX_DEMOS 32 #define MAX_MOVIES 8 #define MAX_CDTRACKS 32 #define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client) -#define MAX_EFRAGS 8192 // Arcane Dimensions required #define MAX_REQUESTS 64 +STATIC_CHECK_SIZEOF( mnode_t, 52, 72 ); +STATIC_CHECK_SIZEOF( mextrasurf_t, 324, 496 ); +STATIC_CHECK_SIZEOF( decal_t, 60, 88 ); +STATIC_CHECK_SIZEOF( mfaceinfo_t, 176, 304 ); + +// model flags (stored in model_t->flags) +#define MODEL_QBSP2 BIT( 28 ) // uses 32-bit types + +// access functions +static inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mod ) +{ +#if !XASH_64BIT + if( unlikely( mod->flags & MODEL_QBSP2 )) // MODEL_QBSP2 + { + if( side == 0 ) + { + if( n->child_0_leaf ) + return (mnode_t *)(mod->leafs + n->child_0_off); + else + return (mnode_t *)(mod->nodes + n->child_0_off); + } + else + { + if( n->child_1_leaf ) + return (mnode_t *)(mod->leafs + n->child_1_off); + else + return (mnode_t *)(mod->nodes + n->child_1_off); + } + } + + return n->children_[side]; +#else + return n->children_[side]; +#endif +} + +static inline void node_children( mnode_t *children[2], const mnode_t *n, const model_t *mod ) +{ + children[0] = node_child( n, 0, mod ); + children[1] = node_child( n, 1, mod ); +} + +static inline int node_firstsurface( const mnode_t *n, const model_t *mod ) +{ + if( mod->flags & MODEL_QBSP2 ) + return n->firstsurface_0 + ( n->firstsurface_1 << 16 ); + else + return n->firstsurface_0; +} + +static inline int node_numsurfaces( const mnode_t *n, const model_t *mod ) +{ + if( mod->flags & MODEL_QBSP2 ) + return n->numsurfaces_0 + ( n->numsurfaces_1 << 16 ); + else + return n->numsurfaces_0; +} #endif//COM_MODEL_H diff --git a/common/const.h b/common/const.h index a7e3d08b..fe4469fa 100644 --- a/common/const.h +++ b/common/const.h @@ -717,6 +717,7 @@ enum kRenderFxExplode, // Scale up really big! kRenderFxGlowShell, // Glowing Shell kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) + kRenderFxLightMultiplier, }; typedef int func_t; diff --git a/common/cvardef.h b/common/cvardef.h index fb50c307..b2486151 100644 --- a/common/cvardef.h +++ b/common/cvardef.h @@ -1,49 +1,122 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -#ifndef CVARDEF_H -#define CVARDEF_H +/* +cvardef.h - quake cvar definition +Copyright (C) 1997-2001 Id Software, Inc. +Copyright (C) 2024 Alibek Omarov -#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc -#define FCVAR_USERINFO (1<<1) // changes the client's info string -#define FCVAR_SERVER (1<<2) // notifies players when changed -#define FCVAR_EXTDLL (1<<3) // defined by external DLL -#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll -#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value -#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. -#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). -#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log -#define FCVAR_NOEXTRAWHITEPACE (1<<9) // strip trailing/leading white space from this cvar -#define FCVAR_PRIVILEGED (1<<10) // only available in privileged mode -#define FCVAR_FILTERABLE (1<<11) // filtered in unprivileged mode if cl_filterstuffcmd is 1 +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. -// Xash3D extensions -#define FCVAR_GLCONFIG (1<<12) // write it into .cfg(see RefAPI) -#define FCVAR_CHANGED (1<<13) // set each time the cvar is changed -#define FCVAR_GAMEUIDLL (1<<14) // defined by the menu DLL -#define FCVAR_CHEAT (1<<15) // can not be changed if cheats are disabled +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// a1ba: let's reuse higher bits for flags extensions from now on -#define FCVAR_LATCH (1<<30) // notify client what this cvar will be applied only after server restart (but don't does more nothing) +See the GNU General Public License for more details. -typedef struct cvar_s +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#ifndef CVAR +#define CVAR + +#include STDINT_H +#include "xash3d_types.h" + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed +or displayed at the console or prog code as well as accessed directly +in C code. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +enum { - char *name; - char *string; - int flags; - float value; - struct cvar_s *next; -} cvar_t; + // GoldSrc compatibility flags + FCVAR_ARCHIVE = 1 << 0, // set to cause it to be saved to vars.rc + FCVAR_USERINFO = 1 << 1, // added to userinfo when changed + FCVAR_SERVER = 1 << 2, // added to serverinfo when changed, will notify clients by default + FCVAR_EXTDLL = 1 << 3, // defined by server.dll + FCVAR_CLIENTDLL = 1 << 4, // defined by client.dll + FCVAR_PROTECTED = 1 << 5, // private server cvar + FCVAR_SPONLY = 1 << 6, // can be set only in singleplayer + FCVAR_PRINTABLEONLY = 1 << 7, // only allows printable characters + FCVAR_UNLOGGED = 1 << 8, // disables notifying client about server cvar change + FCVAR_NOEXTRAWHITESPACE = 1 << 9, // removes space characters from the beginning and the end of the string + FCVAR_PRIVILEGED = 1 << 10, // only available in privileged mode + FCVAR_FILTERABLE = 1 << 11, // treated as privileged if cl_filterstuffcmd is 1, otherwise ignored -#endif//CVARDEF_H + // Xash3D public flags + // FCVAR_LATCH = 1 << 11, // deprecated Xash3D flag, conflicts with FCVAR_FILTERABLE. Use another FCVAR_LATCH! + FCVAR_GLCONFIG = 1 << 12, // set to cause it to be saved to .cfg (see RefAPI) + FCVAR_CHANGED = 1 << 13, // set each time the cvar changed + FCVAR_GAMEUIDLL = 1 << 14, // defined by menu.dll + FCVAR_CHEAT = 1 << 15, // cannot be changed if sv_cheats is 0 + +#if REF_DLL || ENGINE_DLL // Xash3D internal flags, MUST NOT be used outside of engine + FCVAR_RENDERINFO = 1 << 16, // set to cause it to be saved to video.cfg + FCVAR_READ_ONLY = 1 << 17, // display only, cannot be set by user at all + FCVAR_EXTENDED = 1 << 18, // extended cvar structure + FCVAR_ALLOCATED = 1 << 19, // allocated by the engine, must be freed with Mem_Free + FCVAR_VIDRESTART = 1 << 20, // triggers video subsystem to recreate/modify window parameters + FCVAR_TEMPORARY = 1 << 21, // only used to temporarly hold some value, can be unlinked + FCVAR_MOVEVARS = 1 << 22, // access to movevars_t structure, synchornized between client and server + FCVAR_USER_CREATED = 1 << 23, // created by a set command + + FCVAR_REFDLL = 1 << 29, // (Xash3D FWGS internal flag) defined by the renderer DLL +#endif // REF_DLL || ENGINE_DLL + + FCVAR_LATCH = 1 << 30, // (Xash3D FWGS public flag, was FCVAR_FILTERABLE in Xash3D) save changes until server restart +}; + +struct cvar_s { + char *name; + char *string; + uint32_t flags; + float value; + struct cvar_s *next; +}; +typedef struct cvar_s cvar_t; + +STATIC_CHECK_SIZEOF( struct cvar_s, 20, 32 ); + +#if REF_DLL || ENGINE_DLL // Xash3D internal cvar format, MUST NOT be used outside of engine +struct convar_s { + char *name; + char *string; + uint32_t flags; + float value; + struct convar_s *next; + char *desc; + char *def_string; +}; +typedef struct convar_s convar_t; + +#if XASH_64BIT +#define CVAR_SENTINEL (uintptr_t)0xDEADBEEFDEADBEEF +#else +#define CVAR_SENTINEL (uintptr_t)0xDEADBEEF +#endif + +#define CVAR_CHECK_SENTINEL( cv ) ((uintptr_t)(cv)->next == CVAR_SENTINEL) + +#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \ + convar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, (char*)cvdesc, NULL } + +#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) CVAR_DEFINE( cv, #cv, cvstr, cvflags, cvdesc ) +#endif // REF_DLL || ENGINE_DLL + +#endif // CVAR diff --git a/common/defaults.h b/common/defaults.h index a717f743..c2b06b95 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -47,29 +47,11 @@ SETUP BACKENDS DEFINITIONS #endif // XASH_TIMER #ifndef XASH_MESSAGEBOX - #define XASH_MESSAGEBOX MSGBOX_SDL + #if !XASH_NSWITCH // SDL2 messageboxes not available + #define XASH_MESSAGEBOX MSGBOX_SDL + #endif #endif // XASH_MESSAGEBOX #endif - #elif XASH_ANDROID - // we are building for Android platform, use Android APIs - #ifndef XASH_VIDEO - #define XASH_VIDEO VIDEO_ANDROID - #endif // XASH_VIDEO - - #ifndef XASH_INPUT - #define XASH_INPUT INPUT_ANDROID - #endif // XASH_INPUT - - #ifndef XASH_SOUND - #define XASH_SOUND SOUND_OPENSLES - #endif // XASH_SOUND - - #ifndef XASH_MESSAGEBOX - #define XASH_MESSAGEBOX MSGBOX_ANDROID - #endif // XASH_MESSAGEBOX - - #define XASH_USE_EVDEV - #define XASH_DYNAMIC_DLADDR #elif XASH_LINUX // we are building for Linux without SDL2, can draw only to framebuffer yet #ifndef XASH_VIDEO @@ -84,7 +66,7 @@ SETUP BACKENDS DEFINITIONS #define XASH_SOUND SOUND_ALSA #endif // XASH_SOUND - #define XASH_USE_EVDEV + #define XASH_USE_EVDEV 1 #elif XASH_DOS4GW #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_DOS @@ -96,7 +78,6 @@ SETUP BACKENDS DEFINITIONS // usually only 10-20 fds availiable #define XASH_REDUCE_FD #endif - #endif // XASH_DEDICATED // @@ -105,22 +86,13 @@ SETUP BACKENDS DEFINITIONS #ifndef XASH_MESSAGEBOX #if XASH_WIN32 #define XASH_MESSAGEBOX MSGBOX_WIN32 + #elif XASH_NSWITCH + #define XASH_MESSAGEBOX MSGBOX_NSWITCH #else // !XASH_WIN32 #define XASH_MESSAGEBOX MSGBOX_STDERR #endif // !XASH_WIN32 #endif // XASH_MESSAGEBOX -// -// select crashhandler based on defines -// -#ifndef XASH_CRASHHANDLER - #if XASH_WIN32 && defined(DBGHELP) - #define XASH_CRASHHANDLER CRASHHANDLER_DBGHELP - #elif XASH_LINUX || XASH_BSD - #define XASH_CRASHHANDLER CRASHHANDLER_UCONTEXT - #endif // !(XASH_LINUX || XASH_BSD || XASH_WIN32) -#endif - // // no timer - no xash // @@ -132,6 +104,17 @@ SETUP BACKENDS DEFINITIONS #endif // !XASH_WIN32 #endif +// +// determine movie playback backend +// +#ifndef XASH_AVI + #if HAVE_FFMPEG + #define XASH_AVI AVI_FFMPEG + #else + #define XASH_AVI AVI_NULL + #endif +#endif + #ifdef XASH_STATIC_LIBS #define XASH_LIB LIB_STATIC #define XASH_INTERNAL_GAMELIBS @@ -157,10 +140,6 @@ SETUP BACKENDS DEFINITIONS #define XASH_INPUT INPUT_NULL #endif // XASH_INPUT -#ifndef XASH_CRASHHANDLER - #define XASH_CRASHHANDLER CRASHHANDLER_NULL -#endif // XASH_CRASHHANDLER - /* ========================================================================= @@ -169,27 +148,59 @@ Default build-depended cvar and constant values ========================================================================= */ -#if XASH_MOBILE_PLATFORM +// Platform overrides +#if XASH_NSWITCH #define DEFAULT_TOUCH_ENABLE "1" - #define DEFAULT_M_IGNORE "1" -#else // !XASH_MOBILE_PLATFORM + #define DEFAULT_M_IGNORE "1" + #define DEFAULT_MODE_WIDTH 1280 + #define DEFAULT_MODE_HEIGHT 720 + #define DEFAULT_ALLOWCONSOLE 1 +#elif XASH_PSVITA + #define DEFAULT_TOUCH_ENABLE "1" + #define DEFAULT_M_IGNORE "1" + #define DEFAULT_MODE_WIDTH 960 + #define DEFAULT_MODE_HEIGHT 544 + #define DEFAULT_ALLOWCONSOLE 1 +#elif XASH_ANDROID + #define DEFAULT_TOUCH_ENABLE "1" +#elif XASH_MOBILE_PLATFORM + #define DEFAULT_TOUCH_ENABLE "1" + #define DEFAULT_M_IGNORE "1" +#endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH + +#if XASH_IOS + // this means that libraries are provided with engine, but not in game data + // You need add library loading code to library.c when adding new platform + #define XASH_INTERNAL_GAMELIBS +#endif // XASH_IOS + +// Defaults +#ifndef DEFAULT_TOUCH_ENABLE #define DEFAULT_TOUCH_ENABLE "0" +#endif // DEFAULT_TOUCH_ENABLE + +#ifndef DEFAULT_M_IGNORE #define DEFAULT_M_IGNORE "0" -#endif // !XASH_MOBILE_PLATFORM +#endif // DEFAULT_M_IGNORE -#if XASH_ANDROID || XASH_IOS || XASH_EMSCRIPTEN -#define XASH_INTERNAL_GAMELIBS -// this means that libraries are provided with engine, but not in game data -// You need add library loading code to library.c when adding new platform -#endif // XASH_ANDROID || XASH_IOS || XASH_EMSCRIPTEN +#ifndef DEFAULT_JOY_DEADZONE + #define DEFAULT_JOY_DEADZONE "4096" +#endif // DEFAULT_JOY_DEADZONE -// allow override for developer/debug builds #ifndef DEFAULT_DEV #define DEFAULT_DEV 0 #endif // DEFAULT_DEV +#ifndef DEFAULT_ALLOWCONSOLE + #define DEFAULT_ALLOWCONSOLE 0 +#endif // DEFAULT_ALLOWCONSOLE + #ifndef DEFAULT_FULLSCREEN - #define DEFAULT_FULLSCREEN 1 + #define DEFAULT_FULLSCREEN "1" // must be a string #endif // DEFAULT_FULLSCREEN +#ifndef DEFAULT_MAX_EDICTS + #define DEFAULT_MAX_EDICTS 1200 // was 900 before HL25 +#endif // DEFAULT_MAX_EDICTS + #endif // DEFAULTS_H diff --git a/common/enginefeatures.h b/common/enginefeatures.h index dc949dbf..3f71e7ec 100644 --- a/common/enginefeatures.h +++ b/common/enginefeatures.h @@ -1,5 +1,5 @@ /* -features.h - engine features that can be enabled by mod-maker request +enginefeatures.h - engine features that can be enabled by mod-maker request Copyright (C) 2012 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -27,4 +27,20 @@ GNU General Public License for more details. #define ENGINE_COMPUTE_STUDIO_LERP (1<<7) // enable MOVETYPE_STEP lerping back in engine #define ENGINE_LINEAR_GAMMA_SPACE (1<<8) // disable influence of gamma/brightness cvars to textures/lightmaps, for mods with custom renderer +#define ENGINE_DISABLE_HDTEXTURES (1U<<30) // disable support of HD-textures in case custom renderer have separate way to load them +#define ENGINE_STEP_POSHISTORY_LERP (1U<<31) // enable MOVETYPE_STEP interpolation based on position history. Incompatible with ENGINE_COMPUTE_STUDIO_LERP! + +// adjust the mask when features will be added or removed +#define ENGINE_FEATURES_MASK \ + ( ENGINE_WRITE_LARGE_COORD \ + | ENGINE_QUAKE_COMPATIBLE \ + | ENGINE_LOAD_DELUXEDATA \ + | ENGINE_PHYSICS_PUSHER_EXT \ + | ENGINE_LARGE_LIGHTMAPS \ + | ENGINE_COMPENSATE_QUAKE_BUG \ + | ENGINE_IMPROVED_LINETRACE \ + | ENGINE_COMPUTE_STUDIO_LERP \ + | ENGINE_LINEAR_GAMMA_SPACE \ + | ENGINE_STEP_POSHISTORY_LERP ) + #endif//FEATURES_H diff --git a/common/gameinfo.h b/common/gameinfo.h index 467185cc..3b593f24 100644 --- a/common/gameinfo.h +++ b/common/gameinfo.h @@ -19,6 +19,8 @@ GNU General Public License for more details. #define GFL_NOMODELS (1<<0) #define GFL_NOSKILLS (1<<1) #define GFL_RENDER_PICBUTTON_TEXT (1<<2) +#define GFL_HD_BACKGROUND (1<<3) +#define GFL_ANIMATED_TITLE (1<<4) /* ======================================================================== @@ -28,7 +30,7 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct +typedef struct GAMEINFO_s { // filesystem info char gamefolder[64]; // used for change game '-game x' @@ -48,4 +50,48 @@ typedef struct int gamemode; } GAMEINFO; +/* +======================================================================== + +Extended GameInfo struct introduced in Xash3D FWGS + +GAMEINFO can't be reliably extended, as nor engine, nor menu can't be +sure about struct size. By adding struct versioning, we can check the +presense for extra fields. +======================================================================== +*/ + +#define GAMEINFO_VERSION 2 + +typedef enum gametype_e +{ + GAME_NORMAL, + GAME_SINGLEPLAYER_ONLY, + GAME_MULTIPLAYER_ONLY, +} gametype_t; + +typedef struct gameinfo2_s +{ + int gi_version; // should be set to desired struct version, e.g. GAMEINFO_VERSION + + // filesystem info + char gamefolder[64]; // used for change game + char startmap[64]; // map to start singleplayer game from + char trainmap[64]; // map to start hazardous course from (if specified) + char demomap[64]; // map to start demo chapter from (if specified) + char title[64]; // game title + char iconpath[64]; // path to game icon + char version[16]; // game version (optional) + uint32_t flags; // gameinfo flags, extended to fit more flags + + // mod info + char game_url[256]; // link to a developer's site + char update_url[256]; // link to updates page + char type[64]; // single, toolkit, multiplayer, etc + char date[64]; // release date + uint64_t size; // size in bytes + + gametype_t gamemode; +} gameinfo2_t; + #endif//GAMEINFO_H diff --git a/common/kbutton.h b/common/kbutton.h index 504e2345..55015c3d 100644 --- a/common/kbutton.h +++ b/common/kbutton.h @@ -1,18 +1,35 @@ -//========= Copyright 1996-2002, Valve LLC, All rights reserved. ============ -// -// Purpose: -// -// $NoKeywords: $ -//============================================================================= +/* +Copyright (C) 1996-1997 Id Software, Inc. -#if !defined( KBUTTONH ) -#define KBUTTONH -#pragma once +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. -typedef struct kbutton_s +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef KBUTTON_H +#define KBUTTON_H + +// a1ba: copied from WinQuake/client.h +// +// cl_input +// +typedef struct { int down[2]; // key nums holding it down int state; // low bit is down state } kbutton_t; -#endif // !KBUTTONH +STATIC_CHECK_SIZEOF( kbutton_t, 12, 12 ); + +#endif // KBUTTON_H diff --git a/common/net_api.h b/common/net_api.h index 7b1c5bd2..db2c7c10 100644 --- a/common/net_api.h +++ b/common/net_api.h @@ -27,6 +27,7 @@ // Set this flag for things like broadcast requests, etc. where the engine should not // kill the request hook after receiving the first response #define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) +#define FNETAPI_LEGACY_PROTOCOL ( 1<<1 ) // xash3d-fwgs extension struct net_response_s; typedef void (*net_api_response_func_t) ( struct net_response_s *response ); @@ -35,6 +36,7 @@ typedef void (*net_api_response_func_t) ( struct net_response_s *response ); #define NET_ERROR_TIMEOUT ( 1<<0 ) #define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) #define NET_ERROR_UNDEFINED ( 1<<2 ) +#define NET_ERROR_FORBIDDEN ( 1<<3 ) // xash3d-fwgs extension typedef struct net_adrlist_s { diff --git a/common/netadr.h b/common/netadr.h index 4dc00011..0e6641c5 100644 --- a/common/netadr.h +++ b/common/netadr.h @@ -1,37 +1,115 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ +/* +Copyright (C) 1997-2001 Id Software, Inc. -#ifndef NETADR_H -#define NETADR_H +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. -typedef enum +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef NET_ADR_H +#define NET_ADR_H + +#include STDINT_H + +// net.h -- quake's interface to the networking layer +// a1ba: copied from Quake-2/qcommon/qcommon.h and modified to support IPv6 + +#define PORT_ANY -1 + +typedef enum netadrtype_e { - NA_UNUSED, + NA_UNDEFINED = 0, NA_LOOPBACK, NA_BROADCAST, NA_IP, NA_IPX, - NA_BROADCAST_IPX + NA_BROADCAST_IPX, + NA_IP6, + NA_MULTICAST_IP6 } netadrtype_t; -typedef struct netadr_s +/* +Original Quake-2 structure: +typedef struct { netadrtype_t type; - unsigned char ip[4]; - unsigned char ipx[10]; + + byte ip[4]; + byte ipx[10]; + unsigned short port; } netadr_t; +*/ -#endif//NETADR_H +#pragma pack( push, 1 ) +typedef struct netadr_s +{ +// the reason we do this evil thing, is that when this struct contains IPv6 +// address the `type` is 2-byte wide, but when it doesn't `type` must 4-byte +// wide _and_ ip6_0 must be zeroed, to keep it binary compatible. +#if XASH_LITTLE_ENDIAN + uint16_t type; + uint8_t ip6_0[2]; +#elif XASH_BIG_ENDIAN + uint8_t ip6_0[2]; + uint16_t type; +#else +#error +#endif + + union + { + // IPv6 struct + uint8_t ip6_1[14]; + struct + { + union + { + uint8_t ip[4]; + uint32_t ip4; // for easier conversions + }; + uint8_t ipx[10]; + }; + }; + uint16_t port; +} netadr_t; +#pragma pack( pop ) + +static inline netadrtype_t NET_NetadrType( const netadr_t *a ) +{ + if( a->type == NA_IP6 || a->type == NA_MULTICAST_IP6 ) + return (netadrtype_t)a->type; + + if( a->ip6_0[0] || a->ip6_0[1] ) + return NA_UNDEFINED; + + return (netadrtype_t)a->type; +} + +static inline void NET_NetadrSetType( netadr_t *a, netadrtype_t type ) +{ + if( type == NA_IP6 || type == NA_MULTICAST_IP6 ) + { + a->type = type; + return; + } + + a->ip6_0[0] = a->ip6_0[1] = 0; + a->type = type; +} + +STATIC_CHECK_SIZEOF( netadr_t, 20, 20 ); + +#endif // NET_ADR_H diff --git a/common/port.h b/common/port.h index ae60122f..8847eb0f 100644 --- a/common/port.h +++ b/common/port.h @@ -24,6 +24,9 @@ GNU General Public License for more details. #include #define OS_LIB_EXT "dylib" #define OPEN_COMMAND "open" + #elif XASH_EMSCRIPTEN + #define OS_LIB_EXT "wasm" + #define OPEN_COMMAND "???" #else #define OS_LIB_EXT "so" #define OPEN_COMMAND "xdg-open" @@ -35,20 +38,23 @@ GNU General Public License for more details. #define __cdecl #define __stdcall #define _inline static inline - #define FORCEINLINE inline __attribute__((always_inline)) #if XASH_POSIX #include - #include - - #define PATH_SPLITTER "/" - #define HAVE_DUP - - #define O_BINARY 0 - #define O_TEXT 0 + #if XASH_NSWITCH + #define SOLDER_LIBDL_COMPAT + #include + #elif XASH_PSVITA + #define VRTLD_LIBDL_COMPAT + #include + #define O_BINARY 0 + #else + #include + #define HAVE_DUP + #define O_BINARY 0 + #endif + #define O_TEXT 0 #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) - #elif XASH_DOS4GW - #define PATH_SPLITTER "\\" #endif typedef void* HANDLE; @@ -59,14 +65,6 @@ GNU General Public License for more details. int x, y; } POINT; #else // WIN32 - #define PATH_SPLITTER "\\" - #ifdef __MINGW32__ - #define _inline static inline - #define FORCEINLINE inline __attribute__((always_inline)) - #else - #define FORCEINLINE __forceinline - #endif - #define open _open #define read _read #define alloca _alloca @@ -91,8 +89,4 @@ GNU General Public License for more details. #include #include -#if defined XASH_SDL && !defined REF_DLL -#include -#endif - #endif // PORT_H diff --git a/common/r_efx.h b/common/r_efx.h index 366b7c6b..7fdca48a 100644 --- a/common/r_efx.h +++ b/common/r_efx.h @@ -56,6 +56,8 @@ color24 gTracerColors[] = }; */ +#define TRACER_COLORINDEX_DEFAULT 4 + // Temporary entity array #define TENTPRIORITY_LOW 0 #define TENTPRIORITY_HIGH 1 diff --git a/common/render_api.h b/common/render_api.h index 25897a85..0b7f672f 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -107,7 +107,8 @@ typedef enum TF_ARB_FLOAT = (1<<26), // float textures TF_NOCOMPARE = (1<<27), // disable comparing for depth textures TF_ARB_16BIT = (1<<28), // keep image as 16-bit (not 24) - TF_MULTISAMPLE = (1<<29) // multisampling texture + TF_MULTISAMPLE = (1<<29), // multisampling texture + TF_ALLOW_NEAREST = (1<<30), // allows toggling nearest filtering for TF_NOMIPMAP textures } texFlags_t; typedef enum @@ -156,6 +157,23 @@ typedef struct decallist_s modelstate_t studio_state; // studio decals only } decallist_t; +enum movie_parms_e +{ + AVI_PARM_LAST = 0, // marker for SetParm to end parse parsing arguments + AVI_RENDER_TEXNUM, // (int) sets texture to draw into, if 0 will draw to screen + AVI_RENDER_X, // (int) when set to screen, sets position where to draw + AVI_RENDER_Y, + AVI_RENDER_W, // (int) sets texture or screen width + AVI_RENDER_H, // set to -1 to draw full screen + AVI_REWIND, // no argument, rewind playback to the beginning + AVI_ENTNUM, // (int) entity number, -1 for no spatialization + AVI_VOLUME, // (int) volume from 0 to 255 + AVI_ATTN, // (float) attenuation value + AVI_PAUSE, // no argument, pauses playback + AVI_RESUME, // no argument, resumes playback +}; + +struct movie_state_s; struct ref_viewpass_s; typedef struct render_api_s @@ -192,16 +210,16 @@ typedef struct render_api_s void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) // AVIkit support - void *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); - int (*AVI_GetVideoInfo)( void *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int - int (*AVI_GetVideoFrameNumber)( void *Avi, float time ); - byte *(*AVI_GetVideoFrame)( void *Avi, int frame ); + struct movie_state_s *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); + qboolean (*AVI_GetVideoInfo)( struct movie_state_s *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int + int (*AVI_GetVideoFrameNumber)( struct movie_state_s *Avi, float time ); + byte *(*AVI_GetVideoFrame)( struct movie_state_s *Avi, int frame ); void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); - void (*AVI_FreeVideo)( void *Avi ); - int (*AVI_IsActive)( void *Avi ); - void (*AVI_StreamSound)( void *Avi, int entnum, float fvol, float attn, float synctime ); - void (*AVI_Reserved0)( void ); // for potential interface expansion without broken compatibility - void (*AVI_Reserved1)( void ); + void (*AVI_FreeVideo)( struct movie_state_s *Avi ); + qboolean (*AVI_IsActive)( struct movie_state_s *Avi ); + void (*AVI_StreamSound)( struct movie_state_s *Avi, int entnum, float fvol, float attn, float synctime ); + qboolean (*AVI_Think)( struct movie_state_s *Avi ); + qboolean (*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... ); // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) void (*GL_Bind)( int tmu, unsigned int texnum ); @@ -229,7 +247,7 @@ typedef struct render_api_s void (*R_Reserved0)( void ); // static allocations - void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 ); void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); // engine utils (not related with render API but placed here) diff --git a/common/synctype.h b/common/synctype.h new file mode 100644 index 00000000..fac3e6ca --- /dev/null +++ b/common/synctype.h @@ -0,0 +1,24 @@ +/* +synctype.h -- shared synctype_t definition +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2023 Alibek Omarov + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#ifndef SYNCTYPE_H +#define SYNCTYPE_H +typedef enum {ST_SYNC=0, ST_RAND } synctype_t; +#endif diff --git a/common/wadfile.h b/common/wadfile.h index 9dafd13d..d4d24575 100644 --- a/common/wadfile.h +++ b/common/wadfile.h @@ -35,6 +35,7 @@ infotable dlumpinfo_t[dwadinfo_t->numlumps] #define IDWAD2HEADER (('2'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD2" quake wads #define IDWAD3HEADER (('3'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD3" half-life wads +#define WAD3_NAMELEN 16 // dlumpinfo_t->attribs #define ATTR_NONE 0 // allow to read-write @@ -83,4 +84,37 @@ typedef struct mip_s unsigned int offsets[4]; // four mip maps stored } mip_t; +/* +======================================================================== + +.WAD header format (half-Life textures) + +======================================================================== +*/ +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +/* +======================================================================== + +.WAD struct (half-Life textures) + +======================================================================== +*/ +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + signed char type; // TYP_* + signed char attribs; // file attribs + signed char pad0; + signed char pad1; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + #endif//WADFILE_H diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 9b343e8c..a7bc8ac2 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -4,52 +4,53 @@ #include "build.h" +#if XASH_IRIX +#include +#endif + #if XASH_WIN32 #include // off_t #endif // _WIN32 #include // off_t +#ifdef STDINT_H #include STDINT_H +#else // !STDINT_H +#include +#endif // !STDINT_H #include -typedef unsigned char byte; -typedef int sound_t; -typedef float vec_t; -typedef vec_t vec2_t[2]; -typedef vec_t vec3_t[3]; -typedef vec_t vec4_t[4]; -typedef vec_t quat_t[4]; -typedef byte rgba_t[4]; // unsigned byte colorpack -typedef byte rgb_t[3]; // unsigned byte colorpack -typedef vec_t matrix3x4[3][4]; -typedef vec_t matrix4x4[4][4]; -typedef uint32_t poolhandle_t; +typedef uint8_t byte; +typedef float vec_t; +typedef vec_t vec2_t[2]; +#ifndef vec3_t // SDK renames it to Vector +typedef vec_t vec3_t[3]; +#endif +typedef vec_t vec4_t[4]; +typedef vec_t quat_t[4]; +typedef byte rgba_t[4]; // unsigned byte colorpack +typedef byte rgb_t[3]; // unsigned byte colorpack +typedef vec_t matrix3x4[3][4]; +typedef vec_t matrix4x4[4][4]; +typedef uint32_t poolhandle_t; #undef true #undef false -#ifndef __cplusplus -typedef enum { false, true } qboolean; -#else -typedef int qboolean; +// true and false are keywords in C++ and C23 +#if !__cplusplus && __STDC_VERSION__ < 202311L +enum { false, true }; #endif +typedef int qboolean; -typedef uint64_t longtime_t; - -#define MAX_STRING 256 // generic string -#define MAX_INFO_STRING 256 // infostrings are transmitted across network -#define MAX_SERVERINFO_STRING 512 // server handles too many settings. expand to 1024? -#define MAX_LOCALINFO_STRING 32768 // localinfo used on server and not sended to the clients -#define MAX_SYSPATH 1024 // system filepath -#define MAX_PRINT_MSG 8192 // how many symbols can handle single call of Con_Printf or Con_DPrintf -#define MAX_TOKEN 2048 // parse token length -#define MAX_MODS 512 // environment games that engine can keep visible -#define MAX_USERMSG_LENGTH 2048 // don't modify it's relies on a client-side definitions +#define MAX_STRING 256 // generic string +#define MAX_VA_STRING 1024 // compatibility macro +#define MAX_SYSPATH 1024 // system filepath +#define MAX_MODS 512 // environment games that engine can keep visible #define BIT( n ) ( 1U << ( n )) -#define GAMMA ( 2.2f ) // Valve Software gamma -#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 -#define TEXGAMMA ( 0.9f ) // compensate dim textures +#define BIT64( n ) ( 1ULL << ( n )) + #define SetBits( iBitVector, bits ) ((iBitVector) = (iBitVector) | (bits)) #define ClearBits( iBitVector, bits ) ((iBitVector) = (iBitVector) & ~(bits)) #define FBitSet( iBitVector, bit ) ((iBitVector) & (bit)) @@ -66,48 +67,117 @@ typedef uint64_t longtime_t; #define IsColorString( p ) ( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' ) #define ColorIndex( c ) ((( c ) - '0' ) & 7 ) -#if defined(__GNUC__) - #ifdef __i386__ - #define EXPORT __attribute__ ((visibility ("default"),force_align_arg_pointer)) - #define GAME_EXPORT __attribute((force_align_arg_pointer)) +#undef EXPORT + +#if defined( __GNUC__ ) + #if defined( __i386__ ) + #define EXPORT __attribute__(( visibility( "default" ), force_align_arg_pointer )) + #define GAME_EXPORT __attribute__(( force_align_arg_pointer )) #else - #define EXPORT __attribute__ ((visibility ("default"))) + #define EXPORT __attribute__(( visibility ( "default" ))) #define GAME_EXPORT #endif - #define _format(x) __attribute__((format(printf, x, x+1))) - #define NORETURN __attribute__((noreturn)) -#elif defined(_MSC_VER) - #define EXPORT __declspec( dllexport ) - #define GAME_EXPORT - #define _format(x) - #define NORETURN -#else - #define EXPORT - #define GAME_EXPORT - #define _format(x) - #define NORETURN -#endif -#if ( __GNUC__ >= 3 ) - #define unlikely(x) __builtin_expect(x, 0) - #define likely(x) __builtin_expect(x, 1) -#elif defined( __has_builtin ) - #if __has_builtin( __builtin_expect ) - #define unlikely(x) __builtin_expect(x, 0) - #define likely(x) __builtin_expect(x, 1) + #define MALLOC __attribute__(( malloc )) + + // added in GCC 11 + #if __GNUC__ >= 11 + // might want to set noclone due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116893 + // but it's easier to not force mismatched-dealloc to error yet + #define MALLOC_LIKE( x, y ) __attribute__(( malloc( x, y ))) #else - #define unlikely(x) (x) - #define likely(x) (x) + #define MALLOC_LIKE( x, y ) MALLOC #endif + #define NORETURN __attribute__(( noreturn )) + #define NONNULL __attribute__(( nonnull )) + #define RETURNS_NONNULL __attribute__(( returns_nonnull )) + #if __clang__ || __MCST__ + // clang has bugged returns_nonnull for functions pointers, it's ignored and generates a warning about objective-c? O_o + // lcc doesn't support it at all + #define PFN_RETURNS_NONNULL + #else + #define PFN_RETURNS_NONNULL RETURNS_NONNULL + #endif + #define FORMAT_CHECK( x ) __attribute__(( format( printf, x, x + 1 ))) + #define ALLOC_CHECK( x ) __attribute__(( alloc_size( x ))) + #define NO_ASAN __attribute__(( no_sanitize( "address" ))) + #define WARN_UNUSED_RESULT __attribute__(( warn_unused_result )) + #define RENAME_SYMBOL( x ) asm( x ) #else - #define unlikely(x) (x) - #define likely(x) (x) + #if defined( _MSC_VER ) + #define EXPORT __declspec( dllexport ) + #define NO_ASAN __declspec( no_sanitize_address ) + #else + #define EXPORT + #define NO_ASAN + #endif + #define GAME_EXPORT + #define NORETURN + #define NONNULL + #define RETURNS_NONNULL + #define PFN_RETURNS_NONNULL + #define FORMAT_CHECK( x ) + #define ALLOC_CHECK( x ) + #define RENAME_SYMBOL( x ) + #define MALLOC + #define MALLOC_LIKE( x, y ) + #define WARN_UNUSED_RESULT #endif -#if defined( static_assert ) // C11 static_assert -#define STATIC_ASSERT static_assert +#if defined( __has_feature ) + #if __has_feature( address_sanitizer ) + #define USE_ASAN 1 + #endif // __has_feature +#endif // defined( __has_feature ) + +#if !defined( USE_ASAN ) && defined( __SANITIZE_ADDRESS__ ) +#define USE_ASAN 1 +#endif + +#if __GNUC__ >= 3 + #define unlikely( x ) __builtin_expect( x, 0 ) + #define likely( x ) __builtin_expect( x, 1 ) +#elif defined( __has_builtin ) + #if __has_builtin( __builtin_expect ) // this must be after defined() check + #define unlikely( x ) __builtin_expect( x, 0 ) + #define likely( x ) __builtin_expect( x, 1 ) + #endif +#endif + +#if !defined( unlikely ) || !defined( likely ) + #define unlikely( x ) ( x ) + #define likely( x ) ( x ) +#endif + +#if __STDC_VERSION__ >= 202311L || __cplusplus >= 201103L // C23 or C++ static_assert is a keyword + #define STATIC_ASSERT_( ignore, x, y ) static_assert( x, y ) + #define STATIC_ASSERT static_assert +#elif __STDC_VERSION__ >= 201112L // in C11 it's _Static_assert + #define STATIC_ASSERT_( ignore, x, y ) _Static_assert( x, y ) + #define STATIC_ASSERT _Static_assert #else -#define STATIC_ASSERT( x, y ) extern int _static_assert_##__LINE__[( x ) ? 1 : -1] + #define STATIC_ASSERT_( id, x, y ) extern int id[( x ) ? 1 : -1] + // need these to correctly expand the line macro + #define STATIC_ASSERT_3( line, x, y ) STATIC_ASSERT_( static_assert_ ## line, x, y ) + #define STATIC_ASSERT_2( line, x, y ) STATIC_ASSERT_3( line, x, y ) + #define STATIC_ASSERT( x, y ) STATIC_ASSERT_2( __LINE__, x, y ) +#endif + +// at least, statically check size of some public structures +#if XASH_64BIT +#define STATIC_CHECK_SIZEOF( type, size32, size64 ) \ + STATIC_ASSERT( sizeof( type ) == size64, #type " unexpected size" ) +#else +#define STATIC_CHECK_SIZEOF( type, size32, size64 ) \ + STATIC_ASSERT( sizeof( type ) == size32, #type " unexpected size" ) +#endif + +#if !defined( __cplusplus ) && __STDC_VERSION__ >= 199101L // not C++ and C99 or newer + #define XASH_RESTRICT restrict +#elif _MSC_VER || __GNUC__ || __clang__ // compiler-specific extensions + #define XASH_RESTRICT __restrict +#else + #define XASH_RESTRICT // nothing #endif #ifdef XASH_BIG_ENDIAN @@ -140,33 +210,17 @@ _inline float LittleFloat( float f ) #endif -typedef unsigned int dword; -typedef unsigned int uint; -typedef char string[MAX_STRING]; -typedef struct file_s file_t; // normal file -typedef struct stream_s stream_t; // sound stream for background music playing -typedef off_t fs_offset_t; +typedef unsigned int dword; +typedef unsigned int uint; +typedef char string[MAX_STRING]; +typedef off_t fs_offset_t; #if XASH_WIN32 -typedef int fs_size_t; // return type of _read, _write funcs +typedef int fs_size_t; // return type of _read, _write funcs #else /* !XASH_WIN32 */ -typedef ssize_t fs_size_t; +typedef ssize_t fs_size_t; #endif /* !XASH_WIN32 */ -typedef struct dllfunc_s -{ - const char *name; - void **func; -} dllfunc_t; - -typedef struct dll_info_s -{ - const char *name; // name of library - const dllfunc_t *fcts; // list of dll exports - qboolean crash; // crash if dll not found - void *link; // hinstance of loading library -} dll_info_t; - -typedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs ); +typedef void *(*pfnCreateInterface_t)( const char *, int * ); // config strings are a general means of communication from // the server to all connected clients. diff --git a/contrib/mittorn/Makefile.linux b/contrib/mittorn/Makefile.linux deleted file mode 100644 index 6dc654f1..00000000 --- a/contrib/mittorn/Makefile.linux +++ /dev/null @@ -1,206 +0,0 @@ -############################################# -# Makefile.linux - linux makefile -# Copyright (C) 2017 mittorn - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -############################################## - -# Default options - optimized to debug on local machine -CC ?= gcc -CXX ?= g++ -CFLAGS ?= -g -O1 -fsigned-char -Wall -Wextra -Wsign-compare -Wno-unknown-pragmas -Wno-missing-field-initializers -Wno-unused-parameter -Wno-unused-but-set-variable -LDFLAGS = -LBITS := $(shell getconf LONG_BIT) -DEPS := - -XASH_COMMIT := $(firstword $(shell git rev-parse --short=6 HEAD) unknown) - -ifeq ($(XASH_COMMIT),unknown) -$(warning You seems to build xash3d without git) -$(warning Please use git if you are going to publish your build) -endif - -# Pass 64BIT=1 to make arguments to allow amd64 builds -ifneq ($(64BIT),1) - ifeq ($(LBITS),64) - LDFLAGS += -m32 - CFLAGS += -m32 - endif -endif - -TOPDIR = $(PWD)/.. -INCLUDES := -XASH_SINGLE_BINARY ?= 1 -INSTALL_DIR ?= ./install/ -ifeq ($(NANOGL),1) -INCLUDES += -Inanogl -Inanogl/GL -endif -INCLUDES += -I/usr/include/SDL2 -Icommon -I../common -I. -I../pm_shared -Iclient -Iserver -Iclient/vgui -Icommon/sdl - -############################# -# Preprocessor defines: -############################# -DEFINES = - -# Specify commit hash in version string -DEFINES += -DXASH_BUILD_COMMIT=\"$(XASH_COMMIT)\" - - -# Only SDL backend exists on linux -ifeq ($(XASH_DEDICATED),1) - DEFINES += -DXASH_DEDICATED -else - DEFINES += -DXASH_SDL - LIBS += -lSDL2 -endif - -############################################# -# GL/GLES translators. -# You need clone it to engine folder to use -# Only one translator should be enabled -############################################# -ifeq ($(NANOGL),1) -DEFINES += -DXASH_NANOGL -D__MULTITEXTURE_SUPPORT__ -DEGL_LIB=\"libEGL.so\" -endif - -ifeq ($(WES),1) -DEFINES += -DXASH_WES -D__MULTITEXTURE_SUPPORT__ -DEGL_LIB=\"libEGL.so\" -endif - -ifeq ($(REGAL),1) -DEFINES += -DXASH_REGAL -D__MULTITEXTURE_SUPPORT__ -DEGL_LIB=\"regal/lib/linux-32/libRegal.so\" -LIBS += regal/lib/linux-32/libRegal.so -endif - -# Some libc implementations cannot use libdl in static builds, so disable it by default -ifeq ($(XASH_STATIC),1) - - ifneq ($(XASH_STATIC_LIBDL),1) - DEFINES += -DNO_LIBDL - endif - - XASH_SINGLE_BINARY := 1 -endif - -ifneq ($(XASH_STATIC),1) - LIBS += -ldl -endif - -ifeq ($(XASH_STATIC_LIBDL),1) - LIBS += -ldl -endif - -LIBS += -lm -pthread - - -ifeq ($(XASH_SINGLE_BINARY),1) - DEFINES += -DSINGLE_BINARY -endif - -# Collect files -SRCS_CPP = -SRCS = $(wildcard server/*.c) $(wildcard client/vgui/*.c) $(wildcard client/avi/*.c) $(wildcard common/*.c) $(wildcard common/imagelib/*.c) $(wildcard common/soundlib/*.c) $(wildcard common/soundlib/libmpg/*.c) -ifneq ($(XASH_DEDICATED),1) - SRCS += $(wildcard client/*.c) - SRCS += $(wildcard platform/sdl/*.c) - ifeq ($(WES),1) - SRCS += $(wildcard gl-wes-v2/src/*.c) - endif - ifeq ($(NANOGL),1) - SRCS_CPP += $(wildcard nanogl/*.cpp) - endif -endif -OBJS = $(patsubst %.c,obj/%.o,$(SRCS)) -OBJS_CPP = $(patsubst %.cpp,obj/%.o,$(SRCS_CPP)) - -################################# -# Windows DLL loader -# Should be enabled only on i386 builds -################################# -LOADER := -ifeq ($(XASH_DLL_LOADER),1) - DEFINES += -DDLL_LOADER - ifeq ($(XASH_SINGLE_BINARY),1) - LOADER = libloader.a - else - LOADER = libloader.so - endif - DEPS += $(LOADER) - LIBS += $(LOADER) -endif - -# Rules for binaries -ifeq ($(XASH_SINGLE_BINARY),0) - BINARIES = libxash.so -libxash.so : $(OBJS) $(OBJS_CPP) $(DEPS) - $(CC) -fvisibility=hidden -o libxash.so $(LDFLAGS) -shared $(OBJS) $(OBJS_CPP) $(LIBS) - -else -BINARIES = xash -xash: $(OBJS) $(OBJS_CPP) $(DEPS) -ifeq ($(XASH_STATIC),1) - $(CC) -o xash -static $(LDFLAGS) $(OBJS) $(OBJS_CPP) $(LIBS) -else - $(CC) -o xash -fvisibility=hidden $(LDFLAGS) $(OBJS) $(OBJS_CPP) $(LIBS) -endif - -endif - -ifeq ($(XASH_DLL_LOADER),1) -$(LOADER): - $(MAKE) -f ../loader/Makefile.linux -C ../loader $(LOADER) - cp ../loader/$(LOADER) . -endif - -# Create dirs for object files -DIRS := obj obj/server obj/client/avi obj/client/vgui obj/common/sdl obj/common/imagelib obj/common/soundlib/libmpg obj/platform/sdl obj/nanogl -$(OBJS): | $(DIRS) - -$(DIRS) : - mkdir -p $(DIRS) - -# Object rules -obj/%.o : %.c - $(CC) $(CFLAGS) $(INCLUDES) $(DEFINES) -c "$<" -o "$@" -Wno-unused-result -fvisibility=hidden - -obj/%.o : %.cpp - $(CXX) $(CFLAGS) $(INCLUDES) $(DEFINES) -c "$<" -o "$@" - - -default: $(BINARIES) - -.PHONY: depend clean list install - -clean: - $(RM) $(OBJS) $(OBJS_CPP) $(BINARIES) ../loader/*.o ../loader/*.a ../loader/*.so $(LOADER) - -list: - @echo Sources: - @echo $(SRCS) - @echo C++ Sources: - @echo $(SRCS_CPP) - @echo Objects: - @echo $(OBJS) $(OBJS_CPP) - @echo Dirs: - @echo $(DIRS) - @echo Dependencies: - @echo $(DEPS) - -# Simple install rule -install: $(BINARIES) - mkdir -p $(INSTALL_DIR) - -ifeq ($(XASH_SINGLE_BINARY),1) - cp xash $(INSTALL_DIR)/xash_bin -else - cp libxash.so $(INSTALL_DIR)/ - cp libloader.so $(INSTALL_DIR)/ -endif diff --git a/contrib/mittorn/README.md b/contrib/mittorn/README.md deleted file mode 100644 index 289e3b11..00000000 --- a/contrib/mittorn/README.md +++ /dev/null @@ -1,269 +0,0 @@ -**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)* - -- [Building](#building) - - [Manual build (without CMake)](#manual-build-without-cmake) - - [Building Engine](#building-engine) - - [Building launch binary](#building-launch-binary) - - [Building on Windows](#building-on-windows) - - [Visual Studio 2013 (recommended for Windows)](#visual-studio-2013-recommended-for-windows) - - [Visual Studio 6](#visual-studio-6) - - [MinGW](#mingw) - - [Building Game mods](#building-game-mods) - - [Linux](#linux) - - [microndk](#microndk) - - [Makefile.linux](#makefile-linux) - - [Windows](#windows) -- [Running](#running) - - [Manual running](#manual-running) - - [Running with GDB](#running-with-gdb) - - [Using DLL Loader](#using-dll-loader) - - [Running MinGW builds](#running-mingw-builds) - - [Running MinGW builds under GDB](#running-mingw-builds-under-gdb) - -# Building - -## Manual build (without CMake) - -### Building engine - -Clone Xash3D repository using git: - - git clone https://github.com/FWGS/xash3d - -Move to the Xash3D folder: - - cd xash3d/engine - - make -f Makefile.linux XASH_VGUI=1 - -or same for dedicated - - make -f Makefile.linux XASH_DEDICATED=1 - -To enable dll support on linux, build loader library: - - cd ../loader - make -f Makefile.linux libloader.so libloader.a - cp libloader.so libloader.a ../engine/ - -And built engine with XASH_DLL_LOADER=1: - - cd ../engine - make -f Makefile.linux XASH_VGUI=1 XASH_SDL=1 XASH_DLL_LOADER=1 - -or same for dedicated server - - cd ../engine - make -f Makefile.linux XASH_DEDICATED=1 XASH_DLL_LOADER=1 - -### Building engine in separate library - -Some old distros does not support hidden symbol visibility -This results in crash when loading server library - -Building launch binary - - cd (xash3d)/game_launch - gcc xash.c -o xash -ldl -lrt -lm - -Building engine library: - - make -f Makefile.linux XASH_SINGLE_BINARY=0 - -## Building on Windows - -### Visual Studio 2013 (recommended for Windows) - -Download latest prebuilt SDL2 from - -https://www.libsdl.org/release/SDL2-devel-2.0.4-VC.zip - -Unzip and rename `SDL2-2.0.4` folder to `SDL2` and put it next to xash3d project folder. - - ..\xash3d\ - ..\SDL2\ - -Open `xash.sln` with Visual Studio 2013 and make a build. After building, copy contents of `Debug` or -`Release` folder to directory you choose. Copy `valve` folder and `vgui.dll` from your Half Life game -installation folder and `SDL2.dll` form `\SDL2\lib\x86\` to it. -Move `vgui_support.dll` into `valve` folder. - - ..\valve\ - ..\valve\vgui_support.dll - ..\menu.dll - ..\SDL2.dll - ..\vgui.dll - ..\xash.dll - ..\xash.exe - -Now you good to go, just run `xash.exe`. - -### Visual Studio 6 - -This is legacy configuration, but msvc6 seems to generate more stable and more effective code - -Setup your msvc6 enviroment, unpack SDL2-2.0.4 to xash3d folder and do: - - cd (xash3d)\engine - ..\msvc6\build.bat - cd (xash3d)\game_launch - ..\msvc6\build_launch.bat - -### MinGW - -The most effective configuration, but maybe unstable. -Setup your MinGW environment and run: - - cd (xash3d)\engine\ - CC=gcc mingw32-make -f Makefile.mingw - - engine will be built to single exe binary - -## Building game mods - -### Linux - -#### microndk - -All mods that ported to android may be build to linux using Android.mk with microndk: - -[https://github.com/SDLash3D/microndk] - -Clone microndk repo somewhere, change xash3d_config to preffered configuration (change arch to x86 -for example) - -Go to dlls folder if you are building server or cl_dlls if you are building client and do: - - make -f /path/to/microndk/microndk.mk -j4 - -Do: - - make -f /path/to/microndk/microndk.mk -j4 clean - -every time when you build client after building server - -#### Makefile.linux - -### Windows - -On windows common way is using Visual Studio as many mods does not correctly work with mingw. - -Just open project and build it. - -Other is using mingw and microndk, but it was not tested yet. - -hlsdk-xash3d based mods may work fine with mingw. - -You may use microndk to build it. Build process is very similar to linux one. - -After setting up MinGW enviroment, do:
-`mingw32-make -f \path\to\microndk\Microndk.mk` - -to build 64-bit library, use:
-`mingw32-make -f \path\to\microndk\Microndk.mk 64BIT=1` - -Edit xash3d_config.mk to set optimal CFLAGS if you are running server - -# Running - -Copy **valve** folder from Half-Life: - - cp -r $HOME/.steam/steam/steamapps/common/Half-Life/valve $HOME/Games/Xash3D - -**NOTE**: If you build with CMake, you can now use `make install`. It will install binaries where -they must be located. So just run `xash3d` from command line in directory where is gamedata is located. -For additional info look to the CMakeLists.txt in repo root and xash3d.sh script. - -After a successful build, copy the next files to some other directory where you want to run Xash3D: - - cp engine/libxash.so game_launch/xash3d mainui/libxashmenu.so $HOME/Games/Xash3D - -If you built it with XASH_VGUI, also copy vgui.so: - - cp ../hlsdk/linux/vgui.so vgui_support/libvgui_support.so $HOME/Games/Xash3D - -Copy script: - - cp ../xash3d.sh $HOME/Games/Xash3D - -Run: - - cd $HOME/Games/Xash3D - ./xash3d.sh - -## Manual running - -Put xash3d binaries and vgui(optionally) to you game data directory and run: - - LD_LIBRARY_PATH=. ./xash -dev 5 - -## Running under GDB - - LD_LIBRARY_PATH=. gdb --args ./xash -dev 5 - -## Using DLL Loader - -Put vgui_support.dll from windows build to your game data folder and run: - - LD_LIBRARY_PATH=. ./xash -dev 5 -vguilib vgui.dll -clientlib valve/cl_dlls/client.dll -dll dlls/hl.dll - -## Running MinGW builds - -Put exe file to your game data directory - - cd (game)\ - xash_bin -dev 5 - -### Running MinGW builds under GDB - - gdb --args ./xash_bin.exe -dev 5 - -# Useful GDB commands - -Start or restart process: - - (gdb) r - -Show backtrace: - - (gdb) bt - -Full backtrace: - - (gdb) bt full - -Continue execution: - - (gdb) c - -Recover from segmentation fault: - -Skipping function: - - (gdb) handle SIGSEGV nopass - (gdb) ret - (gdb) c - -Restart frame for beginning: - - (gdb) handle SIGSEGV nopass - (gdb) jump *Host_AbortCurrentFrame -Anti-Crash script (useful for scripting servers): -``` -cd /path/to/rootdir -file xash -set args xash -dev 5 -nowcon -handle SIGSEGV nopass -catch signal SIGSEGV -set $crashes=0 -commands -print $crashes++ -if $crashes > 500 -set $crashes=0 -run -else -jump *Host_AbortCurrentFrame -end -end -run -``` diff --git a/contrib/mittorn/setup.sh b/contrib/mittorn/setup.sh deleted file mode 100755 index 828976b6..00000000 --- a/contrib/mittorn/setup.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -SCRIPTDIR=$(dirname "$0") -TOPDIR=$SCRIPTDIR/../../ -cp $SCRIPTDIR/Makefile.dllloader $TOPDIR/loader/ -cp $SCRIPTDIR/Makefile.emscripten $TOPDIR/engine/ -cp $SCRIPTDIR/Makefile.linux $TOPDIR/engine/ -cp $SCRIPTDIR/wscript $TOPDIR/engine/ \ No newline at end of file diff --git a/contrib/mittorn/wscript b/contrib/mittorn/wscript deleted file mode 100644 index 882b79a8..00000000 --- a/contrib/mittorn/wscript +++ /dev/null @@ -1,73 +0,0 @@ -#! /usr/bin/env python -# encoding: utf-8 -# mittorn, 2018 - -VERSION='0.99' -APPNAME='xash3d-fwgs' - -top = '.' - -def options(opt): - opt.load('compiler_c') -# opt.load('msvs') - opt.add_option('--dedicated', action = 'store_true', help = 'build dedicated server only' ) - opt.add_option('--64bits', dest = 'amd64', action = 'store_true', help = 'build 64-bit engine on x86_64' ) - - - -def configure(conf): - conf.load('compiler_c') - conf.env.append_unique('CFLAGS', '-O2') - conf.env.append_unique('DEFINES', 'SINGLE_BINARY') - conf.env.append_value('LINKFLAGS', '-ldl') - conf.env.append_value('LINKFLAGS', '-lm') - conf.env.append_value('LINKFLAGS', '-pthread') - - # check for 64-bit builds - # TODO: check 32-bit toolchain and dependencies - if conf.env.DEST_CPU == 'x86_64' and not conf.options.amd64: - conf.env.append_value('LINKFLAGS', '-m32') - conf.env.append_value('CFLAGS', '-m32') - print('NOTE: will build engine with 64-bit toolchain using -m32') - else: - print('Warning: 64bit engine may be unstable') - - # check for dedicated server build - if conf.options.dedicated: - conf.env.append_unique('DEFINES', 'XASH_DEDICATED') - conf.env.dedicated = True - else: - # TODO: add way to specify SDL2 path, move to separate function - try: - conf.check_cfg(path='sdl2-config', args='--cflags --libs', package='', msg='Checking for SDL2', uselib_store='SDL2') - except conf.errors.ConfigurationError: - conf.fatal('SDL2 not availiable! If you want to build dedicated server, specify --dedicated') - conf.env.append_unique('DEFINES', 'XASH_SDL') - -def build(bld): - # basic build: dedicated only, no dependencies - use = []; - source = bld.path.ant_glob([ - 'common/*.c', - 'common/imagelib/*.c', - 'common/soundlib/*.c', - 'common/soundlib/libmpg/*.c', - 'server/*.c']) - - # add client files and sdl2 library - if not bld.env.dedicated: - use += ['SDL2'] - source += bld.path.ant_glob([ - 'client/*.c', - 'client/vgui/*.c', - 'client/avi/*.c', - 'platform/sdl/*.c']) - - - bld( - target = 'xash', - features = 'c cprogram', - includes = ['../common', '../pm_shared', 'common', 'server', 'client', 'client/vgui', '.' ], - source = source, - use = use - ) diff --git a/engine/alias.h b/engine/alias.h index fda0ba0b..d9f22b67 100644 --- a/engine/alias.h +++ b/engine/alias.h @@ -16,6 +16,10 @@ #ifndef ALIAS_H #define ALIAS_H +#include "build.h" +#include STDINT_H +#include "synctype.h" + /* ============================================================================== @@ -39,16 +43,6 @@ Alias models are position independent, so the cache manager can move them. #define ALIAS_TRACER2 0x0040 // orange split trail + rotate #define ALIAS_TRACER3 0x0080 // purple trail -// must match definition in sprite.h -#ifndef SYNCTYPE_T -#define SYNCTYPE_T -typedef enum -{ - ST_SYNC = 0, - ST_RAND -} synctype_t; -#endif - typedef enum { ALIAS_SINGLE = 0, @@ -63,36 +57,42 @@ typedef enum typedef struct { - int ident; - int version; + int32_t ident; + int32_t version; vec3_t scale; vec3_t scale_origin; float boundingradius; vec3_t eyeposition; - int numskins; - int skinwidth; - int skinheight; - int numverts; - int numtris; - int numframes; - synctype_t synctype; - int flags; + int32_t numskins; + int32_t skinwidth; + int32_t skinheight; + int32_t numverts; + int32_t numtris; + int32_t numframes; + uint32_t synctype; // was synctype_t + int32_t flags; float size; } daliashdr_t; +STATIC_CHECK_SIZEOF( daliashdr_t, 84, 84 ); + typedef struct { - int onseam; - int s; - int t; + int32_t onseam; + int32_t s; + int32_t t; } stvert_t; +STATIC_CHECK_SIZEOF( stvert_t, 12, 12 ); + typedef struct dtriangle_s { - int facesfront; - int vertindex[3]; + int32_t facesfront; + int32_t vertindex[3]; } dtriangle_t; +STATIC_CHECK_SIZEOF( dtriangle_t, 16, 16 ); + #define DT_FACES_FRONT 0x0010 #define ALIAS_ONSEAM 0x0020 @@ -103,36 +103,50 @@ typedef struct char name[16]; // frame name from grabbing } daliasframe_t; +STATIC_CHECK_SIZEOF( daliasframe_t, 24, 24 ); + typedef struct { - int numframes; + int32_t numframes; trivertex_t bboxmin; // lightnormal isn't used trivertex_t bboxmax; // lightnormal isn't used } daliasgroup_t; +STATIC_CHECK_SIZEOF( daliasgroup_t, 12, 12 ); + typedef struct { - int numskins; + int32_t numskins; } daliasskingroup_t; +STATIC_CHECK_SIZEOF( daliasskingroup_t, 4, 4 ); + typedef struct { float interval; } daliasinterval_t; +STATIC_CHECK_SIZEOF( daliasinterval_t, 4, 4 ); + typedef struct { float interval; } daliasskininterval_t; +STATIC_CHECK_SIZEOF( daliasskininterval_t, 4, 4 ); + typedef struct { - aliasframetype_t type; + uint32_t type; // was aliasframetype_t } daliasframetype_t; +STATIC_CHECK_SIZEOF( daliasframetype_t, 4, 4 ); + typedef struct { - aliasskintype_t type; + uint32_t type; // was aliasskintype_t } daliasskintype_t; +STATIC_CHECK_SIZEOF( daliasskintype_t, 4, 4 ); + #endif//ALIAS_H diff --git a/engine/anorms.h b/engine/anorms.h index 6efb35ba..11a9007e 100644 --- a/engine/anorms.h +++ b/engine/anorms.h @@ -1,177 +1,181 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ +/* +Copyright (C) 1996-1997 Id Software, Inc. -{-0.525731, 0.000000, 0.850651}, -{-0.442863, 0.238856, 0.864188}, -{-0.295242, 0.000000, 0.955423}, -{-0.309017, 0.500000, 0.809017}, -{-0.162460, 0.262866, 0.951056}, -{0.000000, 0.000000, 1.000000}, -{0.000000, 0.850651, 0.525731}, -{-0.147621, 0.716567, 0.681718}, -{0.147621, 0.716567, 0.681718}, -{0.000000, 0.525731, 0.850651}, -{0.309017, 0.500000, 0.809017}, -{0.525731, 0.000000, 0.850651}, -{0.295242, 0.000000, 0.955423}, -{0.442863, 0.238856, 0.864188}, -{0.162460, 0.262866, 0.951056}, -{-0.681718, 0.147621, 0.716567}, -{-0.809017, 0.309017, 0.500000}, -{-0.587785, 0.425325, 0.688191}, -{-0.850651, 0.525731, 0.000000}, -{-0.864188, 0.442863, 0.238856}, -{-0.716567, 0.681718, 0.147621}, -{-0.688191, 0.587785, 0.425325}, -{-0.500000, 0.809017, 0.309017}, -{-0.238856, 0.864188, 0.442863}, -{-0.425325, 0.688191, 0.587785}, -{-0.716567, 0.681718, -0.147621}, -{-0.500000, 0.809017, -0.309017}, -{-0.525731, 0.850651, 0.000000}, -{0.000000, 0.850651, -0.525731}, -{-0.238856, 0.864188, -0.442863}, -{0.000000, 0.955423, -0.295242}, -{-0.262866, 0.951056, -0.162460}, -{0.000000, 1.000000, 0.000000}, -{0.000000, 0.955423, 0.295242}, -{-0.262866, 0.951056, 0.162460}, -{0.238856, 0.864188, 0.442863}, -{0.262866, 0.951056, 0.162460}, -{0.500000, 0.809017, 0.309017}, -{0.238856, 0.864188, -0.442863}, -{0.262866, 0.951056, -0.162460}, -{0.500000, 0.809017, -0.309017}, -{0.850651, 0.525731, 0.000000}, -{0.716567, 0.681718, 0.147621}, -{0.716567, 0.681718, -0.147621}, -{0.525731, 0.850651, 0.000000}, -{0.425325, 0.688191, 0.587785}, -{0.864188, 0.442863, 0.238856}, -{0.688191, 0.587785, 0.425325}, -{0.809017, 0.309017, 0.500000}, -{0.681718, 0.147621, 0.716567}, -{0.587785, 0.425325, 0.688191}, -{0.955423, 0.295242, 0.000000}, -{1.000000, 0.000000, 0.000000}, -{0.951056, 0.162460, 0.262866}, -{0.850651, -0.525731, 0.000000}, -{0.955423, -0.295242, 0.000000}, -{0.864188, -0.442863, 0.238856}, -{0.951056, -0.162460, 0.262866}, -{0.809017, -0.309017, 0.500000}, -{0.681718, -0.147621, 0.716567}, -{0.850651, 0.000000, 0.525731}, -{0.864188, 0.442863, -0.238856}, -{0.809017, 0.309017, -0.500000}, -{0.951056, 0.162460, -0.262866}, -{0.525731, 0.000000, -0.850651}, -{0.681718, 0.147621, -0.716567}, -{0.681718, -0.147621, -0.716567}, -{0.850651, 0.000000, -0.525731}, -{0.809017, -0.309017, -0.500000}, -{0.864188, -0.442863, -0.238856}, -{0.951056, -0.162460, -0.262866}, -{0.147621, 0.716567, -0.681718}, -{0.309017, 0.500000, -0.809017}, -{0.425325, 0.688191, -0.587785}, -{0.442863, 0.238856, -0.864188}, -{0.587785, 0.425325, -0.688191}, -{0.688191, 0.587785, -0.425325}, -{-0.147621, 0.716567, -0.681718}, -{-0.309017, 0.500000, -0.809017}, -{0.000000, 0.525731, -0.850651}, -{-0.525731, 0.000000, -0.850651}, -{-0.442863, 0.238856, -0.864188}, -{-0.295242, 0.000000, -0.955423}, -{-0.162460, 0.262866, -0.951056}, -{0.000000, 0.000000, -1.000000}, -{0.295242, 0.000000, -0.955423}, -{0.162460, 0.262866, -0.951056}, -{-0.442863, -0.238856, -0.864188}, -{-0.309017, -0.500000, -0.809017}, -{-0.162460, -0.262866, -0.951056}, -{0.000000, -0.850651, -0.525731}, -{-0.147621, -0.716567, -0.681718}, -{0.147621, -0.716567, -0.681718}, -{0.000000, -0.525731, -0.850651}, -{0.309017, -0.500000, -0.809017}, -{0.442863, -0.238856, -0.864188}, -{0.162460, -0.262866, -0.951056}, -{0.238856, -0.864188, -0.442863}, -{0.500000, -0.809017, -0.309017}, -{0.425325, -0.688191, -0.587785}, -{0.716567, -0.681718, -0.147621}, -{0.688191, -0.587785, -0.425325}, -{0.587785, -0.425325, -0.688191}, -{0.000000, -0.955423, -0.295242}, -{0.000000, -1.000000, 0.000000}, -{0.262866, -0.951056, -0.162460}, -{0.000000, -0.850651, 0.525731}, -{0.000000, -0.955423, 0.295242}, -{0.238856, -0.864188, 0.442863}, -{0.262866, -0.951056, 0.162460}, -{0.500000, -0.809017, 0.309017}, -{0.716567, -0.681718, 0.147621}, -{0.525731, -0.850651, 0.000000}, -{-0.238856, -0.864188, -0.442863}, -{-0.500000, -0.809017, -0.309017}, -{-0.262866, -0.951056, -0.162460}, -{-0.850651, -0.525731, 0.000000}, -{-0.716567, -0.681718, -0.147621}, -{-0.716567, -0.681718, 0.147621}, -{-0.525731, -0.850651, 0.000000}, -{-0.500000, -0.809017, 0.309017}, -{-0.238856, -0.864188, 0.442863}, -{-0.262866, -0.951056, 0.162460}, -{-0.864188, -0.442863, 0.238856}, -{-0.809017, -0.309017, 0.500000}, -{-0.688191, -0.587785, 0.425325}, -{-0.681718, -0.147621, 0.716567}, -{-0.442863, -0.238856, 0.864188}, -{-0.587785, -0.425325, 0.688191}, -{-0.309017, -0.500000, 0.809017}, -{-0.147621, -0.716567, 0.681718}, -{-0.425325, -0.688191, 0.587785}, -{-0.162460, -0.262866, 0.951056}, -{0.442863, -0.238856, 0.864188}, -{0.162460, -0.262866, 0.951056}, -{0.309017, -0.500000, 0.809017}, -{0.147621, -0.716567, 0.681718}, -{0.000000, -0.525731, 0.850651}, -{0.425325, -0.688191, 0.587785}, -{0.587785, -0.425325, 0.688191}, -{0.688191, -0.587785, 0.425325}, -{-0.955423, 0.295242, 0.000000}, -{-0.951056, 0.162460, 0.262866}, -{-1.000000, 0.000000, 0.000000}, -{-0.850651, 0.000000, 0.525731}, -{-0.955423, -0.295242, 0.000000}, -{-0.951056, -0.162460, 0.262866}, -{-0.864188, 0.442863, -0.238856}, -{-0.951056, 0.162460, -0.262866}, -{-0.809017, 0.309017, -0.500000}, -{-0.864188, -0.442863, -0.238856}, -{-0.951056, -0.162460, -0.262866}, -{-0.809017, -0.309017, -0.500000}, -{-0.681718, 0.147621, -0.716567}, -{-0.681718, -0.147621, -0.716567}, -{-0.850651, 0.000000, -0.525731}, -{-0.688191, 0.587785, -0.425325}, -{-0.587785, 0.425325, -0.688191}, -{-0.425325, 0.688191, -0.587785}, -{-0.425325, -0.688191, -0.587785}, -{-0.587785, -0.425325, -0.688191}, -{-0.688191, -0.587785, -0.425325}, +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +{-0.525731, 0.000000, 0.850651}, +{-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, +{-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, +{0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, +{-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, +{0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, +{0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, +{0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, +{-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, +{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, +{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, +{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, +{-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, +{-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, +{-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, +{-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, +{-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, +{0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, +{0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, +{0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, +{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, +{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, +{0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, +{0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, +{0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, +{0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, +{0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, +{0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, +{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, +{0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, +{0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, +{0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, +{0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, +{0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, +{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, +{0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, +{0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, +{0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, +{0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, +{-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, +{0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, +{-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, +{-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, +{0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, +{-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, +{-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, +{-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, +{0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, +{0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, +{0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, +{0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, +{0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, +{0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, +{0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, +{0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, +{0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, +{0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, +{-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, +{-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, +{-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, +{-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, +{-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, +{-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, +{-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, +{-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, +{-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, +{-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, +{0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, +{0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, +{0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, +{0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, +{-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, +{-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, +{-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, +{-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, +{-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, +{-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, +{-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, +{-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, +{-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, +{-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, +{-0.688191, -0.587785, -0.425325}, diff --git a/engine/client/avi/avi.h b/engine/client/avi/avi.h index 6435a161..ace67167 100644 --- a/engine/client/avi/avi.h +++ b/engine/client/avi/avi.h @@ -18,16 +18,14 @@ GNU General Public License for more details. // // avikit.c // -typedef struct movie_state_s movie_state_t; +typedef struct movie_state_s movie_state_t; int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ); byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ); qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ); -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ); -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ); +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ); void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ); movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ); int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ); -int AVI_GetVideoFrameCount( movie_state_t *Avi ); void AVI_CloseVideo( movie_state_t *Avi ); qboolean AVI_IsActive( movie_state_t *Avi ); void AVI_FreeVideo( movie_state_t *Avi ); @@ -35,4 +33,7 @@ movie_state_t *AVI_GetState( int num ); qboolean AVI_Initailize( void ); void AVI_Shutdown( void ); +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ); +qboolean AVI_Think( movie_state_t *Avi ); + #endif // AVI_H diff --git a/engine/client/avi/avi_ffmpeg.c b/engine/client/avi/avi_ffmpeg.c new file mode 100644 index 00000000..07c9f321 --- /dev/null +++ b/engine/client/avi/avi_ffmpeg.c @@ -0,0 +1,969 @@ +/* +avi_ffmpreg.c - playing AVI files (ffmpeg backend) +Copyright (C) FTEQW developers (for plugins/avplug/avdecode.c) +Copyright (C) Sam Lantinga (for tests/testffmpeg.c) +Copyright (C) 2023-2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "defaults.h" +#include "common.h" +#include "client.h" + +static qboolean avi_initialized; +static poolhandle_t avi_mempool; + +#if XASH_AVI == AVI_FFMPEG +#define XASH_FFMPEG_DLOPEN 1 +#include "avi_ffmpeg.h" + +struct movie_state_s +{ + // ffmpeg contexts + AVFormatContext *fmt_ctx; + AVCodecContext *video_ctx; + AVCodecContext *audio_ctx; + struct SwsContext *sws_ctx; + struct SwrContext *swr_ctx; + + AVPacket *pkt; + AVFrame *aframe; + AVFrame *vframe; + AVFrame *vframe_copy; + + int64_t first_time; + int64_t last_time; + + // video stream + byte *dst; + double duration; + int video_stream; + int xres; + int yres; + int dst_linesize; + enum AVPixelFormat pix_fmt; + + // rendering video parameters + int x, y, w, h; // passed to R_DrawStretchRaw + int texture; // passed to R_UploadStretchRaw + + // audio stream + int audio_stream; + int channels; + int rate; + enum AVSampleFormat s_fmt; + + byte *cached_audio; + size_t cached_audio_buf_len; // absolute size of cached_audio array + size_t cached_audio_len; // how many data in bytes we have in cached_audio array + size_t cached_audio_pos; // how far we've read into cached_audio array + + // rendering audio parameters + float attn; + int16_t entnum; // MAX_ENTITY_BITS is 13 + byte volume; + byte active : 1; + byte quiet : 1; + byte paused : 1; +}; + +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) +{ + qboolean ret = true; + va_list va; + va_start( va, parm ); + + while( parm != AVI_PARM_LAST ) + { + float fval; + int val; + + switch( parm ) + { + case AVI_RENDER_TEXNUM: + Avi->texture = va_arg( va, int ); + break; + case AVI_RENDER_X: + Avi->x = va_arg( va, int ); + break; + case AVI_RENDER_Y: + Avi->y = va_arg( va, int ); + break; + case AVI_RENDER_W: + Avi->w = va_arg( va, int ); + break; + case AVI_RENDER_H: + Avi->h = va_arg( va, int ); + break; + case AVI_REWIND: + if( Avi->audio_ctx ) + pavcodec_flush_buffers( Avi->audio_ctx ); + pavcodec_flush_buffers( Avi->video_ctx ); + Avi->cached_audio_len = Avi->cached_audio_pos = 0; + Avi->last_time = -1; + Avi->first_time = 0; + pav_seek_frame( Avi->fmt_ctx, -1, 0, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD ); + break; + case AVI_ENTNUM: + val = va_arg( va, int ); + Avi->entnum = bound( 0, val, MAX_EDICTS ); + break; + case AVI_VOLUME: + val = va_arg( va, int ); + Avi->volume = bound( 0, val, 255 ); + break; + case AVI_ATTN: + fval = va_arg( va, double ); + Avi->attn = Q_max( 0.0f, fval ); + break; + case AVI_PAUSE: + Avi->paused = true; + break; + case AVI_RESUME: + Avi->paused = false; + break; + default: + ret = false; + } + + parm = va_arg( va, enum movie_parms_e ); + } + + va_end( va ); + + return ret; +} + +static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) FORMAT_CHECK( 2 ); +static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) +{ + char buf[MAX_VA_STRING]; + va_list va; + + if( quiet ) + return; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + Con_Printf( S_ERROR "%s", buf ); +} + +static void AVI_SpewAvError( qboolean quiet, const char *func, int numerr ) +{ + if( !quiet ) + { + char errstr[AV_ERROR_MAX_STRING_SIZE]; + pav_strerror( numerr, errstr, sizeof( errstr )); + Con_Printf( S_ERROR "%s: %s (%d)\n", func, errstr, numerr ); + } +} + +static int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type, qboolean quiet ) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *st; + int idx, ret; + + if(( ret = pav_find_best_stream( fmt_ctx, type, -1, -1, NULL, 0 )) < 0 ) + { + AVI_SpewAvError( quiet, "av_find_best_stream", ret ); + return ret; + } + + idx = ret; + st = fmt_ctx->streams[idx]; + + if( !( dec = pavcodec_find_decoder( st->codecpar->codec_id ))) + { + AVI_SpewError( quiet, S_ERROR "Failed to find %s codec\n", pav_get_media_type_string( type )); + return AVERROR( EINVAL ); + } + + if( !( dec_ctx = pavcodec_alloc_context3( dec ))) + { + AVI_SpewError( quiet, S_ERROR "Failed to allocate %s codec context", dec->name ); + return AVERROR( ENOMEM ); + } + + if(( ret = pavcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 ) + { + AVI_SpewAvError( quiet, "avcodec_parameters_to_context", ret ); + pavcodec_free_context( &dec_ctx ); + return ret; + } + + dec_ctx->pkt_timebase = st->time_base; + + if(( ret = pavcodec_open2( dec_ctx, dec, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avcodec_open2", ret ); + pavcodec_free_context( &dec_ctx ); + return ret; + } + + *dst_dec_ctx = dec_ctx; + return idx; // always positive +} + +int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) +{ + return 0; +} + +int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) +{ + return 0; +} + +qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) +{ + if( !Avi->active ) + return false; + + if( xres ) + *xres = Avi->xres; + + if( yres ) + *yres = Avi->yres; + + if( duration ) + *duration = Avi->duration; + + return true; +} + +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) +{ + return Avi ? Avi->active && Avi->audio_ctx : false; +} + +// just let it compile, bruh! +byte *AVI_GetVideoFrame( movie_state_t *Avi, int target ) +{ + return Avi->dst; +} + +static void AVI_StreamAudio( movie_state_t *Avi ) +{ + int buffer_samples, file_samples, file_bytes; + rawchan_t *ch = NULL; + + // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled + qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !s_listener.streaming : false; + + if( !dma.initialized || disable_stream || s_listener.paused || !Avi->cached_audio ) + return; + + ch = S_FindRawChannel( Avi->entnum, true ); + + if( !ch ) + return; + + ch->master_vol = Avi->volume; + ch->dist_mult = (Avi->attn / SND_CLIP_DISTANCE); + + if( ch->s_rawend < soundtime ) + ch->s_rawend = soundtime; + + while( ch->s_rawend < soundtime + ch->max_samples ) + { + size_t copy; + + buffer_samples = ch->max_samples - (ch->s_rawend - soundtime); + + file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED); + if( file_samples <= 1 ) return; // no more samples need + + file_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + + if( file_bytes > ch->max_samples ) + { + file_bytes = ch->max_samples; + file_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); + } + + copy = Q_min( file_bytes, Q_max( Avi->cached_audio_len - Avi->cached_audio_pos, 0 )); + + if( !copy ) + break; + + if( file_bytes > copy ) + { + file_bytes = copy; + file_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); + } + + ch->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 ); + Avi->cached_audio_pos += copy; + } +} + +static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame ) +{ + int samples = frame->nb_samples; + size_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + int outsamples; + uint8_t *ptr; + + // allocate data + if( !Avi->cached_audio ) + { + Avi->cached_audio_buf_len = len; + Avi->cached_audio_pos = 0; + Avi->cached_audio_len = 0; + Avi->cached_audio = Mem_Malloc( avi_mempool, len ); + } + else + { + if( Avi->cached_audio_pos ) + { + // Con_Printf( "%s: erasing old data of size %d\n", __func__, Avi->cached_audio_pos ); + Avi->cached_audio_len -= Avi->cached_audio_pos; + memmove( Avi->cached_audio, Avi->cached_audio + Avi->cached_audio_pos, Avi->cached_audio_len ); + Avi->cached_audio_pos = 0; + } + + if( len + Avi->cached_audio_len > Avi->cached_audio_buf_len ) + { + // 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 ); + Avi->cached_audio_buf_len = len + Avi->cached_audio_len; + Avi->cached_audio = Mem_Realloc( avi_mempool, Avi->cached_audio, Avi->cached_audio_buf_len ); + } + } + + ptr = Avi->cached_audio + Avi->cached_audio_len; + outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples ); + Avi->cached_audio_len += outsamples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; + + // Con_Printf( "%s: got audio chunk of size %d samples\n", __func__, outsamples ); +} + +qboolean AVI_Think( movie_state_t *Avi ) +{ + qboolean decoded = false; + qboolean flushing = false; + qboolean redraw = false; + const double timebase = (double)Avi->video_ctx->pkt_timebase.den / Avi->video_ctx->pkt_timebase.num; + int64_t curtime = round( Platform_DoubleTime() * timebase ); + + if( !Avi->first_time ) // always remember at which timestamp we started playing + Avi->first_time = curtime; + + if( Avi->paused ) + { + // FIXME: there might be a better way to do this + Avi->last_time = curtime; + return true; + } + + // Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); + + while( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps + { + int res; + + AVI_StreamAudio( Avi ); // always flush audio buffers + + // recalc time so we always play last possible frame + curtime = round( Platform_DoubleTime() * timebase ); + + if( Avi->last_time > curtime ) + break; + + if(( res = pav_read_frame( Avi->fmt_ctx, Avi->pkt )) >= 0 ) + { + if( Avi->pkt->stream_index == Avi->audio_stream ) + { + res = pavcodec_send_packet( Avi->audio_ctx, Avi->pkt ); + if( res < 0 ) + AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (audio)", res ); + } + else if( Avi->pkt->stream_index == Avi->video_stream ) + { + res = pavcodec_send_packet( Avi->video_ctx, Avi->pkt ); + if( res < 0 ) + AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (video)", res ); + } + pav_packet_unref( Avi->pkt ); + } + else + { + if( res != AVERROR_EOF ) + AVI_SpewAvError( Avi->quiet, "av_read_frame", res ); + + if( Avi->audio_ctx ) + pavcodec_flush_buffers( Avi->audio_ctx ); + + pavcodec_flush_buffers( Avi->video_ctx ); + flushing = true; + break; + } + + if( Avi->audio_ctx ) + { + while( pavcodec_receive_frame( Avi->audio_ctx, Avi->aframe ) == 0 ) + { + AVI_HandleAudio( Avi, Avi->aframe ); + decoded = true; + } + } + + while( pavcodec_receive_frame( Avi->video_ctx, Avi->vframe ) == 0 ) + { + Avi->last_time = Avi->first_time + Avi->vframe->best_effort_timestamp; + decoded = true; + + if( FBitSet( Avi->vframe->flags, AV_FRAME_FLAG_CORRUPT|AV_FRAME_FLAG_DISCARD )) + continue; + + if( Avi->vframe->decode_error_flags != 0 ) + continue; + + pav_frame_unref( Avi->vframe_copy ); + if( pav_frame_ref( Avi->vframe_copy, Avi->vframe ) == 0 ) + redraw = true; + } + } + + if( redraw ) + { + psws_scale( Avi->sws_ctx, (void*)Avi->vframe_copy->data, Avi->vframe_copy->linesize, 0, Avi->video_ctx->height, + &Avi->dst, &Avi->dst_linesize ); + pav_frame_unref( Avi->vframe_copy ); + } + + if( Avi->texture == 0 ) + { + int w = Avi->w >= 0 ? Avi->w : refState.width; + int h = Avi->h >= 0 ? Avi->h : refState.height; + + ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw ); + } + else if( redraw && Avi->texture > 0 ) + ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst ); + + if( flushing && !decoded ) + return false; // probably hit an EOF + + return true; +} + +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) +{ + byte *dst[4]; + int dst_linesize[4]; + int ret; + + if( Avi->active ) + AVI_CloseVideo( Avi ); + + if( !filename || !avi_initialized ) + return; + + Avi->active = false; + Avi->quiet = quiet; + Avi->video_ctx = Avi->audio_ctx = NULL; + Avi->fmt_ctx = NULL; + + if(( ret = pavformat_open_input( &Avi->fmt_ctx, filename, NULL, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avformat_open_input", ret ); + return; + } + + if(( ret = pavformat_find_stream_info( Avi->fmt_ctx, NULL )) < 0 ) + { + AVI_SpewAvError( quiet, "avformat_find_stream_info", ret ); + return; + } + + if( !( Avi->pkt = pav_packet_alloc( ))) + { + AVI_SpewAvError( quiet, "av_packet_alloc", 0 ); + return; + } + + if( !( Avi->vframe = pav_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); + return; + } + + if( !( Avi->vframe_copy = pav_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); + return; + } + + + Avi->video_stream = AVI_OpenCodecContext( &Avi->video_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_VIDEO, quiet ); + + if( Avi->video_stream < 0 ) + return; + + Avi->xres = Avi->video_ctx->width; + Avi->yres = Avi->video_ctx->height; + Avi->pix_fmt = Avi->video_ctx->pix_fmt; + Avi->duration = Avi->fmt_ctx->duration / (double)AV_TIME_BASE; + Avi->entnum = S_RAW_SOUND_SOUNDTRACK; + Avi->attn = ATTN_NONE; + Avi->volume = 255; + + if( !( Avi->sws_ctx = psws_getContext( Avi->xres, Avi->yres, Avi->pix_fmt, + Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, SWS_POINT, NULL, NULL, NULL ))) + { + AVI_SpewAvError( quiet, "sws_getContext", 0 ); + return; + } + + if(( ret = pav_image_alloc( dst, dst_linesize, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, 1 )) < 0 ) + { + AVI_SpewAvError( quiet, "av_image_alloc", ret ); + return; + } + + Avi->dst = dst[0]; + Avi->dst_linesize = dst_linesize[0]; + + if( load_audio ) + { + if( !( Avi->aframe = pav_frame_alloc( ))) + { + AVI_SpewAvError( quiet, "av_frame_alloc (audio)", 0 ); + return; + } + + Avi->audio_stream = AVI_OpenCodecContext( &Avi->audio_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_AUDIO, quiet ); + + // audio stream was requested but it wasn't found + if( Avi->audio_stream < 0 ) + return; + + Avi->channels = Q_min( Avi->audio_ctx->ch_layout.nb_channels, 2 ); + if( Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8 || Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8P ) + Avi->s_fmt = AV_SAMPLE_FMT_U8; + else Avi->s_fmt = AV_SAMPLE_FMT_S16; + Avi->rate = Avi->audio_ctx->sample_rate; + + if(( ret = pswr_alloc_set_opts2( &Avi->swr_ctx, &Avi->audio_ctx->ch_layout, Avi->s_fmt, Avi->rate, + &Avi->audio_ctx->ch_layout, Avi->audio_ctx->sample_fmt, Avi->audio_ctx->sample_rate, 0, 0 )) < 0 ) + { + AVI_SpewAvError( quiet, "swr_alloc_set_opts2", ret ); + return; + } + + if(( ret = pswr_init( Avi->swr_ctx )) < 0 ) + { + AVI_SpewAvError( quiet, "swr_init", ret ); + return; + } + } + + Avi->active = true; +} + +void AVI_CloseVideo( movie_state_t *Avi ) +{ + if( Avi->active ) + { + if( Avi->cached_audio ) + Mem_Free( Avi->cached_audio ); + + pswr_free( &Avi->swr_ctx ); + pavcodec_free_context( &Avi->audio_ctx ); + pav_frame_free( &Avi->aframe ); + + pav_free( Avi->dst ); + psws_freeContext( Avi->sws_ctx ); + pavcodec_free_context( &Avi->video_ctx ); + pav_frame_free( &Avi->vframe ); + pav_frame_free( &Avi->vframe_copy ); + + pav_packet_free( &Avi->pkt ); + + pavformat_close_input( &Avi->fmt_ctx ); + } + + memset( Avi, 0, sizeof( *Avi )); +} + +#if XASH_FFMPEG_DLOPEN +#define F( x ) #x, (void **)&p##x +static const dllfunc_t libavutil_funcs[] = +{ + { F( avutil_version ) }, + { F( av_frame_alloc ) }, + { F( av_frame_free ) }, + { F( av_frame_ref ) }, + { F( av_frame_unref ) }, + { F( av_strerror ) }, + { F( av_free ) }, + { F( av_get_bytes_per_sample ) }, + { F( av_get_media_type_string ) }, + { F( av_image_alloc ) }, +}; + +static const dllfunc_t libavformat_funcs[] = +{ + { F( avformat_version ) }, + { F( av_find_best_stream ) }, + { F( av_read_frame ) }, + { F( av_seek_frame ) }, + { F( avformat_close_input ) }, + { F( avformat_find_stream_info ) }, + { F( avformat_open_input ) }, +}; + +static const dllfunc_t libavcodec_funcs[] = +{ + { F( avcodec_version ) }, + { F( av_packet_alloc ) }, + { F( av_packet_free ) }, + { F( av_packet_unref ) }, + { F( avcodec_alloc_context3 ) }, + { F( avcodec_find_decoder ) }, + { F( avcodec_flush_buffers ) }, + { F( avcodec_free_context ) }, + { F( avcodec_open2 ) }, + { F( avcodec_parameters_to_context ) }, + { F( avcodec_receive_frame ) }, + { F( avcodec_send_packet ) }, +}; + +static const dllfunc_t libswresample_funcs[] = +{ + { F( swresample_version ) }, + { F( swr_alloc_set_opts2 ) }, + { F( swr_convert ) }, + { F( swr_free ) }, + { F( swr_init ) }, +}; + +static const dllfunc_t libswscale_funcs[] = +{ + { F( swscale_version ) }, + { F( sws_freeContext ) }, + { F( sws_getContext ) }, + { F( sws_scale ) }, +}; +#undef F + +#define SS( x ) #x +#define S( x ) SS( x ) + +static dll_info_t libavutil_info = +{ +#if XASH_WIN32 + .name = "avutil-" S( SUPPORTED_AVU_VERSION_MAJOR ) ".dll", +#else + .name = "libavutil.so." S( SUPPORTED_AVU_VERSION_MAJOR ), +#endif + .fcts = libavutil_funcs, + .num_fcts = ARRAYSIZE( libavutil_funcs ), +}; + +static dll_info_t libavformat_info = +{ +#if XASH_WIN32 + .name = "avformat-" S( SUPPORTED_AVF_VERSION_MAJOR ) ".dll", +#else + .name = "libavformat.so." S( SUPPORTED_AVF_VERSION_MAJOR ), +#endif + .fcts = libavformat_funcs, + .num_fcts = ARRAYSIZE( libavformat_funcs ), +}; + +static dll_info_t libavcodec_info = +{ +#if XASH_WIN32 + .name = "avcodec-" S( SUPPORTED_AVC_VERSION_MAJOR ) ".dll", +#else + .name = "libavcodec.so." S( SUPPORTED_AVC_VERSION_MAJOR ), +#endif + .fcts = libavcodec_funcs, + .num_fcts = ARRAYSIZE( libavcodec_funcs ), +}; + +static dll_info_t libswresample_info = +{ +#if XASH_WIN32 + .name = "swresample-" S( SUPPORTED_SWR_VERSION_MAJOR ) ".dll", +#else + .name = "libswresample.so." S( SUPPORTED_SWR_VERSION_MAJOR ), +#endif + .fcts = libswresample_funcs, + .num_fcts = ARRAYSIZE( libswresample_funcs ), +}; + +static dll_info_t libswscale_info = +{ +#if XASH_WIN32 + .name = "swscale-" S( SUPPORTED_SWS_VERSION_MAJOR ) ".dll", +#else + .name = "libswscale.so." S( SUPPORTED_SWS_VERSION_MAJOR ), +#endif + .fcts = libswscale_funcs, + .num_fcts = ARRAYSIZE( libswscale_funcs ), +}; + +static qboolean AVI_LoadFFmpeg( void ) +{ + if( !Sys_LoadLibrary( &libavutil_info )) + return false; + + if( !Sys_LoadLibrary( &libavformat_info )) + return false; + + if( !Sys_LoadLibrary( &libavcodec_info )) + return false; + + if( !Sys_LoadLibrary( &libswresample_info )) + return false; + + if( !Sys_LoadLibrary( &libswscale_info )) + return false; + + return true; +} + +static void AVI_UnloadFFmpeg( void ) +{ + Sys_FreeLibrary( &libavutil_info ); + Sys_FreeLibrary( &libavformat_info ); + Sys_FreeLibrary( &libavcodec_info ); + Sys_FreeLibrary( &libswresample_info ); + Sys_FreeLibrary( &libswscale_info ); +} + +#else +static qboolean AVI_LoadFFmpeg( void ) +{ + return true; +} + +static void AVI_UnloadFFmpeg( void ) +{ + +} +#endif + +static qboolean AVI_ValidateFFmpegVersion( void ) +{ + uint ver; + + // print version we're compiled with and which version we're running with + ver = pavutil_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVU_VERSION_MAJOR ) + { + Con_Printf( S_ERROR "AVI: Unsupported libavutil version.\n" ); + return false; + } + + ver = pavformat_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVF_VERSION_MAJOR ) + { + Con_Printf( S_ERROR "AVI: Unsupported libavformat version.\n" ); + return false; + } + + ver = pavcodec_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVC_VERSION_MAJOR ) + { + Con_Printf( S_ERROR "AVI: Unsupported libavcodec version.\n" ); + return false; + } + + ver = pswscale_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + if( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWS_VERSION_MAJOR ) + { + Con_Printf( S_ERROR "AVI: Unsupported libswscale version.\n" ); + return false; + } + + ver = pswresample_version(); + Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); + + if( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWR_VERSION_MAJOR ) + { + Con_Printf( S_ERROR "AVI: Unsupported libswresample version.\n" ); + return false; + } + + return true; +} +#else +struct movie_state_s +{ + qboolean active; +}; + +int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) +{ + return 0; +} + +byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) +{ + return NULL; +} + +qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) +{ + return false; +} + +qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) +{ + return false; +} + +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) +{ + ; +} + +int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) +{ + return 0; +} + +void AVI_CloseVideo( movie_state_t *Avi ) +{ + ; +} + +qboolean AVI_Think( movie_state_t *Avi ) +{ + return false; +} + +qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) +{ + return false; +} + +static qboolean AVI_ValidateFFmpegVersion( void ) +{ + return false; +} + +static qboolean AVI_LoadFFmpeg( void ) +{ + return false; +} + +static void AVI_UnloadFFmpeg( void ) +{ + +} +#endif // XASH_AVI == AVI_NULL + +static movie_state_t avi[2]; +movie_state_t *AVI_GetState( int num ) +{ + return &avi[num]; +} + +qboolean AVI_IsActive( movie_state_t *Avi ) +{ + return Avi ? Avi->active : false; +} + +qboolean AVI_Initailize( void ) +{ + if( XASH_AVI == AVI_NULL ) + { + Con_Printf( "AVI: Not supported\n" ); + return false; + } + + if( Sys_CheckParm( "-noavi" )) + { + Con_Printf( "AVI: Disabled\n" ); + return false; + } + + if( !AVI_LoadFFmpeg( )) + return false; + + if( !AVI_ValidateFFmpegVersion( )) + return false; + + avi_initialized = true; + avi_mempool = Mem_AllocPool( "AVI Zone" ); + + return false; +} + +void AVI_Shutdown( void ) +{ + Mem_FreePool( &avi_mempool ); + avi_initialized = false; + + AVI_UnloadFFmpeg(); +} + +movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) +{ + movie_state_t *Avi; + string path; + const char *fullpath; + + // fast reject + if( !avi_initialized ) + return NULL; + + // open cinematic + Q_snprintf( path, sizeof( path ), "media/%s", filename ); + COM_DefaultExtension( path, ".avi", sizeof( path )); + fullpath = FS_GetDiskPath( path, false ); + + if( FS_FileExists( path, false ) && !fullpath ) + { + Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path ); + return NULL; + } + + Avi = Mem_Calloc( avi_mempool, sizeof( movie_state_t )); + AVI_OpenVideo( Avi, fullpath, load_audio, false ); + + if( !AVI_IsActive( Avi )) + { + AVI_FreeVideo( Avi ); // something bad happens + return NULL; + } + + // all done + return Avi; +} + +void AVI_FreeVideo( movie_state_t *Avi ) +{ + if( !Avi ) + return; + + AVI_CloseVideo( Avi ); + + if( Mem_IsAllocatedExt( avi_mempool, Avi )) + Mem_Free( Avi ); +} diff --git a/engine/client/avi/avi_ffmpeg.h b/engine/client/avi/avi_ffmpeg.h new file mode 100644 index 00000000..042ebc43 --- /dev/null +++ b/engine/client/avi/avi_ffmpeg.h @@ -0,0 +1,165 @@ +/* +avi_ffmpreg.c - playing AVI files (ffmpeg backend) +Copyright (C) FTEQW developers (for plugins/avplug/avdecode.c) +Copyright (C) Sam Lantinga (for tests/testffmpeg.c) +Copyright (C) 2023-2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef AVI_FFMPEG_H +#define AVI_FFMPEG_H + +#if XASH_AVI == AVI_FFMPEG +#include +#include +#include +#include +#include + +#if XASH_FFMPEG_DLOPEN + +// the following symbols were taken from ffmpeg public headers +// on each major ffmpeg uprgade, they must be validated +// ffmpeg guarantees API and ABI compatibility between major versions +// so complain if this gets compiled against unsupported yet version +// the same check will be done in runtime to ensure compatibility +#define SUPPORTED_AVU_VERSION_MAJOR 59 +#define SUPPORTED_AVF_VERSION_MAJOR 61 +#define SUPPORTED_AVC_VERSION_MAJOR 61 +#define SUPPORTED_SWR_VERSION_MAJOR 5 +#define SUPPORTED_SWS_VERSION_MAJOR 8 + +#if SUPPORTED_AVU_VERSION_MAJOR != LIBAVUTIL_VERSION_MAJOR +#error +#endif + +#if SUPPORTED_AVF_VERSION_MAJOR != LIBAVFORMAT_VERSION_MAJOR +#error +#endif + +#if SUPPORTED_AVC_VERSION_MAJOR != LIBAVCODEC_VERSION_MAJOR +#error +#endif + +#if SUPPORTED_SWR_VERSION_MAJOR != LIBSWRESAMPLE_VERSION_MAJOR +#error +#endif + +#if SUPPORTED_SWS_VERSION_MAJOR != LIBSWSCALE_VERSION_MAJOR +#error +#endif + +// libavutil +unsigned (*pavutil_version)( void ); +AVFrame *(*pav_frame_alloc)( void ); +void (*pav_frame_free)( AVFrame **frame ); +int (*pav_frame_ref)( AVFrame *dst, const AVFrame *src ); +void (*pav_frame_unref)( AVFrame *frame ); +int (*pav_strerror)( int errnum, char *errbuf, size_t errbuf_size ); +void (*pav_free)( void *ptr ); +int (*pav_get_bytes_per_sample)( enum AVSampleFormat sample_fmt ); +const char *(*pav_get_media_type_string)( enum AVMediaType media_type ); +int (*pav_image_alloc)( uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align ); + +// libavformat +unsigned (*pavformat_version)( void ); +int (*pav_find_best_stream)( AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, const struct AVCodec **decoder_ret, int flags ); +int (*pav_read_frame)( AVFormatContext *s, AVPacket *pkt ); +int (*pav_seek_frame)( AVFormatContext *s, int stream_index, int64_t timestamp, int flags ); +void (*pavformat_close_input)(AVFormatContext **s); +int (*pavformat_find_stream_info)(AVFormatContext *ic, AVDictionary **options); +int (*pavformat_open_input)(AVFormatContext **ps, const char *url, const AVInputFormat *fmt, AVDictionary **options); + +// libavcodec +unsigned (*pavcodec_version)( void ); +AVPacket *(*pav_packet_alloc)( void ); +void (*pav_packet_free)( AVPacket **pkt ); +void (*pav_packet_unref)( AVPacket *pkt ); +AVCodecContext *(*pavcodec_alloc_context3)( const AVCodec *codec ); +const AVCodec *(*pavcodec_find_decoder)( enum AVCodecID id ); +void (*pavcodec_flush_buffers)( AVCodecContext *avctx ); +void (*pavcodec_free_context)( AVCodecContext **avctx ); +int (*pavcodec_open2)( AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options ); +int (*pavcodec_parameters_to_context)( AVCodecContext *codec, const struct AVCodecParameters *par ); +int (*pavcodec_receive_frame)( AVCodecContext *avctx, AVFrame *frame ); +int (*pavcodec_send_packet)( AVCodecContext *avctx, const AVPacket *avpkt ); + +// libswresample +unsigned (*pswresample_version)( void ); +int (*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 ); +int (*pswr_convert)( struct SwrContext *s, uint8_t *const *out, int out_count, const uint8_t *const *in, int in_count ); +void (*pswr_free)( struct SwrContext **s ); +int (*pswr_init)( struct SwrContext *s ); + +// libswscale +unsigned (*pswscale_version)( void ); +void (*psws_freeContext)( struct SwsContext *swsContext ); +struct 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 ); +int (*psws_scale)( struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[] ); + +#else // !XASH_FFMPEG_DLOPEN + +#define SUPPORTED_AVU_VERSION_MAJOR LIBAVUTIL_VERSION_MAJOR +#define SUPPORTED_AVF_VERSION_MAJOR LIBAVFORMAT_VERSION_MAJOR +#define SUPPORTED_AVC_VERSION_MAJOR LIBAVCODEC_VERSION_MAJOR +#define SUPPORTED_SWR_VERSION_MAJOR LIBSWRESAMPLE_VERSION_MAJOR +#define SUPPORTED_SWS_VERSION_MAJOR LIBSWSCALE_VERSION_MAJOR + +// libavutil +#define pavutil_version avutil_version +#define pav_frame_alloc av_frame_alloc +#define pav_frame_ref av_frame_ref +#define pav_frame_unref av_Frame_unref +#define pav_strerror av_strerror +#define pav_free av_free +#define pav_get_bytes_per_sample av_get_bytes_per_sample +#define pav_get_media_type_string av_get_media_type_string +#define pav_image_alloc av_image_alloc + +// libavformat +#define pavformat_version avformat_version +#define pav_find_best_stream av_find_best_stream +#define pav_read_frame av_read_frame +#define pav_seek_frame av_seek_frame +#define pavformat_close_input avformat_close_input +#define pavformat_find_stream_info avformat_find_stream_info +#define pavformat_open_input avformat_open_input + +// libavcodec +#define pavcodec_version avcodec_version +#define pav_packet_alloc av_packet_alloc +#define pav_packet_free av_packet_free +#define pav_packet_unref av_packet_unref +#define pavcodec_alloc_context3 avcodec_alloc_context3 +#define pavcodec_find_decoder avcodec_find_decoder +#define pavcodec_flush_buffers avcodec_flush_buffers +#define pavcodec_free_context avcodec_free_context +#define pavcodec_open2 avcodec_open2 +#define pavcodec_parameters_to_context avcodec_parameters_to_context +#define pavcodec_receive_frame avcodec_receive_frame +#define pavcodec_send_packet avcodec_send_packet + +// libswresample +#define pswresample_version swresample_version +#define pswr_alloc_set_opts2 swr_alloc_set_opts2 +#define pswr_convert swr_convert +#define pswr_free swr_free +#define pswr_init swr_init + +// libswscale +#define pswscale_version swscale_version +#define psws_freeContext sws_freeContext +#define psws_getContext sws_getContext +#define psws_scale sws_scale + +#endif // !XASH_FFMPEG_DLOPEN +#endif // XASH_AVI == AVI_FFMPEG +#endif // AVI_FFMPEG_H diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c deleted file mode 100644 index bb64b13f..00000000 --- a/engine/client/avi/avi_stub.c +++ /dev/null @@ -1,95 +0,0 @@ -/* -avi_stub.c - playing AVI files (stub) -Copyright (C) 2018 a1batross - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "build.h" -#if !XASH_WIN32 -#include "common.h" - -int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) -{ - return 0; -} - -byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) -{ - return NULL; -} - -qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) -{ - return false; -} - -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) -{ - return false; -} - -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) -{ - return 0; -} - -void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) -{ - ; -} - -movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) -{ - return NULL; -} - -int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) -{ - return 0; -} - -int AVI_GetVideoFrameCount( movie_state_t *Avi ) -{ - return 0; -} - -void AVI_CloseVideo( movie_state_t *Avi ) -{ - ; -} - -qboolean AVI_IsActive( movie_state_t *Avi ) -{ - return false; -} - -void AVI_FreeVideo( movie_state_t *Avi ) -{ - ; -} - -movie_state_t *AVI_GetState( int num ) -{ - return NULL; -} - -qboolean AVI_Initailize( void ) -{ - return false; -} - -void AVI_Shutdown( void ) -{ - ; -} - -#endif // WIN32 diff --git a/engine/client/avi/avi_win.c b/engine/client/avi/avi_win.c deleted file mode 100644 index 534b31ce..00000000 --- a/engine/client/avi/avi_win.c +++ /dev/null @@ -1,734 +0,0 @@ -/* -avi_win.c - playing AVI files (based on original AVIKit code, Win32 version) -Copyright (C) 2010 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "build.h" -#if XASH_WIN32 -#include "common.h" -#include "client.h" -#include // video for windows - -// msvfw32.dll exports -static HDRAWDIB (_stdcall *pDrawDibOpen)( void ); -static BOOL (_stdcall *pDrawDibClose)( HDRAWDIB hdd ); -static BOOL (_stdcall *pDrawDibDraw)( HDRAWDIB, HDC, int, int, int, int, LPBITMAPINFOHEADER, void*, int, int, int, int, uint ); - -static dllfunc_t msvfw_funcs[] = -{ -{ "DrawDibOpen", (void **) &pDrawDibOpen }, -{ "DrawDibDraw", (void **) &pDrawDibDraw }, -{ "DrawDibClose", (void **) &pDrawDibClose }, -{ NULL, NULL } -}; - -dll_info_t msvfw_dll = { "msvfw32.dll", msvfw_funcs, false }; - -// msacm32.dll exports -static MMRESULT (_stdcall *pacmStreamOpen)( LPHACMSTREAM, HACMDRIVER, LPWAVEFORMATEX, LPWAVEFORMATEX, LPWAVEFILTER, DWORD, DWORD, DWORD ); -static MMRESULT (_stdcall *pacmStreamPrepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamUnprepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamConvert)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); -static MMRESULT (_stdcall *pacmStreamSize)( HACMSTREAM, DWORD, LPDWORD, DWORD ); -static MMRESULT (_stdcall *pacmStreamClose)( HACMSTREAM, DWORD ); - -static dllfunc_t msacm_funcs[] = -{ -{ "acmStreamOpen", (void **) &pacmStreamOpen }, -{ "acmStreamPrepareHeader", (void **) &pacmStreamPrepareHeader }, -{ "acmStreamUnprepareHeader", (void **) &pacmStreamUnprepareHeader }, -{ "acmStreamConvert", (void **) &pacmStreamConvert }, -{ "acmStreamSize", (void **) &pacmStreamSize }, -{ "acmStreamClose", (void **) &pacmStreamClose }, -{ NULL, NULL } -}; - -dll_info_t msacm_dll = { "msacm32.dll", msacm_funcs, false }; - -// avifil32.dll exports -static int (_stdcall *pAVIStreamInfo)( PAVISTREAM pavi, AVISTREAMINFO *psi, LONG lSize ); -static int (_stdcall *pAVIStreamRead)( PAVISTREAM pavi, LONG lStart, LONG lSamples, void *lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples ); -static PGETFRAME (_stdcall *pAVIStreamGetFrameOpen)( PAVISTREAM pavi, LPBITMAPINFOHEADER lpbiWanted ); -static int (_stdcall *pAVIStreamTimeToSample)( PAVISTREAM pavi, LONG lTime ); -static void* (_stdcall *pAVIStreamGetFrame)( PGETFRAME pg, LONG lPos ); -static int (_stdcall *pAVIStreamGetFrameClose)( PGETFRAME pg ); -static dword (_stdcall *pAVIStreamRelease)( PAVISTREAM pavi ); -static int (_stdcall *pAVIFileOpen)( PAVIFILE *ppfile, LPCSTR szFile, UINT uMode, LPCLSID lpHandler ); -static int (_stdcall *pAVIFileGetStream)( PAVIFILE pfile, PAVISTREAM *ppavi, DWORD fccType, LONG lParam ); -static int (_stdcall *pAVIStreamReadFormat)( PAVISTREAM pavi, LONG lPos,LPVOID lpFormat, LONG *lpcbFormat ); -static int (_stdcall *pAVIStreamStart)( PAVISTREAM pavi ); -static dword (_stdcall *pAVIFileRelease)( PAVIFILE pfile ); -static void (_stdcall *pAVIFileInit)( void ); -static void (_stdcall *pAVIFileExit)( void ); - -static dllfunc_t avifile_funcs[] = -{ -{ "AVIFileExit", (void **) &pAVIFileExit }, -{ "AVIFileGetStream", (void **) &pAVIFileGetStream }, -{ "AVIFileInit", (void **) &pAVIFileInit }, -{ "AVIFileOpenA", (void **) &pAVIFileOpen }, -{ "AVIFileRelease", (void **) &pAVIFileRelease }, -{ "AVIStreamGetFrame", (void **) &pAVIStreamGetFrame }, -{ "AVIStreamGetFrameClose", (void **) &pAVIStreamGetFrameClose }, -{ "AVIStreamGetFrameOpen", (void **) &pAVIStreamGetFrameOpen }, -{ "AVIStreamInfoA", (void **) &pAVIStreamInfo }, -{ "AVIStreamRead", (void **) &pAVIStreamRead }, -{ "AVIStreamReadFormat", (void **) &pAVIStreamReadFormat }, -{ "AVIStreamRelease", (void **) &pAVIStreamRelease }, -{ "AVIStreamStart", (void **) &pAVIStreamStart }, -{ "AVIStreamTimeToSample", (void **) &pAVIStreamTimeToSample }, -{ NULL, NULL } -}; - -dll_info_t avifile_dll = { "avifil32.dll", avifile_funcs, false }; - -typedef struct movie_state_s -{ - qboolean active; - qboolean quiet; // ignore error messages - - PAVIFILE pfile; // avi file pointer - PAVISTREAM video_stream; // video stream pointer - PGETFRAME video_getframe; // pointer to getframe object for video stream - int video_frames; // total frames - int video_xres; // video stream resolution - int video_yres; - float video_fps; // video stream fps - - PAVISTREAM audio_stream; // audio stream pointer - WAVEFORMAT *audio_header; // audio stream header - int audio_header_size; // WAVEFORMAT is returned for PCM data; WAVEFORMATEX for others - int audio_codec; // WAVE_FORMAT_PCM is oldstyle: anything else needs conversion - int audio_length; // in converted samples - int audio_bytes_per_sample; // guess. - - // compressed audio specific data - dword cpa_blockalign; // block size to read - HACMSTREAM cpa_conversion_stream; - ACMSTREAMHEADER cpa_conversion_header; - byte *cpa_srcbuffer; // maintained buffer for raw data - byte *cpa_dstbuffer; - - dword cpa_blocknum; // current block - dword cpa_blockpos; // read position in current block - dword cpa_blockoffset; // corresponding offset in bytes in the output stream - - // for additional unpack Ms-RLE codecs etc - HDC hDC; // compatible DC - HDRAWDIB hDD; // DrawDib handler - HBITMAP hBitmap; // for DIB conversions - byte *pframe_data; // converted framedata -} movie_state_t; - -static qboolean avi_initialized = false; -static movie_state_t avi[2]; - -// Converts a compressed audio stream into uncompressed PCM. -qboolean AVI_ACMConvertAudio( movie_state_t *Avi ) -{ - WAVEFORMATEX dest_header, *sh, *dh; - AVISTREAMINFO stream_info; - dword dest_length; - short bits; - - // WMA codecs, both versions - they simply don't work. - if( Avi->audio_header->wFormatTag == 0x160 || Avi->audio_header->wFormatTag == 0x161 ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM does not support this audio codec.\n" ); - return false; - } - - // get audio stream info to work with - pAVIStreamInfo( Avi->audio_stream, &stream_info, sizeof( stream_info )); - - if( Avi->audio_header_size < sizeof( WAVEFORMATEX )) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" ); - return false; - } - - sh = (WAVEFORMATEX *)Avi->audio_header; - bits = 16; // predict state - - // how much of this is actually required? - dest_header.wFormatTag = WAVE_FORMAT_PCM; // yay - dest_header.wBitsPerSample = bits; // 16bit - dest_header.nChannels = sh->nChannels; - dest_header.nSamplesPerSec = sh->nSamplesPerSec; // take straight from the source stream - dest_header.nAvgBytesPerSec = (bits >> 3) * sh->nChannels * sh->nSamplesPerSec; - dest_header.nBlockAlign = (bits >> 3) * sh->nChannels; - dest_header.cbSize = 0; // no more data. - - dh = &dest_header; - - // open the stream - if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) - { - // try with 8 bit destination instead - bits = 8; - - dest_header.wBitsPerSample = bits; // 8bit - dest_header.nAvgBytesPerSec = ( bits >> 3 ) * sh->nChannels * sh->nSamplesPerSec; - dest_header.nBlockAlign = ( bits >> 3 ) * sh->nChannels; // 1 sample at a time - - if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" ); - return false; - } - } - - Avi->cpa_blockalign = sh->nBlockAlign; - dest_length = 0; - - // mp3 specific fix - if( sh->wFormatTag == 0x55 ) - { - LPMPEGLAYER3WAVEFORMAT k; - - k = (LPMPEGLAYER3WAVEFORMAT)sh; - Avi->cpa_blockalign = k->nBlockSize; - } - - // get the size of the output buffer for streaming the compressed audio - if( pacmStreamSize( Avi->cpa_conversion_stream, Avi->cpa_blockalign, &dest_length, ACM_STREAMSIZEF_SOURCE ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "Couldn't get ACM conversion stream size.\n" ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - return false; - } - - Avi->cpa_srcbuffer = (byte *)Mem_Malloc( cls.mempool, Avi->cpa_blockalign ); - Avi->cpa_dstbuffer = (byte *)Mem_Malloc( cls.mempool, dest_length ); // maintained buffer for raw data - - // prep the headers! - Avi->cpa_conversion_header.cbStruct = sizeof( ACMSTREAMHEADER ); - Avi->cpa_conversion_header.fdwStatus = 0; - Avi->cpa_conversion_header.dwUser = 0; // no user data - Avi->cpa_conversion_header.pbSrc = Avi->cpa_srcbuffer; // source buffer - Avi->cpa_conversion_header.cbSrcLength = Avi->cpa_blockalign; // source buffer size - Avi->cpa_conversion_header.cbSrcLengthUsed = 0; - Avi->cpa_conversion_header.dwSrcUser = 0; // no user data - Avi->cpa_conversion_header.pbDst = Avi->cpa_dstbuffer; // dest buffer - Avi->cpa_conversion_header.cbDstLength = dest_length; // dest buffer size - Avi->cpa_conversion_header.cbDstLengthUsed = 0; - Avi->cpa_conversion_header.dwDstUser = 0; // no user data - - if( pacmStreamPrepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ) != MMSYSERR_NOERROR ) - { - if( !Avi->quiet ) - Con_Reportf( S_ERROR "couldn't prepare stream headers.\n" ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - return false; - } - - Avi->cpa_blocknum = 0; // start at 0. - Avi->cpa_blockpos = 0; - Avi->cpa_blockoffset = 0; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); - - // convert first chunk twice. it often fails the first time. BLACK MAGIC. - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - Avi->audio_bytes_per_sample = (bits >> 3 ) * Avi->audio_header->nChannels; - - return true; -} - -qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) -{ - if( !Avi->active ) - return false; - - if( xres != NULL ) - *xres = Avi->video_xres; - - if( yres != NULL ) - *yres = Avi->video_yres; - - if( duration != NULL ) - *duration = (float)Avi->video_frames / Avi->video_fps; - - return true; -} - -// returns a unique frame identifier -int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) -{ - if( !Avi->active ) - return 0; - - return (time * Avi->video_fps); -} - -int AVI_GetVideoFrameCount( movie_state_t *Avi ) -{ - if( !Avi->active ) - return 0; - - return Avi->video_frames; -} - -int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) -{ - if( !Avi->active || !Avi->audio_stream ) - return 0; - - // UNDONE: what about compressed audio? - return pAVIStreamTimeToSample( Avi->audio_stream, time ) * Avi->audio_bytes_per_sample; -} - -// gets the raw frame data -byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) -{ - LPBITMAPINFOHEADER frame_info; - byte *frame_raw; - - if( !Avi->active ) return NULL; - - if( frame >= Avi->video_frames ) - frame = Avi->video_frames - 1; - - frame_info = (LPBITMAPINFOHEADER)pAVIStreamGetFrame( Avi->video_getframe, frame ); - frame_raw = (byte *)frame_info + frame_info->biSize + frame_info->biClrUsed * sizeof( RGBQUAD ); - pDrawDibDraw( Avi->hDD, Avi->hDC, 0, 0, Avi->video_xres, Avi->video_yres, frame_info, frame_raw, 0, 0, Avi->video_xres, Avi->video_yres, 0 ); - - return Avi->pframe_data; -} - -qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) -{ - if( !Avi->active || Avi->audio_stream == NULL || snd_info == NULL ) - { - return false; - } - - snd_info->rate = Avi->audio_header->nSamplesPerSec; - snd_info->channels = Avi->audio_header->nChannels; - - if( Avi->audio_codec == WAVE_FORMAT_PCM ) // uncompressed audio! - snd_info->width = ( Avi->audio_bytes_per_sample > Avi->audio_header->nChannels ) ? 2 : 1; - else snd_info->width = 2; // assume compressed audio is always 16 bit - - snd_info->size = snd_info->rate * snd_info->width * snd_info->channels; - snd_info->loopStart = 0; // using loopStart as streampos - - return true; -} - -// sync the current audio read to a specific offset -qboolean AVI_SeekPosition( movie_state_t *Avi, dword offset ) -{ - int breaker; - - if( offset < Avi->cpa_blockoffset ) // well, shit. we can't seek backwards... restart - { - if( Avi->cpa_blockoffset - offset < 500000 ) - return false; // don't bother if it's gonna catch up soon - - Avi->cpa_blocknum = 0; // start at 0, eh. - Avi->cpa_blockpos = 0; - Avi->cpa_blockoffset = 0; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); - - // convert first chunk twice. it often fails the first time. BLACK MAGIC. - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - } - - // now then: seek forwards to the required block - breaker = 30; // maximum zero blocks: anti-freeze protection - - while( Avi->cpa_blockoffset + Avi->cpa_conversion_header.cbDstLengthUsed < offset ) - { - Avi->cpa_blocknum++; - Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - if( Avi->cpa_conversion_header.cbDstLengthUsed == 0 ) - breaker--; - else breaker = 30; - - if( breaker <= 0 ) - return false; - - Avi->cpa_blockpos = 0; - } - - // seek to the right position inside the block - Avi->cpa_blockpos = offset - Avi->cpa_blockoffset; - - return true; -} - -// get a chunk of audio from the stream (in bytes) -int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length ) -{ - int result = 0; - int i; - - // zero data past the end of the file - if( offset + length > Avi->audio_length ) - { - if( offset <= Avi->audio_length ) - { - int remaining_length = Avi->audio_length - offset; - - AVI_GetAudioChunk( Avi, audiodata, offset, remaining_length ); - - for( i = remaining_length; i < length; i++ ) - audiodata[i] = 0; - } - else - { - // we out of soundtrack, just zeroing buffer - for( i = 0; i < length; i++ ) - audiodata[i] = 0; - -// return length; - } - } - - // uncompressed audio! - if( Avi->audio_codec == WAVE_FORMAT_PCM ) - { - // very simple - read straight out - pAVIStreamRead( Avi->audio_stream, offset / Avi->audio_bytes_per_sample, length / Avi->audio_bytes_per_sample, audiodata, length, &result, NULL ); - return result; - } - else - { - // compressed audio! - result = 0; - - // seek to correct chunk and all that stuff - if( !AVI_SeekPosition( Avi, offset )) - return 0; // don't continue if we're waiting for the play pointer to catch up - - while( length > 0 ) - { - int blockread = Avi->cpa_conversion_header.cbDstLengthUsed - Avi->cpa_blockpos; - - if( blockread <= 0 ) // read next - { - Avi->cpa_blocknum++; - Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; - - pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); - pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); - - Avi->cpa_blockpos = 0; - continue; - } - - if( blockread > length ) - blockread = length; - - // copy the data - memcpy( audiodata + result, (void *)( Avi->cpa_dstbuffer + Avi->cpa_blockpos ), blockread ); - - Avi->cpa_blockpos += blockread; - result += blockread; - length -= blockread; - } - - return result; - } -} - -void AVI_CloseVideo( movie_state_t *Avi ) -{ - if( Avi->active ) - { - pAVIStreamGetFrameClose( Avi->video_getframe ); - - if( Avi->audio_stream != NULL ) - { - pAVIStreamRelease( Avi->audio_stream ); - Mem_Free( Avi->audio_header ); - - if( Avi->audio_codec != WAVE_FORMAT_PCM ) - { - pacmStreamUnprepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ); - pacmStreamClose( Avi->cpa_conversion_stream, 0 ); - Mem_Free( Avi->cpa_srcbuffer ); - Mem_Free( Avi->cpa_dstbuffer ); - } - } - - pAVIStreamRelease( Avi->video_stream ); - - DeleteObject( Avi->hBitmap ); - pDrawDibClose( Avi->hDD ); - DeleteDC( Avi->hDC ); - } - - memset( Avi, 0, sizeof( movie_state_t )); -} - -void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) -{ - BITMAPINFOHEADER bmih; - AVISTREAMINFO stream_info; - int opened_streams = 0; - LONG hr; - - // default state: non-working. - Avi->active = false; - Avi->quiet = quiet; - - // can't load Video For Windows :-( - if( !avi_initialized ) return; - - // load the AVI - hr = pAVIFileOpen( &Avi->pfile, filename, OF_SHARE_DENY_WRITE, 0L ); - - if( hr != 0 ) // error opening AVI: - { - switch( hr ) - { - case AVIERR_BADFORMAT: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "corrupt file or unknown format.\n" ); - break; - case AVIERR_MEMORY: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "insufficient memory to open file.\n" ); - break; - case AVIERR_FILEREAD: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "disk error reading file.\n" ); - break; - case AVIERR_FILEOPEN: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "disk error opening file.\n" ); - break; - case REGDB_E_CLASSNOTREG: - default: - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "no handler found (or file not found).\n" ); - break; - } - return; - } - - Avi->video_stream = Avi->audio_stream = NULL; - - // open the streams until a stream is not available. - while( 1 ) - { - PAVISTREAM stream = NULL; - - if( pAVIFileGetStream( Avi->pfile, &stream, 0L, opened_streams++ ) != AVIERR_OK ) - break; - - if( stream == NULL ) - break; - - pAVIStreamInfo( stream, &stream_info, sizeof( stream_info )); - - if( stream_info.fccType == streamtypeVIDEO && Avi->video_stream == NULL ) - { - Avi->video_stream = stream; - Avi->video_frames = stream_info.dwLength; - Avi->video_xres = stream_info.rcFrame.right - stream_info.rcFrame.left; - Avi->video_yres = stream_info.rcFrame.bottom - stream_info.rcFrame.top; - Avi->video_fps = (float)stream_info.dwRate / (float)stream_info.dwScale; - } - else if( stream_info.fccType == streamtypeAUDIO && Avi->audio_stream == NULL && load_audio ) - { - int size; - - Avi->audio_stream = stream; - - // read the audio header - pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), 0, &size ); - - Avi->audio_header = (WAVEFORMAT *)Mem_Malloc( cls.mempool, size ); - pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), Avi->audio_header, &size ); - Avi->audio_header_size = size; - Avi->audio_codec = Avi->audio_header->wFormatTag; - - // length of converted audio in samples - Avi->audio_length = (int)((float)stream_info.dwLength / Avi->audio_header->nAvgBytesPerSec ); - Avi->audio_length *= Avi->audio_header->nSamplesPerSec; - - if( Avi->audio_codec != WAVE_FORMAT_PCM ) - { - if( !AVI_ACMConvertAudio( Avi )) - { - Mem_Free( Avi->audio_header ); - Avi->audio_stream = NULL; - continue; - } - } - else Avi->audio_bytes_per_sample = Avi->audio_header->nBlockAlign; - Avi->audio_length *= Avi->audio_bytes_per_sample; - } - else - { - pAVIStreamRelease( stream ); - } - } - - // display error message-stream not found. - if( Avi->video_stream == NULL ) - { - if( Avi->pfile ) // if file is open, close it - pAVIFileRelease( Avi->pfile ); - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "couldn't find a valid video stream.\n" ); - return; - } - - pAVIFileRelease( Avi->pfile ); // release the file - Avi->video_getframe = pAVIStreamGetFrameOpen( Avi->video_stream, NULL ); // open the frame getter - - if( Avi->video_getframe == NULL ) - { - if( !Avi->quiet ) - Con_DPrintf( S_ERROR "error attempting to read video frames.\n" ); - return; // couldn't open frame getter. - } - - bmih.biSize = sizeof( BITMAPINFOHEADER ); - bmih.biPlanes = 1; - bmih.biBitCount = 32; - bmih.biCompression = BI_RGB; - bmih.biWidth = Avi->video_xres; - bmih.biHeight = -Avi->video_yres; // invert height to flip image upside down - - Avi->hDC = CreateCompatibleDC( 0 ); - Avi->hDD = pDrawDibOpen(); - Avi->hBitmap = CreateDIBSection( Avi->hDC, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&Avi->pframe_data), NULL, 0 ); - SelectObject( Avi->hDC, Avi->hBitmap ); - - Avi->active = true; // done -} - -qboolean AVI_IsActive( movie_state_t *Avi ) -{ - if( Avi != NULL ) - return Avi->active; - return false; -} - -movie_state_t *AVI_GetState( int num ) -{ - return &avi[num]; -} - -/* -============= -AVIKit user interface - -============= -*/ -movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) -{ - movie_state_t *Avi; - string path; - const char *fullpath; - - // fast reject - if( !avi_initialized ) - return NULL; - - // open cinematic - Q_snprintf( path, sizeof( path ), "media/%s", filename ); - COM_DefaultExtension( path, ".avi" ); - fullpath = FS_GetDiskPath( path, false ); - - if( FS_FileExists( path, false ) && !fullpath ) - { - Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path ); - return NULL; - } - - Avi = Mem_Malloc( cls.mempool, sizeof( movie_state_t )); - AVI_OpenVideo( Avi, fullpath, load_audio, false ); - - if( !AVI_IsActive( Avi )) - { - AVI_FreeVideo( Avi ); // something bad happens - return NULL; - } - - // all done - return Avi; -} - -void AVI_FreeVideo( movie_state_t *state ) -{ - if( !state ) return; - - if( Mem_IsAllocatedExt( cls.mempool, state )) - { - AVI_CloseVideo( state ); - Mem_Free( state ); - } -} - -qboolean AVI_Initailize( void ) -{ - if( Sys_CheckParm( "-noavi" )) - { - Con_Printf( "AVI: Disabled\n" ); - return false; - } - - if( !Sys_LoadLibrary( &avifile_dll )) - return false; - - if( !Sys_LoadLibrary( &msvfw_dll )) - { - Sys_FreeLibrary( &avifile_dll ); - return false; - } - - if( !Sys_LoadLibrary( &msacm_dll )) - { - Sys_FreeLibrary( &avifile_dll ); - Sys_FreeLibrary( &msvfw_dll ); - return false; - } - - avi_initialized = true; - pAVIFileInit(); - - return true; -} - -void AVI_Shutdown( void ) -{ - if( !avi_initialized ) return; - - pAVIFileExit(); - - Sys_FreeLibrary( &avifile_dll ); - Sys_FreeLibrary( &msvfw_dll ); - Sys_FreeLibrary( &msacm_dll ); - avi_initialized = false; -} -#endif // _WIN32 diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c index fcc3f3cc..f767c772 100644 --- a/engine/client/cl_cmds.c +++ b/engine/client/cl_cmds.c @@ -42,7 +42,8 @@ void CL_PlayVideo_f( void ) switch( Cmd_Argc( )) { case 2: // simple user version - Q_snprintf( path, sizeof( path ), "media/%s.avi", Cmd_Argv( 1 )); + Q_snprintf( path, sizeof( path ), "media/%s", Cmd_Argv( 1 )); + COM_DefaultExtension( path, ".avi", sizeof( path )); SCR_PlayCinematic( path ); break; case 3: // sequenced cinematics used this @@ -85,6 +86,12 @@ void CL_PlayCDTrack_f( void ) paused = false; looped = false; } + else if( !Q_stricmp( command, "playfile" )) + { + S_StartBackgroundTrack( pszTrack, NULL, 0, true ); + paused = false; + looped = false; + } else if( !Q_stricmp( command, "loop" )) { if( Q_isdigit( pszTrack )) @@ -96,6 +103,12 @@ void CL_PlayCDTrack_f( void ) paused = false; looped = true; } + else if( !Q_stricmp( command, "loopfile" )) + { + S_StartBackgroundTrack( pszTrack, pszTrack, 0, true ); + paused = false; + looped = true; + } else if( !Q_stricmp( command, "pause" )) { S_StreamSetPause( true ); @@ -134,49 +147,12 @@ void CL_PlayCDTrack_f( void ) if( paused ) Con_Printf( "Paused %s track %u\n", looped ? "looping" : "playing", track ); else Con_Printf( "Currently %s track %u\n", looped ? "looping" : "playing", track ); } - Con_Printf( "Volume is %f\n", Cvar_VariableValue( "MP3Volume" )); + Con_Printf( "Volume is %f\n", s_musicvolume.value ); return; } else Con_Printf( "%s: unknown command %s\n", Cmd_Argv( 0 ), command ); } -/* -================== -CL_ScreenshotGetName -================== -*/ -qboolean CL_ScreenshotGetName( int lastnum, char *filename ) -{ - if( lastnum < 0 || lastnum > 9999 ) - { - Con_Printf( S_ERROR "unable to write screenshot\n" ); - return false; - } - - Q_sprintf( filename, "scrshots/%s_shot%04d.png", clgame.mapname, lastnum ); - - return true; -} - -/* -================== -CL_SnapshotGetName -================== -*/ -qboolean CL_SnapshotGetName( int lastnum, char *filename ) -{ - if( lastnum < 0 || lastnum > 9999 ) - { - Con_Printf( S_ERROR "unable to write snapshot\n" ); - FS_AllowDirectPaths( false ); - return false; - } - - Q_sprintf( filename, "../%s_%04d.png", clgame.mapname, lastnum ); - - return true; -} - /* ============================================================================== @@ -184,127 +160,6 @@ qboolean CL_SnapshotGetName( int lastnum, char *filename ) ============================================================================== */ -/* -================== -CL_ScreenShot_f - -normal screenshot -================== -*/ -void CL_ScreenShot_f( void ) -{ - int i; - string checkname; - - if( CL_IsDevOverviewMode() == 1 ) - { - // special case for write overview image and script file - Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); - cls.scrshot_action = scrshot_mapshot; // build new frame for mapshot - } - else - { - // scan for a free filename - for( i = 0; i < 9999; i++ ) - { - if( !CL_ScreenshotGetName( i, checkname )) - return; // no namespace - - if( !FS_FileExists( checkname, false )) - break; - } - - Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); - cls.scrshot_action = scrshot_normal; // build new frame for screenshot - } - - cls.envshot_vieworg = NULL; // no custom view - cls.envshot_viewsize = 0; -} - -/* -================== -CL_SnapShot_f - -save screenshots into root dir -================== -*/ -void CL_SnapShot_f( void ) -{ - int i; - string checkname; - - if( CL_IsDevOverviewMode() == 1 ) - { - // special case for write overview image and script file - Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); - cls.scrshot_action = scrshot_mapshot; // build new frame for mapshot - } - else - { - FS_AllowDirectPaths( true ); - - // scan for a free filename - for( i = 0; i < 9999; i++ ) - { - if( !CL_SnapshotGetName( i, checkname )) - return; // no namespace - - if( !FS_FileExists( checkname, false )) - break; - } - - FS_AllowDirectPaths( false ); - Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); - cls.scrshot_action = scrshot_snapshot; // build new frame for screenshot - } - - cls.envshot_vieworg = NULL; // no custom view - cls.envshot_viewsize = 0; -} - -/* -================== -CL_EnvShot_f - -cubemap view -================== -*/ -void CL_EnvShot_f( void ) -{ - if( Cmd_Argc() < 2 ) - { - Con_Printf( S_USAGE "envshot \n" ); - return; - } - - Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); - cls.scrshot_action = scrshot_envshot; // build new frame for envshot - cls.envshot_vieworg = NULL; // no custom view - cls.envshot_viewsize = 0; -} - -/* -================== -CL_SkyShot_f - -skybox view -================== -*/ -void CL_SkyShot_f( void ) -{ - if( Cmd_Argc() < 2 ) - { - Con_Printf( S_USAGE "skyshot \n" ); - return; - } - - Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); - cls.scrshot_action = scrshot_skyshot; // build new frame for skyshot - cls.envshot_vieworg = NULL; // no custom view - cls.envshot_viewsize = 0; -} - /* ================== CL_LevelShot_f @@ -323,7 +178,8 @@ void CL_LevelShot_f( void ) // check for exist if( cls.demoplayback && ( cls.demonum != -1 )) { - Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), + "levelshots/%s_%s.bmp", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ); Q_snprintf( filename, sizeof( filename ), "%s.dem", cls.demoname ); // make sure what levelshot is newer than demo @@ -332,7 +188,8 @@ void CL_LevelShot_f( void ) } else { - Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), + "levelshots/%s_%s.bmp", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ); // make sure what levelshot is newer than bsp ft1 = FS_FileTime( cl.worldmodel->name, false ); @@ -345,23 +202,109 @@ void CL_LevelShot_f( void ) else cls.scrshot_action = scrshot_inactive; // disable - not needs } -/* -================== -CL_SaveShot_f - -mini-pic in loadgame menu -================== -*/ -void CL_SaveShot_f( void ) +static scrshot_t CL_GetScreenshotTypeFromString( const char *string ) { - if( Cmd_Argc() < 2 ) + if( !Q_stricmp( string, "snapshot" )) + return scrshot_snapshot; + + if( !Q_stricmp( string, "screenshot" )) + return scrshot_normal; + + if( !Q_stricmp( string, "saveshot" )) + return scrshot_savegame; + + if( !Q_stricmp( string, "envshot" )) + return scrshot_envshot; + + if( !Q_stricmp( string, "skyshot" )) + return scrshot_skyshot; + + return scrshot_inactive; +} + +void CL_GenericShot_f( void ) +{ + const char *argv0 = Cmd_Argv( 0 ); + scrshot_t type; + + type = CL_GetScreenshotTypeFromString( argv0 ); + + if( type == scrshot_normal || type == scrshot_snapshot ) { - Con_Printf( S_USAGE "saveshot \n" ); - return; + if( CL_IsDevOverviewMode() == 1 ) + type = scrshot_mapshot; + } + else + { + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "%s \n", argv0 ); + return; + } } - Q_sprintf( cls.shotname, DEFAULT_SAVE_DIRECTORY "%s.bmp", Cmd_Argv( 1 )); - cls.scrshot_action = scrshot_savegame; // build new frame for saveshot + switch( type ) + { + case scrshot_envshot: + case scrshot_skyshot: + Q_snprintf( cls.shotname, sizeof( cls.shotname ), "gfx/env/%s", Cmd_Argv( 1 )); + break; + case scrshot_savegame: + Q_snprintf( cls.shotname, sizeof( cls.shotname ), DEFAULT_SAVE_DIRECTORY "%s.bmp", Cmd_Argv( 1 )); + break; + case scrshot_mapshot: + Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); + break; + case scrshot_normal: + case scrshot_snapshot: + { + string checkname; + int i; + + // allow overriding screenshot by users request + if( Cmd_Argc() > 1 ) + { + Q_strncpy( cls.shotname, Cmd_Argv( 1 ), sizeof( cls.shotname )); + break; + } + + if( type == scrshot_snapshot ) + FS_AllowDirectPaths( true ); + + for( i = 0; i < 9999; i++ ) + { + int ret; + + if( type == scrshot_snapshot ) + ret = Q_snprintf( checkname, sizeof( checkname ), "../%s_%04d.png", clgame.mapname, i ); + else + ret = Q_snprintf( checkname, sizeof( checkname ), "scrshots/%s_shot%04d.png", clgame.mapname, i ); + + if( ret <= 0 ) + { + Con_Printf( S_ERROR "unable to write %s\n", argv0 ); + FS_AllowDirectPaths( false ); + return; + } + + if( !FS_FileExists( checkname, true )) + break; + } + + FS_AllowDirectPaths( false ); + + Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); + break; + } + case scrshot_inactive: + case scrshot_plaque: + default: + return; // shouldn't happen + } + + cls.scrshot_action = type; // build new frame for saveshot + cls.envshot_vieworg = NULL; + cls.envshot_viewsize = 0; } /* @@ -403,7 +346,7 @@ void CL_SetSky_f( void ) return; } - ref.dllFuncs.R_SetupSky( Cmd_Argv( 1 )); + R_SetupSky( Cmd_Argv( 1 )); } /* diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index b279b361..73fb45f7 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -50,7 +50,7 @@ qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) if( !cl_allow_download.value ) { - Con_Reportf( "Download refused, cl_allow_download is 0\n" ); + Con_Reportf( "Download refused, cl_allowdownload is 0\n" ); return true; } @@ -70,17 +70,18 @@ qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) return true; } - if( cl.downloadUrl[0] ) - { - HTTP_AddDownload( filepath, pResource->nDownloadSize, true ); - host.downloadcount++; - return false; - } - - MSG_BeginClientCmd( msg, clc_stringcmd ); - MSG_WriteString( msg, va( "dlfile %s", filepath )); host.downloadcount++; + if( cl.http_download ) + { + HTTP_AddDownload( filepath, pResource->nDownloadSize, true, pResource ); + } + else + { + MSG_BeginClientCmd( msg, clc_stringcmd ); + MSG_WriteStringf( msg, "dlfile %s", filepath ); + } + return false; } @@ -104,7 +105,7 @@ void CL_AddToResourceList( resource_t *pResource, resource_t *pList ) void CL_RemoveFromResourceList( resource_t *pResource ) { if( pResource->pPrev == NULL || pResource->pNext == NULL ) - Host_Error( "mislinked resource in CL_RemoveFromResourceList\n" ); + Host_Error( "mislinked resource in %s\n", __func__ ); if( pResource->pNext == pResource || pResource->pPrev == pResource ) Host_Error( "attempt to free last entry in list.\n" ); @@ -119,7 +120,7 @@ void CL_MoveToOnHandList( resource_t *pResource ) { if( !pResource ) { - Con_Reportf( "Null resource passed to CL_MoveToOnHandList\n" ); + Con_Reportf( "Null resource passed to %s\n", __func__ ); return; } @@ -127,7 +128,7 @@ void CL_MoveToOnHandList( resource_t *pResource ) CL_AddToResourceList( pResource, &cl.resourcesonhand ); } -void CL_ClearResourceList( resource_t *pList ) +static void CL_ClearResourceList( resource_t *pList ) { resource_t *p, *n; diff --git a/engine/client/cl_debug.c b/engine/client/cl_debug.c index 7b019daa..4e1c4059 100644 --- a/engine/client/cl_debug.c +++ b/engine/client/cl_debug.c @@ -25,70 +25,6 @@ GNU General Public License for more details. #define MSG_COUNT 32 // last 32 messages parsed #define MSG_MASK (MSG_COUNT - 1) -const char *svc_strings[svc_lastmsg+1] = -{ - "svc_bad", - "svc_nop", - "svc_disconnect", - "svc_event", - "svc_changing", - "svc_setview", - "svc_sound", - "svc_time", - "svc_print", - "svc_stufftext", - "svc_setangle", - "svc_serverdata", - "svc_lightstyle", - "svc_updateuserinfo", - "svc_deltatable", - "svc_clientdata", - "svc_resource", - "svc_pings", - "svc_particle", - "svc_restoresound", - "svc_spawnstatic", - "svc_event_reliable", - "svc_spawnbaseline", - "svc_temp_entity", - "svc_setpause", - "svc_signonnum", - "svc_centerprint", - "svc_unused27", - "svc_unused28", - "svc_unused29", - "svc_intermission", - "svc_finale", - "svc_cdtrack", - "svc_restore", - "svc_cutscene", - "svc_weaponanim", - "svc_bspdecal", - "svc_roomtype", - "svc_addangle", - "svc_usermessage", - "svc_packetentities", - "svc_deltapacketentities", - "svc_choke", - "svc_resourcelist", - "svc_deltamovevars", - "svc_resourcerequest", - "svc_customization", - "svc_crosshairangle", - "svc_soundfade", - "svc_filetxferfailed", - "svc_hltv", - "svc_director", - "svc_voiceinit", - "svc_voicedata", - "svc_deltapacketbones", - "svc_unused55", - "svc_resourcelocation", - "svc_querycvarvalue", - "svc_querycvarvalue2", - "svc_exec", -}; - typedef struct { int command; @@ -109,12 +45,34 @@ const char *CL_MsgInfo( int cmd ) { static string sz; - Q_strcpy( sz, "???" ); + Q_strncpy( sz, "???", sizeof( sz )); if( cmd >= 0 && cmd <= svc_lastmsg ) { // get engine message name - Q_strncpy( sz, svc_strings[cmd], sizeof( sz )); + const char *svc_string = NULL; + + switch( cls.legacymode ) + { + case PROTO_CURRENT: + svc_string = svc_strings[cmd]; + break; + case PROTO_LEGACY: + svc_string = svc_legacy_strings[cmd]; + break; + case PROTO_QUAKE: + svc_string = svc_quake_strings[cmd]; + break; + case PROTO_GOLDSRC: + svc_string = svc_goldsrc_strings[cmd]; + break; + } + + // fall back to current protocol strings + if( !svc_string ) + svc_string = svc_strings[cmd]; + + Q_strncpy( sz, svc_string, sizeof( sz )); } else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) { @@ -195,6 +153,7 @@ static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) FS_Write( fp, &cls.starting_count, sizeof( int )); FS_Write( fp, ¤t_count, sizeof( int )); + FS_Write( fp, &cls.legacymode, sizeof( cls.legacymode )); FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); FS_Close( fp ); @@ -210,7 +169,7 @@ list last 32 messages for debugging net troubleshooting */ void CL_WriteMessageHistory( void ) { - oldcmd_t *old, *failcommand; + oldcmd_t *old; sizebuf_t *msg = &net_message; int i, thecmd; @@ -234,9 +193,49 @@ void CL_WriteMessageHistory( void ) thecmd++; } - failcommand = &cls_message_debug.oldcmd[thecmd]; - Con_Printf( "BAD: %3i:%s\n", MSG_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command )); - if( host_developer.value >= DEV_EXTENDED ) - CL_WriteErrorMessage( MSG_GetNumBytesRead( msg ) - 1, msg ); + old = &cls_message_debug.oldcmd[thecmd]; + Con_Printf( S_RED "BAD: " S_DEFAULT "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); + CL_WriteErrorMessage( old->starting_offset, msg ); cls_message_debug.parsing = false; } + +void CL_ReplayBufferDat_f( void ) +{ + file_t *f = FS_Open( Cmd_Argv( 1 ), "rb", true ); + sizebuf_t msg; + char buffer[NET_MAX_MESSAGE]; + int starting_count, current_count, protocol; + fs_offset_t len; + + if( !f ) + return; + + FS_Read( f, &starting_count, sizeof( starting_count )); + FS_Read( f, ¤t_count, sizeof( current_count )); + FS_Read( f, &protocol, sizeof( protocol )); + + cls.legacymode = protocol; + + len = FS_Read( f, buffer, sizeof( buffer )); + FS_Close( f ); + + MSG_Init( &msg, __func__, buffer, len ); + + Delta_Shutdown(); + Delta_Init(); + + clgame.maxEntities = MAX_EDICTS; + clgame.entities = Mem_Calloc( clgame.mempool, sizeof( *clgame.entities ) * clgame.maxEntities ); + + // ad-hoc implement +#if 0 + { + const int message_pos = 12; // put real number here + MSG_SeekToBit( &msg, ( message_pos - 12 + 1 ) << 3, SEEK_SET ); + + CL_ParseYourMom( &msg, protocol ); + } +#endif + + Sys_Quit( __func__ ); +} diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 92ebdd2b..10b76b73 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -40,6 +40,8 @@ GNU General Public License for more details. #define IDEMOHEADER (('M'<<24)+('E'<<16)+('D'<<8)+'I') // little-endian "IDEM" #define DEMO_PROTOCOL 3 +#define PROTOCOL_GOLDSRC_VERSION_DEMO (PROTOCOL_GOLDSRC_VERSION | (BIT( 7 ))) // should be 48, only to differentiate it from PROTOCOL_LEGACY_VERSION + const char *demo_cmd[dem_lastcmd+1] = { "dem_unknown", @@ -79,7 +81,7 @@ typedef struct typedef struct { demoentry_t *entries; // track entry info - int numentries; // number of tracks + int32_t numentries; // number of tracks } demodirectory_t; // add angles @@ -107,6 +109,42 @@ struct int angle_position; } demo; +static qboolean CL_NextDemo( void ); + +static int CL_GetDemoNetProtocol( connprotocol_t proto ) +{ + switch( proto ) + { + case PROTO_CURRENT: + return PROTOCOL_VERSION; + case PROTO_LEGACY: + return PROTOCOL_LEGACY_VERSION; + case PROTO_QUAKE: + return PROTOCOL_VERSION_QUAKE; + case PROTO_GOLDSRC: + return PROTOCOL_GOLDSRC_VERSION_DEMO; + } + + return PROTOCOL_VERSION; +} + +static connprotocol_t CL_GetProtocolFromDemo( int net_protocol ) +{ + switch( net_protocol ) + { + case PROTOCOL_VERSION: + return PROTO_CURRENT; + case PROTOCOL_LEGACY_VERSION: + return PROTO_LEGACY; + case PROTOCOL_VERSION_QUAKE: + return PROTO_QUAKE; + case PROTOCOL_GOLDSRC_VERSION_DEMO: + return PROTO_GOLDSRC; + } + + return PROTO_CURRENT; +} + /* ==================== CL_StartupDemoHeader @@ -117,13 +155,9 @@ we record a demo on this level */ void CL_StartupDemoHeader( void ) { - if( cls.demoheader ) - { - FS_Close( cls.demoheader ); - } + CL_CloseDemoHeader(); - // Note: this is replacing tmpfile() - cls.demoheader = FS_Open( "demoheader.tmp", "w+b", true ); + cls.demoheader = FS_Open( "demoheader.tmp", "w+bm", true ); if( !cls.demoheader ) { @@ -156,7 +190,7 @@ CL_GetDemoRecordClock write time while demo is recording ==================== */ -float CL_GetDemoRecordClock( void ) +static float CL_GetDemoRecordClock( void ) { return cl.mtime[0]; } @@ -168,7 +202,7 @@ CL_GetDemoPlaybackClock overwrite host.realtime ==================== */ -float CL_GetDemoPlaybackClock( void ) +static float CL_GetDemoPlaybackClock( void ) { return host.realtime + host.frametime; } @@ -184,7 +218,26 @@ double CL_GetDemoFramerate( void ) { if( cls.timedemo ) return 0.0; - return bound( MIN_FPS, demo.header.host_fps, MAX_FPS ); + return bound( MIN_FPS, demo.header.host_fps, MAX_FPS_HARD ); +} + +/* +================= +CL_DemoAborted +================= +*/ +static void CL_DemoAborted( void ) +{ + if( cls.demofile ) + FS_Close( cls.demofile ); + cls.demoplayback = false; + cls.changedemo = false; + cls.timedemo = false; + demo.framecount = 0; + cls.demofile = NULL; + cls.demonum = -1; + + Cvar_DirectSet( &v_dark, "0" ); } /* @@ -194,7 +247,7 @@ CL_WriteDemoCmdHeader Writes the demo command header and time-delta ==================== */ -void CL_WriteDemoCmdHeader( byte cmd, file_t *file ) +static void CL_WriteDemoCmdHeader( byte cmd, file_t *file ) { float dt; @@ -251,7 +304,7 @@ void CL_WriteDemoUserCmd( int cmdnumber ) // write usercmd_t MSG_Init( &buf, "UserCmd", data, sizeof( data )); - CL_WriteUsercmd( &buf, -1, cmdnumber ); // always no delta + CL_WriteUsercmd( PROTO_CURRENT, &buf, -1, cmdnumber ); // always no delta, always in current protocol bytes = MSG_GetNumBytesWritten( &buf ); @@ -267,7 +320,7 @@ Save state of cls.netchan sequences so that we can play the demo correctly. ==================== */ -void CL_WriteDemoSequence( file_t *file ) +static void CL_WriteDemoSequence( file_t *file ) { Assert( file != NULL ); @@ -295,10 +348,6 @@ void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ) if( !file ) return; - // past the start but not recording a demo. - if( !startup && !cls.demorecording ) - return; - swlen = MSG_GetNumBytesWritten( msg ) - start; if( swlen <= 0 ) return; @@ -324,7 +373,7 @@ CL_WriteDemoUserMessage Dumps the user message (demoaction) ==================== */ -void CL_WriteDemoUserMessage( const byte *buffer, size_t size ) +void GAME_EXPORT CL_WriteDemoUserMessage( int size, byte *buffer ) { if( !cls.demorecording || cls.demowaiting ) return; @@ -348,11 +397,12 @@ CL_WriteDemoHeader Write demo header ==================== */ -void CL_WriteDemoHeader( const char *name ) +static void CL_WriteDemoHeader( const char *name ) { - int copysize; - int savepos; - int curpos; + double maxfps; + int copysize; + int savepos; + int curpos; Con_Printf( "recording to %s.\n", name ); cls.demofile = FS_Open( name, "wb", false ); @@ -367,12 +417,14 @@ void CL_WriteDemoHeader( const char *name ) cls.demorecording = true; cls.demowaiting = true; // don't start saving messages until a non-delta compressed message is received + maxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT; + memset( &demo.header, 0, sizeof( demo.header )); demo.header.id = IDEMOHEADER; demo.header.dem_protocol = DEMO_PROTOCOL; - demo.header.net_protocol = cls.legacymode ? PROTOCOL_LEGACY_VERSION : PROTOCOL_VERSION; - demo.header.host_fps = bound( MIN_FPS, host_maxfps->value, MAX_FPS ); + demo.header.net_protocol = CL_GetDemoNetProtocol( cls.legacymode ); + demo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps; Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname )); Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment )); Q_strncpy( demo.header.gamedir, FS_Gamedir(), sizeof( demo.header.gamedir )); @@ -391,6 +443,7 @@ void CL_WriteDemoHeader( const char *name ) // finish off the startup info. CL_WriteDemoCmdHeader( dem_stop, cls.demoheader ); + FS_Flush( cls.demoheader ); // now copy the stuff we cached from the server. copysize = savepos = FS_Tell( cls.demoheader ); @@ -436,7 +489,7 @@ CL_StopRecord finish recording demo ================= */ -void CL_StopRecord( void ) +static void CL_StopRecord( void ) { int i, curpos; float stoptime; @@ -518,14 +571,26 @@ CL_ReadDemoCmdHeader read the demo command ================= */ -void CL_ReadDemoCmdHeader( byte *cmd, float *dt ) +static qboolean CL_ReadDemoCmdHeader( byte *cmd, float *dt ) { // read the command - FS_Read( cls.demofile, cmd, sizeof( byte )); - Assert( *cmd >= 1 && *cmd <= dem_lastcmd ); + // HACKHACK: skip NOPs + do + { + FS_Read( cls.demofile, cmd, sizeof( byte )); + } while( *cmd == dem_unknown ); + + if( *cmd > dem_lastcmd ) + { + Con_Printf( S_ERROR "Demo cmd %d > %d, file offset = %d\n", *cmd, dem_lastcmd, (int)FS_Tell( cls.demofile )); + CL_DemoCompleted(); + return false; + } // read the timestamp FS_Read( cls.demofile, dt, sizeof( float )); + + return true; } /* @@ -536,7 +601,7 @@ read the demo usercmd for predicting and smooth movement during playback the demo ================= */ -void CL_ReadDemoUserCmd( qboolean discard ) +static void CL_ReadDemoUserCmd( qboolean discard ) { byte data[1024]; int cmdnumber; @@ -547,17 +612,27 @@ void CL_ReadDemoUserCmd( qboolean discard ) FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); FS_Read( cls.demofile, &cmdnumber, sizeof( int )); FS_Read( cls.demofile, &bytes, sizeof( short )); + + if( bytes >= sizeof( data )) + { + Con_Printf( S_ERROR "%s: too large dem_usercmd (size %u seq %i)\n", __func__, bytes, outgoing_sequence ); + CL_DemoAborted(); + return; + } + FS_Read( cls.demofile, data, bytes ); if( !discard ) { - usercmd_t nullcmd; + const usercmd_t nullcmd = { 0 }; sizebuf_t buf; demoangle_t *a; - memset( &nullcmd, 0, sizeof( nullcmd )); MSG_Init( &buf, "UserCmd", data, sizeof( data )); + // a1ba: I have no proper explanation why + cmdnumber++; + pcmd = &cl.commands[cmdnumber & CL_UPDATE_MASK]; pcmd->processedfuncs = false; pcmd->senttime = 0.0f; @@ -567,9 +642,7 @@ void CL_ReadDemoUserCmd( qboolean discard ) pcmd->sendsize = 1; // always delta'ing from null - cl.cmd = &pcmd->cmd; - - MSG_ReadDeltaUsercmd( &buf, &nullcmd, cl.cmd ); + MSG_ReadDeltaUsercmd( &buf, &nullcmd, &pcmd->cmd ); // make sure what interp info contain angles from different frames // or lerping will stop working @@ -581,13 +654,16 @@ void CL_ReadDemoUserCmd( qboolean discard ) // record update a->starttime = demo.timestamp; - VectorCopy( cl.cmd->viewangles, a->viewangles ); + VectorCopy( pcmd->cmd.viewangles, a->viewangles ); demo.lasttime = demo.timestamp; } // NOTE: we need to have the current outgoing sequence correct // so we can do prediction correctly during playback cls.netchan.outgoing_sequence = outgoing_sequence; + + // save last usercmd + cl.cmd = pcmd->cmd; } } @@ -598,7 +674,7 @@ CL_ReadDemoSequence read netchan sequences ================= */ -void CL_ReadDemoSequence( qboolean discard ) +static void CL_ReadDemoSequence( qboolean discard ) { int incoming_sequence; int incoming_acknowledged; @@ -632,21 +708,23 @@ void CL_ReadDemoSequence( qboolean discard ) CL_DemoStartPlayback ================= */ -void CL_DemoStartPlayback( int mode ) +static void CL_DemoStartPlayback( int mode ) { if( cls.changedemo ) { + int maxclients = cl.maxclients; + S_StopAllSounds( true ); SCR_BeginLoadingPlaque( false ); - CL_ClearState (); - CL_InitEdicts (); // re-arrange edicts + CL_ClearState( ); + CL_InitEdicts( maxclients ); // re-arrange edicts } else { // NOTE: at this point demo is still valid CL_Disconnect(); - Host_ShutdownServer(); + SV_Shutdown( "Server was killed due to demo playback start\n" ); Con_FastClose(); UI_SetActiveMenu( false ); @@ -660,7 +738,7 @@ void CL_DemoStartPlayback( int mode ) demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message - Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); + CL_SetupNetchanForProtocol( cls.legacymode ); memset( demo.cmds, 0, sizeof( demo.cmds )); demo.angle_position = 1; @@ -670,25 +748,6 @@ void CL_DemoStartPlayback( int mode ) cl.last_command_ack = -1; } -/* -================= -CL_DemoAborted -================= -*/ -void CL_DemoAborted( void ) -{ - if( cls.demofile ) - FS_Close( cls.demofile ); - cls.demoplayback = false; - cls.changedemo = false; - cls.timedemo = false; - demo.framecount = 0; - cls.demofile = NULL; - cls.demonum = -1; - - Cvar_SetValue( "v_dark", 0.0f ); -} - /* ================= CL_DemoCompleted @@ -704,7 +763,7 @@ void CL_DemoCompleted( void ) if( !CL_NextDemo() && !cls.changedemo ) UI_SetActiveMenu( true ); - Cvar_SetValue( "v_dark", 0.0f ); + Cvar_DirectSet( &v_dark, "0" ); } /* @@ -715,7 +774,7 @@ returns true on success, false on failure g-cont. probably captain obvious mode is ON ================= */ -qboolean CL_DemoMoveToNextSection( void ) +static qboolean CL_DemoMoveToNextSection( void ) { if( ++demo.entryIndex >= demo.directory.numentries ) { @@ -737,7 +796,7 @@ qboolean CL_DemoMoveToNextSection( void ) return true; } -qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length ) +static qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length ) { int msglen = 0; @@ -788,7 +847,7 @@ CL_DemoReadMessageQuake reads demo data and write it to client ================= */ -qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length ) +static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length ) { vec3_t viewangles; int msglen = 0; @@ -913,7 +972,8 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) if( !cls.demofile ) break; curpos = FS_Tell( cls.demofile ); - CL_ReadDemoCmdHeader( &cmd, &demo.timestamp ); + if( !CL_ReadDemoCmdHeader( &cmd, &demo.timestamp )) + return false; fElapsedTime = CL_GetDemoPlaybackClock() - demo.starttime; if( !cls.timedemo ) bSkipMessage = ((demo.timestamp - cl_serverframetime()) >= fElapsedTime) ? true : false; @@ -1002,7 +1062,7 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) return CL_ReadRawNetworkData( buffer, length ); } -void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next ) +static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next ) { int i, i0, i1, imod; float at; @@ -1086,8 +1146,7 @@ void CL_DemoInterpolateAngles( void ) QuaternionSlerp( q2, q1, frac, q ); QuaternionAngle( q, cl.viewangles ); } - else if( cl.cmd != NULL ) - VectorCopy( cl.cmd->viewangles, cl.viewangles ); + else VectorCopy( cl.cmd.viewangles, cl.viewangles ); } /* @@ -1097,8 +1156,9 @@ CL_FinishTimeDemo show stats ============== */ -void CL_FinishTimeDemo( void ) +static void CL_FinishTimeDemo( void ) { + qboolean temp = host.allow_console; int frames; double time; @@ -1109,7 +1169,12 @@ void CL_FinishTimeDemo( void ) time = host.realtime - cls.td_starttime; if( !time ) time = 1.0; - Con_Printf( "%i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); + host.allow_console = true; + Con_Printf( "timedemo result: %i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); + host.allow_console = temp; + + if( Sys_CheckParm( "-timedemo" )) + CL_Quit_f(); } /* @@ -1243,7 +1308,7 @@ CL_NextDemo Called when a demo finishes ================== */ -qboolean CL_NextDemo( void ) +static qboolean CL_NextDemo( void ) { char str[MAX_QPATH]; @@ -1293,7 +1358,7 @@ void CL_CheckStartupDemos( void ) } // run demos loop in background mode - Cvar_SetValue( "v_dark", 1.0f ); + Cvar_DirectSet( &v_dark, "1" ); cls.demos_pending = false; cls.demonum = 0; CL_NextDemo (); @@ -1304,16 +1369,16 @@ void CL_CheckStartupDemos( void ) CL_DemoGetName ================== */ -static void CL_DemoGetName( int lastnum, char *filename ) +static void CL_DemoGetName( int lastnum, char *filename, size_t size ) { if( lastnum < 0 || lastnum > 9999 ) { // bound - Q_strcpy( filename, "demo9999" ); + Q_strncpy( filename, "demo9999", size ); return; } - Q_sprintf( filename, "demo%04d", lastnum ); + Q_snprintf( filename, size, "demo%04d", lastnum ); } /* @@ -1367,8 +1432,10 @@ void CL_Record_f( void ) // scan for a free filename for( n = 0; n < 10000; n++ ) { - CL_DemoGetName( n, demoname ); - if( !FS_FileExists( va( "%s.dem", demoname ), true )) + CL_DemoGetName( n, demoname, sizeof( demoname )); + Q_snprintf( demopath, sizeof( demopath ), "%s.dem", demoname ); + + if( !FS_FileExists( demopath, true )) break; } @@ -1381,7 +1448,7 @@ void CL_Record_f( void ) else Q_strncpy( demoname, name, sizeof( demoname )); // open the demo file - Q_sprintf( demopath, "%s.dem", demoname ); + Q_snprintf( demopath, sizeof( demopath ), "%s.dem", demoname ); // make sure that old demo is removed if( FS_FileExists( demopath, false )) @@ -1393,6 +1460,51 @@ void CL_Record_f( void ) CL_WriteDemoHeader( demopath ); } +static qboolean CL_ParseDemoHeader( const char *callee, const char *filename, file_t *f, demoheader_t *hdr, int32_t *numentries ) +{ + if( FS_Read( f, hdr, sizeof( *hdr )) != sizeof( *hdr ) || hdr->id != IDEMOHEADER ) + { + Con_Printf( S_ERROR "%s: %s is not in supported format or not a demo file\n", callee, filename ); + return false; + } + + // force null terminate strings + hdr->mapname[sizeof( hdr->mapname ) - 1] = 0; + hdr->comment[sizeof( hdr->comment ) - 1] = 0; + hdr->gamedir[sizeof( hdr->gamedir ) - 1] = 0; + + if( hdr->dem_protocol != DEMO_PROTOCOL ) + { + Con_Printf( S_ERROR "%s: demo protocol outdated (%i should be %i)\n", + callee, hdr->net_protocol, DEMO_PROTOCOL ); + return false; + } + + if( hdr->net_protocol != PROTOCOL_VERSION && hdr->net_protocol != PROTOCOL_LEGACY_VERSION && hdr->net_protocol != PROTOCOL_GOLDSRC_VERSION_DEMO ) + { + Con_Printf( S_ERROR "%s: net protocol outdated (%i should be %i or %i)\n", + callee, hdr->net_protocol, PROTOCOL_VERSION, PROTOCOL_LEGACY_VERSION ); + return false; + } + + if( FS_Seek( f, hdr->directory_offset, SEEK_SET ) < 0 + || FS_Read( f, numentries, sizeof( *numentries )) != sizeof( *numentries )) + { + Con_Printf( S_ERROR "%s: can't find directory offset in %s, demo file corrupted\n", + callee, filename ); + return false; + } + + if(( *numentries < 1 ) || ( *numentries > 1024 )) + { + Con_Printf( S_ERROR "%s: demo have bogus # of directory entries: %i\n", + callee, *numentries ); + return false; + } + + return true; +} + /* ==================== CL_PlayDemo_f @@ -1408,14 +1520,12 @@ void CL_PlayDemo_f( void ) if( Cmd_Argc() < 2 ) { - Con_Printf( S_USAGE "playdemo \n" ); + Con_Printf( S_USAGE "%s \n", Cmd_Argv( 0 )); return; } if( cls.demoplayback ) - { CL_StopPlayback(); - } if( cls.demorecording ) { @@ -1455,7 +1565,7 @@ void CL_PlayDemo_f( void ) { int c, neg = false; - demo.header.host_fps = host_maxfps->value; + demo.header.host_fps = host_maxfps.value; while(( c = FS_Getc( cls.demofile )) != '\n' ) { @@ -1465,51 +1575,32 @@ void CL_PlayDemo_f( void ) if( neg ) cls.forcetrack = -cls.forcetrack; CL_DemoStartPlayback( DEMO_QUAKE1 ); + cls.legacymode = PROTO_QUAKE; return; // quake demo is started } // read in the demo header - FS_Read( cls.demofile, &demo.header, sizeof( demoheader_t )); - - if( demo.header.id != IDEMOHEADER ) + if( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, cls.demofile, &demo.header, &demo.directory.numentries )) { - Con_Printf( S_ERROR "%s is not a demo file\n", demoname ); - CL_DemoAborted(); - return; - } - - if( demo.header.dem_protocol != DEMO_PROTOCOL ) - { - Con_Printf( S_ERROR "playdemo: demo protocol outdated (%i should be %i)\n", demo.header.dem_protocol, DEMO_PROTOCOL ); - CL_DemoAborted(); - return; - } - - if( demo.header.net_protocol != PROTOCOL_VERSION && - demo.header.net_protocol != PROTOCOL_LEGACY_VERSION ) - { - Con_Printf( S_ERROR "playdemo: net protocol outdated (%i should be %i)\n", demo.header.net_protocol, PROTOCOL_VERSION ); - CL_DemoAborted(); - return; - } - - // now read in the directory structure. - FS_Seek( cls.demofile, demo.header.directory_offset, SEEK_SET ); - FS_Read( cls.demofile, &demo.directory.numentries, sizeof( int )); - - if( demo.directory.numentries < 1 || demo.directory.numentries > 1024 ) - { - Con_Printf( S_ERROR "demo had bogus # of directory entries: %i\n", demo.directory.numentries ); CL_DemoAborted(); return; } // allocate demo entries - demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); + demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( *demo.directory.entries ) * demo.directory.numentries ); for( i = 0; i < demo.directory.numentries; i++ ) { - FS_Read( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); + demoentry_t *entry = &demo.directory.entries[i]; + + if( FS_Read( cls.demofile, entry, sizeof( *entry )) != sizeof( *entry )) + { + Con_Printf( S_ERROR "%s: demo entry %i of %s corrupted", Cmd_Argv( 0 ), i, filename ); + CL_DemoAborted(); + return; + } + + entry->description[sizeof( entry->description ) - 1] = 0; } demo.entryIndex = 0; @@ -1519,9 +1610,11 @@ void CL_PlayDemo_f( void ) CL_DemoStartPlayback( DEMO_XASH3D ); + // must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol + cls.legacymode = CL_GetProtocolFromDemo( demo.header.net_protocol ); + // g-cont. is this need? Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); - cls.legacymode = demo.header.net_protocol == PROTOCOL_LEGACY_VERSION; // begin a playback demo } @@ -1535,12 +1628,6 @@ timedemo */ void CL_TimeDemo_f( void ) { - if( Cmd_Argc() != 2 ) - { - Con_Printf( S_USAGE "timedemo \n" ); - return; - } - CL_PlayDemo_f (); // cls.td_starttime will be grabbed at the second frame of the demo, so @@ -1569,7 +1656,7 @@ void CL_StartDemos_f( void ) c = Cmd_Argc() - 1; if( c > MAX_DEMOS ) { - Con_DPrintf( S_WARN "Host_StartDemos: max %i demos in demoloop\n", MAX_DEMOS ); + Con_DPrintf( S_WARN "%s: max %i demos in demoloop\n", __func__, MAX_DEMOS ); c = MAX_DEMOS; } @@ -1627,3 +1714,78 @@ void CL_Stop_f( void ) S_StopBackgroundTrack(); } } + +void CL_ListDemo_f( void ) +{ + demoheader_t hdr; + int32_t num_entries; + file_t *f; + char filename[MAX_QPATH]; + char demoname[MAX_QPATH]; + int i; + + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "%s \n", Cmd_Argv( 0 )); + return; + } + + Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname )); + COM_StripExtension( demoname ); + Q_snprintf( filename, sizeof( filename ), "%s.dem", demoname ); + + f = FS_Open( filename, "rb", true ); + if( !f ) + { + Con_Printf( S_ERROR "couldn't open %s\n", filename ); + return; + } + + if( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, f, &hdr, &num_entries )) + { + FS_Close( f ); + return; + } + + Con_Printf( "Demo contents for %s:\n" + "\tProtocol: %i net/%i demo\n" + "\tFPS: %g\n" + "\tMap: %s\n" + "\tComment: %s\n" + "\tGame: %s\n", + filename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname, + hdr.comment, hdr.gamedir ); + + for( i = 0; i < num_entries; i++ ) + { + demoentry_t entry; + + Con_Printf( "Demo entry #%i:\n", i ); + + if( FS_Read( f, &entry, sizeof( entry )) != sizeof( entry )) + { + Con_Printf( S_ERROR "can't read demo entry\n" ); + FS_Close( f ); + return; + } + + entry.description[sizeof( entry.description ) - 1] = 0; + + if( entry.entrytype == DEMO_STARTUP ) + { + // startup entries don't have anything useful + Con_Printf( "\tEntry type: " S_YELLOW "startup" S_DEFAULT "\n" ); + } + else + { + Con_Printf( "\tEntry type: " S_GREEN "normal" S_DEFAULT " (%i)\n" + "\tEntry playback time/frames: %.2f seconds/%i frames\n" + "\tEntry flags: 0x%x\n" + "\tEntry description: %s\n", + entry.entrytype, entry.playback_time, entry.playback_frames, + entry.flags, entry.description ); + } + } + + FS_Close( f ); +} diff --git a/engine/client/cl_efrag.c b/engine/client/cl_efrag.c index 366134a2..4a9f4602 100644 --- a/engine/client/cl_efrag.c +++ b/engine/client/cl_efrag.c @@ -28,49 +28,50 @@ GNU General Public License for more details. =============================================================================== */ +#define NUM_EFRAGS_ALLOC 64 // alloc 64 efrags (1-2kb each alloc) + static efrag_t **lastlink; static mnode_t *r_pefragtopnode; static vec3_t r_emins, r_emaxs; static cl_entity_t *r_addent; +static int cl_efrags_num; +static efrag_t *cl_efrags; + +static efrag_t *CL_AllocEfrags( int num ) +{ + int i; + efrag_t *efrags; + + if( !cl.worldmodel ) + { + Host_Error( "%s: called with NULL world\n", __func__ ); + return NULL; + } + + if( num == 0 ) + return NULL; + + // set world to be the owner, so it will get automatically cleaned up + efrags = Mem_Calloc( cl.worldmodel->mempool, sizeof( *efrags ) * num ); + + // initialize linked list + for( i = 0; i < num - 1; i++ ) + efrags[i].entnext = &efrags[i + 1]; + + cl_efrags_num += num; + + return efrags; +} /* -================ -R_RemoveEfrags - -Call when removing an object from the world or moving it to another position -================ +============== +CL_ClearEfrags +============== */ -void R_RemoveEfrags( cl_entity_t *ent ) +void CL_ClearEfrags( void ) { - efrag_t *ef, *old, *walk, **prev; - - ef = ent->efrag; - - while( ef ) - { - prev = &ef->leaf->efrags; - while( 1 ) - { - walk = *prev; - if( !walk ) break; - - if( walk == ef ) - { - // remove this fragment - *prev = ef->leafnext; - break; - } - else prev = &walk->leafnext; - } - - old = ef; - ef = ef->entnext; - - // put it on the free list - old->entnext = clgame.free_efrags; - clgame.free_efrags = old; - } - ent->efrag = NULL; + cl_efrags_num = 0; + cl_efrags = NULL; } /* @@ -96,14 +97,11 @@ static void R_SplitEntityOnNode( mnode_t *node ) leaf = (mleaf_t *)node; // grab an efrag off the free list - ef = clgame.free_efrags; + ef = cl_efrags; if( !ef ) - { - Con_Printf( S_ERROR "too many efrags!\n" ); - return; // no free fragments... - } + ef = CL_AllocEfrags( NUM_EFRAGS_ALLOC ); - clgame.free_efrags = ef->entnext; + cl_efrags = ef->entnext; ef->entity = r_addent; // add the entity link @@ -129,8 +127,10 @@ static void R_SplitEntityOnNode( mnode_t *node ) } // recurse down the contacted sides - if( sides & 1 ) R_SplitEntityOnNode( node->children[0] ); - if( sides & 2 ) R_SplitEntityOnNode( node->children[1] ); + if( sides & 1 ) + R_SplitEntityOnNode( node_child( node, 0, cl.worldmodel )); + if( sides & 2 ) + R_SplitEntityOnNode( node_child( node, 1, cl.worldmodel )); } /* @@ -173,35 +173,29 @@ R_StoreEfrags */ void R_StoreEfrags( efrag_t **ppefrag, int framecount ) { - cl_entity_t *pent; - model_t *clmodel; - efrag_t *pefrag; + efrag_t *pefrag; + cl_entity_t *pent; + model_t *clmodel; while(( pefrag = *ppefrag ) != NULL ) { pent = pefrag->entity; clmodel = pent->model; - switch( clmodel->type ) - { - case mod_alias: - case mod_brush: - case mod_studio: - case mod_sprite: - if( pent->visframe != framecount ) - { - if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) - { - // mark that we've recorded this entity for this frame - pent->curstate.messagenum = cl.parsecount; - pent->visframe = framecount; - } - } + // how this could happen? + if( unlikely( clmodel->type < mod_brush || clmodel->type > mod_studio )) + continue; - ppefrag = &pefrag->leafnext; - break; - default: - break; + if( pent->visframe != framecount ) + { + if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) + { + // mark that we've recorded this entity for this frame + pent->curstate.messagenum = cl.parsecount; + pent->visframe = framecount; + } } + + ppefrag = &pefrag->leafnext; } } diff --git a/engine/client/cl_efx.c b/engine/client/cl_efx.c index 6a07dd49..51ffde8c 100644 --- a/engine/client/cl_efx.c +++ b/engine/client/cl_efx.c @@ -6,7 +6,7 @@ #include "r_efx.h" #include "cl_tent.h" #include "pm_local.h" -#define PART_SIZE Q_max( 0.5f, cl_draw_particles->value ) +#define PART_SIZE Q_max( 0.5f, cl_draw_particles.value ) /* ============================================================== @@ -21,17 +21,20 @@ static int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 }; static int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 }; static int gSparkRamp[9] = { 0xfe, 0xfd, 0xfc, 0x6f, 0x6e, 0x6d, 0x6c, 0x67, 0x60 }; -convar_t *tracerspeed; -convar_t *tracerlength; -convar_t *traceroffset; +static CVAR_DEFINE_AUTO( tracerspeed, "6000", 0, "tracer speed" ); +static CVAR_DEFINE_AUTO( tracerlength, "0.8", 0, "tracer length factor" ); +static CVAR_DEFINE_AUTO( traceroffset, "30", 0, "tracer starting offset" ); -particle_t *cl_active_particles; -particle_t *cl_active_tracers; -particle_t *cl_free_particles; -particle_t *cl_particles = NULL; // particle pool +static particle_t *cl_active_particles; +static particle_t *cl_active_tracers; +static particle_t *cl_free_particles; +static particle_t *cl_particles = NULL; // particle pool static vec3_t cl_avelocities[NUMVERTEXNORMALS]; static float cl_lasttimewarn = 0.0f; +// expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + /* ================ R_LookupColor @@ -100,9 +103,9 @@ void CL_InitParticles( void ) cl_avelocities[i][2] = COM_RandomFloat( 0.0f, 2.55f ); } - tracerspeed = Cvar_Get( "tracerspeed", "6000", 0, "tracer speed" ); - tracerlength = Cvar_Get( "tracerlength", "0.8", 0, "tracer length factor" ); - traceroffset = Cvar_Get( "traceroffset", "30", 0, "tracer starting offset" ); + Cvar_RegisterVariable( &tracerspeed ); + Cvar_RegisterVariable( &tracerlength ); + Cvar_RegisterVariable( &traceroffset ); } /* @@ -140,26 +143,6 @@ void CL_FreeParticles( void ) cl_particles = NULL; } -/* -================ -CL_FreeParticle - -move particle to freelist -================ -*/ -void CL_FreeParticle( particle_t *p ) -{ - if( p->deathfunc ) - { - // call right the deathfunc before die - p->deathfunc( p ); - p->deathfunc = NULL; - } - - p->next = cl_free_particles; - cl_free_particles = p; -} - /* ================ CL_AllocParticleFast @@ -191,11 +174,11 @@ particle_t * GAME_EXPORT R_AllocParticle( void (*callback)( particle_t*, float ) { particle_t *p; - if( !cl_draw_particles->value ) + if( !cl_draw_particles.value ) return NULL; // never alloc particles when we not in game -// if( tr.frametime == 0.0 ) return NULL; + if( cl_clientframetime() == 0.0 ) return NULL; if( !cl_free_particles ) { @@ -238,15 +221,15 @@ R_AllocTracer can return NULL if particles is out ================ */ -particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) +static particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) { particle_t *p; - if( !cl_draw_tracers->value ) + if( !cl_draw_tracers.value ) return NULL; // never alloc particles when we not in game - //if( tr.frametime == 0.0 ) return NULL; + if( cl_clientframetime() == 0.0 ) return NULL; if( !cl_free_particles ) { @@ -269,8 +252,8 @@ particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) VectorCopy( org, p->org ); VectorCopy( vel, p->vel ); p->die = cl.time + life; - p->ramp = tracerlength->value; - p->color = 4; // select custom color + p->ramp = tracerlength.value; + p->color = TRACER_COLORINDEX_DEFAULT; // select custom color p->packedColor = 255; // alpha return p; @@ -282,9 +265,9 @@ VIEWBEAMS MANAGEMENT ============================================================== */ -BEAM *cl_active_beams; -BEAM *cl_free_beams; -BEAM *cl_viewbeams = NULL; // beams pool +static BEAM *cl_active_beams; +static BEAM *cl_free_beams; +static BEAM *cl_viewbeams = NULL; // beams pool /* @@ -320,7 +303,7 @@ R_BeamAlloc ============== */ -BEAM *R_BeamAlloc( void ) +static BEAM *R_BeamAlloc( void ) { BEAM *pBeam; @@ -343,7 +326,7 @@ R_BeamFree ============== */ -void R_BeamFree( BEAM *pBeam ) +static void R_BeamFree( BEAM *pBeam ) { // free particles that have died off. R_FreeDeadParticles( &pBeam->particles ); @@ -625,7 +608,7 @@ CL_BeamAttemptToDie Check for expired beams ============== */ -qboolean CL_BeamAttemptToDie( BEAM *pBeam ) +static qboolean CL_BeamAttemptToDie( BEAM *pBeam ) { Assert( pBeam != NULL ); @@ -656,12 +639,24 @@ and all particle trails (if this is a beamfollow) */ void GAME_EXPORT R_BeamKill( int deadEntity ) { - cl_entity_t *pDeadEntity; + BEAM *beam; - pDeadEntity = R_BeamGetEntity( deadEntity ); - if( !pDeadEntity ) return; + for( beam = cl_active_beams; beam; beam = beam->next ) + { + if( FBitSet( beam->flags, FBEAM_STARTENTITY ) && beam->startEntity == deadEntity ) + { + if( beam->type != TE_BEAMFOLLOW ) + beam->die = cl.time; - CL_KillDeadBeams( pDeadEntity ); + ClearBits( beam->flags, FBEAM_STARTENTITY ); + } + + if( FBitSet( beam->flags, FBEAM_ENDENTITY ) && beam->endEntity == deadEntity ) + { + beam->die = cl.time; + ClearBits( beam->flags, FBEAM_ENDENTITY ); + } + } } /* @@ -1114,9 +1109,7 @@ void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int col int colorMod = 0, packedColor; particle_t *p; - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - packedColor = 255; // use old code for blob particles - else packedColor = 0; + packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles for( i = 0; i < 512; i++ ) { @@ -1149,9 +1142,7 @@ void GAME_EXPORT R_BlobExplosion( const vec3_t org ) particle_t *p; int i, j, packedColor; - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - packedColor = 255; // use old code for blob particles - else packedColor = 0; + packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles for( i = 0; i < 1024; i++ ) { @@ -1256,12 +1247,15 @@ R_BloodStream particle spray 2 =============== */ -void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed ) +void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor, int speed ) { particle_t *p; int i, j; float arc; int accel = speed; // must be integer due to bug in GoldSrc + vec3_t dir; + + VectorNormalize2( ndir, dir ); for( arc = 0.05f, i = 0; i < 100; i++ ) { @@ -1500,8 +1494,6 @@ R_RocketTrail void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) { vec3_t vec, right, up; - static int tracercount; - float s, c, x, y; float len, dec; particle_t *p; @@ -1510,15 +1502,18 @@ void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) if( type == 7 ) { + dec = 1.0f; VectorVectors( vec, right, up ); } - - if( type < 128 ) + else if( type < 128 ) { dec = 3.0f; } else { + // initialize if type will be 7 here + VectorVectors( vec, right, up ); + dec = 1.0f; type -= 128; } @@ -1527,67 +1522,65 @@ void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) while( len > 0 ) { - len -= dec; - p = R_AllocParticle( NULL ); - if( !p ) return; + if( !p ) + return; + len -= dec; p->die = cl.time + 2.0f; switch( type ) { - case 0: // rocket trail - p->ramp = COM_RandomLong( 0, 3 ); + case 0: + case 1: + p->ramp = COM_RandomLong( 0 + type * 2, 3 + type * 2 ); p->color = ramp3[(int)p->ramp]; p->type = pt_fire; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); break; - case 1: // smoke smoke - p->ramp = COM_RandomLong( 2, 5 ); - p->color = ramp3[(int)p->ramp]; - p->type = pt_fire; - VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); - break; - case 2: // blood - p->type = pt_grav; + case 2: p->color = COM_RandomLong( 67, 74 ); + p->type = pt_grav; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); break; case 3: - case 5: // tracer + case 5: + { + static int tracercount; p->die = cl.time + 0.5f; + p->color = ( tracercount & 4 ) * 2; - if( type == 3 ) p->color = 52 + (( tracercount & 4 )<<1 ); - else p->color = 230 + (( tracercount & 4 )<<1 ); + if( type == 3 ) + p->color += 52; + else + p->color += 230; VectorCopy( start, p->org ); tracercount++; - if( FBitSet( tracercount, 1 )) - { - p->vel[0] = 30.0f * vec[1]; - p->vel[1] = 30.0f * -vec[0]; - } - else - { - p->vel[0] = 30.0f * -vec[1]; - p->vel[1] = 30.0f * vec[0]; - } + p->vel[0] = 30.0f * vec[1]; + p->vel[1] = 30.0f * vec[0]; + p->vel[tracercount & 1] = -p->vel[tracercount & 1]; break; - case 4: // slight blood - p->type = pt_grav; + } + case 4: p->color = COM_RandomLong( 67, 70 ); + p->type = pt_grav; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); len -= 3.0f; break; - case 6: // voor trail - p->color = COM_RandomLong( 152, 155 ); - p->die += 0.3f; - VectorAddScalar( start, COM_RandomFloat( -8.0f, 8.0f ), p->org ); + case 6: + p->type = pt_fire; + p->ramp = COM_RandomLong( 0, 3 ); + p->color = ramp3[(int)p->ramp]; + VectorCopy( start, p->org ); break; - case 7: // explosion tracer - x = COM_RandomLong( 0, 65535 ); - y = COM_RandomLong( 8, 16 ); + case 7: + { + float x = COM_RandomLong( 0, 65535 ); + float y = COM_RandomLong( 8, 16 ); + float s, c; + SinCos( x, &s, &c ); s *= y; c *= y; @@ -1595,13 +1588,16 @@ void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) VectorMAMAM( 1.0f, start, s, right, c, up, p->org ); VectorSubtract( start, p->org, p->vel ); VectorScale( p->vel, 2.0f, p->vel ); - VectorMA( p->vel, COM_RandomFloat( 96.0f, 111.0f ), vec, p->vel ); + + x = COM_RandomFloat( 96.0f, 111.0f ); + VectorMA( p->vel, x, vec, p->vel ); + p->ramp = COM_RandomLong( 0, 3 ); p->color = ramp3[(int)p->ramp]; p->type = pt_explode2; break; + } default: - // just build line to show error VectorCopy( start, p->org ); break; } @@ -1610,6 +1606,73 @@ void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) } } +/* +=============== +PM_ParticleLine + +draw line from particles +================ +*/ +static void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ) +{ + float len, curdist; + vec3_t diff, pos; + + // determine distance + VectorSubtract( end, start, diff ); + len = VectorNormalizeLength( diff ); + curdist = 0; + + while( curdist <= len ) + { + VectorMA( start, curdist, diff, pos ); + CL_Particle( pos, pcolor, life, 0, zvel ); + curdist += 2.0f; + } +} + +/* +================ +PM_DrawRectangle + +================ +*/ +static void PM_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life ) +{ + PM_ParticleLine( tl, bl, pcolor, life, 0 ); + PM_ParticleLine( bl, br, pcolor, life, 0 ); + PM_ParticleLine( br, tr, pcolor, life, 0 ); + PM_ParticleLine( tr, tl, pcolor, life, 0 ); +} + +/* +================ +PM_DrawBBox + +================ +*/ +static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ) +{ + vec3_t p[8], tmp; + float gap = BOX_GAP; + int i; + + for( i = 0; i < 8; i++ ) + { + tmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (i & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd( tmp, origin, tmp ); + VectorCopy( tmp, p[i] ); + } + + for( i = 0; i < 6; i++ ) + { + PM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life ); + } +} + /* ================ R_ParticleLine @@ -1773,25 +1836,6 @@ void GAME_EXPORT R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, } } -/* -=============== -R_DebugParticle - -just for debug purposes -=============== -*/ -void R_DebugParticle( const vec3_t pos, byte r, byte g, byte b ) -{ - particle_t *p; - - p = R_AllocParticle( NULL ); - if( !p ) return; - - VectorCopy( pos, p->org ); - p->color = R_LookupColor( r, g, b ); - p->die = cl.time + 0.01f; -} - /* =============== CL_Particle @@ -1824,14 +1868,14 @@ void GAME_EXPORT R_TracerEffect( const vec3_t start, const vec3_t end ) float len, speed; float offset; - speed = Q_max( tracerspeed->value, 3.0f ); + speed = Q_max( tracerspeed.value, 3.0f ); VectorSubtract( end, start, dir ); len = VectorLength( dir ); if( len == 0.0f ) return; VectorScale( dir, 1.0f / len, dir ); // normalize - offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset->value; + offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value; VectorScale( dir, offset, vel ); VectorAdd( start, vel, pos ); VectorScale( dir, speed, vel ); @@ -2057,7 +2101,7 @@ void CL_ReadPointFile_f( void ) else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); } -void CL_FreeDeadBeams( void ) +static void CL_FreeDeadBeams( void ) { BEAM *pBeam, *pNext, *pPrev = NULL; // draw temporary entity beams @@ -2087,16 +2131,16 @@ void CL_FreeDeadBeams( void ) void CL_DrawEFX( float time, qboolean fTrans ) { CL_FreeDeadBeams(); - if( CVAR_TO_BOOL( cl_draw_beams )) + if( cl_draw_beams.value ) ref.dllFuncs.CL_DrawBeams( fTrans, cl_active_beams ); if( fTrans ) { R_FreeDeadParticles( &cl_active_particles ); - if( CVAR_TO_BOOL( cl_draw_particles )) + if( cl_draw_particles.value ) ref.dllFuncs.CL_DrawParticles( time, cl_active_particles, PART_SIZE ); R_FreeDeadParticles( &cl_active_tracers ); - if( CVAR_TO_BOOL( cl_draw_tracers )) + if( cl_draw_tracers.value ) ref.dllFuncs.CL_DrawTracers( time, cl_active_tracers ); } } @@ -2185,4 +2229,3 @@ void CL_ThinkParticle( double frametime, particle_t *p ) break; } } - diff --git a/engine/client/cl_events.c b/engine/client/cl_events.c index 0107a65e..9639409a 100644 --- a/engine/client/cl_events.c +++ b/engine/client/cl_events.c @@ -40,7 +40,7 @@ CL_CalcPlayerVelocity compute velocity for a given client ============= */ -void CL_CalcPlayerVelocity( int idx, vec3_t velocity ) +static void CL_CalcPlayerVelocity( int idx, vec3_t velocity ) { clientdata_t *pcd; vec3_t delta; @@ -78,13 +78,13 @@ CL_DescribeEvent ============= */ -void CL_DescribeEvent( event_info_t *ei, int slot ) +static void CL_DescribeEvent( event_info_t *ei, int slot ) { int idx = (slot & 63) * 2; con_nprint_t info; string origin_str = { 0 }; //, angles_str = { 0 }; - if( !cl_showevents->value ) + if( !cl_showevents.value ) return; info.time_to_live = 1.0f; @@ -192,7 +192,7 @@ void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func ) ev = clgame.events[lastnum]; // NOTE: ev->index will be set later - Q_strncpy( ev->name, szEvName, MAX_QPATH ); + Q_strncpy( ev->name, szEvName, sizeof( ev->name )); ev->func = func; } @@ -202,7 +202,7 @@ CL_FireEvent ============= */ -qboolean CL_FireEvent( event_info_t *ei, int slot ) +static qboolean CL_FireEvent( event_info_t *ei, int slot ) { cl_user_event_t *ev; const char *name; @@ -219,12 +219,24 @@ qboolean CL_FireEvent( event_info_t *ei, int slot ) if( !ev ) { idx = bound( 1, ei->index, ( MAX_EVENTS - 1 )); - Con_Reportf( S_ERROR "CL_FireEvent: %s not precached\n", cl.event_precache[idx] ); + Con_Reportf( S_ERROR "%s: %s not precached\n", __func__, cl.event_precache[idx] ); break; } if( ev->index == ei->index ) { + name = cl.event_precache[ei->index]; + + if( cl_trace_events.value ) + { + Con_Printf( "^3EVENT %s AT %.2f %.2f %.2f\n" // event name + "\t%.2f %.2f %i %i %s %s\n", // bool params + name, ei->args.origin[0], ei->args.origin[1], ei->args.origin[2], + ei->args.fparam1, ei->args.fparam2, + ei->args.iparam1, ei->args.iparam2, + ei->args.bparam1 ? "TRUE" : "FALSE", ei->args.bparam2 ? "TRUE" : "FALSE" ); + } + if( ev->func ) { CL_DescribeEvent( ei, slot ); @@ -232,8 +244,7 @@ qboolean CL_FireEvent( event_info_t *ei, int slot ) return true; } - name = cl.event_precache[ei->index]; - Con_Reportf( S_ERROR "CL_FireEvent: %s not hooked\n", name ); + Con_Reportf( S_ERROR "%s: %s not hooked\n", __func__, name ); break; } } @@ -281,7 +292,7 @@ CL_FindEvent find first empty event ============= */ -event_info_t *CL_FindEmptyEvent( void ) +static event_info_t *CL_FindEmptyEvent( void ) { int i; event_state_t *es; @@ -309,7 +320,7 @@ CL_FindEvent replace only unreliable events ============= */ -event_info_t *CL_FindUnreliableEvent( void ) +static event_info_t *CL_FindUnreliableEvent( void ) { event_state_t *es; event_info_t *ei; @@ -339,7 +350,7 @@ CL_QueueEvent ============= */ -void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ) +static void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ) { event_info_t *ei; @@ -369,7 +380,7 @@ CL_ParseReliableEvent ============= */ -void CL_ParseReliableEvent( sizebuf_t *msg ) +void CL_ParseReliableEvent( sizebuf_t *msg, connprotocol_t proto ) { int event_index; event_args_t nullargs, args; @@ -379,14 +390,26 @@ void CL_ParseReliableEvent( sizebuf_t *msg ) event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); - if( MSG_ReadOneBit( msg )) - delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); - // reliable events not use delta-compression just null-compression - MSG_ReadDeltaEvent( msg, &nullargs, &args ); + if( proto == PROTO_GOLDSRC ) + { + Delta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f ); + if( MSG_ReadOneBit( msg )) + delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); + } + else + { + if( MSG_ReadOneBit( msg )) + delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); + MSG_ReadDeltaEvent( msg, &nullargs, &args ); + } if( args.entindex > 0 && args.entindex <= cl.maxclients ) - args.angles[PITCH] *= -3.0f; + { + args.angles[PITCH] *= 3.0f; + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + args.angles[PITCH] = -args.angles[PITCH]; + } CL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args ); } @@ -398,33 +421,42 @@ CL_ParseEvent ============= */ -void CL_ParseEvent( sizebuf_t *msg ) +void CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto ) { int event_index; int i, num_events; int packet_index; - event_args_t nullargs, args; + const event_args_t nullargs = { 0 }; + event_args_t args = { 0 }; entity_state_t *state; float delay; - - memset( &nullargs, 0, sizeof( nullargs )); - memset( &args, 0, sizeof( args )); + int entity_bits; num_events = MSG_ReadUBitLong( msg, 5 ); + if( proto == PROTO_GOLDSRC ) + entity_bits = MAX_GOLDSRC_ENTITY_BITS; + else if( proto == PROTO_LEGACY ) + entity_bits = MAX_LEGACY_ENTITY_BITS; + else entity_bits = MAX_ENTITY_BITS; + // parse events queue for( i = 0 ; i < num_events; i++ ) { event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); - if( MSG_ReadOneBit( msg )) - packet_index = MSG_ReadUBitLong( msg, cls.legacymode ? MAX_LEGACY_ENTITY_BITS : MAX_ENTITY_BITS ); - else packet_index = -1; - if( MSG_ReadOneBit( msg )) { - MSG_ReadDeltaEvent( msg, &nullargs, &args ); + packet_index = MSG_ReadUBitLong( msg, entity_bits ); + + if( MSG_ReadOneBit( msg )) + { + if( proto == PROTO_GOLDSRC ) + Delta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f ); + else MSG_ReadDeltaEvent( msg, &nullargs, &args ); + } } + else packet_index = -1; if( MSG_ReadOneBit( msg )) delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); @@ -449,7 +481,9 @@ void CL_ParseEvent( sizebuf_t *msg ) if( state->number > 0 && state->number <= cl.maxclients ) { - args.angles[PITCH] *= -3.0f; + args.angles[PITCH] *= 3.0f; + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + args.angles[PITCH] = -args.angles[PITCH]; CL_CalcPlayerVelocity( state->number, args.velocity ); args.ducking = ( state->usehull == 1 ); } @@ -459,7 +493,11 @@ void CL_ParseEvent( sizebuf_t *msg ) if( args.entindex != 0 ) { if( args.entindex > 0 && args.entindex <= cl.maxclients ) - args.angles[PITCH] /= -3.0f; + { + args.angles[PITCH] /= 3.0f; + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + args.angles[PITCH] = -args.angles[PITCH]; + } } } @@ -486,14 +524,14 @@ void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word even // first check event for out of bounds if( eventindex < 1 || eventindex >= MAX_EVENTS ) { - Con_DPrintf( S_ERROR "CL_PlaybackEvent: invalid eventindex %i\n", eventindex ); + Con_DPrintf( S_ERROR "%s: invalid eventindex %i\n", __func__, eventindex ); return; } // check event for precached if( !CL_EventIndex( cl.event_precache[eventindex] )) { - Con_DPrintf( S_ERROR "CL_PlaybackEvent: event %i was not precached\n", eventindex ); + Con_DPrintf( S_ERROR "%s: event %i was not precached\n", __func__, eventindex ); return; } diff --git a/engine/client/cl_font.c b/engine/client/cl_font.c new file mode 100644 index 00000000..1f46d566 --- /dev/null +++ b/engine/client/cl_font.c @@ -0,0 +1,380 @@ +/* +cl_font.c - bare bones engine font manager +Copyright (C) 2023 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "filesystem.h" +#include "client.h" +#include "qfont.h" + +qboolean CL_FixedFont( cl_font_t *font ) +{ + return font && font->valid && font->type == FONT_FIXED; +} + +static int CL_LoadFontTexture( const char *fontname, uint texFlags, int *width ) +{ + int font_width; + int tex; + + if( !g_fsapi.FileExists( fontname, false )) + return 0; + + tex = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, texFlags ); + if( !tex ) + return 0; + + font_width = REF_GET_PARM( PARM_TEX_WIDTH, tex ); + if( !font_width ) + { + ref.dllFuncs.GL_FreeTexture( tex ); + return 0; + } + + *width = font_width; + return tex; +} + +static int CL_FontRenderMode( convar_t *fontrender ) +{ + switch((int)fontrender->value ) + { + case 0: + return kRenderTransAdd; + case 1: + return kRenderTransAlpha; + case 2: + return kRenderTransTexture; + default: + Cvar_DirectSet( fontrender, fontrender->def_string ); + } + + return kRenderTransTexture; +} + +void CL_SetFontRendermode( cl_font_t *font ) +{ + ref.dllFuncs.GL_SetRenderMode( CL_FontRenderMode( font->rendermode )); +} + +qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ) +{ + int font_width, i; + + if( !rendermode ) + return false; + + if( font->valid ) + return true; // already loaded + + font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); + if( !font->hFontTexture ) + return false; + + font->type = FONT_FIXED; + font->valid = true; + font->scale = scale; + font->rendermode = rendermode; + font->charHeight = Q_rint( font_width / 16 * scale ); + + for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) + { + font->fontRc[i].left = ( i * font_width / 16 ) % font_width; + font->fontRc[i].right = font->fontRc[i].left + font_width / 16; + font->fontRc[i].top = ( i / 16 ) * ( font_width / 16 ); + font->fontRc[i].bottom = font->fontRc[i].top + font_width / 16; + + font->charWidths[i] = Q_rint( font_width / 16 * scale ); + } + + return true; +} + +qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ) +{ + fs_offset_t length; + qfont_t src; + byte *pfile; + int font_width, i; + + if( !rendermode ) + return false; + + if( font->valid ) + return true; + + pfile = g_fsapi.LoadFile( fontname, &length, false ); + if( !pfile ) + return false; + + if( length < sizeof( src )) + { + Mem_Free( pfile ); + return false; + } + + memcpy( &src, pfile, sizeof( src )); + Mem_Free( pfile ); + + font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); + if( !font->hFontTexture ) + return false; + + font->type = FONT_VARIABLE; + font->valid = true; + font->scale = scale ? scale : 1.0f; + font->rendermode = rendermode; + font->charHeight = Q_rint( src.rowheight * scale ); + + for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) + { + const charinfo *ci = &src.fontinfo[i]; + + font->fontRc[i].left = (word)ci->startoffset % font_width; + font->fontRc[i].right = font->fontRc[i].left + ci->charwidth; + font->fontRc[i].top = (word)ci->startoffset / font_width; + font->fontRc[i].bottom = font->fontRc[i].top + src.rowheight; + + font->charWidths[i] = Q_rint( src.fontinfo[i].charwidth * scale ); + } + + return true; +} + +void CL_FreeFont( cl_font_t *font ) +{ + if( !font || !font->valid ) + return; + + ref.dllFuncs.GL_FreeTexture( font->hFontTexture ); + memset( font, 0, sizeof( *font )); +} + +static int CL_CalcTabStop( const cl_font_t *font, int x ) +{ + int space = font->charWidths[' ']; + int tab = space * 6; // 6 spaces + int stop = tab - x % tab; + + if( stop < space ) + return tab * 2 - x % tab; // select next + + return stop; +} + +int CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_t *font, int flags ) +{ + wrect_t *rc; + float w, h; + float s1, t1, s2, t2, half = 0.5f; + int texw, texh; + + if( !font || !font->valid || y < -font->charHeight ) + return 0; + + // check if printable + if( number <= 32 ) + { + if( number == ' ' ) + return font->charWidths[' ']; + else if( number == '\t' ) + return CL_CalcTabStop( font, x ); + return 0; + } + + if( FBitSet( flags, FONT_DRAW_UTF8 )) + number = Con_UtfProcessChar( number & 255 ); + else number &= 255; + + if( !number || !font->charWidths[number]) + return 0; + + R_GetTextureParms( &texw, &texh, font->hFontTexture ); + if( !texw || !texh ) + return font->charWidths[number]; + + rc = &font->fontRc[number]; + + if( font->scale <= 1.f || !REF_GET_PARM( PARM_TEX_FILTERING, font->hFontTexture )) + half = 0; + + s1 = ((float)rc->left + half ) / texw; + t1 = ((float)rc->top + half ) / texh; + s2 = ((float)rc->right - half ) / texw; + t2 = ((float)rc->bottom - half ) / texh; + w = ( rc->right - rc->left ) * font->scale; + h = ( rc->bottom - rc->top ) * font->scale; + + if( FBitSet( flags, FONT_DRAW_HUD )) + SPR_AdjustSize( &x, &y, &w, &h ); + + if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) + CL_SetFontRendermode( font ); + + // don't apply color to fixed fonts it's already colored + if( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8 + ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); + else ref.dllFuncs.Color4ub( 255, 255, 255, color[3] ); + ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, font->hFontTexture ); + + return font->charWidths[number]; +} + +int CL_DrawString( float x, float y, const char *s, const rgba_t color, cl_font_t *font, int flags ) +{ + rgba_t current_color; + int draw_len = 0; + + if( !font || !font->valid ) + return 0; + + if( FBitSet( flags, FONT_DRAW_UTF8 )) + Con_UtfProcessChar( 0 ); // clear utf state + + if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) + CL_SetFontRendermode( font ); + + Vector4Copy( color, current_color ); + + while( *s ) + { + if( *s == '\n' ) + { + s++; + + if( !*s ) + break; + + // some client functions ignore newlines + if( !FBitSet( flags, FONT_DRAW_NOLF )) + { + draw_len = 0; + y += font->charHeight; + } + + if( FBitSet( flags, FONT_DRAW_RESETCOLORONLF )) + Vector4Copy( color, current_color ); + continue; + } + + if( IsColorString( s )) + { + // don't copy alpha + if( !FBitSet( flags, FONT_DRAW_FORCECOL )) + VectorCopy( g_color_table[ColorIndex(*( s + 1 ))], current_color ); + + s += 2; + continue; + } + + // skip setting rendermode, it was changed for this string already + draw_len += CL_DrawCharacter( x + draw_len, y, (byte)*s, current_color, font, flags | FONT_DRAW_NORENDERMODE ); + + s++; + } + + return draw_len; +} + +int CL_DrawStringf( cl_font_t *font, float x, float y, const rgba_t color, int flags, const char *fmt, ... ) +{ + va_list va; + char buf[MAX_VA_STRING]; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + return CL_DrawString( x, y, buf, color, font, flags ); +} + +void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ) +{ + if( !font || !font->valid ) return; + if( width ) + { + if( number == '\t' ) + *width = CL_CalcTabStop( font, 0 ); // at least return max tabstop + else *width = font->charWidths[number & 255]; + } + if( height ) *height = font->charHeight; +} + +void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags ) +{ + int draw_len = 0; + + if( !font || !font->valid ) + return; + + if( height ) + *height = font->charHeight; + + if( width ) + *width = 0; + + if( !COM_CheckString( s )) + return; + + if( FBitSet( flags, FONT_DRAW_UTF8 )) + Con_UtfProcessChar( 0 ); // reset utf state + + while( *s ) + { + int number; + + if( *s == '\n' ) + { + // BUG: no check for end string here + // but high chances somebody's relying on this + s++; + draw_len = 0; + if( !FBitSet( flags, FONT_DRAW_NOLF )) + { + if( height ) + *height += font->charHeight; + } + continue; + } + else if( *s == '\t' ) + { + draw_len += CL_CalcTabStop( font, 0 ); // at least return max tabstop + s++; + continue; + } + + if( IsColorString( s )) + { + s += 2; + continue; + } + + if( FBitSet( flags, FONT_DRAW_UTF8 )) + number = Con_UtfProcessChar( (byte)*s ); + else number = (byte)*s; + + if( number ) + { + draw_len += font->charWidths[number]; + + if( width ) + { + if( draw_len > *width ) + *width = draw_len; + } + } + + s++; + } +} diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 81976384..299d4d93 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -24,19 +24,7 @@ GNU General Public License for more details. #include "sound.h" #include "input.h" -#define STUDIO_INTERPOLATION_FIX - -/* -================== -CL_IsPlayerIndex - -detect player entity -================== -*/ -qboolean CL_IsPlayerIndex( int idx ) -{ - return ( idx >= 1 && idx <= cl.maxclients ); -} +// #define STUDIO_INTERPOLATION_FIX /* ========================================================================= @@ -52,19 +40,30 @@ CL_UpdatePositions Store another position into interpolation circular buffer ================== */ -void CL_UpdatePositions( cl_entity_t *ent ) +static void CL_UpdatePositions( cl_entity_t *ent ) { - position_history_t *ph; + position_history_t *ph, *prev; + + prev = &ent->ph[ent->current_position]; ent->current_position = (ent->current_position + 1) & HISTORY_MASK; ph = &ent->ph[ent->current_position]; VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.angles, ph->angles ); - if( ent->model && ent->model->type == mod_brush ) - ph->animtime = ent->curstate.animtime; - else - ph->animtime = cl.time; + ph->animtime = ent->curstate.animtime; + + // a1ba: for some reason, this sometimes still may happen + // at this time, I'm not sure whether this bug happens in delta readwrite code + // or server just decides to go backwards and really sends these values + if( ph->animtime < prev->animtime ) + { + // try to deduce real animtime by looking up the difference between + // server messages (cl.mtime is never modified ny the interpolation code) + float diff = Q_max( 0, ent->curstate.msg_time - ent->prevstate.msg_time ); + + ph->animtime = prev->animtime + diff; + } } /* @@ -74,7 +73,7 @@ CL_ResetPositions Interpolation init or reset after teleporting ================== */ -void CL_ResetPositions( cl_entity_t *ent ) +static void CL_ResetPositions( cl_entity_t *ent ) { position_history_t store; @@ -84,8 +83,7 @@ void CL_ResetPositions( cl_entity_t *ent ) ent->current_position = 1; memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX ); - memcpy( &ent->ph[1], &store, sizeof( position_history_t )); - memcpy( &ent->ph[0], &store, sizeof( position_history_t )); + ent->ph[1] = ent->ph[0] = store; } /* @@ -96,7 +94,7 @@ check for instant movement in case we don't want interpolate this ================== */ -qboolean CL_EntityTeleported( cl_entity_t *ent ) +static qboolean CL_EntityTeleported( cl_entity_t *ent ) { float len, maxlen; vec3_t delta; @@ -117,7 +115,7 @@ CL_CompareTimestamps round-off floating errors ================== */ -qboolean CL_CompareTimestamps( float t1, float t2 ) +static qboolean CL_CompareTimestamps( float t1, float t2 ) { int iTime1 = t1 * 1000; int iTime2 = t2 * 1000; @@ -132,9 +130,9 @@ CL_EntityIgnoreLerp some ents will be ignore lerping ================== */ -qboolean CL_EntityIgnoreLerp( cl_entity_t *e ) +static qboolean CL_EntityIgnoreLerp( cl_entity_t *e ) { - if( cl_nointerp->value > 0.f ) + if( cl_nointerp.value > 0.f ) return true; if( e->model && e->model->type == mod_alias ) @@ -149,7 +147,7 @@ CL_EntityCustomLerp ================== */ -qboolean CL_EntityCustomLerp( cl_entity_t *e ) +static qboolean CL_EntityCustomLerp( cl_entity_t *e ) { switch( e->curstate.movetype ) { @@ -159,6 +157,13 @@ qboolean CL_EntityCustomLerp( cl_entity_t *e ) case MOVETYPE_FLY: case MOVETYPE_COMPOUND: return false; + + // ABSOLUTELY STUPID HACK TO ALLOW MONSTERS + // INTERPOLATION IN GRAVGUNMOD COOP + // MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT + case MOVETYPE_TOSS: + if( cls.legacymode == PROTO_LEGACY && e->model && e->model->type == mod_studio ) + return false; } return true; @@ -171,7 +176,7 @@ CL_ParametricMove check for parametrical moved entities ================== */ -qboolean CL_ParametricMove( cl_entity_t *ent ) +static qboolean CL_ParametricMove( cl_entity_t *ent ) { float frac, dt, t; vec3_t delta; @@ -208,7 +213,7 @@ CL_UpdateLatchedVars ==================== */ -void CL_UpdateLatchedVars( cl_entity_t *ent ) +static void CL_UpdateLatchedVars( cl_entity_t *ent ) { if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio )) return; // below fields used only for alias and studio interpolation @@ -241,7 +246,7 @@ CL_GetStudioEstimatedFrame ==================== */ -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) +static float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) { studiohdr_t *pstudiohdr; mstudioseqdesc_t *pseqdesc; @@ -251,11 +256,11 @@ float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) { pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model ); - if( pstudiohdr ) + if( pstudiohdr && pstudiohdr->numseq > 0 ) { sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 ); pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc ); + return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time ); } } @@ -303,7 +308,7 @@ CL_ProcessEntityUpdate apply changes since new frame received ================== */ -void CL_ProcessEntityUpdate( cl_entity_t *ent ) +static void CL_ProcessEntityUpdate( cl_entity_t *ent ) { qboolean parametric; @@ -349,11 +354,10 @@ CL_FindInterpolationUpdates find two timestamps ================== */ -qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, position_history_t **ph0, position_history_t **ph1 ) +static qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 ) { qboolean extrapolate = true; uint i, i0, i1, imod; - float at; imod = ent->current_position; i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end) @@ -361,8 +365,10 @@ qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, positi for( i = 1; i < HISTORY_MAX - 1; i++ ) { - at = ent->ph[( imod - i ) & HISTORY_MASK].animtime; - if( at == 0.0f ) break; + double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime; + + if( at == 0.0f ) + break; if( targettime > at ) { @@ -374,8 +380,8 @@ qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, positi } } - if( ph0 != NULL ) *ph0 = &ent->ph[i0]; - if( ph1 != NULL ) *ph1 = &ent->ph[i1]; + *ph0 = &ent->ph[i0]; + *ph1 = &ent->ph[i1]; return extrapolate; } @@ -387,23 +393,19 @@ CL_PureOrigin non-local players interpolation ================== */ -void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangles ) +static void CL_PureOrigin( cl_entity_t *ent, double t, vec3_t outorigin, vec3_t outangles ) { - qboolean extrapolate; - float t1, t0, frac; + double t1, t0, frac; position_history_t *ph0, *ph1; vec3_t delta; // NOTE: ph0 is next, ph1 is a prev - extrapolate = CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 ); - - if ( !ph0 || !ph1 ) - return; + CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 ); t0 = ph0->animtime; t1 = ph1->animtime; - if( t0 != 0.0f ) + if( t0 != 0.0 ) { vec4_t q, q1, q2; @@ -411,9 +413,9 @@ void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangle if( !Q_equal( t0, t1 )) frac = ( t - t1 ) / ( t0 - t1 ); - else frac = 1.0f; + else frac = 1.0; - frac = bound( 0.0f, frac, 1.2f ); + frac = bound( 0.0, frac, 1.2 ); VectorMA( ph1->origin, frac, delta, outorigin ); @@ -437,11 +439,11 @@ CL_InterpolateModel non-players interpolation ================== */ -int CL_InterpolateModel( cl_entity_t *e ) +static int CL_InterpolateModel( cl_entity_t *e ) { position_history_t *ph0 = NULL, *ph1 = NULL; vec3_t origin, angles, delta; - float t, t1, t2, frac; + double t, t1, t2, frac; vec4_t q, q1, q2; VectorCopy( e->curstate.origin, e->origin ); @@ -464,18 +466,15 @@ int CL_InterpolateModel( cl_entity_t *e ) if( cl.maxclients <= 1 ) return 1; - if( e->model->type == mod_brush && !cl_bmodelinterp->value ) + if( e->model->type == mod_brush && !cl_bmodelinterp.value ) return 1; if( cl.local.moving && cl.local.onground == e->index ) return 1; - t = cl.time - cl_interp->value; + t = cl.time - cl_interp.value; CL_FindInterpolationUpdates( e, t, &ph0, &ph1 ); - if( ph0 == NULL || ph1 == NULL ) - return 0; - t1 = ph1->animtime; t2 = ph0->animtime; @@ -489,9 +488,7 @@ int CL_InterpolateModel( cl_entity_t *e ) return 0; } - // HACKHACK: workaround buggy position history animtime - // going backward sometimes - if( Q_equal( t2, t1 ) || t2 < t1 ) + if( Q_equal( t2, t1 )) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); @@ -529,7 +526,7 @@ interpolate non-local clients */ void CL_ComputePlayerOrigin( cl_entity_t *ent ) { - float targettime; + double targettime; vec4_t q, q1, q2; vec3_t origin; vec3_t angles; @@ -537,7 +534,7 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent ) if( !ent->player ) return; - if( cl_nointerp->value > 0.f ) + if( cl_nointerp.value > 0.f ) { VectorCopy( ent->curstate.angles, ent->angles ); VectorCopy( ent->curstate.origin, ent->origin ); @@ -555,7 +552,7 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent ) return; } - targettime = cl.time - cl_interp->value; + targettime = cl.time - cl_interp.value; CL_PureOrigin( ent, targettime, origin, angles ); VectorCopy( angles, ent->angles ); @@ -569,7 +566,7 @@ CL_ProcessPlayerState process player states after the new packet has received ================= */ -void CL_ProcessPlayerState( int playerindex, entity_state_t *state ) +static void CL_ProcessPlayerState( int playerindex, entity_state_t *state ) { entity_state_t *ps; @@ -589,7 +586,7 @@ reset latched state if this frame entity was teleported or just EF_NOINTERP was set ================= */ -void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent ) +static void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent ) { if( CHECKVISBIT( frame->flags, pnum )) { @@ -654,6 +651,24 @@ FRAME PARSING ========================================================================= */ +static qboolean CL_ParseEntityNumFromPacket( sizebuf_t *msg, int *newnum, connprotocol_t proto ) +{ + if( proto == PROTO_LEGACY ) + { + *newnum = MSG_ReadWord( msg ); + if( *newnum == 0 ) + return false; + } + else + { + *newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + if( *newnum == LAST_EDICT ) + return false; + } + + return true; +} + /* ================= CL_FlushEntityPacket @@ -661,7 +676,7 @@ CL_FlushEntityPacket Read and ignore whole entity packet. ================= */ -void CL_FlushEntityPacket( sizebuf_t *msg ) +static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto ) { int newnum; entity_state_t from, to; @@ -674,16 +689,57 @@ void CL_FlushEntityPacket( sizebuf_t *msg ) // read it all, but ignore it while( 1 ) { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - if( newnum == LAST_EDICT ) break; // done + if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto )) + break; // done if( MSG_CheckOverflow( msg )) - Host_Error( "CL_FlushEntityPacket: overflow\n" ); + Host_Error( "%s: overflow\n", __func__ ); MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ) ? DELTA_PLAYER : DELTA_ENTITY, cl.mtime[0] ); } } +qboolean CL_ValidateDeltaPacket( uint oldpacket, frame_t *oldframe ) +{ + int subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF; + + if( subtracted == 0 ) + { + Con_NPrintf( 2, "^3Warning:^1 update too old\n^7\n" ); + return false; + } + + if( subtracted >= CL_UPDATE_MASK ) + { + // we can't use this, it is too old + Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); + return false; + } + + if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES )) + { + Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); + return false; + } + + return true; +} + +int CL_UpdateOldEntNum( int oldindex, frame_t *oldframe, entity_state_t **oldent ) +{ + if( !oldframe ) + { + *oldent = NULL; + return MAX_ENTNUMBER; + } + + if( oldindex >= oldframe->num_entities ) + return MAX_ENTNUMBER; + + *oldent = &cls.packet_entities[(oldframe->first_entity + oldindex) % cls.num_client_entities]; + return (*oldent)->number; +} + /* ================= CL_DeltaEntity @@ -691,7 +747,7 @@ CL_DeltaEntity processing delta update ================= */ -void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update ) +static void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update ) { cl_entity_t *ent; entity_state_t *state; @@ -706,7 +762,7 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t if(( newnum < 0 ) || ( newnum >= clgame.maxEntities )) { - Con_DPrintf( S_ERROR "CL_DeltaEntity: invalid newnum: %d\n", newnum ); + Con_DPrintf( S_ERROR "%s: invalid newnum: %d\n", __func__, newnum ); if( has_update ) MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); return; @@ -718,7 +774,7 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t if( has_update ) alive = MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); - else memcpy( state, old, sizeof( entity_state_t )); + else *state = *old; if( !alive ) { @@ -738,7 +794,13 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t SETVISBIT( frame->flags, pack ); // release beams from previous entity - CL_KillDeadBeams( ent ); + + // a1ba: check that this entity number was never used on client + // as beams can be transferred before this entity was sent to client + // (for example, beam was sent over during beam entity spawn + // but referenced start point entity hasn't been sent over due to PVS) + if( ent->curstate.messagenum != 0 ) + CL_KillDeadBeams( ent ); } // add entity to packet @@ -754,12 +816,11 @@ An svc_packetentities has just been parsed, deal with the rest of the data stream. ================== */ -int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) +int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto ) { frame_t *newframe, *oldframe; int oldindex, newnum, oldnum; int playerbytes = 0; - int oldpacket; int bufStart; entity_state_t *oldent; qboolean player; @@ -770,7 +831,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) CL_WriteDemoJumpTime(); // sentinel count. save it for debug checking - if( cls.legacymode ) + if( proto == PROTO_LEGACY ) count = MSG_ReadWord( msg ); else count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1; @@ -784,32 +845,12 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) if( delta ) { - int subtracted; - - oldpacket = MSG_ReadByte( msg ); - subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF; - - if( subtracted == 0 ) - { - Con_NPrintf( 2, "^3Warning:^1 update too old\n^7\n" ); - CL_FlushEntityPacket( msg ); - return playerbytes; - } - - if( subtracted >= CL_UPDATE_MASK ) - { - // we can't use this, it is too old - Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); - CL_FlushEntityPacket( msg ); - return playerbytes; - } - + uint oldpacket = MSG_ReadByte( msg ); oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK]; - if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES )) + if( !CL_ValidateDeltaPacket( oldpacket, oldframe )) { - Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); - CL_FlushEntityPacket( msg ); + CL_FlushEntityPacket( msg, proto ); return playerbytes; } } @@ -817,8 +858,6 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) { // this is a full update that we can start delta compressing from now oldframe = NULL; - oldpacket = -1; // delta too old or is initial message - cl.send_reply = true; // send reply cls.demowaiting = false; // we can start recording now } @@ -827,58 +866,23 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) oldent = NULL; oldindex = 0; - - if( !oldframe ) - { - oldnum = MAX_ENTNUMBER; - } - else - { - if( oldindex >= oldframe->num_entities ) - { - oldnum = MAX_ENTNUMBER; - } - else - { - oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; - oldnum = oldent->number; - } - } + oldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent ); while( 1 ) { - int lastedict; - if( cls.legacymode ) - { - newnum = MSG_ReadWord( msg ); - lastedict = 0; - } - else - { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - lastedict = LAST_EDICT; - } + if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto )) + break; // done - if( newnum == lastedict ) break; // end of packet entities if( MSG_CheckOverflow( msg )) - Host_Error( "CL_ParsePacketEntities: overflow\n" ); + Host_Error( "%s: overflow\n", __func__ ); + player = CL_IsPlayerIndex( newnum ); while( oldnum < newnum ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); - oldindex++; - - if( oldindex >= oldframe->num_entities ) - { - oldnum = MAX_ENTNUMBER; - } - else - { - oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; - oldnum = oldent->number; - } + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); } if( oldnum == newnum ) @@ -887,17 +891,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) bufStart = MSG_GetNumBytesRead( msg ); CL_DeltaEntity( msg, newframe, newnum, oldent, true ); if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; - oldindex++; - - if( oldindex >= oldframe->num_entities ) - { - oldnum = MAX_ENTNUMBER; - } - else - { - oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; - oldnum = oldent->number; - } + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); continue; } @@ -916,21 +910,11 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); - oldindex++; - - if( oldindex >= oldframe->num_entities ) - { - oldnum = MAX_ENTNUMBER; - } - else - { - oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; - oldnum = oldent->number; - } + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); } if( newframe->num_entities != count && newframe->num_entities != 0 ) - Con_Reportf( S_WARN "CL_Parse%sPacketEntities: (%i should be %i)\n", delta ? "Delta" : "", newframe->num_entities, count ); + Con_Reportf( S_WARN "%s%s: (%i should be %i)\n", __func__, delta ? "Delta" : "", newframe->num_entities, count ); if( !newframe->valid ) return playerbytes; // frame is not valid but message was parsed @@ -948,7 +932,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) cls.signon = SIGNONS; // Clear loading plaque. - CL_SignonReply (); + CL_SignonReply( proto ); } return playerbytes; @@ -970,9 +954,25 @@ all the visible entities should pass this filter */ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) { + qboolean draw_player = true; + if( !ent || !ent->model ) return false; + // don't add the player in firstperson mode + if( RP_LOCALCLIENT( ent )) + { + cl.local.apply_effects = true; + + if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) + { + // we don't draw player in default renderer in firstperson mode + // but let the client.dll know about player entity anyway + // for use in custom renderers + draw_player = false; + } + } + // check for adding this entity if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name )) { @@ -982,14 +982,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) return false; } - // don't add the player in firstperson mode - if( RP_LOCALCLIENT( ent )) - { - cl.local.apply_effects = true; - - if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) - return false; - } + if( !draw_player ) + return false; if( entityType == ET_BEAM ) { @@ -1022,7 +1016,7 @@ CL_LinkCustomEntity Add server beam to draw list ============= */ -void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state ) +static void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state ) { ent->curstate.movetype = state->modelindex; // !!! @@ -1045,7 +1039,7 @@ Create visible entities in the correct position for all current players ============= */ -void CL_LinkPlayers( frame_t *frame ) +static void CL_LinkPlayers( frame_t *frame ) { entity_state_t *state; cl_entity_t *ent; @@ -1128,7 +1122,7 @@ CL_LinkPacketEntities =============== */ -void CL_LinkPacketEntities( frame_t *frame ) +static void CL_LinkPacketEntities( frame_t *frame ) { cl_entity_t *ent; entity_state_t *state; @@ -1152,7 +1146,7 @@ void CL_LinkPacketEntities( frame_t *frame ) if( !ent ) { - Con_Reportf( S_ERROR "CL_LinkPacketEntity: bad entity %i\n", state->number ); + Con_Reportf( S_ERROR "%s: bad entity %i\n", __func__, state->number ); continue; } @@ -1193,17 +1187,33 @@ void CL_LinkPacketEntities( frame_t *frame ) #else if( ent->lastmove >= cl.time ) { + float at = ent->curstate.animtime; + CL_ResetLatchedVars( ent, true ); + + if( cl_fixmodelinterpolationartifacts.value ) + ent->latched.prevanimtime = ent->curstate.animtime = at; + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); - // disable step interpolation in client.dll - ent->curstate.movetype = MOVETYPE_NONE; + if( !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) + { + // disable step interpolation in client.dll + ent->curstate.movetype = MOVETYPE_NONE; + } } else { - // restore step interpolation in client.dll - ent->curstate.movetype = MOVETYPE_STEP; + if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) + { + interpolate = true; + } + else + { + // restore step interpolation in client.dll + ent->curstate.movetype = MOVETYPE_STEP; + } } #endif } @@ -1228,11 +1238,24 @@ void CL_LinkPacketEntities( frame_t *frame ) if ( !CL_InterpolateModel( ent )) continue; } - else if( ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address )) + // a1ba: in GoldSrc this is done for cstrike and czero + // but let modders use this as an engine feature + else if( FBitSet( host.features, ENGINE_STEP_POSHISTORY_LERP ) && + ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address )) { if( !CL_InterpolateModel( ent )) continue; } +#if 0 + // ABSOLUTELY STUPID HACK TO ALLOW MONSTERS + // INTERPOLATION IN GRAVGUNMOD COOP + // MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT + else if( cls.legacymode == PROTO_LEGACY && ent->model->type == mod_studio && ent->curstate.movetype == MOVETYPE_TOSS ) + { + if( !CL_InterpolateModel( ent )) + continue; + } +#endif else { // no interpolation right now @@ -1323,7 +1346,7 @@ void CL_EmitEntities( void ) return; // animate lightestyles - ref.dllFuncs.CL_RunLightStyles (); + ref.dllFuncs.CL_RunLightStyles( CL_GetLightStyle( 0 )); // decay dynamic lights CL_DecayLights (); @@ -1428,9 +1451,3 @@ qboolean CL_GetMovieSpatialization( rawchan_t *ch ) return true; } - -void CL_ExtraUpdate( void ) -{ - clgame.dllFuncs.IN_Accumulate(); - S_ExtraUpdate(); -} diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index 6bb50705..95c0244f 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -13,6 +13,12 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#if XASH_SDL == 2 +#include // SDL_GetWindowPosition +#elif XASH_SDL == 3 +#include // SDL_GetWindowPosition +#endif // XASH_SDL + #include "common.h" #include "client.h" #include "const.h" @@ -34,10 +40,10 @@ GNU General Public License for more details. #define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels) #define TEXT_MSGNAME "TextMessage%i" -char cl_textbuffer[MAX_TEXTCHANNELS][2048]; -client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS]; +static char cl_textbuffer[MAX_TEXTCHANNELS][2048]; +static client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS]; -static dllfunc_t cdll_exports[] = +static const dllfunc_t cdll_exports[] = { { "Initialize", (void **)&clgame.dllFuncs.pfnInitialize }, { "HUD_VidInit", (void **)&clgame.dllFuncs.pfnVidInit }, @@ -76,11 +82,10 @@ static dllfunc_t cdll_exports[] = { "IN_ClearStates", (void **)&clgame.dllFuncs.IN_ClearStates }, { "V_CalcRefdef", (void **)&clgame.dllFuncs.pfnCalcRefdef }, { "KB_Find", (void **)&clgame.dllFuncs.KB_Find }, -{ NULL, NULL } }; // optional exports -static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher +static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher { { "HUD_GetStudioModelInterface", (void **)&clgame.dllFuncs.pfnGetStudioModelInterface }, { "HUD_DirectorMessage", (void **)&clgame.dllFuncs.pfnDirectorMessage }, @@ -91,62 +96,10 @@ static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher { "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext { "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext { "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext -{ NULL, NULL } }; static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); -/* -==================== -CL_GetEntityByIndex - -Render callback for studio models -==================== -*/ -cl_entity_t *CL_GetEntityByIndex( int index ) -{ - if( !clgame.entities ) // not in game yet - return NULL; - - if( index < 0 || index >= clgame.maxEntities ) - return NULL; - - if( index == 0 ) - return clgame.entities; - - return CL_EDICT_NUM( index ); -} - -/* -================ -CL_ModelHandle - -get model handle by index -================ -*/ -model_t *CL_ModelHandle( int modelindex ) -{ - if( modelindex < 0 || modelindex >= MAX_MODELS ) - return NULL; - return cl.models[modelindex]; -} - -/* -==================== -CL_IsThirdPerson - -returns true if thirdperson is enabled -==================== -*/ -qboolean CL_IsThirdPerson( void ) -{ - cl.local.thirdperson = clgame.dllFuncs.CL_IsThirdPerson(); - - if( cl.local.thirdperson ) - return true; - return false; -} - /* ==================== CL_CreatePlaylist @@ -154,7 +107,7 @@ CL_CreatePlaylist Create a default valve playlist ==================== */ -void CL_CreatePlaylist( const char *filename ) +static void CL_CreatePlaylist( const char *filename ) { file_t *f; @@ -200,7 +153,7 @@ CL_InitCDAudio Initialize CD playlist ==================== */ -void CL_InitCDAudio( const char *filename ) +static void CL_InitCDAudio( const char *filename ) { byte *afile; char *pfile; @@ -221,12 +174,17 @@ void CL_InitCDAudio( const char *filename ) // format: trackname\n [num] while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { - if( !Q_stricmp( token, "blank" )) token[0] = '\0'; - Q_strncpy( clgame.cdtracks[c], token, sizeof( clgame.cdtracks[0] )); + if( !Q_stricmp( token, "blank" )) + clgame.cdtracks[c][0] = '\0'; + else + { + Q_snprintf( clgame.cdtracks[c], sizeof( clgame.cdtracks[c] ), + "media/%s", token ); + } if( ++c > MAX_CDTRACKS - 1 ) { - Con_Reportf( S_WARN "CD_Init: too many tracks %i in %s\n", MAX_CDTRACKS, filename ); + Con_Reportf( S_WARN "%s: too many tracks %i in %s\n", __func__, MAX_CDTRACKS, filename ); break; } } @@ -234,22 +192,6 @@ void CL_InitCDAudio( const char *filename ) Mem_Free( afile ); } -/* -==================== -CL_PointContents - -Return contents for point -==================== -*/ -int CL_PointContents( const vec3_t p ) -{ - int cont = PM_PointContents( clgame.pmove, p ); - - if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) - cont = CONTENTS_WATER; - return cont; -} - /* ============= CL_AdjustXPos @@ -263,18 +205,18 @@ static int CL_AdjustXPos( float x, int width, int totalWidth ) if( x == -1 ) { - xPos = ( refState.width - width ) * 0.5f; + xPos = ( clgame.scrInfo.iWidth - width ) * 0.5f; } else { if ( x < 0 ) - xPos = (1.0f + x) * refState.width - totalWidth; // Alight right + xPos = (1.0f + x) * clgame.scrInfo.iWidth - totalWidth; // Alight right else // align left - xPos = x * refState.width; + xPos = x * clgame.scrInfo.iWidth; } - if( xPos + width > refState.width ) - xPos = refState.width - width; + if( xPos + width > clgame.scrInfo.iWidth ) + xPos = clgame.scrInfo.iWidth - width; else if( xPos < 0 ) xPos = 0; @@ -294,19 +236,19 @@ static int CL_AdjustYPos( float y, int height ) if( y == -1 ) // centered? { - yPos = ( refState.height - height ) * 0.5f; + yPos = ( clgame.scrInfo.iHeight - height ) * 0.5f; } else { // Alight bottom? if( y < 0 ) - yPos = (1.0f + y) * refState.height - height; // Alight bottom + yPos = (1.0f + y) * clgame.scrInfo.iHeight - height; // Alight bottom else // align top - yPos = y * refState.height; + yPos = y * clgame.scrInfo.iHeight; } - if( yPos + height > refState.height ) - yPos = refState.height - height; + if( yPos + height > clgame.scrInfo.iHeight ) + yPos = clgame.scrInfo.iHeight - height; else if( yPos < 0 ) yPos = 0; @@ -322,35 +264,25 @@ print centerscreen message */ void CL_CenterPrint( const char *text, float y ) { - int length = 0; - int width = 0; - char *s; + cl_font_t *font = Con_GetCurFont(); - if( !COM_CheckString( text )) + if( !COM_CheckString( text ) || !font || !font->valid ) return; - clgame.centerPrint.lines = 1; clgame.centerPrint.totalWidth = 0; clgame.centerPrint.time = cl.mtime[0]; // allow pause for centerprint Q_strncpy( clgame.centerPrint.message, text, sizeof( clgame.centerPrint.message )); - s = clgame.centerPrint.message; - // count the number of lines for centering - while( *s ) - { - if( *s == '\n' ) - { - clgame.centerPrint.lines++; - if( width > clgame.centerPrint.totalWidth ) - clgame.centerPrint.totalWidth = width; - width = 0; - } - else width += clgame.scrInfo.charWidths[*s]; - s++; - length++; - } + CL_DrawStringLen( font, + clgame.centerPrint.message, + &clgame.centerPrint.totalWidth, + &clgame.centerPrint.totalHeight, + FONT_DRAW_HUD | FONT_DRAW_UTF8 ); + + if( font->charHeight ) + clgame.centerPrint.lines = clgame.centerPrint.totalHeight / font->charHeight; + else clgame.centerPrint.lines = 1; - clgame.centerPrint.totalHeight = ( clgame.centerPrint.lines * clgame.scrInfo.iCharHeight ); clgame.centerPrint.y = CL_AdjustYPos( y, clgame.centerPrint.totalHeight ); } @@ -361,102 +293,47 @@ SPR_AdjustSize draw hudsprite routine ==================== */ -static void SPR_AdjustSize( float *x, float *y, float *w, float *h ) +void SPR_AdjustSize( float *x, float *y, float *w, float *h ) { float xscale, yscale; + if( refState.width == clgame.scrInfo.iWidth && refState.height == clgame.scrInfo.iHeight ) + return; + // scale for screen sizes xscale = refState.width / (float)clgame.scrInfo.iWidth; yscale = refState.height / (float)clgame.scrInfo.iHeight; - if( x ) *x *= xscale; - if( y ) *y *= yscale; - if( w ) *w *= xscale; - if( h ) *h *= yscale; + *x *= xscale; + *y *= yscale; + *w *= xscale; + *h *= yscale; } -/* -==================== -SPR_AdjustSize - -draw hudsprite routine -==================== -*/ -static void SPR_AdjustSizei( int *x, int *y, int *w, int *h ) +static void SPR_AdjustTexCoords( int texnum, float width, float height, float *s1, float *t1, float *s2, float *t2 ) { - float xscale, yscale; + const qboolean filtering = REF_GET_PARM( PARM_TEX_FILTERING, texnum ); + const int xremainder = refState.width % clgame.scrInfo.iWidth; + const int yremainder = refState.height % clgame.scrInfo.iHeight; - // scale for screen sizes - xscale = refState.width / (float)clgame.scrInfo.iWidth; - yscale = refState.height / (float)clgame.scrInfo.iHeight; - - if( x ) *x *= xscale; - if( y ) *y *= yscale; - if( w ) *w *= xscale; - if( h ) *h *= yscale; -} - -/* -==================== -PictAdjustSize - -draw hudsprite routine -==================== -*/ -void PicAdjustSize( float *x, float *y, float *w, float *h ) -{ - if( !clgame.ds.adjust_size ) return; - - SPR_AdjustSize( x, y, w, h ); -} - -static qboolean SPR_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) -{ - float dudx, dvdy; - - // clip sub rect to sprite - if(( width == 0 ) || ( height == 0 )) - return false; - - if( *x + *width <= clgame.ds.scissor_x ) - return false; - if( *x >= clgame.ds.scissor_x + clgame.ds.scissor_width ) - return false; - if( *y + *height <= clgame.ds.scissor_y ) - return false; - if( *y >= clgame.ds.scissor_y + clgame.ds.scissor_height ) - return false; - - dudx = (*u1 - *u0) / *width; - dvdy = (*v1 - *v0) / *height; - - if( *x < clgame.ds.scissor_x ) + if(( filtering || xremainder ) && refState.width != clgame.scrInfo.iWidth ) { - *u0 += (clgame.ds.scissor_x - *x) * dudx; - *width -= clgame.ds.scissor_x - *x; - *x = clgame.ds.scissor_x; + // align to texel if scaling + *s1 += 0.5f; + *s2 -= 0.5f; } - if( *x + *width > clgame.ds.scissor_x + clgame.ds.scissor_width ) + if(( filtering || yremainder ) && refState.height != clgame.scrInfo.iHeight ) { - *u1 -= (*x + *width - (clgame.ds.scissor_x + clgame.ds.scissor_width)) * dudx; - *width = clgame.ds.scissor_x + clgame.ds.scissor_width - *x; + // align to texel if scaling + *t1 += 0.5f; + *t2 -= 0.5f; } - if( *y < clgame.ds.scissor_y ) - { - *v0 += (clgame.ds.scissor_y - *y) * dvdy; - *height -= clgame.ds.scissor_y - *y; - *y = clgame.ds.scissor_y; - } - - if( *y + *height > clgame.ds.scissor_y + clgame.ds.scissor_height ) - { - *v1 -= (*y + *height - (clgame.ds.scissor_y + clgame.ds.scissor_height)) * dvdy; - *height = clgame.ds.scissor_y + clgame.ds.scissor_height - *y; - } - - return true; + *s1 /= width; + *t1 /= height; + *s2 /= width; + *t2 /= height; } /* @@ -482,11 +359,11 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei height = h; } + texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame ); + if( prc ) { - wrect_t rc; - - rc = *prc; + wrect_t rc = *prc; // Sigh! some stupid modmakers set wrong rectangles in hud.txt if( rc.left <= 0 || rc.left >= width ) rc.left = 0; @@ -494,11 +371,13 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei if( rc.right <= 0 || rc.right > width ) rc.right = width; if( rc.bottom <= 0 || rc.bottom > height ) rc.bottom = height; + s1 = rc.left; + t1 = rc.top; + s2 = rc.right; + t2 = rc.bottom; + // calc user-defined rectangle - s1 = (float)rc.left / width; - t1 = (float)rc.top / height; - s2 = (float)rc.right / width; - t2 = (float)rc.bottom / height; + SPR_AdjustTexCoords( texnum, width, height, &s1, &t1, &s2, &t2 ); width = rc.right - rc.left; height = rc.bottom - rc.top; } @@ -509,12 +388,11 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei } // pass scissor test if supposed - if( clgame.ds.scissor_test && !SPR_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + if( !CL_Scissor( &clgame.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; // scale for screen sizes SPR_AdjustSize( &x, &y, &width, &height ); - texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame ); ref.dllFuncs.Color4ub( clgame.ds.spriteColor[0], clgame.ds.spriteColor[1], clgame.ds.spriteColor[2], clgame.ds.spriteColor[3] ); ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, texnum ); } @@ -528,6 +406,7 @@ called each frame */ void CL_DrawCenterPrint( void ) { + cl_font_t *font = Con_GetCurFont(); char *pText; int i, j, x, y; int width, lineLength; @@ -537,7 +416,7 @@ void CL_DrawCenterPrint( void ) if( !clgame.centerPrint.time ) return; - if(( cl.time - clgame.centerPrint.time ) >= scr_centertime->value ) + if(( cl.time - clgame.centerPrint.time ) >= scr_centertime.value ) { // time expired clgame.centerPrint.time = 0.0f; @@ -547,8 +426,9 @@ void CL_DrawCenterPrint( void ) y = clgame.centerPrint.y; // start y colorDefault = g_color_table[7]; pText = clgame.centerPrint.message; - Con_DrawCharacterLen( 0, NULL, &charHeight ); + CL_DrawCharacterLen( font, 0, NULL, &charHeight ); + CL_SetFontRendermode( font ); for( i = 0; i < clgame.centerPrint.lines; i++ ) { lineLength = 0; @@ -556,12 +436,15 @@ void CL_DrawCenterPrint( void ) while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH ) { - byte c = *pText; - line[lineLength] = c; - Con_DrawCharacterLen( c, &charWidth, NULL ); + int number = Con_UtfProcessChar(( byte ) * pText ); + pText++; + if( number == 0 ) + continue; + + line[lineLength] = number; + CL_DrawCharacterLen( font, number, &charWidth, NULL ); width += charWidth; lineLength++; - pText++; } if( lineLength == MAX_LINELENGTH ) @@ -575,12 +458,47 @@ void CL_DrawCenterPrint( void ) for( j = 0; j < lineLength; j++ ) { if( x >= 0 && y >= 0 && x <= refState.width ) - x += Con_DrawCharacter( x, y, line[j], colorDefault ); + x += CL_DrawCharacter( x, y, line[j], colorDefault, font, FONT_DRAW_HUD | FONT_DRAW_NORENDERMODE ); } y += charHeight; } } +static int V_FadeAlpha( screenfade_t *sf ) +{ + int alpha; + + if( cl.time > sf->fadeReset && cl.time > sf->fadeEnd ) + { + if( !FBitSet( sf->fadeFlags, FFADE_STAYOUT )) + return 0; + } + + if( FBitSet( sf->fadeFlags, FFADE_STAYOUT )) + { + alpha = sf->fadealpha; + if( FBitSet( sf->fadeFlags, FFADE_OUT ) && sf->fadeTotalEnd > cl.time ) + { + alpha += sf->fadeSpeed * ( sf->fadeTotalEnd - cl.time ); + } + else + { + sf->fadeEnd = cl.time + 0.1; + } + } + else + { + alpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time ); + if( FBitSet( sf->fadeFlags, FFADE_OUT )) + { + alpha += sf->fadealpha; + } + } + alpha = bound( 0, alpha, sf->fadealpha ); + + return alpha; +} + /* ============= CL_DrawScreenFade @@ -589,44 +507,39 @@ fill screen with specfied color can be modulated ============= */ -void CL_DrawScreenFade( void ) +static void CL_DrawScreenFade( void ) { screenfade_t *sf = &clgame.fade; - int iFadeAlpha, testFlags; + int alpha; - // keep pushing reset time out indefinitely - if( sf->fadeFlags & FFADE_STAYOUT ) - sf->fadeReset = cl.time + 0.1f; + alpha = V_FadeAlpha( sf ); - if( sf->fadeReset == 0.0f && sf->fadeEnd == 0.0f ) - return; // inactive - - // all done? - if(( cl.time > sf->fadeReset ) && ( cl.time > sf->fadeEnd )) - { - memset( &clgame.fade, 0, sizeof( clgame.fade )); + if( !alpha ) return; - } - testFlags = (sf->fadeFlags & ~FFADE_MODULATE); - - // fading... - if( testFlags == FFADE_STAYOUT ) + if( !FBitSet( sf->fadeFlags, FFADE_MODULATE )) { - iFadeAlpha = sf->fadealpha; + ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); + ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha ); + } + else if( Host_IsQuakeCompatible( )) + { + // Quake Wrapper and Quake Remake use FFADE_MODULATE for item pickups + // so hack the check here + ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); + ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha ); } else { - iFadeAlpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time ); - if( sf->fadeFlags & FFADE_OUT ) iFadeAlpha += sf->fadealpha; - iFadeAlpha = bound( 0, iFadeAlpha, sf->fadealpha ); + ref.dllFuncs.GL_SetRenderMode( kRenderScreenFadeModulate ); + + ref.dllFuncs.Color4ub( + (uint16_t)( sf->fader * alpha + ( 255 - alpha ) * 255 ) >> 8, + (uint16_t)( sf->fadeg * alpha + ( 255 - alpha ) * 255 ) >> 8, + (uint16_t)( sf->fadeb * alpha + ( 255 - alpha ) * 255 ) >> 8, + 255 ); } - ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, iFadeAlpha ); - - if( sf->fadeFlags & FFADE_MODULATE ) - ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); - else ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); ref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1, R_GetBuiltinTexture( REF_WHITE_TEXTURE )); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); @@ -649,7 +562,11 @@ static void CL_InitTitles( const char *filename ) // initialize text messages (game_text) for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { - cl_textmessage[i].pName = _copystring( clgame.mempool, va( TEXT_MSGNAME, i ), __FILE__, __LINE__ ); + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); + + cl_textmessage[i].pName = copystringpool( clgame.mempool, name ); cl_textmessage[i].pMessage = cl_textbuffer[i]; } @@ -715,12 +632,12 @@ void CL_ParseTextMessage( sizebuf_t *msg ) text->g2 = MSG_ReadByte( msg ); text->b2 = MSG_ReadByte( msg ); text->a2 = MSG_ReadByte( msg ); - text->fadein = (float)(MSG_ReadShort( msg ) / 256.0f ); - text->fadeout = (float)(MSG_ReadShort( msg ) / 256.0f ); - text->holdtime = (float)(MSG_ReadShort( msg ) / 256.0f ); + text->fadein = (float)(MSG_ReadWord( msg ) / 256.0f ); + text->fadeout = (float)(MSG_ReadWord( msg ) / 256.0f ); + text->holdtime = (float)(MSG_ReadWord( msg ) / 256.0f ); if( text->effect == 2 ) - text->fxtime = (float)(MSG_ReadShort( msg ) / 256.0f ); + text->fxtime = (float)(MSG_ReadWord( msg ) / 256.0f ); else text->fxtime = 0.0f; // to prevent grab too long messages @@ -777,23 +694,6 @@ void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ) CL_HudMessage( text->pName ); } -/* -==================== -CL_GetLocalPlayer - -Render callback for studio models -==================== -*/ -cl_entity_t *CL_GetLocalPlayer( void ) -{ - cl_entity_t *player; - - player = CL_EDICT_NUM( cl.playernum + 1 ); - Assert( player != NULL ); - - return player; -} - /* ==================== CL_GetMaxlients @@ -813,7 +713,7 @@ CL_SoundFromIndex return soundname from index ==================== */ -const char *CL_SoundFromIndex( int index ) +static const char *CL_SoundFromIndex( int index ) { sfx_t *sfx = NULL; int hSound; @@ -824,20 +724,106 @@ const char *CL_SoundFromIndex( int index ) if( !hSound ) { - Con_DPrintf( S_ERROR "CL_SoundFromIndex: invalid sound index %i\n", index ); + Con_DPrintf( S_ERROR "%s: invalid sound index %i\n", __func__, index ); return NULL; } sfx = S_GetSfxByHandle( hSound ); if( !sfx ) { - Con_DPrintf( S_ERROR "CL_SoundFromIndex: bad sfx for index %i\n", index ); + Con_DPrintf( S_ERROR "%s: bad sfx for index %i\n", __func__, index ); return NULL; } return sfx->name; } +/* +================ +CL_EnableScissor + +enable scissor test +================ +*/ +void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height ) +{ + scissor->x = x; + scissor->y = y; + scissor->width = width; + scissor->height = height; + scissor->test = true; +} + +/* +================ +CL_DisableScissor + +disable scissor test +================ +*/ +void CL_DisableScissor( scissor_state_t *scissor ) +{ + scissor->test = false; +} + +/* +================ +CL_Scissor + +perform common scissor test +================ +*/ +qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) +{ + float dudx, dvdy; + + if( !scissor->test ) + return true; + + // clip sub rect to sprite + if( *width == 0 || *height == 0 ) + return false; + + if( *x + *width <= scissor->x ) + return false; + if( *x >= scissor->x + scissor->width ) + return false; + if( *y + *height <= scissor->y ) + return false; + if( *y >= scissor->y + scissor->height ) + return false; + + dudx = (*u1 - *u0) / *width; + dvdy = (*v1 - *v0) / *height; + + if( *x < scissor->x ) + { + *u0 += (scissor->x - *x) * dudx; + *width -= scissor->x - *x; + *x = scissor->x; + } + + if( *x + *width > scissor->x + scissor->width ) + { + *u1 -= (*x + *width - (scissor->x + scissor->width)) * dudx; + *width = scissor->x + scissor->width - *x; + } + + if( *y < scissor->y ) + { + *v0 += (scissor->y - *y) * dvdy; + *height -= scissor->y - *y; + *y = scissor->y; + } + + if( *y + *height > scissor->y + scissor->height ) + { + *v1 -= (*y + *height - (scissor->y + scissor->height)) * dvdy; + *height = scissor->y + scissor->height - *y; + } + return true; +} + /* ========= SPR_EnableScissor @@ -852,11 +838,7 @@ static void GAME_EXPORT SPR_EnableScissor( int x, int y, int width, int height ) width = bound( 0, width, clgame.scrInfo.iWidth - x ); height = bound( 0, height, clgame.scrInfo.iHeight - y ); - clgame.ds.scissor_x = x; - clgame.ds.scissor_width = width; - clgame.ds.scissor_y = y; - clgame.ds.scissor_height = height; - clgame.ds.scissor_test = true; + CL_EnableScissor( &clgame.ds.scissor, x, y, width, height ); } /* @@ -867,11 +849,7 @@ SPR_DisableScissor */ static void GAME_EXPORT SPR_DisableScissor( void ) { - clgame.ds.scissor_x = 0; - clgame.ds.scissor_width = 0; - clgame.ds.scissor_y = 0; - clgame.ds.scissor_height = 0; - clgame.ds.scissor_test = false; + CL_DisableScissor( &clgame.ds.scissor ); } /* @@ -881,12 +859,12 @@ CL_DrawCrosshair Render crosshair ==================== */ -void CL_DrawCrosshair( void ) +static void CL_DrawCrosshair( void ) { int x, y, width, height; float xscale, yscale; - if( !clgame.ds.pCrosshair || !cl_crosshair->value ) + if( !clgame.ds.pCrosshair || !cl_crosshair.value ) return; // any camera on or client is died @@ -941,28 +919,21 @@ CL_DrawLoading draw loading progress bar ============= */ -static void CL_DrawLoadingOrPaused( qboolean paused, float percent ) +static void CL_DrawLoadingOrPaused( int tex ) { - int x, y, width, height, right; + float x, y, width, height; + int iWidth, iHeight; - R_GetTextureParms( &width, &height, paused ? cls.pauseIcon : cls.loadingBar ); - x = ( clgame.scrInfo.iWidth - width ) >> 1; - y = ( clgame.scrInfo.iHeight - height) >> 1; + R_GetTextureParms( &iWidth, &iHeight, tex ); + x = ( clgame.scrInfo.iWidth - iWidth ) / 2.0f; + y = ( clgame.scrInfo.iHeight - iHeight ) / 2.0f; + width = iWidth; + height = iHeight; - SPR_AdjustSizei( &x, &y, &width, &height ); - - if( !paused ) - { - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); - ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar ); - } - else - { - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); - ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.pauseIcon ); - } + SPR_AdjustSize( &x, &y, &width, &height ); + ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); + ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); + ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, tex ); } void CL_DrawHUD( int state ) @@ -988,15 +959,20 @@ void CL_DrawHUD( int state ) CL_DrawCrosshair (); CL_DrawCenterPrint (); clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission ); - CL_DrawLoadingOrPaused( true, 0.0f ); + if( showpause.value ) + { + if( !cls.pauseIcon ) + cls.pauseIcon = SCR_LoadPauseIcon(); + CL_DrawLoadingOrPaused( Q_max( 0, cls.pauseIcon )); + } break; case CL_LOADING: - CL_DrawLoadingOrPaused( false, scr_loading->value ); + CL_DrawLoadingOrPaused( cls.loadingBar ); break; case CL_CHANGELEVEL: if( cls.draw_changelevel ) { - CL_DrawLoadingOrPaused( false, 100.0f ); + CL_DrawLoadingOrPaused( cls.loadingBar ); cls.draw_changelevel = false; } break; @@ -1008,7 +984,7 @@ static void CL_ClearUserMessage( char *pszName, int svc_num ) int i; for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) - if( ( clgame.msg[i].number == svc_num ) && Q_strcmp( clgame.msg[i].name, pszName ) ) + if( ( clgame.msg[i].number == svc_num ) && Q_stricmp( clgame.msg[i].name, pszName ) ) clgame.msg[i].number = 0; } @@ -1017,10 +993,10 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) int i; if( !pszName || !*pszName ) - Host_Error( "CL_LinkUserMessage: bad message name\n" ); + Host_Error( "%s: bad message name\n", __func__ ); if( svc_num <= svc_lastmsg ) - Host_Error( "CL_LinkUserMessage: tried to hook a system message \"%s\"\n", svc_strings[svc_num] ); + Host_Error( "%s: tried to hook a system message \"%s\"\n", __func__, svc_strings[svc_num] ); // see if already hooked for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) @@ -1037,7 +1013,7 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) if( i == MAX_USER_MESSAGES ) { - Host_Error( "CL_LinkUserMessage: MAX_USER_MESSAGES hit!\n" ); + Host_Error( "%s: MAX_USER_MESSAGES hit!\n", __func__ ); return; } @@ -1048,23 +1024,18 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) CL_ClearUserMessage( pszName, svc_num ); } -void CL_FreeEntity( cl_entity_t *pEdict ) -{ - Assert( pEdict != NULL ); - R_RemoveEfrags( pEdict ); - CL_KillDeadBeams( pEdict ); -} - void CL_ClearWorld( void ) { - cl_entity_t *worldmodel; + if( clgame.entities ) // check if we have entities, legacy protocol support kinda breaks this logic + { + cl_entity_t *worldmodel = clgame.entities; - worldmodel = clgame.entities; - worldmodel->curstate.modelindex = 1; // world model - worldmodel->curstate.solid = SOLID_BSP; - worldmodel->curstate.movetype = MOVETYPE_PUSH; - worldmodel->model = cl.worldmodel; - worldmodel->index = 0; + worldmodel->curstate.modelindex = 1; // world model + worldmodel->curstate.solid = SOLID_BSP; + worldmodel->curstate.movetype = MOVETYPE_PUSH; + worldmodel->model = cl.worldmodel; + worldmodel->index = 0; + } world.max_recursion = 0; @@ -1072,18 +1043,18 @@ void CL_ClearWorld( void ) clgame.numStatics = 0; } -void CL_InitEdicts( void ) +void CL_InitEdicts( int maxclients ) { Assert( clgame.entities == NULL ); if( !clgame.mempool ) return; // Host_Error without client #if XASH_LOW_MEMORY != 2 - CL_UPDATE_BACKUP = ( cl.maxclients <= 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; + CL_UPDATE_BACKUP = ( maxclients <= 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; #endif cls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES; cls.packet_entities = Mem_Realloc( clgame.mempool, cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities ); clgame.entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities ); - clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); + clgame.static_entities = NULL; // will be initialized later clgame.numStatics = 0; if(( clgame.maxRemapInfos - 1 ) != clgame.maxEntities ) @@ -1093,12 +1064,12 @@ void CL_InitEdicts( void ) clgame.remap_info = (remap_info_t **)Mem_Calloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos ); } - ref.dllFuncs.R_ProcessEntData( true ); + ref.dllFuncs.R_ProcessEntData( true, clgame.entities, clgame.maxEntities ); } void CL_FreeEdicts( void ) { - ref.dllFuncs.R_ProcessEntData( false ); + ref.dllFuncs.R_ProcessEntData( false, NULL, 0 ); if( clgame.entities ) Mem_Free( clgame.entities ); @@ -1124,7 +1095,7 @@ void CL_ClearEdicts( void ) // in case we stopped with error clgame.maxEntities = 2; - CL_InitEdicts(); + CL_InitEdicts( cl.maxclients ); } /* @@ -1142,6 +1113,126 @@ void CL_ClearSpriteTextures( void ) clgame.sprites[i].needload = NL_UNREFERENCED; } +// it's a Valve default value for LoadMapSprite (probably must be power of two) +#define MAPSPRITE_SIZE 128 + +/* +==================== +Mod_LoadMapSprite + +Loading a bitmap image as sprite with multiple frames +as pieces of input image +==================== +*/ +static void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) +{ + rgbdata_t *pix, temp = { 0 }; + char texname[128]; + int i, w, h; + int xl, yl; + int numframes; + msprite_t *psprite; + char poolname[MAX_VA_STRING]; + + if( loaded ) *loaded = false; + Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); + Image_SetForceFlags( IL_OVERVIEW ); + pix = FS_LoadImage( texname, buffer, size ); + Image_ClearForceFlags(); + if( !pix ) return; // bad image or something else + + mod->type = mod_sprite; + + if( pix->width % MAPSPRITE_SIZE ) + w = pix->width - ( pix->width % MAPSPRITE_SIZE ); + else w = pix->width; + + if( pix->height % MAPSPRITE_SIZE ) + h = pix->height - ( pix->height % MAPSPRITE_SIZE ); + else h = pix->height; + + if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; + if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; + + // resample image if needed + Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f ); + + w = h = MAPSPRITE_SIZE; + + // check range + if( w > pix->width ) w = pix->width; + if( h > pix->height ) h = pix->height; + + // determine how many frames we needs + numframes = (pix->width * pix->height) / (w * h); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); + psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); + mod->cache.data = psprite; // make link to extradata + + psprite->type = SPR_FWD_PARALLEL_ORIENTED; + psprite->texFormat = SPR_ALPHTEST; + psprite->numframes = mod->numframes = numframes; + psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); + + mod->mins[0] = mod->mins[1] = -w / 2; + mod->maxs[0] = mod->maxs[1] = w / 2; + mod->mins[2] = -h / 2; + mod->maxs[2] = h / 2; + + // create a temporary pic + temp.width = w; + temp.height = h; + temp.type = pix->type; + temp.flags = pix->flags; + temp.size = w * h * PFDesc[temp.type].bpp; + temp.buffer = Mem_Malloc( mod->mempool, temp.size ); + temp.palette = NULL; + + // chop the image and upload into video memory + for( i = xl = yl = 0; i < numframes; i++ ) + { + mspriteframe_t *pspriteframe; + int xh = xl + w, yh = yl + h, x, y, j; + int linedelta = ( pix->width - w ) * 4; + byte *src = pix->buffer + ( yl * pix->width + xl ) * 4; + byte *dst = temp.buffer; + + // cut block from source + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + for( j = 0; j < 4; j++ ) + *dst++ = *src++; + src += linedelta; + } + + // build uinque frame name + Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); + + psprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t )); + pspriteframe = psprite->frames[i].frameptr; + pspriteframe->width = w; + pspriteframe->height = h; + pspriteframe->up = ( h >> 1 ); + pspriteframe->left = -( w >> 1 ); + pspriteframe->down = ( h >> 1 ) - h; + pspriteframe->right = w + -( w >> 1 ); + pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE ); + + xl += w; + if( xl >= pix->width ) + { + xl = 0; + yl += h; + } + } + + FS_FreeImage( pix ); + Mem_Free( temp.buffer ); + if( loaded ) *loaded = true; +} + /* ============= CL_LoadHudSprite @@ -1162,7 +1253,8 @@ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, // it's hud sprite, make difference names to prevent free shared textures if( type == SPR_CLIENT || type == SPR_HUDSPRITE ) SetBits( m_pSprite->flags, MODEL_CLIENT ); - m_pSprite->numtexinfo = texFlags; // store texFlags into numtexinfo + + m_pSprite->numtexinfo = texFlags; // store texFlags for renderer into numtexinfo if( !FS_FileExists( szSpriteName, false ) ) { @@ -1186,10 +1278,10 @@ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, return false; if( type == SPR_MAPSPRITE ) - ref.dllFuncs.Mod_LoadMapSprite( m_pSprite, buf, size, &loaded ); + Mod_LoadMapSprite( m_pSprite, buf, size, &loaded ); else { - Mod_LoadSpriteModel( m_pSprite, buf, &loaded, texFlags ); + Mod_LoadSpriteModel( m_pSprite, buf, &loaded ); ref.dllFuncs.Mod_ProcessRenderData( m_pSprite, true, buf ); } @@ -1218,22 +1310,18 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla { char name[MAX_QPATH]; model_t *mod; - int i; - - // use high indices for client sprites - // for GoldSrc bug-compatibility - const int start = type != SPR_HUDSPRITE ? MAX_CLIENT_SPRITES / 2 : 0; + int i, start; if( !COM_CheckString( filename )) { - Con_Reportf( S_ERROR "CL_LoadSpriteModel: bad name!\n" ); + Con_Reportf( S_ERROR "%s: bad name!\n", __func__ ); return NULL; } Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); - for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) + for( i = 0, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) { if( !Q_stricmp( mod->name, name )) { @@ -1250,8 +1338,15 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla } // find a free model slot spot - for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) - if( !mod->name[0] ) break; // this is a valid spot + // use low indices only for HUD sprites + // for GoldSrc bug compatibility + start = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2; + + for( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) + { + if( !mod->name[0] ) + break; // this is a valid spot + } if( i == MAX_CLIENT_SPRITES / 2 ) { @@ -1303,8 +1398,10 @@ HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) ========= pfnSPR_Load +function exported for support GoldSrc Monitor utility ========= */ +HSPRITE EXPORT pfnSPR_Load( const char *szPicName ); HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) { model_t *spr; @@ -1321,7 +1418,7 @@ CL_GetSpritePointer ============= */ -const model_t *CL_GetSpritePointer( HSPRITE hSprite ) +static const model_t *CL_GetSpritePointer( HSPRITE hSprite ) { model_t *mod; int index = hSprite - 1; @@ -1351,11 +1448,13 @@ const model_t *CL_GetSpritePointer( HSPRITE hSprite ) ========= pfnSPR_Frames +function exported for support GoldSrc Monitor utility ========= */ +int EXPORT pfnSPR_Frames( HSPRITE hPic ); int EXPORT pfnSPR_Frames( HSPRITE hPic ) { - int numFrames; + int numFrames = 0; ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic )); @@ -1370,7 +1469,7 @@ pfnSPR_Height */ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame ) { - int sprHeight; + int sprHeight = 0; ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic )); @@ -1385,7 +1484,7 @@ pfnSPR_Width */ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame ) { - int sprWidth; + int sprWidth = 0; ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic )); @@ -1400,7 +1499,13 @@ pfnSPR_Set */ static void GAME_EXPORT pfnSPR_Set( HSPRITE hPic, int r, int g, int b ) { - clgame.ds.pSprite = CL_GetSpritePointer( hPic ); + const model_t *sprite = CL_GetSpritePointer( hPic ); + + // a1ba: do not alter the state if invalid HSPRITE was passed + if( !sprite ) + return; + + clgame.ds.pSprite = sprite; clgame.ds.spriteColor[0] = bound( 0, r, 255 ); clgame.ds.spriteColor[1] = bound( 0, g, 255 ); clgame.ds.spriteColor[2] = bound( 0, b, 255 ); @@ -1415,7 +1520,7 @@ pfnSPR_Draw */ static void GAME_EXPORT pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc ) { - ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); + ref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha ); SPR_DrawGeneric( frame, x, y, -1, -1, prc ); } @@ -1470,12 +1575,12 @@ static void GAME_EXPORT pfnSPR_DrawAdditive( int frame, int x, int y, const wrec /* ========= -pfnSPR_GetList +SPR_GetList for parsing half-life scripts - hud.txt etc ========= */ -static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount ) +static client_sprite_t *SPR_GetList( char *psz, int *piCount ) { cached_spritelist_t *pEntry = &clgame.sprlist[0]; int slot, index, numSprites = 0; @@ -1501,7 +1606,7 @@ static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount ) if( slot == MAX_CLIENT_SPRITES ) { - Con_Printf( S_ERROR "SPR_GetList: overflow cache!\n" ); + Con_Printf( S_ERROR "%s: overflow cache!\n", __func__ ); return NULL; } @@ -1565,37 +1670,18 @@ CL_FillRGBA ============= */ -void GAME_EXPORT CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a ) +static void GAME_EXPORT CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a ) { - float _x = x, _y = y, _w = w, _h = h; + float x_ = x, y_ = y, w_ = w, h_ = h; r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); - SPR_AdjustSize( &_x, &_y, &_w, &_h ); + SPR_AdjustSize( &x_, &y_, &w_, &h_ ); -#if 1 - ref.dllFuncs.FillRGBA( _x, _y, _w, _h, r, g, b, a ); -#else - pglDisable( GL_TEXTURE_2D ); - pglEnable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f ); - - pglBegin( GL_QUADS ); - pglVertex2f( _x, _y ); - pglVertex2f( _x + _w, _y ); - pglVertex2f( _x + _w, _y + _h ); - pglVertex2f( _x, _y + _h ); - pglEnd (); - - pglColor3f( 1.0f, 1.0f, 1.0f ); - pglEnable( GL_TEXTURE_2D ); - pglDisable( GL_BLEND ); -#endif + ref.dllFuncs.FillRGBA( kRenderTransAdd, x_, y_, w_, h_, r, g, b, a ); } /* @@ -1607,23 +1693,44 @@ get actual screen info */ int GAME_EXPORT CL_GetScreenInfo( SCREENINFO *pscrinfo ) { - float scale_factor = hud_scale->value; + qboolean apply_scale_factor = false; // we don't want floating point inaccuracies + float scale_factor = hud_scale.value; + + if( FBitSet( hud_fontscale.flags, FCVAR_CHANGED )) + { + CL_FreeFont( &cls.creditsFont ); + SCR_LoadCreditsFont(); + + ClearBits( hud_fontscale.flags, FCVAR_CHANGED ); + } // setup screen info clgame.scrInfo.iSize = sizeof( clgame.scrInfo ); clgame.scrInfo.iFlags = SCRINFO_SCREENFLASH; - if( scale_factor && scale_factor != 1.0f) + if( hud_scale.value >= 320.0f && hud_scale.value >= hud_scale_minimal_width.value ) + { + scale_factor = refState.width / hud_scale.value; + apply_scale_factor = scale_factor > 1.0f; + } + else if( scale_factor && scale_factor != 1.0f ) + { + float scaled_width = (float)refState.width / scale_factor; + if( scaled_width >= hud_scale_minimal_width.value ) + apply_scale_factor = true; + } + + if( apply_scale_factor ) { clgame.scrInfo.iWidth = (float)refState.width / scale_factor; clgame.scrInfo.iHeight = (float)refState.height / scale_factor; - clgame.scrInfo.iFlags |= SCRINFO_STRETCHED; + SetBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED ); } else { clgame.scrInfo.iWidth = refState.width; clgame.scrInfo.iHeight = refState.height; - clgame.scrInfo.iFlags &= ~SCRINFO_STRETCHED; + ClearBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED ); } if( !pscrinfo ) return 0; @@ -1654,6 +1761,34 @@ static void GAME_EXPORT pfnSetCrosshair( HSPRITE hspr, wrect_t rc, int r, int g, clgame.ds.rcCrosshair = rc; } + +/* +============= +pfnCvar_RegisterVariable + +============= +*/ +static cvar_t *GAME_EXPORT pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ) +{ + // a1ba: try to mitigate outdated client.dll vulnerabilities + if( !Q_stricmp( szName, "motdfile" ) + || !Q_stricmp( szName, "sensitivity" )) + flags |= FCVAR_PRIVILEGED; + + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_CLIENTDLL )); +} + +static int GAME_EXPORT Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ) +{ + int flags = CMD_CLIENTDLL; + + // a1ba: try to mitigate outdated client.dll vulnerabilities + if( !Q_stricmp( cmd_name, "motd_write" )) + flags |= CMD_PRIVILEGED; + + return Cmd_AddCommandEx( cmd_name, function, "client command", flags, __func__ ); +} + /* ============= pfnHookUserMsg @@ -1677,7 +1812,7 @@ static int GAME_EXPORT pfnHookUserMsg( const char *pszName, pfnUserMsgHook pfn ) if( i == MAX_USER_MESSAGES ) { - Host_Error( "HookUserMsg: MAX_USER_MESSAGES hit!\n" ); + Host_Error( "%s: MAX_USER_MESSAGES hit!\n", __func__ ); return 0; } @@ -1696,14 +1831,12 @@ pfnServerCmd */ static int GAME_EXPORT pfnServerCmd( const char *szCmdString ) { - string buf; - if( !COM_CheckString( szCmdString )) return 0; // just like the client typed "cmd xxxxx" at the console - Q_snprintf( buf, sizeof( buf ) - 1, "cmd %s\n", szCmdString ); - Cbuf_AddText( buf ); + MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, szCmdString ); return 1; } @@ -1727,7 +1860,8 @@ static int GAME_EXPORT pfnClientCmd( const char *szCmdString ) else { // will exec later - Q_strncat( host.deferred_cmd, va( "%s\n", szCmdString ), sizeof( host.deferred_cmd )); + Q_strncat( host.deferred_cmd, szCmdString, sizeof( host.deferred_cmd )); + Q_strncat( host.deferred_cmd, "\n", sizeof( host.deferred_cmd )); } return 1; @@ -1830,7 +1964,11 @@ client_textmessage_t *CL_TextMessageGet( const char *pName ) // first check internal messages for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { - if( !Q_strcmp( pName, va( TEXT_MSGNAME, i ))) + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); + + if( !Q_strcmp( pName, name )) return cl_textmessage + i; } @@ -1852,24 +1990,13 @@ returns drawed chachter width (in real screen pixels) */ static int GAME_EXPORT pfnDrawCharacter( int x, int y, int number, int r, int g, int b ) { - if( !cls.creditsFont.valid ) - return 0; + rgba_t color = { r, g, b, 255 }; + int flags = FONT_DRAW_HUD; - if( hud_utf8->value ) - number = Con_UtfProcessChar( number ); + if( hud_utf8.value ) + flags |= FONT_DRAW_UTF8; - number &= 255; - - if( number < 32 ) return 0; - if( y < -clgame.scrInfo.iCharHeight ) - return 0; - - clgame.ds.adjust_size = true; - pfnPIC_Set( cls.creditsFont.hFontTexture, r, g, b, 255 ); - pfnPIC_DrawAdditive( x, y, -1, -1, &cls.creditsFont.fontRc[number] ); - clgame.ds.adjust_size = false; - - return clgame.scrInfo.charWidths[number]; + return CL_DrawCharacter( x, y, number, color, &cls.creditsFont, flags ); } /* @@ -1881,20 +2008,12 @@ drawing string like a console string */ int GAME_EXPORT pfnDrawConsoleString( int x, int y, char *string ) { - int drawLen; + cl_font_t *font = Con_GetFont( con_fontsize.value ); + rgba_t color; + Vector4Copy( clgame.ds.textColor, color ); + Vector4Set( clgame.ds.textColor, 255, 255, 255, 255 ); - if( !COM_CheckString( string )) - return 0; // silent ignore - Con_SetFont( con_fontsize->value ); - - clgame.ds.adjust_size = true; - drawLen = Con_DrawString( x, y, string, clgame.ds.textColor ); - MakeRGBA( clgame.ds.textColor, 255, 255, 255, 255 ); - clgame.ds.adjust_size = false; - - Con_RestoreFont(); - - return (x + drawLen); // exclude color prexfixes + return x + CL_DrawString( x, y, string, color, font, FONT_DRAW_UTF8 | FONT_DRAW_HUD ); } /* @@ -1922,9 +2041,10 @@ compute string length in screen pixels */ void GAME_EXPORT pfnDrawConsoleStringLen( const char *pText, int *length, int *height ) { - Con_SetFont( con_fontsize->value ); - Con_DrawStringLen( pText, length, height ); - Con_RestoreFont(); + cl_font_t *font = Con_GetFont( con_fontsize.value ); + + if( height ) *height = font->charHeight; + CL_DrawStringLen( font, pText, length, NULL, FONT_DRAW_UTF8 | FONT_DRAW_HUD ); } /* @@ -1968,8 +2088,9 @@ GetWindowCenterX static int GAME_EXPORT pfnGetWindowCenterX( void ) { int x = 0; + #if XASH_WIN32 - if( m_ignore->value ) + if( m_ignore.value ) { POINT pos; GetCursorPos( &pos ); @@ -1977,7 +2098,7 @@ static int GAME_EXPORT pfnGetWindowCenterX( void ) } #endif -#if XASH_SDL == 2 +#if XASH_SDL >= 2 SDL_GetWindowPosition( host.hWnd, &x, NULL ); #endif @@ -1993,8 +2114,9 @@ GetWindowCenterY static int GAME_EXPORT pfnGetWindowCenterY( void ) { int y = 0; + #if XASH_WIN32 - if( m_ignore->value ) + if( m_ignore.value ) { POINT pos; GetCursorPos( &pos ); @@ -2002,7 +2124,7 @@ static int GAME_EXPORT pfnGetWindowCenterY( void ) } #endif -#if XASH_SDL == 2 +#if XASH_SDL >= 2 SDL_GetWindowPosition( host.hWnd, NULL, &y ); #endif @@ -2084,7 +2206,7 @@ pfnGetViewModel ============= */ -cl_entity_t* GAME_EXPORT CL_GetViewModel( void ) +static cl_entity_t* GAME_EXPORT CL_GetViewModel( void ) { return &clgame.viewent; } @@ -2106,60 +2228,54 @@ pfnCalcShake ============= */ -void GAME_EXPORT pfnCalcShake( void ) +static void GAME_EXPORT pfnCalcShake( void ) { - int i; - float fraction, freq; - float localAmp; + screen_shake_t *const shake = &clgame.shake; + float frametime, fraction, freq; + int i; - if( clgame.shake.time == 0 ) - return; - - if(( cl.time > clgame.shake.time ) || clgame.shake.amplitude <= 0 || clgame.shake.frequency <= 0 ) + if( cl.time > shake->time || shake->amplitude <= 0 || shake->frequency <= 0 || shake->duration <= 0 ) { - memset( &clgame.shake, 0, sizeof( clgame.shake )); + // reset shake + if( shake->time != 0 ) + { + shake->time = 0; + shake->applied_angle = 0; + VectorClear( shake->applied_offset ); + } + return; } - if( cl.time > clgame.shake.next_shake ) - { - // higher frequency means we recalc the extents more often and perturb the display again - clgame.shake.next_shake = cl.time + ( 1.0f / clgame.shake.frequency ); + frametime = cl_clientframetime(); - // compute random shake extents (the shake will settle down from this) + if( cl.time > shake->next_shake ) + { + // get next shake time based on frequency over duration + shake->next_shake = (float)cl.time + shake->frequency / shake->duration; + + // randomize each shake for( i = 0; i < 3; i++ ) - clgame.shake.offset[i] = COM_RandomFloat( -clgame.shake.amplitude, clgame.shake.amplitude ); - clgame.shake.angle = COM_RandomFloat( -clgame.shake.amplitude * 0.25f, clgame.shake.amplitude * 0.25f ); + shake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude ); + shake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f ); } - // ramp down amplitude over duration (fraction goes from 1 to 0 linearly with slope 1/duration) - fraction = ( clgame.shake.time - cl.time ) / clgame.shake.duration; + // get initial fraction and frequency values over the duration + fraction = ((float)cl.time - shake->time ) / shake->duration; + freq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f; - // ramp up frequency over duration - if( fraction ) - { - freq = ( clgame.shake.frequency / fraction ); - } - else - { - freq = 0; - } + // quickly approach zero but apply time over sine wave + fraction *= fraction * sin( cl.time * freq ); - // square fraction to approach zero more quickly - fraction *= fraction; + // apply shake offset + for( i = 0; i < 3; i++ ) + shake->applied_offset[i] = shake->offset[i] * fraction; - // Sine wave that slowly settles to zero - fraction = fraction * sin( cl.time * freq ); + // apply roll angle + shake->applied_angle = shake->angle * fraction; - // add to view origin - VectorScale( clgame.shake.offset, fraction, clgame.shake.applied_offset ); - - // add to roll - clgame.shake.applied_angle = clgame.shake.angle * fraction; - - // drop amplitude a bit, less for higher frequency shakes - localAmp = clgame.shake.amplitude * ( host.frametime / ( clgame.shake.duration * clgame.shake.frequency )); - clgame.shake.amplitude -= localAmp; + // decrease amplitude, but slower on longer shakes or higher frequency + shake->amplitude -= shake->amplitude * ( frametime / ( shake->frequency * shake->duration )); } /* @@ -2168,10 +2284,13 @@ pfnApplyShake ============= */ -void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor ) +static void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor ) { - if( origin ) VectorMA( origin, factor, clgame.shake.applied_offset, origin ); - if( angles ) angles[ROLL] += clgame.shake.applied_angle * factor; + if( origin ) + VectorMA( origin, factor, clgame.shake.applied_offset, origin ); + + if( angles ) + angles[ROLL] += clgame.shake.applied_angle * factor; } /* @@ -2191,45 +2310,14 @@ pfnPointContents ============= */ -static int GAME_EXPORT pfnPointContents( const float *p, int *truecontents ) +int GAME_EXPORT PM_CL_PointContents( const float *p, int *truecontents ) { - int cont, truecont; - - truecont = cont = PM_PointContents( clgame.pmove, p ); - if( truecontents ) *truecontents = truecont; - - if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) - cont = CONTENTS_WATER; - return cont; + return PM_PointContentsPmove( clgame.pmove, p, truecontents ); } -/* -============= -pfnTraceLine - -============= -*/ -static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) +pmtrace_t *PM_CL_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) { - static pmtrace_t tr; - int old_usehull; - - old_usehull = clgame.pmove->usehull; - clgame.pmove->usehull = usehull; - - switch( flags ) - { - case PM_TRACELINE_PHYSENTSONLY: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); - break; - case PM_TRACELINE_ANYVISIBLE: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL ); - break; - } - - clgame.pmove->usehull = old_usehull; - - return &tr; + return PM_TraceLine( clgame.pmove, start, end, flags, usehull, ignore_pe ); } static void GAME_EXPORT pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin ) @@ -2276,7 +2364,7 @@ static void GAME_EXPORT pfnHookEvent( const char *filename, pfnEventHook pfn ) if( !Q_stricmp( name, ev->name ) && ev->func != NULL ) { - Con_Reportf( S_WARN "CL_HookEvent: %s already hooked!\n", name ); + Con_Reportf( S_WARN "%s: %s already hooked!\n", __func__, name ); return; } } @@ -2300,7 +2388,7 @@ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname ) if( eventIndex >= MAX_EVENTS ) return; - if( entnum < 0 || entnum > clgame.maxEntities ) + if( entnum < 0 || entnum >= clgame.maxEntities ) return; es = &cl.events; @@ -2324,7 +2412,7 @@ pfnPlaySound ============= */ -void GAME_EXPORT pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch ) +static void GAME_EXPORT pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch ) { S_StartSound( org, ent, chan, S_RegisterSound( samp ), vol, attn, pitch, flags ); } @@ -2335,11 +2423,10 @@ CL_FindModelIndex ============= */ -int GAME_EXPORT CL_FindModelIndex( const char *m ) +static int GAME_EXPORT CL_FindModelIndex( const char *m ) { - char filepath[MAX_QPATH]; - static float lasttimewarn; - int i; + char filepath[MAX_QPATH]; + int i; if( !COM_CheckString( m )) return 0; @@ -2356,13 +2443,6 @@ int GAME_EXPORT CL_FindModelIndex( const char *m ) return i+1; } - if( lasttimewarn < host.realtime ) - { - // tell user about problem (but don't spam console) - Con_Printf( S_ERROR "Could not find index for model %s: not precached\n", filepath ); - lasttimewarn = host.realtime + 1.0f; - } - return 0; } @@ -2372,7 +2452,7 @@ pfnIsLocal ============= */ -int GAME_EXPORT pfnIsLocal( int playernum ) +static int GAME_EXPORT pfnIsLocal( int playernum ) { if( playernum == cl.playernum ) return true; @@ -2385,7 +2465,7 @@ pfnLocalPlayerDucking ============= */ -int GAME_EXPORT pfnLocalPlayerDucking( void ) +static int GAME_EXPORT pfnLocalPlayerDucking( void ) { return (cl.local.usehull == 1) ? true : false; } @@ -2396,7 +2476,7 @@ pfnLocalPlayerViewheight ============= */ -void GAME_EXPORT pfnLocalPlayerViewheight( float *view_ofs ) +static void GAME_EXPORT pfnLocalPlayerViewheight( float *view_ofs ) { if( view_ofs ) VectorCopy( cl.viewheight, view_ofs ); } @@ -2407,12 +2487,12 @@ pfnLocalPlayerBounds ============= */ -void GAME_EXPORT pfnLocalPlayerBounds( int hull, float *mins, float *maxs ) +static void GAME_EXPORT pfnLocalPlayerBounds( int hull, float *mins, float *maxs ) { if( hull >= 0 && hull < 4 ) { - if( mins ) VectorCopy( clgame.pmove->player_mins[hull], mins ); - if( maxs ) VectorCopy( clgame.pmove->player_maxs[hull], maxs ); + if( mins ) VectorCopy( host.player_mins[hull], mins ); + if( maxs ) VectorCopy( host.player_maxs[hull], maxs ); } } @@ -2422,7 +2502,7 @@ pfnIndexFromTrace ============= */ -int GAME_EXPORT pfnIndexFromTrace( struct pmtrace_s *pTrace ) +static int GAME_EXPORT pfnIndexFromTrace( struct pmtrace_s *pTrace ) { #if 0 // Velaron: breaks compatibility with mods that call the function after CL_PopPMStates if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent ) @@ -2457,7 +2537,7 @@ pfnGetVisent ============= */ -physent_t *pfnGetVisent( int idx ) +static physent_t *pfnGetVisent( int idx ) { if( idx >= 0 && idx < clgame.pmove->numvisent ) { @@ -2467,16 +2547,60 @@ physent_t *pfnGetVisent( int idx ) return NULL; } +static int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags ) +{ + return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags ); +} + +/* +============= +CL_PushTraceBounds + +============= +*/ +static void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs ) +{ + if( !host.trace_bounds_pushed ) + { + memcpy( host.player_mins_backup, host.player_mins, sizeof( host.player_mins_backup )); + memcpy( host.player_maxs_backup, host.player_maxs, sizeof( host.player_maxs_backup )); + + host.trace_bounds_pushed = true; + } + + hullnum = bound( 0, hullnum, 3 ); + VectorCopy( mins, host.player_mins[hullnum] ); + VectorCopy( maxs, host.player_maxs[hullnum] ); +} + +/* +============= +CL_PopTraceBounds + +============= +*/ +static void GAME_EXPORT CL_PopTraceBounds( void ) +{ + if( !host.trace_bounds_pushed ) + { + Con_Reportf( S_ERROR "%s called without push!\n", __func__ ); + return; + } + + host.trace_bounds_pushed = false; + memcpy( host.player_mins, host.player_mins_backup, sizeof( host.player_mins )); + memcpy( host.player_maxs, host.player_maxs_backup, sizeof( host.player_maxs )); +} + /* ============= pfnSetTraceHull ============= */ -void GAME_EXPORT CL_SetTraceHull( int hull ) +static void GAME_EXPORT CL_SetTraceHull( int hull ) { clgame.pmove->usehull = bound( 0, hull, 3 ); - } /* @@ -2485,7 +2609,7 @@ pfnPlayerTrace ============= */ -void GAME_EXPORT CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ) +static void GAME_EXPORT CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ) { if( !tr ) return; *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); @@ -2497,7 +2621,7 @@ pfnPlayerTraceExt ============= */ -void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ) +static void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ) { if( !tr ) return; *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pfnIgnore ); @@ -2505,19 +2629,13 @@ void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, in /* ============= -pfnTraceTexture +CL_TraceTexture ============= */ -static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) +const char * GAME_EXPORT PM_CL_TraceTexture( int ground, float *vstart, float *vend ) { - physent_t *pe; - - if( ground < 0 || ground >= clgame.pmove->numphysent ) - return NULL; // bad ground - - pe = &clgame.pmove->physents[ground]; - return PM_TraceTexture( pe, vstart, vend ); + return PM_TraceTexture( clgame.pmove, ground, vstart, vend ); } /* @@ -2528,13 +2646,7 @@ pfnTraceSurface */ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) { - physent_t *pe; - - if( ground < 0 || ground >= clgame.pmove->numphysent ) - return NULL; // bad ground - - pe = &clgame.pmove->physents[ground]; - return PM_TraceSurface( pe, vstart, vend ); + return PM_TraceSurfacePmove( clgame.pmove, ground, vstart, vend ); } /* @@ -2543,7 +2655,7 @@ pfnGetMovevars ============= */ -movevars_t *pfnGetMoveVars( void ) +static movevars_t *pfnGetMoveVars( void ) { return &clgame.movevars; } @@ -2554,7 +2666,7 @@ pfnStopAllSounds ============= */ -void GAME_EXPORT pfnStopAllSounds( int ent, int entchannel ) +static void GAME_EXPORT pfnStopAllSounds( int ent, int entchannel ) { S_StopSound( ent, entchannel, NULL ); } @@ -2579,7 +2691,7 @@ model_t *CL_LoadModel( const char *modelname, int *index ) return CL_ModelHandle( i ); } -int GAME_EXPORT CL_AddEntity( int entityType, cl_entity_t *pEnt ) +static int GAME_EXPORT CL_AddEntity( int entityType, cl_entity_t *pEnt ) { if( !pEnt ) return false; @@ -2596,25 +2708,14 @@ pfnGetGameDirectory ============= */ -const char *pfnGetGameDirectory( void ) +static const char *pfnGetGameDirectory( void ) { static char szGetGameDir[MAX_SYSPATH]; - Q_strcpy( szGetGameDir, GI->gamefolder ); + Q_strncpy( szGetGameDir, GI->gamefolder, sizeof( szGetGameDir )); return szGetGameDir; } -/* -============= -Key_LookupBinding - -============= -*/ -const char *Key_LookupBinding( const char *pBinding ) -{ - return Key_KeynumToString( Key_GetKey( pBinding )); -} - /* ============= pfnGetLevelName @@ -2625,7 +2726,10 @@ static const char *pfnGetLevelName( void ) { static char mapname[64]; - if( cls.state >= ca_connected ) + // a1ba: don't return maps/.bsp if no map is loaded yet + // in GoldSrc this is handled by cl.levelname field but we don't have it + // so emulate this behavior here + if( cls.state >= ca_connected && COM_CheckStringEmpty( clgame.mapname )) Q_snprintf( mapname, sizeof( mapname ), "maps/%s.bsp", clgame.mapname ); else mapname[0] = '\0'; // not in game @@ -2638,7 +2742,7 @@ pfnGetScreenFade ============= */ -void GAME_EXPORT pfnGetScreenFade( struct screenfade_s *fade ) +static void GAME_EXPORT pfnGetScreenFade( struct screenfade_s *fade ) { if( fade ) *fade = clgame.fade; } @@ -2660,7 +2764,7 @@ pfnLoadMapSprite ============= */ -model_t *pfnLoadMapSprite( const char *filename ) +static model_t *pfnLoadMapSprite( const char *filename ) { model_t *mod; @@ -2672,13 +2776,55 @@ model_t *pfnLoadMapSprite( const char *filename ) return NULL; } +/* +============= +COM_AddAppDirectoryToSearchPath + +============= +*/ +static void GAME_EXPORT COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ) +{ + FS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH ); +} + + +/* +=========== +COM_ExpandFilename + +Finds the file in the search path, copies over the name with the full path name. +This doesn't search in the pak file. +=========== +*/ +static int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ) +{ + char result[MAX_SYSPATH]; + + if( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 ) + return 0; + + // filename examples: + // media\sierra.avi - D:\Xash3D\valve\media\sierra.avi + // models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl + if( g_fsapi.GetFullDiskPath( result, sizeof( result ), fileName, false )) + { + // check for enough room + if( Q_strlen( result ) > nameOutBufferSize ) + return 0; + + Q_strncpy( nameOutBuffer, result, nameOutBufferSize ); + return 1; + } + return 0; +} + /* ============= PlayerInfo_ValueForKey ============= */ -const char *PlayerInfo_ValueForKey( int playerNum, const char *key ) +static const char *PlayerInfo_ValueForKey( int playerNum, const char *key ) { // find the player if(( playerNum > cl.maxclients ) || ( playerNum < 1 )) @@ -2696,7 +2842,7 @@ PlayerInfo_SetValueForKey ============= */ -void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value ) +static void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value ) { convar_t *var; @@ -2709,10 +2855,10 @@ void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value ) { Cvar_DirectSet( var, value ); } - else if( Info_SetValueForStarKey( cls.userinfo, key, value, MAX_INFO_STRING )) + else if( Info_SetValueForStarKey( cls.userinfo, key, value, sizeof( cls.userinfo ))) { // time to update server copy of userinfo - CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", key, value ); + CL_UpdateInfo( key, value ); } } @@ -2722,7 +2868,7 @@ pfnGetPlayerUniqueID ============= */ -qboolean GAME_EXPORT pfnGetPlayerUniqueID( int iPlayer, char playerID[16] ) +static qboolean GAME_EXPORT pfnGetPlayerUniqueID( int iPlayer, char playerID[16] ) { if( iPlayer < 1 || iPlayer > cl.maxclients ) return false; @@ -2742,7 +2888,7 @@ pfnGetTrackerIDForPlayer obsolete, unused ============= */ -int GAME_EXPORT pfnGetTrackerIDForPlayer( int playerSlot ) +static int GAME_EXPORT pfnGetTrackerIDForPlayer( int playerSlot ) { return 0; } @@ -2754,7 +2900,7 @@ pfnGetPlayerForTrackerID obsolete, unused ============= */ -int GAME_EXPORT pfnGetPlayerForTrackerID( int trackerID ) +static int GAME_EXPORT pfnGetPlayerForTrackerID( int trackerID ) { return 0; } @@ -2765,7 +2911,7 @@ pfnServerCmdUnreliable ============= */ -int GAME_EXPORT pfnServerCmdUnreliable( char *szCmdString ) +static int GAME_EXPORT pfnServerCmdUnreliable( char *szCmdString ) { if( !COM_CheckString( szCmdString )) return 0; @@ -2782,7 +2928,7 @@ pfnGetMousePos ============= */ -void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt ) +static void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt ) { if( !ppt ) return; @@ -2797,7 +2943,7 @@ pfnSetMouseEnable legacy of dinput code ============= */ -void GAME_EXPORT pfnSetMouseEnable( qboolean fEnable ) +static void GAME_EXPORT pfnSetMouseEnable( qboolean fEnable ) { } @@ -2891,23 +3037,7 @@ pfnVGUI2DrawCharacter */ static int GAME_EXPORT pfnVGUI2DrawCharacter( int x, int y, int number, unsigned int font ) { - if( !cls.creditsFont.valid ) - return 0; - - number &= 255; - - number = Con_UtfProcessChar( number ); - - if( number < 32 ) return 0; - if( y < -clgame.scrInfo.iCharHeight ) - return 0; - - clgame.ds.adjust_size = true; - gameui.ds.gl_texturenum = cls.creditsFont.hFontTexture; - pfnPIC_DrawAdditive( x, y, -1, -1, &cls.creditsFont.fontRc[number] ); - clgame.ds.adjust_size = false; - - return clgame.scrInfo.charWidths[number]; + return pfnDrawCharacter( x, y, number, 255, 255, 255 ); } /* @@ -2918,9 +3048,6 @@ pfnVGUI2DrawCharacterAdditive */ static int GAME_EXPORT pfnVGUI2DrawCharacterAdditive( int x, int y, int ch, int r, int g, int b, unsigned int font ) { - if( !hud_utf8->value ) - ch = Con_UtfProcessChar( ch ); - return pfnDrawCharacter( x, y, ch, r, g, b ); } @@ -2932,16 +3059,13 @@ pfnDrawString */ static int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b ) { - int iWidth = 0; - Con_UtfProcessChar(0); + rgba_t color = { r, g, b, 255 }; + int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF; - // draw the string until we hit the null character or a newline character - for ( ; *str != 0 && *str != '\n'; str++ ) - { - iWidth += pfnVGUI2DrawCharacterAdditive( x + iWidth, y, (unsigned char)*str, r, g, b, 0 ); - } + if( hud_utf8.value ) + SetBits( flags, FONT_DRAW_UTF8 ); - return iWidth; + return CL_DrawString( x, y, str, color, &cls.creditsFont, flags ); } /* @@ -2952,11 +3076,18 @@ pfnDrawStringReverse */ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int r, int g, int b ) { - // find the end of the string - char *szIt; - for( szIt = (char*)str; *szIt != 0; szIt++ ) - x -= clgame.scrInfo.charWidths[ (unsigned char) *szIt ]; - return pfnDrawString( x, y, str, r, g, b ); + rgba_t color = { r, g, b, 255 }; + int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF; + int width; + + if( hud_utf8.value ) + SetBits( flags, FONT_DRAW_UTF8 ); + + CL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags ); + + x -= width; + + return CL_DrawString( x, y, str, color, &cls.creditsFont, flags ); } /* @@ -3026,37 +3157,18 @@ pfnFillRGBABlend ============= */ -void GAME_EXPORT CL_FillRGBABlend( int x, int y, int w, int h, int r, int g, int b, int a ) +static void GAME_EXPORT CL_FillRGBABlend( int x, int y, int w, int h, int r, int g, int b, int a ) { - float _x = x, _y = y, _w = w, _h = h; + float x_ = x, y_ = y, w_ = w, h_ = h; r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); - SPR_AdjustSize( &_x, &_y, &_w, &_h ); + SPR_AdjustSize( &x_, &y_, &w_, &h_ ); -#if 1 // REFTODO: - ref.dllFuncs.FillRGBABlend( _x, _y, _w, _h, r, g, b, a ); -#else - pglDisable( GL_TEXTURE_2D ); - pglEnable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f ); - - pglBegin( GL_QUADS ); - pglVertex2f( _x, _y ); - pglVertex2f( _x + _w, _y ); - pglVertex2f( _x + _w, _y + _h ); - pglVertex2f( _x, _y + _h ); - pglEnd (); - - pglColor3f( 1.0f, 1.0f, 1.0f ); - pglEnable( GL_TEXTURE_2D ); - pglDisable( GL_BLEND ); -#endif + ref.dllFuncs.FillRGBA( kRenderTransTexture, x_, y_, w_, h_, r, g, b, a ); } /* @@ -3088,7 +3200,7 @@ pfnParseFile handle colon separately ============= */ -char *pfnParseFile( char *data, char *token ) +static char *pfnParseFile( char *data, char *token ) { return COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL ); } @@ -3219,7 +3331,7 @@ Heavy legacy of Quake... */ void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ) { - if( clgame.ds.renderMode == kRenderTransAlpha ) + if( rendermode == kRenderTransAlpha ) { clgame.ds.triRGBA[3] = a / 255.0f; ref.dllFuncs.Color4f( r, g, b, a ); @@ -3253,28 +3365,6 @@ DemoApi implementation ================= */ -/* -================= -Demo_IsRecording - -================= -*/ -static int GAME_EXPORT Demo_IsRecording( void ) -{ - return cls.demorecording; -} - -/* -================= -Demo_IsPlayingback - -================= -*/ -static int GAME_EXPORT Demo_IsPlayingback( void ) -{ - return cls.demoplayback; -} - /* ================= Demo_IsTimeDemo @@ -3286,17 +3376,6 @@ static int GAME_EXPORT Demo_IsTimeDemo( void ) return cls.timedemo; } -/* -================= -Demo_WriteBuffer - -================= -*/ -static void GAME_EXPORT Demo_WriteBuffer( int size, byte *buffer ) -{ - CL_WriteDemoUserMessage( buffer, size ); -} - /* ================= NetworkApi implementation @@ -3309,7 +3388,7 @@ NetAPI_InitNetworking ================= */ -void GAME_EXPORT NetAPI_InitNetworking( void ) +static void GAME_EXPORT NetAPI_InitNetworking( void ) { NET_Config( true, false ); // allow remote } @@ -3320,7 +3399,7 @@ NetAPI_InitNetworking ================= */ -void GAME_EXPORT NetAPI_Status( net_status_t *status ) +static void GAME_EXPORT NetAPI_Status( net_status_t *status ) { qboolean connected = false; int packet_loss = 0; @@ -3338,8 +3417,8 @@ void GAME_EXPORT NetAPI_Status( net_status_t *status ) status->latency = (connected) ? cl.frames[cl.parsecountmod].latency : 0.0; status->remote_address = cls.netchan.remote_address; status->packet_loss = packet_loss; - status->local_address = net_local; - status->rate = rate->value; + NET_GetLocalAddress( &status->local_address, NULL ); // NetAPI doesn't know about IPv6 + status->rate = rate.value; } /* @@ -3348,21 +3427,23 @@ NetAPI_SendRequest ================= */ -void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response ) +static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response ) { net_request_t *nr = NULL; - string req; int i; if( !response ) { - Con_DPrintf( S_ERROR "Net_SendRequest: no callbcak specified for request with context %i!\n", context ); + Con_DPrintf( S_ERROR "%s: no callbcak specified for request with context %i!\n", __func__, context ); return; } - if( remote_address->type >= NA_IPX ) + if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX ) return; // IPX no longer support + if( request == NETAPI_REQUEST_SERVERLIST ) + return; // no support for server list requests + // find a free request for( i = 0; i < MAX_REQUESTS; i++ ) { @@ -3399,26 +3480,8 @@ void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double nr->resp.remote_address = *remote_address; nr->flags = flags; - if( request == NETAPI_REQUEST_SERVERLIST ) - { - char fullquery[512] = "1\xFF" "0.0.0.0:0\0" "\\gamedir\\"; - - // make sure what port is specified - if( !nr->resp.remote_address.port ) - nr->resp.remote_address.port = MSG_BigShort( PORT_MASTER ); - - // grab the list from the master server - Q_strcpy( &fullquery[22], GI->gamefolder ); - NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamefolder ) + 23, fullquery, nr->resp.remote_address ); - clgame.request_type = NET_REQUEST_CLIENT; - clgame.master_request = nr; // holds the master request unitl the master acking - } - else - { - // local servers request - Q_snprintf( req, sizeof( req ), "netinfo %i %i %i", PROTOCOL_VERSION, context, request ); - Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, "%s", req ); - } + // local servers request + Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO" %i %i %i", FBitSet( flags, FNETAPI_LEGACY_PROTOCOL ) ? PROTOCOL_LEGACY_VERSION : PROTOCOL_VERSION, context, request ); } /* @@ -3427,11 +3490,11 @@ NetAPI_CancelRequest ================= */ -void GAME_EXPORT NetAPI_CancelRequest( int context ) +static void GAME_EXPORT NetAPI_CancelRequest( int context ) { net_request_t *nr; int i; - +; // find a specified request for( i = 0; i < MAX_REQUESTS; i++ ) { @@ -3446,13 +3509,6 @@ void GAME_EXPORT NetAPI_CancelRequest( int context ) nr->pfnFunc( &nr->resp ); } - if( clgame.net_requests[i].resp.type == NETAPI_REQUEST_SERVERLIST && &clgame.net_requests[i] == clgame.master_request ) - { - if( clgame.request_type == NET_REQUEST_CLIENT ) - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; - } - memset( &clgame.net_requests[i], 0, sizeof( net_request_t )); break; } @@ -3481,8 +3537,6 @@ void GAME_EXPORT NetAPI_CancelAllRequests( void ) } memset( clgame.net_requests, 0, sizeof( clgame.net_requests )); - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; } /* @@ -3491,7 +3545,7 @@ NetAPI_AdrToString ================= */ -const char *NetAPI_AdrToString( netadr_t *a ) +static const char *NetAPI_AdrToString( netadr_t *a ) { return NET_AdrToString( *a ); } @@ -3502,40 +3556,18 @@ NetAPI_CompareAdr ================= */ -int GAME_EXPORT NetAPI_CompareAdr( netadr_t *a, netadr_t *b ) +static int GAME_EXPORT NetAPI_CompareAdr( netadr_t *a, netadr_t *b ) { return NET_CompareAdr( *a, *b ); } -/* -================= -NetAPI_StringToAdr - -================= -*/ -int GAME_EXPORT NetAPI_StringToAdr( char *s, netadr_t *a ) -{ - return NET_StringToAdr( s, a ); -} - -/* -================= -NetAPI_ValueForKey - -================= -*/ -const char *NetAPI_ValueForKey( const char *s, const char *key ) -{ - return Info_ValueForKey( s, key ); -} - /* ================= NetAPI_RemoveKey ================= */ -void GAME_EXPORT NetAPI_RemoveKey( char *s, const char *key ) +static void GAME_EXPORT NetAPI_RemoveKey( char *s, const char *key ) { Info_RemoveKey( s, key ); } @@ -3546,7 +3578,7 @@ NetAPI_SetValueForKey ================= */ -void GAME_EXPORT NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize ) +static void GAME_EXPORT NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize ) { if( key[0] == '*' ) return; Info_SetValueForStarKey( s, key, value, maxsize ); @@ -3566,7 +3598,7 @@ Voice_StartVoiceTweakMode ================= */ -int GAME_EXPORT Voice_StartVoiceTweakMode( void ) +static int GAME_EXPORT Voice_StartVoiceTweakMode( void ) { return 0; } @@ -3577,7 +3609,7 @@ Voice_EndVoiceTweakMode ================= */ -void GAME_EXPORT Voice_EndVoiceTweakMode( void ) +static void GAME_EXPORT Voice_EndVoiceTweakMode( void ) { } @@ -3587,7 +3619,7 @@ Voice_SetControlFloat ================= */ -void GAME_EXPORT Voice_SetControlFloat( VoiceTweakControl iControl, float value ) +static void GAME_EXPORT Voice_SetControlFloat( VoiceTweakControl iControl, float value ) { } @@ -3597,7 +3629,7 @@ Voice_GetControlFloat ================= */ -float GAME_EXPORT Voice_GetControlFloat( VoiceTweakControl iControl ) +static float GAME_EXPORT Voice_GetControlFloat( VoiceTweakControl iControl ) { return 1.0f; } @@ -3709,7 +3741,7 @@ static event_api_t gEventApi = CL_WeaponAnim, pfnPrecacheEvent, CL_PlaybackEvent, - pfnTraceTexture, + PM_CL_TraceTexture, pfnStopAllSounds, pfnKillEvents, CL_PlayerTraceExt, // Xash3D added @@ -3725,13 +3757,13 @@ static event_api_t gEventApi = static demo_api_t gDemoApi = { - Demo_IsRecording, - Demo_IsPlayingback, + (void *)CL_IsRecordDemo, + (void *)CL_IsPlaybackDemo, Demo_IsTimeDemo, - Demo_WriteBuffer, + CL_WriteDemoUserMessage, }; -static net_api_t gNetApi = +net_api_t gNetApi = { NetAPI_InitNetworking, NetAPI_Status, @@ -3740,8 +3772,8 @@ static net_api_t gNetApi = NetAPI_CancelAllRequests, NetAPI_AdrToString, NetAPI_CompareAdr, - NetAPI_StringToAdr, - NetAPI_ValueForKey, + (void *)NET_StringToAdr, + Info_ValueForKey, NetAPI_RemoveKey, NetAPI_SetValueForKey, }; @@ -3767,7 +3799,7 @@ static cl_enginefunc_t gEngfuncs = pfnSPR_DrawAdditive, SPR_EnableScissor, SPR_DisableScissor, - pfnSPR_GetList, + SPR_GetList, CL_FillRGBA, CL_GetScreenInfo, pfnSetCrosshair, @@ -3814,9 +3846,9 @@ static cl_enginefunc_t gEngfuncs = pfnGetClientTime, pfnCalcShake, pfnApplyShake, - pfnPointContents, + PM_CL_PointContents, CL_WaterEntity, - pfnTraceLine, + PM_CL_TraceLine, CL_LoadModel, CL_AddEntity, CL_GetSpritePointer, @@ -3909,24 +3941,28 @@ void CL_UnloadProgs( void ) if( Q_stricmp( GI->gamefolder, "hlfx" ) || GI->version != 0.5f ) clgame.dllFuncs.pfnShutdown(); + if( GI->internal_vgui_support ) + VGui_Shutdown(); + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); + Cvar_Unlink( FCVAR_CLIENTDLL ); + Cmd_Unlink( CMD_CLIENTDLL ); + COM_FreeLibrary( clgame.hInstance ); Mem_FreePool( &cls.mempool ); Mem_FreePool( &clgame.mempool ); memset( &clgame, 0, sizeof( clgame )); - - Cvar_Unlink( FCVAR_CLIENTDLL ); - Cmd_Unlink( CMD_CLIENTDLL ); } qboolean CL_LoadProgs( const char *name ) { static playermove_t gpMove; - const dllfunc_t *func; - CL_EXPORT_FUNCS GetClientAPI; // single export - qboolean critical_exports = true; + CL_EXPORT_FUNCS GetClientAPI; // single export + qboolean valid_single_export = false; + qboolean missed_exports = false; + int i; if( clgame.hInstance ) CL_UnloadProgs(); @@ -3937,107 +3973,92 @@ qboolean CL_LoadProgs( const char *name ) clgame.mempool = Mem_AllocPool( "Client Edicts Zone" ); clgame.entities = NULL; - // NOTE: important stuff! - // vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS - // during LoadLibrary - VGui_Startup( name, gameui.globals->scrWidth, gameui.globals->scrHeight ); - // a1ba: we need to check if client.dll has direct dependency on SDL2 // and if so, disable relative mouse mode #if XASH_WIN32 && !XASH_64BIT - if( ( clgame.client_dll_uses_sdl = COM_CheckLibraryDirectDependency( name, OS_LIB_PREFIX "SDL2." OS_LIB_EXT, false ) ) ) - { - Con_Printf( S_NOTE "%s uses SDL2 for mouse input\n", name ); - } - else - { - Con_Printf( S_NOTE "%s uses Windows API for mouse input\n", name ); - } -#else - // this doesn't mean other platforms uses SDL2 in any case - // it just helps input code to stay platform-independent - clgame.client_dll_uses_sdl = true; + clgame.client_dll_uses_sdl = COM_CheckLibraryDirectDependency( name, OS_LIB_PREFIX "SDL2." OS_LIB_EXT, false ); + Con_Printf( S_NOTE "%s uses %s for mouse input\n", name, clgame.client_dll_uses_sdl ? "SDL2" : "Windows API" ); #endif + // NOTE: important stuff! + // vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS during LoadLibrary + if( !GI->internal_vgui_support && VGui_LoadProgs( NULL )) + VGui_Startup( refState.width, refState.height ); + else + GI->internal_vgui_support = true; // we failed to load vgui_support, but let's probe client.dll for support anyway + clgame.hInstance = COM_LoadLibrary( name, false, false ); - if( !clgame.hInstance ) return false; + + if( !clgame.hInstance ) + return false; + + // delayed vgui initialization for internal support + if( GI->internal_vgui_support && VGui_LoadProgs( clgame.hInstance )) + VGui_Startup( refState.width, refState.height ); // clear exports - for( func = cdll_exports; func && func->name; func++ ) - *func->func = NULL; + ClearExports( cdll_exports, ARRAYSIZE( cdll_exports )); // trying to get single export if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "GetClientAPI" )) != NULL ) { - Con_Reportf( "CL_LoadProgs: found single callback export\n" ); + Con_Reportf( "%s: found single callback export\n", __func__ ); // trying to fill interface now GetClientAPI( &clgame.dllFuncs ); } else if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "F" )) != NULL ) { - Con_Reportf( "CL_LoadProgs: found single callback export (secured client dlls)\n" ); + Con_Reportf( "%s: found single callback export (secured client dlls)\n", __func__ ); // trying to fill interface now CL_GetSecuredClientAPI( GetClientAPI ); } - if ( GetClientAPI != NULL ) - { - // check critical functions again - for( func = cdll_exports; func && func->name; func++ ) - { - if( func->func == NULL ) - break; // BAH critical function was missed - } + if( GetClientAPI != NULL ) // check critical functions again + valid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports )); - // because all the exports are loaded through function 'F" - if( !func || !func->name ) - critical_exports = false; - } - - for( func = cdll_exports; func && func->name != NULL; func++ ) + for( i = 0; i < ARRAYSIZE( cdll_exports ); i++ ) { - if( *func->func != NULL ) - continue; // already get through 'F' + if( *(cdll_exports[i].func) != NULL ) + continue; // already gott through 'F' or 'GetClientAPI' // functions are cleared before all the extensions are evaluated - if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL ) + if(( *(cdll_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_exports[i].name )) == NULL ) { - Con_Reportf( "CL_LoadProgs: failed to get address of %s proc\n", func->name ); + Con_Reportf( S_ERROR "%s: failed to get address of %s proc\n", __func__, cdll_exports[i].name ); - if( critical_exports ) - { - COM_FreeLibrary( clgame.hInstance ); - clgame.hInstance = NULL; - return false; - } + // print all not found exports at once, for debug + missed_exports = true; } } + if( missed_exports ) + { + COM_FreeLibrary( clgame.hInstance ); + clgame.hInstance = NULL; + return false; + } + // it may be loaded through 'GetClientAPI' so we don't need to clear them - if( critical_exports ) - { - // clear new exports - for( func = cdll_new_exports; func && func->name; func++ ) - *func->func = NULL; - } + if( !valid_single_export ) + ClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports )); - for( func = cdll_new_exports; func && func->name != NULL; func++ ) + for( i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ ) { - if( *func->func != NULL ) - continue; // already get through 'F' + if( *(cdll_new_exports[i].func) != NULL ) + continue; // already gott through 'F' or 'GetClientAPI' // functions are cleared before all the extensions are evaluated // NOTE: new exports can be missed without stop the engine - if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL ) - Con_Reportf( "CL_LoadProgs: failed to get address of %s proc\n", func->name ); + if(( *(cdll_new_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_new_exports[i].name )) == NULL ) + Con_Reportf( S_WARN "%s: failed to get address of %s proc\n", __func__, cdll_new_exports[i].name ); } if( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION )) { COM_FreeLibrary( clgame.hInstance ); - Con_Reportf( "CL_LoadProgs: can't init client API\n" ); + Con_Reportf( "%s: can't init client API\n", __func__ ); clgame.hInstance = NULL; return false; } @@ -4054,12 +4075,12 @@ qboolean CL_LoadProgs( const char *name ) CL_InitTempEnts (); if( !R_InitRenderAPI()) // Xash3D extension - Con_Reportf( S_WARN "CL_LoadProgs: couldn't get render API\n" ); + Con_Reportf( S_WARN "%s: couldn't get render API\n", __func__ ); if( !Mobile_Init() ) // Xash3D FWGS extension: mobile interface - Con_Reportf( S_WARN "CL_LoadProgs: couldn't get mobility API\n" ); + Con_Reportf( S_WARN "%s: couldn't get mobility API\n", __func__ ); - CL_InitEdicts (); // initailize local player and world + CL_InitEdicts( cl.maxclients ); // initailize local player and world CL_InitClientMove(); // initialize pm_shared // initialize game diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index e5bf2683..3da932c2 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -25,6 +25,14 @@ static void UI_UpdateUserinfo( void ); gameui_static_t gameui; +static void UI_ToggleAllowConsole_f( void ) +{ + host.allow_console = host.allow_console_init = true; + + if( gameui.globals ) + gameui.globals->developer = true; +} + void UI_UpdateMenu( float realtime ) { if( !gameui.hInstance ) return; @@ -216,12 +224,14 @@ UI_ShowConnectionWarning show message box ================= */ -void UI_ShowMessageBox( const char *text ) +qboolean UI_ShowMessageBox( const char *text ) { if( gameui.dllFuncs2.pfnShowMessageBox ) { gameui.dllFuncs2.pfnShowMessageBox( text ); + return true; } + return false; } void UI_ConnectionProgress_Disconnect( void ) @@ -292,14 +302,11 @@ void UI_ConnectionProgress_ParseServerInfo( const char *server ) static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, float width, float height ) { - static float cin_time; - static int last_frame = -1; - byte *cin_data = NULL; movie_state_t *cin_state; - int cin_frame; - qboolean redraw = false; - if( !gameui.drawLogo ) return; + if( !gameui.drawLogo ) + return; + cin_state = AVI_GetState( CIN_LOGO ); if( !AVI_IsActive( cin_state )) @@ -309,7 +316,7 @@ static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, flo // run cinematic if not Q_snprintf( path, sizeof( path ), "media/%s", filename ); - COM_DefaultExtension( path, ".avi" ); + COM_DefaultExtension( path, ".avi", sizeof( path )); fullpath = FS_GetDiskPath( path, false ); if( FS_FileExists( path, false ) && !fullpath ) @@ -326,37 +333,25 @@ static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, flo gameui.drawLogo = false; return; } - - cin_time = 0.0f; - last_frame = -1; } if( width <= 0 || height <= 0 ) { // precache call, don't draw - cin_time = 0.0f; - last_frame = -1; return; } - // advances cinematic time (ignores maxfps and host_framerate settings) - cin_time += host.realframetime; - - // restarts the cinematic - if( cin_time > gameui.logo_length ) - cin_time = 0.0f; + AVI_SetParm( cin_state, + AVI_RENDER_TEXNUM, 0, + AVI_RENDER_X, (int)x, + AVI_RENDER_Y, (int)y, + AVI_RENDER_W, (int)width, + AVI_RENDER_H, (int)height, + AVI_PARM_LAST ); // read the next frame - cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); - - if( cin_frame != last_frame ) - { - cin_data = AVI_GetVideoFrame( cin_state, cin_frame ); - last_frame = cin_frame; - redraw = true; - } - - ref.dllFuncs.R_DrawStretchRaw( x, y, width, height, gameui.logo_xres, gameui.logo_yres, cin_data, redraw ); + if( !AVI_Think( cin_state )) + AVI_SetParm( cin_state, AVI_REWIND, AVI_PARM_LAST ); } static int GAME_EXPORT UI_GetLogoWidth( void ) @@ -397,76 +392,61 @@ void Host_Credits( void ) gameui.dllFuncs.pfnFinalCredits(); } -static void UI_ConvertGameInfo( GAMEINFO *out, gameinfo_t *in ) +static void UI_ConvertGameInfo( gameinfo2_t *out, const gameinfo_t *in ) { + out->gi_version = GAMEINFO_VERSION; + Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder )); Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap )); Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap )); + Q_strncpy( out->demomap, in->demomap, sizeof( out->demomap )); Q_strncpy( out->title, in->title, sizeof( out->title )); Q_snprintf( out->version, sizeof( out->version ), "%g", in->version ); + Q_strncpy( out->iconpath, in->iconpath, sizeof( out->iconpath )); Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url )); Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url )); - Q_strncpy( out->size, Q_pretifymem( in->size, 0 ), sizeof( out->size )); + out->size = in->size; Q_strncpy( out->type, in->type, sizeof( out->type )); Q_strncpy( out->date, in->date, sizeof( out->date )); out->gamemode = in->gamemode; if( in->nomodels ) - out->flags |= GFL_NOMODELS; + SetBits( out->flags, GFL_NOMODELS ); if( in->noskills ) - out->flags |= GFL_NOSKILLS; + SetBits( out->flags, GFL_NOSKILLS ); if( in->render_picbutton_text ) - out->flags |= GFL_RENDER_PICBUTTON_TEXT; + SetBits( out->flags, GFL_RENDER_PICBUTTON_TEXT ); + if( in->hd_background ) + SetBits( out->flags, GFL_HD_BACKGROUND ); + if( in->animated_title ) + SetBits( out->flags, GFL_ANIMATED_TITLE ); } -static qboolean PIC_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) +static void UI_ToOldGameInfo( GAMEINFO *out, const gameinfo2_t *in ) { - float dudx, dvdy; + Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder )); + Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap )); + Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap )); + Q_strncpy( out->title, in->title, sizeof( out->title )); + Q_strncpy( out->version, in->version, sizeof( out->version )); + out->flags = in->flags & 0xFFFF; + Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url )); + Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url )); + Q_strncpy( out->size, Q_memprint( in->size ), sizeof( out->size )); + Q_strncpy( out->type, in->type, sizeof( out->type )); + Q_strncpy( out->date, in->date, sizeof( out->date )); + out->gamemode = in->gamemode; +} - // clip sub rect to sprite - if(( width == 0 ) || ( height == 0 )) - return false; +static void UI_GetModsInfo( void ) +{ + int i; - if( *x + *width <= gameui.ds.scissor_x ) - return false; - if( *x >= gameui.ds.scissor_x + gameui.ds.scissor_width ) - return false; - if( *y + *height <= gameui.ds.scissor_y ) - return false; - if( *y >= gameui.ds.scissor_y + gameui.ds.scissor_height ) - return false; - - dudx = (*u1 - *u0) / *width; - dvdy = (*v1 - *v0) / *height; - - if( *x < gameui.ds.scissor_x ) - { - *u0 += (gameui.ds.scissor_x - *x) * dudx; - *width -= gameui.ds.scissor_x - *x; - *x = gameui.ds.scissor_x; - } - - if( *x + *width > gameui.ds.scissor_x + gameui.ds.scissor_width ) - { - *u1 -= (*x + *width - (gameui.ds.scissor_x + gameui.ds.scissor_width)) * dudx; - *width = gameui.ds.scissor_x + gameui.ds.scissor_width - *x; - } - - if( *y < gameui.ds.scissor_y ) - { - *v0 += (gameui.ds.scissor_y - *y) * dvdy; - *height -= gameui.ds.scissor_y - *y; - *y = gameui.ds.scissor_y; - } - - if( *y + *height > gameui.ds.scissor_y + gameui.ds.scissor_height ) - { - *v1 -= (*y + *height - (gameui.ds.scissor_y + gameui.ds.scissor_height)) * dvdy; - *height = gameui.ds.scissor_y + gameui.ds.scissor_height - *y; - } - return true; + gameui.modsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.modsInfo ) * FI->numgames ); + for( i = 0; i < FI->numgames; i++ ) + UI_ConvertGameInfo( &gameui.modsInfo[i], FI->games[i] ); } /* @@ -487,10 +467,10 @@ static void PIC_DrawGeneric( float x, float y, float width, float height, const if( prc ) { // calc user-defined rectangle - s1 = (float)prc->left / (float)w; - t1 = (float)prc->top / (float)h; - s2 = (float)prc->right / (float)w; - t2 = (float)prc->bottom / (float)h; + s1 = prc->left / (float)w; + t1 = prc->top / (float)h; + s2 = prc->right / (float)w; + t2 = prc->bottom / (float)h; if( width == -1 && height == -1 ) { @@ -511,10 +491,9 @@ static void PIC_DrawGeneric( float x, float y, float width, float height, const } // pass scissor test if supposed - if( gameui.ds.scissor_test && !PIC_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; - PicAdjustSize( &x, &y, &width, &height ); ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, gameui.ds.gl_texturenum ); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } @@ -537,7 +516,7 @@ static HIMAGE GAME_EXPORT pfnPIC_Load( const char *szPicName, const byte *image_ if( !COM_CheckString( szPicName )) { - Con_Reportf( S_ERROR "CL_LoadImage: refusing to load image with empty name\n" ); + Con_Reportf( S_ERROR "%s: refusing to load image with empty name\n", __func__ ); return 0; } @@ -587,7 +566,7 @@ pfnPIC_Set ========= */ -void GAME_EXPORT pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ) +static void GAME_EXPORT pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ) { gameui.ds.gl_texturenum = hPic; r = bound( 0, r, 255 ); @@ -603,7 +582,7 @@ pfnPIC_Draw ========= */ -void GAME_EXPORT pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ) +static void GAME_EXPORT pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); PIC_DrawGeneric( x, y, width, height, prc ); @@ -615,7 +594,7 @@ pfnPIC_DrawTrans ========= */ -void GAME_EXPORT pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ) +static void GAME_EXPORT pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); PIC_DrawGeneric( x, y, width, height, prc ); @@ -627,7 +606,7 @@ pfnPIC_DrawHoles ========= */ -void GAME_EXPORT pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ) +static void GAME_EXPORT pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha ); PIC_DrawGeneric( x, y, width, height, prc ); @@ -639,7 +618,7 @@ pfnPIC_DrawAdditive ========= */ -void GAME_EXPORT pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ) +static void GAME_EXPORT pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); PIC_DrawGeneric( x, y, width, height, prc ); @@ -659,11 +638,7 @@ static void GAME_EXPORT pfnPIC_EnableScissor( int x, int y, int width, int heigh width = bound( 0, width, gameui.globals->scrWidth - x ); height = bound( 0, height, gameui.globals->scrHeight - y ); - gameui.ds.scissor_x = x; - gameui.ds.scissor_width = width; - gameui.ds.scissor_y = y; - gameui.ds.scissor_height = height; - gameui.ds.scissor_test = true; + CL_EnableScissor( &gameui.ds.scissor, x, y, width, height ); } /* @@ -674,11 +649,7 @@ pfnPIC_DisableScissor */ static void GAME_EXPORT pfnPIC_DisableScissor( void ) { - gameui.ds.scissor_x = 0; - gameui.ds.scissor_width = 0; - gameui.ds.scissor_y = 0; - gameui.ds.scissor_height = 0; - gameui.ds.scissor_test = false; + CL_DisableScissor( &gameui.ds.scissor ); } /* @@ -693,10 +664,24 @@ static void GAME_EXPORT pfnFillRGBA( int x, int y, int width, int height, int r, g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); - ref.dllFuncs.Color4ub( r, g, b, a ); - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); - ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) ); - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); + + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, width, height, r, g, b, a ); +} + +/* +============= +pfnCvar_RegisterVariable + +============= +*/ +static cvar_t *GAME_EXPORT pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ) +{ + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_GAMEUIDLL )); +} + +static int GAME_EXPORT Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ) +{ + return Cmd_AddCommandEx( cmd_name, function, "gameui command", CMD_GAMEUIDLL, __func__ ); } /* @@ -766,7 +751,7 @@ static void GAME_EXPORT pfnDrawCharacter( int ix, int iy, int iwidth, int iheigh t2 = t1 + size; // pass scissor test if supposed - if( gameui.ds.scissor_test && !PIC_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); @@ -895,7 +880,7 @@ send client connect */ static void GAME_EXPORT pfnClientJoin( const netadr_t adr ) { - Cbuf_AddText( va( "connect %s\n", NET_AdrToString( adr ))); + Cbuf_AddTextf( "connect %s\n", NET_AdrToString( adr )); } /* @@ -964,11 +949,12 @@ pfnGetGameInfo ========= */ -static int GAME_EXPORT pfnGetGameInfo( GAMEINFO *pgameinfo ) +static int GAME_EXPORT pfnGetOldGameInfo( GAMEINFO *pgameinfo ) { - if( !pgameinfo ) return 0; + if( !pgameinfo ) + return 0; - *pgameinfo = gameui.gameInfo; + UI_ToOldGameInfo( pgameinfo, &gameui.gameInfo ); return 1; } @@ -980,8 +966,26 @@ pfnGetGamesList */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { - if( numGames ) *numGames = FI->numgames; - return gameui.modsInfo; + if( numGames ) + *numGames = FI->numgames; + + if( !gameui.oldModsInfo ) + { + int i; + + if( !gameui.modsInfo ) + UI_GetModsInfo(); + + // first allocate array of pointers + gameui.oldModsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo ) * FI->numgames ); + for( i = 0; i < FI->numgames; i++ ) + { + gameui.oldModsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo[i] )); + UI_ToOldGameInfo( gameui.oldModsInfo[i], &gameui.modsInfo[i] ); + } + } + + return gameui.oldModsInfo; } /* @@ -1026,30 +1030,23 @@ pfnCheckGameDll ========= */ -int GAME_EXPORT pfnCheckGameDll( void ) +static int GAME_EXPORT pfnCheckGameDll( void ) { - string dllpath; - void *hInst; - -#if TARGET_OS_IPHONE - // loading server library drains too many ram - // so 512MB iPod Touch cannot even connect to - // to servers in cstrike +#ifdef XASH_INTERNAL_GAMELIBS return true; -#endif +#else + string dllpath; if( svgame.hInstance ) return true; COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath )); - if(( hInst = COM_LoadLibrary( dllpath, true, false )) != NULL ) - { - COM_FreeLibrary( hInst ); // don't increase linker's reference counter + if( FS_FileExists( dllpath, false )) return true; - } - Con_Reportf( S_WARN "Could not load server library: %s\n", COM_GetLibraryError() ); + return false; +#endif } /* @@ -1060,7 +1057,7 @@ pfnChangeInstance */ static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) { - Con_Reportf( S_ERROR "ChangeInstance menu call is deprecated!\n" ); + Con_Reportf( S_ERROR "%s menu call is deprecated!\n", __func__ ); } /* @@ -1102,7 +1099,7 @@ static void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, in Platform_ShellExecute( path, parms ); if( shouldExit ) - Sys_Quit(); + Sys_Quit( __func__ ); } /* @@ -1141,8 +1138,35 @@ static int pfnDelete( const char *path ) return FS_Delete( path ); } +static void GAME_EXPORT pfnCon_DefaultColor( int r, int g, int b ) +{ + Con_DefaultColor( r, g, b, true ); +} + +static void GAME_EXPORT pfnSetCursor( void *hCursor ) +{ + uintptr_t cursor; + + if( !gameui.use_extended_api ) + return; // ignore original Xash menus + + cursor = (uintptr_t)hCursor; + if( cursor < dc_user || cursor > dc_last ) + return; + + Platform_SetCursorType( cursor ); +} + +static void GAME_EXPORT pfnGetGameDir( char *out ) +{ + if( !out ) + return; + + Q_strncpy( out, GI->gamefolder, sizeof( GI->gamefolder )); +} + // engine callbacks -static ui_enginefuncs_t gEngfuncs = +static const ui_enginefuncs_t gEngfuncs = { pfnPIC_Load, GL_FreeImage, @@ -1180,7 +1204,7 @@ static ui_enginefuncs_t gEngfuncs = UI_DrawConsoleString, UI_DrawSetTextColor, Con_DrawStringLen, - Con_DefaultColor, + pfnCon_DefaultColor, pfnGetPlayerModel, pfnSetPlayerModel, pfnClearScene, @@ -1206,7 +1230,7 @@ static ui_enginefuncs_t gEngfuncs = pfnKeyGetState, pfnMemAlloc, pfnMemFree, - pfnGetGameInfo, + pfnGetOldGameInfo, pfnGetGamesList, pfnGetFilesList, SV_GetSaveComment, @@ -1220,10 +1244,10 @@ static ui_enginefuncs_t gEngfuncs = pfnHostEndGame, COM_RandomFloat, COM_RandomLong, - IN_SetCursor, + pfnSetCursor, pfnIsMapValid, GL_ProcessTexture, - COM_CompareFileTime, + pfnCompareFileTime, VID_GetModeString, (void*)COM_SaveFile, pfnDelete @@ -1234,16 +1258,16 @@ static void pfnEnableTextInput( int enable ) Key_EnableTextInput( enable, false ); } -static int pfnGetRenderers( unsigned int num, char *shortName, size_t size1, char *readableName, size_t size2 ) +static int pfnGetRenderers( unsigned int num, char *short_name, size_t size1, char *long_name, size_t size2 ) { - if( num >= ref.numRenderers ) + if( num >= ref.num_renderers ) return 0; - if( shortName && size1 ) - Q_strncpy( shortName, ref.shortNames[num], size1 ); + if( short_name && size1 ) + Q_strncpy( short_name, ref.short_names[num], size1 ); - if( readableName && size2 ) - Q_strncpy( readableName, ref.readableNames[num], size2 ); + if( long_name && size2 ) + Q_strncpy( long_name, ref.long_names[num], size2 ); return 1; } @@ -1253,6 +1277,38 @@ static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned i return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); } +static gameinfo2_t *pfnGetGameInfo( int gi_version ) +{ + if( gi_version != gameui.gameInfo.gi_version ) + return NULL; + + return &gameui.gameInfo; +} + +static gameinfo2_t *pfnGetModInfo( int gi_version, int i ) +{ + if( i < 0 || i >= FI->numgames ) + return NULL; + + if( !gameui.modsInfo ) + UI_GetModsInfo(); + + if( gi_version != gameui.modsInfo[i].gi_version ) + return NULL; + + return &gameui.modsInfo[i]; +} + +static int pfnIsCvarReadOnly( const char *name ) +{ + convar_t *cv = Cvar_FindVar( name ); + + if( !cv ) + return -1; + + return FBitSet( cv->flags, FCVAR_READ_ONLY ) ? 1 : 0; +} + static ui_extendedfuncs_t gExtendedfuncs = { pfnEnableTextInput, @@ -1262,7 +1318,13 @@ static ui_extendedfuncs_t gExtendedfuncs = pfnGetRenderers, Sys_DoubleTime, pfnParseFileSafe, - NET_AdrToString + NET_AdrToString, + NET_CompareAdrSort, + Sys_GetNativeObject, + &gNetApi, + pfnGetGameInfo, + pfnGetModInfo, + pfnIsCvarReadOnly, }; void UI_UnloadProgs( void ) @@ -1272,14 +1334,15 @@ void UI_UnloadProgs( void ) // deinitialize game gameui.dllFuncs.pfnShutdown(); + Cmd_RemoveCommand( "ui_allowconsole" ); Cvar_FullSet( "host_gameuiloaded", "0", FCVAR_READ_ONLY ); + Cvar_Unlink( FCVAR_GAMEUIDLL ); + Cmd_Unlink( CMD_GAMEUIDLL ); + COM_FreeLibrary( gameui.hInstance ); Mem_FreePool( &gameui.mempool ); memset( &gameui, 0, sizeof( gameui )); - - Cvar_Unlink( FCVAR_GAMEUIDLL ); - Cmd_Unlink( CMD_GAMEUIDLL ); } qboolean UI_LoadProgs( void ) @@ -1322,71 +1385,65 @@ qboolean UI_LoadProgs( void ) if(( GetMenuAPI = (MENUAPI)COM_GetProcAddress( gameui.hInstance, "GetMenuAPI" )) == NULL ) { COM_FreeLibrary( gameui.hInstance ); - Con_Reportf( "UI_LoadProgs: can't init menu API\n" ); + Con_Reportf( "%s: can't init menu API\n", __func__ ); gameui.hInstance = NULL; return false; } - gameui.use_text_api = false; + gameui.use_extended_api = false; // make local copy of engfuncs to prevent overwrite it with user dll - memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); + gpEngfuncs = gEngfuncs; gameui.mempool = Mem_AllocPool( "Menu Pool" ); if( !GetMenuAPI( &gameui.dllFuncs, &gpEngfuncs, gameui.globals )) { COM_FreeLibrary( gameui.hInstance ); - Con_Reportf( "UI_LoadProgs: can't init menu API\n" ); + Con_Reportf( "%s: can't init menu API\n", __func__ ); Mem_FreePool( &gameui.mempool ); gameui.hInstance = NULL; return false; } // make local copy of engfuncs to prevent overwrite it with user dll - memcpy( &gpExtendedfuncs, &gExtendedfuncs, sizeof( gExtendedfuncs )); + gpExtendedfuncs = gExtendedfuncs; memset( &gameui.dllFuncs2, 0, sizeof( gameui.dllFuncs2 )); // try to initialize new extended API if( ( GetExtAPI = (UIEXTENEDEDAPI)COM_GetProcAddress( gameui.hInstance, "GetExtAPI" ) ) ) { - Con_Reportf( "UI_LoadProgs: extended Menu API found\n" ); + Con_Reportf( "%s: extended Menu API found\n", __func__ ); if( GetExtAPI( MENU_EXTENDED_API_VERSION, &gameui.dllFuncs2, &gpExtendedfuncs ) ) { - Con_Reportf( "UI_LoadProgs: extended Menu API initialized\n" ); - gameui.use_text_api = true; + Con_Reportf( "%s: extended Menu API initialized\n", __func__ ); + gameui.use_extended_api = true; } } else // otherwise, fallback to old and deprecated extensions { if( ( GiveTextApi = (UITEXTAPI)COM_GetProcAddress( gameui.hInstance, "GiveTextAPI" ) ) ) { - Con_Reportf( "UI_LoadProgs: extended text API found\n" ); + Con_Reportf( "%s: extended text API found\n", __func__ ); Con_Reportf( S_WARN "Text API is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" ); if( GiveTextApi( &gpExtendedfuncs ) ) // they are binary compatible, so we can just pass extended funcs API to menu { - Con_Reportf( "UI_LoadProgs: extended text API initialized\n" ); - gameui.use_text_api = true; + Con_Reportf( "%s: extended text API initialized\n", __func__ ); + gameui.use_extended_api = true; } } gameui.dllFuncs2.pfnAddTouchButtonToList = (ADDTOUCHBUTTONTOLIST)COM_GetProcAddress( gameui.hInstance, "AddTouchButtonToList" ); if( gameui.dllFuncs2.pfnAddTouchButtonToList ) { - Con_Reportf( "UI_LoadProgs: AddTouchButtonToList call found\n" ); + Con_Reportf( "%s: AddTouchButtonToList call found\n", __func__ ); Con_Reportf( S_WARN "AddTouchButtonToList is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" ); } } Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); - - // setup gameinfo - for( i = 0; i < FI->numgames; i++ ) - { - gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO )); - UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] ); - } + Cmd_AddRestrictedCommand( "ui_allowconsole", UI_ToggleAllowConsole_f, "unlocks developer console" ); UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index e6149ff7..cfef780e 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -22,72 +22,88 @@ GNU General Public License for more details. #include "vgui_draw.h" #include "library.h" #include "vid_common.h" +#include "pm_local.h" +#include "multi_emulator.h" -#define MAX_TOTAL_CMDS 32 -#define MAX_CMD_BUFFER 8000 -#define CONNECTION_PROBLEM_TIME 15.0 // 15 seconds -#define CL_CONNECTION_RETRIES 10 -#define CL_TEST_RETRIES_NORESPONCE 2 -#define CL_TEST_RETRIES 5 +#define MAX_CMD_BUFFER 8000 +#define CL_CONNECTION_TIMEOUT 15.0f +#define CL_CONNECTION_RETRIES 10 +#define CL_TEST_RETRIES 5 +CVAR_DEFINE_AUTO( showpause, "1", 0, "show pause logo when paused" ); CVAR_DEFINE_AUTO( mp_decals, "300", FCVAR_ARCHIVE, "decals limit in multiplayer" ); -CVAR_DEFINE_AUTO( dev_overview, "0", 0, "draw level in overview-mode" ); -CVAR_DEFINE_AUTO( cl_resend, "6.0", 0, "time to resend connect" ); -CVAR_DEFINE_AUTO( cl_allow_download, "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); -CVAR_DEFINE_AUTO( cl_allow_upload, "1", FCVAR_ARCHIVE, "allow to uploading resources to the server" ); +static CVAR_DEFINE_AUTO( dev_overview, "0", 0, "draw level in overview-mode" ); +static CVAR_DEFINE_AUTO( cl_resend, "6.0", 0, "time to resend connect" ); +CVAR_DEFINE( cl_allow_download, "cl_allowdownload", "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); +static CVAR_DEFINE( cl_allow_upload, "cl_allowupload", "1", FCVAR_ARCHIVE, "allow to uploading resources to the server" ); CVAR_DEFINE_AUTO( cl_download_ingame, "1", FCVAR_ARCHIVE, "allow to downloading resources while client is active" ); -CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" ); -CVAR_DEFINE_AUTO( cl_logocolor, "orange", FCVAR_ARCHIVE, "player logo color" ); -CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" ); -convar_t *rcon_client_password; -convar_t *rcon_address; -convar_t *cl_timeout; -convar_t *cl_nopred; -convar_t *cl_nodelta; -convar_t *cl_crosshair; -convar_t *cl_cmdbackup; -convar_t *cl_showerror; -convar_t *cl_bmodelinterp; -convar_t *cl_draw_particles; -convar_t *cl_draw_tracers; -convar_t *cl_lightstyle_lerping; -convar_t *cl_idealpitchscale; -convar_t *cl_nosmooth; -convar_t *cl_smoothtime; -convar_t *cl_clockreset; -convar_t *cl_fixtimerate; -convar_t *hud_scale; -convar_t *cl_solid_players; -convar_t *cl_draw_beams; -convar_t *cl_updaterate; -convar_t *cl_showevents; -convar_t *cl_cmdrate; -convar_t *cl_interp; -convar_t *cl_nointerp; -convar_t *cl_dlmax; -convar_t *cl_upmax; +static CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" ); +static CVAR_DEFINE_AUTO( cl_logocolor, "orange", FCVAR_ARCHIVE, "player logo color" ); +static CVAR_DEFINE_AUTO( cl_logoext, "bmp", FCVAR_ARCHIVE, "temporary cvar to tell engine which logo must be packed" ); +CVAR_DEFINE_AUTO( cl_logomaxdim, "96", FCVAR_ARCHIVE, "maximum decal dimension" ); +static CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" ); -convar_t *cl_lw; -convar_t *cl_charset; -convar_t *cl_trace_messages; -convar_t *cl_nat; -convar_t *hud_utf8; -convar_t *ui_renderworld; +CVAR_DEFINE( cl_draw_particles, "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); +CVAR_DEFINE( cl_draw_tracers, "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); +CVAR_DEFINE( cl_draw_beams, "r_drawbeams", "1", FCVAR_CHEAT, "render beams" ); + +static CVAR_DEFINE_AUTO( rcon_address, "", FCVAR_PRIVILEGED, "remote control address" ); +CVAR_DEFINE_AUTO( cl_timeout, "60", 0, "connect timeout (in-seconds)" ); +CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" ); +static CVAR_DEFINE_AUTO( cl_nodelta, "0", 0, "disable delta-compression for server messages" ); +CVAR_DEFINE( cl_crosshair, "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); +static CVAR_DEFINE_AUTO( cl_cmdbackup, "10", FCVAR_ARCHIVE, "how many additional history commands are sent" ); +CVAR_DEFINE_AUTO( cl_showerror, "0", FCVAR_ARCHIVE, "show prediction error" ); +CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", FCVAR_ARCHIVE, "enable bmodel interpolation" ); +static CVAR_DEFINE_AUTO( cl_lightstyle_lerping, "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" ); +CVAR_DEFINE_AUTO( cl_idealpitchscale, "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" ); +CVAR_DEFINE_AUTO( cl_nosmooth, "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" ); +CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", FCVAR_ARCHIVE, "time to smooth up" ); +CVAR_DEFINE_AUTO( cl_clockreset, "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" ); +static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" ); +CVAR_DEFINE_AUTO( hud_fontscale, "1.0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud font texture" ); +CVAR_DEFINE_AUTO( hud_fontrender, "0", FCVAR_ARCHIVE, "hud font render mode (0: additive, 1: holes, 2: trans)" ); +CVAR_DEFINE_AUTO( hud_scale, "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" ); +CVAR_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" ); +CVAR_DEFINE_AUTO( cl_solid_players, "1", 0, "Make all players not solid (can't traceline them)" ); +CVAR_DEFINE_AUTO( cl_updaterate, "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); +CVAR_DEFINE_AUTO( cl_showevents, "0", FCVAR_ARCHIVE, "show events playback" ); +CVAR_DEFINE_AUTO( cl_cmdrate, "30", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); +CVAR_DEFINE( cl_interp, "ex_interp", "0.1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "Interpolate object positions starting this many seconds in past" ); +CVAR_DEFINE_AUTO( cl_nointerp, "0", 0, "disable interpolation of entities and players" ); +static CVAR_DEFINE_AUTO( cl_dlmax, "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); +static CVAR_DEFINE_AUTO( cl_upmax, "508", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); + +CVAR_DEFINE_AUTO( cl_lw, "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable client weapon predicting" ); +CVAR_DEFINE_AUTO( cl_charset, "utf-8", FCVAR_ARCHIVE, "1-byte charset to use (iconv style)" ); +CVAR_DEFINE_AUTO( cl_trace_consistency, "0", FCVAR_ARCHIVE, "enable consistency info tracing (good for developers)" ); +CVAR_DEFINE_AUTO( cl_trace_stufftext, "0", FCVAR_ARCHIVE, "enable stufftext (server-to-client console commands) tracing (good for developers)" ); +CVAR_DEFINE_AUTO( cl_trace_messages, "0", FCVAR_ARCHIVE|FCVAR_CHEAT, "enable message names tracing (good for developers)" ); +CVAR_DEFINE_AUTO( cl_trace_events, "0", FCVAR_ARCHIVE|FCVAR_CHEAT, "enable events tracing (good for developers)" ); +static CVAR_DEFINE_AUTO( cl_nat, "0", 0, "show servers running under NAT" ); +CVAR_DEFINE_AUTO( hud_utf8, "0", FCVAR_ARCHIVE, "Use utf-8 encoding for hud text" ); +CVAR_DEFINE_AUTO( ui_renderworld, "0", FCVAR_ARCHIVE, "render world when UI is visible" ); +static CVAR_DEFINE_AUTO( cl_maxframetime, "0", 0, "set deadline timer for client rendering to catch freezes" ); +CVAR_DEFINE_AUTO( cl_fixmodelinterpolationartifacts, "1", 0, "try to fix up models interpolation on a moving platforms (monsters on trains for example)" ); // // userinfo // -convar_t *name; -convar_t *model; -convar_t *topcolor; -convar_t *bottomcolor; -convar_t *rate; +static char username[32]; +static CVAR_DEFINE_AUTO( name, username, FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_PRINTABLEONLY|FCVAR_FILTERABLE, "player name" ); +static CVAR_DEFINE_AUTO( model, "", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player model ('player' is a singleplayer model)" ); +static CVAR_DEFINE_AUTO( topcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player top color" ); +static CVAR_DEFINE_AUTO( bottomcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player bottom color" ); +CVAR_DEFINE_AUTO( rate, "25000", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); + +static CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE, "you wouldn't steal a car" ); +static CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, "1", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "add [Xash3D] to the nickname when connecting to GoldSrc servers" ); client_t cl; client_static_t cls; clgame_static_t clgame; -void CL_InternetServers_f( void ); +static void CL_SendMasterServerScanRequest( void ); //====================================================================== int GAME_EXPORT CL_Active( void ) @@ -106,17 +122,12 @@ qboolean CL_IsInGame( void ) if( host.type == HOST_DEDICATED ) return true; // always active for dedicated servers - if( cl.background || CL_GetMaxClients() > 1 ) + if( cl.background || cl.maxclients > 1 ) return true; // always active for multiplayer or background map return ( cls.key_dest == key_game ); // active if not menu or console } -qboolean CL_IsInMenu( void ) -{ - return ( cls.key_dest == key_menu ); -} - qboolean CL_IsInConsole( void ) { return ( cls.key_dest == key_console ); @@ -137,26 +148,11 @@ qboolean CL_IsRecordDemo( void ) return cls.demorecording; } -qboolean CL_IsTimeDemo( void ) -{ - return cls.timedemo; -} - qboolean CL_DisableVisibility( void ) { return cls.envshot_disable_vis; } -qboolean CL_IsBackgroundDemo( void ) -{ - return ( cls.demoplayback && cls.demonum != -1 ); -} - -qboolean CL_IsBackgroundMap( void ) -{ - return ( cl.background && !cls.demoplayback ); -} - char *CL_Userinfo( void ) { return cls.userinfo; @@ -173,6 +169,11 @@ int CL_IsDevOverviewMode( void ) return 0; } +connprotocol_t CL_Protocol( void ) +{ + return cls.legacymode; +} + /* =============== CL_CheckClientState @@ -181,7 +182,7 @@ finalize connection process and begin new frame with new cls.state =============== */ -void CL_CheckClientState( void ) +static void CL_CheckClientState( void ) { // first update is the pre-final signon stage if(( cls.state == ca_connected || cls.state == ca_validate ) && ( cls.signon == SIGNONS )) @@ -199,18 +200,34 @@ void CL_CheckClientState( void ) } } -int CL_GetFragmentSize( void *unused, fragsize_t mode ) +static int CL_GetGoldSrcFragmentSize( void *unused, fragsize_t mode ) { - if( mode == FRAGSIZE_SPLIT ) + switch( mode ) + { + case FRAGSIZE_SPLIT: + return 1200; // MAX_RELIABLE_PAYLOAD + case FRAGSIZE_UNRELIABLE: + return 1400; // MAX_ROUTABLE_PACKET + default: + if( cls.state == ca_active ) + return bound( 16, cl_dlmax.value, 1024 ); + return 128; + } +} + +static int CL_GetFragmentSize( void *unused, fragsize_t mode ) +{ + switch( mode ) + { + case FRAGSIZE_SPLIT: return 0; - - if( mode == FRAGSIZE_UNRELIABLE ) + case FRAGSIZE_UNRELIABLE: return NET_MAX_MESSAGE; - - if( Netchan_IsLocal( &cls.netchan )) - return FRAGMENT_LOCAL_SIZE; - - return cl_upmax->value; + default: + if( Netchan_IsLocal( &cls.netchan )) + return FRAGMENT_LOCAL_SIZE; + return cl_upmax.value; + } } /* @@ -220,15 +237,15 @@ CL_SignonReply An svc_signonnum has been received, perform a client side setup ===================== */ -void CL_SignonReply( void ) +void CL_SignonReply( connprotocol_t proto ) { // g-cont. my favorite message :-) - Con_Reportf( "CL_SignonReply: %i\n", cls.signon ); + Con_Reportf( "%s: %i\n", __func__, cls.signon ); switch( cls.signon ) { case 1: - CL_ServerCommand( true, "begin" ); + CL_ServerCommand( true, proto == PROTO_GOLDSRC ? "sendents" : "begin" ); if( host_developer.value >= DEV_EXTENDED ) Mem_PrintStats(); break; @@ -241,11 +258,6 @@ void CL_SignonReply( void ) } } -float CL_LerpInterval( void ) -{ - return Q_max( cl_interp->value, 1.f / cl_updaterate->value ); -} - /* =============== CL_LerpPoint @@ -256,55 +268,26 @@ should be put at. */ static float CL_LerpPoint( void ) { - float frac = 1.0f; - float server_frametime = cl_serverframetime(); + double f = cl_serverframetime(); + double frac; - if( server_frametime == 0.0f || cls.timedemo ) + if( f == 0.0 || cls.timedemo ) { + double fgap = cl_clientframetime(); cl.time = cl.mtime[0]; + + // maybe don't need for Xash demos + if( cls.demoplayback ) + cl.oldtime = cl.mtime[0] - fgap; + return 1.0f; } - if( server_frametime > 0.1f ) - { - // dropped packet, or start of demo - cl.mtime[1] = cl.mtime[0] - 0.1f; - server_frametime = 0.1f; - } -#if 0 - /* - g-cont: this code more suitable for singleplayer - NOTE in multiplayer causes significant framerate stutter/jitter and - occuring frames with zero time delta and even with negative time delta. - game becomes more twitchy and as if without interpolation. - */ - frac = (cl.time - cl.mtime[1]) / f; - if( frac < 0.0f ) - { - if( frac < -0.01f ) - cl.time = cl.mtime[1]; - frac = 0.0f; - } - else if( frac > 1.0f ) - { - if( frac > 1.01f ) - cl.time = cl.mtime[0]; - frac = 1.0f; - } -#else - // for multiplayer - if( cl_interp->value > 0.001f ) - { - // manual lerp value (goldsrc mode) - float td = Q_max( 0.f, cl.time - cl.mtime[0] ); - frac = td / CL_LerpInterval(); - } - else if( server_frametime > 0.001f ) - { - // automatic lerp (classic mode) - frac = ( cl.time - cl.mtime[1] ) / server_frametime; - } -#endif + if( cl_interp.value <= 0.001 ) + return 1.0f; + + frac = ( cl.time - cl.mtime[0] ) / cl_interp.value; + return frac; } @@ -315,7 +298,7 @@ CL_DriftInterpolationAmount Drift interpolation value (this is used for server unlag system) =============== */ -int CL_DriftInterpolationAmount( int goal ) +static int CL_DriftInterpolationAmount( int goal ) { float fgoal, maxmove, diff; int msec; @@ -343,20 +326,20 @@ CL_ComputeClientInterpolationAmount Validate interpolation cvars, calc interpolation window =============== */ -void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) +static void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) { const float epsilon = 0.001f; // to avoid float invalid comparision - float min_interp = MIN_EX_INTERP; + float min_interp; float max_interp = MAX_EX_INTERP; float interpolation_time; - if( cl_updaterate->value < MIN_UPDATERATE ) + if( cl_updaterate.value < MIN_UPDATERATE ) { Con_Printf( "cl_updaterate minimum is %f, resetting to default (20)\n", MIN_UPDATERATE ); Cvar_Reset( "cl_updaterate" ); } - if( cl_updaterate->value > MAX_UPDATERATE ) + if( cl_updaterate.value > MAX_UPDATERATE ) { Con_Printf( "cl_updaterate clamped at maximum (%f)\n", MAX_UPDATERATE ); Cvar_SetValue( "cl_updaterate", MAX_UPDATERATE ); @@ -365,15 +348,15 @@ void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) if( cls.spectator ) max_interp = 0.2f; - min_interp = 1.0f / cl_updaterate->value; - interpolation_time = CL_LerpInterval( ); + min_interp = 1.0f / cl_updaterate.value; + interpolation_time = cl_interp.value * 1000.0; - if( (cl_interp->value + epsilon) < min_interp ) + if( (cl_interp.value + epsilon) < min_interp ) { Con_Printf( "ex_interp forced up to %.1f msec\n", min_interp * 1000.f ); Cvar_SetValue( "ex_interp", min_interp ); } - else if( (cl_interp->value - epsilon) > max_interp ) + else if( (cl_interp.value - epsilon) > max_interp ) { Con_Printf( "ex_interp forced down to %.1f msec\n", max_interp * 1000.f ); Cvar_SetValue( "ex_interp", max_interp ); @@ -389,32 +372,22 @@ CL_ComputePacketLoss ================= */ -void CL_ComputePacketLoss( void ) +static void CL_ComputePacketLoss( void ) { - int i, frm; - frame_t *frame; - int count = 0; - int lost = 0; + int i, lost = 0; if( host.realtime < cls.packet_loss_recalc_time ) return; - // recalc every second cls.packet_loss_recalc_time = host.realtime + 1.0; - // compuate packet loss for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ ) { - frm = i; - frame = &cl.frames[frm & CL_UPDATE_MASK]; - - if( frame->receivedtime == -1.0 ) + if( cl.frames[i & CL_UPDATE_MASK].receivedtime == -1.0 ) lost++; - count++; } - if( count <= 0 ) cls.packet_loss = 0.0f; - else cls.packet_loss = ( 100.0f * (float)lost ) / (float)count; + cls.packet_loss = lost * 100.0f / (float)CL_UPDATE_BACKUP; } /* @@ -434,7 +407,7 @@ void CL_UpdateFrameLerp( void ) cl.commands[(cls.netchan.outgoing_sequence - 1) & CL_UPDATE_MASK].frame_lerp = cl.lerpFrac; } -void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, pred_viewangle_t **next ) +static void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, pred_viewangle_t **next ) { int i, i0, i1, imod; float at; @@ -476,7 +449,7 @@ void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, *frac = bound( 0.0f, *frac, 1.0f ); } -void CL_ApplyAddAngle( void ) +static void CL_ApplyAddAngle( void ) { pred_viewangle_t *prev = NULL, *next = NULL; float addangletotal = 0.0f; @@ -512,23 +485,22 @@ CL_ProcessShowTexturesCmds navigate around texture atlas =============== */ -qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) +static qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) { static int oldbuttons; int changed; - int pressed, released; + int released; - if( !gl_showtextures->value || CL_IsDevOverviewMode( )) + if( !r_showtextures.value || CL_IsDevOverviewMode( )) return false; changed = (oldbuttons ^ cmd->buttons); - pressed = changed & cmd->buttons; released = changed & (~cmd->buttons); if( released & ( IN_RIGHT|IN_MOVERIGHT )) - Cvar_SetValue( "r_showtextures", gl_showtextures->value + 1 ); + Cvar_SetValue( "r_showtextures", r_showtextures.value + 1 ); if( released & ( IN_LEFT|IN_MOVELEFT )) - Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); + Cvar_SetValue( "r_showtextures", Q_max( 1, r_showtextures.value - 1 )); oldbuttons = cmd->buttons; return true; @@ -541,7 +513,7 @@ CL_ProcessOverviewCmds Transform user movement into overview adjust =============== */ -qboolean CL_ProcessOverviewCmds( usercmd_t *cmd ) +static qboolean CL_ProcessOverviewCmds( usercmd_t *cmd ) { ref_overview_t *ov = &clgame.overView; int sign = 1; @@ -549,7 +521,7 @@ qboolean CL_ProcessOverviewCmds( usercmd_t *cmd ) float step = (2.0f / size) * host.realframetime; float step2 = step * 100.0f * (2.0f / ov->flZoom); - if( !CL_IsDevOverviewMode() || gl_showtextures->value ) + if( !CL_IsDevOverviewMode() || r_showtextures.value ) return false; if( ov->flZoom < 0.0f ) sign = -1; @@ -593,7 +565,7 @@ CL_UpdateClientData tell the client.dll about player origin, angles, fov, etc ================= */ -void CL_UpdateClientData( void ) +static void CL_UpdateClientData( void ) { client_data_t cdat; @@ -620,24 +592,40 @@ void CL_UpdateClientData( void ) CL_CreateCmd ================= */ -void CL_CreateCmd( void ) +static void CL_CreateCmd( void ) { - usercmd_t cmd; - runcmd_t *pcmd; - vec3_t angles; - qboolean active; - int input_override; - int i, ms; + usercmd_t nullcmd = { 0 }, *cmd; + runcmd_t *pcmd; + qboolean active; + double accurate_ms; + vec3_t angles; + int input_override; + int i, ms; - if( cls.state < ca_connected || cls.state == ca_cinematic ) + if( cls.state <= ca_connected || cls.state == ca_cinematic ) return; // store viewangles in case it's will be freeze VectorCopy( cl.viewangles, angles ); - ms = bound( 1, host.frametime * 1000, 255 ); - memset( &cmd, 0, sizeof( cmd )); input_override = 0; + // fix rounding error and framerate depending player move + accurate_ms = host.frametime * 1000; + ms = (int)accurate_ms; + cl.frametime_remainder += accurate_ms - ms; // accumulate rounding error each frame + + // add a ms if error accumulates enough + if( cl.frametime_remainder >= 1.0 ) + { + int ms2 = (int)cl.frametime_remainder; + + ms += ms2; + cl.frametime_remainder -= ms2; + } + + // ms can't be negative, rely on error accumulation only if FPS > 1000 + ms = Q_min( ms, 255 ); + CL_SetSolidEntities(); CL_PushPMStates(); CL_SetSolidPlayers( cl.playernum ); @@ -645,21 +633,26 @@ void CL_CreateCmd( void ) // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; pcmd = &cl.commands[i]; - pcmd->processedfuncs = false; if( !cls.demoplayback ) { + pcmd->processedfuncs = false; pcmd->senttime = host.realtime; memset( &pcmd->cmd, 0, sizeof( pcmd->cmd )); pcmd->receivedtime = -1.0; pcmd->heldback = false; pcmd->sendsize = 0; + cmd = &pcmd->cmd; + } + else + { + cmd = &nullcmd; } active = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback ); Platform_PreCreateMove(); - clgame.dllFuncs.CL_CreateMove( host.frametime, &pcmd->cmd, active ); - IN_EngineAppendMove( host.frametime, &pcmd->cmd, active ); + clgame.dllFuncs.CL_CreateMove( host.frametime, cmd, active ); + IN_EngineAppendMove( host.frametime, cmd, active ); CL_PopPMStates(); @@ -681,34 +674,32 @@ void CL_CreateCmd( void ) } // demo always have commands so don't overwrite them - if( !cls.demoplayback ) cl.cmd = &pcmd->cmd; + if( !cls.demoplayback ) cl.cmd = pcmd->cmd; // predict all unacknowledged movements CL_PredictMovement( false ); } -void CL_WriteUsercmd( sizebuf_t *msg, int from, int to ) +void CL_WriteUsercmd( connprotocol_t proto, sizebuf_t *msg, int from, int to ) { - usercmd_t nullcmd; - usercmd_t *f, *t; + const usercmd_t nullcmd = { 0 }; + const usercmd_t *f; + usercmd_t *t; Assert( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP )); Assert( to >= 0 && to < MULTIPLAYER_BACKUP ); - if( from == -1 ) - { - memset( &nullcmd, 0, sizeof( nullcmd )); - f = &nullcmd; - } - else - { - f = &cl.commands[from].cmd; - } - + f = from == -1 ? &nullcmd : &cl.commands[from].cmd; t = &cl.commands[to].cmd; // write it into the buffer - MSG_WriteDeltaUsercmd( msg, f, t ); + if( proto == PROTO_GOLDSRC ) + { + MSG_StartBitWriting( msg ); + Delta_WriteGSFields( msg, DT_USERCMD_T, f, t, 0.0f ); + MSG_EndBitWriting( msg ); + } + else MSG_WriteDeltaUsercmd( msg, f, t ); } /* @@ -719,178 +710,172 @@ Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ -void CL_WritePacket( void ) +static void CL_WritePacket( void ) { - sizebuf_t buf; - qboolean send_command = false; - byte data[MAX_CMD_BUFFER]; - int i, from, to, key, size; - int numbackup = 2; - int numcmds; - int newcmds; - int cmdnumber; + sizebuf_t buf; + byte data[MAX_CMD_BUFFER] = { 0 }; + runcmd_t *pcmd; + int numbackup, maxbackup, maxcmds; + const connprotocol_t proto = cls.legacymode; + + // FIXME: on Xash protocol we don't send move commands until ca_active + // to prevent outgoing_command outrun incoming_acknowledged + // which is fatal for some buggy mods like TFC + // + // ... but GoldSrc don't have (real) ca_validate state, so we consider + // ca_validate the same as ca_active, otherwise we don't pass validation + // of server-side mods like ReAuthCheck + const connstate_t min_state = proto == PROTO_GOLDSRC ? ca_validate : ca_active; // don't send anything if playing back a demo if( cls.demoplayback || cls.state < ca_connected || cls.state == ca_cinematic ) return; - CL_ComputePacketLoss (); + if( cls.state < min_state ) + { + Netchan_TransmitBits( &cls.netchan, 0, "" ); + return; + } + // cls.state can only be ca_validate or ca_active from here + + CL_ComputePacketLoss( ); MSG_Init( &buf, "ClientData", data, sizeof( data )); - // Determine number of backup commands to send along - numbackup = bound( 0, cl_cmdbackup->value, MAX_BACKUP_COMMANDS ); - if( cls.state == ca_connected ) numbackup = 0; + switch( proto ) + { + case PROTO_GOLDSRC: + maxbackup = MAX_GOLDSRC_BACKUP_CMDS; + maxcmds = MAX_GOLDSRC_TOTAL_CMDS; + break; + case PROTO_LEGACY: + maxbackup = MAX_LEGACY_BACKUP_CMDS; + maxcmds = MAX_LEGACY_TOTAL_CMDS; + break; + default: + maxbackup = MAX_BACKUP_COMMANDS; + maxcmds = MAX_TOTAL_CMDS; + break; + } + + numbackup = bound( 0, cl_cmdbackup.value, maxbackup ); + + // allow extended usercmd limit + if( proto == PROTO_GOLDSRC && cls.build_num >= 5971 ) + maxcmds = MAX_GOLDSRC_EXTENDED_TOTAL_CMDS - numbackup; // clamp cmdrate - if( cl_cmdrate->value < 0.0f ) Cvar_SetValue( "cl_cmdrate", 0.0f ); - else if( cl_cmdrate->value > 100.0f ) Cvar_SetValue( "cl_cmdrate", 100.0f ); + if( cl_cmdrate.value < 10.0f ) + Cvar_DirectSet( &cl_cmdrate, "10" ); + else if( cl_cmdrate.value > 100.0f ) + Cvar_DirectSet( &cl_cmdrate, "100" ); - // Check to see if we can actually send this command + // are we hltv spectator? + if( cls.spectator && cl.delta_sequence == cl.validsequence && ( !cls.demorecording || !cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime ) + return; - // In single player, send commands as fast as possible - // Otherwise, only send when ready and when not choking bandwidth - if( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->value )) - send_command = true; + // can send this command? + pcmd = &cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK]; - if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan, true )) - send_command = true; + if( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal.value ) || ( host.realtime >= cls.nextcmdtime && Netchan_CanPacket( &cls.netchan, true ))) + pcmd->heldback = false; + else pcmd->heldback = true; - if( cl.send_reply ) + // immediately add it to the demo, regardless if we send the message or not + if( cls.demorecording ) + CL_WriteDemoUserCmd( cls.netchan.outgoing_sequence & CL_UPDATE_MASK ); + + if( !pcmd->heldback ) { - cl.send_reply = false; - send_command = true; - } + int newcmds, numcmds; + int from, i, key; + int packet_loss = bound( 0, (int)cls.packet_loss, 100 ); - // spectator is not sending cmds to server - if( cls.spectator && cls.state == ca_active && cl.delta_sequence == cl.validsequence ) - { - if( !( cls.demorecording && cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime ) - return; - } + cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate.value ); - if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) - { - if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) - { - Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); - cl.validsequence = 0; - } - } - - if( cl_nodelta->value ) - cl.validsequence = 0; - - if( send_command ) - { - int outgoing_sequence; - - if( cl_cmdrate->value > 0 ) // clamped between 10 and 100 fps - cls.nextcmdtime = host.realtime + bound( 0.1f, ( 1.0f / cl_cmdrate->value ), 0.01f ); - else cls.nextcmdtime = host.realtime; // always able to send right away - - if( cls.lastoutgoingcommand == -1 ) - { - outgoing_sequence = cls.netchan.outgoing_sequence; + if( cls.lastoutgoingcommand < 0 ) cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; - } - else outgoing_sequence = cls.lastoutgoingcommand + 1; - // begin a client move command + newcmds = cls.netchan.outgoing_sequence - cls.lastoutgoingcommand; + newcmds = bound( 0, newcmds, maxcmds ); + numcmds = newcmds + numbackup; + + // goldsrc starts writing clc_move earlier but it doesn't make sense if it's not going to be sent MSG_BeginClientCmd( &buf, clc_move ); - // save the position for a checksum byte + if( proto == PROTO_GOLDSRC ) + MSG_WriteByte( &buf, 0 ); // command length + key = MSG_GetRealBytesWritten( &buf ); MSG_WriteByte( &buf, 0 ); - // write packet lossage percentation - MSG_WriteByte( &buf, cls.packet_loss ); + if( proto == PROTO_GOLDSRC && voice_loopback.value ) + SetBits( packet_loss, BIT( 7 ) ); // set 7-th bit to tell server that we want voice loopback - // say how many backups we'll be sending + MSG_WriteByte( &buf, packet_loss ); MSG_WriteByte( &buf, numbackup ); - - // how many real commands have queued up - newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); - - // put an upper/lower bound on this - newcmds = bound( 0, newcmds, cls.legacymode?MAX_LEGACY_TOTAL_CMDS:MAX_TOTAL_CMDS ); - if( cls.state == ca_connected ) newcmds = 0; - MSG_WriteByte( &buf, newcmds ); - numcmds = newcmds + numbackup; - from = -1; - - for( i = numcmds - 1; i >= 0; i-- ) + for( from = -1, i = numcmds - 1; i >= 0; i-- ) { - cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; + int to = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; - to = cmdnumber; - CL_WriteUsercmd( &buf, from, to ); + CL_WriteUsercmd( proto, &buf, from, to ); from = to; - - if( MSG_CheckOverflow( &buf )) - Host_Error( "CL_WritePacket: overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } - // calculate a checksum over the move commands - size = MSG_GetRealBytesWritten( &buf ) - key - 1; - buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); + // finalize message + if( proto == PROTO_GOLDSRC ) + { + int size = MSG_GetRealBytesWritten( &buf ) - key - 1; - // message we are constructing. - i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; + buf.pData[key - 1] = Q_min( size, 255 ); + buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence ); + COM_Munge( &buf.pData[key + 1], Q_min( size, 255 ), cls.netchan.outgoing_sequence ); + } + else if( !Host_IsLocalClient( )) + { + int size = MSG_GetRealBytesWritten( &buf ) - key - 1; + buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence ); + } - // determine if we need to ask for a new set of delta's. - if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) + // check if we're timing out + if( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged >= CL_UPDATE_MASK && host.realtime - cls.netchan.last_received >= CL_CONNECTION_TIMEOUT ) + { + Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); + Con_NPrintf( 2, "^1Auto-disconnect in %.1f seconds^7", cl_timeout.value - ( host.realtime - cls.netchan.last_received )); + cl.validsequence = 0; + } + + if( cl_nodelta.value ) + cl.validsequence = 0; + + if( cl.validsequence && ( !cls.demorecording || !cls.demowaiting )) { cl.delta_sequence = cl.validsequence; - MSG_BeginClientCmd( &buf, clc_delta ); - MSG_WriteByte( &buf, cl.validsequence & 0xFF ); - } - else - { - // request delta compression of entities - cl.delta_sequence = -1; + MSG_WriteByte( &buf, cl.validsequence & 0xff ); } + else cl.delta_sequence = -1; - if( MSG_CheckOverflow( &buf )) - Host_Error( "CL_WritePacket: overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); - - // remember outgoing command that we are sending + // command finished, remember last sent sequence id cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; + pcmd->sendsize = MSG_GetNumBytesWritten( &buf ); - // update size counter for netgraph - cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].sendsize = MSG_GetNumBytesWritten( &buf ); - cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false; - - // send voice data to the server CL_AddVoiceToDatagram(); - // composite the rest of the datagram.. + // now add unreliable, if there is enough space if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf )) MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram )); MSG_Clear( &cls.datagram ); - // deliver the message (or update reliable) Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); } else { - // mark command as held back so we'll send it next time - cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = true; - - // increment sequence number so we can detect that we've held back packets. cls.netchan.outgoing_sequence++; } - if( cls.demorecording && numbackup > 0 ) - { - // Back up one because we've incremented outgoing_sequence each frame by 1 unit - cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; - CL_WriteDemoUserCmd( cmdnumber ); - } - // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); } @@ -902,7 +887,7 @@ CL_SendCommand Called every frame to builds and sends a command packet to the server. ================= */ -void CL_SendCommand( void ) +static void CL_SendCommand( void ) { // we create commands even if a demo is playing, CL_CreateCmd(); @@ -916,7 +901,7 @@ void CL_SendCommand( void ) CL_BeginUpload_f ================== */ -void CL_BeginUpload_f( void ) +static void CL_BeginUpload_f( void ) { const char *name; resource_t custResource; @@ -941,16 +926,16 @@ void CL_BeginUpload_f( void ) memset( &custResource, 0, sizeof( custResource )); COM_HexConvert( name + 4, 32, md5 ); - if( HPAK_ResourceForHash( CUSTOM_RES_PATH, md5, &custResource )) + if( HPAK_ResourceForHash( hpk_custom_file.string, md5, &custResource )) { if( memcmp( md5, custResource.rgucMD5_hash, 16 )) { - Con_Reportf( "Bogus data retrieved from %s, attempting to delete entry\n", CUSTOM_RES_PATH ); - HPAK_RemoveLump( CUSTOM_RES_PATH, &custResource ); + Con_Reportf( "Bogus data retrieved from %s, attempting to delete entry\n", hpk_custom_file.string ); + HPAK_RemoveLump( hpk_custom_file.string, &custResource ); return; } - if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &custResource, &buf, &size )) + if( HPAK_GetDataPointer( hpk_custom_file.string, &custResource, &buf, &size )) { byte md5[16]; MD5Context_t ctx; @@ -966,7 +951,7 @@ void CL_BeginUpload_f( void ) Con_Reportf( "Purported: %s\n", MD5_Print( custResource.rgucMD5_hash ) ); Con_Reportf( "Actual : %s\n", MD5_Print( md5 ) ); Con_Reportf( "Removing conflicting lump\n" ); - HPAK_RemoveLump( CUSTOM_RES_PATH, &custResource ); + HPAK_RemoveLump( hpk_custom_file.string, &custResource ); return; } } @@ -988,7 +973,7 @@ CL_Quit_f void CL_Quit_f( void ) { CL_Disconnect(); - Sys_Quit(); + Sys_Quit( "command" ); } /* @@ -1005,6 +990,56 @@ void CL_Drop( void ) CL_Disconnect(); } +static void CL_GetCDKey( char *protinfo, size_t protinfosize ) +{ + byte hash[16] = { 0 }; + MD5Context_t ctx = { 0 }; + char key[64]; + int keylength; + + keylength = Q_snprintf( key, sizeof( key ), "%u", COM_RandomLong( 0, 0x7ffffffe )); + + MD5Init( &ctx ); + MD5Update( &ctx, key, keylength ); + MD5Final( hash, &ctx ); + + Q_strnlwr( MD5_Print( hash ), key, sizeof( key )); + + Info_SetValueForKey( protinfo, "cdkey", key, protinfosize ); +} + +static void CL_WriteSteamTicket( sizebuf_t *send ) +{ + const char *s; + uint32_t crc; + char buf[768] = { 0 }; // setti and steamemu return 768 + int i = sizeof( buf ); + + if( !Q_strcmp( cl_ticket_generator.string, "null" )) + { + MSG_WriteBytes( send, buf, 512 ); // specifically 512 bytes of zeros + return; + } + + //if( !Q_strcmp( cl_ticket_generator.string, "steam" ) + //{ + // i = SteamBroker_InitiateGameConnection( buf, sizeof( buf )); + // MSG_WriteBytes( send, buf, i ); + // return; + //} + + s = ID_GetMD5(); + CRC32_Init( &crc ); + CRC32_ProcessBuffer( &crc, s, Q_strlen( s )); + crc = CRC32_Final( crc ); + i = GenerateRevEmu2013( buf, s, crc ); + MSG_WriteBytes( send, buf, i ); + + // RevEmu2013: pTicket[1] = revHash (low), pTicket[5] = 0x01100001 (high) + *(uint32_t*)cls.steamid = LittleLong( ((uint32_t*)buf)[1] ); + *(uint32_t*)(cls.steamid + 4) = LittleLong( ((uint32_t*)buf)[5] ); +} + /* ======================= CL_SendConnectPacket @@ -1013,82 +1048,151 @@ We have gotten a challenge from the server, so try and connect. ====================== */ -void CL_SendConnectPacket( void ) +static void CL_SendConnectPacket( connprotocol_t proto, int challenge ) { - char protinfo[MAX_INFO_STRING]; - const char *qport; - const char *key; - netadr_t adr; + char protinfo[MAX_INFO_STRING]; + const char *key = ID_GetMD5(); + netadr_t adr = { 0 }; + int input_devices; + netadrtype_t adrtype; + + protinfo[0] = 0; if( !NET_StringToAdr( cls.servername, &adr )) { - Con_Printf( "CL_SendConnectPacket: bad server address\n"); + Con_Printf( "%s: bad server address\n", __func__ ); cls.connect_time = 0; return; } + adrtype = NET_NetadrType( &adr ); + if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); - qport = Cvar_VariableString( "net_qport" ); - key = ID_GetMD5(); - memset( protinfo, 0, sizeof( protinfo )); + input_devices = IN_CollectInputDevices(); + IN_LockInputDevices( adrtype != NA_LOOPBACK ? true : false ); - if( adr.type == NA_LOOPBACK ) + // GoldSrc doesn't need sv_cheats set to 0, it's handled by svc_goldsrc_sendextrainfo + // it also doesn't need useragent string + if( adrtype != NA_LOOPBACK && proto != PROTO_GOLDSRC ) { - IN_LockInputDevices( false ); - } - else - { - int input_devices; + Cvar_SetCheatState(); + Cvar_FullSet( "sv_cheats", "0", FCVAR_READ_ONLY | FCVAR_SERVER ); - input_devices = IN_CollectInputDevices(); - IN_LockInputDevices( true ); - - Info_SetValueForKey( protinfo, "d", va( "%d", input_devices ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "d", sizeof( protinfo ), "%d", input_devices ); Info_SetValueForKey( protinfo, "v", XASH_VERSION, sizeof( protinfo ) ); - Info_SetValueForKey( protinfo, "b", va( "%d", Q_buildnum() ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "b", sizeof( protinfo ), "%d", Q_buildnum( )); Info_SetValueForKey( protinfo, "o", Q_buildos(), sizeof( protinfo ) ); Info_SetValueForKey( protinfo, "a", Q_buildarch(), sizeof( protinfo ) ); } - if( cls.legacymode ) + if( proto == PROTO_GOLDSRC ) { - // set related userinfo keys - if( cl_dlmax->value >= 40000 || cl_dlmax->value < 100 ) - Info_SetValueForKey( cls.userinfo, "cl_maxpacket", "1400", sizeof( cls.userinfo ) ); - else - Info_SetValueForKey( cls.userinfo, "cl_maxpacket", cl_dlmax->string, sizeof( cls.userinfo ) ); + const char *name; + sizebuf_t send; + byte send_buf[2048]; - if( !*Info_ValueForKey( cls.userinfo,"cl_maxpayload") ) + Info_SetValueForKey( protinfo, "prot", "3", sizeof( protinfo )); // steam auth type + Info_SetValueForKeyf( protinfo, "unique", sizeof( protinfo ), "%i", 0xffffffff ); + Info_SetValueForKey( protinfo, "raw", "steam", sizeof( protinfo )); + CL_GetCDKey( protinfo, sizeof( protinfo )); + + // remove keys set for legacy protocol + Info_RemoveKey( cls.userinfo, "cl_maxpacket" ); + Info_RemoveKey( cls.userinfo, "cl_maxpayload" ); + + name = Info_ValueForKey( cls.userinfo, "name" ); + if( cl_advertise_engine_in_name.value && Q_strnicmp( name, "[Xash3D]", 8 )) + Info_SetValueForKeyf( cls.userinfo, "name", sizeof( cls.userinfo ), "[Xash3D]%s", name ); + + MSG_Init( &send, "GoldSrcConnect", send_buf, sizeof( send_buf )); + MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET ); + MSG_WriteStringf( &send, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", + PROTOCOL_GOLDSRC_VERSION, challenge, protinfo, cls.userinfo ); + MSG_SeekToBit( &send, -8, SEEK_CUR ); // rewrite null terminator + CL_WriteSteamTicket( &send ); + + if( MSG_CheckOverflow( &send )) + Con_Printf( S_ERROR "%s: %s overflow!\n", __func__, MSG_GetName( &send ) ); + + NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr ); + Con_Printf( "Trying to connect with GoldSrc 48 protocol\n" ); + } + else if( proto == PROTO_LEGACY ) + { + const char *dlmax; + int qport = Cvar_VariableInteger( "net_qport" ); + + // reset nickname from cvar value + Info_SetValueForKey( cls.userinfo, "name", name.string, sizeof( cls.userinfo )); + + // set related userinfo keys + dlmax = ( cl_dlmax.value >= 100 && cl_dlmax.value < 40000 ) ? cl_dlmax.string : "1400"; + Info_SetValueForKey( cls.userinfo, "cl_maxpacket", dlmax, sizeof( cls.userinfo )); + + if( !COM_CheckStringEmpty( Info_ValueForKey( cls.userinfo, "cl_maxpayload" ))) Info_SetValueForKey( cls.userinfo, "cl_maxpayload", "1000", sizeof( cls.userinfo ) ); - Info_SetValueForKey( protinfo, "i", key, sizeof( protinfo ) ); + Info_SetValueForKey( protinfo, "i", key, sizeof( protinfo )); - Netchan_OutOfBandPrint( NS_CLIENT, adr, "connect %i %i %i \"%s\" %d \"%s\"\n", - PROTOCOL_LEGACY_VERSION, Q_atoi( qport ), cls.challenge, cls.userinfo, NET_LEGACY_EXT_SPLIT, protinfo ); - Con_Printf( "Trying to connect by legacy protocol\n" ); + Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i %i \"%s\" %d \"%s\"\n", + PROTOCOL_LEGACY_VERSION, qport, challenge, cls.userinfo, NET_LEGACY_EXT_SPLIT, protinfo ); + Con_Printf( "Trying to connect with legacy protocol\n" ); } else { + const char *qport = Cvar_VariableString( "net_qport" ); int extensions = NET_EXT_SPLITSIZE; - if( cl_dlmax->value > FRAGMENT_MAX_SIZE || cl_dlmax->value < FRAGMENT_MIN_SIZE ) - Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); + // reset nickname from cvar value + Info_SetValueForKey( cls.userinfo, "name", name.string, sizeof( cls.userinfo )); + if( cl_dlmax.value > FRAGMENT_MAX_SIZE || cl_dlmax.value < FRAGMENT_MIN_SIZE ) + Cvar_DirectSetValue( &cl_dlmax, FRAGMENT_DEFAULT_SIZE ); + + // remove keys set for legacy protocol Info_RemoveKey( cls.userinfo, "cl_maxpacket" ); Info_RemoveKey( cls.userinfo, "cl_maxpayload" ); Info_SetValueForKey( protinfo, "uuid", key, sizeof( protinfo )); Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo )); - Info_SetValueForKey( protinfo, "ext", va("%d", extensions), sizeof( protinfo )); + Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", extensions); - Netchan_OutOfBandPrint( NS_CLIENT, adr, "connect %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, cls.challenge, protinfo, cls.userinfo ); - Con_Printf( "Trying to connect by modern protocol\n" ); + Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, challenge, protinfo, cls.userinfo ); + Con_Printf( "Trying to connect with modern protocol\n" ); } cls.timestart = Sys_DoubleTime(); } +/* +================= +CL_GetTestFragmentSize + +Returns bandwidth test fragment size +================= +*/ +static int CL_GetTestFragmentSize( void ) +{ + // const int fragmentSizes[CL_TEST_RETRIES] = { 64000, 32000, 10666, 5200, 1400 }; + + // it turns out, even if we pass the bandwidth test, it doesn't mean we can use such large fragments + // as a temporary solution, use smaller fragment sizes + const int fragmentSizes[CL_TEST_RETRIES] = { 1400, 1200, 1000, 800, 508 }; + if( cls.connect_retry >= 0 && cls.connect_retry < CL_TEST_RETRIES ) + return bound( FRAGMENT_MIN_SIZE, fragmentSizes[cls.connect_retry], FRAGMENT_MAX_SIZE ); + else + return FRAGMENT_MIN_SIZE; +} + +static void CL_SendGetChallenge( netadr_t to ) +{ + // always send GoldSrc-styled getchallenge message + // Xash servers will ignore it but for GoldSrc it will help + // in auto-detection + Netchan_OutOfBandPrint( NS_CLIENT, to, C2S_GETCHALLENGE" steam\n" ); +} + /* ================= CL_CheckForResend @@ -1096,13 +1200,15 @@ CL_CheckForResend Resend a connect message if the last one has timed out ================= */ -void CL_CheckForResend( void ) +static void CL_CheckForResend( void ) { - netadr_t adr; - int res; + netadr_t adr; + net_gai_state_t res; + float resendTime; + qboolean bandwidthTest; if( cls.internetservers_wait ) - CL_InternetServers_f(); + CL_SendMasterServerScanRequest(); // if the local server is running and we aren't then connect if( cls.state == ca_disconnected && SV_Active( )) @@ -1110,10 +1216,11 @@ void CL_CheckForResend( void ) cls.signon = 0; cls.state = ca_connecting; Q_strncpy( cls.servername, "localhost", sizeof( cls.servername )); - cls.serveradr.type = NA_LOOPBACK; + NET_NetadrSetType( &cls.serveradr, NA_LOOPBACK ); + cls.legacymode = PROTO_CURRENT; // we don't need a challenge on the localhost - CL_SendConnectPacket(); + CL_SendConnectPacket( PROTO_CURRENT, 0 ); return; } @@ -1122,22 +1229,25 @@ void CL_CheckForResend( void ) return; if( cl_resend.value < CL_MIN_RESEND_TIME ) - Cvar_SetValue( "cl_resend", CL_MIN_RESEND_TIME ); + Cvar_DirectSetValue( &cl_resend, CL_MIN_RESEND_TIME ); else if( cl_resend.value > CL_MAX_RESEND_TIME ) - Cvar_SetValue( "cl_resend", CL_MAX_RESEND_TIME ); + Cvar_DirectSetValue( &cl_resend, CL_MAX_RESEND_TIME ); - if(( host.realtime - cls.connect_time ) < cl_resend.value ) + bandwidthTest = cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && cls.connect_retry <= CL_TEST_RETRIES; + resendTime = bandwidthTest ? 2.0f : cl_resend.value; + + if(( host.realtime - cls.connect_time ) < resendTime ) return; - res = NET_StringToAdrNB( cls.servername, &adr ); + res = NET_StringToAdrNB( cls.servername, &adr, false ); - if( !res ) + if( res == NET_EAI_NONAME ) { CL_Disconnect(); return; } - if( res == 2 ) + if( res == NET_EAI_AGAIN ) { cls.connect_time = MAX_HEARTBEAT; return; @@ -1146,38 +1256,43 @@ void CL_CheckForResend( void ) // only retry so many times before failure. if( cls.connect_retry >= CL_CONNECTION_RETRIES ) { - Con_DPrintf( S_ERROR "CL_CheckForResend: couldn't connected\n" ); + Con_DPrintf( S_ERROR "%s: couldn't connect\n", __func__ ); CL_Disconnect(); return; } if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); - if( cls.connect_retry == CL_TEST_RETRIES_NORESPONCE ) + if( cls.connect_retry == CL_TEST_RETRIES ) { // too many fails use default connection method - Con_Printf( "hi-speed connection is failed, use default method\n" ); - Netchan_OutOfBandPrint( NS_CLIENT, adr, "getchallenge\n" ); - Cvar_SetValue( "cl_dlmax", FRAGMENT_MIN_SIZE ); + Con_Printf( "Bandwidth test failed, fallback to default connecting method\n" ); + Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry + 1 ); + CL_SendGetChallenge( adr ); + Cvar_DirectSetValue( &cl_dlmax, FRAGMENT_MIN_SIZE ); cls.connect_time = host.realtime; cls.connect_retry++; return; } cls.serveradr = adr; - cls.max_fragment_size = Q_max( FRAGMENT_MAX_SIZE, cls.max_fragment_size >> Q_min( 1, cls.connect_retry )); + cls.max_fragment_size = CL_GetTestFragmentSize(); cls.connect_time = host.realtime; // for retransmit requests cls.connect_retry++; - Con_Printf( "Connecting to %s... [retry #%i]\n", cls.servername, cls.connect_retry ); - - if( !cls.legacymode && cl_test_bandwidth.value ) - Netchan_OutOfBandPrint( NS_CLIENT, adr, "bandwidth %i %i\n", PROTOCOL_VERSION, cls.max_fragment_size ); + if( bandwidthTest ) + { + Con_Printf( "Connecting to %s... (retry #%i, fragment size %i)\n", cls.servername, cls.connect_retry, cls.max_fragment_size ); + Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_BANDWIDTHTEST" %i %i\n", PROTOCOL_VERSION, cls.max_fragment_size ); + } else - Netchan_OutOfBandPrint( NS_CLIENT, adr, "getchallenge\n" ); + { + Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry ); + CL_SendGetChallenge( adr ); + } } -resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index ) +static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index ) { resource_t *r = &cl.resourcelist[cl.num_resources]; @@ -1194,40 +1309,74 @@ resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qbo return r; } -void CL_CreateResourceList( void ) +static void CL_CreateResourceList( void ) { - char szFileName[MAX_OSPATH]; - byte rgucMD5_hash[16]; + char szFileName[MAX_OSPATH]; + byte rgucMD5_hash[16] = { 0 }; resource_t *pNewResource; int nSize; file_t *fp; HPAK_FlushHostQueue(); cl.num_resources = 0; - - Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.bmp" ); memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash )); + // sanitize cvar value + if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" )) + Cvar_DirectSet( &cl_logoext, "bmp" ); + + Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string ); + if( cls.legacymode == PROTO_GOLDSRC ) + { + CL_ConvertImageToWAD3( szFileName ); + Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName )); + } fp = FS_Open( szFileName, "rb", true ); - if( fp ) + if( !fp ) + return; + + MD5_HashFile( rgucMD5_hash, szFileName, NULL ); + nSize = FS_FileLength( fp ); + + if( nSize != 0 ) { - MD5_HashFile( rgucMD5_hash, szFileName, NULL ); - nSize = FS_FileLength( fp ); + pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 ); - if( nSize != 0 ) + if( pNewResource ) { - pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 ); - - if( pNewResource ) - { - SetBits( pNewResource->ucFlags, RES_CUSTOM ); - memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 ); - HPAK_AddLump( false, CUSTOM_RES_PATH, pNewResource, NULL, fp ); - } + SetBits( pNewResource->ucFlags, RES_CUSTOM ); + memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 ); + HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp ); } - FS_Close( fp ); } + + FS_Close( fp ); +} + +static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto ) +{ + if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" )) + { + *proto = PROTO_CURRENT; + return true; + } + + if( !Q_stricmp( s, "legacy" ) || !Q_strcmp( s, "48" )) + { + *proto = PROTO_LEGACY; + return true; + } + + if( !Q_stricmp( s, "goldsrc" ) || !Q_stricmp( s, "gs" )) + { + *proto = PROTO_GOLDSRC; + return true; + } + + // quake protocol only used for demos + Con_Printf( "Unknown protocol. Supported are: 49 (current), 48 (legacy), gs (goldsrc)\n" ); + return false; } /* @@ -1236,27 +1385,24 @@ CL_Connect_f ================ */ -void CL_Connect_f( void ) +static void CL_Connect_f( void ) { string server; - qboolean legacyconnect = false; + connprotocol_t proto = PROTO_CURRENT; - // hidden hint to connect by using legacy protocol - if( Cmd_Argc() == 3 ) + // hint to connect by using legacy protocol + if( Cmd_Argc() == 3 && !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ) && Cmd_Argc() != 2 ) { - legacyconnect = !Q_strcmp( Cmd_Argv( 2 ), "legacy" ); - } - else if( Cmd_Argc() != 2 ) - { - Con_Printf( S_USAGE "connect \n" ); + Con_Printf( S_USAGE "connect [protocol]\n" ); return; } Q_strncpy( server, Cmd_Argv( 1 ), sizeof( server )); // if running a local server, kill it and reissue - if( SV_Active( )) Host_ShutdownServer(); - NET_Config( true, !CVAR_TO_BOOL( cl_nat )); // allow remote + if( SV_Active( )) + SV_Shutdown( "Server was killed due to connection to remote server\n" ); + NET_Config( true, !cl_nat.value ); // allow remote Con_Printf( "server %s\n", server ); CL_Disconnect(); @@ -1266,7 +1412,7 @@ void CL_Connect_f( void ) Key_SetKeyDest( key_console ); cls.state = ca_connecting; - cls.legacymode = legacyconnect; + cls.legacymode = proto; Q_strncpy( cls.servername, server, sizeof( cls.servername )); cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately cls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size @@ -1283,55 +1429,55 @@ Send the rest of the command line over as an unconnected command. ===================== */ -void CL_Rcon_f( void ) +static void CL_Rcon_f( void ) { - char message[1024]; - netadr_t to; - string command; + char message[1024]; + sizebuf_t msg; + netadr_t to; int i; - if( !COM_CheckString( rcon_client_password->string )) + if( !COM_CheckString( rcon_password.string )) { Con_Printf( "You must set 'rcon_password' before issuing an rcon command.\n" ); return; } - message[0] = (char)255; - message[1] = (char)255; - message[2] = (char)255; - message[3] = (char)255; - message[4] = 0; - NET_Config( true, false ); // allow remote - Q_strcat( message, "rcon " ); - Q_strcat( message, rcon_client_password->string ); - Q_strcat( message, " " ); - - for( i = 1; i < Cmd_Argc(); i++ ) - { - Cmd_Escape( command, Cmd_Argv( i ), sizeof( command )); - Q_strcat( message, command ); - Q_strcat( message, " " ); - } - if( cls.state >= ca_connected ) { to = cls.netchan.remote_address; } else { - if( !COM_CheckString( rcon_address->string )) + if( !COM_CheckString( rcon_address.string )) { Con_Printf( "You must either be connected or set the 'rcon_address' cvar to issue rcon commands\n" ); return; } - NET_StringToAdr( rcon_address->string, &to ); - if( to.port == 0 ) to.port = MSG_BigShort( PORT_SERVER ); + NET_StringToAdr( rcon_address.string, &to ); + if( to.port == 0 ) + to.port = MSG_BigShort( PORT_SERVER ); } - NET_SendPacket( NS_CLIENT, Q_strlen( message ) + 1, message, to ); + MSG_Init( &msg, "RconMessage", message, sizeof( message )); + MSG_WriteLong( &msg, -1 ); + MSG_WriteStringf( &msg, C2S_RCON" %s ", rcon_password.string ); + MSG_SeekToBit( &msg, -8, SEEK_CUR ); + + for( i = 1; i < Cmd_Argc(); i++ ) + { + string command; + + Cmd_Escape( command, Cmd_Argv( i ), sizeof( command )); + MSG_WriteString( &msg, command ); + MSG_SeekToBit( &msg, -8, SEEK_CUR ); + MSG_WriteChar( &msg, ' ' ); + } + MSG_WriteByte( &msg, 0 ); + + NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ), to ); } @@ -1354,7 +1500,7 @@ void CL_ClearState( void ) CL_ClearEffects (); CL_FreeEdicts (); - CL_ClearPhysEnts (); + PM_ClearPhysEnts( clgame.pmove ); NetAPI_CancelAllRequests(); // wipe the entire cl structure @@ -1362,6 +1508,7 @@ void CL_ClearState( void ) MSG_Clear( &cls.netchan.message ); memset( &clgame.fade, 0, sizeof( clgame.fade )); memset( &clgame.shake, 0, sizeof( clgame.shake )); + clgame.mapname[0] = '\0'; Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); cl.maxclients = 1; // allow to drawing player in menu cl.mtime[0] = cl.mtime[1] = 1.0f; // because level starts from 1.0f second @@ -1389,7 +1536,7 @@ CL_SendDisconnectMessage Sends a disconnect message to the server ===================== */ -void CL_SendDisconnectMessage( void ) +static void CL_SendDisconnectMessage( connprotocol_t proto ) { sizebuf_t buf; byte data[32]; @@ -1398,10 +1545,12 @@ void CL_SendDisconnectMessage( void ) MSG_Init( &buf, "LastMessage", data, sizeof( data )); MSG_BeginClientCmd( &buf, clc_stringcmd ); - MSG_WriteString( &buf, "disconnect" ); + if( proto == PROTO_GOLDSRC ) + MSG_WriteString( &buf, "dropclient\n" ); + else MSG_WriteString( &buf, "disconnect" ); - if( !cls.netchan.remote_address.type ) - cls.netchan.remote_address.type = NA_LOOPBACK; + if( NET_NetadrType( &cls.netchan.remote_address ) == NA_UNDEFINED ) + NET_NetadrSetType( &cls.netchan.remote_address, NA_LOOPBACK ); // make sure message will be delivered Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); @@ -1411,20 +1560,50 @@ void CL_SendDisconnectMessage( void ) int CL_GetSplitSize( void ) { - int splitsize; + int splitsize = (int)cl_dlmax.value; - if( Host_IsDedicated() ) - return 0; - - if( !(cls.extensions & NET_EXT_SPLITSIZE) ) + if( !FBitSet( cls.extensions, NET_EXT_SPLITSIZE )) return 1400; - splitsize = cl_dlmax->value; - - if( splitsize < FRAGMENT_MIN_SIZE || splitsize > FRAGMENT_MAX_SIZE ) + if(( splitsize < FRAGMENT_MIN_SIZE ) || ( splitsize > FRAGMENT_MAX_SIZE )) + { Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); + return FRAGMENT_DEFAULT_SIZE; + } - return cl_dlmax->value; + return (int)cl_dlmax.value; +} + +void CL_SetupNetchanForProtocol( connprotocol_t proto ) +{ + int (*pfnBlockSize)( void *, fragsize_t ) = CL_GetFragmentSize; + uint flags = 0; + + switch( proto ) + { + case PROTO_GOLDSRC: + SetBits( flags, NETCHAN_USE_MUNGE | NETCHAN_USE_BZIP2 | NETCHAN_GOLDSRC ); + pfnBlockSize = CL_GetGoldSrcFragmentSize; + break; + case PROTO_LEGACY: + if( FBitSet( Q_atoi( Cmd_Argv( 1 )), NET_LEGACY_EXT_SPLIT )) + { + SetBits( flags, NETCHAN_USE_LEGACY_SPLIT ); + Con_Reportf( "^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d)\n", (int)cl_dlmax.value, 65536 ); + } + break; + default: + if( !Host_IsLocalClient( )) + SetBits( flags, NETCHAN_USE_LZSS ); + + cls.extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" )); + + if( FBitSet( cls.extensions, NET_EXT_SPLITSIZE )) + Con_Reportf( "^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\n", (int)cl_dlmax.value ); + break; + } + + Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, pfnBlockSize, flags ); } /* @@ -1434,33 +1613,11 @@ CL_Reconnect build a request to reconnect client ===================== */ -void CL_Reconnect( qboolean setup_netchan ) +static void CL_Reconnect( qboolean setup_netchan ) { if( setup_netchan ) { - Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); - - if( cls.legacymode ) - { - unsigned int extensions = Q_atoi( Cmd_Argv( 1 ) ); - - if( extensions & NET_LEGACY_EXT_SPLIT ) - { - // only enable incoming split for legacy mode - cls.netchan.split = true; - Con_Reportf( "^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d)\n", (int)cl_dlmax->value, 65536 ); - } - } - else - { - cls.extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" )); - - if( cls.extensions & NET_EXT_SPLITSIZE ) - { - Con_Reportf( "^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\n", (int)cl_dlmax->value ); - } - } - + CL_SetupNetchanForProtocol( cls.legacymode ); } else { @@ -1495,8 +1652,6 @@ This is also called on Host_Error, so it shouldn't cause any errors */ void CL_Disconnect( void ) { - cls.legacymode = false; - if( cls.state == ca_disconnected ) return; @@ -1507,7 +1662,7 @@ void CL_Disconnect( void ) CL_Stop_f(); // send a disconnect message to the server - CL_SendDisconnectMessage(); + CL_SendDisconnectMessage( cls.legacymode ); CL_ClearState (); S_StopBackgroundTrack (); @@ -1523,9 +1678,10 @@ void CL_Disconnect( void ) cls.set_lastdemo = false; cls.connect_retry = 0; cls.signon = 0; + cls.legacymode = PROTO_CURRENT; // back to menu in non-developer mode - if( host_developer.value || CL_IsInMenu( )) + if( host_developer.value || cls.key_dest == key_menu ) return; UI_SetActiveMenu( true ); @@ -1550,7 +1706,7 @@ void CL_Crashed( void ) CL_Stop_f(); // stop any demos // send a disconnect message to the server - CL_SendDisconnectMessage(); + CL_SendDisconnectMessage( cls.legacymode ); Host_WriteOpenGLConfig(); Host_WriteConfig(); // write config @@ -1561,49 +1717,139 @@ void CL_Crashed( void ) CL_LocalServers_f ================= */ -void CL_LocalServers_f( void ) +static void CL_LocalServers_f( void ) { - netadr_t adr; + netadr_t adr = { 0 }; Con_Printf( "Scanning for servers on the local network area...\n" ); NET_Config( true, true ); // allow remote // send a broadcast packet - adr.type = NA_BROADCAST; + NET_NetadrSetType( &adr, NA_BROADCAST ); adr.port = MSG_BigShort( PORT_SERVER ); + Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); - Netchan_OutOfBandPrint( NS_CLIENT, adr, "info %i", PROTOCOL_VERSION ); + NET_NetadrSetType( &adr, NA_MULTICAST_IP6 ); + Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); } -#define MS_SCAN_REQUEST "1\xFF" "0.0.0.0:0\0" +/* +================= +CL_BuildMasterServerScanRequest +================= +*/ +static size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t *key, qboolean nat, const char *filter ) +{ + size_t remaining; + char *info, temp[32]; + + if( unlikely( size < sizeof( MS_SCAN_REQUEST ))) + return 0; + + Q_strncpy( buf, MS_SCAN_REQUEST, size ); + + info = buf + sizeof( MS_SCAN_REQUEST ) - 1; + remaining = size - sizeof( MS_SCAN_REQUEST ); + + Q_strncpy( info, filter, remaining ); + + *key = COM_RandomLong( 0, 0x7FFFFFFF ); + +#ifndef XASH_ALL_SERVERS + Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); +#endif + // let master know about client version + Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); + Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining ); + Info_SetValueForKey( info, "commit", g_buildcommit, remaining ); + Info_SetValueForKey( info, "branch", g_buildbranch, remaining ); + Info_SetValueForKey( info, "os", Q_buildos(), remaining ); + Info_SetValueForKey( info, "arch", Q_buildarch(), remaining ); + + Q_snprintf( temp, sizeof( temp ), "%d", Q_buildnum() ); + Info_SetValueForKey( info, "buildnum", temp, remaining ); + + Q_snprintf( temp, sizeof( temp ), "%x", *key ); + Info_SetValueForKey( info, "key", temp, remaining ); + + return sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ); +} + +/* +================= +CL_SendMasterServerScanRequest +================= +*/ +static void CL_SendMasterServerScanRequest( void ) +{ + cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, + cls.internetservers_query_len, cls.internetservers_query ); + cls.internetservers_pending = true; +} /* ================= CL_InternetServers_f ================= */ -void CL_InternetServers_f( void ) +static void CL_InternetServers_f( void ) { - char fullquery[512] = MS_SCAN_REQUEST; - char *info = fullquery + sizeof( MS_SCAN_REQUEST ) - 1; - const size_t remaining = sizeof( fullquery ) - sizeof( MS_SCAN_REQUEST ); + qboolean nat = cl_nat.value != 0.0f; + uint32_t key; + + if( Cmd_Argc( ) > 2 || ( Cmd_Argc( ) == 2 && !Info_IsValid( Cmd_Argv( 1 )))) + { + Con_Printf( S_USAGE "internetservers [filter]\n" ); + return; + } + + cls.internetservers_query_len = CL_BuildMasterServerScanRequest( + cls.internetservers_query, sizeof( cls.internetservers_query ), + &cls.internetservers_key, nat, Cmd_Argv( 1 )); + + Con_Printf( "Scanning for servers on the internet area...\n" ); NET_Config( true, true ); // allow remote - Con_Printf( "Scanning for servers on the internet area...\n" ); - Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); - Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version - Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); + CL_SendMasterServerScanRequest(); +} - cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); - cls.internetservers_pending = true; +static void CL_QueryServer_f( void ) +{ + netadr_t adr; + connprotocol_t proto; - if( !cls.internetservers_wait ) + if( Cmd_Argc( ) != 3 ) { - // now we clearing the vgui request - if( clgame.master_request != NULL ) - memset( clgame.master_request, 0, sizeof( net_request_t )); - clgame.request_type = NET_REQUEST_GAMEUI; + Con_Printf( S_USAGE "ui_queryserver \n" ); + return; + } + + NET_Config( true, false ); + + if( !NET_StringToAdr( Cmd_Argv( 1 ), &adr )) + { + Con_Printf( S_ERROR "%s: can't parse %s", __func__, Cmd_Argv( 1 )); + return; + } + + if( adr.port == 0 ) + adr.port = PORT_SERVER; + + if( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto )) + return; + + switch( proto ) + { + case PROTO_GOLDSRC: + Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator! + break; + case PROTO_LEGACY: + Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION ); + break; + case PROTO_CURRENT: + Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); + break; } } @@ -1614,7 +1860,7 @@ CL_Reconnect_f The server is changing levels ================= */ -void CL_Reconnect_f( void ) +static void CL_Reconnect_f( void ) { if( cls.state == ca_disconnected ) return; @@ -1629,6 +1875,8 @@ void CL_Reconnect_f( void ) if( COM_CheckString( cls.servername )) { + connprotocol_t proto = cls.legacymode; + if( cls.state >= ca_connected ) CL_Disconnect(); @@ -1636,6 +1884,7 @@ void CL_Reconnect_f( void ) cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connecting; cls.signon = 0; + cls.legacymode = proto; // don't change protocol Con_Printf( "reconnecting...\n" ); } @@ -1648,7 +1897,7 @@ CL_FixupColorStringsForInfoString all the keys and values must be ends with ^7 ================= */ -void CL_FixupColorStringsForInfoString( const char *in, char *out ) +static void CL_FixupColorStringsForInfoString( const char *in, char *out, size_t len ) { qboolean hasPrefix = false; qboolean endOfKeyVal = false; @@ -1661,7 +1910,7 @@ void CL_FixupColorStringsForInfoString( const char *in, char *out ) count++; } - while( *in && count < MAX_INFO_STRING ) + while( *in && count < len ) { if( IsColorString( in )) color = ColorIndex( *(in+1)); @@ -1712,17 +1961,17 @@ CL_ParseStatusMessage Handle a reply from a info ================= */ -void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) +static void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { - static char infostring[MAX_INFO_STRING+8]; + static char infostring[512+8]; char *s = MSG_ReadString( msg ); int i; - const char *magic = ": wrong version\n"; + const char *magic = ": wrong version\n", *p; size_t len = Q_strlen( s ), magiclen = Q_strlen( magic ); if( len >= magiclen && !Q_strcmp( s + len - magiclen, magic )) { - Netchan_OutOfBandPrint( NS_CLIENT, from, "info %i", PROTOCOL_LEGACY_VERSION ); + Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION ); return; } @@ -1732,7 +1981,7 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) return; } - CL_FixupColorStringsForInfoString( s, infostring ); + CL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring )); if( !COM_CheckString( Info_ValueForKey( infostring, "gamedir" ))) { @@ -1740,9 +1989,18 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) return; // unsupported proto } - if( !COM_CheckString( Info_ValueForKey( infostring, "p" ))) + Info_RemoveKey( infostring, "gs" ); // don't let servers pretend they're something else + + p = Info_ValueForKey( infostring, "p" ); + if( !COM_CheckStringEmpty( p )) { - Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); + Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring )); + Info_SetValueForKey( infostring, "p", "48", sizeof( infostring )); + Con_Printf( "^3Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); + } + else if( !Q_strcmp( p, "48" )) + { + Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring )); Con_Printf( "^3Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); } else @@ -1754,6 +2012,85 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) UI_AddServerToList( from, infostring ); } +static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg ) +{ + static char s[512+8]; + int p, numcl, maxcl, password, remaining; + string host, map, gamedir, version; + connprotocol_t proto; + char *replace; + + // set to beginning but skip header + MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET ); + + p = MSG_ReadByte( msg ); + Q_strncpy( host, MSG_ReadString( msg ), sizeof( host )); + Q_strncpy( map, MSG_ReadString( msg ), sizeof( map )); + Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir )); + MSG_ReadString( msg ); // game description + MSG_ReadShort( msg ); // app id + numcl = MSG_ReadByte( msg ); + maxcl = MSG_ReadByte( msg ); + MSG_ReadByte( msg ); // bots count + MSG_ReadByte( msg ); // dedicated + MSG_ReadByte( msg ); // operating system + password = MSG_ReadByte( msg ); + Q_strncpy( version, MSG_ReadString( msg ), sizeof( version )); + + if( MSG_CheckOverflow( msg )) + { + Con_Printf( "%s: malfored info packet from %s\n", __func__, NET_AdrToString( from )); + return; + } + + // time to figure out protocol + if( p == PROTOCOL_VERSION ) + proto = PROTO_CURRENT; + else if( p == PROTOCOL_LEGACY_VERSION ) + { + if( Q_stristr( version, "Stdio" )) + proto = PROTO_GOLDSRC; + else + proto = PROTO_LEGACY; + } + else + { + Con_Printf( "%s: unsupported protocol %d from %s\n", __func__, p, NET_AdrToString( from )); + return; + } + + // now construct infostring for mainui + Info_SetValueForKeyf( s, "p", sizeof( s ), "%i", proto == PROTO_CURRENT ? PROTOCOL_VERSION : PROTOCOL_LEGACY_VERSION ); + Info_SetValueForKey( s, "gs", proto == PROTO_GOLDSRC ? "1" : "0", sizeof( s )); + Info_SetValueForKey( s, "map", map, sizeof( s )); + Info_SetValueForKey( s, "dm", "0", sizeof( s )); // obsolete keys + Info_SetValueForKey( s, "team", "0", sizeof( s )); + Info_SetValueForKey( s, "coop", "0", sizeof( s )); + Info_SetValueForKeyf( s, "numcl", sizeof( s ), "%i", numcl ); + Info_SetValueForKeyf( s, "maxcl", sizeof( s ), "%i", maxcl ); + Info_SetValueForKey( s, "gamedir", gamedir, sizeof( s )); + Info_SetValueForKey( s, "password", password ? "1" : "0", sizeof( s )); + + // write host last so we can try to cut off too long hostnames + // TODO: value size limit for infostrings + remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1; + if( remaining < 0 ) + { + // should never happen? + Con_Printf( S_ERROR "%s: infostring overflow!\n", __func__ ); + return; + } + + while(( replace = Q_strpbrk( host, "\\\"" ))) + { + *replace = ' '; // find a better replacement? + } + + Info_SetValueForKey( s, "host", host, sizeof( s )); + + UI_AddServerToList( from, s ); +} + /* ================= CL_ParseNETInfoMessage @@ -1761,52 +2098,87 @@ CL_ParseNETInfoMessage Handle a reply from a netinfo ================= */ -void CL_ParseNETInfoMessage( netadr_t from, sizebuf_t *msg, const char *s ) +static void CL_ParseNETInfoMessage( netadr_t from, const char *s ) { - net_request_t *nr; - static char infostring[MAX_INFO_STRING+8]; + net_request_t *nr = NULL; + static char infostring[MAX_PRINT_MSG]; int i, context, type; int errorBits = 0; const char *val; + size_t slen; context = Q_atoi( Cmd_Argv( 1 )); type = Q_atoi( Cmd_Argv( 2 )); - while( *s != '\\' ) s++; // fetching infostring - // check for errors - val = Info_ValueForKey( s, "neterror" ); - - if( !Q_stricmp( val, "protocol" )) - SetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED ); - else if( !Q_stricmp( val, "undefined" )) - SetBits( errorBits, NET_ERROR_UNDEFINED ); - - CL_FixupColorStringsForInfoString( s, infostring ); - - // find a request with specified context + // find request with specified context and type for( i = 0; i < MAX_REQUESTS; i++ ) { - nr = &clgame.net_requests[i]; - - if( nr->resp.context == context && nr->resp.type == type ) + if( clgame.net_requests[i].resp.context == context && clgame.net_requests[i].resp.type == type ) { - // setup the answer - nr->resp.response = infostring; - nr->resp.remote_address = from; - nr->resp.error = NET_SUCCESS; - nr->resp.ping = host.realtime - nr->timesend; - - if( nr->timeout <= host.realtime ) - SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); - SetBits( nr->resp.error, errorBits ); // misc error bits - - nr->pfnFunc( &nr->resp ); - - if( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE )) - memset( nr, 0, sizeof( *nr )); // done - return; + nr = &clgame.net_requests[i]; + break; } } + + // not found, ignore + if( nr == NULL ) + return; + + // find the payload + s = Q_strchr( s, ' ' ); // skip netinfo + if( !s ) + return; + + s = Q_strchr( s + 1, ' ' ); // skip challenge + if( !s ) + return; + + s = Q_strchr( s + 1, ' ' ); // skip type + if( s ) + s++; // skip final whitespace + else if( type != NETAPI_REQUEST_PING ) // ping have no payload, and that's ok + return; + + if( s ) + { + if( s[0] == '\\' ) + { + // check for errors + val = Info_ValueForKey( s, "neterror" ); + + if( !Q_stricmp( val, "protocol" )) + SetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED ); + else if( !Q_stricmp( val, "undefined" )) + SetBits( errorBits, NET_ERROR_UNDEFINED ); + else if( !Q_stricmp( val, "forbidden" )) + SetBits( errorBits, NET_ERROR_FORBIDDEN ); + + CL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring )); + } + else + { + Q_strncpy( infostring, s, sizeof( infostring )); + } + } + else + { + infostring[0] = 0; + } + + // setup the answer + nr->resp.response = infostring; + nr->resp.remote_address = from; + nr->resp.error = NET_SUCCESS; + nr->resp.ping = host.realtime - nr->timesend; + + if( nr->timeout <= host.realtime ) + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + SetBits( nr->resp.error, errorBits ); // misc error bits + + nr->pfnFunc( &nr->resp ); + + if( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE )) + memset( nr, 0, sizeof( *nr )); // done } /* @@ -1816,7 +2188,7 @@ CL_ProcessNetRequests check for timeouts ================= */ -void CL_ProcessNetRequests( void ) +static void CL_ProcessNetRequests( void ) { net_request_t *nr; int i; @@ -1891,6 +2263,246 @@ static qboolean CL_IsFromConnectingServer( netadr_t from ) NET_CompareAdr( cls.serveradr, from ); } +static void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg ) +{ + byte recv_buf[NET_MAX_FRAGMENT]; + dword crcValue; + int realsize; + dword crcValue2 = 0; + + // this message only used during connection + // it doesn't make sense after client_connect + if( cls.state != ca_connecting ) + return; + + if( !CL_IsFromConnectingServer( from )) + return; + + crcValue = MSG_ReadLong( msg ); + realsize = MSG_GetMaxBytes( msg ) - MSG_GetNumBytesRead( msg ); + + if( cls.max_fragment_size != MSG_GetMaxBytes( msg )) + { + if( cls.connect_retry >= CL_TEST_RETRIES ) + { + // too many fails use default connection method + Con_Printf( "hi-speed connection is failed, use default method\n" ); + CL_SendGetChallenge( from ); + Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); + cls.connect_time = host.realtime; + return; + } + + // if we waiting more than cl_timeout or packet was trashed + cls.connect_time = MAX_HEARTBEAT; + return; // just wait for a next responce + } + + // reading test buffer + MSG_ReadBytes( msg, recv_buf, realsize ); + + // procssing the CRC + CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize ); + + if( crcValue == crcValue2 ) + { + // packet was sucessfully delivered, adjust the fragment size and get challenge + + Con_DPrintf( "CRC %x is matched, get challenge, fragment size %d\n", crcValue, cls.max_fragment_size ); + CL_SendGetChallenge( from ); + Cvar_SetValue( "cl_dlmax", cls.max_fragment_size ); + cls.connect_time = host.realtime; + } + else + { + if( cls.connect_retry >= CL_TEST_RETRIES ) + { + // too many fails use default connection method + Con_Printf( "hi-speed connection is failed, use default method\n" ); + CL_SendGetChallenge( from ); + Cvar_SetValue( "cl_dlmax", FRAGMENT_MIN_SIZE ); + cls.connect_time = host.realtime; + return; + } + + Msg( "got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d\n", crcValue2, crcValue, cls.max_fragment_size >> 1 ); + + // trying the next size of packet + cls.connect_time = MAX_HEARTBEAT; + } +} + +static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from ) +{ + if( !CL_IsFromConnectingServer( from )) + return; + + if( cls.state == ca_connected ) + { + Con_DPrintf( S_ERROR "dup connect received. ignored\n"); + return; + } + + if( proto == PROTO_GOLDSRC ) + { + if( Q_strcmp( c, S2C_GOLDSRC_CONNECTION )) + { + Con_DPrintf( S_ERROR "GoldSrc client connect expected but wasn't received, ignored\n"); + return; + } + + if( Cmd_Argc() > 4 ) + cls.build_num = Q_atoi( Cmd_Argv( 4 )); + } + else if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION )) + { + Con_DPrintf( S_ERROR "GoldSrc client connect received but wasn't expected, ignored\n"); + return; + } + + CL_Reconnect( true ); + UI_SetActiveMenu( cl.background ); +} + +static void CL_Print( const char *c, const char *args, netadr_t from, sizebuf_t *msg ) +{ + const char *s; + + s = c[0] == A2C_GOLDSRC_PRINT ? args + 1 : MSG_ReadString( msg ); + + if( !COM_CheckStringEmpty( s )) + return; + + Con_Printf( "Remote message from %s:\n", NET_AdrToString( from )); + Con_Printf( "%s%c", s, s[Q_strlen( s ) - 1] != '\n' ? '\n' : '\0' ); +} + +static void CL_Challenge( const char *c, netadr_t from ) +{ + if( cls.state != ca_connecting ) + return; + + if( !CL_IsFromConnectingServer( from )) + return; + + // try to autodetect protocol by challenge response + if( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) + cls.legacymode = PROTO_GOLDSRC; + + // challenge from the server we are connecting to + CL_SendConnectPacket( cls.legacymode, Q_atoi( Cmd_Argv( 1 ))); +} + +static void CL_ErrorMsg( const char *c, const char *args, netadr_t from, sizebuf_t *msg ) +{ + char formatted_msg[MAX_VA_STRING]; + + if( !CL_IsFromConnectingServer( from )) + return; + + if( msg != NULL && !Q_strcmp( c, S2C_ERRORMSG )) + { + const char *s = MSG_ReadString( msg ); + Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", s ); + } + else if( c[0] == S2C_GOLDSRC_REJECT ) + { + Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 1 ); + } + else if( c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD ) + { + if( !Q_strnicmp( &c[1], "BADPASSWORD", 11 )) + Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 12 ); + else + Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 1 ); + } + + // in case we're in console or it's classic mainui which doesn't support messageboxes + if( !UI_IsVisible() || !UI_ShowMessageBox( formatted_msg )) + Msg( "%s\n", formatted_msg ); + + // don't disconnect, errormsg is a FWGS extension and + // always followed by disconnect message +} + +static void CL_Reject( const char *c, const char *args, netadr_t from ) +{ + // this message only used during connection + // it doesn't make sense after client_connect + if( cls.state != ca_connecting ) + return; + + if( !CL_IsFromConnectingServer( from )) + return; + + CL_ErrorMsg( c, args, from, NULL ); + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + CL_Disconnect_f(); +} + +static void CL_ServerList( netadr_t from, sizebuf_t *msg ) +{ + if( !NET_IsMasterAdr( from )) + { + Con_Printf( S_WARN "unexpected server list packet from %s\n", NET_AdrToString( from )); + return; + } + + // check the extra header + if( MSG_ReadByte( msg ) == 0x7f ) + { + uint32_t key = MSG_ReadDword( msg ); + + if( cls.internetservers_key != key ) + { + Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from )); + return; + } + + MSG_ReadByte( msg ); // reserved byte + } + else + { + Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from )); + return; + } + + // serverlist got from masterserver + while( MSG_GetNumBitsLeft( msg ) > 8 ) + { + uint8_t addr[16]; + netadr_t servadr = { 0 }; + + if( NET_NetadrType( &from ) == NA_IP6 ) // IPv6 master server only sends IPv6 addresses + { + MSG_ReadBytes( msg, addr, sizeof( addr )); + NET_IP6BytesToNetadr( &servadr, addr ); + NET_NetadrSetType( &servadr, NA_IP6 ); + } + else + { + MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP + NET_NetadrSetType( &servadr, NA_IP ); + } + servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port + + // list is ends here + if( !servadr.port ) + break; + + NET_Config( true, false ); // allow remote + Netchan_OutOfBandPrint( NS_CLIENT, servadr, A2A_INFO" %i", PROTOCOL_VERSION ); + } + + if( cls.internetservers_pending ) + { + UI_ResetPing(); + cls.internetservers_pending = false; + } +} + /* ================= CL_ConnectionlessPacket @@ -1898,14 +2510,10 @@ CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ -void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) +static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { - char *args; - const char *c; - char buf[MAX_SYSPATH]; - int len = sizeof( buf ); - int dataoffset = 0; - netadr_t servadr; + char *args; + const char *c; MSG_Clear( msg ); MSG_ReadLong( msg ); // skip the -1 @@ -1915,280 +2523,77 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); - Con_Reportf( "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); + Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c ); // server connection - if( !Q_strcmp( c, "client_connect" )) + if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION )) { - if( !CL_IsFromConnectingServer( from )) - return; + CL_ClientConnect( cls.legacymode, c, from ); + } + else if( !Q_strcmp( c, A2A_INFO )) + { + CL_ParseStatusMessage( from, msg ); // server responding to a status broadcast + } + else if( c[0] == S2A_GOLDSRC_INFO ) + { + CL_ParseGoldSrcStatusMessage( from, msg ); + } + else if( !Q_strcmp( c, A2A_NETINFO )) + { + CL_ParseNETInfoMessage( from, args ); // server responding to a status broadcast + } + else if( c[0] == A2C_GOLDSRC_PRINT || !Q_strcmp( c, A2C_PRINT )) + { + CL_Print( c, args, from, msg ); + } + else if( !Q_strcmp( c, S2C_BANDWIDTHTEST )) + { + CL_HandleTestPacket( from, msg ); + } + else if( !Q_strcmp( c, A2A_PING )) + { + Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_ACK ); + } + else if( !Q_strcmp( c, A2A_GOLDSRC_PING )) + { + Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_GOLDSRC_ACK ); + } + else if( !Q_strcmp( c, A2A_ACK ) || !Q_strcmp( c, A2A_GOLDSRC_ACK )) + { + // no-op + } + else if( !Q_strcmp( c, S2C_CHALLENGE ) || !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) + { + CL_Challenge( c, from ); + } + else if( !Q_strcmp( c, S2C_REJECT ) || c[0] == S2C_GOLDSRC_REJECT || c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD ) + { + CL_Reject( c, args, from ); + } + else if( !Q_strcmp( c, S2C_ERRORMSG )) + { + CL_ErrorMsg( c, args, from, msg ); + } + else if( !Q_strcmp( c, M2A_SERVERSLIST )) + { + CL_ServerList( from, msg ); + } + else + { + char buf[MAX_SYSPATH]; + int len = sizeof( buf ); - if( cls.state == ca_connected ) + if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { - Con_DPrintf( S_ERROR "dup connect received. ignored\n"); - return; - } - - CL_Reconnect( true ); - UI_SetActiveMenu( cl.background ); - } - else if( !Q_strcmp( c, "info" )) - { - // server responding to a status broadcast - CL_ParseStatusMessage( from, msg ); - } - else if( !Q_strcmp( c, "netinfo" )) - { - // server responding to a status broadcast - CL_ParseNETInfoMessage( from, msg, args ); - } - else if( !Q_strcmp( c, "cmd" )) - { - // remote command from gui front end - if( !NET_IsLocalAddress( from )) - { - Con_Printf( "Command packet from remote host. Ignored.\n" ); - return; - } - -#if XASH_SDL == 2 - SDL_ShowWindow( host.hWnd ); -#endif - args = MSG_ReadString( msg ); - Cbuf_AddText( args ); - Cbuf_AddText( "\n" ); - } - else if( !Q_strcmp( c, "print" )) - { - // print command from somewhere - Con_Printf( "%s", MSG_ReadString( msg )); - } - else if( !Q_strcmp( c, "testpacket" )) - { - byte recv_buf[NET_MAX_FRAGMENT]; - dword crcValue; - int realsize; - dword crcValue2 = 0; - - if( !CL_IsFromConnectingServer( from )) - return; - - crcValue = MSG_ReadLong( msg ); - realsize = MSG_GetMaxBytes( msg ) - MSG_GetNumBytesRead( msg ); - - if( cls.max_fragment_size != MSG_GetMaxBytes( msg )) - { - if( cls.connect_retry >= CL_TEST_RETRIES ) - { - // too many fails use default connection method - Con_Printf( "hi-speed connection is failed, use default method\n" ); - Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); - Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); - cls.connect_time = host.realtime; - return; - } - - // if we waiting more than cl_timeout or packet was trashed - cls.connect_time = MAX_HEARTBEAT; - return; // just wait for a next responce - } - - // reading test buffer - MSG_ReadBytes( msg, recv_buf, realsize ); - - // procssing the CRC - CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize ); - - if( crcValue == crcValue2 ) - { - // packet was sucessfully delivered, adjust the fragment size and get challenge - - Con_DPrintf( "CRC %x is matched, get challenge, fragment size %d\n", crcValue, cls.max_fragment_size ); - Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); - Cvar_SetValue( "cl_dlmax", cls.max_fragment_size ); - cls.connect_time = host.realtime; + // user out of band message (must be handled in SV_ConnectionlessPacket) + if( len > 0 ) + Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf ); } else { - if( cls.connect_retry >= CL_TEST_RETRIES ) - { - // too many fails use default connection method - Con_Printf( "hi-speed connection is failed, use default method\n" ); - Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); - Cvar_SetValue( "cl_dlmax", FRAGMENT_MIN_SIZE ); - cls.connect_time = host.realtime; - return; - } - - Msg( "got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d\n", crcValue2, crcValue, cls.max_fragment_size >> 1 ); - - // trying the next size of packet - cls.connect_time = MAX_HEARTBEAT; + Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); } } - else if( !Q_strcmp( c, "ping" )) - { - // ping from somewhere - Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); - } - else if( !Q_strcmp( c, "challenge" )) - { - if( !CL_IsFromConnectingServer( from )) - return; - - // challenge from the server we are connecting to - cls.challenge = Q_atoi( Cmd_Argv( 1 )); - CL_SendConnectPacket(); - return; - } - else if( !Q_strcmp( c, "echo" )) - { - if( !CL_IsFromConnectingServer( from )) - return; - - // echo request from server - Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); - } - else if( !Q_strcmp( c, "disconnect" )) - { - if( !CL_IsFromConnectingServer( from )) - return; - - // a disconnect message from the server, which will happen if the server - // dropped the connection but it is still getting packets from us - CL_Disconnect_f(); - - if( NET_CompareAdr( from, cls.legacyserver )) - { - Cbuf_AddText( va( "connect %s legacy\n", NET_AdrToString( from ))); - memset( &cls.legacyserver, 0, sizeof( cls.legacyserver )); - } - } - else if( !Q_strcmp( c, "errormsg" )) - { - if( !CL_IsFromConnectingServer( from )) - return; - - args = MSG_ReadString( msg ); - - if( !Q_strcmp( args, "Server uses protocol version 48.\n" )) - { - cls.legacyserver = from; - } - else - { - if( UI_IsVisible() ) - UI_ShowMessageBox( va("^3Server message^7\n%s", args ) ); - Msg( "%s", args ); - } - } - else if( !Q_strcmp( c, "updatemsg" )) - { - // got an update message from master server - // show update dialog from menu - netadr_t adr; - qboolean preferStore = true; - - if( !Q_strcmp( Cmd_Argv( 1 ), "nostore" ) ) - preferStore = false; - - // trust only hardcoded master server - if( NET_StringToAdr( MASTERSERVER_ADR, &adr ) ) - { - if( NET_CompareAdr( from, adr )) - { - UI_ShowUpdateDialog( preferStore ); - } - } - else - { - // in case we don't have master anymore - UI_ShowUpdateDialog( preferStore ); - } - } - else if( !Q_strcmp( c, "f" )) - { - if( !NET_IsMasterAdr( from )) - { - Con_Printf( S_WARN "unexpected server list packet from %s\n", NET_AdrToString( from )); - return; - } - - // serverlist got from masterserver - while( MSG_GetNumBitsLeft( msg ) > 8 ) - { - MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP - servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port - servadr.type = NA_IP; - - // list is ends here - if( !servadr.port ) - { - if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) - { - net_request_t *nr = clgame.master_request; - net_adrlist_t *list, **prev; - - // setup the answer - nr->resp.remote_address = from; - nr->resp.error = NET_SUCCESS; - nr->resp.ping = host.realtime - nr->timesend; - - if( nr->timeout <= host.realtime ) - SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); - - Con_Printf( "serverlist call: %s\n", NET_AdrToString( from )); - nr->pfnFunc( &nr->resp ); - - // throw the list, now it will be stored in user area - prev = (net_adrlist_t**)&nr->resp.response; - - while( 1 ) - { - list = *prev; - if( !list ) break; - - // throw out any variables the game created - *prev = list->next; - Mem_Free( list ); - } - memset( nr, 0, sizeof( *nr )); // done - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; - } - break; - } - - if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) - { - net_request_t *nr = clgame.master_request; - net_adrlist_t *list; - - // adding addresses into list - list = Z_Malloc( sizeof( *list )); - list->remote_address = servadr; - list->next = nr->resp.response; - nr->resp.response = list; - } - else if( clgame.request_type == NET_REQUEST_GAMEUI ) - { - NET_Config( true, false ); // allow remote - Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); - } - } - - if( cls.internetservers_pending ) - { - UI_ResetPing(); - cls.internetservers_pending = false; - } - } - else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) - { - // user out of band message (must be handled in CL_ConnectionlessPacket) - if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf ); - } - else Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); } /* @@ -2198,35 +2603,72 @@ CL_GetMessage Handles recording and playback of demos, on top of NET_ code ==================== */ -int CL_GetMessage( byte *data, size_t *length ) +static qboolean CL_GetMessage( byte *data, size_t *length ) { if( cls.demoplayback ) - { - if( CL_DemoReadMessage( data, length )) - return true; - return false; - } + return CL_DemoReadMessage( data, length ); - if( NET_GetPacket( NS_CLIENT, &net_from, data, length )) - return true; - return false; + return NET_GetPacket( NS_CLIENT, &net_from, data, length ); } +static void CL_ParseNetMessage( sizebuf_t *msg, void (*parsefn)( sizebuf_t * )) +{ + cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame + CL_Parse_Debug( true ); // begin parsing + + parsefn( msg ); + + cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; + CL_Parse_Debug( false ); // done + + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + if( !cls.demoplayback ) + { + if( cls.state != ca_active ) + CL_WriteDemoMessage( true, cls.starting_count, msg ); + + if( cls.demorecording && !cls.demowaiting ) + CL_WriteDemoMessage( false, cls.starting_count, msg ); + } +} + + /* ================= CL_ReadNetMessage ================= */ -void CL_ReadNetMessage( void ) +static void CL_ReadNetMessage( void ) { size_t curSize; + void (*parsefn)( sizebuf_t *msg ); + + switch( cls.legacymode ) + { + case PROTO_LEGACY: + parsefn = CL_ParseLegacyServerMessage; + break; + case PROTO_QUAKE: + parsefn = CL_ParseQuakeMessage; + break; + case PROTO_GOLDSRC: + parsefn = CL_ParseGoldSrcServerMessage; + break; + default: + parsefn = CL_ParseServerMessage; + break; + } while( CL_GetMessage( net_message_buffer, &curSize )) { - if( cls.legacymode && *((int *)&net_message_buffer) == 0xFFFFFFFE ) + const int split_header = LittleLong( 0xFFFFFFFE ); + if( cls.legacymode == PROTO_LEGACY && !memcmp( &split_header, net_message_buffer, sizeof( split_header ))) + { // Will rewrite existing packet by merged if( !NetSplit_GetLong( &cls.netchan.netsplit, &net_from, net_message_buffer, &curSize ) ) continue; + } MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); @@ -2240,28 +2682,36 @@ void CL_ReadNetMessage( void ) // can't be a valid sequenced packet if( cls.state < ca_connected ) continue; - if( !cls.demoplayback && MSG_GetMaxBytes( &net_message ) < 8 ) + if( !cls.demoplayback ) { - Con_Printf( S_WARN "CL_ReadPackets: %s:runt packet\n", NET_AdrToString( net_from )); - continue; + if( MSG_GetMaxBytes( &net_message ) < 8 ) + { + Con_Printf( S_WARN "%s: %s:runt packet\n", __func__, NET_AdrToString( net_from )); + continue; + } + + // packet from server + if( !NET_CompareAdr( net_from, cls.netchan.remote_address )) + { + Con_DPrintf( S_ERROR "%s: %s:sequenced packet without connection\n", __func__, NET_AdrToString( net_from )); + continue; + } + + if( !Netchan_Process( &cls.netchan, &net_message )) + continue; // wasn't accepted for some reason } - // packet from server - if( !cls.demoplayback && !NET_CompareAdr( net_from, cls.netchan.remote_address )) + if( cls.state == ca_active ) { - Con_DPrintf( S_ERROR "CL_ReadPackets: %s:sequenced packet without connection\n", NET_AdrToString( net_from )); - continue; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; + } + else + { + CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); } - if( !cls.demoplayback && !Netchan_Process( &cls.netchan, &net_message )) - continue; // wasn't accepted for some reason - - // run special handler for quake demos - if( cls.demoplayback == DEMO_QUAKE1 ) - CL_ParseQuakeMessage( &net_message, true ); - else if( cls.legacymode ) CL_ParseLegacyServerMessage( &net_message, true ); - else CL_ParseServerMessage( &net_message, true ); - cl.send_reply = true; + CL_ParseNetMessage( &net_message, parsefn ); } // build list of all solid entities per next frame (exclude clients) @@ -2274,7 +2724,7 @@ void CL_ReadNetMessage( void ) if( Netchan_CopyNormalFragments( &cls.netchan, &net_message, &curSize )) { MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); - CL_ParseServerMessage( &net_message, false ); + CL_ParseNetMessage( &net_message, parsefn ); } if( Netchan_CopyFileFragments( &cls.netchan, &net_message )) @@ -2298,12 +2748,12 @@ Updates the local time and reads/handles messages on client net connection. ================= */ -void CL_ReadPackets( void ) +static void CL_ReadPackets( void ) { // decide the simulation time cl.oldtime = cl.time; - if( cls.demoplayback != DEMO_XASH3D && !cl.paused ) + if( !cl.paused ) cl.time += host.frametime; // demo time @@ -2345,7 +2795,7 @@ void CL_ReadPackets( void ) // check timeout if( cls.state >= ca_connected && cls.state != ca_cinematic && !cls.demoplayback ) { - if( host.realtime - cls.netchan.last_received > cl_timeout->value ) + if( host.realtime - cls.netchan.last_received > cl_timeout.value ) { Con_Printf( "\nServer connection timed out.\n" ); CL_Disconnect(); @@ -2362,7 +2812,7 @@ CL_CleanFileName Replace the displayed name for some resources ==================== */ -const char *CL_CleanFileName( const char *filename ) +static const char *CL_CleanFileName( const char *filename ) { if( COM_CheckString( filename ) && filename[0] == '!' ) return "customization"; @@ -2378,7 +2828,7 @@ CL_RegisterCustomization register custom resource for player ==================== */ -void CL_RegisterCustomization( resource_t *resource ) +static void CL_RegisterCustomization( resource_t *resource ) { qboolean bFound = false; customization_t *pList; @@ -2417,22 +2867,29 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { int sound_len = sizeof( DEFAULT_SOUNDPATH ) - 1; byte rgucMD5_hash[16]; - const char *pfilename; resource_t *p; if( COM_CheckString( filename ) && successfully_received ) { if( filename[0] != '!' ) Con_Printf( "processing %s\n", filename ); + + if( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 )) + { + // skip "downloaded/" part to avoid mismatch with needed resources list + filename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1; + } } else if( !successfully_received ) { Con_Printf( S_ERROR "server failed to transmit file '%s'\n", CL_CleanFileName( filename )); } - if( cls.legacymode ) + + if( cls.legacymode == PROTO_LEGACY ) { if( host.downloadcount > 0 ) host.downloadcount--; + if( !host.downloadcount ) { MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); @@ -2441,11 +2898,6 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) return; } - pfilename = filename; - - if( !Q_strnicmp( filename, DEFAULT_SOUNDPATH, sound_len )) - pfilename += sound_len; - for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) { if( !Q_strnicmp( filename, "!MD5", 4 )) @@ -2455,18 +2907,20 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) if( !memcmp( p->rgucMD5_hash, rgucMD5_hash, 16 )) break; } + else if( p->type == t_sound ) + { + const char *pfilename = filename; + + if( !Q_strnicmp( filename, DEFAULT_SOUNDPATH, sound_len )) + pfilename += sound_len; + + if( !Q_stricmp( p->szFileName, pfilename )) + break; + } else { - if( p->type == t_generic ) - { - if( !Q_stricmp( p->szFileName, filename )) + if( !Q_stricmp( p->szFileName, filename )) break; - } - else - { - if( !Q_stricmp( p->szFileName, pfilename )) - break; - } } } @@ -2483,14 +2937,14 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { if( p->ucFlags & RES_CUSTOM ) { - HPAK_AddLump( true, CUSTOM_RES_PATH, p, cls.netchan.tempbuffer, NULL ); + HPAK_AddLump( true, hpk_custom_file.string, p, cls.netchan.tempbuffer, NULL ); CL_RegisterCustomization( p ); } } else { Con_Printf( "Downloaded %i bytes for purported %i byte file, ignoring download\n", - cls.netchan.tempbuffersize, p->nDownloadSize ); + cls.netchan.tempbuffersize, p->nDownloadSize ); } if( cls.netchan.tempbuffer ) @@ -2520,7 +2974,7 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) MSG_Init( &msg, "Resource Registration", msg_buf, sizeof( msg_buf )); if( CL_PrecacheResources( )) - CL_RegisterResources( &msg ); + CL_RegisterResources( &msg, cls.legacymode ); if( MSG_GetNumBytesWritten( &msg ) > 0 ) { @@ -2531,7 +2985,7 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename ) if( cls.netchan.tempbuffer ) { - Con_Printf( "Received a decal %s, but didn't find it in resources needed list!\n", pfilename ); + Con_Printf( "Received a decal %s, but didn't find it in resources needed list!\n", filename ); Mem_Free( cls.netchan.tempbuffer ); } @@ -2556,7 +3010,7 @@ void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); if( reliable ) @@ -2571,13 +3025,44 @@ void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) } } +/* +=============== +CL_UpdateInfo + +tell server about changed userinfo +=============== +*/ +void CL_UpdateInfo( const char *key, const char *value ) +{ + switch( cls.legacymode ) + { + case PROTO_LEGACY: + if( cls.state != ca_active ) + break; + + MSG_BeginClientCmd( &cls.netchan.message, clc_legacy_userinfo ); + MSG_WriteString( &cls.netchan.message, cls.userinfo ); + break; + case PROTO_GOLDSRC: + if( cl_advertise_engine_in_name.value && !Q_stricmp( key, "name" ) && Q_strnicmp( value, "[Xash3D]", 8 )) + { + CL_ServerCommand( true, "setinfo \"%s\" \"[Xash3D]%s\"\n", key, value ); + break; + } + // intentional fallthrough + default: + CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", key, value ); + break; + } +} + //============================================================================= /* ============== CL_SetInfo_f ============== */ -void CL_SetInfo_f( void ) +static void CL_SetInfo_f( void ) { convar_t *var; @@ -2585,7 +3070,7 @@ void CL_SetInfo_f( void ) { Con_Printf( "User info settings:\n" ); Info_Print( cls.userinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( cls.userinfo )); + Con_Printf( "Total %zu symbols\n", Q_strlen( cls.userinfo )); return; } @@ -2605,7 +3090,7 @@ void CL_SetInfo_f( void ) { Cvar_DirectSet( var, Cmd_Argv( 2 )); } - else if( Info_SetValueForKey( cls.userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_INFO_STRING )) + else if( Info_SetValueForKey( cls.userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), sizeof( cls.userinfo ))) { // send update only on successfully changed userinfo Cmd_ForwardToServer (); @@ -2617,17 +3102,46 @@ void CL_SetInfo_f( void ) CL_Physinfo_f ============== */ -void CL_Physinfo_f( void ) +static void CL_Physinfo_f( void ) { Con_Printf( "Phys info settings:\n" ); Info_Print( cls.physinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( cls.physinfo )); + Con_Printf( "Total %zu symbols\n", Q_strlen( cls.physinfo )); +} + +static qboolean CL_ShouldRescanFilesystem( void ) +{ + resource_t *res; + qboolean retval = false; + + for( res = cl.resourcesonhand.pNext; res && res != &cl.resourcesonhand; res = res->pNext ) + { + if( res->type == t_generic ) + { + const char *ext = COM_FileExtension( res->szFileName ); + + if( !g_fsapi.IsArchiveExtensionSupported( ext, IAES_ONLY_REAL_ARCHIVES )) + continue; + + if( FBitSet( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED )) + continue; + + SetBits( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED ); + retval = true; + } + } + + return retval; } qboolean CL_PrecacheResources( void ) { resource_t *pRes; + // if we downloaded new WAD files or any other archives they must be added to searchpath + if( CL_ShouldRescanFilesystem( )) + FS_Rescan_f(); + // NOTE: world need to be loaded as first model for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) { @@ -2680,7 +3194,7 @@ qboolean CL_PrecacheResources( void ) switch( pRes->type ) { case t_sound: - if( pRes->nIndex != -1 ) + if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.sound_precache ) && pRes->nIndex < ARRAYSIZE( cl.sound_index )) { if( FBitSet( pRes->ucFlags, RES_WASMISSING )) { @@ -2713,40 +3227,49 @@ qboolean CL_PrecacheResources( void ) case t_skin: break; case t_model: - cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); - if( pRes->szFileName[0] != '*' ) + if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.models )) { - if( pRes->nIndex != -1 ) + cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); + if( pRes->szFileName[0] != '*' ) { - cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, true ); - - if( cl.models[pRes->nIndex] == NULL ) + if( pRes->nIndex != -1 ) { - if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) + cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, true ); + + if( cl.models[pRes->nIndex] == NULL ) { - S_EndRegistration(); - CL_Disconnect_f(); - return false; + if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) + { + S_EndRegistration(); + CL_Disconnect_f(); + return false; + } } } - } - else - { - CL_LoadClientSprite( pRes->szFileName ); + else + { + CL_LoadClientSprite( pRes->szFileName ); + } } } break; case t_decal: - if( !FBitSet( pRes->ucFlags, RES_CUSTOM )) + if( !FBitSet( pRes->ucFlags, RES_CUSTOM ) && pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( host.draw_decals )) Q_strncpy( host.draw_decals[pRes->nIndex], pRes->szFileName, sizeof( host.draw_decals[0] )); break; case t_generic: - Q_strncpy( cl.files_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.files_precache[0] )); - cl.numfiles = Q_max( cl.numfiles, pRes->nIndex + 1 ); + if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.files_precache )) + { + Q_strncpy( cl.files_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.files_precache[0] )); + cl.numfiles = Q_max( cl.numfiles, pRes->nIndex + 1 ); + } break; case t_eventscript: - Q_strncpy( cl.event_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.event_precache[0] )); - CL_SetEventIndex( cl.event_precache[pRes->nIndex], pRes->nIndex ); + if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.event_precache )) + { + Q_strncpy( cl.event_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.event_precache[0] )); + CL_SetEventIndex( cl.event_precache[pRes->nIndex], pRes->nIndex ); + } break; default: break; @@ -2772,7 +3295,7 @@ CL_FullServerinfo_f Sent by server when serverinfo changes ================== */ -void CL_FullServerinfo_f( void ) +static void CL_FullServerinfo_f( void ) { if( Cmd_Argc() != 2 ) { @@ -2790,7 +3313,7 @@ CL_Escape_f Escape to menu from game ================= */ -void CL_Escape_f( void ) +static void CL_Escape_f( void ) { if( cls.key_dest == key_menu ) return; @@ -2803,12 +3326,28 @@ void CL_Escape_f( void ) else UI_SetActiveMenu( true ); } +static void CL_ListMessages_f( void ) +{ + int i; + + Con_Printf( "num size name\n" ); + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + if( !COM_CheckStringEmpty( clgame.msg[i].name )) + break; + + Con_Printf( "%3d\t%3d\t%s\n", clgame.msg[i].number, clgame.msg[i].size, clgame.msg[i].name ); + } + + Con_Printf( "Total %i messages\n", i ); +} + /* ================= CL_InitLocal ================= */ -void CL_InitLocal( void ) +static void CL_InitLocal( void ) { cls.state = ca_disconnected; cls.signon = 0; @@ -2817,6 +3356,10 @@ void CL_InitLocal( void ) cl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded; cl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand; + Cvar_RegisterVariable( &cl_ticket_generator ); + Cvar_RegisterVariable( &cl_advertise_engine_in_name ); + + Cvar_RegisterVariable( &showpause ); Cvar_RegisterVariable( &mp_decals ); Cvar_RegisterVariable( &dev_overview ); Cvar_RegisterVariable( &cl_resend ); @@ -2825,64 +3368,72 @@ void CL_InitLocal( void ) Cvar_RegisterVariable( &cl_download_ingame ); Cvar_RegisterVariable( &cl_logofile ); Cvar_RegisterVariable( &cl_logocolor ); + Cvar_RegisterVariable( &cl_logoext ); + Cvar_RegisterVariable( &cl_logomaxdim ); Cvar_RegisterVariable( &cl_test_bandwidth ); Voice_RegisterCvars(); + VGui_RegisterCvars(); // register our variables - cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); - cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0, "disable delta-compression for server messages" ); - cl_idealpitchscale = Cvar_Get( "cl_idealpitchscale", "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" ); - cl_solid_players = Cvar_Get( "cl_solid_players", "1", 0, "Make all players not solid (can't traceline them)" ); - cl_interp = Cvar_Get( "ex_interp", "0.1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "Interpolate object positions starting this many seconds in past" ); - cl_timeout = Cvar_Get( "cl_timeout", "60", 0, "connect timeout (in-seconds)" ); - cl_charset = Cvar_Get( "cl_charset", "utf-8", FCVAR_ARCHIVE, "1-byte charset to use (iconv style)" ); - hud_utf8 = Cvar_Get( "hud_utf8", "0", FCVAR_ARCHIVE, "Use utf-8 encoding for hud text" ); + Cvar_RegisterVariable( &cl_crosshair ); + Cvar_RegisterVariable( &cl_nodelta ); + Cvar_RegisterVariable( &cl_idealpitchscale ); + Cvar_RegisterVariable( &cl_solid_players ); + Cvar_RegisterVariable( &cl_interp ); + Cvar_RegisterVariable( &cl_timeout ); + Cvar_RegisterVariable( &cl_charset ); + Cvar_RegisterVariable( &hud_utf8 ); - rcon_client_password = Cvar_Get( "rcon_password", "", FCVAR_PRIVILEGED, "remote control client password" ); - rcon_address = Cvar_Get( "rcon_address", "", FCVAR_PRIVILEGED, "remote control address" ); + Cvar_RegisterVariable( &rcon_address ); - cl_trace_messages = Cvar_Get( "cl_trace_messages", "0", FCVAR_ARCHIVE|FCVAR_CHEAT, "enable message names tracing (good for developers)"); + Cvar_RegisterVariable( &cl_trace_consistency ); + Cvar_RegisterVariable( &cl_trace_stufftext ); + Cvar_RegisterVariable( &cl_trace_messages ); + Cvar_RegisterVariable( &cl_trace_events ); // userinfo - cl_nopred = Cvar_Get( "cl_nopred", "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" ); - name = Cvar_Get( "name", Sys_GetCurrentUser(), FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_PRINTABLEONLY, "player name" ); - model = Cvar_Get( "model", "", FCVAR_USERINFO|FCVAR_ARCHIVE, "player model ('player' is a singleplayer model)" ); - cl_updaterate = Cvar_Get( "cl_updaterate", "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); - cl_dlmax = Cvar_Get( "cl_dlmax", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); - cl_upmax = Cvar_Get( "cl_upmax", "1200", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); - cl_nat = Cvar_Get( "cl_nat", "0", 0, "show servers running under NAT" ); - rate = Cvar_Get( "rate", "3500", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); - topcolor = Cvar_Get( "topcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player top color" ); - bottomcolor = Cvar_Get( "bottomcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player bottom color" ); - cl_lw = Cvar_Get( "cl_lw", "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable client weapon predicting" ); + Cvar_RegisterVariable( &cl_nopred ); + Q_strncpy( username, Sys_GetCurrentUser(), sizeof( username )); // initialize before registering variable + Cvar_RegisterVariable( &name ); + Cvar_Get( "ui_username", username, FCVAR_READ_ONLY|FCVAR_PRIVILEGED, "default user name" ); + Cvar_RegisterVariable( &model ); + Cvar_RegisterVariable( &cl_updaterate ); + Cvar_RegisterVariable( &cl_dlmax ); + Cvar_RegisterVariable( &cl_upmax ); + Cvar_RegisterVariable( &cl_nat ); + Cvar_RegisterVariable( &rate ); + Cvar_RegisterVariable( &topcolor ); + Cvar_RegisterVariable( &bottomcolor ); + Cvar_RegisterVariable( &cl_lw ); Cvar_Get( "cl_lc", "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable lag compensation" ); Cvar_Get( "password", "", FCVAR_USERINFO, "server password" ); Cvar_Get( "team", "", FCVAR_USERINFO, "player team" ); Cvar_Get( "skin", "", FCVAR_USERINFO, "player skin" ); - cl_nosmooth = Cvar_Get( "cl_nosmooth", "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" ); - cl_nointerp = Cvar_Get( "cl_nointerp", "0", FCVAR_CLIENTDLL, "disable interpolation of entities and players" ); - cl_smoothtime = Cvar_Get( "cl_smoothtime", "0", FCVAR_ARCHIVE, "time to smooth up" ); - cl_cmdbackup = Cvar_Get( "cl_cmdbackup", "10", FCVAR_ARCHIVE, "how many additional history commands are sent" ); - cl_cmdrate = Cvar_Get( "cl_cmdrate", "30", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); - cl_draw_particles = Cvar_Get( "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); - cl_draw_tracers = Cvar_Get( "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); - cl_draw_beams = Cvar_Get( "r_drawbeams", "1", FCVAR_CHEAT, "render beams" ); - cl_lightstyle_lerping = Cvar_Get( "cl_lightstyle_lerping", "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" ); - cl_showerror = Cvar_Get( "cl_showerror", "0", FCVAR_ARCHIVE, "show prediction error" ); - cl_bmodelinterp = Cvar_Get( "cl_bmodelinterp", "1", FCVAR_ARCHIVE, "enable bmodel interpolation" ); - cl_clockreset = Cvar_Get( "cl_clockreset", "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" ); - cl_fixtimerate = Cvar_Get( "cl_fixtimerate", "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" ); - hud_scale = Cvar_Get( "hud_scale", "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" ); + Cvar_RegisterVariable( &cl_nosmooth ); + Cvar_RegisterVariable( &cl_nointerp ); + Cvar_RegisterVariable( &cl_smoothtime ); + Cvar_RegisterVariable( &cl_cmdbackup ); + Cvar_RegisterVariable( &cl_cmdrate ); + Cvar_RegisterVariable( &cl_draw_particles ); + Cvar_RegisterVariable( &cl_draw_tracers ); + Cvar_RegisterVariable( &cl_draw_beams ); + Cvar_RegisterVariable( &cl_lightstyle_lerping ); + Cvar_RegisterVariable( &cl_showerror ); + Cvar_RegisterVariable( &cl_bmodelinterp ); + Cvar_RegisterVariable( &cl_clockreset ); + Cvar_RegisterVariable( &cl_fixtimerate ); + Cvar_RegisterVariable( &hud_fontscale ); + Cvar_RegisterVariable( &hud_fontrender ); + Cvar_RegisterVariable( &hud_scale ); + Cvar_RegisterVariable( &hud_scale_minimal_width ); Cvar_Get( "cl_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); - cl_showevents = Cvar_Get( "cl_showevents", "0", FCVAR_ARCHIVE, "show events playback" ); + Cvar_RegisterVariable( &cl_showevents ); Cvar_Get( "lastdemo", "", FCVAR_ARCHIVE, "last played demo" ); - ui_renderworld = Cvar_Get( "ui_renderworld", "0", FCVAR_ARCHIVE, "render world when UI is visible" ); - - // these two added to shut up CS 1.5 about 'unknown' commands - Cvar_Get( "lightgamma", "1", FCVAR_ARCHIVE, "ambient lighting level (legacy, unused)" ); - Cvar_Get( "direct", "1", FCVAR_ARCHIVE, "direct lighting level (legacy, unused)" ); + Cvar_RegisterVariable( &ui_renderworld ); + Cvar_RegisterVariable( &cl_maxframetime ); + Cvar_RegisterVariable( &cl_fixmodelinterpolationartifacts ); // server commands Cmd_AddCommand ("noclip", NULL, "enable or disable no clipping mode" ); @@ -2895,10 +3446,17 @@ void CL_InitLocal( void ) Cmd_AddCommand ("god", NULL, "enable godmode" ); Cmd_AddCommand ("fov", NULL, "set client field of view" ); + Cmd_AddRestrictedCommand ("ent_list", NULL, "list entities on server" ); + Cmd_AddRestrictedCommand ("ent_fire", NULL, "fire entity command (be careful)" ); + Cmd_AddRestrictedCommand ("ent_info", NULL, "dump entity information" ); + Cmd_AddRestrictedCommand ("ent_create", NULL, "create entity with specified values (be careful)" ); + Cmd_AddRestrictedCommand ("ent_getvars", NULL, "put parameters of specified entities to client's' ent_last_* cvars" ); + // register our commands Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); - Cmd_AddCommand ("localservers", CL_LocalServers_f, "collect info about local servers" ); - Cmd_AddCommand ("internetservers", CL_InternetServers_f, "collect info about internet servers" ); + Cmd_AddRestrictedCommand( "localservers", CL_LocalServers_f, "collect info about local servers" ); + Cmd_AddRestrictedCommand( "internetservers", CL_InternetServers_f, "collect info about internet servers" ); + Cmd_AddRestrictedCommand( "ui_queryserver", CL_QueryServer_f, "query server info from console" ); Cmd_AddCommand ("cd", CL_PlayCDTrack_f, "Play cd-track (not real cd-player of course)" ); Cmd_AddCommand ("mp3", CL_PlayCDTrack_f, "Play mp3-track (based on virtual cd-player)" ); Cmd_AddCommand ("waveplaylen", CL_WavePlayLen_f, "Get approximate length of wave file"); @@ -2907,14 +3465,15 @@ void CL_InitLocal( void ) Cmd_AddRestrictedCommand ("userinfo", CL_SetInfo_f, "examine or change the userinfo string (alias of setinfo)" ); Cmd_AddCommand ("physinfo", CL_Physinfo_f, "print current client physinfo" ); Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server" ); - Cmd_AddCommand ("record", CL_Record_f, "record a demo" ); + Cmd_AddRestrictedCommand( "record", CL_Record_f, "record a demo" ); Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "play a demo" ); Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "demo benchmark" ); - Cmd_AddCommand ("killdemo", CL_DeleteDemo_f, "delete a specified demo file" ); + Cmd_AddRestrictedCommand( "killdemo", CL_DeleteDemo_f, "delete a specified demo file" ); Cmd_AddCommand ("startdemos", CL_StartDemos_f, "start playing back the selected demos sequentially" ); Cmd_AddCommand ("demos", CL_Demos_f, "restart looping demos defined by the last startdemos command" ); Cmd_AddCommand ("movie", CL_PlayVideo_f, "play a movie" ); Cmd_AddCommand ("stop", CL_Stop_f, "stop playing or recording a demo" ); + Cmd_AddCommand( "listdemo", CL_ListDemo_f, "list demo entries" ); Cmd_AddCommand ("info", NULL, "collect info about local servers with specified protocol" ); Cmd_AddCommand ("escape", CL_Escape_f, "escape from game to menu" ); Cmd_AddCommand ("togglemenu", CL_Escape_f, "toggle between game and menu" ); @@ -2923,15 +3482,17 @@ void CL_InitLocal( void ) Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f, "sent by server when serverinfo changes" ); Cmd_AddCommand ("upload", CL_BeginUpload_f, "uploading file to the server" ); + Cmd_AddRestrictedCommand( "replaybufferdat", CL_ReplayBufferDat_f, "development and debugging tool" ); + Cmd_AddRestrictedCommand ("quit", CL_Quit_f, "quit from game" ); Cmd_AddRestrictedCommand ("exit", CL_Quit_f, "quit from game" ); - Cmd_AddCommand ("screenshot", CL_ScreenShot_f, "takes a screenshot of the next rendered frame" ); - Cmd_AddCommand ("snapshot", CL_SnapShot_f, "takes a snapshot of the next rendered frame" ); - Cmd_AddCommand ("envshot", CL_EnvShot_f, "takes a six-sides cubemap shot with specified name" ); - Cmd_AddCommand ("skyshot", CL_SkyShot_f, "takes a six-sides envmap (skybox) shot with specified name" ); + Cmd_AddCommand ("screenshot", CL_GenericShot_f, "takes a screenshot of the next rendered frame" ); + Cmd_AddCommand ("snapshot", CL_GenericShot_f, "takes a snapshot of the next rendered frame" ); + Cmd_AddCommand ("envshot", CL_GenericShot_f, "takes a six-sides cubemap shot with specified name" ); + Cmd_AddCommand ("skyshot", CL_GenericShot_f, "takes a six-sides envmap (skybox) shot with specified name" ); Cmd_AddCommand ("levelshot", CL_LevelShot_f, "same as \"screenshot\", used for create plaque images" ); - Cmd_AddCommand ("saveshot", CL_SaveShot_f, "used for create save previews with LoadGame menu" ); + Cmd_AddCommand ("saveshot", CL_GenericShot_f, "used for create save previews with LoadGame menu" ); Cmd_AddCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" ); @@ -2939,6 +3500,10 @@ void CL_InitLocal( void ) Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" ); Cmd_AddCommand ("precache", CL_LegacyPrecache_f, "legacy server compatibility" ); + Cmd_AddCommand( "richpresence_gamemode", Cmd_Null_f, "compatibility command, does nothing" ); + Cmd_AddCommand( "richpresence_update", Cmd_Null_f, "compatibility command, does nothing" ); + + Cmd_AddCommand( "cl_list_messages", CL_ListMessages_f, "list registered user messages" ); } //============================================================================ @@ -2950,23 +3515,23 @@ slowly adjuct client clock to smooth lag effect ================== */ -void CL_AdjustClock( void ) +static void CL_AdjustClock( void ) { - if( cl.timedelta == 0.0f || !cl_fixtimerate->value ) + if( cl.timedelta == 0.0f || !cl_fixtimerate.value ) return; - if( cl_fixtimerate->value < 0.0f ) + if( cl_fixtimerate.value < 0.0f ) Cvar_SetValue( "cl_fixtimerate", 7.5f ); if( fabs( cl.timedelta ) >= 0.001f ) { double msec, adjust; - float sign; + double sign; - msec = ( cl.timedelta * 1000.0f ); - sign = ( msec < 0 ) ? 1.0f : -1.0f; - msec = fabs( msec ); - adjust = sign * ( cl_fixtimerate->value / 1000.0f ); + msec = ( cl.timedelta * 1000.0 ); + sign = ( msec < 0 ) ? 1.0 : -1.0; + msec = Q_min( cl_fixtimerate.value, fabs( msec )); + adjust = sign * ( msec / 1000.0 ); if( fabs( adjust ) < fabs( cl.timedelta )) { @@ -3013,6 +3578,8 @@ void Host_ClientFrame( void ) { // if client is not active, do nothing if( !cls.initialized ) return; + if( cls.key_dest == key_game && cls.state == ca_active && !Con_Visible() ) + Platform_SetTimer( cl_maxframetime.value ); // if running the server remotely, send intentions now after // the incoming messages have been read @@ -3051,9 +3618,6 @@ void Host_ClientFrame( void ) // catch changes video settings VID_CheckChanges(); - // process VGUI - VGui_RunFrame (); - // update the screen SCR_UpdateScreen (); @@ -3085,7 +3649,7 @@ void CL_Init( void ) VID_Init(); // init video S_Init(); // init sound - Voice_Init( VOICE_DEFAULT_CODEC, 3 ); // init voice + Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device) // unreliable buffer. unsed for unreliable commands and voice stream MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); @@ -3094,9 +3658,12 @@ void CL_Init( void ) COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); - if( !CL_LoadProgs( libpath ) ) - Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError() ); + if( !CL_LoadProgs( libpath )) + Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( )); + ID_Init(); + + cls.build_num = 0; cls.initialized = true; cl.maxclients = 1; // allow to drawing player in menu cls.olddemonum = -1; @@ -3111,9 +3678,9 @@ CL_Shutdown */ void CL_Shutdown( void ) { - Con_Printf( "CL_Shutdown()\n" ); + Con_Printf( "%s()\n", __func__ ); - if( !host.crashed && cls.initialized ) + if( host.status != HOST_CRASHED && cls.initialized ) { Host_WriteOpenGLConfig (); Host_WriteVideoConfig (); @@ -3129,9 +3696,12 @@ void CL_Shutdown( void ) CL_UnloadProgs (); cls.initialized = false; - VGui_Shutdown(); + // for client-side VGUI support we use other order + if( FI && FI->GameInfo && !FI->GameInfo->internal_vgui_support ) + VGui_Shutdown(); - FS_Delete( "demoheader.tmp" ); // remove tmp file + if( g_fsapi.Delete ) + g_fsapi.Delete( "demoheader.tmp" ); // remove tmp file SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them S_Shutdown (); R_Shutdown (); diff --git a/engine/client/cl_mobile.c b/engine/client/cl_mobile.c index 3e59b85a..7e441830 100644 --- a/engine/client/cl_mobile.c +++ b/engine/client/cl_mobile.c @@ -20,26 +20,19 @@ GNU General Public License for more details. #include "input.h" #include "platform/platform.h" -mobile_engfuncs_t *gMobileEngfuncs; +static CVAR_DEFINE_AUTO( vibration_length, "1.0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "vibration length" ); +static CVAR_DEFINE_AUTO( vibration_enable, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "enable vibration" ); -convar_t *vibration_length; -convar_t *vibration_enable; +static cl_font_t g_scaled_font; +static float g_font_scale; static void pfnVibrate( float life, char flags ) { - if( !vibration_enable->value ) + if( !vibration_enable.value || life < 0.0f ) return; - if( life < 0.0f ) - { - Con_Reportf( S_WARN "Negative vibrate time: %f\n", life ); - return; - } - - //Con_Reportf( "Vibrate: %f %d\n", life, flags ); - // here goes platform-specific backends - Platform_Vibrate( life * vibration_length->value, flags ); + Platform_Vibrate( life * vibration_length.value, flags ); } static void Vibrate_f( void ) @@ -50,7 +43,7 @@ static void Vibrate_f( void ) return; } - pfnVibrate( Q_atof( Cmd_Argv(1) ), VIBRATE_NORMAL ); + pfnVibrate( Q_atof( Cmd_Argv( 1 )), VIBRATE_NORMAL ); } static void pfnEnableTextInput( int enable ) @@ -60,37 +53,28 @@ static void pfnEnableTextInput( int enable ) static int pfnDrawScaledCharacter( int x, int y, int number, int r, int g, int b, float scale ) { - int width = clgame.scrInfo.charWidths[number] * scale * hud_scale->value; - int height = clgame.scrInfo.iCharHeight * scale * hud_scale->value; + // this call is very ineffective and possibly broken! + rgba_t color = { r, g, b, 255 }; + int flags = FONT_DRAW_HUD; - if( !cls.creditsFont.valid ) - return 0; + if( hud_utf8.value ) + SetBits( flags, FONT_DRAW_UTF8 ); - x *= hud_scale->value; - y *= hud_scale->value; + if( fabs( g_font_scale - scale ) > 0.1f || + g_scaled_font.hFontTexture != cls.creditsFont.hFontTexture ) + { + int i; - number &= 255; - number = Con_UtfProcessChar( number ); + g_scaled_font = cls.creditsFont; + g_scaled_font.scale *= scale; + g_scaled_font.charHeight *= scale; + for( i = 0; i < ARRAYSIZE( g_scaled_font.charWidths ); i++ ) + g_scaled_font.charWidths[i] *= scale; - if( number < 32 ) - return 0; + g_font_scale = scale; + } - if( y < -height ) - return 0; - - pfnPIC_Set( cls.creditsFont.hFontTexture, r, g, b, 255 ); - pfnPIC_DrawAdditive( x, y, width, height, &cls.creditsFont.fontRc[number] ); - - return width; -} - -static void *pfnGetNativeObject( const char *obj ) -{ - if( !obj ) - return NULL; - - // Backend should consider that obj is case-sensitive - return Platform_GetNativeObject( obj ); + return CL_DrawCharacter( x, y, number, color, &g_scaled_font, flags ); } static void pfnTouch_HideButtons( const char *name, byte state ) @@ -108,7 +92,12 @@ static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned i return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); } -static mobile_engfuncs_t gpMobileEngfuncs = +static void GAME_EXPORT pfnSetCustomClientID( const char *id ) +{ + // deprecated +} + +static const mobile_engfuncs_t gMobileEngfuncs = { MOBILITY_API_VERSION, pfnVibrate, @@ -121,28 +110,45 @@ static mobile_engfuncs_t gpMobileEngfuncs = Touch_ResetDefaultButtons, pfnDrawScaledCharacter, Sys_Warn, - pfnGetNativeObject, - ID_SetCustomClientID, + Sys_GetNativeObject, + pfnSetCustomClientID, pfnParseFileSafe }; qboolean Mobile_Init( void ) { - qboolean success = false; pfnMobilityInterface ExportToClient; - // find a mobility interface - ExportToClient = COM_GetProcAddress( clgame.hInstance, MOBILITY_CLIENT_EXPORT ); - gMobileEngfuncs = &gpMobileEngfuncs; + Cmd_AddCommand( "vibrate", Vibrate_f, "Vibrate for specified time"); + Cvar_RegisterVariable( &vibration_length ); + Cvar_RegisterVariable( &vibration_enable ); - if( ExportToClient && !ExportToClient( gMobileEngfuncs ) ) - success = true; + // find mobility interface + if(( ExportToClient = COM_GetProcAddress( clgame.hInstance, MOBILITY_CLIENT_EXPORT ))) + { + static mobile_engfuncs_t mobile_engfuncs; // keep a copy, don't let user change engine pointers - Cmd_AddCommand( "vibrate", (xcommand_t)Vibrate_f, "Vibrate for specified time"); - vibration_length = Cvar_Get( "vibration_length", "1.0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "Vibration length"); - vibration_enable = Cvar_Get( "vibration_enable", "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "Enable vibration"); + mobile_engfuncs = gMobileEngfuncs; - return success; + if( !ExportToClient( &mobile_engfuncs )) + { + Con_Reportf( "%s: ^2initailized extended MobilityAPI ^7ver. %i\n", __func__, MOBILITY_API_VERSION ); + return true; + } + + // make sure that mobile functions are cleared +#if 1 + // some SDKs define export as returning void, breaking the contract + // ignore result for now... + return true; +#else + memset( &mobile_engfuncs, 0, sizeof( mobile_engfuncs )); + + return false; // just tell user about problems +#endif + } + + return true; // mobile interface is missed } void Mobile_Shutdown( void ) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index 7c9ee99a..7dc79b17 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -33,12 +33,12 @@ GNU General Public License for more details. #define NETGRAPH_NET_COLORS 5 #define NUM_LATENCY_SAMPLES 8 -convar_t *net_graph; -static convar_t *net_graphpos; -static convar_t *net_graphwidth; -static convar_t *net_graphheight; -static convar_t *net_graphsolid; -static convar_t *net_scale; +CVAR_DEFINE_AUTO( net_graph, "0", FCVAR_ARCHIVE, "draw network usage graph" ); +static CVAR_DEFINE_AUTO( net_graphpos, "1", FCVAR_ARCHIVE, "network usage graph position" ); +static CVAR_DEFINE_AUTO( net_scale, "5", FCVAR_ARCHIVE, "network usage graph scale level" ); +static CVAR_DEFINE_AUTO( net_graphwidth, "192", FCVAR_ARCHIVE, "network usage graph width" ); +static CVAR_DEFINE_AUTO( net_graphheight, "64", FCVAR_ARCHIVE, "network usage graph height" ); +static CVAR_DEFINE_AUTO( net_graphsolid, "1", FCVAR_ARCHIVE, "fill segments in network usage graph" ); static struct packet_latency_t { @@ -63,9 +63,9 @@ static byte netcolors[NETGRAPH_NET_COLORS+NETGRAPH_LERP_HEIGHT][4] = // other will be generated through NetGraph_InitColors() }; -static byte sendcolor[4] = { 88, 29, 130, 255 }; -static byte holdcolor[4] = { 255, 0, 0, 200 }; -static byte extrap_base_color[4] = { 255, 255, 255, 255 }; +static const byte sendcolor[4] = { 88, 29, 130, 255 }; +static const byte holdcolor[4] = { 255, 0, 0, 200 }; +static const byte extrap_base_color[4] = { 255, 255, 255, 255 }; static netbandwidthgraph_t netstat_graph[NET_TIMINGS]; static float packet_loss; static float packet_choke; @@ -79,7 +79,7 @@ NetGraph_DrawRect NetGraph_FillRGBA shortcut ========== */ -static void NetGraph_DrawRect( wrect_t *rect, byte colors[4] ) +static void NetGraph_DrawRect( const wrect_t *rect, const byte colors[4] ) { ref.dllFuncs.Color4ub( colors[0], colors[1], colors[2], colors[3] ); // color for this quad @@ -211,7 +211,7 @@ static void NetGraph_GetFrameData( float *latency, int *latency_count ) else { int frame_latency = Q_min( 1.0f, f->latency ); - p->latency = (( frame_latency + 0.1f ) / 1.1f ) * ( net_graphheight->value - NETGRAPH_LERP_HEIGHT - 2 ); + p->latency = (( frame_latency + 0.1f ) / 1.1f ) * ( net_graphheight.value - NETGRAPH_LERP_HEIGHT - 2 ); if( i > cls.netchan.incoming_sequence - NUM_LATENCY_SAMPLES ) { @@ -260,7 +260,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) for( a = 0; a < w; a++ ) { i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK; - h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight->value * 0.7f); + h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight.value * 0.7f); fill.left = x + w - a - 1; fill.right = fill.bottom = 1; @@ -273,7 +273,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) h -= extrap_point; fill.top -= extrap_point; - if( !net_graphsolid->value ) + if( !net_graphsolid.value ) { fill.top -= (h - 1); start = (h - 1); @@ -281,7 +281,10 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) for( j = start; j < h; j++ ) { - NetGraph_DrawRect( &fill, netcolors[NETGRAPH_NET_COLORS + j + extrap_point] ); + int color = NETGRAPH_NET_COLORS + j + extrap_point; + color = Q_min( color, ARRAYSIZE( netcolors ) - 1 ); + + NetGraph_DrawRect( &fill, netcolors[color] ); fill.top--; } } @@ -292,12 +295,15 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) fill.top -= h; h = extrap_point - h; - if( !net_graphsolid->value ) + if( !net_graphsolid.value ) h = 1; for( j = 0; j < h; j++ ) { - NetGraph_DrawRect( &fill, netcolors[NETGRAPH_NET_COLORS + j + oldh] ); + int color = NETGRAPH_NET_COLORS + j + oldh; + color = Q_min( color, ARRAYSIZE( netcolors ) - 1 ); + + NetGraph_DrawRect( &fill, netcolors[color] ); fill.top--; } } @@ -328,7 +334,7 @@ NetGraph_DrawHatches */ static void NetGraph_DrawHatches( int x, int y ) { - int ystep = (int)( 10.0f / net_scale->value ); + int ystep = (int)( 10.0f / net_scale.value ); byte colorminor[4] = { 0, 63, 63, 200 }; byte color[4] = { 0, 200, 0, 255 }; wrect_t hatch = { x, 4, y, 1 }; @@ -336,9 +342,9 @@ static void NetGraph_DrawHatches( int x, int y ) ystep = Q_max( ystep, 1 ); - for( starty = hatch.top; hatch.top > 0 && ((starty - hatch.top) * net_scale->value < (maxmsgbytes + 50)); hatch.top -= ystep ) + for( starty = hatch.top; hatch.top > 0 && ((starty - hatch.top) * net_scale.value < (maxmsgbytes + 50)); hatch.top -= ystep ) { - if(!((int)((starty - hatch.top) * net_scale->value ) % 50 )) + if(!((int)((starty - hatch.top) * net_scale.value ) % 50 )) { NetGraph_DrawRect( &hatch, color ); } @@ -358,19 +364,20 @@ NetGraph_DrawTextFields static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int count, float avg, int packet_loss, int packet_choke, int graphtype ) { static int lastout; + cl_font_t *font = Con_GetFont( 0 ); rgba_t colors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 }; int ptx = Q_max( x + w - NETGRAPH_LERP_HEIGHT - 1, 1 ); int pty = Q_max( rect.top + rect.bottom - NETGRAPH_LERP_HEIGHT - 3, 1 ); int out, i = ( cls.netchan.outgoing_sequence - 1 ) & NET_TIMINGS_MASK; int j = cls.netchan.incoming_sequence & NET_TIMINGS_MASK; - int last_y = y - net_graphheight->value; + int last_y = y - net_graphheight.value; if( count > 0 ) { avg = avg / (float)( count - ( host.frametime * FRAMERATE_AVG_FRAC )); - if( cl_updaterate->value > 0.0f ) - avg -= 1000.0f / cl_updaterate->value; + if( cl_updaterate.value > 0.0f ) + avg -= 1000.0f / cl_updaterate.value; // can't be below zero avg = Q_max( 0.0f, avg ); @@ -379,16 +386,17 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun // move rolling average framerate = FRAMERATE_AVG_FRAC * host.frametime + ( 1.0f - FRAMERATE_AVG_FRAC ) * framerate; - Con_SetFont( 0 ); + + CL_SetFontRendermode( font ); if( framerate > 0.0f ) { - y -= net_graphheight->value; + y -= net_graphheight.value; - Con_DrawString( x, y, va( "%.1f fps" , 1.0f / framerate ), colors ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, "%.1f fps" , 1.0f / framerate); if( avg > 1.0f ) - Con_DrawString( x + 75, y, va( "%i ms" , (int)avg ), colors ); + CL_DrawStringf( font, x + 75, y, colors, FONT_DRAW_NORENDERMODE, "%i ms" , (int)avg ); y += 15; @@ -396,10 +404,12 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun if( !out ) out = lastout; else lastout = out; - Con_DrawString( x, y, va( "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, + "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ); y += 15; - Con_DrawString( x, y, va( "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, + "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ); y += 15; if( graphtype > 2 ) @@ -407,16 +417,14 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun int loss = (int)(( packet_loss + PACKETLOSS_AVG_FRAC ) - 0.01f ); int choke = (int)(( packet_choke + PACKETCHOKE_AVG_FRAC ) - 0.01f ); - Con_DrawString( x, y, va( "loss: %i choke: %i", loss, choke ), colors ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, "loss: %i choke: %i", loss, choke ); } } if( graphtype < 3 ) - Con_DrawString( ptx, pty, va( "%i/s", (int)cl_cmdrate->value ), colors ); + CL_DrawStringf( font, ptx, pty, colors, FONT_DRAW_NORENDERMODE, "%i/s", (int)cl_cmdrate.value ); - Con_DrawString( ptx, last_y, va( "%i/s" , (int)cl_updaterate->value ), colors ); - - Con_RestoreFont(); + CL_DrawStringf( font, ptx, last_y, colors, FONT_DRAW_NORENDERMODE, "%i/s" , (int)cl_updaterate.value ); } /* @@ -427,12 +435,12 @@ NetGraph_DrawDataSegment */ static int NetGraph_DrawDataSegment( wrect_t *fill, int bytes, byte r, byte g, byte b, byte a ) { - float h = bytes / net_scale->value; + float h = bytes / net_scale.value; byte colors[4] = { r, g, b, a }; fill->top -= (int)h; - if( net_graphsolid->value ) + if( net_graphsolid.value ) fill->bottom = (int)h; else fill->bottom = 1; @@ -490,7 +498,7 @@ NetGraph_DrawDataUsage static void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype ) { int a, i, h, lastvalidh = 0, ping; - int pingheight = net_graphheight->value - NETGRAPH_LERP_HEIGHT - 2; + int pingheight = net_graphheight.value - NETGRAPH_LERP_HEIGHT - 2; wrect_t fill = { 0 }; byte color[4]; @@ -546,7 +554,7 @@ static void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype ) continue; color[0] = color[1] = color[2] = color[3] = 255; - fill.top = y - net_graphheight->value - 1; + fill.top = y - net_graphheight.value - 1; fill.bottom = 1; if( NetGraph_AtEdge( a, w )) @@ -581,7 +589,7 @@ static void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype ) if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].voicebytes, 255, 255, 255, 255 )) continue; - fill.top = y - net_graphheight->value - 1; + fill.top = y - net_graphheight.value - 1; fill.bottom = 1; fill.top -= 2; @@ -590,7 +598,7 @@ static void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype ) } if( graphtype >= 2 ) - NetGraph_DrawHatches( x, y - net_graphheight->value - 1 ); + NetGraph_DrawHatches( x, y - net_graphheight.value - 1 ); } /* @@ -605,12 +613,12 @@ static void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y ) rect->right = refState.width; rect->bottom = refState.height; - *w = Q_min( NET_TIMINGS, net_graphwidth->value ); + *w = Q_min( NET_TIMINGS, net_graphwidth.value ); if( rect->right < *w + 10 ) *w = rect->right - 10; // detect x and y position - switch( (int)net_graphpos->value ) + switch( (int)net_graphpos.value ) { case 1: // right sided *x = rect->left + rect->right - 5 - *w; @@ -649,20 +657,13 @@ void SCR_DrawNetGraph( void ) in_graph = clgame.dllFuncs.KB_Find( "in_graph" ); - if( in_graph->state & 1 ) - { + if( in_graph && in_graph->state & 1 ) graphtype = 2; - } - else if( net_graph->value != 0.0f ) - { - graphtype = (int)net_graph->value; - } - else - { - return; - } + else if( net_graph.value != 0.0f ) + graphtype = (int)net_graph.value; + else return; - if( net_scale->value <= 0 ) + if( net_scale.value <= 0 ) Cvar_SetValue( "net_scale", 0.1f ); NetGraph_GetScreenPos( &rect, &w, &x, &y ); @@ -689,12 +690,12 @@ void SCR_DrawNetGraph( void ) void CL_InitNetgraph( void ) { - net_graph = Cvar_Get( "net_graph", "0", FCVAR_ARCHIVE, "draw network usage graph" ); - net_graphpos = Cvar_Get( "net_graphpos", "1", FCVAR_ARCHIVE, "network usage graph position" ); - net_scale = Cvar_Get( "net_scale", "5", FCVAR_ARCHIVE, "network usage graph scale level" ); - net_graphwidth = Cvar_Get( "net_graphwidth", "192", FCVAR_ARCHIVE, "network usage graph width" ); - net_graphheight = Cvar_Get( "net_graphheight", "64", FCVAR_ARCHIVE, "network usage graph height" ); - net_graphsolid = Cvar_Get( "net_graphsolid", "1", FCVAR_ARCHIVE, "fill segments in network usage graph" ); + Cvar_RegisterVariable( &net_graph ); + Cvar_RegisterVariable( &net_graphpos ); + Cvar_RegisterVariable( &net_scale ); + Cvar_RegisterVariable( &net_graphwidth ); + Cvar_RegisterVariable( &net_graphheight ); + Cvar_RegisterVariable( &net_graphsolid ); packet_loss = packet_choke = 0.0; NetGraph_InitColors(); diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index de8f46f3..54a69a81 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -31,7 +31,7 @@ CL_UserMsgStub Default stub for missed callbacks =============== */ -int CL_UserMsgStub( const char *pszName, int iSize, void *pbuf ) +static int CL_UserMsgStub( const char *pszName, int iSize, void *pbuf ) { return 1; } @@ -57,7 +57,7 @@ CL_ParseSoundPacket ================== */ -void CL_ParseSoundPacket( sizebuf_t *msg ) +static void CL_ParseSoundPacket( sizebuf_t *msg ) { vec3_t pos; int chan, sound; @@ -156,7 +156,7 @@ void CL_ParseRestoreSoundPacket( sizebuf_t *msg ) char sentenceName[32]; if( flags & SND_SEQUENCE ) - Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound + MAX_SOUNDS_NONSENTENCE ); + Q_snprintf( sentenceName, sizeof( sentenceName ), "!#%i", sound + MAX_SOUNDS_NONSENTENCE ); else Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); handle = S_RegisterSound( sentenceName ); @@ -181,14 +181,14 @@ CL_ParseServerTime ================== */ -void CL_ParseServerTime( sizebuf_t *msg ) +void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto ) { double dt; cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = MSG_ReadFloat( msg ); - if( cls.demoplayback == DEMO_QUAKE1 ) + if( proto == PROTO_QUAKE ) return; // don't mess the time if( cl.maxclients == 1 ) @@ -196,7 +196,7 @@ void CL_ParseServerTime( sizebuf_t *msg ) dt = cl.time - cl.mtime[0]; - if( fabs( dt ) > cl_clockreset->value ) // 0.1 by default + if( fabs( dt ) > cl_clockreset.value ) // 0.1 by default { cl.time = cl.mtime[0]; cl.timedelta = 0.0f; @@ -216,7 +216,7 @@ CL_ParseSignon ================== */ -void CL_ParseSignon( sizebuf_t *msg ) +void CL_ParseSignon( sizebuf_t *msg, connprotocol_t proto ) { int i = MSG_ReadByte( msg ); @@ -228,7 +228,7 @@ void CL_ParseSignon( sizebuf_t *msg ) } cls.signon = i; - CL_SignonReply(); + CL_SignonReply( proto ); } /* @@ -249,9 +249,9 @@ void CL_ParseMovevars( sizebuf_t *msg ) // update sky if changed if( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped ) - ref.dllFuncs.R_SetupSky( clgame.movevars.skyName ); + R_SetupSky( clgame.movevars.skyName ); - memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t )); + clgame.oldmovevars = clgame.movevars; clgame.entities->curstate.scale = clgame.movevars.waveHeight; // keep features an actual! @@ -264,7 +264,7 @@ CL_ParseParticles ================== */ -void CL_ParseParticles( sizebuf_t *msg ) +void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto ) { vec3_t org, dir; int i, count, color; @@ -277,8 +277,12 @@ void CL_ParseParticles( sizebuf_t *msg ) count = MSG_ReadByte( msg ); color = MSG_ReadByte( msg ); - if( count == 255 ) count = 1024; - life = MSG_ReadByte( msg ) * 0.125f; + if( count == 255 ) + count = 1024; + + if( proto == PROTO_GOLDSRC ) + life = 0.0f; + else life = MSG_ReadByte( msg ) * 0.125f; if( life != 0.0f && count == 1 ) { @@ -304,13 +308,16 @@ CL_ParseStaticEntity static client entity ================== */ -void CL_ParseStaticEntity( sizebuf_t *msg ) +static void CL_ParseStaticEntity( sizebuf_t *msg ) { int i, newnum; - entity_state_t from, to; + const entity_state_t from = { 0 }; + entity_state_t to; cl_entity_t *ent; - memset( &from, 0, sizeof( from )); + if( !clgame.static_entities ) + clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] ); @@ -388,7 +395,6 @@ void CL_ParseStaticDecal( sizebuf_t *msg ) { vec3_t origin; int decalIndex, entityIndex, modelIndex; - cl_entity_t *ent = NULL; float scale; int flags; @@ -458,6 +464,7 @@ void CL_BatchResourceRequest( qboolean initialize ) byte data[MAX_INIT_MSG]; resource_t *p, *n; sizebuf_t msg; + qboolean done_downloading = true; MSG_Init( &msg, "Resource Batch", data, sizeof( data )); @@ -487,20 +494,24 @@ void CL_BatchResourceRequest( qboolean initialize ) case t_model: case t_eventscript: if( !CL_CheckFile( &msg, p )) + { + done_downloading = false; break; + } CL_MoveToOnHandList( p ); break; case t_skin: CL_MoveToOnHandList( p ); break; case t_decal: - if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, p, NULL, NULL )) + if( !HPAK_GetDataPointer( hpk_custom_file.string, p, NULL, NULL )) { if( !FBitSet( p->ucFlags, RES_REQUESTED )) { MSG_BeginClientCmd( &msg, clc_stringcmd ); - MSG_WriteString( &msg, va( "dlfile !MD5%s", MD5_Print( p->rgucMD5_hash ) ) ); + MSG_WriteStringf( &msg, "dlfile !MD5%s", MD5_Print( p->rgucMD5_hash ));; SetBits( p->ucFlags, RES_REQUESTED ); + done_downloading = false; } break; } @@ -525,13 +536,9 @@ void CL_BatchResourceRequest( qboolean initialize ) if( cls.state != ca_disconnected ) { - if( !cl.downloadUrl[0] && !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + if( done_downloading && CL_PrecacheResources( )) { - CL_RegisterResources( &msg ); - } - if( cl.downloadUrl[0] && host.downloadcount == 0 && CL_PrecacheResources( ) ) - { - CL_RegisterResources( &msg ); + CL_RegisterResources( &msg, cls.legacymode ); } Netchan_CreateFragments( &cls.netchan, &msg ); @@ -587,7 +594,7 @@ int CL_EstimateNeededResources( void ) return nTotalSize; } -void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) +static void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) { resourceinfo_t ri; @@ -603,6 +610,8 @@ void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) } else { + HTTP_ResetProcessState(); + cls.state = ca_validate; cls.dl.custom = false; } @@ -616,7 +625,7 @@ void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) CL_BatchResourceRequest( !bCustom ); } -customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type ) +static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type ) { customization_t *pList; @@ -628,7 +637,7 @@ customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type return NULL; } -void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove ) +static void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove ) { customization_t *pList; customization_t *pNext; @@ -692,7 +701,7 @@ void CL_ParseCustomization( sizebuf_t *msg ) if( !cl_allow_download.value ) { - Con_DPrintf( "Refusing new resource, cl_allow_download set to 0\n" ); + Con_DPrintf( "Refusing new resource, cl_allowdownload set to 0\n" ); Mem_Free( pRes ); return; } @@ -719,7 +728,7 @@ void CL_ParseCustomization( sizebuf_t *msg ) } } - if( HPAK_GetDataPointer( CUSTOM_RES_PATH, pRes, NULL, NULL )) + if( HPAK_GetDataPointer( hpk_custom_file.string, pRes, NULL, NULL )) { qboolean bError = false; @@ -785,7 +794,9 @@ void CL_ParseResourceRequest( sizebuf_t *msg ) MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 ); } - if( MSG_GetNumBytesWritten( &sbuf ) > 0 ) + // a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always + // write to the buffer + // if( MSG_GetNumBytesWritten( &sbuf ) > 0 ) { Netchan_CreateFragments( &cls.netchan, &sbuf ); Netchan_FragSend( &cls.netchan ); @@ -799,7 +810,7 @@ CL_CreateCustomizationList loading custom decal for self ================== */ -void CL_CreateCustomizationList( void ) +static void CL_CreateCustomizationList( void ) { resource_t *pResource; player_info_t *pPlayer; @@ -843,54 +854,134 @@ void CL_ParseFileTransferFailed( sizebuf_t *msg ) CL_ParseServerData ================== */ -void CL_ParseServerData( sizebuf_t *msg ) +void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto ) { char gamefolder[MAX_QPATH]; + string mapfile; qboolean background; - int i; + int i, required_version; + uint32_t mapCRC; + + HPAK_CheckSize( hpk_custom_file.string ); + + switch( proto ) + { + case PROTO_LEGACY: + required_version = PROTOCOL_LEGACY_VERSION; + Con_Reportf( "Legacy serverdata packet received.\n" ); + break; + case PROTO_GOLDSRC: + required_version = PROTOCOL_GOLDSRC_VERSION; + Con_Reportf( "GoldSrc serverdata packet received.\n" ); + break; + default: + required_version = PROTOCOL_VERSION; + Con_Reportf( "Serverdata packet received.\n" ); + break; + } - Con_Reportf( "Serverdata packet received.\n" ); cls.timestart = Sys_DoubleTime(); - cls.demowaiting = false; // server is changed // wipe the client_t struct if( !cls.changelevel && !cls.changedemo ) CL_ClearState (); + + // Re-init hud video, especially if we changed game directories + clgame.dllFuncs.pfnVidInit(); + cls.state = ca_connected; // parse protocol version number i = MSG_ReadLong( msg ); - - if( i != PROTOCOL_VERSION ) - Host_Error( "Server use invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION ); + if( i != required_version ) // GoldSrc protocol version is 48, same as Xash3D 48 + Host_Error( "Server use invalid protocol (%i should be %i)\n", i, required_version ); cl.servercount = MSG_ReadLong( msg ); cl.checksum = MSG_ReadLong( msg ); - cl.playernum = MSG_ReadByte( msg ); - cl.maxclients = MSG_ReadByte( msg ); - clgame.maxEntities = MSG_ReadWord( msg ); - clgame.maxEntities = bound( MIN_EDICTS, clgame.maxEntities, MAX_EDICTS ); - clgame.maxModels = MSG_ReadWord( msg ); - Q_strncpy( clgame.mapname, MSG_ReadString( msg ), MAX_STRING ); - Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING ); - background = MSG_ReadOneBit( msg ); - Q_strncpy( gamefolder, MSG_ReadString( msg ), MAX_QPATH ); - host.features = (uint)MSG_ReadLong( msg ); - - // receive the player hulls - for( i = 0; i < MAX_MAP_HULLS * 3; i++ ) + if( proto == PROTO_GOLDSRC ) { - host.player_mins[i/3][i%3] = MSG_ReadChar( msg ); - host.player_maxs[i/3][i%3] = MSG_ReadChar( msg ); + byte clientdllmd5[16]; + const char *s; + + MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 )); + cl.maxclients = MSG_ReadByte( msg ); + cl.playernum = MSG_ReadByte( msg ); + COM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff ); + + MSG_SeekToBit( msg, sizeof( uint8_t ) << 3, SEEK_CUR ); // quake leftover, coop flag + + Q_strncpy( gamefolder, MSG_ReadString( msg ), sizeof( gamefolder )); + Con_Printf( "Remote host: %s\n", MSG_ReadString( msg )); + Q_strncpy( clgame.mapname, COM_FileWithoutPath( MSG_ReadString( msg )), sizeof( clgame.mapname )); + COM_StripExtension( clgame.mapname ); + + s = MSG_ReadString( msg ); + if( COM_CheckStringEmpty( s )) + Con_Printf( "Server map cycle: %s\n", s ); // VALVEWHY? + + if( MSG_ReadByte( msg )) + Con_Printf( "Uh, server says it's VAC2 secured.\n" ); + + background = false; + clgame.maxEntities = GI->max_edicts + (( cl.maxclients - 1 ) * 15 ); + clgame.maxEntities = bound( MIN_LEGACY_EDICTS, clgame.maxEntities, MAX_GOLDSRC_EDICTS ); + clgame.maxModels = 512; // ??? + Q_strncpy( clgame.maptitle, clgame.mapname, sizeof( clgame.maptitle )); + + Host_ValidateEngineFeatures( 0, 0 ); + } + else + { + uint32_t mask; + + cl.playernum = MSG_ReadByte( msg ); + cl.maxclients = MSG_ReadByte( msg ); + clgame.maxEntities = MSG_ReadWord( msg ); + if( proto == PROTO_LEGACY ) + { + clgame.maxEntities = bound( MIN_LEGACY_EDICTS, clgame.maxEntities, MAX_LEGACY_EDICTS ); + clgame.maxModels = 512; // ??? + mask = ENGINE_LEGACY_FEATURES_MASK; + } + else + { + clgame.maxEntities = bound( MIN_EDICTS, clgame.maxEntities, MAX_EDICTS ); + clgame.maxModels = MSG_ReadWord( msg ); + mask = ENGINE_FEATURES_MASK; + } + Q_strncpy( clgame.mapname, MSG_ReadString( msg ), sizeof( clgame.mapname )); + Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), sizeof( clgame.maptitle )); + background = MSG_ReadOneBit( msg ); + Q_strncpy( gamefolder, MSG_ReadString( msg ), sizeof( gamefolder )); + Host_ValidateEngineFeatures( mask, MSG_ReadDword( msg )); + + if( proto != PROTO_LEGACY ) + { + // receive the player hulls + for( i = 0; i < MAX_MAP_HULLS * 3; i++ ) + { + host.player_mins[i/3][i%3] = MSG_ReadChar( msg ); + host.player_maxs[i/3][i%3] = MSG_ReadChar( msg ); + } + } + } + + Q_snprintf( mapfile, sizeof( mapfile ), "maps/%s.bsp", clgame.mapname ); + if( CRC32_MapFile( &cl.worldmapCRC, mapfile, cl.maxclients > 1 )) + { + // validate map checksum + if( cl.worldmapCRC != cl.checksum ) + { + Con_Printf( S_ERROR "Your map [%s] differs from the server's.\n", clgame.mapname ); + CL_Disconnect_f(); // for local game, call EndGame + Host_AbortCurrentFrame(); // to avoid svc_bad + } } if( clgame.maxModels > MAX_MODELS ) Con_Printf( S_WARN "server model limit is above client model limit %i > %i\n", clgame.maxModels, MAX_MODELS ); - // Re-init hud video, especially if we changed game directories - clgame.dllFuncs.pfnVidInit(); - if( Con_FixedFont( )) { // seperate the printfs so the server message can have a color @@ -907,18 +998,14 @@ void CL_ParseServerData( sizebuf_t *msg ) // loading user settings CSCR_LoadDefaultCVars( "user.scr" ); - if( r_decals->value > mp_decals.value ) - Cvar_SetValue( "r_decals", mp_decals.value ); + if( r_decals.value > mp_decals.value ) + Cvar_DirectSet( &r_decals, mp_decals.string ); } - else Cvar_Reset( "r_decals" ); + else Cvar_DirectSet( &r_decals, NULL ); // set the background state if( cls.demoplayback && ( cls.demonum != -1 )) - { - // re-init mouse - host.mouse_visible = false; cl.background = true; - } else cl.background = background; if( cl.background ) // tell the game parts about background state @@ -946,7 +1033,7 @@ void CL_ParseServerData( sizebuf_t *msg ) Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle )); if( !cls.changelevel && !cls.changedemo ) - CL_InitEdicts (); // re-arrange edicts + CL_InitEdicts( cl.maxclients ); // re-arrange edicts // get splash name if( cls.demoplayback && ( cls.demonum != -1 )) @@ -954,9 +1041,9 @@ void CL_ParseServerData( sizebuf_t *msg ) else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" )); Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar - if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + if(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background ) { - if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true )) + if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name.string ), true )) Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime) } @@ -966,7 +1053,14 @@ void CL_ParseServerData( sizebuf_t *msg ) CL_CreateCustomizationList(); // request resources from server - CL_ServerCommand( true, "sendres %i\n", cl.servercount ); + if( proto == PROTO_GOLDSRC ) + { + CL_ServerCommand( true, "sendres" ); + } + else if( proto != PROTO_LEGACY ) + { + CL_ServerCommand( true, "sendres %i\n", cl.servercount ); + } memset( &clgame.movevars, 0, sizeof( clgame.movevars )); memset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars )); @@ -980,7 +1074,7 @@ void CL_ParseServerData( sizebuf_t *msg ) CL_ParseClientData =================== */ -void CL_ParseClientData( sizebuf_t *msg ) +void CL_ParseClientData( sizebuf_t *msg, connprotocol_t proto ) { float parsecounttime; int i, j, command_ack; @@ -1114,7 +1208,9 @@ void CL_ParseClientData( sizebuf_t *msg ) from_wd = nullwd; } - MSG_ReadClientData( msg, from_cd, to_cd, cl.mtime[0] ); + if( proto == PROTO_GOLDSRC ) + Delta_ReadGSFields( msg, DT_CLIENTDATA_T, from_cd, to_cd, cl.mtime[0] ); + else MSG_ReadClientData( msg, from_cd, to_cd, cl.mtime[0] ); for( i = 0; i < 64; i++ ) { @@ -1122,9 +1218,11 @@ void CL_ParseClientData( sizebuf_t *msg ) if( !MSG_ReadOneBit( msg )) break; // read the weapon idx - idx = MSG_ReadUBitLong( msg, cls.legacymode ? MAX_LEGACY_WEAPON_BITS : MAX_WEAPON_BITS ); + idx = MSG_ReadUBitLong( msg, proto == PROTO_LEGACY ? MAX_LEGACY_WEAPON_BITS : MAX_WEAPON_BITS ); - MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], cl.mtime[0] ); + if( proto == PROTO_GOLDSRC ) + Delta_ReadGSFields( msg, DT_WEAPONDATA_T, &from_wd[idx], &to_wd[idx], cl.mtime[0] ); + else MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], cl.mtime[0] ); } // make a local copy of physinfo @@ -1141,39 +1239,80 @@ void CL_ParseClientData( sizebuf_t *msg ) CL_ParseBaseline ================== */ -void CL_ParseBaseline( sizebuf_t *msg ) +void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto ) { - int i, newnum; - entity_state_t nullstate; - qboolean player; - cl_entity_t *ent; + const entity_state_t nullstate = { 0 }; Delta_InitClient (); // finalize client delta's - memset( &nullstate, 0, sizeof( nullstate )); - while( 1 ) { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - if( newnum == LAST_EDICT ) break; // end of baselines + cl_entity_t *ent; + qboolean player; + int newnum; + + if( proto == PROTO_LEGACY ) + { + newnum = MSG_ReadWord( msg ); + } + else if( proto == PROTO_GOLDSRC ) + { + uint value = MSG_ReadWord( msg ); + + if( value == 0xffff ) break; // end of baselines + + MSG_SeekToBit( msg, -16, SEEK_CUR ); + newnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS ); + } + else + { + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + if( newnum == LAST_EDICT ) break; // end of baselines + } + player = CL_IsPlayerIndex( newnum ); if( newnum >= clgame.maxEntities ) - Host_Error( "CL_AllocEdict: no free edicts\n" ); + Host_Error( "%s: no free edicts\n", __func__ ); ent = CL_EDICT_NUM( newnum ); - memset( &ent->prevstate, 0, sizeof( ent->prevstate )); + ent->prevstate = nullstate; ent->index = newnum; - MSG_ReadDeltaEntity( msg, &ent->prevstate, &ent->baseline, newnum, player, 1.0f ); + if( proto == PROTO_GOLDSRC ) + { + int type = MSG_ReadUBitLong( msg, 2 ); + int delta_type; + + if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T; + else if( type != ENTITY_NORMAL ) delta_type = DT_CUSTOM_ENTITY_STATE_T; + else delta_type = DT_ENTITY_STATE_T; + + Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f ); + ent->baseline.entityType = type; + } + else MSG_ReadDeltaEntity( msg, &nullstate, &ent->baseline, newnum, player, 1.0f ); + + if( proto == PROTO_LEGACY ) + break; // only one baseline allowed in legacy protocol } - cl.instanced_baseline_count = MSG_ReadUBitLong( msg, 6 ); - - for( i = 0; i < cl.instanced_baseline_count; i++ ) + if( proto != PROTO_LEGACY ) { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - MSG_ReadDeltaEntity( msg, &nullstate, &cl.instanced_baseline[i], newnum, false, 1.0f ); + int i; + + cl.instanced_baseline_count = MSG_ReadUBitLong( msg, 6 ); + + for( i = 0; i < cl.instanced_baseline_count; i++ ) + { + if( proto == PROTO_GOLDSRC ) + Delta_ReadGSFields( msg, DT_ENTITY_STATE_T, &nullstate, &cl.instanced_baseline[i], 1.0f ); + else + { + int newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + MSG_ReadDeltaEntity( msg, &nullstate, &cl.instanced_baseline[i], newnum, false, 1.0f ); + } + } } } @@ -1182,15 +1321,16 @@ void CL_ParseBaseline( sizebuf_t *msg ) CL_ParseLightStyle ================ */ -void CL_ParseLightStyle( sizebuf_t *msg ) +void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto ) { int style; const char *s; - float f; + float f = cl.mtime[0]; style = MSG_ReadByte( msg ); s = MSG_ReadString( msg ); - f = MSG_ReadFloat( msg ); + if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE ) + f = MSG_ReadFloat( msg ); CL_SetLightstyle( style, s, f ); } @@ -1204,9 +1344,7 @@ set the view angle to this absolute value */ void CL_ParseSetAngle( sizebuf_t *msg ) { - cl.viewangles[0] = MSG_ReadBitAngle( msg, 16 ); - cl.viewangles[1] = MSG_ReadBitAngle( msg, 16 ); - cl.viewangles[2] = MSG_ReadBitAngle( msg, 16 ); + MSG_ReadVec3Angles( msg, cl.viewangles ); } /* @@ -1286,30 +1424,33 @@ CL_RegisterUserMessage register new user message or update existing ================ */ -void CL_RegisterUserMessage( sizebuf_t *msg ) +void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto ) { - char *pszName; - int svc_num, size, bits; + char *pszName; + char szName[17]; + int size; + int svc_num = MSG_ReadByte( msg ); - svc_num = MSG_ReadByte( msg ); - - if( cls.legacymode ) + if( proto == PROTO_LEGACY || proto == PROTO_GOLDSRC ) { size = MSG_ReadByte( msg ); - bits = 8; + if( size == UINT8_MAX ) + size = -1; } else { size = MSG_ReadWord( msg ); - bits = 16; + if( size == UINT16_MAX ) + size = -1; } - pszName = MSG_ReadString( msg ); - - // important stuff - if( size == ( BIT( bits ) - 1 ) ) - size = -1; - svc_num = bound( 0, svc_num, 255 ); + if( proto == PROTO_GOLDSRC ) + { + MSG_ReadBytes( msg, szName, sizeof( szName ) - 1 ); + szName[16] = 0; + pszName = szName; + } + else pszName = MSG_ReadString( msg ); CL_LinkUserMessage( pszName, svc_num, size ); } @@ -1321,20 +1462,35 @@ CL_UpdateUserinfo collect userinfo from all players ================ */ -void CL_UpdateUserinfo( sizebuf_t *msg ) +void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto ) { int slot, id; qboolean active; player_info_t *player; - slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); + if( proto == PROTO_GOLDSRC ) + { + slot = MSG_ReadByte( msg ); + id = MSG_ReadLong( msg ); + active = true; + } + else if( proto == PROTO_LEGACY ) + { + slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); + id = 0; // bogus + active = MSG_ReadOneBit( msg ) ? true : false; + } + else + { + slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); + id = MSG_ReadLong( msg ); // unique user ID + active = MSG_ReadOneBit( msg ) ? true : false; + } if( slot >= MAX_CLIENTS ) - Host_Error( "CL_ParseServerMessage: svc_updateuserinfo >= MAX_CLIENTS\n" ); + Host_Error( "%s: svc_updateuserinfo >= MAX_CLIENTS\n", __func__ ); - id = MSG_ReadLong( msg ); // unique user ID player = &cl.players[slot]; - active = MSG_ReadOneBit( msg ) ? true : false; if( active ) { @@ -1344,16 +1500,27 @@ void CL_UpdateUserinfo( sizebuf_t *msg ) player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" )); - MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey )); + if( proto != PROTO_LEGACY ) + MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey )); - if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t )); + if( proto == PROTO_GOLDSRC && ( !COM_CheckStringEmpty( player->userinfo ) || !COM_CheckStringEmpty( player->name ))) + active = false; + + if( active && slot == cl.playernum ) + gameui.playerinfo = *player; } - else + + + if( !active ) { COM_ClearCustomizationList( &player->customdata, true ); memset( player, 0, sizeof( *player )); } + + // in GoldSrc userinfo might be empty but userid is always sent as separate value + // thus avoids clean up even after client disconnect + player->userid = id; } /* @@ -1423,17 +1590,23 @@ collect pings and packet lossage from clients */ void CL_UpdateUserPings( sizebuf_t *msg ) { - int i, slot; - player_info_t *player; - - for( i = 0; i < MAX_CLIENTS; i++ ) + // a1ba: there was a MAX_PLAYERS check but it doesn't make sense + // because pings message always ends by null bit + while( 1 ) { - if( !MSG_ReadOneBit( msg )) break; // end of message + int slot; + player_info_t *player; + + if( !MSG_ReadOneBit( msg )) + break; // end of message slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); - if( slot >= MAX_CLIENTS ) - Host_Error( "CL_ParseServerMessage: svc_pings > MAX_CLIENTS\n" ); + if( unlikely( slot >= MAX_CLIENTS )) + { + Host_Error( "%s: svc_pings > MAX_CLIENTS\n", __func__ ); + return; + } player = &cl.players[slot]; player->ping = MSG_ReadUBitLong( msg, 12 ); @@ -1441,24 +1614,51 @@ void CL_UpdateUserPings( sizebuf_t *msg ) } } -void CL_SendConsistencyInfo( sizebuf_t *msg ) +static const char *CL_CheckTypeToString( int check_type ) +{ + // renamed so they have same width and look better in console output + switch( check_type ) + { + case force_exactfile: + return "exactfile"; + case force_model_samebounds: + return "samebounds"; + case force_model_specifybounds: + return "specbounds"; + case force_model_specifybounds_if_avail: + return "specbounds2"; + } + return "unknown"; +} + +static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto ) { qboolean user_changed_diskfile; vec3_t mins, maxs; string filename; CRC32_t crcFile; - byte md5[16]; + byte md5[16] = { 0 }; consistency_t *pc; - int i; + int i, pos; if( !cl.need_force_consistency_response ) return; cl.need_force_consistency_response = false; MSG_BeginClientCmd( msg, clc_fileconsistency ); + pos = MSG_GetNumBytesWritten( msg ); + if( proto == PROTO_GOLDSRC ) + { + MSG_WriteShort( msg, 0 ); + MSG_StartBitWriting( msg ); + } + + FS_AllowDirectPaths( true ); for( i = 0; i < cl.num_consistency; i++ ) { + qboolean have_file = true; + pc = &cl.consistency_list[i]; user_changed_diskfile = false; @@ -1469,7 +1669,10 @@ void CL_SendConsistencyInfo( sizebuf_t *msg ) Q_snprintf( filename, sizeof( filename ), DEFAULT_SOUNDPATH "%s", pc->filename ); else Q_strncpy( filename, pc->filename, sizeof( filename )); - if( Q_strstr( filename, "models/" )) + COM_FixSlashes( filename ); + have_file = FS_FileExists( filename, false ); + + if( Q_strstr( filename, "models/" ) && have_file ) { CRC32_Init( &crcFile ); CRC32_File( &crcFile, filename ); @@ -1480,19 +1683,47 @@ void CL_SendConsistencyInfo( sizebuf_t *msg ) switch( pc->check_type ) { case force_exactfile: + // servers rely on md5 not being initialized after previous file + // if current file doesn't exist MD5_HashFile( md5, filename, NULL ); - pc->value = *(int *)md5; + memcpy( &pc->value, md5, sizeof( pc->value )); + LittleLongSW( pc->value ); if( user_changed_diskfile ) MSG_WriteUBitLong( msg, 0, 32 ); else MSG_WriteUBitLong( msg, pc->value, 32 ); break; + + case force_model_specifybounds_if_avail: + if( have_file ) + { + if( !Mod_GetStudioBounds( filename, mins, maxs )) + Host_Error( "unable to find %s\n", filename ); + + if( user_changed_diskfile ) + { + VectorSet( mins, -9999.9f, -9999.9f, -9999.9f ); + VectorSet( maxs, 9999.9f, 9999.9f, 9999.9f ); + } + } + else + { + VectorSet( mins, -1.0f, -1.0f, -1.0f ); + VectorCopy( mins, maxs ); + } + + MSG_WriteBytes( msg, mins, 12 ); + MSG_WriteBytes( msg, maxs, 12 ); + break; case force_model_samebounds: case force_model_specifybounds: if( !Mod_GetStudioBounds( filename, mins, maxs )) Host_Error( "unable to find %s\n", filename ); if( user_changed_diskfile ) - ClearBounds( maxs, mins ); // g-cont. especially swapped + { + VectorSet( mins, -9999.9f, -9999.9f, -9999.9f ); + VectorSet( maxs, 9999.9f, 9999.9f, 9999.9f ); + } MSG_WriteBytes( msg, mins, 12 ); MSG_WriteBytes( msg, maxs, 12 ); break; @@ -1502,7 +1733,56 @@ void CL_SendConsistencyInfo( sizebuf_t *msg ) } } + FS_AllowDirectPaths( false ); + MSG_WriteOneBit( msg, 0 ); + + if( proto == PROTO_GOLDSRC ) + { + int len; + MSG_EndBitWriting( msg ); + + len = MSG_GetNumBytesWritten( msg ) - pos - 2; + *(short *)&msg->pData[pos] = len; + + COM_Munge( &msg->pData[pos + 2], len, cl.servercount ); + } +} + +/* +================== +CL_StartDark +================== +*/ +static void CL_StartDark( void ) +{ + if( v_dark.value ) + { + screenfade_t *sf = &clgame.fade; + float fadetime = 5.0f; + client_textmessage_t *title; + + title = CL_TextMessageGet( "GAMETITLE" ); + if( Host_IsQuakeCompatible( )) + fadetime = 1.0f; + + if( title ) + { + // get settings from titles.txt + sf->fadeEnd = title->holdtime + title->fadeout; + sf->fadeReset = title->fadeout; + } + else sf->fadeEnd = sf->fadeReset = fadetime; + + sf->fadeFlags = FFADE_IN; + sf->fader = sf->fadeg = sf->fadeb = 0; + sf->fadealpha = 255; + sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; + sf->fadeReset += cl.time; + sf->fadeEnd += sf->fadeReset; + + Cvar_DirectSet( &v_dark, "0" ); + } } /* @@ -1512,7 +1792,7 @@ CL_RegisterResources Clean up and move to next part of sequence. ================== */ -void CL_RegisterResources( sizebuf_t *msg ) +void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto ) { model_t *mod; int i; @@ -1524,7 +1804,7 @@ void CL_RegisterResources( sizebuf_t *msg ) } if( !cls.demoplayback ) - CL_SendConsistencyInfo( msg ); + CL_SendConsistencyInfo( msg, proto ); // All done precaching. cl.worldmodel = CL_ModelHandle( 1 ); // get world pointer @@ -1548,12 +1828,15 @@ void CL_RegisterResources( sizebuf_t *msg ) CL_ClearWorld (); - // update the ref state. - R_UpdateRefState (); + // load skybox + R_SetupSky( clgame.movevars.skyName ); // tell rendering system we have a new set of models. ref.dllFuncs.R_NewMap (); + // check if this map must start from dark screen + CL_StartDark (); + CL_SetupOverviewParams(); // release unused SpriteTextures @@ -1571,7 +1854,13 @@ void CL_RegisterResources( sizebuf_t *msg ) // done with all resources, issue prespawn command. // Include server count in case server disconnects and changes level during d/l MSG_BeginClientCmd( msg, clc_stringcmd ); - MSG_WriteString( msg, va( "spawn %i", cl.servercount )); + if( proto == PROTO_GOLDSRC ) + { + int32_t crc = cl.worldmapCRC; + COM_Munge2((byte*)&crc, sizeof( crc ), ( 0xff - cl.servercount ) & 0xff ); + MSG_WriteStringf( msg, "spawn %i %i", cl.servercount, crc ); + } + else MSG_WriteStringf( msg, "spawn %i", cl.servercount ); } } else @@ -1581,7 +1870,7 @@ void CL_RegisterResources( sizebuf_t *msg ) } } -void CL_ParseConsistencyInfo( sizebuf_t *msg ) +static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto ) { int lastcheck; int delta; @@ -1601,6 +1890,15 @@ void CL_ParseConsistencyInfo( sizebuf_t *msg ) if( !cl.need_force_consistency_response ) return; + if( !pResource ) + { + Host_Error( "%s: malformed consistency info packet (resources needed is NULL)\n", __func__ ); + return; + } + + if( cl_trace_consistency.value ) + Con_Printf( "Server wants consistency of the following resources:\n" ); + skip_crc_change = NULL; lastcheck = 0; @@ -1609,7 +1907,7 @@ void CL_ParseConsistencyInfo( sizebuf_t *msg ) isdelta = MSG_ReadOneBit( msg ); if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck; - else delta = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); + else delta = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_MODEL_BITS : MAX_MODEL_BITS ); skip = delta - lastcheck; @@ -1618,22 +1916,35 @@ void CL_ParseConsistencyInfo( sizebuf_t *msg ) if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" )) Mod_NeedCRC( pResource->szFileName, false ); pResource = pResource->pNext; + + if( !pResource ) + { + Host_Error( "%s: malformed consistency info packet (last check %d, delta %d, position %d)\n", __func__, lastcheck, delta, i ); + return; + } } if( cl.num_consistency >= MAX_MODELS ) - Host_Error( "CL_CheckConsistency: MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); + Host_Error( "%s: MAX_MODELS limit exceeded (%d)\n", __func__, MAX_MODELS ); pc = &cl.consistency_list[cl.num_consistency]; cl.num_consistency++; - memset( pc, 0, sizeof( consistency_t )); + memset( pc, 0, sizeof( *pc )); pc->filename = pResource->szFileName; pc->issound = (pResource->type == t_sound); pc->orig_index = delta; pc->value = 0; if( pResource->type == t_model && memcmp( nullbuffer, pResource->rguc_reserved, 32 )) + { + if( proto == PROTO_GOLDSRC ) + COM_UnMunge( pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), cl.servercount ); pc->check_type = pResource->rguc_reserved[0]; + } + + if( cl_trace_consistency.value ) + Con_Printf( "%s\t%s\t%s\n", COM_ResourceTypeFromIndex( pResource->type ), CL_CheckTypeToString( pc->check_type ), pc->filename ); skip_crc_change = pResource; lastcheck = delta; @@ -1646,12 +1957,12 @@ CL_ParseResourceList ============== */ -void CL_ParseResourceList( sizebuf_t *msg ) +void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto ) { resource_t *pResource; int i, total; - total = MSG_ReadUBitLong( msg, MAX_RESOURCE_BITS ); + total = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS ); for( i = 0; i < total; i++ ) { @@ -1660,7 +1971,7 @@ void CL_ParseResourceList( sizebuf_t *msg ) Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName )); pResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); - pResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 ); + pResource->nDownloadSize = MSG_ReadBitLong( msg, 24, proto != PROTO_GOLDSRC ); pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING; if( FBitSet( pResource->ucFlags, RES_CUSTOM )) @@ -1672,7 +1983,7 @@ void CL_ParseResourceList( sizebuf_t *msg ) CL_AddToResourceList( pResource, &cl.resourcesneeded ); } - CL_ParseConsistencyInfo( msg ); + CL_ParseConsistencyInfo( msg, proto ); CL_StartResourceDownloading( "Verifying and downloading resources...\n", false ); } @@ -1688,7 +1999,7 @@ void CL_ParseVoiceInit( sizebuf_t *msg ) char *pszCodec = MSG_ReadString( msg ); int quality = MSG_ReadByte( msg ); - Voice_Init( pszCodec, quality ); + Voice_Init( pszCodec, quality, false ); // init requested codec and the device } /* @@ -1697,32 +2008,47 @@ CL_ParseVoiceData ================== */ -void CL_ParseVoiceData( sizebuf_t *msg ) +void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto ) { - int size, idx, frames; - byte received[8192]; + int size, idx, frames = 0; + byte received[VOICE_MAX_DATA_SIZE]; idx = MSG_ReadByte( msg ) + 1; - frames = MSG_ReadByte( msg ); - - size = MSG_ReadShort( msg ); - size = Q_min( size, sizeof( received )); - - MSG_ReadBytes( msg, received, size ); - if ( idx <= 0 || idx > cl.maxclients ) return; - // must notify through as both local player and normal client + // must notify client.dll about server ack'ing our local client voice data if( idx == cl.playernum + 1 ) - Voice_StatusAck( &voice.local, VOICE_LOOPBACK_INDEX ); + Voice_LoopbackAck(); - Voice_StatusAck( &voice.players_status[idx], idx ); + if( proto == PROTO_GOLDSRC ) + { + size = MSG_ReadShort( msg ); + if( size > VOICE_MAX_GS_DATA_SIZE ) + { + Con_Printf( S_ERROR "Voice data size is too large: %d bytes (max: %d)\n", size, VOICE_MAX_GS_DATA_SIZE ); + return; + } + } + else + { + frames = MSG_ReadByte( msg ); + size = MSG_ReadShort( msg ); + if( size > VOICE_MAX_DATA_SIZE ) + { + Con_Printf( S_ERROR "Voice data size is too large: %d bytes (max: %d)\n", size, VOICE_MAX_DATA_SIZE ); + return; + } + } + + size = Q_min( size, sizeof( received )); if ( !size ) return; + MSG_ReadBytes( msg, received, size ); + Voice_AddIncomingData( idx, received, size, frames ); } @@ -1743,13 +2069,10 @@ void CL_ParseResLocation( sizebuf_t *msg ) return; } - while( ( data = COM_ParseFile( data, token, sizeof( token ) ) ) ) + while(( data = COM_ParseFile( data, token, sizeof( token )))) { Con_Reportf( "Adding %s as download location\n", token ); - - if( !cl.downloadUrl[0] ) - Q_strncpy( cl.downloadUrl, token, sizeof( token ) ); - + cl.http_download = true; HTTP_AddCustomServer( token ); } } @@ -1780,8 +2103,12 @@ void CL_ParseHLTV( sizebuf_t *msg ) break; case HLTV_LISTEN: cls.signon = SIGNONS; +#if 1 + MSG_ReadString( msg ); +#else NET_StringToAdr( MSG_ReadString( msg ), &cls.hltv_listen_address ); -// NET_JoinGroup( cls.netchan.sock, cls.hltv_listen_address ); + NET_JoinGroup( cls.netchan.sock, cls.hltv_listen_address ); +#endif SCR_EndLoadingPlaque(); break; default: @@ -1814,12 +2141,19 @@ CL_ParseScreenShake Set screen shake ============== */ -void CL_ParseScreenShake( sizebuf_t *msg ) +static void CL_ParseScreenShake( sizebuf_t *msg ) { - clgame.shake.amplitude = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); - clgame.shake.duration = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); - clgame.shake.frequency = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<8)); - clgame.shake.time = cl.time + Q_max( clgame.shake.duration, 0.01f ); + float amplitude = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 )); + float duration = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 )); + float frequency = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 8 )); + + // don't overwrite larger existing shake + if( amplitude > clgame.shake.amplitude ) + clgame.shake.amplitude = amplitude; + + clgame.shake.duration = duration; + clgame.shake.time = cl.time + clgame.shake.duration; + clgame.shake.frequency = frequency; clgame.shake.next_shake = 0.0f; // apply immediately } @@ -1830,7 +2164,7 @@ CL_ParseScreenFade Set screen fade ============== */ -void CL_ParseScreenFade( sizebuf_t *msg ) +static void CL_ParseScreenFade( sizebuf_t *msg ) { float duration, holdTime; screenfade_t *sf = &clgame.fade; @@ -1839,7 +2173,7 @@ void CL_ParseScreenFade( sizebuf_t *msg ) duration = (float)MSG_ReadWord( msg ); holdTime = (float)MSG_ReadWord( msg ); sf->fadeFlags = MSG_ReadShort( msg ); - flScale = ( sf->fadeFlags & FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f); + flScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f); sf->fader = MSG_ReadByte( msg ); sf->fadeg = MSG_ReadByte( msg ); @@ -1852,7 +2186,7 @@ void CL_ParseScreenFade( sizebuf_t *msg ) // calc fade speed if( duration > 0 ) { - if( sf->fadeFlags & FFADE_OUT ) + if( FBitSet( sf->fadeFlags, FFADE_OUT )) { if( sf->fadeEnd ) { @@ -1860,6 +2194,7 @@ void CL_ParseScreenFade( sizebuf_t *msg ) } sf->fadeEnd += cl.time; + sf->fadeTotalEnd = sf->fadeEnd; sf->fadeReset += sf->fadeEnd; } else @@ -1883,9 +2218,9 @@ Find the client cvar value and sent it back to the server ============== */ -void CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext ) +void CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext, const connprotocol_t proto ) { - const char *cvarName, *response; + const char *cvarName, *response = NULL; convar_t *cvar; int requestID; @@ -1893,30 +2228,51 @@ void CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext ) requestID = MSG_ReadLong( msg ); cvarName = MSG_ReadString( msg ); - cvar = Cvar_FindVar( cvarName ); - if( cvar ) + if( proto == PROTO_GOLDSRC ) { - if( cvar->flags & FCVAR_PRIVILEGED ) - response = "CVAR is privileged"; - else if( cvar->flags & FCVAR_SERVER ) - response = "CVAR is server-only"; - else if( cvar->flags & FCVAR_PROTECTED ) - response = "CVAR is protected"; - else - response = cvar->string; + if( !Q_stricmp( cvarName, "sv_version" )) + response = "1.1.2.2/Stdio,48,10211"; + } + + if( !response ) + { + cvar = Cvar_FindVar( cvarName ); + + if( cvar ) + { + if( cvar->flags & FCVAR_PRIVILEGED ) + response = "CVAR is privileged"; + else if( cvar->flags & FCVAR_SERVER ) + response = "CVAR is server-only"; + else if( cvar->flags & FCVAR_PROTECTED ) + response = "CVAR is protected"; + else + response = cvar->string; + } + else if( proto == PROTO_LEGACY ) + { + response = "Not Found"; + } + else + { + response = "Bad CVAR request"; + } } - else response = "Bad CVAR request"; if( ext ) { - MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue2 ); + int clc_msg = proto == PROTO_GOLDSRC ? clc_goldsrc_requestcvarvalue2 : clc_requestcvarvalue2; + + MSG_BeginClientCmd( &cls.netchan.message, clc_msg ); MSG_WriteLong( &cls.netchan.message, requestID ); MSG_WriteString( &cls.netchan.message, cvarName ); } else { - MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue ); + int clc_msg = proto == PROTO_GOLDSRC ? clc_goldsrc_requestcvarvalue : clc_requestcvarvalue; + + MSG_BeginClientCmd( &cls.netchan.message, clc_msg ); } MSG_WriteString( &cls.netchan.message, response ); } @@ -1961,10 +2317,10 @@ void CL_ParseExec( sizebuf_t *msg ) { Cbuf_AddText( "exec mapdefault.cfg\n" ); - COM_FileBase( clgame.mapname, mapname ); + COM_FileBase( clgame.mapname, mapname, sizeof( mapname )); if ( COM_CheckString( mapname ) ) - Cbuf_AddText( va( "exec %s.cfg\n", mapname ) ); + Cbuf_AddTextf( "exec %s.cfg\n", mapname ); } } @@ -2014,7 +2370,7 @@ CL_ParseUserMessage handles all user messages ============== */ -void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) +void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto ) { byte pbuf[MAX_USERMSG_LENGTH]; int i, iSize; @@ -2023,7 +2379,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) if( svc_num <= svc_lastmsg || svc_num > ( MAX_USER_MESSAGES + svc_lastmsg )) { // out or range - Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num ); + Host_Error( "%s: illegible server message %d\n", __func__, svc_num ); return; } @@ -2035,7 +2391,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) } if( i == MAX_USER_MESSAGES ) // probably unregistered - Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num ); + Host_Error( "%s: illegible server message %d\n", __func__, svc_num ); // NOTE: some user messages handled into engine if( !Q_strcmp( clgame.msg[i].name, "ScreenShake" )) @@ -2054,21 +2410,21 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) // message with variable sizes receive an actual size as first byte if( iSize == -1 ) { - if( cls.legacymode ) + if( proto == PROTO_GOLDSRC || proto == PROTO_LEGACY ) iSize = MSG_ReadByte( msg ); else iSize = MSG_ReadWord( msg ); } if( iSize >= MAX_USERMSG_LENGTH ) { - Msg("WTF??? %d %d\n", i, svc_num ); + Con_Reportf( "%s: user message %s size limit hit (%d > %d)!\n", __func__, clgame.msg[i].name, iSize, MAX_USERMSG_LENGTH ); return; } // parse user message into buffer MSG_ReadBytes( msg, pbuf, iSize ); - if( cl_trace_messages->value ) + if( cl_trace_messages.value ) { Con_Reportf( "^3USERMSG %s SIZE %i SVC_NUM %i\n", clgame.msg[i].name, iSize, clgame.msg[i].number ); @@ -2090,7 +2446,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) } else { - Con_DPrintf( S_ERROR "UserMsg: No pfn %s %d\n", clgame.msg[i].name, clgame.msg[i].number ); + Con_DPrintf( S_ERROR "%s: No pfn %s %d\n", __func__, clgame.msg[i].name, clgame.msg[i].number ); clgame.msg[i].func = CL_UserMsgStub; // throw warning only once } } @@ -2102,6 +2458,54 @@ ACTION MESSAGES ===================================================================== */ + +/* +============ +CL_ParseCommonDLLMessage + +parse a message which structure is enforced by DLL compatibility +it should always be the same regardless of protocol used +============ +*/ +qboolean CL_ParseCommonDLLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset ) +{ + int param1, param2; + + switch( svc_num ) + { + case svc_temp_entity: + CL_ParseTempEntity( msg, proto ); // need protocol because message header differs + cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - startoffset; + break; + case svc_intermission: + cl.intermission = 1; + break; + case svc_cdtrack: + param1 = MSG_ReadByte( msg ); + param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum + param2 = MSG_ReadByte( msg ); + param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum + S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false ); + break; + case svc_weaponanim: + param1 = MSG_ReadByte( msg ); // iAnim + param2 = MSG_ReadByte( msg ); // body + CL_WeaponAnim( param1, param2 ); + break; + case svc_roomtype: + param1 = MSG_ReadShort( msg ); + Cvar_SetValue( "room_type", param1 ); + break; + case svc_director: + CL_ParseDirector( msg ); + break; + default: + return false; + } + + return true; +} + /* ===================== CL_ParseServerMessage @@ -2109,36 +2513,19 @@ CL_ParseServerMessage dispatch messages ===================== */ -void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) +void CL_ParseServerMessage( sizebuf_t *msg ) { size_t bufStart, playerbytes; - int cmd, param1, param2; + int cmd; int old_background; const char *s; - cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame - CL_Parse_Debug( true ); // begin parsing - - if( normal_message ) - { - // assume no entity/player update this packet - if( cls.state == ca_active ) - { - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; - } - else - { - CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); - } - } - // parse the message while( 1 ) { if( MSG_CheckOverflow( msg )) { - Host_Error( "CL_ParseServerMessage: overflow!\n" ); + Host_Error( "%s: overflow!\n", __func__ ); return; } @@ -2154,6 +2541,9 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) // record command for debugging spew on parse problem CL_Parse_RecordCommand( cmd, bufStart ); + if( CL_ParseCommonDLLMessage( msg, PROTO_CURRENT, cmd, bufStart )) + continue; + // other commands switch( cmd ) { @@ -2168,13 +2558,15 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) Host_AbortCurrentFrame (); break; case svc_event: - CL_ParseEvent( msg ); + CL_ParseEvent( msg, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_changing: old_background = cl.background; if( MSG_ReadOneBit( msg )) { + int maxclients = cl.maxclients; + cls.changelevel = true; S_StopAllSounds( true ); @@ -2186,8 +2578,8 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) cls.changedemo = true; } - CL_ClearState (); - CL_InitEdicts (); // re-arrange edicts + CL_ClearState(); + CL_InitEdicts( maxclients ); // re-arrange edicts } else Con_Printf( "Server disconnected, reconnecting\n" ); @@ -2204,6 +2596,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) else cls.state = ca_connecting; cl.background = old_background; cls.connect_time = MAX_HEARTBEAT; + cls.connect_retry = 0; } break; case svc_setview: @@ -2214,18 +2607,25 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_time: - CL_ParseServerTime( msg ); + CL_ParseServerTime( msg, PROTO_CURRENT ); break; case svc_print: Con_Printf( "%s", MSG_ReadString( msg )); break; case svc_stufftext: s = MSG_ReadString( msg ); + if( cl_trace_stufftext.value ) + { + size_t len = Q_strlen( s ); + Con_Printf( "Stufftext: %s%c", s, len && s[len-1] == '\n' ? '\0' : '\n' ); + } + #ifdef HACKS_RELATED_HLMODS // disable Cry Of Fear antisave protection if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) break; // too early #endif + Cbuf_AddFilteredText( s ); break; case svc_setangle: @@ -2233,19 +2633,19 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) break; case svc_serverdata: Cbuf_Execute(); // make sure any stuffed commands are done - CL_ParseServerData( msg ); + CL_ParseServerData( msg, PROTO_CURRENT ); break; case svc_lightstyle: - CL_ParseLightStyle( msg ); + CL_ParseLightStyle( msg, PROTO_CURRENT ); break; case svc_updateuserinfo: - CL_UpdateUserinfo( msg ); + CL_UpdateUserinfo( msg, PROTO_CURRENT ); break; case svc_deltatable: Delta_ParseTableField( msg ); break; case svc_clientdata: - CL_ParseClientData( msg ); + CL_ParseClientData( msg, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_resource: @@ -2255,7 +2655,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) CL_UpdateUserPings( msg ); break; case svc_particle: - CL_ParseParticles( msg ); + CL_ParseParticles( msg, PROTO_CURRENT ); break; case svc_restoresound: CL_ParseRestoreSoundPacket( msg ); @@ -2265,69 +2665,46 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) CL_ParseStaticEntity( msg ); break; case svc_event_reliable: - CL_ParseReliableEvent( msg ); + CL_ParseReliableEvent( msg, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_spawnbaseline: - CL_ParseBaseline( msg ); - break; - case svc_temp_entity: - CL_ParseTempEntity( msg ); - cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart; + CL_ParseBaseline( msg, PROTO_CURRENT ); break; case svc_setpause: cl.paused = ( MSG_ReadOneBit( msg ) != 0 ); break; case svc_signonnum: - CL_ParseSignon( msg ); + CL_ParseSignon( msg, PROTO_CURRENT ); break; case svc_centerprint: CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); break; - case svc_intermission: - cl.intermission = 1; - break; case svc_finale: CL_ParseFinaleCutscene( msg, 2 ); break; - case svc_cdtrack: - param1 = MSG_ReadByte( msg ); - param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum - param2 = MSG_ReadByte( msg ); - param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum - S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false ); - break; case svc_restore: CL_ParseRestore( msg ); break; case svc_cutscene: CL_ParseFinaleCutscene( msg, 3 ); break; - case svc_weaponanim: - param1 = MSG_ReadByte( msg ); // iAnim - param2 = MSG_ReadByte( msg ); // body - CL_WeaponAnim( param1, param2 ); - break; case svc_bspdecal: CL_ParseStaticDecal( msg ); break; - case svc_roomtype: - param1 = MSG_ReadShort( msg ); - Cvar_SetValue( "room_type", param1 ); - break; case svc_addangle: CL_ParseAddAngle( msg ); break; case svc_usermessage: - CL_RegisterUserMessage( msg ); + CL_RegisterUserMessage( msg, PROTO_CURRENT ); break; case svc_packetentities: - playerbytes = CL_ParsePacketEntities( msg, false ); + playerbytes = CL_ParsePacketEntities( msg, false, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.players += playerbytes; cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; break; case svc_deltapacketentities: - playerbytes = CL_ParsePacketEntities( msg, true ); + playerbytes = CL_ParsePacketEntities( msg, true, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.players += playerbytes; cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; break; @@ -2336,7 +2713,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; break; case svc_resourcelist: - CL_ParseResourceList( msg ); + CL_ParseResourceList( msg, PROTO_CURRENT ); break; case svc_deltamovevars: CL_ParseMovevars( msg ); @@ -2359,893 +2736,29 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) case svc_hltv: CL_ParseHLTV( msg ); break; - case svc_director: - CL_ParseDirector( msg ); - break; case svc_voiceinit: CL_ParseVoiceInit( msg ); break; case svc_voicedata: - CL_ParseVoiceData( msg ); + CL_ParseVoiceData( msg, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_resourcelocation: CL_ParseResLocation( msg ); break; case svc_querycvarvalue: - CL_ParseCvarValue( msg, false ); + CL_ParseCvarValue( msg, false, PROTO_CURRENT ); break; case svc_querycvarvalue2: - CL_ParseCvarValue( msg, true ); + CL_ParseCvarValue( msg, true, PROTO_CURRENT ); break; case svc_exec: CL_ParseExec( msg ); break; default: - CL_ParseUserMessage( msg, cmd ); + CL_ParseUserMessage( msg, cmd, PROTO_CURRENT ); cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; break; } } - - cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; - CL_Parse_Debug( false ); // done - - // we don't know if it is ok to save a demo message until - // after we have parsed the frame - if( !cls.demoplayback ) - { - if( cls.demorecording && !cls.demowaiting ) - { - CL_WriteDemoMessage( false, cls.starting_count, msg ); - } - else if( cls.state != ca_active ) - { - CL_WriteDemoMessage( true, cls.starting_count, msg ); - } - } -} - -/* -================== -CL_ParseBaseline -================== -*/ -void CL_LegacyParseBaseline( sizebuf_t *msg ) -{ - int i, newnum; - qboolean player; - cl_entity_t *ent; - - Delta_InitClient (); // finalize client delta's - - newnum = MSG_ReadWord( msg ); - player = CL_IsPlayerIndex( newnum ); - - if( newnum >= clgame.maxEntities ) - Host_Error( "CL_AllocEdict: no free edicts\n" ); - - ent = CL_EDICT_NUM( newnum ); - memset( &ent->prevstate, 0, sizeof( ent->prevstate )); - ent->index = newnum; - - MSG_ReadDeltaEntity( msg, &ent->prevstate, &ent->baseline, newnum, player, 1.0f ); - -} - -/* -================== -CL_ParseServerData -================== -*/ -void CL_ParseLegacyServerData( sizebuf_t *msg ) -{ - string gamefolder; - qboolean background; - int i; - - Con_Reportf( "Legacy serverdata packet received.\n" ); - - cls.timestart = Sys_DoubleTime(); - - cls.demowaiting = false; // server is changed - //clgame.load_sequence++; // now all hud sprites are invalid - - // wipe the client_t struct - if( !cls.changelevel && !cls.changedemo ) - CL_ClearState (); - cls.state = ca_connected; - - // parse protocol version number - i = MSG_ReadLong( msg ); - //cls.serverProtocol = i; - - if( i != 48 ) - Host_Error( "Server uses invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION ); - - cl.servercount = MSG_ReadLong( msg ); - cl.checksum = MSG_ReadLong( msg ); - cl.playernum = MSG_ReadByte( msg ); - cl.maxclients = MSG_ReadByte( msg ); - clgame.maxEntities = MSG_ReadWord( msg ); - clgame.maxEntities = bound( 30, clgame.maxEntities, 4096 ); - clgame.maxModels = 512; - Q_strncpy( clgame.mapname, MSG_ReadString( msg ), MAX_STRING ); - Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING ); - background = MSG_ReadOneBit( msg ); - Q_strncpy( gamefolder, MSG_ReadString( msg ), MAX_STRING ); - host.features = (uint)MSG_ReadLong( msg ); - - // Re-init hud video, especially if we changed game directories - clgame.dllFuncs.pfnVidInit(); - - if( Con_FixedFont( )) - { - // seperate the printfs so the server message can have a color - Con_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" ); - Con_Print( va( "%c%s\n\n", 2, clgame.maptitle )); - } - - // multiplayer game? - if( cl.maxclients > 1 ) - { - // allow console in multiplayer games - host.allow_console = true; - - // loading user settings - CSCR_LoadDefaultCVars( "user.scr" ); - - if( r_decals->value > mp_decals.value ) - Cvar_SetValue( "r_decals", mp_decals.value ); - } - else Cvar_Reset( "r_decals" ); - - // set the background state - if( cls.demoplayback && ( cls.demonum != -1 )) - { - // re-init mouse - host.mouse_visible = false; - cl.background = true; - } - else cl.background = background; - - if( cl.background ) // tell the game parts about background state - Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); - else Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); - - if( !cls.changelevel ) - { - // continue playing if we are changing level - S_StopBackgroundTrack (); - } - - if( !cls.changedemo ) - UI_SetActiveMenu( cl.background ); - else if( !cls.demoplayback ) - Key_SetKeyDest( key_menu ); - - // don't reset cursor in background mode - if( cl.background ) - IN_MouseRestorePos(); - - // will be changed later - cl.viewentity = cl.playernum + 1; - gameui.globals->maxClients = cl.maxclients; - Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle )); - - if( !cls.changelevel && !cls.changedemo ) - CL_InitEdicts (); // re-arrange edicts - - // get splash name - if( cls.demoplayback && ( cls.demonum != -1 )) - Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", cls.demoname, refState.wideScreen ? "16x9" : "4x3" )); - else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" )); - Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar - - if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) - { - if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true )) - Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen - cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime) - } - - for( i = 0; i < MAX_CLIENTS; i++ ) - COM_ClearCustomizationList( &cl.players[i].customdata, true ); - CL_CreateCustomizationList(); - - memset( &clgame.movevars, 0, sizeof( clgame.movevars )); - memset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars )); - memset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint )); - cl.video_prepped = false; - cl.audio_prepped = false; -} - -/* -================== -CL_ParseStaticEntity - -static client entity -================== -*/ -void CL_LegacyParseStaticEntity( sizebuf_t *msg ) -{ - int i; - entity_state_t state; - cl_entity_t *ent; - - memset( &state, 0, sizeof( state )); - state.modelindex = MSG_ReadShort( msg ); - state.sequence = MSG_ReadByte( msg ); - state.frame = MSG_ReadByte( msg ); - state.colormap = MSG_ReadWord( msg ); - state.skin = MSG_ReadByte( msg ); - - for( i = 0; i < 3; i++ ) - { - state.origin[i] = MSG_ReadCoord( msg ); - state.angles[i] = MSG_ReadBitAngle( msg, 16 ); - } - - state.rendermode = MSG_ReadByte( msg ); - - if( state.rendermode != kRenderNormal ) - { - state.renderamt = MSG_ReadByte( msg ); - state.rendercolor.r = MSG_ReadByte( msg ); - state.rendercolor.g = MSG_ReadByte( msg ); - state.rendercolor.b = MSG_ReadByte( msg ); - state.renderfx = MSG_ReadByte( msg ); - } - - i = clgame.numStatics; - if( i >= MAX_STATIC_ENTITIES ) - { - Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" ); - return; - } - - ent = &clgame.static_entities[i]; - clgame.numStatics++; - - // all states are same - ent->baseline = ent->curstate = ent->prevstate = state; - ent->index = 0; // static entities doesn't has the numbers - - // statics may be respawned in game e.g. for demo recording - if( cls.state == ca_connected || cls.state == ca_validate ) - ent->trivial_accept = INVALID_HANDLE; - - // setup the new static entity - VectorCopy( ent->curstate.origin, ent->origin ); - VectorCopy( ent->curstate.angles, ent->angles ); - ent->model = CL_ModelHandle( state.modelindex ); - ent->curstate.framerate = 1.0f; - CL_ResetLatchedVars( ent, true ); - - if( ent->curstate.rendermode == kRenderNormal && ent->model != NULL ) - { - // auto 'solid' faces - if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( )) - { - ent->curstate.rendermode = kRenderTransAlpha; - ent->curstate.renderamt = 255; - } - } - - R_AddEfrags( ent ); // add link -} - - - -void CL_LegacyParseSoundPacket( sizebuf_t *msg, qboolean is_ambient ) -{ - vec3_t pos; - int chan, sound; - float volume, attn; - int flags, pitch, entnum; - sound_t handle = 0; - - flags = MSG_ReadWord( msg ); - if( flags & SND_LEGACY_LARGE_INDEX ) - { - sound = MSG_ReadWord( msg ); - flags &= ~SND_LEGACY_LARGE_INDEX; - } - else - sound = MSG_ReadByte( msg ); - chan = MSG_ReadByte( msg ); - - if( FBitSet( flags, SND_VOLUME )) - volume = (float)MSG_ReadByte( msg ) / 255.0f; - else volume = VOL_NORM; - - if( FBitSet( flags, SND_ATTENUATION )) - attn = (float)MSG_ReadByte( msg ) / 64.0f; - else attn = ATTN_NONE; - - if( FBitSet( flags, SND_PITCH )) - pitch = MSG_ReadByte( msg ); - else pitch = PITCH_NORM; - - // entity reletive - entnum = MSG_ReadWord( msg ); - - // positioned in space - MSG_ReadVec3Coord( msg, pos ); - - if( FBitSet( flags, SND_SENTENCE )) - { - char sentenceName[32]; - - //if( FBitSet( flags, SND_SEQUENCE )) - //Q_snprintf( sentenceName, sizeof( sentenceName ), "!#%i", sound + MAX_SOUNDS ); - //else - Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); - - handle = S_RegisterSound( sentenceName ); - } - else handle = cl.sound_index[sound]; // see precached sound - - if( !cl.audio_prepped ) - return; // too early - - // g-cont. sound and ambient sound have only difference with channel - if( is_ambient ) - { - S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); - } - else - { - S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags ); - } -} -/* -================ -CL_PrecacheSound - -prceache sound from server -================ -*/ -void CL_LegacyPrecacheSound( sizebuf_t *msg ) -{ - int soundIndex; - - soundIndex = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); - - if( soundIndex < 0 || soundIndex >= MAX_SOUNDS ) - Host_Error( "CL_PrecacheSound: bad soundindex %i\n", soundIndex ); - - Q_strncpy( cl.sound_precache[soundIndex], MSG_ReadString( msg ), sizeof( cl.sound_precache[0] )); - - // when we loading map all resources is precached sequentially - //if( !cl.audio_prepped ) return; - - cl.sound_index[soundIndex] = S_RegisterSound( cl.sound_precache[soundIndex] ); -} - -void CL_LegacyPrecacheModel( sizebuf_t *msg ) -{ - int modelIndex; - string model; - - modelIndex = MSG_ReadUBitLong( msg, MAX_LEGACY_MODEL_BITS ); - - if( modelIndex < 0 || modelIndex >= MAX_MODELS ) - Host_Error( "CL_PrecacheModel: bad modelindex %i\n", modelIndex ); - - Q_strncpy( model, MSG_ReadString( msg ), MAX_STRING ); - //Q_strncpy( cl.model_precache[modelIndex], BF_ReadString( msg ), sizeof( cl.model_precache[0] )); - - // when we loading map all resources is precached sequentially - //if( !cl.video_prepped ) return; - if( modelIndex == 1 && !cl.worldmodel ) - { - CL_ClearWorld (); - - cl.models[modelIndex] = cl.worldmodel = Mod_LoadWorld( model, true ); - return; - - } - - //Mod_RegisterModel( cl.model_precache[modelIndex], modelIndex ); - - cl.models[modelIndex] = Mod_ForName( model, false, false ); - cl.nummodels = Q_max( cl.nummodels, modelIndex ); -} - -void CL_LegacyPrecacheEvent( sizebuf_t *msg ) -{ - int eventIndex; - - eventIndex = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); - - if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) - Host_Error( "CL_PrecacheEvent: bad eventindex %i\n", eventIndex ); - - Q_strncpy( cl.event_precache[eventIndex], MSG_ReadString( msg ), sizeof( cl.event_precache[0] )); - - // can be set now - CL_SetEventIndex( cl.event_precache[eventIndex], eventIndex ); -} - - -void CL_LegacyUpdateUserinfo( sizebuf_t *msg ) -{ - int slot, id = 0; - qboolean active; - player_info_t *player; - - slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); - - if( slot >= MAX_CLIENTS ) - Host_Error( "CL_ParseServerMessage: svc_updateuserinfo >= MAX_CLIENTS\n" ); - - //id = MSG_ReadLong( msg ); // unique user ID - player = &cl.players[slot]; - active = MSG_ReadOneBit( msg ) ? true : false; - - if( active ) - { - Q_strncpy( player->userinfo, MSG_ReadString( msg ), sizeof( player->userinfo )); - Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name )); - Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model )); - player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); - player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); - player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" )); - //MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey )); - - if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t )); - } - else memset( player, 0, sizeof( *player )); -} -#if XASH_LOW_MEMORY == 0 -#define MAX_LEGACY_RESOURCES 2048 -#elif XASH_LOW_MEMORY == 2 -#define MAX_LEGACY_RESOURCES 1 -#elif XASH_LOW_MEMORY == 1 -#define MAX_LEGACY_RESOURCES 512 -#endif -/* -============== -CL_ParseResourceList - -============== -*/ -void CL_LegacyParseResourceList( sizebuf_t *msg ) -{ - int i = 0; - - static struct - { - int rescount; - int restype[MAX_LEGACY_RESOURCES]; - char resnames[MAX_LEGACY_RESOURCES][MAX_QPATH]; - } reslist; - memset( &reslist, 0, sizeof( reslist )); - - reslist.rescount = MSG_ReadWord( msg ) - 1; - - if( reslist.rescount > MAX_LEGACY_RESOURCES ) - Host_Error("MAX_RESOURCES reached\n"); - - for( i = 0; i < reslist.rescount; i++ ) - { - reslist.restype[i] = MSG_ReadWord( msg ); - Q_strncpy( reslist.resnames[i], MSG_ReadString( msg ), MAX_QPATH ); - } - - if( CL_IsPlaybackDemo() ) - { - return; - } - - host.downloadcount = 0; - - for( i = 0; i < reslist.rescount; i++ ) - { - const char *path; - - if( reslist.restype[i] == t_sound ) - path = va( DEFAULT_SOUNDPATH "%s", reslist.resnames[i] ); - else path = reslist.resnames[i]; - - if( FS_FileExists( path, false )) - continue; // already exists - - host.downloadcount++; - HTTP_AddDownload( path, -1, true ); - } - - if( !host.downloadcount ) - { - MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); - MSG_WriteString( &cls.netchan.message, "continueloading" ); - } -} - -/* -===================== -CL_ParseLegacyServerMessage - -dispatch messages -===================== -*/ -void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) -{ - size_t bufStart, playerbytes; - int cmd, param1, param2; - int old_background; - const char *s; - - cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame - CL_Parse_Debug( true ); // begin parsing - - if( normal_message ) - { - // assume no entity/player update this packet - if( cls.state == ca_active ) - { - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; - } - else - { - CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); - } - } - - // parse the message - while( 1 ) - { - if( MSG_CheckOverflow( msg )) - { - Host_Error( "CL_ParseServerMessage: overflow!\n" ); - return; - } - - // mark start position - bufStart = MSG_GetNumBytesRead( msg ); - - // end of message (align bits) - if( MSG_GetNumBitsLeft( msg ) < 8 ) - break; - - cmd = MSG_ReadServerCmd( msg ); - - // record command for debugging spew on parse problem - CL_Parse_RecordCommand( cmd, bufStart ); - - // other commands - switch( cmd ) - { - case svc_bad: - Host_Error( "svc_bad\n" ); - break; - case svc_nop: - // this does nothing - break; - case svc_disconnect: - CL_Drop (); - Host_AbortCurrentFrame (); - break; - case svc_legacy_event: - CL_ParseEvent( msg ); - cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_legacy_changing: - old_background = cl.background; - if( MSG_ReadOneBit( msg )) - { - cls.changelevel = true; - S_StopAllSounds( true ); - - Con_Printf( "Server changing, reconnecting\n" ); - - if( cls.demoplayback ) - { - SCR_BeginLoadingPlaque( cl.background ); - cls.changedemo = true; - } - - CL_ClearState (); - CL_InitEdicts (); // re-arrange edicts - } - else Con_Printf( "Server disconnected, reconnecting\n" ); - - if( cls.demoplayback ) - { - cl.background = (cls.demonum != -1) ? true : false; - cls.state = ca_connected; - } - else - { - // g-cont. local client skip the challenge - if( SV_Active( )) - cls.state = ca_disconnected; - else cls.state = ca_connecting; - cl.background = old_background; - cls.connect_time = MAX_HEARTBEAT; - } - break; - case svc_setview: - CL_ParseViewEntity( msg ); - break; - case svc_sound: - CL_LegacyParseSoundPacket( msg, false ); - cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_legacy_ambientsound: - CL_LegacyParseSoundPacket( msg, true ); - cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; - - break; - case svc_time: - CL_ParseServerTime( msg ); - break; - case svc_print: - Con_Printf( "%s", MSG_ReadString( msg )); - break; - case svc_stufftext: - s = MSG_ReadString( msg ); -#ifdef HACKS_RELATED_HLMODS - // disable Cry Of Fear antisave protection - if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) - break; // too early -#endif - - Con_Reportf( "Stufftext: %s", s ); - Cbuf_AddFilteredText( s ); - break; - case svc_setangle: - CL_ParseSetAngle( msg ); - break; - case svc_serverdata: - Cbuf_Execute(); // make sure any stuffed commands are done - CL_ParseLegacyServerData( msg ); - break; - case svc_lightstyle: - CL_ParseLightStyle( msg ); - break; - case svc_updateuserinfo: - CL_LegacyUpdateUserinfo( msg ); - break; - case svc_deltatable: - Delta_ParseTableField( msg ); - break; - case svc_clientdata: - CL_ParseClientData( msg ); - cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_resource: - CL_ParseResource( msg ); - break; - case svc_pings: - CL_UpdateUserPings( msg ); - break; - case svc_particle: - CL_ParseParticles( msg ); - break; - case svc_restoresound: - CL_ParseRestoreSoundPacket( msg ); - cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_spawnstatic: - CL_ParseStaticEntity( msg ); - break; - case svc_event_reliable: - CL_ParseReliableEvent( msg ); - cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_spawnbaseline: - CL_LegacyParseBaseline( msg ); - break; - case svc_temp_entity: - CL_ParseTempEntity( msg ); - cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart; - break; - case svc_setpause: - cl.paused = ( MSG_ReadOneBit( msg ) != 0 ); - break; - case svc_signonnum: - CL_ParseSignon( msg ); - break; - case svc_centerprint: - CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); - break; - case svc_intermission: - cl.intermission = 1; - break; - case svc_legacy_modelindex: - CL_LegacyPrecacheModel( msg ); - - break; - case svc_legacy_soundindex: - CL_LegacyPrecacheSound( msg ); - break; - case svc_cdtrack: - param1 = MSG_ReadByte( msg ); - param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum - param2 = MSG_ReadByte( msg ); - param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum - S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false ); - break; - case svc_restore: - CL_ParseRestore( msg ); - break; - case svc_legacy_eventindex: - //CL_ParseFinaleCutscene( msg, 3 ); - CL_LegacyPrecacheEvent(msg); - break; - case svc_weaponanim: - param1 = MSG_ReadByte( msg ); // iAnim - param2 = MSG_ReadByte( msg ); // body - CL_WeaponAnim( param1, param2 ); - break; - case svc_bspdecal: - CL_ParseStaticDecal( msg ); - break; - case svc_roomtype: - param1 = MSG_ReadShort( msg ); - Cvar_SetValue( "room_type", param1 ); - break; - case svc_addangle: - CL_ParseAddAngle( msg ); - break; - case svc_usermessage: - CL_RegisterUserMessage( msg ); - break; - case svc_packetentities: - playerbytes = CL_ParsePacketEntities( msg, false ); - cl.frames[cl.parsecountmod].graphdata.players += playerbytes; - cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; - break; - case svc_deltapacketentities: - playerbytes = CL_ParsePacketEntities( msg, true ); - cl.frames[cl.parsecountmod].graphdata.players += playerbytes; - cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; - break; - case svc_legacy_chokecount: - { - int i, j; - i = MSG_ReadByte( msg ); - j = cls.netchan.incoming_acknowledged - 1; - for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- ) - { - if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 ) - { - cl.frames[j & CL_UPDATE_MASK].choked = true; - cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0; - i--; - } - } - break; - } - //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true; - //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; - break; - case svc_resourcelist: - CL_LegacyParseResourceList( msg ); - break; - case svc_deltamovevars: - CL_ParseMovevars( msg ); - break; - case svc_resourcerequest: - CL_ParseResourceRequest( msg ); - break; - case svc_customization: - CL_ParseCustomization( msg ); - break; - case svc_crosshairangle: - CL_ParseCrosshairAngle( msg ); - break; - case svc_soundfade: - CL_ParseSoundFade( msg ); - break; - case svc_filetxferfailed: - CL_ParseFileTransferFailed( msg ); - break; - case svc_hltv: - CL_ParseHLTV( msg ); - break; - case svc_director: - CL_ParseDirector( msg ); - break; - case svc_resourcelocation: - CL_ParseResLocation( msg ); - break; - case svc_querycvarvalue: - CL_ParseCvarValue( msg, false ); - break; - case svc_querycvarvalue2: - CL_ParseCvarValue( msg, true ); - break; - default: - CL_ParseUserMessage( msg, cmd ); - cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; - break; - } - } - - cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; - CL_Parse_Debug( false ); // done - - // we don't know if it is ok to save a demo message until - // after we have parsed the frame - if( !cls.demoplayback ) - { - if( cls.demorecording && !cls.demowaiting ) - { - CL_WriteDemoMessage( false, cls.starting_count, msg ); - } - else if( cls.state != ca_active ) - { - CL_WriteDemoMessage( true, cls.starting_count, msg ); - } - } -} - -void CL_LegacyPrecache_f( void ) -{ - int spawncount, i; - model_t *mod; - - if( !cls.legacymode ) - return; - - spawncount = Q_atoi( Cmd_Argv( 1 )); - - Con_Printf( "Setting up renderer...\n" ); - - // load tempent sprites (glowshell, muzzleflashes etc) - CL_LoadClientSprites (); - - // invalidate all decal indexes - memset( cl.decal_index, 0, sizeof( cl.decal_index )); - cl.video_prepped = true; - cl.audio_prepped = true; - if( clgame.entities ) - clgame.entities->model = cl.worldmodel; - - // update the ref state. - R_UpdateRefState (); - - // tell rendering system we have a new set of models. - ref.dllFuncs.R_NewMap (); - - CL_SetupOverviewParams(); - - // release unused SpriteTextures - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) - { - if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) - Mod_FreeModel( mod ); - } - -// Mod_FreeUnused (); - - if( host_developer.value <= DEV_NONE ) - Con_ClearNotify(); // clear any lines of console text - - // done with all resources, issue prespawn command. - // Include server count in case server disconnects and changes level during d/l - MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); - MSG_WriteString( &cls.netchan.message, va( "begin %i", spawncount )); - cls.signon = SIGNONS; -} - -void CL_LegacyUpdateInfo( void ) -{ - if( !cls.legacymode ) - return; - - if( cls.state != ca_active ) - return; - - MSG_BeginClientCmd( &cls.netchan.message, clc_legacy_userinfo ); - MSG_WriteString( &cls.netchan.message, cls.userinfo ); -} - -qboolean CL_LegacyMode( void ) -{ - return cls.legacymode; } diff --git a/engine/client/cl_parse_48.c b/engine/client/cl_parse_48.c new file mode 100644 index 00000000..3703935a --- /dev/null +++ b/engine/client/cl_parse_48.c @@ -0,0 +1,633 @@ +/* +cl_parse.c - parse a message received from the server +Copyright (C) 2008 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "particledef.h" +#include "cl_tent.h" +#include "shake.h" +#include "hltv.h" +#include "input.h" + + +/* +================== +CL_ParseStaticEntity + +static client entity +================== +*/ +static void CL_LegacyParseStaticEntity( sizebuf_t *msg ) +{ + int i; + entity_state_t state = { 0 }; + cl_entity_t *ent; + + if( !clgame.static_entities ) + clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); + + state.modelindex = MSG_ReadShort( msg ); + state.sequence = MSG_ReadByte( msg ); + state.frame = MSG_ReadByte( msg ); + state.colormap = MSG_ReadWord( msg ); + state.skin = MSG_ReadByte( msg ); + + for( i = 0; i < 3; i++ ) + { + state.origin[i] = MSG_ReadCoord( msg ); + state.angles[i] = MSG_ReadBitAngle( msg, 16 ); + } + + state.rendermode = MSG_ReadByte( msg ); + + if( state.rendermode != kRenderNormal ) + { + state.renderamt = MSG_ReadByte( msg ); + state.rendercolor.r = MSG_ReadByte( msg ); + state.rendercolor.g = MSG_ReadByte( msg ); + state.rendercolor.b = MSG_ReadByte( msg ); + state.renderfx = MSG_ReadByte( msg ); + } + + i = clgame.numStatics; + if( i >= MAX_STATIC_ENTITIES ) + { + Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" ); + return; + } + + ent = &clgame.static_entities[i]; + clgame.numStatics++; + + // all states are same + ent->baseline = ent->curstate = ent->prevstate = state; + ent->index = 0; // static entities doesn't has the numbers + + // statics may be respawned in game e.g. for demo recording + if( cls.state == ca_connected || cls.state == ca_validate ) + ent->trivial_accept = INVALID_HANDLE; + + // setup the new static entity + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + ent->model = CL_ModelHandle( state.modelindex ); + ent->curstate.framerate = 1.0f; + CL_ResetLatchedVars( ent, true ); + + if( ent->curstate.rendermode == kRenderNormal && ent->model != NULL ) + { + // auto 'solid' faces + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( )) + { + ent->curstate.rendermode = kRenderTransAlpha; + ent->curstate.renderamt = 255; + } + } + + R_AddEfrags( ent ); // add link +} + +static void CL_LegacyParseSoundPacket( sizebuf_t *msg, qboolean is_ambient ) +{ + vec3_t pos; + int chan, sound; + float volume, attn; + int flags, pitch, entnum; + sound_t handle = 0; + + flags = MSG_ReadWord( msg ); + if( flags & SND_LEGACY_LARGE_INDEX ) + { + sound = MSG_ReadWord( msg ); + flags &= ~SND_LEGACY_LARGE_INDEX; + } + else + sound = MSG_ReadByte( msg ); + chan = MSG_ReadByte( msg ); + + if( FBitSet( flags, SND_VOLUME )) + volume = (float)MSG_ReadByte( msg ) / 255.0f; + else volume = VOL_NORM; + + if( FBitSet( flags, SND_ATTENUATION )) + attn = (float)MSG_ReadByte( msg ) / 64.0f; + else attn = ATTN_NONE; + + if( FBitSet( flags, SND_PITCH )) + pitch = MSG_ReadByte( msg ); + else pitch = PITCH_NORM; + + // entity reletive + entnum = MSG_ReadWord( msg ); + + // positioned in space + MSG_ReadVec3Coord( msg, pos ); + + if( FBitSet( flags, SND_SENTENCE )) + { + char sentenceName[32]; + Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); + handle = S_RegisterSound( sentenceName ); + } + else handle = cl.sound_index[sound]; // see precached sound + + if( !cl.audio_prepped ) + return; // too early + + // g-cont. sound and ambient sound have only difference with channel + if( is_ambient ) + { + S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); + } + else + { + S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags ); + } +} +/* +================ +CL_PrecacheSound + +prceache sound from server +================ +*/ +static void CL_LegacyPrecacheSound( sizebuf_t *msg ) +{ + int soundIndex; + + soundIndex = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); + + if( soundIndex < 0 || soundIndex >= MAX_SOUNDS ) + Host_Error( "%s: bad soundindex %i\n", __func__, soundIndex ); + + Q_strncpy( cl.sound_precache[soundIndex], MSG_ReadString( msg ), sizeof( cl.sound_precache[0] )); + + // when we loading map all resources is precached sequentially + //if( !cl.audio_prepped ) return; + + cl.sound_index[soundIndex] = S_RegisterSound( cl.sound_precache[soundIndex] ); +} + +static void CL_LegacyPrecacheModel( sizebuf_t *msg ) +{ + int modelIndex; + string model; + + modelIndex = MSG_ReadUBitLong( msg, MAX_LEGACY_MODEL_BITS ); + + if( modelIndex < 0 || modelIndex >= MAX_MODELS ) + Host_Error( "%s: bad modelindex %i\n", __func__, modelIndex ); + + Q_strncpy( model, MSG_ReadString( msg ), sizeof( model )); + //Q_strncpy( cl.model_precache[modelIndex], BF_ReadString( msg ), sizeof( cl.model_precache[0] )); + + // when we loading map all resources is precached sequentially + //if( !cl.video_prepped ) return; + if( modelIndex == 1 && !cl.worldmodel ) + { + CL_ClearWorld (); + + cl.models[modelIndex] = cl.worldmodel = Mod_LoadWorld( model, true ); + return; + + } + + //Mod_RegisterModel( cl.model_precache[modelIndex], modelIndex ); + + cl.models[modelIndex] = Mod_ForName( model, false, false ); + cl.nummodels = Q_max( cl.nummodels, modelIndex ); +} + +static void CL_LegacyPrecacheEvent( sizebuf_t *msg ) +{ + int eventIndex; + + eventIndex = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); + + if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) + Host_Error( "%s: bad eventindex %i\n", __func__, eventIndex ); + + Q_strncpy( cl.event_precache[eventIndex], MSG_ReadString( msg ), sizeof( cl.event_precache[0] )); + + // can be set now + CL_SetEventIndex( cl.event_precache[eventIndex], eventIndex ); +} + +#if XASH_LOW_MEMORY == 0 +#define MAX_LEGACY_RESOURCES 2048 +#elif XASH_LOW_MEMORY == 2 +#define MAX_LEGACY_RESOURCES 1 +#elif XASH_LOW_MEMORY == 1 +#define MAX_LEGACY_RESOURCES 512 +#endif +/* +============== +CL_ParseResourceList + +============== +*/ +static void CL_LegacyParseResourceList( sizebuf_t *msg ) +{ + int i = 0; + static struct + { + int rescount; + int restype[MAX_LEGACY_RESOURCES]; + char resnames[MAX_LEGACY_RESOURCES][MAX_QPATH]; + } reslist; + + memset( &reslist, 0, sizeof( reslist )); + reslist.rescount = MSG_ReadWord( msg ) - 1; + + if( reslist.rescount > MAX_LEGACY_RESOURCES ) + Host_Error( "MAX_RESOURCES reached\n" ); + + for( i = 0; i < reslist.rescount; i++ ) + { + reslist.restype[i] = MSG_ReadWord( msg ); + Q_strncpy( reslist.resnames[i], MSG_ReadString( msg ), sizeof( reslist.resnames[i] )); + } + + if( cls.demoplayback ) + return; + + if( !cl_allow_download.value ) + { + Con_DPrintf( "Refusing new resource, cl_allowdownload set to 0\n" ); + reslist.rescount = 0; + } + + if( cls.state == ca_active && !cl_download_ingame.value ) + { + Con_DPrintf( "Refusing new resource, cl_download_ingame set to 0\n" ); + reslist.rescount = 0; + } + + HTTP_ResetProcessState(); + + host.downloadcount = 0; + + for( i = 0; i < reslist.rescount; i++ ) + { + char soundpath[MAX_VA_STRING]; + const char *path; + + if( reslist.restype[i] == t_sound ) + { + Q_snprintf( soundpath, sizeof( soundpath ), DEFAULT_SOUNDPATH "%s", reslist.resnames[i] ); + + path = soundpath; + } + else path = reslist.resnames[i]; + + if( FS_FileExists( path, false )) + continue; // already exists + + host.downloadcount++; + HTTP_AddDownload( path, -1, true, NULL ); + } + + if( !host.downloadcount ) + { + MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, "continueloading" ); + } +} + +/* +===================== +CL_ParseLegacyServerMessage + +dispatch messages +===================== +*/ +void CL_ParseLegacyServerMessage( sizebuf_t *msg ) +{ + size_t bufStart, playerbytes; + int cmd; + int old_background; + const char *s; + + // parse the message + while( 1 ) + { + if( MSG_CheckOverflow( msg )) + { + Host_Error( "%s: overflow!\n", __func__ ); + return; + } + + // mark start position + bufStart = MSG_GetNumBytesRead( msg ); + + // end of message (align bits) + if( MSG_GetNumBitsLeft( msg ) < 8 ) + break; + + cmd = MSG_ReadServerCmd( msg ); + + // record command for debugging spew on parse problem + CL_Parse_RecordCommand( cmd, bufStart ); + + if( CL_ParseCommonDLLMessage( msg, PROTO_LEGACY, cmd, bufStart )) + continue; + + // other commands + switch( cmd ) + { + case svc_bad: + Host_Error( "svc_bad\n" ); + break; + case svc_nop: + // this does nothing + break; + case svc_disconnect: + CL_Drop (); + Host_AbortCurrentFrame (); + break; + case svc_legacy_event: + CL_ParseEvent( msg, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_legacy_changing: + old_background = cl.background; + if( MSG_ReadOneBit( msg )) + { + int maxclients = cl.maxclients; + + // we can only changelevel in singleplayer + // and singleplayer always runs in current protocol + // cls.changelevel = true; + S_StopAllSounds( true ); + + Con_Printf( "Server changing, reconnecting\n" ); + + if( cls.demoplayback ) + { + SCR_BeginLoadingPlaque( cl.background ); + cls.changedemo = true; + } + + CL_ClearState( ); + + // a1ba: need to restore cl.maxclients because engine chooses + // frame backups count depending on this value + // In general, it's incorrect to call CL_InitEdicts right after + // CL_ClearState because of this bug. Some time later this logic + // should be re-done. + CL_InitEdicts( maxclients ); // re-arrange edicts + } + else Con_Printf( "Server disconnected, reconnecting\n" ); + + if( cls.demoplayback ) + { + cl.background = (cls.demonum != -1) ? true : false; + cls.state = ca_connected; + } + else + { + // g-cont. local client skip the challenge + if( SV_Active( )) + cls.state = ca_disconnected; + else cls.state = ca_connecting; + cl.background = old_background; + cls.connect_time = MAX_HEARTBEAT; + cls.connect_retry = 0; + } + break; + case svc_setview: + CL_ParseViewEntity( msg ); + break; + case svc_sound: + CL_LegacyParseSoundPacket( msg, false ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_legacy_ambientsound: + CL_LegacyParseSoundPacket( msg, true ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + + break; + case svc_time: + CL_ParseServerTime( msg, PROTO_LEGACY ); + break; + case svc_print: + Con_Printf( "%s", MSG_ReadString( msg )); + break; + case svc_stufftext: + s = MSG_ReadString( msg ); + if( cl_trace_stufftext.value ) + { + size_t len = Q_strlen( s ); + Con_Printf( "Stufftext: %s%c", s, len && s[len-1] == '\n' ? '\0' : '\n' ); + } + +#ifdef HACKS_RELATED_HLMODS + // disable Cry Of Fear antisave protection + if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) + break; // too early +#endif + Cbuf_AddFilteredText( s ); + break; + case svc_setangle: + CL_ParseSetAngle( msg ); + break; + case svc_serverdata: + Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerData( msg, PROTO_LEGACY ); + break; + case svc_lightstyle: + CL_ParseLightStyle( msg, PROTO_LEGACY ); + break; + case svc_updateuserinfo: + CL_UpdateUserinfo( msg, PROTO_LEGACY ); + break; + case svc_deltatable: + Delta_ParseTableField( msg ); + break; + case svc_clientdata: + CL_ParseClientData( msg, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_resource: + CL_ParseResource( msg ); + break; + case svc_pings: + CL_UpdateUserPings( msg ); + break; + case svc_particle: + CL_ParseParticles( msg, PROTO_LEGACY ); + break; + case svc_restoresound: + Con_Printf( S_ERROR "%s: svc_restoresound: implement me!\n", __func__ ); + break; + case svc_spawnstatic: + CL_LegacyParseStaticEntity( msg ); + break; + case svc_event_reliable: + CL_ParseReliableEvent( msg, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_spawnbaseline: + CL_ParseBaseline( msg, PROTO_LEGACY ); + break; + case svc_setpause: + cl.paused = ( MSG_ReadOneBit( msg ) != 0 ); + break; + case svc_signonnum: + CL_ParseSignon( msg, PROTO_LEGACY ); + break; + case svc_centerprint: + CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); + break; + case svc_legacy_modelindex: + CL_LegacyPrecacheModel( msg ); + break; + case svc_legacy_soundindex: + CL_LegacyPrecacheSound( msg ); + break; + case svc_restore: + CL_ParseRestore( msg ); + break; + case svc_legacy_eventindex: + CL_LegacyPrecacheEvent(msg); + break; + case svc_bspdecal: + CL_ParseStaticDecal( msg ); + break; + case svc_addangle: + CL_ParseAddAngle( msg ); + break; + case svc_usermessage: + CL_RegisterUserMessage( msg, PROTO_LEGACY ); + break; + case svc_packetentities: + playerbytes = CL_ParsePacketEntities( msg, false, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_deltapacketentities: + playerbytes = CL_ParsePacketEntities( msg, true, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_legacy_chokecount: + { + int i, j; + i = MSG_ReadByte( msg ); + j = cls.netchan.incoming_acknowledged - 1; + for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- ) + { + if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 ) + { + cl.frames[j & CL_UPDATE_MASK].choked = true; + cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0; + i--; + } + } + break; + } + //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true; + //cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; + break; + case svc_resourcelist: + CL_LegacyParseResourceList( msg ); + break; + case svc_deltamovevars: + CL_ParseMovevars( msg ); + break; + case svc_resourcerequest: + CL_ParseResourceRequest( msg ); + break; + case svc_customization: + CL_ParseCustomization( msg ); + break; + case svc_crosshairangle: + CL_ParseCrosshairAngle( msg ); + break; + case svc_soundfade: + CL_ParseSoundFade( msg ); + break; + case svc_filetxferfailed: + CL_ParseFileTransferFailed( msg ); + break; + case svc_hltv: + CL_ParseHLTV( msg ); + break; + case svc_resourcelocation: + CL_ParseResLocation( msg ); + break; + case svc_querycvarvalue: + CL_ParseCvarValue( msg, false, PROTO_LEGACY ); + break; + case svc_querycvarvalue2: + CL_ParseCvarValue( msg, true, PROTO_LEGACY ); + break; + default: + CL_ParseUserMessage( msg, cmd, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; + break; + } + } +} + +void CL_LegacyPrecache_f( void ) +{ + int spawncount, i; + model_t *mod; + + if( cls.legacymode != PROTO_LEGACY ) + return; + + spawncount = Q_atoi( Cmd_Argv( 1 )); + + Con_Printf( "Setting up renderer...\n" ); + + // load tempent sprites (glowshell, muzzleflashes etc) + CL_LoadClientSprites (); + + // invalidate all decal indexes + memset( cl.decal_index, 0, sizeof( cl.decal_index )); + cl.video_prepped = true; + cl.audio_prepped = true; + if( clgame.entities ) + clgame.entities->model = cl.worldmodel; + + // load skybox + R_SetupSky( clgame.movevars.skyName ); + + // tell rendering system we have a new set of models. + ref.dllFuncs.R_NewMap (); + + CL_SetupOverviewParams(); + + // release unused SpriteTextures + for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + { + if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) + Mod_FreeModel( mod ); + } + +// Mod_FreeUnused (); + + if( host_developer.value <= DEV_NONE ) + Con_ClearNotify(); // clear any lines of console text + + // done with all resources, issue prespawn command. + // Include server count in case server disconnects and changes level during d/l + MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); + MSG_WriteStringf( &cls.netchan.message, "begin %i", spawncount ); + cls.signon = SIGNONS - 1; +} diff --git a/engine/client/cl_parse_gs.c b/engine/client/cl_parse_gs.c new file mode 100644 index 00000000..5bfce813 --- /dev/null +++ b/engine/client/cl_parse_gs.c @@ -0,0 +1,784 @@ +/* +cl_parse.c - parse a message received from the server (GoldSrc 48 protocol) +Copyright (C) 2008 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "particledef.h" +#include "cl_tent.h" +#include "shake.h" +#include "hltv.h" +#include "input.h" +#include "server.h" + +static void CL_ParseExtraInfo( sizebuf_t *msg ) +{ + string clientfallback; + + Q_strncpy( clientfallback, MSG_ReadString( msg ), sizeof( clientfallback )); + if( COM_CheckStringEmpty( clientfallback )) + Con_Reportf( S_ERROR "%s: TODO: add fallback directory %s!\n", __func__, clientfallback ); + + if( MSG_ReadByte( msg )) + { + Cvar_FullSet( "sv_cheats", "1", FCVAR_READ_ONLY | FCVAR_SERVER ); + } + else + { + Cvar_SetCheatState(); + Cvar_FullSet( "sv_cheats", "0", FCVAR_READ_ONLY | FCVAR_SERVER ); + } +} + +static void CL_ParseNewMovevars( sizebuf_t *msg ) +{ + Delta_InitClient(); // finalize client delta's + + clgame.movevars.gravity = MSG_ReadFloat( msg ); + clgame.movevars.stopspeed = MSG_ReadFloat( msg ); + clgame.movevars.maxspeed = MSG_ReadFloat( msg ); + clgame.movevars.spectatormaxspeed = MSG_ReadFloat( msg ); + clgame.movevars.accelerate = MSG_ReadFloat( msg ); + clgame.movevars.airaccelerate = MSG_ReadFloat( msg ); + clgame.movevars.wateraccelerate = MSG_ReadFloat( msg ); + clgame.movevars.friction = MSG_ReadFloat( msg ); + clgame.movevars.edgefriction = MSG_ReadFloat( msg ); + clgame.movevars.waterfriction = MSG_ReadFloat( msg ); + clgame.movevars.entgravity = MSG_ReadFloat( msg ); + clgame.movevars.bounce = MSG_ReadFloat( msg ); + clgame.movevars.stepsize = MSG_ReadFloat( msg ); + clgame.movevars.maxvelocity = MSG_ReadFloat( msg ); + clgame.movevars.zmax = MSG_ReadFloat( msg ); + clgame.movevars.waveHeight = MSG_ReadFloat( msg ); + clgame.movevars.footsteps = MSG_ReadByte( msg ); + clgame.movevars.rollangle = MSG_ReadFloat( msg ); + clgame.movevars.rollspeed = MSG_ReadFloat( msg ); + clgame.movevars.skycolor_r = MSG_ReadFloat( msg ); + clgame.movevars.skycolor_g = MSG_ReadFloat( msg ); + clgame.movevars.skycolor_b = MSG_ReadFloat( msg ); + clgame.movevars.skyvec_x = MSG_ReadFloat( msg ); + clgame.movevars.skyvec_y = MSG_ReadFloat( msg ); + clgame.movevars.skyvec_z = MSG_ReadFloat( msg ); + + Q_strncpy( clgame.movevars.skyName, MSG_ReadString( msg ), sizeof( clgame.movevars.skyName )); + + // water alpha is not allowed + if( !FBitSet( world.flags, FWORLD_WATERALPHA )) + clgame.movevars.wateralpha = 1.0f; + + // update sky if changed + if( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped ) + R_SetupSky( clgame.movevars.skyName ); + + clgame.oldmovevars = clgame.movevars; + + // FIXME: set world wave height when entities will be allocated + if( clgame.entities ) + clgame.entities->curstate.scale = clgame.movevars.waveHeight; + + // keep features an actual! + clgame.oldmovevars.features = clgame.movevars.features = host.features; +} + +typedef struct delta_header_t +{ + qboolean remove; + qboolean custom; + qboolean instanced; + uint16_t instanced_baseline_index; + uint16_t offset; +} delta_header_t; + +static int CL_ParseDeltaHeader( sizebuf_t *msg, qboolean delta, int oldnum, struct delta_header_t *hdr ) +{ + int entnum = oldnum; + memset( hdr, 0, sizeof( *hdr )); + + if( !delta ) + { + // if we have one bit set, then it's a next entity in line + // if we have next bit NON set, then it's a one of next 64 entities + // if not, it's a new entity + if( MSG_ReadOneBit( msg )) + entnum++; + else if( MSG_ReadOneBit( msg ) == 0 ) + entnum += MSG_ReadUBitLong( msg, 6 ); + else + entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS ); + } + else + { + // does this packet encode entity deletion? + hdr->remove = MSG_ReadOneBit( msg ); + + // same logic as above + if( MSG_ReadOneBit( msg ) == 0 ) + entnum += MSG_ReadUBitLong( msg, 6 ); + else entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS ); + } + + // if we are not removing this entity + if( !hdr->remove ) + { + hdr->custom = MSG_ReadOneBit( msg ); + + // do we got instanced baselines in svc_spawnbaselines? + if( cl.instanced_baseline_count ) + { + hdr->instanced = MSG_ReadOneBit( msg ); + if( hdr->instanced ) + hdr->instanced_baseline_index = MSG_ReadUBitLong( msg, 6 ); + } + + if( !delta && !hdr->instanced ) + { + if( MSG_ReadOneBit( msg )) + hdr->offset = MSG_ReadUBitLong( msg, 6 ); + } + } + + return entnum; +} + +static int CL_GetEntityDelta( const struct delta_header_t *hdr, int entnum ) +{ + if( hdr->custom ) + return DT_CUSTOM_ENTITY_STATE_T; + + if( CL_IsPlayerIndex( entnum )) + return DT_ENTITY_STATE_PLAYER_T; + + return DT_ENTITY_STATE_T; +} + +static int CL_FlushEntityPacketGS( frame_t *frame, sizebuf_t *msg ) +{ + int playerbytes = 0, numbase = 0; + + frame->valid = false; + cl.validsequence = 0; // can't render a frame + + // read it all but ignore it + while( 1 ) + { + int newnum, bufstart; + entity_state_t from = { 0 }, to; + delta_header_t hdr; + qboolean player; + + if( MSG_ReadWord( msg ) != 0 ) + { + MSG_SeekToBit( msg, -16, SEEK_CUR ); + numbase = newnum = CL_ParseDeltaHeader( msg, true, numbase, &hdr ); + } + else break; + + if( MSG_CheckOverflow( msg )) + Host_Error( "%s: overflow\n", __func__ ); + + player = CL_IsPlayerIndex( newnum ); + bufstart = MSG_GetNumBytesRead( msg ); + + if( hdr.remove ) + continue; + + Delta_ReadGSFields( msg, CL_GetEntityDelta( &hdr, newnum ), &from, &to, cl.mtime[0] ); + + if( player ) + playerbytes += MSG_GetNumBytesRead( msg ) - bufstart; + } + + if( MSG_CheckOverflow( msg )) + Host_Error( "%s: overflow\n", __func__ ); + + return playerbytes; +} + +static void CL_DeltaEntityGS( const delta_header_t *hdr, sizebuf_t *msg, frame_t *frame, int newnum, const entity_state_t *from ) +{ + cl_entity_t *ent; + entity_state_t *to; + qboolean newent = from == NULL; + int pack = frame->num_entities; + qboolean has_update = msg != NULL; + + // alloc next slot to store update + to = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities]; + + if(( newnum < 0 ) || ( newnum >= clgame.maxEntities )) + { + Con_DPrintf( S_ERROR "CL_DeltaEntity: invalid newnum: %d\n", newnum ); + Host_Error( "%s: bad delta entity number: %i\n", __func__, newnum ); + return; + } + + ent = CL_EDICT_NUM( newnum ); + if( hdr->remove ) + { + if( !newent ) + CL_KillDeadBeams( ent ); + else + Con_Printf( S_WARN "%s: entity remove on non-delta update (%d)\n", __func__, newnum ); + return; + } + + ent->index = newnum; // enumerate entity index + if( newent ) + { + if( hdr->instanced ) + from = &cl.instanced_baseline[hdr->instanced_baseline_index]; + else if( hdr->offset != 0 ) + from = &cls.packet_entities[(cls.next_client_entities - hdr->offset) % cls.num_client_entities]; + else + from = &ent->baseline; + } + + if( has_update ) + Delta_ReadGSFields( msg, CL_GetEntityDelta( hdr, newnum ), from, to, cl.mtime[0] ); + else memcpy( to, from, sizeof( entity_state_t )); + + to->entityType = hdr->custom ? ENTITY_BEAM : ENTITY_NORMAL; + to->number = newnum; + + if( newent ) + { + // interpolation must be reset + SETVISBIT( frame->flags, pack ); + + // release beams from previous entity + + // a1ba: check that this entity number was never used on client + // as beams can be transferred before this entity was sent to client + // (for example, beam was sent over during beam entity spawn + // but referenced start point entity hasn't been sent over due to PVS) + if( ent->curstate.messagenum != 0 ) + CL_KillDeadBeams( ent ); + } + + // add entity to packet + cls.next_client_entities++; + frame->num_entities++; +} + +static void CL_CopyPacketEntity( frame_t *frame, int num, const entity_state_t *from ) +{ + delta_header_t fakehdr = + { + .custom = FBitSet( from->entityType, ENTITY_BEAM ) == ENTITY_BEAM, + }; + CL_DeltaEntityGS( &fakehdr, NULL, frame, num, from ); +} + +static int CL_ParsePacketEntitiesGS( sizebuf_t *msg, qboolean delta ) +{ + frame_t *frame, *oldframe; + int oldindex, oldnum, numbase = 0; + entity_state_t *oldent; + int count; + int playerbytes = 0; + + // save first uncompressed packet as timestamp + if( cls.changelevel && !delta && cls.demorecording ) + CL_WriteDemoJumpTime(); + + count = MSG_ReadWord( msg ); + + frame = &cl.frames[cl.parsecountmod]; + memset( frame->flags, 0, sizeof( frame->flags )); + frame->first_entity = cls.next_client_entities; + frame->num_entities = 0; + frame->valid = true; + + if( delta ) + { + uint oldpacket = MSG_ReadByte( msg ); + oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK]; + + if( !CL_ValidateDeltaPacket( oldpacket, oldframe )) + { + MSG_StartBitWriting( msg ); + CL_FlushEntityPacketGS( frame, msg ); + MSG_EndBitWriting( msg ); + return playerbytes; + } + } + else + { + oldframe = NULL; + cls.demowaiting = false; + } + + cl.validsequence = cls.netchan.incoming_sequence; + + MSG_StartBitWriting( msg ); + + oldent = NULL; + oldindex = 0; + oldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent ); + + // read it all but ignore it + while( 1 ) + { + int bufstart, newnum; + qboolean player; + delta_header_t hdr; + int val = MSG_ReadWord( msg ); + + if( val ) + { + MSG_SeekToBit( msg, -16, SEEK_CUR ); + numbase = newnum = CL_ParseDeltaHeader( msg, delta, numbase, &hdr ); + } + else break; + + if( MSG_CheckOverflow( msg )) + Host_Error( "%s: overflow\n", __func__ ); + + player = CL_IsPlayerIndex( newnum ); + + while( oldnum < newnum ) + { + if( !delta ) + Con_Printf( S_WARN "%s: old frame copy on non-delta update (%d < %d)\n", __func__, oldnum, newnum ); + + // one or more entities from the old packet are unchanged + CL_CopyPacketEntity( frame, oldnum, oldent ); + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); + } + + bufstart = MSG_GetNumBytesRead( msg ); + + if( oldnum == newnum ) + { + if( !delta ) + Con_Printf( S_WARN "%s: delta entity on non-delta update (%d)\n", __func__, oldnum ); + + // from delta + CL_DeltaEntityGS( &hdr, msg, frame, newnum, oldent ); + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); + } + else if( oldnum > newnum ) + { + // from baseline + CL_DeltaEntityGS( &hdr, msg, frame, newnum, NULL ); + } + + if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufstart; + } + + if( MSG_CheckOverflow( msg )) + Host_Error( "%s: overflow\n", __func__ ); + + // any remaining entities in the old frame are copied over + while( oldnum != MAX_ENTNUMBER ) + { + // one or more entities from the old packet are unchanged + CL_CopyPacketEntity( frame, oldnum, oldent ); + oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); + } + + MSG_EndBitWriting( msg ); + + if( frame->num_entities != count ) + Con_Reportf( S_WARN "CL_Parse%sPacketEntitiesGS: (%i should be %i)\n", delta ? "Delta" : "", frame->num_entities, count ); + + if( !frame->valid ) + return playerbytes; + + CL_ProcessPacket( frame ); + CL_SetSolidEntities(); + + // first update, received world, remove loading plaque + if( cls.signon == ( SIGNONS - 1 )) + { + cls.signon = SIGNONS; + CL_SignonReply( PROTO_GOLDSRC ); + } + + return playerbytes; +} + +static float MSG_ReadGSBitCoord( sizebuf_t *sb ) +{ + float value = 0; + int ival, fval; + + ival = MSG_ReadOneBit( sb ); + fval = MSG_ReadOneBit( sb ); + + if( ival || fval ) + { + int sign = MSG_ReadOneBit( sb ); + + if( ival ) + ival = MSG_ReadUBitLong( sb, 12 ); + if( fval ) + fval = MSG_ReadUBitLong( sb, 3 ); + + value = (float)( fval / 8.0 + ival ); + if( sign ) + value = -value; + } + + return value; +} + +static void MSG_ReadGSBitVec3Coord( sizebuf_t *sb, vec3_t fa ) +{ + qboolean x, y, z; + + VectorClear( fa ); + + x = MSG_ReadOneBit( sb ); + y = MSG_ReadOneBit( sb ); + z = MSG_ReadOneBit( sb ); + if( x ) + fa[0] = MSG_ReadGSBitCoord( sb ); + if( y ) + fa[1] = MSG_ReadGSBitCoord( sb ); + if( z ) + fa[2] = MSG_ReadGSBitCoord( sb ); +} + +static void CL_ParseSoundPacketGS( sizebuf_t *msg ) +{ + vec3_t pos; + int chan, sound; + float volume, attn; + int flags, pitch, entnum; + sound_t handle = 0; + + MSG_StartBitWriting( msg ); + + flags = MSG_ReadUBitLong( msg, 9 ); + + if( FBitSet( flags, SND_VOLUME )) + volume = (float)MSG_ReadByte( msg ) / 255.0f; + else volume = VOL_NORM; + + if( FBitSet( flags, SND_ATTENUATION )) + attn = (float)MSG_ReadByte( msg ) / 64.0f; + else attn = ATTN_NONE; + + chan = MSG_ReadUBitLong( msg, 3 ); + entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS ); + if( FBitSet( flags, SND_LEGACY_LARGE_INDEX )) + sound = MSG_ReadWord( msg ); + else sound = MSG_ReadByte( msg ); + MSG_ReadGSBitVec3Coord( msg, pos ); + + if( FBitSet( flags, SND_PITCH )) + pitch = MSG_ReadByte( msg ); + else pitch = PITCH_NORM; + + MSG_EndBitWriting( msg ); + + ClearBits( flags, SND_LEGACY_LARGE_INDEX ); + + if( FBitSet( flags, SND_SENTENCE )) + { + char sentenceName[32]; + Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); + handle = S_RegisterSound( sentenceName ); + } + else handle = cl.sound_index[sound]; // see precached sound + + if( !cl.audio_prepped ) + return; // too early + + // g-cont. sound and ambient sound have only difference with channel + if( chan == CHAN_STATIC ) + { + S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); + } + else + { + S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags ); + } +} + +static void CL_ParseSpawnStaticSound( sizebuf_t *msg ) +{ + vec3_t pos; + int handle, entnum, pitch, flags; + float volume, attn; + + MSG_ReadVec3Coord( msg, pos ); + handle = MSG_ReadShort( msg ); + volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f ); + attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f ); + entnum = MSG_ReadShort( msg ); + pitch = MSG_ReadByte( msg ); + flags = MSG_ReadByte( msg ); + + S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); +} + +/* +===================================================================== + +ACTION MESSAGES + +===================================================================== +*/ +/* +===================== +CL_ParseGoldSrcServerMessage + +dispatch messages +===================== +*/ +void CL_ParseGoldSrcServerMessage( sizebuf_t *msg ) +{ + size_t bufStart, playerbytes; + int cmd, param1; + const char *s; + + // parse the message + while( 1 ) + { + if( MSG_CheckOverflow( msg )) + { + Host_Error( "%s: overflow!\n", __func__ ); + return; + } + + // mark start position + bufStart = MSG_GetNumBytesRead( msg ); + + // end of message (align bits) + if( MSG_GetNumBitsLeft( msg ) < 8 ) + break; + + cmd = MSG_ReadServerCmd( msg ); + + // record command for debugging spew on parse problem + CL_Parse_RecordCommand( cmd, bufStart ); + + if( CL_ParseCommonDLLMessage( msg, PROTO_GOLDSRC, cmd, bufStart )) + continue; + + // other commands + switch( cmd ) + { + case svc_bad: + Host_Error( "svc_bad\n" ); + break; + case svc_nop: + case svc_spawnstatic: + case svc_goldsrc_damage: + case svc_goldsrc_killedmonster: + case svc_goldsrc_foundsecret: + // this does nothing + break; + case svc_disconnect: + s = MSG_ReadString( msg ); + if( COM_CheckStringEmpty( s )) + Con_Printf( "Server issued disconnect. Reason: %s\n", s ); + CL_Drop (); + Host_AbortCurrentFrame (); + break; + case svc_event: + MSG_StartBitWriting( msg ); + CL_ParseEvent( msg, PROTO_GOLDSRC ); + MSG_EndBitWriting( msg ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_goldsrc_version: + param1 = MSG_ReadLong( msg ); + if( param1 != PROTOCOL_GOLDSRC_VERSION ) + Host_Error( "Server use invalid protocol (%i should be %i)\n", param1, PROTOCOL_GOLDSRC_VERSION ); + break; + case svc_setview: + CL_ParseViewEntity( msg ); + break; + case svc_sound: + CL_ParseSoundPacketGS( msg ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_time: + CL_ParseServerTime( msg, PROTO_GOLDSRC ); + break; + case svc_print: + Con_Printf( "%s", MSG_ReadString( msg )); + break; + case svc_stufftext: + s = MSG_ReadString( msg ); + if( cl_trace_stufftext.value ) + { + size_t len = Q_strlen( s ); + Con_Printf( "Stufftext: %s%c", s, len && s[len-1] == '\n' ? '\0' : '\n' ); + } + +#ifdef HACKS_RELATED_HLMODS + // disable Cry Of Fear antisave protection + if( !Q_strnicmp( s, "disconnect", 10 ) && cls.signon != SIGNONS ) + break; // too early +#endif + Cbuf_AddFilteredText( s ); + break; + case svc_setangle: + CL_ParseSetAngle( msg ); + break; + case svc_serverdata: + Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerData( msg, PROTO_GOLDSRC ); + break; + case svc_lightstyle: + CL_ParseLightStyle( msg, PROTO_GOLDSRC ); + break; + case svc_updateuserinfo: + CL_UpdateUserinfo( msg, PROTO_GOLDSRC ); + break; + case svc_deltatable: + Delta_ParseTableField_GS( msg ); + break; + case svc_clientdata: + MSG_StartBitWriting( msg ); + CL_ParseClientData( msg, PROTO_GOLDSRC ); + MSG_EndBitWriting( msg ); + cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_goldsrc_stopsound: + param1 = MSG_ReadWord( msg ); + S_StopSound( param1 >> 3, param1 & 7, NULL ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_pings: + MSG_StartBitWriting( msg ); + CL_UpdateUserPings( msg ); + MSG_EndBitWriting( msg ); + break; + case svc_particle: + CL_ParseParticles( msg, PROTO_GOLDSRC ); + break; + case svc_event_reliable: + MSG_StartBitWriting( msg ); + CL_ParseReliableEvent( msg, PROTO_GOLDSRC ); + MSG_EndBitWriting( msg ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_spawnbaseline: + MSG_StartBitWriting( msg ); + CL_ParseBaseline( msg, PROTO_GOLDSRC ); + MSG_EndBitWriting( msg ); + break; + case svc_setpause: + cl.paused = ( MSG_ReadByte( msg ) != 0 ); + break; + case svc_signonnum: + CL_ParseSignon( msg, PROTO_GOLDSRC ); + break; + case svc_centerprint: + CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); + break; + case svc_goldsrc_spawnstaticsound: + CL_ParseSpawnStaticSound( msg ); + break; + case svc_finale: + CL_ParseFinaleCutscene( msg, 2 ); + break; + case svc_restore: + CL_ParseRestore( msg ); + break; + case svc_cutscene: + CL_ParseFinaleCutscene( msg, 3 ); + break; + case svc_goldsrc_decalname: + param1 = MSG_ReadByte( msg ); + s = MSG_ReadString( msg ); + Q_strncpy( host.draw_decals[param1], s, sizeof( host.draw_decals[param1] )); + break; + case svc_addangle: + CL_ParseAddAngle( msg ); + break; + case svc_usermessage: + CL_RegisterUserMessage( msg, PROTO_GOLDSRC ); + break; + case svc_packetentities: + playerbytes = CL_ParsePacketEntitiesGS( msg, false ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_deltapacketentities: + playerbytes = CL_ParsePacketEntitiesGS( msg, true ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_choke: + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; + break; + case svc_resourcelist: + MSG_StartBitWriting( msg ); + CL_ParseResourceList( msg, PROTO_GOLDSRC ); + MSG_EndBitWriting( msg ); + break; + case svc_deltamovevars: + CL_ParseNewMovevars( msg ); + break; + case svc_resourcerequest: + CL_ParseResourceRequest( msg ); + break; + case svc_customization: + CL_ParseCustomization( msg ); + break; + case svc_crosshairangle: + CL_ParseCrosshairAngle( msg ); + break; + case svc_soundfade: + CL_ParseSoundFade( msg ); + break; + case svc_filetxferfailed: + CL_ParseFileTransferFailed( msg ); + break; + case svc_hltv: + CL_ParseHLTV( msg ); + break; + case svc_voiceinit: + CL_ParseVoiceInit( msg ); + break; + case svc_voicedata: + CL_ParseVoiceData( msg, PROTO_GOLDSRC ); + cl.frames[cl.parsecountmod].graphdata.voicebytes += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_resourcelocation: + CL_ParseResLocation( msg ); + break; + case svc_goldsrc_sendextrainfo: + CL_ParseExtraInfo( msg ); + break; + case svc_goldsrc_timescale: + // we can set sys_timescale to anything we want but in GoldSrc it's locked for + // HLTV and demoplayback. Do we really want to have it then if both are out of scope? + Con_Reportf( S_ERROR "%s: svc_goldsrc_timescale: implement me!\n", __func__ ); + MSG_ReadFloat( msg ); + break; + case svc_querycvarvalue: + CL_ParseCvarValue( msg, false, PROTO_GOLDSRC ); + break; + case svc_querycvarvalue2: + CL_ParseCvarValue( msg, true, PROTO_GOLDSRC ); + break; + case svc_exec: + CL_ParseExec( msg ); + break; + default: + CL_ParseUserMessage( msg, cmd, PROTO_LEGACY ); + cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; + break; + } + } +} diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 04dedf1f..0fd14ad7 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -26,20 +26,6 @@ GNU General Public License for more details. #define MIN_PREDICTION_EPSILON 0.5f // complain if error is > this and we have cl_showerror set #define MAX_PREDICTION_ERROR 64.0f // above this is assumed to be a teleport, don't smooth, etc. -/* -============= -CL_ClearPhysEnts - -============= -*/ -void CL_ClearPhysEnts( void ) -{ - clgame.pmove->numtouch = 0; - clgame.pmove->numvisent = 0; - clgame.pmove->nummoveent = 0; - clgame.pmove->numphysent = 0; -} - /* ============= CL_PushPMStates @@ -68,39 +54,14 @@ void GAME_EXPORT CL_PopPMStates( void ) clgame.pushed = false; } -/* -============= -CL_PushTraceBounds - -============= -*/ -void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs ) -{ - hullnum = bound( 0, hullnum, 3 ); - VectorCopy( mins, clgame.pmove->player_mins[hullnum] ); - VectorCopy( maxs, clgame.pmove->player_maxs[hullnum] ); -} - -/* -============= -CL_PopTraceBounds - -============= -*/ -void GAME_EXPORT CL_PopTraceBounds( void ) -{ - memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins )); - memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs )); -} - /* =============== CL_IsPredicted =============== */ -qboolean CL_IsPredicted( void ) +static qboolean CL_IsPredicted( void ) { - if( cl_nopred->value || cl.intermission ) + if( cl_nopred.value || cl.intermission ) return false; // never predict the quake demos @@ -197,7 +158,7 @@ void CL_SetIdealPitch( void ) } if( steps < 2 ) return; - cl.local.idealpitch = -dir * cl_idealpitchscale->value; + cl.local.idealpitch = -dir * cl_idealpitchscale.value; } /* @@ -208,7 +169,7 @@ check for instant movement in case we don't want interpolate this ================== */ -qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to ) +static qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to ) { int len, maxlen; vec3_t delta; @@ -248,7 +209,7 @@ void CL_CheckPredictionError( void ) // save the prediction error for interpolation if( dist > MAX_PREDICTION_ERROR ) { - if( cl_showerror->value && host_developer.value ) + if( cl_showerror.value && host_developer.value ) Con_NPrintf( 10 + ( ++pos & 3 ), "^3player teleported:^7 %.3f units\n", dist ); // a teleport or something or gamepaused @@ -256,7 +217,7 @@ void CL_CheckPredictionError( void ) } else { - if( cl_showerror->value && dist > MIN_PREDICTION_EPSILON && host_developer.value ) + if( cl_showerror.value && dist > MIN_PREDICTION_EPSILON && host_developer.value ) Con_NPrintf( 10 + ( ++pos & 3 ), "^1prediction error:^7 %.3f units\n", dist ); VectorCopy( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame] ); @@ -267,7 +228,7 @@ void CL_CheckPredictionError( void ) // GoldSrc checks for singleplayer // we would check for local server if( dist > MIN_CORRECTION_DISTANCE && !SV_Active() ) - cls.correction_time = cl_smoothtime->value; + cls.correction_time = cl_smoothtime.value; } } @@ -359,11 +320,15 @@ static void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboole // client or bot Q_snprintf( pe->name, sizeof( pe->name ), "player %i", pe->player - 1 ); } - else + else if( mod != NULL ) { // otherwise copy the modelname Q_strncpy( pe->name, mod->name, sizeof( pe->name )); } + else + { + Q_strncpy( pe->name, "entity %i", state->number ); + } pe->model = pe->studiomodel = NULL; @@ -383,7 +348,7 @@ static void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboole } // rare case: not solid entities in vistrace - if( visent && VectorIsNull( pe->mins )) + if( visent && VectorIsNull( pe->mins ) && mod != NULL ) { VectorCopy( mod->mins, pe->mins ); VectorCopy( mod->maxs, pe->maxs ); @@ -431,7 +396,7 @@ CL_AddLinksToPmove collect solid entities ==================== */ -void CL_AddLinksToPmove( frame_t *frame ) +static void CL_AddLinksToPmove( frame_t *frame ) { entity_state_t *state; model_t *model; @@ -547,7 +512,7 @@ void GAME_EXPORT CL_SetSolidPlayers( int playernum ) physent_t *pe; int i; - if( !cl_solid_players->value ) + if( !cl_solid_players.value ) return; for( i = 0; i < MAX_CLIENTS; i++ ) @@ -583,8 +548,8 @@ void GAME_EXPORT CL_SetSolidPlayers( int playernum ) // some fields needs to be override from cls.predicted_players VectorCopy( player->origin, pe->origin ); VectorCopy( player->angles, pe->angles ); - VectorCopy( clgame.pmove->player_mins[player->usehull], pe->mins ); - VectorCopy( clgame.pmove->player_maxs[player->usehull], pe->maxs ); + VectorCopy( host.player_mins[player->usehull], pe->mins ); + VectorCopy( host.player_maxs[player->usehull], pe->maxs ); pe->movetype = player->movetype; pe->solid = player->solid; } @@ -701,11 +666,6 @@ cl_entity_t *CL_GetWaterEntity( const float *rgflPos ) return CL_GetEntityByIndex( entnum ); } -int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags ) -{ - return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags ); -} - static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) { return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, NULL ); @@ -713,33 +673,7 @@ static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) static void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr ) { - int i; - - for( i = 0; i < clgame.pmove->numtouch; i++ ) - { - if( clgame.pmove->touchindex[i].ent == hitent ) - return; - } - - if( clgame.pmove->numtouch >= MAX_PHYSENTS ) - return; - - VectorCopy( clgame.pmove->velocity, tr->deltavelocity ); - tr->ent = hitent; - - clgame.pmove->touchindex[clgame.pmove->numtouch++] = *tr; -} - -static int GAME_EXPORT pfnPointContents( float *p, int *truecontents ) -{ - int cont, truecont; - - truecont = cont = PM_PointContents( clgame.pmove, p ); - if( truecontents ) *truecontents = truecont; - - if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) - cont = CONTENTS_WATER; - return cont; + PM_StuckTouch( clgame.pmove, hitent, tr ); } static int GAME_EXPORT pfnTruePointContents( float *p ) @@ -747,101 +681,19 @@ static int GAME_EXPORT pfnTruePointContents( float *p ) return PM_TruePointContents( clgame.pmove, p ); } -static int GAME_EXPORT pfnHullPointContents( struct hull_s *hull, int num, float *p ) -{ - return PM_HullPointContents( hull, num, p ); -} - static pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe ) { return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); } -pmtrace_t *PM_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) -{ - static pmtrace_t tr; - int old_usehull; - - old_usehull = clgame.pmove->usehull; - clgame.pmove->usehull = usehull; - - switch( flags ) - { - case PM_TRACELINE_PHYSENTSONLY: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); - break; - case PM_TRACELINE_ANYVISIBLE: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL ); - break; - } - - clgame.pmove->usehull = old_usehull; - - return &tr; -} - -static hull_t *pfnHullForBsp( physent_t *pe, float *offset ) +static void *pfnHullForBsp( physent_t *pe, float *offset ) { return PM_HullForBsp( pe, clgame.pmove, offset ); } static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace ) { - int old_usehull; - vec3_t start_l, end_l; - vec3_t offset, temp; - qboolean rotated; - matrix4x4 matrix; - hull_t *hull; - - PM_InitTrace( trace, end ); - - old_usehull = clgame.pmove->usehull; - clgame.pmove->usehull = 2; - - hull = PM_HullForBsp( pe, clgame.pmove, offset ); - - clgame.pmove->usehull = old_usehull; - - if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) - rotated = true; - else rotated = false; - - if( rotated ) - { - Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); - Matrix4x4_VectorITransform( matrix, start, start_l ); - Matrix4x4_VectorITransform( matrix, end, end_l ); - } - else - { - VectorSubtract( start, offset, start_l ); - VectorSubtract( end, offset, end_l ); - } - - PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace ); - trace->ent = NULL; - - if( rotated ) - { - VectorCopy( trace->plane.normal, temp ); - Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); - } - - VectorLerp( start, trace->fraction, end, trace->endpos ); - - return trace->fraction; -} - -static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) -{ - physent_t *pe; - - if( ground < 0 || ground >= clgame.pmove->numphysent ) - return NULL; // bad ground - - pe = &clgame.pmove->physents[ground]; - return PM_TraceTexture( pe, vstart, vend ); + return PM_TraceModel( clgame.pmove, pe, start, end, trace ); } static void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ) @@ -870,25 +722,7 @@ static int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, p static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) { - static pmtrace_t tr; - int old_usehull; - - old_usehull = clgame.pmove->usehull; - clgame.pmove->usehull = usehull; - - switch( flags ) - { - case PM_TRACELINE_PHYSENTSONLY: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter ); - break; - case PM_TRACELINE_ANYVISIBLE: - tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, -1, pmFilter ); - break; - } - - clgame.pmove->usehull = old_usehull; - - return &tr; + return PM_TraceLineEx( clgame.pmove, start, end, flags, usehull, pmFilter ); } /* @@ -928,23 +762,23 @@ void CL_InitClientMove( void ) clgame.pmove->Con_Printf = Con_Printf; clgame.pmove->Sys_FloatTime = Sys_DoubleTime; clgame.pmove->PM_StuckTouch = pfnStuckTouch; - clgame.pmove->PM_PointContents = pfnPointContents; + clgame.pmove->PM_PointContents = (void*)PM_CL_PointContents; clgame.pmove->PM_TruePointContents = pfnTruePointContents; - clgame.pmove->PM_HullPointContents = pfnHullPointContents; + clgame.pmove->PM_HullPointContents = (void*)PM_HullPointContents; clgame.pmove->PM_PlayerTrace = pfnPlayerTrace; - clgame.pmove->PM_TraceLine = PM_TraceLine; + clgame.pmove->PM_TraceLine = PM_CL_TraceLine; clgame.pmove->RandomLong = COM_RandomLong; clgame.pmove->RandomFloat = COM_RandomFloat; clgame.pmove->PM_GetModelType = pfnGetModelType; clgame.pmove->PM_GetModelBounds = pfnGetModelBounds; - clgame.pmove->PM_HullForBsp = (void*)pfnHullForBsp; + clgame.pmove->PM_HullForBsp = pfnHullForBsp; clgame.pmove->PM_TraceModel = pfnTraceModel; clgame.pmove->COM_FileSize = COM_FileSize; clgame.pmove->COM_LoadFile = COM_LoadFile; clgame.pmove->COM_FreeFile = COM_FreeFile; clgame.pmove->memfgets = COM_MemFgets; clgame.pmove->PM_PlaySound = pfnPlaySound; - clgame.pmove->PM_TraceTexture = pfnTraceTexture; + clgame.pmove->PM_TraceTexture = PM_CL_TraceTexture; clgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull; clgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx; clgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx; @@ -955,26 +789,25 @@ void CL_InitClientMove( void ) clgame.dllFuncs.pfnPlayerMoveInit( clgame.pmove ); } -static void PM_CheckMovingGround( clientdata_t *cd, entity_state_t *state, float frametime ) +static void CL_SetupPMove( playermove_t *pmove, const local_state_t *from, const usercmd_t *ucmd, qboolean runfuncs, double time ) { - if(!( cd->flags & FL_BASEVELOCITY )) - { - // apply momentum (add in half of the previous frame of velocity first) - VectorMA( cd->velocity, 1.0f + (frametime * 0.5f), state->basevelocity, cd->velocity ); - VectorClear( state->basevelocity ); - } - cd->flags &= ~FL_BASEVELOCITY; -} - -void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time ) -{ - entity_state_t *ps; - clientdata_t *cd; + const entity_state_t *ps; + const clientdata_t *cd; ps = &from->playerstate; cd = &from->client; pmove->player_index = ps->number - 1; + + // a1ba: workaround bug where the server refuse to send our local player in delta + // cl.playernum, in theory, must be equal to our local player index anyway + // + // this might not be a real solution, since everything else will be bogus + // but we need to properly run prediction and avoid potential memory + // corruption + if( pmove->player_index < 0 ) + pmove->player_index = bound( 0, cl.playernum, cl.maxclients - 1 ); + pmove->multiplayer = (cl.maxclients > 1); pmove->runfuncs = runfuncs; pmove->time = time * 1000.0f; @@ -986,13 +819,13 @@ void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, q VectorCopy( ps->basevelocity, pmove->basevelocity ); VectorCopy( cd->view_ofs, pmove->view_ofs ); VectorClear( pmove->movedir ); - pmove->flDuckTime = cd->flDuckTime; + pmove->flDuckTime = (float)cd->flDuckTime; pmove->bInDuck = cd->bInDuck; pmove->usehull = ps->usehull; pmove->flTimeStepSound = cd->flTimeStepSound; pmove->iStepLeft = ps->iStepLeft; pmove->flFallVelocity = ps->flFallVelocity; - pmove->flSwimTime = cd->flSwimTime; + pmove->flSwimTime = (float)cd->flSwimTime; VectorCopy( cd->punchangle, pmove->punchangle ); pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code pmove->effects = ps->effects; @@ -1000,7 +833,7 @@ void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, q pmove->gravity = ps->gravity; pmove->friction = ps->friction; pmove->oldbuttons = ps->oldbuttons; - pmove->waterjumptime = cd->waterjumptime; + pmove->waterjumptime = (float)cd->waterjumptime; pmove->dead = (cl.local.health <= 0); pmove->deadflag = cd->deadflag; pmove->spectator = (cls.spectator != 0); @@ -1024,10 +857,10 @@ void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, q VectorCopy( cd->vuser4, pmove->vuser4 ); pmove->cmd = *ucmd; // copy current cmds - Q_strncpy( pmove->physinfo, cls.physinfo, MAX_INFO_STRING ); + Q_strncpy( pmove->physinfo, cls.physinfo, sizeof( pmove->physinfo )); } -void CL_FinishPMove( playermove_t *pmove, local_state_t *to ) +static const void CL_FinishPMove( const playermove_t *pmove, local_state_t *to ) { entity_state_t *ps; clientdata_t *cd; @@ -1038,7 +871,7 @@ void CL_FinishPMove( playermove_t *pmove, local_state_t *to ) cd->flags = pmove->flags; cd->bInDuck = pmove->bInDuck; cd->flTimeStepSound = pmove->flTimeStepSound; - cd->flDuckTime = pmove->flDuckTime; + cd->flDuckTime = (int)pmove->flDuckTime; cd->flSwimTime = (int)pmove->flSwimTime; cd->waterjumptime = (int)pmove->waterjumptime; cd->watertype = pmove->watertype; @@ -1051,7 +884,7 @@ void CL_FinishPMove( playermove_t *pmove, local_state_t *to ) VectorCopy( pmove->angles, ps->angles ); VectorCopy( pmove->basevelocity, ps->basevelocity ); VectorCopy( pmove->punchangle, cd->punchangle ); - ps->oldbuttons = pmove->cmd.buttons; + ps->oldbuttons = (uint)pmove->cmd.buttons; ps->friction = pmove->friction; ps->movetype = pmove->movetype; ps->onground = pmove->onground; @@ -1080,7 +913,7 @@ CL_RunUsercmd Runs prediction code for user cmd ================= */ -void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed ) +static void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed ) { usercmd_t cmd; @@ -1140,7 +973,7 @@ void CL_MoveSpectatorCamera( void ) CL_SetUpPlayerPrediction( false, true ); CL_SetSolidPlayers( cl.playernum ); - CL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, cl.cmd, true, &time, (uint)( time * 100.0 )); + CL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, &cl.cmd, true, &time, (uint)( time * 100.0 )); VectorCopy( cls.spectator_state.client.velocity, cl.simvel ); VectorCopy( cls.spectator_state.client.origin, cl.simorg ); @@ -1159,13 +992,9 @@ void CL_PredictMovement( qboolean repredicting ) { runcmd_t *to_cmd = NULL, *from_cmd; local_state_t *from = NULL, *to = NULL; - uint current_command; - uint current_command_mod; - frame_t *frame = NULL; + frame_t *frame = NULL; uint i, stoppoint; - qboolean runfuncs; double f = 1.0; - cl_entity_t *ent; double time; if( cls.state != ca_active || cls.spectator ) @@ -1176,7 +1005,7 @@ void CL_PredictMovement( qboolean repredicting ) CL_SetUpPlayerPrediction( false, false ); - if( cls.state != ca_active || !cl.validsequence ) + if( !cl.validsequence ) return; if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) @@ -1217,6 +1046,10 @@ void CL_PredictMovement( qboolean repredicting ) for( i = 1; i < CL_UPDATE_MASK && cls.netchan.incoming_acknowledged + i < cls.netchan.outgoing_sequence + stoppoint; i++ ) { + uint current_command; + uint current_command_mod; + qboolean runfuncs; + current_command = cls.netchan.incoming_acknowledged + i; current_command_mod = current_command & CL_UPDATE_MASK; @@ -1256,7 +1089,7 @@ void CL_PredictMovement( qboolean repredicting ) cl.local.onground = frame->playerstate[cl.playernum].onground; else cl.local.onground = -1; - if( !repredicting || !CVAR_TO_BOOL( cl_lw )) + if( !repredicting || !cl_lw.value ) cl.local.viewmodel = to->client.viewmodel; cl.local.repredicting = false; cl.local.moving = false; @@ -1288,12 +1121,12 @@ void CL_PredictMovement( qboolean repredicting ) cl.local.waterlevel = to->client.waterlevel; cl.local.usehull = to->playerstate.usehull; - if( !repredicting || !CVAR_TO_BOOL( cl_lw )) + if( !repredicting || !cl_lw.value ) cl.local.viewmodel = to->client.viewmodel; if( FBitSet( to->client.flags, FL_ONGROUND )) { - ent = CL_GetEntityByIndex( cl.local.lastground ); + cl_entity_t *ent = CL_GetEntityByIndex( cl.local.lastground ); cl.local.onground = cl.local.lastground; cl.local.moving = false; @@ -1315,27 +1148,27 @@ void CL_PredictMovement( qboolean repredicting ) else { cl.local.onground = -1; - cl.local.moving = 0; + cl.local.moving = false; } - if( cls.correction_time > 0 && !cl_nosmooth->value && cl_smoothtime->value ) + if( cls.correction_time > 0 && !cl_nosmooth.value && cl_smoothtime.value ) { - vec3_t delta; - float frac; + vec3_t delta; + float frac; // only decay timer once per frame if( !repredicting ) cls.correction_time -= host.frametime; // Make sure smoothtime is postive - if( cl_smoothtime->value <= 0.0f ) - Cvar_DirectSet( cl_smoothtime, "0.1" ); + if( cl_smoothtime.value <= 0.0f ) + Cvar_DirectSet( &cl_smoothtime, "0.1" ); // Clamp from 0 to cl_smoothtime.value - cls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime->value ); + cls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime.value ); // Compute backward interpolation fraction along full correction - frac = 1.0f - cls.correction_time / cl_smoothtime->value; + frac = 1.0f - cls.correction_time / cl_smoothtime.value; // Determine how much error we still have to make up for VectorSubtract( cl.simorg, cl.local.lastorigin, delta ); diff --git a/engine/client/cl_qparse.c b/engine/client/cl_qparse.c index 76865362..b294bfa8 100644 --- a/engine/client/cl_qparse.c +++ b/engine/client/cl_qparse.c @@ -22,22 +22,24 @@ GNU General Public License for more details. #include "hltv.h" #include "input.h" -#define STAT_HEALTH 0 -#define STAT_FRAGS 1 -#define STAT_WEAPON 2 -#define STAT_AMMO 3 -#define STAT_ARMOR 4 -#define STAT_WEAPONFRAME 5 -#define STAT_SHELLS 6 -#define STAT_NAILS 7 -#define STAT_ROCKETS 8 -#define STAT_CELLS 9 -#define STAT_ACTIVEWEAPON 10 -#define STAT_TOTALSECRETS 11 -#define STAT_TOTALMONSTERS 12 -#define STAT_SECRETS 13 // bumped on client side by svc_foundsecret -#define STAT_MONSTERS 14 // bumped by svc_killedmonster -#define MAX_STATS 32 +enum { + STAT_HEALTH = 0, + STAT_FRAGS, + STAT_WEAPON, + STAT_AMMO, + STAT_ARMOR, + STAT_WEAPONFRAME, + STAT_SHELLS, + STAT_NAILS, + STAT_ROCKETS, + STAT_CELLS, + STAT_ACTIVEWEAPON, + STAT_TOTALSECRETS, + STAT_TOTALMONSTERS, + STAT_SECRETS, // bumped on client side by svc_foundsecret + STAT_MONSTERS, // bumped by svc_killedmonster + MAX_STATS = 32, +}; static char cmd_buf[8192]; static char msg_buf[8192]; @@ -211,7 +213,7 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) clgame.maxEntities = GI->max_edicts; clgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS ); clgame.maxModels = MAX_MODELS; - Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING ); + Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), sizeof( clgame.maptitle )); // Re-init hud video, especially if we changed game directories clgame.dllFuncs.pfnVidInit(); @@ -232,14 +234,10 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) // loading user settings CSCR_LoadDefaultCVars( "user.scr" ); - if( r_decals->value > mp_decals.value ) - Cvar_SetValue( "r_decals", mp_decals.value ); + if( r_decals.value > mp_decals.value ) + Cvar_DirectSet( &r_decals, mp_decals.string ); } - else Cvar_Reset( "r_decals" ); - - // re-init mouse - if( cl.background ) - host.mouse_visible = false; + else Cvar_DirectSet( &r_decals, NULL ); if( cl.background ) // tell the game parts about background state Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); @@ -262,7 +260,7 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle )); if( !cls.changelevel && !cls.changedemo ) - CL_InitEdicts (); // re-arrange edicts + CL_InitEdicts( cl.maxclients ); // re-arrange edicts // Quake just have a large packet of initialization data for( i = 1; i < MAX_MODELS; i++ ) @@ -306,9 +304,9 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" )); Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar - if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + if(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background ) { - if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true )) + if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name.string ), true )) Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime) } @@ -332,7 +330,7 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg ) clgame.movevars.gravity = 800.0f; // quake doesn't write gravity in demos clgame.movevars.maxvelocity = 2000.0f; - memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t )); + clgame.oldmovevars = clgame.movevars; } /* @@ -418,7 +416,7 @@ If an entities model or origin changes from frame to frame, it must be relinked. Other attributes can change without relinking. ================== */ -void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits ) +static void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits ) { int i, newnum, pack; qboolean forcelink; @@ -433,7 +431,7 @@ void CL_ParseQuakeEntityData( sizebuf_t *msg, int bits ) cls.signon = SIGNONS; // Clear loading plaque. - CL_SignonReply (); + CL_SignonReply( PROTO_QUAKE ); } // alloc next slot to store update @@ -569,7 +567,7 @@ CL_ParseQuakeParticles ================== */ -void CL_ParseQuakeParticle( sizebuf_t *msg ) +static void CL_ParseQuakeParticle( sizebuf_t *msg ) { int count, color; vec3_t org, dir; @@ -591,7 +589,7 @@ CL_ParseQuakeStaticSound =================== */ -void CL_ParseQuakeStaticSound( sizebuf_t *msg ) +static void CL_ParseQuakeStaticSound( sizebuf_t *msg ) { int sound_num; float vol, attn; @@ -624,17 +622,18 @@ static void CL_ParseQuakeDamage( sizebuf_t *msg ) /* =================== -CL_ParseQuakeStaticEntity +CL_ParseStaticEntity =================== */ static void CL_ParseQuakeStaticEntity( sizebuf_t *msg ) { - entity_state_t state; + entity_state_t state = { 0 }; cl_entity_t *ent; int i; - memset( &state, 0, sizeof( state )); + if( !clgame.static_entities ) + clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); state.modelindex = MSG_ReadByte( msg ); state.frame = MSG_ReadByte( msg ); @@ -650,7 +649,7 @@ static void CL_ParseQuakeStaticEntity( sizebuf_t *msg ) i = clgame.numStatics; if( i >= MAX_STATIC_ENTITIES ) { - Con_Printf( S_ERROR "CL_ParseStaticEntity: static entities limit exceeded!\n" ); + Con_Printf( S_ERROR "%s: static entities limit exceeded!\n", __func__ ); return; } @@ -698,17 +697,13 @@ static void CL_ParseQuakeBaseline( sizebuf_t *msg ) cl_entity_t *ent; int newnum; - memset( &state, 0, sizeof( state )); newnum = MSG_ReadWord( msg ); // entnum if( newnum >= clgame.maxEntities ) - Host_Error( "CL_AllocEdict: no free edicts\n" ); - - ent = CL_EDICT_NUM( newnum ); - memset( &ent->prevstate, 0, sizeof( ent->prevstate )); - ent->index = newnum; + Host_Error( "%s: no free edicts\n", __func__ ); // parse baseline + memset( &state, 0, sizeof( state )); state.modelindex = MSG_ReadByte( msg ); state.frame = MSG_ReadByte( msg ); state.colormap = MSG_ReadByte( msg ); @@ -719,10 +714,11 @@ static void CL_ParseQuakeBaseline( sizebuf_t *msg ) state.angles[1] = MSG_ReadAngle( msg ); state.origin[2] = MSG_ReadCoord( msg ); state.angles[2] = MSG_ReadAngle( msg ); - ent->player = CL_IsPlayerIndex( newnum ); - memcpy( &ent->baseline, &state, sizeof( entity_state_t )); - memcpy( &ent->prevstate, &state, sizeof( entity_state_t )); + ent = CL_EDICT_NUM( newnum ); + ent->index = newnum; + ent->player = CL_IsPlayerIndex( newnum ); + ent->prevstate = ent->baseline = state; } /* @@ -784,7 +780,7 @@ static void CL_ParseQuakeSignon( sizebuf_t *msg ) int i = MSG_ReadByte( msg ); if( i == 3 ) cls.signon = SIGNONS - 1; - Con_Reportf( "CL_Signon: %d\n", i ); + Con_Reportf( "%s: %d\n", __func__, i ); } /* @@ -822,7 +818,7 @@ CL_QuakeStuffText ================== */ -void CL_QuakeStuffText( const char *text ) +static void CL_QuakeStuffText( const char *text ) { Q_strncat( cmd_buf, text, sizeof( cmd_buf )); @@ -837,7 +833,7 @@ CL_QuakeExecStuff ================== */ -void CL_QuakeExecStuff( void ) +static void CL_QuakeExecStuff( void ) { char *text = cmd_buf; char token[256]; @@ -850,7 +846,7 @@ void CL_QuakeExecStuff( void ) while( 1 ) { // skip whitespace up to a /n - while( *text && ((byte)*text) <= ' ' && *text != '\r' && *text != '\n' ) + while( *text && ((byte)*text ) <= ' ' && *text != '\r' && *text != '\n' ) text++; if( *text == '\n' || *text == '\r' ) @@ -892,38 +888,21 @@ CL_ParseQuakeMessage ================== */ -void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ) +void CL_ParseQuakeMessage( sizebuf_t *msg ) { int cmd, param1, param2; size_t bufStart; const char *str; - cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame - CL_Parse_Debug( true ); // begin parsing - // init excise buffer MSG_Init( &msg_demo, "UserMsg", msg_buf, sizeof( msg_buf )); - if( normal_message ) - { - // assume no entity/player update this packet - if( cls.state == ca_active ) - { - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; - cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; - } - else - { - CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); - } - } - // parse the message while( 1 ) { if( MSG_CheckOverflow( msg )) { - Host_Error( "CL_ParseServerMessage: overflow!\n" ); + Host_Error( "%s: overflow!\n", __func__ ); return; } @@ -972,7 +951,7 @@ void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ) break; case svc_time: Cbuf_AddText( "\n" ); // new frame was started - CL_ParseServerTime( msg ); + CL_ParseServerTime( msg, PROTO_QUAKE ); break; case svc_print: str = MSG_ReadString( msg ); @@ -991,9 +970,7 @@ void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ) CL_ParseQuakeServerInfo( msg ); break; case svc_lightstyle: - param1 = MSG_ReadByte( msg ); - str = MSG_ReadString( msg ); - CL_SetLightstyle( param1, str, cl.mtime[0] ); + CL_ParseLightStyle( msg, PROTO_QUAKE ); break; case svc_updatename: param1 = MSG_ReadByte( msg ); @@ -1114,14 +1091,11 @@ void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ) } break; default: - Host_Error( "CL_ParseServerMessage: Illegible server message\n" ); + Host_Error( "%s: Illegible server message\n", __func__ ); break; } } - cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; - CL_Parse_Debug( false ); // done - // now process packet. CL_ProcessPacket( &cl.frames[cl.parsecountmod] ); diff --git a/engine/client/cl_remap.c b/engine/client/cl_remap.c index 3a9c74c8..d8aec4f1 100644 --- a/engine/client/cl_remap.c +++ b/engine/client/cl_remap.c @@ -41,11 +41,24 @@ CL_CmpStudioTextures return true if equal ==================== */ -qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, mstudiotexture_t *p2 ) +static qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, remap_info_t *remap ) { int i; + mstudiotexture_t *p2; - if( !p1 || !p2 ) return false; + if( !p1 ) // no textures + return false; + + if( !remap ) // current model has no remap + return false; + + if( !remap->textures ) // shouldn't happen, just in case + return false; + + if( numtexs != remap->numtextures ) // amount of textures differs, it's a different model + return false; + + p2 = remap->ptexture; for( i = 0; i < numtexs; i++, p1++, p2++ ) { @@ -65,7 +78,7 @@ CL_CreateRawTextureFromPixels Convert texture_t struct into mstudiotexture_t prototype ==================== */ -byte *CL_CreateRawTextureFromPixels( texture_t *tx, size_t *size, int topcolor, int bottomcolor ) +static byte *CL_CreateRawTextureFromPixels( texture_t *tx, size_t *size, int topcolor, int bottomcolor ) { static mstudiotexture_t pin; byte *pal; @@ -98,7 +111,7 @@ CL_DuplicateTexture Dupliacte texture with remap pixels ==================== */ -void CL_DuplicateTexture( cl_entity_t *entity, model_t *model, mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) +static void CL_DuplicateTexture( cl_entity_t *entity, model_t *model, mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) { const char *name; texture_t *tx = NULL; @@ -121,7 +134,8 @@ void CL_DuplicateTexture( cl_entity_t *entity, model_t *model, mstudiotexture_t break; // found } - Assert( tx != NULL ); + if( !tx ) + return; // backup original palette pal = (byte *)(tx + 1) + (tx->width * tx->height); @@ -141,7 +155,7 @@ CL_UpdateStudioTexture Update texture top and bottom colors ==================== */ -void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) +static void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) { rgbdata_t *pic; texture_t *tx = NULL; @@ -157,12 +171,13 @@ void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, in // build name of original texture Q_strncpy( mdlname, entity->model->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); index = ref.dllFuncs.GL_FindTexture( texname ); - if( !index ) return; // couldn't find texture + if( !index ) + return; // couldn't find texture // search for pixels for( i = 0; i < entity->model->numtextures; i++ ) @@ -172,7 +187,8 @@ void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, in break; // found } - Assert( tx != NULL ); + if( !tx ) + return; // couldn't find texture // backup original palette pal = (byte *)(tx + 1) + (tx->width * tx->height); @@ -202,7 +218,7 @@ CL_UpdateAliasTexture Update texture top and bottom colors ==================== */ -void CL_UpdateAliasTexture( cl_entity_t *entity, unsigned short *texture, int skinnum, int topcolor, int bottomcolor ) +static void CL_UpdateAliasTexture( cl_entity_t *entity, unsigned short *texture, int skinnum, int topcolor, int bottomcolor ) { char texname[MAX_QPATH]; rgbdata_t skin, *pic; @@ -234,6 +250,68 @@ void CL_UpdateAliasTexture( cl_entity_t *entity, unsigned short *texture, int sk ref.dllFuncs.GL_ProcessTexture( *texture, -1.0f, topcolor, bottomcolor ); } +/* +==================== +CL_FreeRemapInfo + +Release remap info per entity +==================== +*/ +static void CL_FreeRemapInfo( remap_info_t *info ) +{ + int i; + + Assert( info != NULL ); + + // release all colormap texture copies + for( i = 0; i < info->numtextures; i++ ) + { + if( info->ptexture != NULL ) + { + if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) + ref.dllFuncs.GL_FreeTexture( info->ptexture[i].index ); + } + + if( info->textures[i] != 0 ) + ref.dllFuncs.GL_FreeTexture( info->textures[i] ); + } + + Mem_Free( info ); // release struct +} + +/* +==================== +CL_UpdateRemapInfo + +Update all remaps per entity +==================== +*/ +static void CL_UpdateRemapInfo( cl_entity_t *entity, int topcolor, int bottomcolor ) +{ + remap_info_t *info; + int i; + + i = ( entity == &clgame.viewent ) ? clgame.maxEntities : entity->curstate.number; + info = clgame.remap_info[i]; + if( !info ) return; // no remap info + + if( info->topcolor == topcolor && info->bottomcolor == bottomcolor ) + return; // values is valid + + for( i = 0; i < info->numtextures; i++ ) + { + if( info->ptexture != NULL ) + { + if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) + CL_UpdateStudioTexture( entity, &info->ptexture[i], topcolor, bottomcolor ); + } + else CL_UpdateAliasTexture( entity, &info->textures[i], i, topcolor, bottomcolor ); + } + + info->topcolor = topcolor; + info->bottomcolor = bottomcolor; +} + /* ==================== CL_AllocRemapInfo @@ -242,7 +320,7 @@ Allocate new remap info per entity and make copy of remap textures ==================== */ -void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int bottomcolor ) +static void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int bottomcolor ) { remap_info_t *info; studiohdr_t *phdr; @@ -282,10 +360,9 @@ void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int b if( !phdr ) return; // bad model? src = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); - dst = (clgame.remap_info[i] ? clgame.remap_info[i]->ptexture : NULL); // NOTE: we must copy all the structures 'mstudiotexture_t' for easy access when model is rendering - if( !CL_CmpStudioTextures( phdr->numtextures, src, dst ) || clgame.remap_info[i]->model != model ) + if( !CL_CmpStudioTextures( phdr->numtextures, src, clgame.remap_info[i] ) || clgame.remap_info[i]->model != model ) { // this code catches studiomodel change with another studiomodel with remap textures // e.g. playermodel 'barney' with playermodel 'gordon' @@ -350,68 +427,6 @@ void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int b info->model = model; } -/* -==================== -CL_UpdateRemapInfo - -Update all remaps per entity -==================== -*/ -void CL_UpdateRemapInfo( cl_entity_t *entity, int topcolor, int bottomcolor ) -{ - remap_info_t *info; - int i; - - i = ( entity == &clgame.viewent ) ? clgame.maxEntities : entity->curstate.number; - info = clgame.remap_info[i]; - if( !info ) return; // no remap info - - if( info->topcolor == topcolor && info->bottomcolor == bottomcolor ) - return; // values is valid - - for( i = 0; i < info->numtextures; i++ ) - { - if( info->ptexture != NULL ) - { - if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) - CL_UpdateStudioTexture( entity, &info->ptexture[i], topcolor, bottomcolor ); - } - else CL_UpdateAliasTexture( entity, &info->textures[i], i, topcolor, bottomcolor ); - } - - info->topcolor = topcolor; - info->bottomcolor = bottomcolor; -} - -/* -==================== -CL_FreeRemapInfo - -Release remap info per entity -==================== -*/ -void CL_FreeRemapInfo( remap_info_t *info ) -{ - int i; - - Assert( info != NULL ); - - // release all colormap texture copies - for( i = 0; i < info->numtextures; i++ ) - { - if( info->ptexture != NULL ) - { - if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) - ref.dllFuncs.GL_FreeTexture( info->ptexture[i].index ); - } - - if( info->textures[i] != 0 ) - ref.dllFuncs.GL_FreeTexture( info->textures[i] ); - } - - Mem_Free( info ); // release struct -} - /* ==================== CL_ClearAllRemaps @@ -434,3 +449,21 @@ void CL_ClearAllRemaps( void ) } clgame.remap_info = NULL; } + +/* +============= +CL_EntitySetRemapColors +============= +*/ +qboolean CL_EntitySetRemapColors( cl_entity_t *e, model_t *mod, int top, int bottom ) +{ + CL_AllocRemapInfo( e, mod, top, bottom ); + + if( CL_GetRemapInfoForEntity( e )) + { + CL_UpdateRemapInfo( e, top, bottom ); + return true; + } + + return false; +} diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 4351cde1..0418d5c2 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -20,7 +20,7 @@ GNU General Public License for more details. int R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ) { - return Mod_FatPVS( org, radius, visbuffer, world.visbytes, merge, fullvis ); + return Mod_FatPVS( org, radius, visbuffer, world.visbytes, merge, fullvis, false ); } lightstyle_t *CL_GetLightStyle( int number ) @@ -126,7 +126,7 @@ CL_GenericHandle ============= */ -const char *CL_GenericHandle( int fileindex ) +static const char *CL_GenericHandle( int fileindex ) { if( fileindex < 0 || fileindex >= MAX_CUSTOM ) return 0; @@ -138,12 +138,7 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe switch( parm ) { case PARM_BSP2_SUPPORTED: -#ifdef SUPPORT_BSP2_FORMAT return 1; -#endif - return 0; - case PARM_SKY_SPHERE: - return FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX ); case PARAM_GAMEPAUSED: return cl.paused; case PARM_CLIENT_INGAME: @@ -164,6 +159,20 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe return (intptr_t)world.deluxedata; case PARM_SHADOWDATA: return (intptr_t)world.shadowdata; + case PARM_FULLSCREEN: + return refState.fullScreen; + case PARM_WIDESCREEN: + return refState.wideScreen; + case PARM_SCREEN_WIDTH: + return refState.width; + case PARM_SCREEN_HEIGHT: + return refState.height; + case PARM_SKY_SPHERE: + return FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX ); + case PARM_SURF_SAMPLESIZE: + if( arg >= 0 && arg < cl.worldmodel->numsurfaces ) + return Mod_SampleSizeForFace( &cl.worldmodel->surfaces[arg] ); + return LM_SAMPLE_SIZE; default: // indicates call from client.dll if( checkRef ) @@ -179,26 +188,41 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe return CL_IsThirdPerson(); case PARM_QUAKE_COMPATIBLE: return Host_IsQuakeCompatible(); - case PARM_PLAYER_INDEX: - return cl.playernum + 1; - case PARM_VIEWENT_INDEX: - return cl.viewentity; case PARM_CONNSTATE: return (int)cls.state; case PARM_PLAYING_DEMO: return cls.demoplayback; case PARM_WATER_LEVEL: return cl.local.waterlevel; - case PARM_MAX_CLIENTS: - return cl.maxclients; case PARM_LOCAL_HEALTH: return cl.local.health; case PARM_LOCAL_GAME: return Host_IsLocalGame(); case PARM_NUMENTITIES: return pfnNumberOfEntities(); - case PARM_NUMMODELS: - return cl.nummodels; + case PARM_GET_CLIENT_PTR: + return (intptr_t)&cl.time; // with the offset + case PARM_GET_HOST_PTR: + return (intptr_t)&host.realtime; // with the offset + case PARM_GET_WORLD_PTR: + return (intptr_t)&world; + case PARM_GET_MOVEVARS_PTR: + return (intptr_t)&clgame.movevars; + case PARM_GET_PALETTE_PTR: + return (intptr_t)&clgame.palette; + case PARM_GET_VIEWENT_PTR: + return (intptr_t)&clgame.viewent; + case PARM_GET_TEXGAMMATABLE_PTR: + case PARM_GET_LIGHTGAMMATABLE_PTR: + case PARM_GET_SCREENGAMMATABLE_PTR: + case PARM_GET_LINEARGAMMATABLE_PTR: + return V_GetGammaPtr( parm ); + case PARM_GET_LIGHTSTYLES_PTR: + return (intptr_t)CL_GetLightStyle( 0 ); + case PARM_GET_DLIGHTS_PTR: + return (intptr_t)CL_GetDynamicLight( 0 ); + case PARM_GET_ELIGHTS_PTR: + return (intptr_t)CL_GetEntityLight( 0 ); } } return 0; @@ -209,6 +233,11 @@ static intptr_t pfnRenderGetParm( int parm, int arg ) return CL_RenderGetParm( parm, arg, true ); } +static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, float attn, float synctime ) +{ + return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound +} + static render_api_t gRenderAPI = { pfnRenderGetParm, // GL_RenderGetParm, @@ -234,16 +263,16 @@ static render_api_t gRenderAPI = NULL, // DrawSingleDecal, NULL, // R_DecalSetupVerts, NULL, // R_EntityRemoveDecals, - (void*)AVI_LoadVideo, - (void*)AVI_GetVideoInfo, - (void*)AVI_GetVideoFrameNumber, - (void*)AVI_GetVideoFrame, + AVI_LoadVideo, + AVI_GetVideoInfo, + AVI_GetVideoFrameNumber, + AVI_GetVideoFrame, NULL, // R_UploadStretchRaw, - (void*)AVI_FreeVideo, - (void*)AVI_IsActive, - S_StreamAviSamples, - NULL, - NULL, + AVI_FreeVideo, + AVI_IsActive, + pfnAVI_StreamSound, + AVI_Think, + AVI_SetParm, NULL, // GL_Bind, NULL, // GL_SelectTexture, NULL, // GL_LoadTexMatrixExt, @@ -269,7 +298,7 @@ static render_api_t gRenderAPI = R_Mem_Free, pfnGetFilesList, pfnFileBufferCRC32, - COM_CompareFileTime, + pfnCompareFileTime, Host_Error, (void*)CL_ModelHandle, pfnTime, @@ -331,7 +360,7 @@ qboolean R_InitRenderAPI( void ) { if( clgame.dllFuncs.pfnGetRenderInterface( CL_RENDER_INTERFACE_VERSION, &gRenderAPI, &clgame.drawFuncs )) { - Con_Reportf( "CL_LoadProgs: ^2initailized extended RenderAPI ^7ver. %i\n", CL_RENDER_INTERFACE_VERSION ); + Con_Reportf( "%s: ^2initailized extended RenderAPI ^7ver. %i\n", __func__, CL_RENDER_INTERFACE_VERSION ); return true; } diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index ee212d46..ea6017a9 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -20,18 +20,20 @@ GNU General Public License for more details. #include "input.h" #include "library.h" -convar_t *scr_centertime; -convar_t *scr_loading; -convar_t *scr_download; -convar_t *scr_viewsize; -convar_t *cl_testlights; -convar_t *cl_allow_levelshots; -convar_t *cl_levelshot_name; -static convar_t *cl_envshot_size; -convar_t *v_dark; -static convar_t *net_speeds; -static convar_t *cl_showfps; -static convar_t *cl_showpos; +CVAR_DEFINE_AUTO( scr_centertime, "2.5", 0, "centerprint hold time" ); +CVAR_DEFINE_AUTO( scr_loading, "0", 0, "loading bar progress" ); +CVAR_DEFINE_AUTO( scr_download, "-1", 0, "downloading bar progress" ); +CVAR_DEFINE( scr_viewsize, "viewsize", "120", FCVAR_ARCHIVE, "screen size (quake only)" ); +CVAR_DEFINE_AUTO( cl_testlights, "0", FCVAR_CHEAT, "test dynamic lights" ); +CVAR_DEFINE( cl_allow_levelshots, "allow_levelshots", "0", FCVAR_ARCHIVE, "allow engine to use indivdual levelshots instead of 'loading' image" ); +CVAR_DEFINE_AUTO( cl_levelshot_name, "*black", 0, "contains path to current levelshot" ); +static CVAR_DEFINE_AUTO( cl_envshot_size, "256", FCVAR_ARCHIVE, "envshot size of cube side" ); +CVAR_DEFINE_AUTO( v_dark, "0", 0, "starts level from dark screen" ); +static CVAR_DEFINE_AUTO( net_speeds, "0", FCVAR_ARCHIVE, "show network packets" ); +static CVAR_DEFINE_AUTO( cl_showfps, "0", FCVAR_ARCHIVE, "show client fps" ); +static CVAR_DEFINE_AUTO( cl_showpos, "0", FCVAR_ARCHIVE, "show local player position and velocity" ); +static CVAR_DEFINE_AUTO( cl_showents, "0", FCVAR_ARCHIVE | FCVAR_CHEAT, "show entities information (largely undone)" ); +static CVAR_DEFINE_AUTO( cl_showcmd, "0", 0, "visualize usercmd button presses" ); typedef struct { @@ -59,7 +61,7 @@ void SCR_DrawFPS( int height ) char fpsstring[64]; int offset; - if( cls.state != ca_active || !cl_showfps->value || cl.background ) + if( cls.state != ca_active || !cl_showfps.value || cl.background ) return; switch( cls.scrshot_action ) @@ -95,7 +97,7 @@ void SCR_DrawFPS( int height ) if( curfps < minfps ) minfps = curfps; if( curfps > maxfps ) maxfps = curfps; - if( cl_showfps->value == 2 ) + if( cl_showfps.value == 2 ) Q_snprintf( fpsstring, sizeof( fpsstring ), "fps: ^1%4i min, ^3%4i cur, ^2%4i max", minfps, curfps, maxfps ); else Q_snprintf( fpsstring, sizeof( fpsstring ), "%4i fps", curfps ); MakeRGBA( color, 255, 255, 255, 255 ); @@ -119,7 +121,7 @@ void SCR_DrawPos( void ) cl_entity_t *ent; rgba_t color; - if( cls.state != ca_active || !cl_showpos->value || cl.background ) + if( cls.state != ca_active || !cl_showpos.value || cl.background ) return; ent = CL_GetLocalPlayer(); @@ -140,6 +142,131 @@ void SCR_DrawPos( void ) Con_DrawString( refState.width / 2, 4, msg, color ); } +/* +============== +SCR_DrawEnts +============== +*/ +void SCR_DrawEnts( void ) +{ + rgba_t color = { 255, 255, 255, 255 }; + int i; + + if( cls.state != ca_active || !cl_showents.value || ( cl.maxclients > 1 && !cls.demoplayback )) + return; + + // this probably better hook CL_AddVisibleEntities + // as entities might get added by client.dll + for( i = 0; i < clgame.maxEntities; i++ ) + { + const cl_entity_t *ent = &clgame.entities[i]; + string msg; + vec3_t screen, pos; + + if( ent->curstate.messagenum != cl.parsecount ) + continue; + + VectorCopy( ent->origin, pos ); + + if( ent->model != NULL ) + { + vec3_t v; + + // simple model type filter + if( cl_showents.value > 1 ) + { + if( ent->model->type != (modtype_t)( cl_showents.value - 2 )) + continue; + } + + VectorAverage( ent->model->mins, ent->model->maxs, v ); + VectorAdd( pos, v, pos ); + } + + if( !ref.dllFuncs.WorldToScreen( pos, screen )) + { + Q_snprintf( msg, sizeof( msg ), + "entity %d\n" + "model %s\n" + "movetype %d\n", + ent->index, + ent->model ? ent->model->name : "(null)", + ent->curstate.movetype ); + + screen[0] = 0.5f * screen[0] * refState.width; + screen[1] = -0.5f * screen[1] * refState.height; + screen[0] += 0.5f * refState.width; + screen[1] += 0.5f * refState.height; + + Con_DrawString( screen[0], screen[1], msg, color ); + } + } +} + +/* +============== +SCR_DrawUserCmd + +another debugging aids, shows pressed buttons +============== +*/ +void SCR_DrawUserCmd( void ) +{ + runcmd_t *pcmd = &cl.commands[( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK]; + struct + { + int mask; + const char *name; + } buttons[16] = + { + { IN_ATTACK, "attack" }, + { IN_JUMP, "jump" }, + { IN_DUCK, "duck" }, + { IN_FORWARD, "forward" }, + { IN_BACK, "back" }, + { IN_USE, "use" }, + { IN_CANCEL, "cancel" }, + { IN_LEFT, "left" }, + { IN_RIGHT, "right" }, + { IN_MOVELEFT, "moveleft" }, + { IN_MOVERIGHT, "moveright" }, + { IN_ATTACK2, "attack2" }, + { IN_RUN, "run" }, + { IN_RELOAD, "reload" }, + { IN_ALT1, "alt1" }, + { IN_SCORE, "score" }, + }; + cl_font_t *font = Con_GetCurFont(); + string msg; + int i, ypos = 100; + + if( cls.state != ca_active || !cl_showcmd.value ) + return; + + for( i = 0; i < ARRAYSIZE( buttons ); i++ ) + { + rgba_t rgba; + + rgba[0] = FBitSet( pcmd->cmd.buttons, buttons[i].mask ) ? 0 : 255; + rgba[1] = FBitSet( pcmd->cmd.buttons, buttons[i].mask ) ? 255 : 0; + rgba[2] = 0; + rgba[3] = 255; + + Con_DrawString( 100, ypos, buttons[i].name, rgba ); + + ypos += font->charHeight; + } + + Q_snprintf( msg, sizeof( msg ), + "F/S/U: %g %g %g\n" + "impulse: %u\n" + "msec: %u", + pcmd->cmd.forwardmove, pcmd->cmd.sidemove, pcmd->cmd.upmove, + pcmd->cmd.impulse, + pcmd->cmd.msec ); + Con_DrawString( 100, ypos, msg, g_color_table[7] ); +} + /* ============== SCR_NetSpeeds @@ -150,8 +277,7 @@ same as r_speeds but for network channel void SCR_NetSpeeds( void ) { static char msg[MAX_SYSPATH]; - int x, y, height; - char *p, *start, *end; + int x, y; float time = cl.mtime[0]; static int min_svfps = 100; static int max_svfps = 0; @@ -160,11 +286,12 @@ void SCR_NetSpeeds( void ) static int max_clfps = 0; int cur_clfps = 0; rgba_t color; + cl_font_t *font = Con_GetCurFont(); if( !host.allow_console ) return; - if( !net_speeds->value || cls.state != ca_active ) + if( !net_speeds.value || cls.state != ca_active ) return; // prevent to get too big values at max @@ -196,25 +323,11 @@ void SCR_NetSpeeds( void ) Q_memprint( cls.netchan.total_sended ) ); - x = refState.width - 320; + x = refState.width - 320 * font->scale; y = 384; - Con_DrawStringLen( NULL, NULL, &height ); MakeRGBA( color, 255, 255, 255, 255 ); - - p = start = msg; - - do - { - end = Q_strchr( p, '\n' ); - if( end ) msg[end-start] = '\0'; - - Con_DrawString( x, y, p, color ); - y += height; - - if( end ) p = end + 1; - else break; - } while( 1 ); + CL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF ); } /* @@ -231,31 +344,15 @@ void SCR_RSpeeds( void ) if( ref.dllFuncs.R_SpeedsMessage( msg, sizeof( msg ))) { - int x, y, height; - char *p, *start, *end; + int x, y; rgba_t color; + cl_font_t *font = Con_GetCurFont(); - x = refState.width - 340; + x = refState.width - 340 * font->scale; y = 64; - Con_DrawStringLen( NULL, NULL, &height ); MakeRGBA( color, 255, 255, 255, 255 ); - - p = start = msg; - do - { - end = Q_strchr( p, '\n' ); - if( end ) msg[end-start] = '\0'; - - Con_DrawString( x, y, p, color ); - y += height; - - // handle '\n\n' - if( *p == '\n' ) - y += height; - if( end ) p = end + 1; - else break; - } while( 1 ); + CL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF ); } } @@ -282,7 +379,7 @@ VID_WriteOverviewScript Create overview script file =============== */ -void VID_WriteOverviewScript( void ) +static void VID_WriteOverviewScript( void ) { ref_overview_t *ov = &clgame.overView; string filename; @@ -318,9 +415,14 @@ void SCR_MakeScreenShot( void ) qboolean iRet = false; int viewsize; + if( cls.scrshot_action == scrshot_inactive ) + return; + if( cls.envshot_viewsize > 0 ) viewsize = cls.envshot_viewsize; - else viewsize = cl_envshot_size->value; + else viewsize = cl_envshot_size.value; + + V_CheckGamma(); switch( cls.scrshot_action ) { @@ -347,8 +449,6 @@ void SCR_MakeScreenShot( void ) if( iRet ) VID_WriteOverviewScript(); // store overview script too break; - case scrshot_inactive: - return; } // report @@ -372,15 +472,19 @@ void SCR_MakeScreenShot( void ) SCR_DrawPlaque ================ */ -void SCR_DrawPlaque( void ) +static qboolean SCR_DrawPlaque( void ) { - if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + if(( cl_allow_levelshots.value && !cls.changelevel ) || cl.background ) { - int levelshot = ref.dllFuncs.GL_LoadTexture( cl_levelshot_name->string, NULL, 0, TF_IMAGE ); + int levelshot = ref.dllFuncs.GL_LoadTexture( cl_levelshot_name.string, NULL, 0, TF_IMAGE ); ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); ref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1, levelshot ); if( !cl.background ) CL_DrawHUD( CL_LOADING ); + + return true; } + + return false; } /* @@ -390,15 +494,10 @@ SCR_BeginLoadingPlaque */ void SCR_BeginLoadingPlaque( qboolean is_background ) { - float oldclear = 0;; S_StopAllSounds( true ); cl.audio_prepped = false; // don't play ambients - cl.video_prepped = false; - if( !Host_IsDedicated() ) - oldclear = gl_clear->value; - - if( CL_IsInMenu( ) && !cls.changedemo && !is_background ) + if( cls.key_dest == key_menu && !cls.changedemo && !is_background ) { UI_SetActiveMenu( false ); if( cls.state == ca_disconnected && !(GameState->curstate == STATE_RUNFRAME && GameState->nextstate != STATE_RUNFRAME) ) @@ -411,18 +510,15 @@ void SCR_BeginLoadingPlaque( qboolean is_background ) if( cls.key_dest == key_console ) return; - if( !Host_IsDedicated() ) - gl_clear->value = 0.0f; - if( is_background ) IN_MouseSavePos( ); cls.draw_changelevel = !is_background; SCR_UpdateScreen(); + + // set video_prepped after update screen, so engine can draw last remaining frame + cl.video_prepped = false; cls.disable_screen = host.realtime; cl.background = is_background; // set right state before svc_serverdata is came - if( !Host_IsDedicated() ) - gl_clear->value = oldclear; - // SNDDMA_LockSound(); } @@ -443,7 +539,7 @@ void SCR_EndLoadingPlaque( void ) SCR_AddDirtyPoint ================= */ -void SCR_AddDirtyPoint( int x, int y ) +static void SCR_AddDirtyPoint( int x, int y ) { if( x < scr_dirty.x1 ) scr_dirty.x1 = x; if( x > scr_dirty.x2 ) scr_dirty.x2 = x; @@ -462,6 +558,18 @@ void SCR_DirtyScreen( void ) SCR_AddDirtyPoint( refState.width - 1, refState.height - 1 ); } +static void R_DrawTileClear( int texnum, int x, int y, int w, int h, float tw, float th ) +{ + float s1 = x / tw; + float t1 = y / th; + float s2 = ( x + w ) / tw; + float t2 = ( y + h ) / th; + + ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); + ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); + ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, texnum ); +} + /* ================ SCR_TileClear @@ -469,12 +577,24 @@ SCR_TileClear */ void SCR_TileClear( void ) { - int i, top, bottom, left, right; + int i, top, bottom, left, right, texnum; dirty_t clear; + float tw, th; - if( scr_viewsize->value >= 120 ) + if( likely( scr_viewsize.value >= 120 )) return; // full screen rendering + if( !cls.tileImage ) + { + cls.tileImage = ref.dllFuncs.GL_LoadTexture( "gfx/backtile.lmp", NULL, 0, TF_NOMIPMAP ); + if( !cls.tileImage ) + cls.tileImage = -1; + } + + if( cls.tileImage > 0 ) + texnum = cls.tileImage; + else texnum = 0; + // erase rect will be the union of the past three frames // so tripple buffering works properly clear = scr_dirty; @@ -507,11 +627,14 @@ void SCR_TileClear( void ) left = clgame.viewport[0]; right = left + clgame.viewport[2] - 1; + tw = REF_GET_PARM( PARM_TEX_SRC_WIDTH, texnum ); + th = REF_GET_PARM( PARM_TEX_SRC_HEIGHT, texnum ); + if( clear.y1 < top ) { // clear above view screen i = clear.y2 < top-1 ? clear.y2 : top - 1; - ref.dllFuncs.R_DrawTileClear( cls.tileImage, clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1 + 1 ); + R_DrawTileClear( texnum, clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1 + 1, tw, th ); clear.y1 = top; } @@ -519,7 +642,7 @@ void SCR_TileClear( void ) { // clear below view screen i = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1; - ref.dllFuncs.R_DrawTileClear( cls.tileImage, clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i + 1 ); + R_DrawTileClear( texnum, clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i + 1, tw, th ); clear.y2 = bottom; } @@ -527,7 +650,7 @@ void SCR_TileClear( void ) { // clear left of view screen i = clear.x2 < left - 1 ? clear.x2 : left - 1; - ref.dllFuncs.R_DrawTileClear( cls.tileImage, clear.x1, clear.y1, i - clear.x1 + 1, clear.y2 - clear.y1 + 1 ); + R_DrawTileClear( texnum, clear.x1, clear.y1, i - clear.x1 + 1, clear.y2 - clear.y1 + 1, tw, th ); clear.x1 = left; } @@ -535,7 +658,7 @@ void SCR_TileClear( void ) { // clear left of view screen i = clear.x1 > right + 1 ? clear.x1 : right + 1; - ref.dllFuncs.R_DrawTileClear( cls.tileImage, i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1 + 1 ); + R_DrawTileClear( texnum, i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1 + 1, tw, th ); clear.x2 = right; } } @@ -550,6 +673,8 @@ text to the screen. */ void SCR_UpdateScreen( void ) { + qboolean screen_redraw = true; // assume screen has been redrawn + if( !V_PreRender( )) return; switch( cls.state ) @@ -560,7 +685,7 @@ void SCR_UpdateScreen( void ) case ca_connecting: case ca_connected: case ca_validate: - SCR_DrawPlaque(); + screen_redraw = SCR_DrawPlaque(); break; case ca_active: Con_RunConsole (); @@ -570,82 +695,15 @@ void SCR_UpdateScreen( void ) SCR_DrawCinematic(); break; default: - Host_Error( "SCR_UpdateScreen: bad cls.state\n" ); + Host_Error( "%s: bad cls.state\n", __func__ ); break; } - V_PostRender(); -} - -qboolean SCR_LoadFixedWidthFont( const char *fontname ) -{ - int i, fontWidth; - - if( cls.creditsFont.valid ) - return true; // already loaded - - if( !FS_FileExists( fontname, false )) - return false; - - cls.creditsFont.hFontTexture = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, TF_IMAGE|TF_KEEP_SOURCE ); - R_GetTextureParms( &fontWidth, NULL, cls.creditsFont.hFontTexture ); - cls.creditsFont.charHeight = clgame.scrInfo.iCharHeight = fontWidth / 16; - cls.creditsFont.type = FONT_FIXED; - cls.creditsFont.valid = true; - - // build fixed rectangles - for( i = 0; i < 256; i++ ) - { - cls.creditsFont.fontRc[i].left = (i * (fontWidth / 16)) % fontWidth; - cls.creditsFont.fontRc[i].right = cls.creditsFont.fontRc[i].left + fontWidth / 16; - cls.creditsFont.fontRc[i].top = (i / 16) * (fontWidth / 16); - cls.creditsFont.fontRc[i].bottom = cls.creditsFont.fontRc[i].top + fontWidth / 16; - cls.creditsFont.charWidths[i] = clgame.scrInfo.charWidths[i] = fontWidth / 16; - } - - return true; -} - -qboolean SCR_LoadVariableWidthFont( const char *fontname ) -{ - int i, fontWidth; - byte *buffer; - fs_offset_t length; - qfont_t *src; - - if( cls.creditsFont.valid ) - return true; // already loaded - - if( !FS_FileExists( fontname, false )) - return false; - - cls.creditsFont.hFontTexture = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, TF_IMAGE ); - R_GetTextureParms( &fontWidth, NULL, cls.creditsFont.hFontTexture ); - - // half-life font with variable chars witdh - buffer = FS_LoadFile( fontname, &length, false ); - - // setup creditsfont - if( buffer && length >= sizeof( qfont_t )) - { - src = (qfont_t *)buffer; - cls.creditsFont.charHeight = clgame.scrInfo.iCharHeight = src->rowheight; - cls.creditsFont.type = FONT_VARIABLE; - - // build rectangles - for( i = 0; i < 256; i++ ) - { - cls.creditsFont.fontRc[i].left = (word)src->fontinfo[i].startoffset % fontWidth; - cls.creditsFont.fontRc[i].right = cls.creditsFont.fontRc[i].left + src->fontinfo[i].charwidth; - cls.creditsFont.fontRc[i].top = (word)src->fontinfo[i].startoffset / fontWidth; - cls.creditsFont.fontRc[i].bottom = cls.creditsFont.fontRc[i].top + src->rowheight; - cls.creditsFont.charWidths[i] = clgame.scrInfo.charWidths[i] = src->fontinfo[i].charwidth; - } - cls.creditsFont.valid = true; - } - if( buffer ) Mem_Free( buffer ); - - return true; + // during changelevel we might have a few frames when we have nothing to draw + // (assuming levelshots are off) and drawing 2d on top of nothing or cleared screen + // is ugly, specifically with Adreno and ImgTec GPUs + if( screen_redraw || !cls.changelevel || !cls.changedemo ) + V_PostRender(); } /* @@ -657,22 +715,41 @@ INTERNAL RESOURCE */ void SCR_LoadCreditsFont( void ) { - const char *path = "gfx/creditsfont.fnt"; - dword crc; + cl_font_t *const font = &cls.creditsFont; + qboolean success = false; + float scale = hud_fontscale.value; + dword crc = 0; // replace default gfx.wad textures by current charset's font if( !CRC32_File( &crc, "gfx.wad" ) || crc == 0x49eb9f16 ) { - const char *path2 = va("creditsfont_%s.fnt", Cvar_VariableString( "con_charset" ) ); - if( FS_FileExists( path2, false ) ) - path = path2; + string charsetFnt; + + if( Q_snprintf( charsetFnt, sizeof( charsetFnt ), + "creditsfont_%s.fnt", Cvar_VariableString( "con_charset" )) > 0 ) + { + if( FS_FileExists( charsetFnt, false )) + success = Con_LoadVariableWidthFont( charsetFnt, font, scale, &hud_fontrender, TF_FONT ); + } } - if( !SCR_LoadVariableWidthFont( path )) + if( !success ) + success = Con_LoadVariableWidthFont( "gfx/creditsfont.fnt", font, scale, &hud_fontrender, TF_FONT ); + + if( !success ) + success = Con_LoadFixedWidthFont( "gfx/conchars", font, scale, &hud_fontrender, TF_FONT ); + + // copy font size for client.dll + if( success ) { - if( !SCR_LoadFixedWidthFont( "gfx/conchars" )) - Con_DPrintf( S_ERROR "failed to load HUD font\n" ); + int i; + + clgame.scrInfo.iCharHeight = cls.creditsFont.charHeight; + + for( i = 0; i < ARRAYSIZE( cls.creditsFont.charWidths ); i++ ) + clgame.scrInfo.charWidths[i] = cls.creditsFont.charWidths[i]; } + else Con_DPrintf( S_ERROR "failed to load HUD font\n" ); } /* @@ -682,7 +759,7 @@ SCR_InstallParticlePalette INTERNAL RESOURCE ================ */ -void SCR_InstallParticlePalette( void ) +static void SCR_InstallParticlePalette( void ) { rgbdata_t *pic; int i; @@ -716,6 +793,18 @@ void SCR_InstallParticlePalette( void ) } } +int SCR_LoadPauseIcon( void ) +{ + int texnum = 0; + + if( FS_FileExists( "gfx/paused.lmp", false )) + texnum = ref.dllFuncs.GL_LoadTexture( "gfx/paused.lmp", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST ); + else if( FS_FileExists( "gfx/pause.lmp", false )) + texnum = ref.dllFuncs.GL_LoadTexture( "gfx/pause.lmp", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST ); + + return texnum ? texnum : -1; +} + /* ================ SCR_RegisterTextures @@ -726,26 +815,19 @@ INTERNAL RESOURCE void SCR_RegisterTextures( void ) { // register gfx.wad images - - if( FS_FileExists( "gfx/paused.lmp", false )) - cls.pauseIcon = ref.dllFuncs.GL_LoadTexture( "gfx/paused.lmp", NULL, 0, TF_IMAGE ); - else if( FS_FileExists( "gfx/pause.lmp", false )) - cls.pauseIcon = ref.dllFuncs.GL_LoadTexture( "gfx/pause.lmp", NULL, 0, TF_IMAGE ); - if( FS_FileExists( "gfx/lambda.lmp", false )) { - if( cl_allow_levelshots->value ) - cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE ); - else cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE ); + if( cl_allow_levelshots.value ) + cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE|TF_ALLOW_NEAREST ); + else cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST ); } else if( FS_FileExists( "gfx/loading.lmp", false )) { - if( cl_allow_levelshots->value ) - cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE ); - else cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE ); + if( cl_allow_levelshots.value ) + cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE|TF_ALLOW_NEAREST ); + else cls.loadingBar = ref.dllFuncs.GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE|TF_ALLOW_NEAREST ); } - cls.tileImage = ref.dllFuncs.GL_LoadTexture( "gfx/backtile.lmp", NULL, 0, TF_NOMIPMAP ); } /* @@ -755,9 +837,9 @@ SCR_SizeUp_f Keybinding command ================= */ -void SCR_SizeUp_f( void ) +static void SCR_SizeUp_f( void ) { - Cvar_SetValue( "viewsize", Q_min( scr_viewsize->value + 10, 120 )); + Cvar_SetValue( "viewsize", Q_min( scr_viewsize.value + 10, 120 )); } @@ -768,9 +850,9 @@ SCR_SizeDown_f Keybinding command ================= */ -void SCR_SizeDown_f( void ) +static void SCR_SizeDown_f( void ) { - Cvar_SetValue( "viewsize", Q_max( scr_viewsize->value - 10, 30 )); + Cvar_SetValue( "viewsize", Q_max( scr_viewsize.value - 10, 30 )); } /* @@ -780,7 +862,6 @@ SCR_VidInit */ void SCR_VidInit( void ) { - string libpath; if( !ref.initialized ) // don't call VidInit too soon return; @@ -795,8 +876,11 @@ void SCR_VidInit( void ) gameui.globals->scrHeight = refState.height; } - COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); - VGui_Startup( libpath, refState.width, refState.height ); + // notify vgui about screen size change + if( clgame.hInstance ) + { + VGui_Startup( refState.width, refState.height ); + } CL_ClearSpriteTextures(); // now all hud sprites are invalid @@ -806,6 +890,7 @@ void SCR_VidInit( void ) // restart console size Con_VidInit (); + Touch_NotifyResize(); } /* @@ -817,18 +902,22 @@ void SCR_Init( void ) { if( scr_init ) return; - scr_centertime = Cvar_Get( "scr_centertime", "2.5", 0, "centerprint hold time" ); - cl_levelshot_name = Cvar_Get( "cl_levelshot_name", "*black", 0, "contains path to current levelshot" ); - cl_allow_levelshots = Cvar_Get( "allow_levelshots", "0", FCVAR_ARCHIVE, "allow engine to use indivdual levelshots instead of 'loading' image" ); - scr_loading = Cvar_Get( "scr_loading", "0", 0, "loading bar progress" ); - scr_download = Cvar_Get( "scr_download", "-1", 0, "downloading bar progress" ); - cl_testlights = Cvar_Get( "cl_testlights", "0", 0, "test dynamic lights" ); - cl_envshot_size = Cvar_Get( "cl_envshot_size", "256", FCVAR_ARCHIVE, "envshot size of cube side" ); - v_dark = Cvar_Get( "v_dark", "0", 0, "starts level from dark screen" ); - scr_viewsize = Cvar_Get( "viewsize", "120", FCVAR_ARCHIVE, "screen size" ); - net_speeds = Cvar_Get( "net_speeds", "0", FCVAR_ARCHIVE, "show network packets" ); - cl_showfps = Cvar_Get( "cl_showfps", "1", FCVAR_ARCHIVE, "show client fps" ); - cl_showpos = Cvar_Get( "cl_showpos", "0", FCVAR_ARCHIVE, "show local player position and velocity" ); + Cvar_RegisterVariable( &scr_centertime ); + Cvar_RegisterVariable( &cl_levelshot_name ); + Cvar_RegisterVariable( &cl_allow_levelshots ); + Cvar_RegisterVariable( &scr_loading ); + Cvar_RegisterVariable( &scr_download ); + Cvar_RegisterVariable( &cl_testlights ); + Cvar_RegisterVariable( &cl_envshot_size ); + Cvar_RegisterVariable( &v_dark ); + Cvar_RegisterVariable( &scr_viewsize ); + Cvar_RegisterVariable( &net_speeds ); + Cvar_RegisterVariable( &cl_showfps ); + Cvar_RegisterVariable( &cl_showpos ); + Cvar_RegisterVariable( &cl_showcmd ); +#ifdef _DEBUG + Cvar_RegisterVariable( &cl_showents ); +#endif // NDEBUG // register our commands Cmd_AddCommand( "skyname", CL_SetSky_f, "set new skybox by basename" ); @@ -861,7 +950,6 @@ void SCR_Shutdown( void ) { if( !scr_init ) return; - Cmd_RemoveCommand( "timerefresh" ); Cmd_RemoveCommand( "skyname" ); Cmd_RemoveCommand( "viewpos" ); UI_SetActiveMenu( false ); diff --git a/engine/client/cl_securedstub.c b/engine/client/cl_securedstub.c index fd7746b1..f45ba024 100644 --- a/engine/client/cl_securedstub.c +++ b/engine/client/cl_securedstub.c @@ -405,12 +405,15 @@ static cldll_func_dst_t cldllFuncDst = void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F ) { - cldll_func_src_t cldllFuncSrc = { 0 }; modfuncs_t modFuncs = { 0 }; // secured client dlls need these - *(cldll_func_dst_t **)&cldllFuncSrc.pfnVidInit = &cldllFuncDst; - *(modfuncs_t **)&cldllFuncSrc.pfnInitialize = &modFuncs; + cldll_func_src_t cldllFuncSrc = + { + (void *)&modFuncs, + NULL, + (void *)&cldllFuncDst + }; // trying to fill interface now F( &cldllFuncSrc ); diff --git a/engine/client/cl_spray.c b/engine/client/cl_spray.c new file mode 100644 index 00000000..5525bebc --- /dev/null +++ b/engine/client/cl_spray.c @@ -0,0 +1,179 @@ +/* +cl_spray.c - spray conversion for GoldSrc protocol +Copyright (C) 2025 Xash3D FWGS contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" +#include "imagelib.h" + +#define SPRAY_MAX_SURFACE 12228 +#define SPRAY_PALETTE_SIZE 256 +#define SPRAY_PALETTE_BYTES ( SPRAY_PALETTE_SIZE * 3 ) +#define SPRAY_ALPHA_THRESHOLD ( SPRAY_PALETTE_SIZE / 2 ) +#define SPRAY_FILENAME "tempdecal.wad" + +// adjusts spray dimensions +static void CL_AdjustSprayDimensions( int *width, int *height ) +{ + float aspect = (float)( *width ) / (float)( *height ); + int h, w; + for( h = (( *height ) / 16 ) * 16; h >= 16; h -= 16 ) + { + w = ((int)( h * aspect ) / 16 ) * 16; + if( w < 16 || w > *width ) + continue; + + if( w * h < SPRAY_MAX_SURFACE ) + { + *width = w; + *height = h; + return; + } + } + // fallback: minimal size + *width = 16; + *height = 16; +} + +// loads and prepares the image +static rgbdata_t *CL_LoadAndPrepareImage( const char *filename, int *width, int *height ) +{ + rgbdata_t *image = NULL; + + Image_SetForceFlags( IL_KEEP_8BIT ); + image = FS_LoadImage( filename, NULL, 0 ); + + if( !image ) + return NULL; + + *width = image->width; + *height = image->height; + CL_AdjustSprayDimensions( width, height ); + + if( *width != image->width || *height != image->height ) + { + const int bpp = PFDesc[image->type].bpp; + const int palette_size = 256 * ( image->type == PF_INDEXED_32 ? 4 : 3 ); + rgbdata_t *scaled; + qboolean resampled; + + // resample image to fit spray size constraints + byte *resampled_buf = Image_ResampleInternal( + image->buffer, image->width, image->height, + *width, *height, image->type, &resampled + ); + + if( !resampled_buf ) + { + FS_FreeImage( image ); + return NULL; + } + + scaled = Mem_Malloc( host.imagepool, sizeof( *scaled )); + *scaled = *image; + scaled->width = *width; + scaled->height = *height; + scaled->size = *width * *height * bpp; + scaled->buffer = Mem_Malloc( host.imagepool, scaled->size ); + memcpy( scaled->buffer, resampled_buf, scaled->size ); + + if( image->palette ) + { + // copy 8-bit palette for resampled bmp + scaled->palette = Mem_Malloc( host.imagepool, palette_size ); + memcpy( scaled->palette, image->palette, palette_size ); + } + + FS_FreeImage( image ); + image = scaled; + } + + return image; +} + +// converts an image to WAD3 spray or miptex format +qboolean CL_ConvertImageToWAD3( const char *filename ) +{ + qboolean is_indexed_img; + int width = 0, height = 0; + int i; + byte palette[SPRAY_PALETTE_BYTES]; + byte *indexed = NULL; + rgbdata_t *image = NULL; + rgbdata_t *quant = NULL; + rgbdata_t temp_image = {0}; + + image = CL_LoadAndPrepareImage( filename, &width, &height ); + if( !image ) + return false; + + is_indexed_img = image->palette != NULL; + if( is_indexed_img ) + { + // copy bmp palette from rgba to rgb + for( i = 0; i < 256; ++i ) + { + palette[i * 3 + 0] = image->palette[i * 4 + 0]; // R + palette[i * 3 + 1] = image->palette[i * 4 + 1]; // G + palette[i * 3 + 2] = image->palette[i * 4 + 2]; // B + } + indexed = image->buffer; + } + else + { + quant = Mem_Malloc( host.imagepool, sizeof( *quant )); + *quant = *image; + quant->buffer = Mem_Malloc( host.imagepool, quant->size ); + memcpy( quant->buffer, image->buffer, quant->size ); + Image_Quantize( quant ); // it's so weird, it writes result to same structure as used for input data + + if( !quant || !quant->buffer || !quant->palette ) + goto cleanup; + + // set index 255 for transparent pixels in rgba images + if( image->type == PF_RGBA_32 ) + { + for( i = 0; i < width * height; ++i ) + { + if( image->buffer[i * 4 + 3] <= SPRAY_ALPHA_THRESHOLD ) + quant->buffer[i] = 255; + } + } + + quant->palette[255 * 3 + 0] = 0; + quant->palette[255 * 3 + 1] = 0; + quant->palette[255 * 3 + 2] = 255; + memcpy( palette, quant->palette, SPRAY_PALETTE_BYTES ); + indexed = quant->buffer; + } + + temp_image.width = width; + temp_image.height = height; + temp_image.type = PF_INDEXED_32; + temp_image.buffer = indexed; + temp_image.size = width * height; + temp_image.palette = palette; + + if( is_indexed_img ) + temp_image.flags |= IMAGE_GRADIENT_DECAL; + + return FS_SaveImage( SPRAY_FILENAME, &temp_image ); + +cleanup: + if( image ) + FS_FreeImage( image ); + if( quant ) + FS_FreeImage( quant ); + return false; +} diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 8158ce2c..833cc262 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -35,17 +35,17 @@ TEMPENTS MANAGEMENT #define SHARD_VOLUME 12.0f // on shard ever n^3 units #define MAX_MUZZLEFLASH 3 -TEMPENTITY *cl_active_tents; -TEMPENTITY *cl_free_tents; -TEMPENTITY *cl_tempents = NULL; // entities pool +static TEMPENTITY *cl_active_tents; +static TEMPENTITY *cl_free_tents; +static TEMPENTITY *cl_tempents = NULL; // entities pool -model_t *cl_sprite_muzzleflash[MAX_MUZZLEFLASH]; // muzzle flashes -model_t *cl_sprite_dot = NULL; -model_t *cl_sprite_ricochet = NULL; -model_t *cl_sprite_shell = NULL; -model_t *cl_sprite_glow = NULL; +static model_t *cl_sprite_muzzleflash[MAX_MUZZLEFLASH]; // muzzle flashes +static model_t *cl_sprite_ricochet = NULL; +static model_t *cl_sprite_glow = NULL; +model_t *cl_sprite_dot = NULL; +model_t *cl_sprite_shell = NULL; -const char *cl_default_sprites[] = +static const char *const cl_default_sprites[] = { // built-in sprites "sprites/muzzleflash1.spr", @@ -57,35 +57,7 @@ const char *cl_default_sprites[] = "sprites/shellchrome.spr", }; -const char *cl_player_shell_sounds[] = -{ - "player/pl_shell1.wav", - "player/pl_shell2.wav", - "player/pl_shell3.wav", -}; - -const char *cl_weapon_shell_sounds[] = -{ - "weapons/sshell1.wav", - "weapons/sshell2.wav", - "weapons/sshell3.wav", -}; - -const char *cl_ricochet_sounds[] = -{ - "weapons/ric1.wav", - "weapons/ric2.wav", - "weapons/ric3.wav", - "weapons/ric4.wav", - "weapons/ric5.wav", -}; - -const char *cl_explode_sounds[] = -{ - "weapons/explode3.wav", - "weapons/explode4.wav", - "weapons/explode5.wav", -}; +static void CL_PlayerDecal( int playerIndex, int textureIndex, int entityIndex, float *pos ); /* ================ @@ -146,6 +118,7 @@ client resources not precached by server */ void CL_AddClientResources( void ) { + const char *snd; char filepath[MAX_QPATH]; int i; @@ -161,64 +134,48 @@ void CL_AddClientResources( void ) } // then check sounds - for( i = 0; i < ARRAYSIZE( cl_player_shell_sounds ); i++ ) + for( i = 0; ( snd = SoundList_Get( BouncePlayerShell, i )); i++ ) { - Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_player_shell_sounds[i] ); + Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd ); if( !FS_FileExists( filepath, false )) - CL_AddClientResource( cl_player_shell_sounds[i], t_sound ); + CL_AddClientResource( snd, t_sound ); } - for( i = 0; i < ARRAYSIZE( cl_weapon_shell_sounds ); i++ ) + for( i = 0; ( snd = SoundList_Get( BounceWeaponShell, i )); i++ ) { - Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_weapon_shell_sounds[i] ); + Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd ); if( !FS_FileExists( filepath, false )) - CL_AddClientResource( cl_weapon_shell_sounds[i], t_sound ); + CL_AddClientResource( snd, t_sound ); } - for( i = 0; i < ARRAYSIZE( cl_explode_sounds ); i++ ) + for( i = 0; ( snd = SoundList_Get( Explode, i )); i++ ) { - Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_explode_sounds[i] ); + Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd ); if( !FS_FileExists( filepath, false )) - CL_AddClientResource( cl_explode_sounds[i], t_sound ); + CL_AddClientResource( snd, t_sound ); } #if 0 // ric sounds was precached by server-side - for( i = 0; i < ARRAYSIZE( cl_ricochet_sounds ); i++ ) + for( i = 0; ( snd = SoundList_Get( Ricochet, i )); i++ ) { - Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", cl_ricochet_sounds[i] ); + Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd ); if( !FS_FileExists( filepath, false )) - CL_AddClientResource( cl_ricochet_sounds[i], t_sound ); + CL_AddClientResource( snd, t_sound ); } #endif } - -/* -================ -CL_InitTempents - -================ -*/ -void CL_InitTempEnts( void ) -{ - cl_tempents = Mem_Calloc( cls.mempool, sizeof( TEMPENTITY ) * GI->max_tents ); - CL_ClearTempEnts(); - - // load tempent sprites (glowshell, muzzleflashes etc) - CL_LoadClientSprites (); -} - /* ================ CL_ClearTempEnts ================ */ -void CL_ClearTempEnts( void ) +static void CL_ClearTempEnts( void ) { int i; @@ -235,6 +192,21 @@ void CL_ClearTempEnts( void ) cl_active_tents = NULL; } +/* +================ +CL_InitTempents + +================ +*/ +void CL_InitTempEnts( void ) +{ + cl_tempents = Mem_Calloc( cls.mempool, sizeof( TEMPENTITY ) * GI->max_tents ); + CL_ClearTempEnts(); + + // load tempent sprites (glowshell, muzzleflashes etc) + CL_LoadClientSprites (); +} + /* ================ CL_FreeTempEnts @@ -255,7 +227,7 @@ CL_PrepareTEnt set default values ============== */ -void CL_PrepareTEnt( TEMPENTITY *pTemp, model_t *pmodel ) +static void CL_PrepareTEnt( TEMPENTITY *pTemp, model_t *pmodel ) { int frameCount = 0; int modelIndex = 0; @@ -296,10 +268,10 @@ CL_TempEntPlaySound play collide sound ============== */ -void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) +static void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) { float fvol; - char soundname[32]; + const char *soundname = NULL; qboolean isshellcasing = false; int zvel; @@ -310,36 +282,39 @@ void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) switch( pTemp->hitSound ) { case BOUNCE_GLASS: - Q_snprintf( soundname, sizeof( soundname ), "debris/glass%i.wav", COM_RandomLong( 1, 4 )); + soundname = SoundList_GetRandom( BounceGlass ); break; case BOUNCE_METAL: - Q_snprintf( soundname, sizeof( soundname ), "debris/metal%i.wav", COM_RandomLong( 1, 6 )); + soundname = SoundList_GetRandom( BounceMetal ); break; case BOUNCE_FLESH: - Q_snprintf( soundname, sizeof( soundname ), "debris/flesh%i.wav", COM_RandomLong( 1, 7 )); + soundname = SoundList_GetRandom( BounceFlesh ); break; case BOUNCE_WOOD: - Q_snprintf( soundname, sizeof( soundname ), "debris/wood%i.wav", COM_RandomLong( 1, 4 )); + soundname = SoundList_GetRandom( BounceWood ); break; case BOUNCE_SHRAP: - Q_strncpy( soundname, cl_ricochet_sounds[COM_RandomLong( 0, 4 )], sizeof( soundname ) ); + soundname = SoundList_GetRandom( Ricochet ); break; case BOUNCE_SHOTSHELL: - Q_strncpy( soundname, cl_weapon_shell_sounds[COM_RandomLong( 0, 2 )], sizeof( soundname ) ); + soundname = SoundList_GetRandom( BounceWeaponShell ); isshellcasing = true; // shell casings have different playback parameters fvol = 0.5f; break; case BOUNCE_SHELL: - Q_strncpy( soundname, cl_player_shell_sounds[COM_RandomLong( 0, 2 )], sizeof( soundname ) ); + soundname = SoundList_GetRandom( BouncePlayerShell ); isshellcasing = true; // shell casings have different playback parameters break; case BOUNCE_CONCRETE: - Q_snprintf( soundname, sizeof( soundname ), "debris/concrete%i.wav", COM_RandomLong( 1, 3 )); + soundname = SoundList_GetRandom( BounceConcrete ); break; default: // null sound return; } + if( !soundname ) + return; + zvel = abs( pTemp->entity.baseline.origin[2] ); // only play one out of every n @@ -380,7 +355,7 @@ CL_TEntAddEntity add entity to renderlist ============== */ -int CL_TempEntAddEntity( cl_entity_t *pEntity ) +static int CL_TempEntAddEntity( cl_entity_t *pEntity ) { vec3_t mins, maxs; @@ -432,7 +407,7 @@ CL_TEntAddEntity free the first low priority tempent it finds. ============== */ -qboolean CL_FreeLowPriorityTempEnt( void ) +static qboolean CL_FreeLowPriorityTempEnt( void ) { TEMPENTITY *pActive = cl_active_tents; TEMPENTITY *pPrev = NULL; @@ -468,11 +443,16 @@ alloc normal\low priority tempentity */ TEMPENTITY *CL_TempEntAlloc( const vec3_t org, model_t *pmodel ) { + static float cl_lasttimewarn; TEMPENTITY *pTemp; if( !cl_free_tents ) { - Con_DPrintf( "Overflow %d temporary ents!\n", GI->max_tents ); + if( cl_lasttimewarn < host.realtime ) + { + Con_DPrintf( "Overflow %d temporary ents!\n", GI->max_tents ); + cl_lasttimewarn = host.realtime + 1.0f; + } return NULL; } @@ -587,59 +567,54 @@ R_FizzEffect Create a fizz effect ============== */ -void GAME_EXPORT R_FizzEffect( cl_entity_t *pent, int modelIndex, int density ) +void GAME_EXPORT R_FizzEffect( cl_entity_t *ent, int modelIndex, int density ) { - TEMPENTITY *pTemp; - int i, width, depth, count; - float angle, maxHeight, speed; - float xspeed, yspeed, zspeed; - vec3_t origin; - model_t *mod; + const float base_time = cl.time - 0.1f; + model_t *mod = CL_ModelHandle( modelIndex ); + vec3_t volume, mins, maxs; + vec2_t speed; + int i; - if( !pent || pent->curstate.modelindex <= 0 ) + if( !ent || !ent->model || !modelIndex || !mod ) return; - if(( mod = CL_ModelHandle( pent->curstate.modelindex )) == NULL ) - return; + VectorCopy( ent->model->mins, mins ); + VectorCopy( ent->model->maxs, maxs ); - count = density + 1; - density = count * 3 + 6; - maxHeight = mod->maxs[2] - mod->mins[2]; - width = mod->maxs[0] - mod->mins[0]; - depth = mod->maxs[1] - mod->mins[1]; - - speed = ( pent->curstate.rendercolor.r<<8 | pent->curstate.rendercolor.g ); - if( pent->curstate.rendercolor.b ) - speed = -speed; - - angle = DEG2RAD( pent->angles[YAW] ); - SinCos( angle, &yspeed, &xspeed ); - - xspeed *= speed; - yspeed *= speed; - - for( i = 0; i < count; i++ ) + if( ent->angles[1] != 0.0f ) { - origin[0] = mod->mins[0] + COM_RandomLong( 0, width - 1 ); - origin[1] = mod->mins[1] + COM_RandomLong( 0, depth - 1 ); - origin[2] = mod->mins[2]; - pTemp = CL_TempEntAlloc( origin, CL_ModelHandle( modelIndex )); + const float base_speed = ( ent->curstate.rendercolor.b ? -1.0f : 1.0f ) * ( ent->curstate.rendercolor.r * 256.0f + ent->curstate.rendercolor.g ); + SinCos( DEG2RAD( ent->angles[1] ), &speed[1], &speed[0] ); + speed[0] *= base_speed; + speed[1] *= base_speed; + } + else speed[0] = speed[1] = 0.0f; - if ( !pTemp ) return; + VectorSubtract( maxs, mins, volume ); - pTemp->flags |= FTENT_SINEWAVE; + for( i = 0; i <= density; i++ ) + { + TEMPENTITY *tent; + vec3_t origin; - pTemp->x = origin[0]; - pTemp->y = origin[1]; + VectorCopy( mins, origin ); + origin[0] += COM_RandomLong( 0, (int)volume[0] - 1 ); + origin[1] += COM_RandomLong( 0, (int)volume[1] - 1 ); - zspeed = COM_RandomLong( 80, 140 ); - VectorSet( pTemp->entity.baseline.origin, xspeed, yspeed, zspeed ); - pTemp->die = cl.time + ( maxHeight / zspeed ) - 0.1f; - pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); - // Set sprite scale - pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f ); - pTemp->entity.curstate.rendermode = kRenderTransAlpha; - pTemp->entity.curstate.renderamt = 255; + if( !( tent = CL_TempEntAlloc( origin, mod ))) + return; + + tent->x = origin[0]; + tent->y = origin[1]; + tent->die = base_time; + tent->flags |= FTENT_SINEWAVE; + tent->entity.curstate.rendermode = kRenderTransAlpha; + Vector2Copy( speed, tent->entity.baseline.origin ); + + tent->entity.baseline.origin[2] = COM_RandomLong( 80, 140 ); + tent->die += volume[2] / tent->entity.baseline.origin[2]; + tent->entity.curstate.frame = COM_RandomLong( 0, tent->frameMax ); + tent->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f ); } } @@ -799,25 +774,15 @@ Detach entity from player */ void GAME_EXPORT R_KillAttachedTents( int client ) { - int i; + TEMPENTITY *tent; if( client <= 0 || client > cl.maxclients ) return; - for( i = 0; i < GI->max_tents; i++ ) + for( tent = cl_active_tents; tent; tent = tent->next ) { - TEMPENTITY *pTemp = &cl_tempents[i]; - - if( !FBitSet( pTemp->flags, FTENT_PLYRATTACHMENT )) - continue; - - // this TEMPENTITY is player attached. - // if it is attached to this client, set it to die instantly. - if( pTemp->clientIndex == client ) - { - // good enough, it will die on next tent update. - pTemp->die = cl.time; - } + if( FBitSet( tent->flags, FTENT_PLYRATTACHMENT ) && tent->clientIndex == client ) + tent->die = cl.time; } } @@ -990,7 +955,6 @@ void GAME_EXPORT R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex if( pTemp->entity.curstate.frame > 8.0f ) pTemp->entity.curstate.frame = pTemp->frameMax; - pTemp->entity.baseline.origin[2] += COM_RandomFloat( 4.0f, 16.0f ) * size; pTemp->entity.angles[2] = COM_RandomFloat( 0.0f, 360.0f ); pTemp->bounceFactor = 0.0f; } @@ -1037,7 +1001,7 @@ void GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t vecSpot[1] = pos[1] + COM_RandomFloat( -0.5f, 0.5f ) * size[1]; vecSpot[2] = pos[2] + COM_RandomFloat( -0.5f, 0.5f ) * size[2]; - if( CL_PointContents( vecSpot ) != CONTENTS_SOLID ) + if( PM_CL_PointContents( vecSpot, NULL ) != CONTENTS_SOLID ) break; // valid spot } @@ -1245,37 +1209,28 @@ apply params for exploding sprite */ void GAME_EXPORT R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags ) { - if( !pTemp ) return; + qboolean noadditive, drawalpha, rotate; - if( FBitSet( flags, TE_EXPLFLAG_NOADDITIVE )) - { - // solid sprite - pTemp->entity.curstate.rendermode = kRenderNormal; - pTemp->entity.curstate.renderamt = 255; - } - else if( FBitSet( flags, TE_EXPLFLAG_DRAWALPHA )) - { - // alpha sprite (came from hl2) - pTemp->entity.curstate.rendermode = kRenderTransAlpha; - pTemp->entity.curstate.renderamt = 180; - } - else - { - // additive sprite - pTemp->entity.curstate.rendermode = kRenderTransAdd; - pTemp->entity.curstate.renderamt = 180; - } + if( !pTemp ) + return; - if( FBitSet( flags, TE_EXPLFLAG_ROTATE )) - { - // came from hl2 - pTemp->entity.angles[2] = COM_RandomLong( 0, 360 ); - } + noadditive = FBitSet( flags, TE_EXPLFLAG_NOADDITIVE ); + drawalpha = FBitSet( flags, TE_EXPLFLAG_DRAWALPHA ); + rotate = FBitSet( flags, TE_EXPLFLAG_ROTATE ); - pTemp->entity.curstate.renderfx = kRenderFxNone; - pTemp->entity.baseline.origin[2] = 8; - pTemp->entity.origin[2] += 10; pTemp->entity.curstate.scale = scale; + pTemp->entity.baseline.origin[2] = 8.0f; + pTemp->entity.origin[2] = pTemp->entity.origin[2] + 10.0f; + if( rotate ) + pTemp->entity.angles[2] = COM_RandomFloat( 0.0, 360.0f ); + + pTemp->entity.curstate.rendermode = noadditive ? kRenderNormal : + drawalpha ? kRenderTransAlpha : kRenderTransAdd; + pTemp->entity.curstate.renderamt = noadditive ? 0xff : 0xb4; + pTemp->entity.curstate.renderfx = 0; + pTemp->entity.curstate.rendercolor.r = 0; + pTemp->entity.curstate.rendercolor.g = 0; + pTemp->entity.curstate.rendercolor.b = 0; } /* @@ -1302,6 +1257,73 @@ void GAME_EXPORT R_Sprite_Smoke( TEMPENTITY *pTemp, float scale ) pTemp->entity.curstate.scale = scale; } +static 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 ) +{ + model_t *mod = CL_ModelHandle( modelIndex ); + float noise = spread / 100.0f; + float znoise = noise * 1.5f; + int i; + + znoise = Q_min( 1.0f, znoise ); + + if( !mod ) + { + Con_Reportf( "%s: No model %d!\n", func, modelIndex ); + return; + } + + for( i = 0; i < count; i++ ) + { + TEMPENTITY *tent = CL_TempEntAlloc( pos, mod ); + float rand_speed; + vec3_t noise_vec, vout; + + if( !tent ) + break; + + tent->flags |= FTENT_SLOWGRAVITY; + tent->entity.curstate.rendermode = rendermode; + tent->entity.curstate.renderamt = 255; + tent->entity.baseline.renderamt = 255; + tent->entity.curstate.renderfx = kRenderFxNoDissipation; + + noise_vec[0] = COM_RandomFloat( -noise, noise ); + noise_vec[1] = COM_RandomFloat( -noise, noise ); + noise_vec[2] = COM_RandomFloat( 0.0f, znoise ); + rand_speed = COM_RandomFloat( speed * 0.8f, speed * 1.2f ); + + VectorAdd( dir, noise_vec, vout ); + VectorScale( vout, rand_speed, tent->entity.baseline.origin ); + + if( sprite_spray ) // if TE_SPRITE_SPRAY + { + tent->flags |= FTENT_FADEOUT; + tent->fadeSpeed = 2.0f; + + tent->entity.curstate.framerate = 0.5f; + tent->die = cl.time + 0.35; + tent->entity.curstate.frame = COM_RandomLong( 0, tent->frameMax ); // frameMax inclusive + } + else + { + tent->flags |= FTENT_COLLIDEWORLD; + + if( tent->frameMax > 1 ) + { + tent->flags |= FTENT_SPRANIMATE; + tent->entity.curstate.framerate = 10.0f; + tent->die = cl.time + tent->frameMax * 0.1; + } + else + { + tent->entity.curstate.framerate = 1.0f; + tent->die = cl.time + 0.35; + } + tent->entity.curstate.frame = 0.0f; + } + } +} + /* =============== R_Spray @@ -1311,61 +1333,7 @@ Throws a shower of sprites or models */ void GAME_EXPORT R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread, int rendermode ) { - TEMPENTITY *pTemp; - float noise; - float znoise; - model_t *pmodel; - int i; - - if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) - { - Con_Reportf( "No model %d!\n", modelIndex ); - return; - } - - noise = (float)spread / 100.0f; - - // more vertical displacement - znoise = Q_min( 1.0f, noise * 1.5f ); - - for( i = 0; i < count; i++ ) - { - pTemp = CL_TempEntAlloc( pos, pmodel ); - if( !pTemp ) return; - - pTemp->entity.curstate.rendermode = rendermode; - pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255; - pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; - - if( rendermode != kRenderGlow ) - { - // spray - pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY; - - if( pTemp->frameMax > 1 ) - { - pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY | FTENT_SPRANIMATE; - pTemp->die = cl.time + (pTemp->frameMax * 0.1f); - pTemp->entity.curstate.framerate = 10; - } - else pTemp->die = cl.time + 0.35f; - } - else - { - // sprite spray - pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); - pTemp->flags |= FTENT_FADEOUT | FTENT_SLOWGRAVITY; - pTemp->entity.curstate.framerate = 0.5; - pTemp->die = cl.time + 0.35f; - pTemp->fadeSpeed = 2.0; - } - - // make the spittle fly the direction indicated, but mix in some noise. - pTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -noise, noise ); - pTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -noise, noise ); - pTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, znoise ); - VectorScale( pTemp->entity.baseline.origin, COM_RandomFloat(( speed * 0.8f ), ( speed * 1.2f )), pTemp->entity.baseline.origin ); - } + R_Spray_Generic( __func__, pos, dir, modelIndex, count, speed, spread, rendermode, false ); } /* @@ -1377,7 +1345,7 @@ Spray of alpha sprites */ void GAME_EXPORT R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread ) { - R_Spray( pos, dir, modelIndex, count, speed, spread, kRenderGlow ); + R_Spray_Generic( __func__, pos, dir, modelIndex, count, speed, spread, kRenderTransAlpha, true ); } /* @@ -1498,7 +1466,7 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse ) =============== R_SparkEffect -Create a streaks + richochet sprite +Create a streaks + ricochet sprite =============== */ void GAME_EXPORT R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax ) @@ -1514,18 +1482,25 @@ R_RicochetSound Make a random ricochet sound ============== */ -static void R_RicochetSound_( const vec3_t pos, int sound ) +static void R_RicochetSoundByName( const vec3_t pos, const char *name ) { - sound_t handle; - - handle = S_RegisterSound( cl_ricochet_sounds[sound] ); - + sound_t handle; + handle = S_RegisterSound( name ); S_StartSound( pos, 0, CHAN_AUTO, handle, VOL_NORM, 1.0, 100, 0 ); } +static void R_RicochetSoundByIndex( const vec3_t pos, int idx ) +{ + const char *name = SoundList_Get( Ricochet, idx ); + if( name ) + R_RicochetSoundByName( pos, name ); +} + void GAME_EXPORT R_RicochetSound( const vec3_t pos ) { - R_RicochetSound_( pos, COM_RandomLong( 0, 4 )); + const char *name = SoundList_GetRandom( Ricochet ); + if( name ) + R_RicochetSoundByName( pos, name ); } /* @@ -1672,8 +1647,12 @@ void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerat if( !FBitSet( flags, TE_EXPLFLAG_NOSOUND )) { - hSound = S_RegisterSound( cl_explode_sounds[COM_RandomLong( 0, 2 )] ); - S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 ); + const char *name = SoundList_GetRandom( Explode ); + if( name ) + { + hSound = S_RegisterSound( name ); + S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 ); + } } } @@ -1781,7 +1760,7 @@ void GAME_EXPORT R_FireField( float *org, int radius, int modelIndex, int count, else if( FBitSet( flags, TEFIRE_FLAG_ADDITIVE )) { pTemp->entity.curstate.rendermode = kRenderTransAdd; - pTemp->entity.curstate.renderamt = 80; + pTemp->entity.curstate.renderamt = 180; } else { @@ -1901,13 +1880,13 @@ CL_ParseTempEntity handle temp-entity messages ============== */ -void CL_ParseTempEntity( sizebuf_t *msg ) +void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto ) { - sizebuf_t buf; - byte pbuf[2048]; + sizebuf_t buf, *pbuf; + byte msg_data[2048]; int iSize; int type, color, count, flags; - int decalIndex, modelIndex, entityIndex; + int decalIndex = 0, modelIndex = 0, entityIndex = 0; float scale, life, frameRate, vel, random; float brightness, r, g, b; vec3_t pos, pos2, ang; @@ -1916,24 +1895,32 @@ void CL_ParseTempEntity( sizebuf_t *msg ) cl_entity_t *pEnt; dlight_t *dl; sound_t hSound; + const char *name; - if( cls.legacymode ) - iSize = MSG_ReadByte( msg ); - else iSize = MSG_ReadWord( msg ); + if( proto != PROTO_GOLDSRC ) + { + if( proto == PROTO_LEGACY ) + iSize = MSG_ReadByte( msg ); + else iSize = MSG_ReadWord( msg ); - decalIndex = modelIndex = entityIndex = 0; + // this will probably be fatal anyway + if( iSize > sizeof( msg_data )) + Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __func__ ); - // this will probably be fatal anyway - if( iSize > sizeof( pbuf )) - Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __FUNCTION__ ); + // parse user message into buffer + MSG_ReadBytes( msg, msg_data, iSize ); - // parse user message into buffer - MSG_ReadBytes( msg, pbuf, iSize ); + // init a safe tempbuffer + MSG_Init( &buf, "TempEntity", msg_data, iSize ); - // init a safe tempbuffer - MSG_Init( &buf, "TempEntity", pbuf, iSize ); + pbuf = &buf; + } + else + { + pbuf = msg; + } - type = MSG_ReadByte( &buf ); + type = MSG_ReadByte( pbuf ); switch( type ) { @@ -1950,77 +1937,80 @@ void CL_ParseTempEntity( sizebuf_t *msg ) case TE_BEAMRING: case TE_BEAMHOSE: case TE_KILLBEAM: - CL_ParseViewBeam( &buf, type ); + CL_ParseViewBeam( pbuf, type ); break; case TE_GUNSHOT: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); R_RicochetSound( pos ); R_RunParticleEffect( pos, vec3_origin, 0, 20 ); break; case TE_EXPLOSION: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - frameRate = MSG_ReadByte( &buf ); - flags = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + scale = (float)(MSG_ReadByte( pbuf ) * 0.1f); + frameRate = MSG_ReadByte( pbuf ); + flags = MSG_ReadByte( pbuf ); R_Explosion( pos, modelIndex, scale, frameRate, flags ); break; case TE_TAREXPLOSION: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); R_BlobExplosion( pos ); - hSound = S_RegisterSound( cl_explode_sounds[0] ); - S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 ); + if(( name = SoundList_Get( Explode, 0 ))) + { + hSound = S_RegisterSound( name ); + S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 ); + } break; case TE_SMOKE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - frameRate = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + scale = (float)(MSG_ReadByte( pbuf ) * 0.1f); + frameRate = MSG_ReadByte( pbuf ); pTemp = R_DefaultSprite( pos, modelIndex, frameRate ); R_Sprite_Smoke( pTemp, scale ); break; case TE_TRACER: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); R_TracerEffect( pos, pos2 ); break; case TE_SPARKS: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); R_SparkShower( pos ); break; case TE_LAVASPLASH: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); R_LavaSplash( pos ); break; case TE_TELEPORT: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); R_TeleportSplash( pos ); break; case TE_EXPLOSION2: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - color = MSG_ReadByte( &buf ); - count = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + color = MSG_ReadByte( pbuf ); + count = MSG_ReadByte( pbuf ); R_ParticleExplosion2( pos, color, count ); dl = CL_AllocDlight( 0 ); @@ -2029,33 +2019,36 @@ void CL_ParseTempEntity( sizebuf_t *msg ) dl->die = cl.time + 0.5; dl->decay = 300; - hSound = S_RegisterSound( cl_explode_sounds[0] ); - S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 0.6f, PITCH_NORM, 0 ); + if(( name = SoundList_Get( Explode, 0 ))) + { + hSound = S_RegisterSound( name ); + S_StartSound( pos, -1, CHAN_AUTO, hSound, VOL_NORM, 1.0f, PITCH_NORM, 0 ); + } break; case TE_BSPDECAL: case TE_DECAL: case TE_WORLDDECAL: case TE_WORLDDECALHIGH: case TE_DECALHIGH: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); if( type == TE_BSPDECAL ) { - decalIndex = MSG_ReadShort( &buf ); - entityIndex = MSG_ReadShort( &buf ); + decalIndex = MSG_ReadShort( pbuf ); + entityIndex = MSG_ReadShort( pbuf ); if( entityIndex ) - modelIndex = MSG_ReadShort( &buf ); + modelIndex = MSG_ReadShort( pbuf ); else modelIndex = 0; } else { - decalIndex = MSG_ReadByte( &buf ); + decalIndex = MSG_ReadByte( pbuf ); if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) decalIndex += 256; if( type == TE_DECALHIGH || type == TE_DECAL ) - entityIndex = MSG_ReadShort( &buf ); + entityIndex = MSG_ReadShort( pbuf ); else entityIndex = 0; pEnt = CL_GetEntityByIndex( entityIndex ); @@ -2064,348 +2057,348 @@ void CL_ParseTempEntity( sizebuf_t *msg ) CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, type == TE_BSPDECAL ? FDECAL_PERMANENT : 0 ); break; case TE_IMPLOSION: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - scale = MSG_ReadByte( &buf ); - count = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + scale = MSG_ReadByte( pbuf ); + count = MSG_ReadByte( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_Implosion( pos, scale, count, life ); break; case TE_SPRITETRAIL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - life = (float)MSG_ReadByte( &buf ) * 0.1f; - scale = (float)MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + life = (float)MSG_ReadByte( pbuf ) * 0.1f; + scale = (float)MSG_ReadByte( pbuf ); if( !scale ) scale = 1.0f; else scale *= 0.1f; - vel = (float)MSG_ReadByte( &buf ) * 10; - random = (float)MSG_ReadByte( &buf ) * 10; + vel = (float)MSG_ReadByte( pbuf ) * 10; + random = (float)MSG_ReadByte( pbuf ) * 10; R_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel ); break; case TE_SPRITE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - scale = (float)MSG_ReadByte( &buf ) * 0.1f; - brightness = (float)MSG_ReadByte( &buf ) / 255.0f; + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + scale = (float)MSG_ReadByte( pbuf ) * 0.1f; + brightness = (float)MSG_ReadByte( pbuf ) / 255.0f; R_TempSprite( pos, vec3_origin, scale, modelIndex, kRenderTransAdd, kRenderFxNone, brightness, 0.0, FTENT_SPRANIMATE ); break; case TE_GLOWSPRITE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - life = (float)MSG_ReadByte( &buf ) * 0.1f; - scale = (float)MSG_ReadByte( &buf ) * 0.1f; - brightness = (float)MSG_ReadByte( &buf ) / 255.0f; + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + life = (float)MSG_ReadByte( pbuf ) * 0.1f; + scale = (float)MSG_ReadByte( pbuf ) * 0.1f; + brightness = (float)MSG_ReadByte( pbuf ) / 255.0f; R_TempSprite( pos, vec3_origin, scale, modelIndex, kRenderGlow, kRenderFxNoDissipation, brightness, life, FTENT_FADEOUT ); break; case TE_STREAK_SPLASH: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - color = MSG_ReadByte( &buf ); - count = MSG_ReadShort( &buf ); - vel = (float)MSG_ReadShort( &buf ); - random = (float)MSG_ReadShort( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + color = MSG_ReadByte( pbuf ); + count = MSG_ReadShort( pbuf ); + vel = (float)MSG_ReadShort( pbuf ); + random = (float)MSG_ReadShort( pbuf ); R_StreakSplash( pos, pos2, color, count, vel, -random, random ); break; case TE_DLIGHT: dl = CL_AllocDlight( 0 ); - dl->origin[0] = MSG_ReadCoord( &buf ); - dl->origin[1] = MSG_ReadCoord( &buf ); - dl->origin[2] = MSG_ReadCoord( &buf ); - dl->radius = (float)(MSG_ReadByte( &buf ) * 10.0f); - dl->color.r = MSG_ReadByte( &buf ); - dl->color.g = MSG_ReadByte( &buf ); - dl->color.b = MSG_ReadByte( &buf ); - dl->die = cl.time + (float)(MSG_ReadByte( &buf ) * 0.1f); - dl->decay = (float)(MSG_ReadByte( &buf ) * 10.0f); + dl->origin[0] = MSG_ReadCoord( pbuf ); + dl->origin[1] = MSG_ReadCoord( pbuf ); + dl->origin[2] = MSG_ReadCoord( pbuf ); + dl->radius = (float)(MSG_ReadByte( pbuf ) * 10.0f); + dl->color.r = MSG_ReadByte( pbuf ); + dl->color.g = MSG_ReadByte( pbuf ); + dl->color.b = MSG_ReadByte( pbuf ); + dl->die = cl.time + (float)(MSG_ReadByte( pbuf ) * 0.1f); + dl->decay = (float)(MSG_ReadByte( pbuf ) * 10.0f); break; case TE_ELIGHT: - dl = CL_AllocElight( MSG_ReadShort( &buf )); - dl->origin[0] = MSG_ReadCoord( &buf ); - dl->origin[1] = MSG_ReadCoord( &buf ); - dl->origin[2] = MSG_ReadCoord( &buf ); - dl->radius = MSG_ReadCoord( &buf ); - dl->color.r = MSG_ReadByte( &buf ); - dl->color.g = MSG_ReadByte( &buf ); - dl->color.b = MSG_ReadByte( &buf ); - life = (float)MSG_ReadByte( &buf ) * 0.1f; + dl = CL_AllocElight( MSG_ReadShort( pbuf )); + dl->origin[0] = MSG_ReadCoord( pbuf ); + dl->origin[1] = MSG_ReadCoord( pbuf ); + dl->origin[2] = MSG_ReadCoord( pbuf ); + dl->radius = MSG_ReadCoord( pbuf ); + dl->color.r = MSG_ReadByte( pbuf ); + dl->color.g = MSG_ReadByte( pbuf ); + dl->color.b = MSG_ReadByte( pbuf ); + life = (float)MSG_ReadByte( pbuf ) * 0.1f; dl->die = cl.time + life; - dl->decay = MSG_ReadCoord( &buf ); + dl->decay = MSG_ReadCoord( pbuf ); if( life != 0 ) dl->decay /= life; break; case TE_TEXTMESSAGE: - CL_ParseTextMessage( &buf ); + CL_ParseTextMessage( pbuf ); break; case TE_LINE: case TE_BOX: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - life = (float)(MSG_ReadShort( &buf ) * 0.1f); - r = MSG_ReadByte( &buf ); - g = MSG_ReadByte( &buf ); - b = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + life = (float)(MSG_ReadShort( pbuf ) * 0.1f); + r = MSG_ReadByte( pbuf ); + g = MSG_ReadByte( pbuf ); + b = MSG_ReadByte( pbuf ); if( type == TE_LINE ) R_ParticleLine( pos, pos2, r, g, b, life ); else R_ParticleBox( pos, pos2, r, g, b, life ); break; case TE_LARGEFUNNEL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - flags = MSG_ReadShort( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + flags = MSG_ReadShort( pbuf ); R_LargeFunnel( pos, flags ); R_FunnelSprite( pos, modelIndex, flags ); break; case TE_BLOODSTREAM: case TE_BLOOD: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - color = MSG_ReadByte( &buf ); - count = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + color = MSG_ReadByte( pbuf ); + count = MSG_ReadByte( pbuf ); if( type == TE_BLOOD ) R_Blood( pos, pos2, color, count ); else R_BloodStream( pos, pos2, color, count ); break; case TE_SHOWLINE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); R_ShowLine( pos, pos2 ); break; case TE_FIZZ: - entityIndex = MSG_ReadShort( &buf ); - modelIndex = MSG_ReadShort( &buf ); - scale = MSG_ReadByte( &buf ); // same as density + entityIndex = MSG_ReadShort( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + scale = MSG_ReadByte( pbuf ); // same as density pEnt = CL_GetEntityByIndex( entityIndex ); R_FizzEffect( pEnt, modelIndex, scale ); break; case TE_MODEL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); ang[0] = 0.0f; - ang[1] = MSG_ReadAngle( &buf ); // yaw angle + ang[1] = MSG_ReadAngle( pbuf ); // yaw angle ang[2] = 0.0f; - modelIndex = MSG_ReadShort( &buf ); - flags = MSG_ReadByte( &buf ); // sound flags - life = (float)(MSG_ReadByte( &buf ) * 0.1f); + modelIndex = MSG_ReadShort( pbuf ); + flags = MSG_ReadByte( pbuf ); // sound flags + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_TempModel( pos, pos2, ang, life, modelIndex, flags ); break; case TE_EXPLODEMODEL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - vel = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadShort( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + vel = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadShort( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_TempSphereModel( pos, vel, life, count, modelIndex ); break; case TE_BREAKMODEL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - ang[0] = MSG_ReadCoord( &buf ); - ang[1] = MSG_ReadCoord( &buf ); - ang[2] = MSG_ReadCoord( &buf ); - random = (float)MSG_ReadByte( &buf ) * 10.0f; - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - flags = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + ang[0] = MSG_ReadCoord( pbuf ); + ang[1] = MSG_ReadCoord( pbuf ); + ang[2] = MSG_ReadCoord( pbuf ); + random = (float)MSG_ReadByte( pbuf ) * 10.0f; + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); + flags = MSG_ReadByte( pbuf ); R_BreakModel( pos, pos2, ang, random, life, count, modelIndex, (char)flags ); break; case TE_GUNSHOTDECAL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - entityIndex = MSG_ReadShort( &buf ); - decalIndex = MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + entityIndex = MSG_ReadShort( pbuf ); + decalIndex = MSG_ReadByte( pbuf ); CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 ); R_BulletImpactParticles( pos ); flags = COM_RandomLong( 0, 0x7fff ); - if( flags < 0x3fff ) - R_RicochetSound_( pos, flags % 5 ); + if( flags < 0x3fff && ( count = SoundList_Count( Ricochet ))) + R_RicochetSoundByIndex( pos, flags % count ); break; case TE_SPRAY: case TE_SPRITE_SPRAY: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - vel = (float)MSG_ReadByte( &buf ); - random = (float)MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + vel = (float)MSG_ReadByte( pbuf ); + random = (float)MSG_ReadByte( pbuf ); if( type == TE_SPRAY ) { - flags = MSG_ReadByte( &buf ); // rendermode + flags = MSG_ReadByte( pbuf ); // rendermode R_Spray( pos, pos2, modelIndex, count, vel, random, flags ); } else R_Sprite_Spray( pos, pos2, modelIndex, count, vel * 2.0f, random ); break; case TE_ARMOR_RICOCHET: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + scale = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, scale ); R_RicochetSound( pos ); break; case TE_PLAYERDECAL: - color = MSG_ReadByte( &buf ) - 1; // playernum - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - entityIndex = MSG_ReadShort( &buf ); - decalIndex = MSG_ReadByte( &buf ); + color = MSG_ReadByte( pbuf ) - 1; // playernum + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + entityIndex = MSG_ReadShort( pbuf ); + decalIndex = MSG_ReadByte( pbuf ); CL_PlayerDecal( color, decalIndex, entityIndex, pos ); break; case TE_BUBBLES: case TE_BUBBLETRAIL: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - scale = MSG_ReadCoord( &buf ); // water height - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - vel = MSG_ReadCoord( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + scale = MSG_ReadCoord( pbuf ); // water height + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + vel = MSG_ReadCoord( pbuf ); if( type == TE_BUBBLES ) R_Bubbles( pos, pos2, scale, modelIndex, count, vel ); else R_BubbleTrail( pos, pos2, scale, modelIndex, count, vel ); break; case TE_BLOODSPRITE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); // sprite #1 - decalIndex = MSG_ReadShort( &buf ); // sprite #2 - color = MSG_ReadByte( &buf ); - scale = (float)MSG_ReadByte( &buf ); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); // sprite #1 + decalIndex = MSG_ReadShort( pbuf ); // sprite #2 + color = MSG_ReadByte( pbuf ); + scale = (float)MSG_ReadByte( pbuf ); R_BloodSprite( pos, color, modelIndex, decalIndex, scale ); break; case TE_PROJECTILE: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - modelIndex = MSG_ReadShort( &buf ); - life = MSG_ReadByte( &buf ); - color = MSG_ReadByte( &buf ); // playernum + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + life = MSG_ReadByte( pbuf ); + color = MSG_ReadByte( pbuf ); // playernum R_Projectile( pos, pos2, modelIndex, life, color, NULL ); break; case TE_PLAYERSPRITES: - color = MSG_ReadShort( &buf ); // entitynum - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - random = (float)MSG_ReadByte( &buf ); + color = MSG_ReadShort( pbuf ); // entitynum + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + random = (float)MSG_ReadByte( pbuf ); R_PlayerSprites( color, modelIndex, count, random ); break; case TE_PARTICLEBURST: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - scale = (float)MSG_ReadShort( &buf ); - color = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + scale = (float)MSG_ReadShort( pbuf ); + color = MSG_ReadByte( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_ParticleBurst( pos, scale, color, life ); break; case TE_FIREFIELD: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - scale = (float)MSG_ReadShort( &buf ); - modelIndex = MSG_ReadShort( &buf ); - count = MSG_ReadByte( &buf ); - flags = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + scale = (float)MSG_ReadShort( pbuf ); + modelIndex = MSG_ReadShort( pbuf ); + count = MSG_ReadByte( pbuf ); + flags = MSG_ReadByte( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_FireField( pos, scale, modelIndex, count, flags, life ); break; case TE_PLAYERATTACHMENT: - color = MSG_ReadByte( &buf ); // playernum - scale = MSG_ReadCoord( &buf ); // height - modelIndex = MSG_ReadShort( &buf ); - life = (float)(MSG_ReadShort( &buf ) * 0.1f); + color = MSG_ReadByte( pbuf ); // playernum + scale = MSG_ReadCoord( pbuf ); // height + modelIndex = MSG_ReadShort( pbuf ); + life = (float)(MSG_ReadShort( pbuf ) * 0.1f); R_AttachTentToPlayer( color, modelIndex, scale, life ); break; case TE_KILLPLAYERATTACHMENTS: - color = MSG_ReadByte( &buf ); // playernum + color = MSG_ReadByte( pbuf ); // playernum R_KillAttachedTents( color ); break; case TE_MULTIGUNSHOT: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ) * 0.1f; - pos2[1] = MSG_ReadCoord( &buf ) * 0.1f; - pos2[2] = MSG_ReadCoord( &buf ) * 0.1f; - ang[0] = MSG_ReadCoord( &buf ) * 0.01f; - ang[1] = MSG_ReadCoord( &buf ) * 0.01f; + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ) * 0.1f; + pos2[1] = MSG_ReadCoord( pbuf ) * 0.1f; + pos2[2] = MSG_ReadCoord( pbuf ) * 0.1f; + ang[0] = MSG_ReadCoord( pbuf ) * 0.01f; + ang[1] = MSG_ReadCoord( pbuf ) * 0.01f; ang[2] = 0.0f; - count = MSG_ReadByte( &buf ); - decalIndices[0] = MSG_ReadByte( &buf ); + count = MSG_ReadByte( pbuf ); + decalIndices[0] = MSG_ReadByte( pbuf ); R_MultiGunshot( pos, pos2, ang, count, 1, decalIndices ); break; case TE_USERTRACER: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - pos2[0] = MSG_ReadCoord( &buf ); - pos2[1] = MSG_ReadCoord( &buf ); - pos2[2] = MSG_ReadCoord( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - color = MSG_ReadByte( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + pos[0] = MSG_ReadCoord( pbuf ); + pos[1] = MSG_ReadCoord( pbuf ); + pos[2] = MSG_ReadCoord( pbuf ); + pos2[0] = MSG_ReadCoord( pbuf ); + pos2[1] = MSG_ReadCoord( pbuf ); + pos2[2] = MSG_ReadCoord( pbuf ); + life = (float)(MSG_ReadByte( pbuf ) * 0.1f); + color = MSG_ReadByte( pbuf ); + scale = (float)(MSG_ReadByte( pbuf ) * 0.1f); R_UserTracerParticle( pos, pos2, life, color, scale, 0, NULL ); break; default: - Con_DPrintf( S_ERROR "ParseTempEntity: illegible TE message %i\n", type ); + Con_DPrintf( S_ERROR "%s: illegible TE message %i\n", __func__, type ); break; } // throw warning - if( MSG_CheckOverflow( &buf )) - Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message %i\n", type ); + if( MSG_CheckOverflow( pbuf )) + Con_DPrintf( S_WARN "%s: overflow TE message %i\n", __func__, type ); } @@ -2423,25 +2416,25 @@ LIGHT STYLE MANAGEMENT CL_ClearLightStyles ================ */ -void CL_ClearLightStyles( void ) +static void CL_ClearLightStyles( void ) { memset( cl.lightstyles, 0, sizeof( cl.lightstyles )); } void CL_SetLightstyle( int style, const char *s, float f ) { - int i, k; - lightstyle_t *ls; - float val1, val2; + int i; + lightstyle_t *ls; - Assert( s != NULL ); - Assert( style >= 0 && style < MAX_LIGHTSTYLES ); + if( unlikely( style < 0 || style >= MAX_LIGHTSTYLES )) + { + Con_Printf( S_WARN "%s: ignored invalid lightstyle id %d\n", __func__, style ); + return; + } ls = &cl.lightstyles[style]; - Q_strncpy( ls->pattern, s, sizeof( ls->pattern )); - - ls->length = Q_strlen( s ); + ls->length = Q_strncpy( ls->pattern, s, sizeof( ls->pattern )); ls->time = f; // set local time for( i = 0; i < ls->length; i++ ) @@ -2451,10 +2444,10 @@ void CL_SetLightstyle( int style, const char *s, float f ) // check for allow interpolate // NOTE: fast flickering styles looks ugly when interpolation is running - for( k = 0; k < (ls->length - 1); k++ ) + for( i = 0; i < ( ls->length - 1 ); i++ ) { - val1 = ls->map[(k+0) % ls->length]; - val2 = ls->map[(k+1) % ls->length]; + float val1 = ls->map[( i + 0 ) % ls->length]; + float val2 = ls->map[( i + 1 ) % ls->length]; if( fabs( val1 - val2 ) > STYLE_LERPING_THRESHOLD ) { @@ -2463,7 +2456,8 @@ void CL_SetLightstyle( int style, const char *s, float f ) } } - Con_Reportf( "Lightstyle %i (%s), interp %s\n", style, ls->pattern, ls->interp ? "Yes" : "No" ); + if( ls->length >= 1 ) + Con_Reportf( "Lightstyle %i (%s), interp %s\n", style, ls->pattern, ls->interp ? "Yes" : "No" ); } /* @@ -2473,15 +2467,15 @@ DLIGHT MANAGEMENT ============================================================== */ -dlight_t cl_dlights[MAX_DLIGHTS]; -dlight_t cl_elights[MAX_ELIGHTS]; +static dlight_t cl_dlights[MAX_DLIGHTS]; +static dlight_t cl_elights[MAX_ELIGHTS]; /* ================ CL_ClearDlights ================ */ -void CL_ClearDlights( void ) +static void CL_ClearDlights( void ) { memset( cl_dlights, 0, sizeof( cl_dlights )); memset( cl_elights, 0, sizeof( cl_elights )); @@ -2585,31 +2579,43 @@ CL_DecayLights */ void CL_DecayLights( void ) { - dlight_t *dl; - float time; + const float time = cl.time; + const float dt = cl.time - cl.oldtime; int i; - time = cl.time - cl.oldtime; - - for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ ) + for( i = 0; i < MAX_DLIGHTS; i++ ) { - if( !dl->radius ) continue; + dlight_t *dl = &cl_dlights[i]; - dl->radius -= time * dl->decay; - if( dl->radius < 0 ) dl->radius = 0; + if( !dl->radius ) + continue; - if( dl->die < cl.time || !dl->radius ) + if( dl->die < time ) + { + memset( dl, 0, sizeof( *dl )); + continue; + } + + dl->radius -= dt * dl->decay; + if( dl->radius <= 0 ) memset( dl, 0, sizeof( *dl )); } - for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ ) + for( i = 0; i < MAX_ELIGHTS; i++ ) { - if( !dl->radius ) continue; + dlight_t *dl = &cl_elights[i]; - dl->radius -= time * dl->decay; - if( dl->radius < 0 ) dl->radius = 0; + if( !dl->radius ) + continue; - if( dl->die < cl.time || !dl->radius ) + if( dl->die < time ) + { + memset( dl, 0, sizeof( *dl )); + continue; + } + + dl->radius -= dt * dl->decay; + if( dl->radius <= 0 ) memset( dl, 0, sizeof( *dl )); } } @@ -2633,12 +2639,12 @@ CL_UpdateFlashlight update client flashlight ================ */ -void CL_UpdateFlashlight( cl_entity_t *ent ) +static void CL_UpdateFlashlight( cl_entity_t *ent ) { vec3_t forward, view_ofs; vec3_t vecSrc, vecEnd; float falloff; - pmtrace_t *trace; + pmtrace_t trace; cl_entity_t *hit; dlight_t *dl; @@ -2669,32 +2675,65 @@ void CL_UpdateFlashlight( cl_entity_t *ent ) VectorAdd( ent->origin, view_ofs, vecSrc ); VectorMA( vecSrc, FLASHLIGHT_DISTANCE, forward, vecEnd ); - trace = CL_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX ); + trace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_BOX ); // update flashlight endpos dl = CL_AllocDlight( ent->index ); #if 1 - hit = CL_GetEntityByIndex( clgame.pmove->visents[trace->ent].info ); + hit = CL_GetEntityByIndex( clgame.pmove->physents[trace.ent].info ); if( hit && hit->model && ( hit->model->type == mod_alias || hit->model->type == mod_studio )) VectorCopy( hit->origin, dl->origin ); - else VectorCopy( trace->endpos, dl->origin ); + else VectorCopy( trace.endpos, dl->origin ); #else VectorCopy( trace->endpos, dl->origin ); #endif // compute falloff - falloff = trace->fraction * FLASHLIGHT_DISTANCE; + falloff = trace.fraction * FLASHLIGHT_DISTANCE; if( falloff < 500.0f ) falloff = 1.0f; else falloff = 500.0f / falloff; falloff *= falloff; // apply brigthness to dlight - dl->color.r = bound( 0, falloff * 255, 255 ); - dl->color.g = bound( 0, falloff * 255, 255 ); - dl->color.b = bound( 0, falloff * 255, 255 ); + dl->color.r = dl->color.g = dl->color.b = bound( 0, falloff * 255, 255 ); dl->die = cl.time + 0.01f; // die on next frame dl->radius = 80; } +static void R_EntityDimlight( cl_entity_t *ent, int key ) +{ + dlight_t *dl = CL_AllocDlight( key ); + + VectorCopy( ent->origin, dl->origin ); + dl->color.r = dl->color.g = dl->color.b = 100; + dl->radius = COM_RandomFloat( 200.0f, 231.0f ); + dl->die = cl.time + 0.001; +} + +static void R_EntityLight( cl_entity_t *ent, int key ) +{ + dlight_t *dl = CL_AllocDlight( key ); + + VectorCopy( ent->origin, dl->origin ); + dl->color.r = dl->color.g = dl->color.b = 100; + dl->radius = 200; + dl->die = cl.time + 0.001; + + R_RocketFlare( ent->origin ); +} + +static void R_EntityBrightlight( cl_entity_t *ent, int key, int radius ) +{ + dlight_t *dl = CL_AllocDlight( key ); + + VectorCopy( ent->origin, dl->origin ); + dl->origin[2] += 16.0f; + dl->color.r = dl->color.g = dl->color.b = 250; + if( !radius ) + dl->radius = COM_RandomFloat( 400.0f, 431.0f ); + else dl->radius = 400; + dl->die = cl.time + 0.001; +} + /* ================ CL_AddEntityEffects @@ -2704,50 +2743,46 @@ apply various effects to entity origin or attachment */ void CL_AddEntityEffects( cl_entity_t *ent ) { - // yellow flies effect 'monster stuck in the wall' - if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD ) && !RP_LOCALCLIENT( ent )) - R_EntityParticles( ent ); - - if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) + // players have special set of effects, from CL_LinkPlayers + if( ent->player && ent->index != cl.viewentity ) { - if( ent->player && !Host_IsQuakeCompatible( )) + if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT )) + R_EntityBrightlight( ent, ent->index /* 4 in GoldSrc */, 0 ); + + if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) + R_EntityDimlight( ent, ent->index /* 4 in GoldSrc */ ); + } + else if( RP_LOCALCLIENT( ent )) + { + // from CL_PlayerFlashlight + if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT )) + R_EntityBrightlight( ent, ent->index /* 1 in GoldSrc */, 400 ); + else if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) { - CL_UpdateFlashlight( ent ); - } - else - { - dlight_t *dl = CL_AllocDlight( ent->index ); - dl->color.r = dl->color.g = dl->color.b = 100; - dl->radius = COM_RandomFloat( 200, 231 ); - VectorCopy( ent->origin, dl->origin ); - dl->die = cl.time + 0.001; + if( Host_IsQuakeCompatible( )) + R_EntityDimlight( ent, ent->index ); + else + CL_UpdateFlashlight( ent ); } } - - if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT )) + else { - dlight_t *dl = CL_AllocDlight( ent->index ); - dl->color.r = dl->color.g = dl->color.b = 250; - if( ent->player ) dl->radius = 400; // don't flickering - else dl->radius = COM_RandomFloat( 400, 431 ); - VectorCopy( ent->origin, dl->origin ); - dl->die = cl.time + 0.001; - dl->origin[2] += 16.0f; - } + // from CL_LinkPacketEntities + if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD )) + R_EntityParticles( ent ); - // add light effect - if( FBitSet( ent->curstate.effects, EF_LIGHT )) - { - dlight_t *dl = CL_AllocDlight( ent->index ); - dl->color.r = dl->color.g = dl->color.b = 100; - VectorCopy( ent->origin, dl->origin ); - R_RocketFlare( ent->origin ); - dl->die = cl.time + 0.001; - dl->radius = 200; + if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT )) + R_EntityBrightlight( ent, ent->index, 0 ); + + if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) + R_EntityDimlight( ent, ent->index ); + + if( FBitSet( ent->curstate.effects, EF_LIGHT )) + R_EntityLight( ent, ent->curstate.number ); } // studio models are handle muzzleflashes difference - if( FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ) && Mod_AliasExtradata( ent->model )) + if( FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ) && ent->model && ent->model->type == mod_alias ) { dlight_t *dl = CL_AllocDlight( ent->index ); vec3_t fv; @@ -2776,15 +2811,11 @@ void CL_AddModelEffects( cl_entity_t *ent ) vec3_t neworigin; vec3_t oldorigin; - if( !ent->model ) return; + if( !ent->model || ent->player ) + return; - switch( ent->model->type ) - { - case mod_alias: - case mod_studio: - break; - default: return; - } + if( ent->model->type != mod_alias && ent->model->type != mod_studio ) + return; if( cls.demoplayback == DEMO_QUAKE1 ) { @@ -2804,19 +2835,15 @@ void CL_AddModelEffects( cl_entity_t *ent ) if( FBitSet( ent->model->flags, STUDIO_GIB )) R_RocketTrail( oldorigin, neworigin, 2 ); - - if( FBitSet( ent->model->flags, STUDIO_ZOMGIB )) + else if( FBitSet( ent->model->flags, STUDIO_ZOMGIB )) R_RocketTrail( oldorigin, neworigin, 4 ); - - if( FBitSet( ent->model->flags, STUDIO_TRACER )) + else if( FBitSet( ent->model->flags, STUDIO_TRACER )) R_RocketTrail( oldorigin, neworigin, 3 ); - - if( FBitSet( ent->model->flags, STUDIO_TRACER2 )) + else if( FBitSet( ent->model->flags, STUDIO_TRACER2 )) R_RocketTrail( oldorigin, neworigin, 5 ); - - if( FBitSet( ent->model->flags, STUDIO_ROCKET )) + else if( FBitSet( ent->model->flags, STUDIO_ROCKET )) { - dlight_t *dl = CL_AllocDlight( ent->index ); + dlight_t *dl = CL_AllocDlight( ent->curstate.number ); dl->color.r = dl->color.g = dl->color.b = 200; VectorCopy( ent->origin, dl->origin ); @@ -2830,11 +2857,9 @@ void CL_AddModelEffects( cl_entity_t *ent ) R_RocketTrail( oldorigin, neworigin, 0 ); } - - if( FBitSet( ent->model->flags, STUDIO_GRENADE )) + else if( FBitSet( ent->model->flags, STUDIO_GRENADE )) R_RocketTrail( oldorigin, neworigin, 1 ); - - if( FBitSet( ent->model->flags, STUDIO_TRACER3 )) + else if( FBitSet( ent->model->flags, STUDIO_TRACER3 )) R_RocketTrail( oldorigin, neworigin, 6 ); } @@ -2852,10 +2877,10 @@ void CL_TestLights( void ) float f, r; dlight_t *dl; - if( !CVAR_TO_BOOL( cl_testlights )) + if( !cl_testlights.value ) return; - numLights = bound( 1, cl_testlights->value, MAX_DLIGHTS ); + numLights = bound( 1, cl_testlights.value, MAX_DLIGHTS ); AngleVectors( cl.viewangles, forward, right, NULL ); for( i = 0; i < numLights; i++ ) @@ -2914,7 +2939,7 @@ CL_PlayerDecal spray custom colored decal (clan logo etc) =============== */ -void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos ) +static void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos ) { int textureIndex = 0; customization_t *pCust = NULL; @@ -2928,8 +2953,26 @@ void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos { if( !pCust->nUserData1 ) { - const char *decalname = va( "player%dlogo%d", playernum, customIndex ); + char decalname[MAX_VA_STRING]; + int width, height; + + Q_snprintf( decalname, sizeof( decalname ), "player%dlogo%d", playernum, customIndex ); + textureIndex = ref.dllFuncs.GL_FindTexture( decalname ); + if( textureIndex != 0 ) + ref.dllFuncs.GL_FreeTexture( textureIndex ); + pCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL ); + + width = REF_GET_PARM( PARM_TEX_WIDTH, pCust->nUserData1 ); + height = REF_GET_PARM( PARM_TEX_HEIGHT, pCust->nUserData1 ); + + if( width > cl_logomaxdim.value || height > cl_logomaxdim.value ) + { + double scale = cl_logomaxdim.value / Q_max( width, height ); + width = round( width * scale ); + height = round( height * scale ); + ref.dllFuncs.R_OverrideTextureSourceSize( pCust->nUserData1, width, height ); // default custom decal from HL1 + } } textureIndex = pCust->nUserData1; } @@ -2974,8 +3017,47 @@ int GAME_EXPORT CL_DecalIndex( int id ) if( cl.decal_index[id] == 0 ) { + int gl_texturenum = 0; + Image_SetForceFlags( IL_LOAD_DECAL ); - cl.decal_index[id] = ref.dllFuncs.GL_LoadTexture( host.draw_decals[id], NULL, 0, TF_DECAL ); + + if( Mod_AllowMaterials( )) + { + string decalname; + + if( Q_snprintf( decalname, sizeof( decalname ), "materials/decals/%s.tga", host.draw_decals[id] ) > 0 ) + { + if( g_fsapi.FileExists( decalname, false )) + { + gl_texturenum = ref.dllFuncs.GL_LoadTexture( decalname, NULL, 0, TF_DECAL ); + if( host_allow_materials.value == 2.0f ) + Con_Printf( "Looking for %s decal replacement...%s (%s)\n", host.draw_decals[id], gl_texturenum != 0 ? S_GREEN "OK" : S_RED "FAIL", decalname ); + } + else if( host_allow_materials.value == 2.0f ) + Con_Printf( "Looking for %s decal replacement..." S_YELLOW "MISS (%s)\n", host.draw_decals[id], decalname ); + } + else if( host_allow_materials.value == 2.0f ) + Con_Printf( "Looking for %s decal replacement..." S_YELLOW "MISS (overflow)\n", host.draw_decals[id] ); + + if( gl_texturenum ) + { + byte *fin; + + Q_snprintf( decalname, sizeof( decalname ), "decals.wad/%s", host.draw_decals[id] ); + + if(( fin = g_fsapi.LoadFile( decalname, NULL, false )) != NULL ) + { + mip_t *mip = (mip_t *)fin; + ref.dllFuncs.R_OverrideTextureSourceSize( gl_texturenum, mip->width, mip->height ); + Mem_Free( fin ); + } + } + } + + if( !gl_texturenum ) + gl_texturenum = ref.dllFuncs.GL_LoadTexture( host.draw_decals[id], NULL, 0, TF_DECAL ); + + cl.decal_index[id] = gl_texturenum; Image_ClearForceFlags(); } @@ -3002,26 +3084,6 @@ EFRAGS MANAGEMENT ============================================================== */ -efrag_t cl_efrags[MAX_EFRAGS]; - -/* -============== -CL_ClearEfrags -============== -*/ -void CL_ClearEfrags( void ) -{ - int i; - - memset( cl_efrags, 0, sizeof( cl_efrags )); - - // allocate the efrags and chain together into a free list - clgame.free_efrags = cl_efrags; - for( i = 0; i < MAX_EFRAGS - 1; i++ ) - clgame.free_efrags[i].entnext = &clgame.free_efrags[i+1]; - clgame.free_efrags[i].entnext = NULL; -} - /* ======================= R_ClearStaticEntities @@ -3059,4 +3121,3 @@ void CL_ClearEffects( void ) CL_ClearParticles (); CL_ClearLightStyles (); } - diff --git a/engine/client/cl_tent.h b/engine/client/cl_tent.h index 1cae7155..53a7e2b1 100644 --- a/engine/client/cl_tent.h +++ b/engine/client/cl_tent.h @@ -77,11 +77,9 @@ void R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int void R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life ); void R_PlayerSprites( int client, int modelIndex, int count, int size ); void R_Sprite_WallPuff( struct tempent_s *pTemp, float scale ); -void R_DebugParticle( const vec3_t pos, byte r, byte g, byte b ); void R_RicochetSound( const vec3_t pos ); struct dlight_s *CL_AllocDlight( int key ); struct dlight_s *CL_AllocElight( int key ); -void CL_UpdateFlashlight( cl_entity_t *pEnt ); void CL_AddEntityEffects( cl_entity_t *ent ); void CL_AddModelEffects( cl_entity_t *ent ); void CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags ); diff --git a/engine/client/cl_video.c b/engine/client/cl_video.c index 1b4ad6e5..6a734ab6 100644 --- a/engine/client/cl_video.c +++ b/engine/client/cl_video.c @@ -24,11 +24,6 @@ AVI PLAYING ================================================================= */ -static int xres, yres; -static float video_duration; -static float cin_time; -static int cin_frame; -static wavdata_t cin_audio; static movie_state_t *cin_state; /* @@ -68,7 +63,7 @@ qboolean SCR_NextMovie( void ) return true; } -void SCR_CreateStartupVids( void ) +static void SCR_CreateStartupVids( void ) { file_t *f; @@ -88,7 +83,17 @@ void SCR_CheckStartupVids( void ) char *pfile; string token; - if( Sys_CheckParm( "-nointro" ) || host_developer.value || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME ) +#if 0 + if( host_developer.value ) + { + // don't run movies where we in developer-mode + cls.movienum = -1; + CL_CheckStartupDemos(); + return; + } +#endif + + if( Sys_CheckParm( "-nointro" ) || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME ) { // don't run movies where we in developer-mode cls.movienum = -1; @@ -147,23 +152,9 @@ void SCR_RunCinematic( void ) Key_SetKeyDest( key_menu ); S_StopStreaming(); cls.movienum = -1; - cin_time = 0.0f; cls.signon = 0; return; } - - // advances cinematic time (ignores maxfps and host_framerate settings) - cin_time += host.realframetime; - - // stop the video after it finishes - if( cin_time > video_duration + 0.1f ) - { - SCR_NextMovie( ); - return; - } - - // read the next frame - cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); } /* @@ -176,21 +167,11 @@ should be skipped */ qboolean SCR_DrawCinematic( void ) { - static int last_frame = -1; - qboolean redraw = false; - byte *frame = NULL; - - if( !ref.initialized || cin_time <= 0.0f ) + if( !ref.initialized ) return false; - if( cin_frame != last_frame ) - { - frame = AVI_GetVideoFrame( cin_state, cin_frame ); - last_frame = cin_frame; - redraw = true; - } - - ref.dllFuncs.R_DrawStretchRaw( 0, 0, refState.width, refState.height, xres, yres, frame, redraw ); + if( !AVI_Think( cin_state )) + return SCR_NextMovie(); return true; } @@ -202,63 +183,69 @@ SCR_PlayCinematic */ qboolean SCR_PlayCinematic( const char *arg ) { - string path; + int x, y, w, h; const char *fullpath; + double video_ratio, screen_ratio, scale; fullpath = FS_GetDiskPath( arg, false ); if( FS_FileExists( arg, false ) && !fullpath ) { - Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", path ); + Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", arg ); return false; } AVI_OpenVideo( cin_state, fullpath, true, false ); - if( !AVI_IsActive( cin_state )) + if( !AVI_IsActive( cin_state ) || !AVI_GetVideoInfo( cin_state, &w, &h, NULL )) { AVI_CloseVideo( cin_state ); return false; } - if( !( AVI_GetVideoInfo( cin_state, &xres, &yres, &video_duration ))) // couldn't open this at all. + video_ratio = (double)w / (double)h; + screen_ratio = (double)refState.width / (double)refState.height; + + if( video_ratio < screen_ratio ) + scale = (double)refState.height / (double)h; + else + scale = (double)refState.width / (double)w; + + w = Q_rint( w * scale ); + h = Q_rint( h * scale ); + + if( video_ratio < screen_ratio ) { - AVI_CloseVideo( cin_state ); - return false; + x = (refState.width - w) / 2.0; + y = 0; + } + else + { + x = 0; + y = (refState.height - h) / 2.0; } - if( AVI_GetAudioInfo( cin_state, &cin_audio )) + if( AVI_HaveAudioTrack( cin_state )) { // begin streaming S_StopAllSounds( true ); S_StartStreaming(); } + AVI_SetParm( cin_state, + AVI_RENDER_X, x, + AVI_RENDER_Y, y, + AVI_RENDER_W, w, + AVI_RENDER_H, h, + AVI_PARM_LAST ); + UI_SetActiveMenu( false ); cls.state = ca_cinematic; Con_FastClose(); - cin_time = 0.0f; cls.signon = 0; return true; } -int SCR_GetAudioChunk( char *rawdata, int length ) -{ - int r; - - r = AVI_GetAudioChunk( cin_state, rawdata, cin_audio.loopStart, length ); - cin_audio.loopStart += r; // advance play position - - return r; -} - -wavdata_t *SCR_GetMovieInfo( void ) -{ - if( AVI_IsActive( cin_state )) - return &cin_audio; - return NULL; -} - /* ================== SCR_StopCinematic @@ -271,7 +258,6 @@ void SCR_StopCinematic( void ) AVI_CloseVideo( cin_state ); S_StopStreaming(); - cin_time = 0.0f; cls.state = ca_disconnected; cls.signon = 0; diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index cac27075..0a35b734 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -29,17 +29,17 @@ V_CalcViewRect calc frame rectangle (Quake1 style) =============== */ -void V_CalcViewRect( void ) +static void V_CalcViewRect( void ) { qboolean full = false; int sb_lines; float size; - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + if( Host_IsQuakeCompatible( )) { // intermission is always full screen if( cl.intermission ) size = 120.0f; - else size = scr_viewsize->value; + else size = scr_viewsize.value; if( size >= 120.0f ) sb_lines = 0; // no status bar at all @@ -47,12 +47,12 @@ void V_CalcViewRect( void ) sb_lines = 24; // no inventory else sb_lines = 48; - if( scr_viewsize->value >= 100.0f ) + if( scr_viewsize.value >= 100.0f ) { full = true; size = 100.0f; } - else size = scr_viewsize->value; + else size = scr_viewsize.value; if( cl.intermission ) { @@ -88,7 +88,7 @@ void V_CalcViewRect( void ) V_SetupViewModel =============== */ -void V_SetupViewModel( void ) +static void V_SetupViewModel( void ) { cl_entity_t *view = &clgame.viewent; player_info_t *info = &cl.players[cl.playernum]; @@ -118,7 +118,7 @@ void V_SetupViewModel( void ) V_SetRefParams =============== */ -void V_SetRefParams( ref_params_t *fd ) +static void V_SetRefParams( ref_params_t *fd ) { memset( fd, 0, sizeof( ref_params_t )); @@ -144,7 +144,9 @@ void V_SetRefParams( ref_params_t *fd ) VectorCopy( cl.viewangles, fd->cl_viewangles ); fd->health = cl.local.health; VectorCopy( cl.crosshairangle, fd->crosshairangle ); - fd->viewsize = scr_viewsize->value; + if( Host_IsQuakeCompatible( )) + fd->viewsize = scr_viewsize.value; + else fd->viewsize = 120.0f; VectorCopy( cl.punchangle, fd->punchangle ); fd->maxclients = cl.maxclients; @@ -163,7 +165,7 @@ void V_SetRefParams( ref_params_t *fd ) // get pointers to movement vars and user cmd fd->movevars = &clgame.movevars; - fd->cmd = cl.cmd; + fd->cmd = &cl.cmd; // setup viewport fd->viewport[0] = clgame.viewport[0]; @@ -182,7 +184,7 @@ V_MergeOverviewRefdef merge refdef with overview settings =============== */ -void V_RefApplyOverview( ref_viewpass_t *rvp ) +static void V_RefApplyOverview( ref_viewpass_t *rvp ) { ref_overview_t *ov = &clgame.overView; float aspect; @@ -229,12 +231,59 @@ void V_RefApplyOverview( ref_viewpass_t *rvp ) ref.dllFuncs.GL_OrthoBounds( mins, maxs ); } +/* +==================== +V_CalcFov +==================== +*/ +static float V_CalcFov( float *fov_x, float width, float height ) +{ + float x, half_fov_y; + + if( *fov_x < 1.0f || *fov_x > 179.0f ) + *fov_x = 90.0f; // default value + + x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); + half_fov_y = atan( height / x ); + + return RAD2DEG( half_fov_y ) * 2; +} + +/* +==================== +V_AdjustFov +==================== +*/ +static void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ) +{ + float x, y; + + if( width * 3 == 4 * height || width * 4 == height * 5 ) + { + // 4:3 or 5:4 ratio + return; + } + + if( lock_x ) + { + *fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI_F / 360.0f * 0.5f )) * 360 / M_PI_F; + return; + } + + y = V_CalcFov( fov_x, 640, 480 ); + x = *fov_x; + + *fov_x = V_CalcFov( &y, height, width ); + if( *fov_x < x ) *fov_x = x; + else *fov_y = y; +} + /* ============= V_GetRefParams ============= */ -void V_GetRefParams( ref_params_t *fd, ref_viewpass_t *rvp ) +static void V_GetRefParams( ref_params_t *fd, ref_viewpass_t *rvp ) { // part1: deniable updates VectorCopy( fd->simvel, cl.simvel ); @@ -264,7 +313,7 @@ void V_GetRefParams( ref_params_t *fd, ref_viewpass_t *rvp ) rvp->fov_y = V_CalcFov( &rvp->fov_x, clgame.viewport[2], clgame.viewport[3] ); // adjust FOV for widescreen - if( refState.wideScreen && r_adjust_fov->value ) + if( refState.wideScreen && r_adjust_fov.value ) V_AdjustFov( &rvp->fov_x, &rvp->fov_y, clgame.viewport[2], clgame.viewport[3], false ); rvp->flags = 0; @@ -292,14 +341,16 @@ qboolean V_PreRender( void ) // if the screen is disabled (loading plaque is up) if( cls.disable_screen ) { - if(( host.realtime - cls.disable_screen ) > cl_timeout->value ) + if(( host.realtime - cls.disable_screen ) > cl_timeout.value ) { - Con_Reportf( "V_PreRender: loading plaque timed out\n" ); + Con_Reportf( "%s: loading plaque timed out\n", __func__ ); cls.disable_screen = 0.0f; } return false; } + V_CheckGamma(); + ref.dllFuncs.R_BeginFrame( !cl.paused && ( cls.state == ca_active )); GL_UpdateSwapInterval( ); @@ -317,11 +368,13 @@ V_RenderView */ void V_RenderView( void ) { - ref_params_t rp; + // HACKHACK: make ref params static + // not really critical but allows client.dll to take address of refdef and don't trigger ASan + static ref_params_t rp; ref_viewpass_t rvp; int viewnum = 0; - if( !cl.video_prepped || ( !ui_renderworld->value && UI_IsVisible() && !cl.background )) + if( !cl.video_prepped || ( !ui_renderworld.value && UI_IsVisible() && !cl.background )) return; // still loading V_CalcViewRect (); // compute viewport rectangle @@ -357,14 +410,14 @@ void V_RenderView( void ) #define NODE_INTERVAL_X(x) (x * 16.0f) #define NODE_INTERVAL_Y(x) (x * 16.0f) -void R_DrawLeafNode( float x, float y, float scale ) +static void R_DrawLeafNode( float x, float y, float scale ) { float downScale = scale * 0.25f;// * POINT_SIZE; ref.dllFuncs.R_DrawStretchPic( x - downScale * 0.5f, y - downScale * 0.5f, downScale, downScale, 0, 0, 1, 1, R_GetBuiltinTexture( REF_PARTICLE_TEXTURE ) ); } -void R_DrawNodeConnection( float x, float y, float x2, float y2 ) +static void R_DrawNodeConnection( float x, float y, float x2, float y2 ) { ref.dllFuncs.Begin( TRI_LINES ); ref.dllFuncs.Vertex3f( x, y, 0 ); @@ -372,7 +425,7 @@ void R_DrawNodeConnection( float x, float y, float x2, float y2 ) ref.dllFuncs.End( ); } -void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, mleaf_t *viewleaf ) +static void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, mleaf_t *viewleaf ) { float downScale = scale * 0.8f; @@ -413,27 +466,25 @@ void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int shownodes, R_DrawNodeConnection( x, y, x + scale, y + scale ); } - R_ShowTree_r( node->children[1], x - scale, y + scale, downScale, shownodes, viewleaf ); - R_ShowTree_r( node->children[0], x + scale, y + scale, downScale, shownodes, viewleaf ); + R_ShowTree_r( node_child( node, 1, cl.worldmodel ), x - scale, y + scale, downScale, shownodes, viewleaf ); + R_ShowTree_r( node_child( node, 0, cl.worldmodel ), x + scale, y + scale, downScale, shownodes, viewleaf ); world.recursion_level--; } -void R_ShowTree( void ) +static void R_ShowTree( void ) { float x = (float)((refState.width - (int)POINT_SIZE) >> 1); float y = NODE_INTERVAL_Y(1.0f); mleaf_t *viewleaf; - if( !cl.worldmodel || !CVAR_TO_BOOL( r_showtree )) + if( !cl.worldmodel || !r_showtree.value ) return; world.recursion_level = 0; - viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes ); + viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel ); - //pglEnable( GL_BLEND ); - //pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - //pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + ref.dllFuncs.TriRenderMode( kRenderTransTexture ); //pglLineWidth( 2.0f ); ref.dllFuncs.Color4f( 1, 0.7f, 0, 1.0f ); @@ -455,7 +506,6 @@ V_PostRender */ void V_PostRender( void ) { - static double oldtime; qboolean draw_2d = false; ref.dllFuncs.R_AllowFog( false ); @@ -482,7 +532,9 @@ void V_PostRender( void ) SCR_RSpeeds(); SCR_NetSpeeds(); SCR_DrawPos(); + SCR_DrawEnts(); SCR_DrawNetGraph(); + SCR_DrawUserCmd(); SV_DrawOrthoTriangles(); CL_DrawDemoRecording(); CL_DrawHUD( CL_CHANGELEVEL ); @@ -500,5 +552,8 @@ void V_PostRender( void ) SCR_MakeScreenShot(); ref.dllFuncs.R_AllowFog( true ); + Platform_SetTimer( 0.0f ); ref.dllFuncs.R_EndFrame(); + + V_CheckGammaEnd(); } diff --git a/engine/client/client.h b/engine/client/client.h index 684150d1..ae3d1148 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -108,7 +108,6 @@ extern int CL_UPDATE_BACKUP; #define MIN_UPDATERATE 10.0f #define MAX_UPDATERATE 102.0f -#define MIN_EX_INTERP 0.005f #define MAX_EX_INTERP 0.1f #define CL_MIN_RESEND_TIME 1.5f // mininum time gap (in seconds) before a subsequent connection request is sent. @@ -130,7 +129,6 @@ typedef struct // misc local info qboolean repredicting; // repredicting in progress - qboolean thirdperson; qboolean apply_effects; // local player will not added but we should apply their effects: flashlight etc float idealpitch; int viewmodel; @@ -139,7 +137,7 @@ typedef struct int light_level; int waterlevel; int usehull; - int moving; + qboolean moving; int pushmsec; int weapons; float maxspeed; @@ -175,6 +173,26 @@ typedef struct // at every server map change typedef struct { + // ==== shared through RefAPI's ref_client_t ==== + double time; // this is the time value that the client + // is rendering at. always <= cls.realtime + // a lerp point for other data + double oldtime; // previous cl.time, time-oldtime is used + // to decay light values and smooth step ups + int viewentity; + + // server state information + int playernum; + int maxclients; + + int nummodels; + model_t *models[MAX_MODELS+1]; // precached models (plus sentinel slot) + + qboolean paused; + + vec3_t simorg; // predicted origin + // ==== shared through RefAPI's ref_client_t === + int servercount; // server identification for prespawns int validsequence; // this is the sequence number of the last good // world snapshot/update we got. If this is 0, we can't @@ -184,7 +202,6 @@ typedef struct qboolean video_prepped; // false if on new level or new ref dll qboolean audio_prepped; // false if on new level or new snd dll - qboolean paused; int delta_sequence; // acknowledged sequence number @@ -194,7 +211,6 @@ typedef struct int last_command_ack; int last_incoming_sequence; - qboolean send_reply; qboolean background; // not real game, just a background qboolean first_frame; // first rendering frame qboolean proxy_redirect; // spectator stuff @@ -206,25 +222,19 @@ typedef struct runcmd_t commands[MULTIPLAYER_BACKUP]; // each mesage will send several old cmds local_state_t predicted_frames[MULTIPLAYER_BACKUP]; // local client state - double time; // this is the time value that the client - // is rendering at. always <= cls.realtime - // a lerp point for other data - double oldtime; // previous cl.time, time-oldtime is used - // to decay light values and smooth step ups - float timedelta; // floating delta between two updates + double timedelta; // floating delta between two updates char serverinfo[MAX_SERVERINFO_STRING]; player_info_t players[MAX_CLIENTS]; // collected info about all other players include himself double lastresourcecheck; - string downloadUrl; + qboolean http_download; event_state_t events; // predicting stuff but not only... cl_local_data_t local; // player final info - usercmd_t *cmd; // cl.commands[outgoing_sequence].cmd - int viewentity; + usercmd_t cmd; // cl.commands[outgoing_sequence].cmd vec3_t viewangles; vec3_t viewheight; vec3_t punchangle; @@ -237,14 +247,9 @@ typedef struct float addangletotal; float prevaddangletotal; - // predicted origin and velocity - vec3_t simorg; + // predicted velocity vec3_t simvel; - // server state information - int playernum; - int maxclients; - entity_state_t instanced_baseline[MAX_CUSTOM_BASELINES]; int instanced_baseline_count; @@ -252,8 +257,6 @@ typedef struct char event_precache[MAX_EVENTS][MAX_QPATH]; char files_precache[MAX_CUSTOM][MAX_QPATH]; lightstyle_t lightstyles[MAX_LIGHTSTYLES]; - model_t *models[MAX_MODELS+1]; // precached models (plus sentinel slot) - int nummodels; int numfiles; consistency_t consistency_list[MAX_MODELS]; @@ -271,6 +274,10 @@ typedef struct model_t *worldmodel; // pointer to world int lostpackets; // count lost packets and show dialog in menu + + double frametime_remainder; + + uint worldmapCRC; } client_t; /* @@ -319,32 +326,45 @@ typedef struct pfnEventHook func; // user-defined function } cl_user_event_t; -#define FONT_FIXED 0 -#define FONT_VARIABLE 1 +#define FONT_FIXED 0 +#define FONT_VARIABLE 1 + +#define FONT_DRAW_HUD BIT( 0 ) // pass to drawing function to apply hud_scale +#define FONT_DRAW_UTF8 BIT( 1 ) // call UtfProcessChar +#define FONT_DRAW_FORCECOL BIT( 2 ) // ignore colorcodes +#define FONT_DRAW_NORENDERMODE BIT( 3 ) // ignore font's default rendermode +#define FONT_DRAW_NOLF BIT( 4 ) // ignore \n +#define FONT_DRAW_RESETCOLORONLF BIT( 5 ) // yet another flag to simulate consecutive Con_DrawString calls... typedef struct { - int hFontTexture; // handle to texture - wrect_t fontRc[256]; // rectangles - byte charWidths[256]; - int charHeight; - int type; - qboolean valid; // all rectangles are valid + int hFontTexture; // handle to texture + wrect_t fontRc[256]; // tex coords + float scale; // scale factor + byte charWidths[256]; // scaled widths + int charHeight; // scaled height + int type; // fixed width font or variable + convar_t *rendermode; // user-defined default rendermode + qboolean valid; // all rectangles are valid } cl_font_t; +typedef struct scissor_state_s +{ + int x; + int y; + int width; + int height; + qboolean test; +} scissor_state_t; + typedef struct { + // scissor test + scissor_state_t scissor; + // temp handle const model_t *pSprite; // pointer to current SpriteTexture - // scissor test - int scissor_x; - int scissor_y; - int scissor_width; - int scissor_height; - qboolean scissor_test; - qboolean adjust_size; // allow to adjust scale for fonts - int renderMode; // override kRenderMode from TriAPI TRICULLSTYLE cullMode; // override CULL FACE from TriAPI @@ -371,14 +391,10 @@ typedef struct cl_predicted_player_s typedef struct { - int gl_texturenum; // this is a real texnum - // scissor test - int scissor_x; - int scissor_y; - int scissor_width; - int scissor_height; - qboolean scissor_test; + scissor_state_t scissor; + + int gl_texturenum; // this is a real texnum // holds text color rgba_t textColor; @@ -414,13 +430,6 @@ typedef struct float applied_angle; } screen_shake_t; -typedef enum -{ - NET_REQUEST_CANCEL = 0, // request was cancelled for some reasons - NET_REQUEST_GAMEUI, // called from GameUI - NET_REQUEST_CLIENT, // called from Client -} net_request_type_t; - typedef struct { net_response_t resp; @@ -482,14 +491,13 @@ typedef struct client_textmessage_t *titles; // title messages, not network messages int numTitles; - net_request_type_t request_type; // filter the requests net_request_t net_requests[MAX_REQUESTS]; // no reason to keep more - net_request_t *master_request; // queued master request - efrag_t *free_efrags; // linked efrags cl_entity_t viewent; // viewmodel +#if XASH_WIN32 qboolean client_dll_uses_sdl; +#endif } clgame_static_t; typedef struct @@ -503,8 +511,9 @@ typedef struct player_info_t playerinfo; // local playerinfo gameui_draw_t ds; // draw2d stuff (menu images) - GAMEINFO gameInfo; // current gameInfo - GAMEINFO *modsInfo[MAX_MODS]; // simplified gameInfo for MainUI + gameinfo2_t gameInfo; // current gameInfo + gameinfo2_t *modsInfo; // simplified gameInfo for MainUI, allocated by demand + GAMEINFO **oldModsInfo; // simplified gameInfo for older MainUI, allocated by demand ui_globalvars_t *globals; @@ -513,7 +522,7 @@ typedef struct int logo_yres; float logo_length; - qboolean use_text_api; + qboolean use_extended_api; } gameui_static_t; typedef struct @@ -534,12 +543,8 @@ typedef struct poolhandle_t mempool; // client premamnent pool: edicts etc - netadr_t hltv_listen_address; - int signon; // 0 to SIGNONS, for the signon sequence. - int quakePort; // a 16 bit value that allows quake servers - // to work around address translating routers - // g-cont. this port allow many copies of engine in multiplayer game + // connection information char servername[MAX_QPATH]; // name of server from original connect double connect_time; // for connection retransmits @@ -556,7 +561,6 @@ typedef struct byte datagram_buf[MAX_DATAGRAM]; netchan_t netchan; - int challenge; // from the server to use for connecting float packet_loss; double packet_loss_recalc_time; @@ -618,14 +622,23 @@ typedef struct file_t *demofile; file_t *demoheader; // contain demo startup info in case we record a demo on this level qboolean internetservers_wait; // internetservers is waiting for dns request - qboolean internetservers_pending; // internetservers is waiting for dns request + qboolean internetservers_pending; // if true, clean master server pings + uint32_t internetservers_key; // compare key to validate master server reply + char internetservers_query[512]; // cached query + uint32_t internetservers_query_len; - // legacy mode support - qboolean legacymode; // one-way 48 protocol compatibility - netadr_t legacyserver; + // multiprotocol support + connprotocol_t legacymode; int extensions; netadr_t serveradr; + + // do we accept utf8 as input + qboolean accept_utf8; + + // server's build number (might be zero) + int build_num; + uint8_t steamid[8]; } client_static_t; #ifdef __cplusplus @@ -644,56 +657,58 @@ extern gameui_static_t gameui; // // cvars // +extern convar_t showpause; extern convar_t mp_decals; -extern convar_t cl_logofile; -extern convar_t cl_logocolor; +extern convar_t cl_logomaxdim; extern convar_t cl_allow_download; -extern convar_t cl_allow_upload; extern convar_t cl_download_ingame; -extern convar_t *cl_nopred; -extern convar_t *cl_timeout; -extern convar_t *cl_nodelta; -extern convar_t *cl_interp; -extern convar_t *cl_nointerp; -extern convar_t *cl_showerror; -extern convar_t *cl_nosmooth; -extern convar_t *cl_smoothtime; -extern convar_t *cl_crosshair; -extern convar_t *cl_testlights; -extern convar_t *cl_cmdrate; -extern convar_t *cl_updaterate; -extern convar_t *cl_solid_players; -extern convar_t *cl_idealpitchscale; -extern convar_t *cl_allow_levelshots; -extern convar_t *cl_lightstyle_lerping; -extern convar_t *cl_draw_particles; -extern convar_t *cl_draw_tracers; -extern convar_t *cl_levelshot_name; -extern convar_t *cl_draw_beams; -extern convar_t *cl_clockreset; -extern convar_t *cl_fixtimerate; -extern convar_t *hud_scale; -extern convar_t *gl_showtextures; -extern convar_t *cl_bmodelinterp; -extern convar_t *cl_lw; // local weapons -extern convar_t *cl_charset; -extern convar_t *cl_trace_messages; -extern convar_t *hud_utf8; -extern convar_t *cl_showevents; -extern convar_t *scr_centertime; -extern convar_t *scr_viewsize; -extern convar_t *scr_loading; -extern convar_t *v_dark; // start from dark -extern convar_t *net_graph; -extern convar_t *rate; -extern convar_t *m_ignore; -extern convar_t *r_showtree; -extern convar_t *ui_renderworld; +extern convar_t cl_nopred; +extern convar_t cl_timeout; +extern convar_t cl_interp; +extern convar_t cl_nointerp; +extern convar_t cl_showerror; +extern convar_t cl_nosmooth; +extern convar_t cl_smoothtime; +extern convar_t cl_crosshair; +extern convar_t cl_testlights; +extern convar_t cl_cmdrate; +extern convar_t cl_updaterate; +extern convar_t cl_solid_players; +extern convar_t cl_idealpitchscale; +extern convar_t cl_allow_levelshots; +extern convar_t cl_draw_particles; +extern convar_t cl_draw_tracers; +extern convar_t cl_levelshot_name; +extern convar_t cl_draw_beams; +extern convar_t cl_clockreset; +extern convar_t hud_fontscale; +extern convar_t hud_fontrender; +extern convar_t hud_scale; +extern convar_t hud_scale_minimal_width; +extern convar_t r_showtextures; +extern convar_t cl_bmodelinterp; +extern convar_t cl_lw; // local weapons +extern convar_t cl_charset; +extern convar_t cl_trace_consistency; +extern convar_t cl_trace_stufftext; +extern convar_t cl_trace_messages; +extern convar_t cl_trace_events; +extern convar_t hud_utf8; +extern convar_t cl_showevents; +extern convar_t scr_centertime; +extern convar_t scr_viewsize; +extern convar_t scr_loading; +extern convar_t v_dark; // start from dark +extern convar_t net_graph; +extern convar_t rate; +extern convar_t m_ignore; +extern convar_t r_showtree; +extern convar_t ui_renderworld; +extern convar_t cl_fixmodelinterpolationartifacts; //============================================================================= void CL_SetLightstyle( int style, const char* s, float f ); -void CL_RunLightStyles( void ); void CL_DecayLights( void ); dlight_t *CL_GetDynamicLight( int number ); dlight_t *CL_GetEntityLight( int number ); @@ -704,12 +719,8 @@ dlight_t *CL_GetEntityLight( int number ); // cl_cmds.c // void CL_Quit_f( void ); -void CL_ScreenShot_f( void ); -void CL_SnapShot_f( void ); +void CL_GenericShot_f( void ); void CL_PlayCDTrack_f( void ); -void CL_EnvShot_f( void ); -void CL_SkyShot_f( void ); -void CL_SaveShot_f( void ); void CL_LevelShot_f( void ); void CL_SetSky_f( void ); void SCR_Viewpos_f( void ); @@ -727,11 +738,10 @@ void CL_ClearResourceLists( void ); // // cl_debug.c // +void CL_ReplayBufferDat_f( void ); void CL_Parse_Debug( qboolean enable ); void CL_Parse_RecordCommand( int cmd, int startoffset ); void CL_ResetFrame( frame_t *frame ); -void CL_WriteMessageHistory( void ); -const char *CL_MsgInfo( int cmd ); // // cl_efx.c @@ -742,17 +752,15 @@ void CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel ); // cl_main.c // void CL_Init( void ); -void CL_SendCommand( void ); void CL_Disconnect_f( void ); void CL_ProcessFile( qboolean successfully_received, const char *filename ); -void CL_WriteUsercmd( sizebuf_t *msg, int from, int to ); -int CL_GetFragmentSize( void *unused , fragsize_t mode ); +void CL_WriteUsercmd( connprotocol_t proto, sizebuf_t *msg, int from, int to ); +void CL_SetupNetchanForProtocol( connprotocol_t proto ); qboolean CL_PrecacheResources( void ); void CL_SetupOverviewParams( void ); void CL_UpdateFrameLerp( void ); int CL_IsDevOverviewMode( void ); -void CL_PingServers_f( void ); -void CL_SignonReply( void ); +void CL_SignonReply( connprotocol_t proto ); void CL_ClearState( void ); // @@ -762,15 +770,13 @@ void CL_StartupDemoHeader( void ); void CL_DrawDemoRecording( void ); void CL_WriteDemoUserCmd( int cmdnumber ); void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ); -void CL_WriteDemoUserMessage( const byte *buffer, size_t size ); +void CL_WriteDemoUserMessage( int size, byte *buffer ); qboolean CL_DemoReadMessage( byte *buffer, size_t *length ); void CL_DemoInterpolateAngles( void ); void CL_CheckStartupDemos( void ); void CL_WriteDemoJumpTime( void ); void CL_CloseDemoHeader( void ); void CL_DemoCompleted( void ); -void CL_StopPlayback( void ); -void CL_StopRecord( void ); void CL_PlayDemo_f( void ); void CL_TimeDemo_f( void ); void CL_StartDemos_f( void ); @@ -778,94 +784,173 @@ void CL_Demos_f( void ); void CL_DeleteDemo_f( void ); void CL_Record_f( void ); void CL_Stop_f( void ); +void CL_ListDemo_f( void ); +int CL_GetDemoComment( const char *demoname, char *comment ); // // cl_events.c // -void CL_ParseEvent( sizebuf_t *msg ); -void CL_ParseReliableEvent( sizebuf_t *msg ); +void CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseReliableEvent( sizebuf_t *msg, connprotocol_t proto ); void CL_SetEventIndex( const char *szEvName, int ev_index ); -void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ); void CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func ); -void CL_BatchResourceRequest( qboolean initialize ); -int CL_EstimateNeededResources( void ); void CL_ResetEvent( event_info_t *ei ); word CL_EventIndex( const char *name ); void CL_FireEvents( void ); +// +// cl_font.c +// +qboolean CL_FixedFont( cl_font_t *font ); +qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ); +qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ); +void CL_FreeFont( cl_font_t *font ); +void CL_SetFontRendermode( cl_font_t *font ); +int CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_t *font, int flags ); +int CL_DrawString( float x, float y, const char *s, const rgba_t color, cl_font_t *font, int flags ); +void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ); +void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags ); +int CL_DrawStringf( cl_font_t *font, float x, float y, const rgba_t color, int flags, const char *fmt, ... ) FORMAT_CHECK( 6 ); + + // // cl_game.c // void CL_UnloadProgs( void ); qboolean CL_LoadProgs( const char *name ); -void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ); void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ); -void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ); -void CL_ParseTextMessage( sizebuf_t *msg ); void CL_DrawHUD( int state ); -void CL_InitEdicts( void ); +void CL_InitEdicts( int maxclients ); void CL_FreeEdicts( void ); void CL_ClearWorld( void ); void CL_DrawCenterPrint( void ); void CL_ClearSpriteTextures( void ); -void CL_FreeEntity( cl_entity_t *pEdict ); void CL_CenterPrint( const char *text, float y ); void CL_TextMessageParse( byte *pMemFile, int fileSize ); client_textmessage_t *CL_TextMessageGet( const char *pName ); -int pfnDecalIndexFromName( const char *szDecalName ); -int pfnIndexFromTrace( struct pmtrace_s *pTrace ); -model_t *CL_ModelHandle( int modelindex ); void NetAPI_CancelAllRequests( void ); -int CL_FindModelIndex( const char *m ); -cl_entity_t *CL_GetLocalPlayer( void ); model_t *CL_LoadClientSprite( const char *filename ); model_t *CL_LoadModel( const char *modelname, int *index ); -HSPRITE EXPORT pfnSPR_Load( const char *szPicName ); HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ); -void PicAdjustSize( float *x, float *y, float *w, float *h ); +void SPR_AdjustSize( float *x, float *y, float *w, float *h ); int CL_GetScreenInfo( SCREENINFO *pscrinfo ); -void CL_FillRGBA( int x, int y, int width, int height, int r, int g, int b, int a ); -void CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ); -void CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ); -void CL_SetTraceHull( int hull ); -void CL_GetMousePosition( int *mx, int *my ); // TODO: move to input -cl_entity_t* CL_GetViewModel( void ); -void pfnGetScreenFade( struct screenfade_s *fade ); +pmtrace_t *PM_CL_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ); +const char *PM_CL_TraceTexture( int ground, float *vstart, float *vend ); +int PM_CL_PointContents( const float *p, int *truecontents ); physent_t *pfnGetPhysent( int idx ); struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ); -movevars_t *pfnGetMoveVars( void ); +void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height ); +void CL_DisableScissor( scissor_state_t *scissor ); +qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ); -_inline cl_entity_t *CL_EDICT_NUM( int n ) +static inline cl_entity_t *CL_EDICT_NUM( int index ) { - if( !clgame.entities ) + if( !clgame.entities ) // not in game yet { - Host_Error( "CL_EDICT_NUM: clgame.entities is NULL\n"); + Host_Error( "%s: clgame.entities is NULL\n", __func__ ); return NULL; } - if(( n >= 0 ) && ( n < clgame.maxEntities )) - return clgame.entities + n; + if( index < 0 || index >= clgame.maxEntities ) + { + Host_Error( "%s: bad number %i\n", __func__, index ); + return NULL; + } - Host_Error( "CL_EDICT_NUM: bad number %i\n", n ); - return NULL; + return clgame.entities + index; +} + +static inline cl_entity_t *CL_GetEntityByIndex( int index ) +{ + if( !clgame.entities ) // not in game yet + return NULL; + + if( index < 0 || index >= clgame.maxEntities ) + return NULL; + + return clgame.entities + index; +} + +static inline model_t *CL_ModelHandle( int modelindex ) +{ + return modelindex >= 0 && modelindex < MAX_MODELS ? cl.models[modelindex] : NULL; +} + +static inline qboolean CL_IsThirdPerson( void ) +{ + return clgame.dllFuncs.CL_IsThirdPerson(); +} + +static inline cl_entity_t *CL_GetLocalPlayer( void ) +{ + cl_entity_t *player = CL_GetEntityByIndex( cl.playernum + 1 ); + + // HACKHACK: GoldSrc doesn't do this, but some mods actually check it for null pointer + // this is a lesser evil than changing semantics of HUD_VidInit and call it after entities are allocated + if( !player ) + Con_Printf( S_WARN "%s: client entities are not initialized yet! Returning NULL...\n", __func__ ); + + return player; } // // cl_parse.c // -void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ); -void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ); -void CL_LegacyPrecache_f( void ); - -void CL_ParseTempEntity( sizebuf_t *msg ); -void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ); +void CL_ParseSetAngle( sizebuf_t *msg ); +void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto ); +void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseResource( sizebuf_t *msg ); +void CL_ParseClientData( sizebuf_t *msg, connprotocol_t proto ); +void CL_UpdateUserPings( sizebuf_t *msg ); +void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseRestoreSoundPacket( sizebuf_t *msg ); +void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseSignon( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseRestore( sizebuf_t *msg ); +void CL_ParseStaticDecal( sizebuf_t *msg ); +void CL_ParseAddAngle( sizebuf_t *msg ); +void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseMovevars( sizebuf_t *msg ); +void CL_ParseResourceRequest( sizebuf_t *msg ); +void CL_ParseCustomization( sizebuf_t *msg ); +void CL_ParseCrosshairAngle( sizebuf_t *msg ); +void CL_ParseSoundFade( sizebuf_t *msg ); +void CL_ParseFileTransferFailed( sizebuf_t *msg ); +void CL_ParseHLTV( sizebuf_t *msg ); +void CL_ParseDirector( sizebuf_t *msg ); +void CL_ParseVoiceInit( sizebuf_t *msg ); +void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseResLocation( sizebuf_t *msg ); +void CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext, const connprotocol_t proto ); +void CL_ParseServerMessage( sizebuf_t *msg ); +qboolean CL_ParseCommonDLLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset ); +void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto ); qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf ); qboolean CL_RequestMissingResources( void ); -void CL_RegisterResources ( sizebuf_t *msg ); +void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto ); void CL_ParseViewEntity( sizebuf_t *msg ); -void CL_ParseServerTime( sizebuf_t *msg ); +void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto ); +void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto ); +void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ); +void CL_ParseTextMessage( sizebuf_t *msg ); +void CL_ParseExec( sizebuf_t *msg ); +void CL_BatchResourceRequest( qboolean initialize ); +int CL_EstimateNeededResources( void ); + +// +// cl_parse_48.c +// +void CL_ParseLegacyServerMessage( sizebuf_t *msg ); +void CL_LegacyPrecache_f( void ); + +// +// cl_parse_gs.c +// +void CL_ParseGoldSrcServerMessage( sizebuf_t *msg ); // // cl_scrn.c @@ -873,9 +958,8 @@ void CL_ParseServerTime( sizebuf_t *msg ); void SCR_VidInit( void ); void SCR_TileClear( void ); void SCR_DirtyScreen( void ); -void SCR_AddDirtyPoint( int x, int y ); -void SCR_InstallParticlePalette( void ); void SCR_EndLoadingPlaque( void ); +int SCR_LoadPauseIcon( void ); void SCR_RegisterTextures( void ); void SCR_LoadCreditsFont( void ); void SCR_MakeScreenShot( void ); @@ -884,6 +968,8 @@ void SCR_NetSpeeds( void ); void SCR_RSpeeds( void ); void SCR_DrawFPS( int height ); void SCR_DrawPos( void ); +void SCR_DrawEnts( void ); +void SCR_DrawUserCmd( void ); // // cl_netgraph.c @@ -909,55 +995,50 @@ void CL_SetSolidPlayers( int playernum ); void CL_InitClientMove( void ); void CL_PredictMovement( qboolean repredicting ); void CL_CheckPredictionError( void ); -qboolean CL_IsPredicted( void ); -int CL_TruePointContents( const vec3_t p ); -int CL_PointContents( const vec3_t p ); int CL_WaterEntity( const float *rgflPos ); cl_entity_t *CL_GetWaterEntity( const float *rgflPos ); -void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time ); -int CL_TestLine( const vec3_t start, const vec3_t end, int flags ); pmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags ); pmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags ); -void CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs ); -void CL_PopTraceBounds( void ); void CL_MoveSpectatorCamera( void ); void CL_SetLastUpdate( void ); void CL_RedoPrediction( void ); -void CL_ClearPhysEnts( void ); void CL_PushPMStates( void ); void CL_PopPMStates( void ); void CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient ); +void CL_SetIdealPitch( void ); // // cl_qparse.c // -void CL_ParseQuakeMessage( sizebuf_t *msg, qboolean normal_message ); +void CL_ParseQuakeMessage( sizebuf_t *msg ); // // cl_frame.c // struct channel_s; struct rawchan_s; -int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ); +qboolean CL_ValidateDeltaPacket( uint oldpacket, frame_t *oldframe ); +int CL_UpdateOldEntNum( int oldindex, frame_t *oldframe, entity_state_t **oldent ); +int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto ); qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ); void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset ); qboolean CL_GetEntitySpatialization( struct channel_s *ch ); qboolean CL_GetMovieSpatialization( struct rawchan_s *ch ); -void CL_ProcessPlayerState( int playerindex, entity_state_t *state ); void CL_ComputePlayerOrigin( cl_entity_t *clent ); void CL_ProcessPacket( frame_t *frame ); void CL_MoveThirdpersonCamera( void ); -qboolean CL_IsPlayerIndex( int idx ); -void CL_SetIdealPitch( void ); void CL_EmitEntities( void ); +static inline qboolean CL_IsPlayerIndex( int idx ) +{ + return idx >= 1 && idx <= cl.maxclients ? true : false; +} + // // cl_remap.c // remap_info_t *CL_GetRemapInfoForEntity( cl_entity_t *e ); -void CL_AllocRemapInfo( cl_entity_t *entity, model_t *model, int topcolor, int bottomcolor ); -void CL_FreeRemapInfo( remap_info_t *info ); -void CL_UpdateRemapInfo( cl_entity_t *ent, int topcolor, int bottomcolor ); +qboolean CL_EntitySetRemapColors( cl_entity_t *e, model_t *mod, int top, int bottom ); void CL_ClearAllRemaps( void ); // @@ -969,24 +1050,27 @@ lightstyle_t *CL_GetLightStyle( int number ); int R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); const ref_overview_t *GL_GetOverviewParms( void ); +// +// cl_spray.c +// +qboolean CL_ConvertImageToWAD3( const char *filename ); + // // cl_efrag.c // void R_StoreEfrags( efrag_t **ppefrag, int framecount ); void R_AddEfrags( cl_entity_t *ent ); -void R_RemoveEfrags( cl_entity_t *ent ); + // // cl_tent.c // struct particle_s; -int CL_AddEntity( int entityType, cl_entity_t *pEnt ); void CL_WeaponAnim( int iAnim, int body ); void CL_ClearEffects( void ); void CL_ClearEfrags( void ); void CL_TestLights( void ); void CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale ); void CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags ); -void CL_PlayerDecal( int playerIndex, int textureIndex, int entityIndex, float *pos ); void R_FreeDeadParticles( struct particle_s **ppparticles ); void CL_AddClientResource( const char *filename, int type ); void CL_AddClientResources( void ); @@ -994,7 +1078,6 @@ void CL_InitParticles( void ); void CL_ClearParticles( void ); void CL_FreeParticles( void ); void CL_InitTempEnts( void ); -void CL_ClearTempEnts( void ); void CL_FreeTempEnts( void ); void CL_TempEntUpdate( void ); void CL_InitViewBeams( void ); @@ -1008,12 +1091,11 @@ void CL_ReadPointFile_f( void ); void CL_DrawEFX( float time, qboolean fTrans ); void CL_ThinkParticle( double frametime, particle_t *p ); void CL_ReadLineFile_f( void ); -void CL_RunLightStyles( void ); // // console.c // -extern convar_t *con_fontsize; +extern convar_t con_fontsize; int Con_Visible( void ); qboolean Con_FixedFont( void ); void Con_VidInit( void ); @@ -1028,28 +1110,23 @@ int Con_UtfProcessChar( int in ); int Con_UtfProcessCharForce( int in ); int Con_UtfMoveLeft( char *str, int pos ); int Con_UtfMoveRight( char *str, int pos, int length ); -void Con_DrawStringLen( const char *pText, int *length, int *height ); -int Con_DrawString( int x, int y, const char *string, rgba_t setColor ); -int Con_DrawCharacter( int x, int y, int number, rgba_t color ); -void Con_DrawCharacterLen( int number, int *width, int *height ); -void Con_DefaultColor( int r, int g, int b ); -void Con_InvalidateFonts( void ); -void Con_SetFont( int fontNum ); +void Con_DefaultColor( int r, int g, int b, qboolean gameui ); +cl_font_t *Con_GetCurFont( void ); +cl_font_t *Con_GetFont( int num ); +int Con_DrawString( int x, int y, const char *string, const rgba_t setColor ); // legacy, use cl_font.c +void GAME_EXPORT Con_DrawStringLen( const char *pText, int *length, int *height ); // legacy, use cl_font.c void Con_CharEvent( int key ); -void Con_RestoreFont( void ); void Key_Console( int key ); void Key_Message( int key ); void Con_FastClose( void ); void Con_Bottom( void ); -void Con_Top( void ); void Con_PageDown( int lines ); void Con_PageUp( int lines ); // // s_main.c // -void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data ); -void S_StreamAviSamples( void *Avi, int entnum, float fvol, float attn, float synctime ); +typedef int sound_t; void S_StartBackgroundTrack( const char *intro, const char *loop, int position, qboolean fullpath ); void S_StopBackgroundTrack( void ); void S_StreamSetPause( int pause ); @@ -1086,7 +1163,7 @@ qboolean UI_MouseInRect( void ); qboolean UI_IsVisible( void ); void UI_ResetPing( void ); void UI_ShowUpdateDialog( qboolean preferStore ); -void UI_ShowMessageBox( const char *text ); +qboolean UI_ShowMessageBox( const char *text ); void UI_AddTouchButtonToList( const char *name, const char *texture, const char *command, unsigned char *color, int flags ); void UI_ConnectionProgress_Disconnect( void ); void UI_ConnectionProgress_Download( const char *pszFileName, const char *pszServerName, const char *pszServerPath, int iCurrent, int iTotal, const char *comment ); @@ -1095,11 +1172,6 @@ void UI_ConnectionProgress_Precache( void ); void UI_ConnectionProgress_Connect( const char *server ); void UI_ConnectionProgress_ChangeLevel( void ); void UI_ConnectionProgress_ParseServerInfo( const char *server ); -void pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ); -void pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ); -void pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ); -void pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ); -void pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ); // // cl_mobile.c @@ -1129,22 +1201,28 @@ void CL_PlayVideo_f( void ); // keys.c // int Key_IsDown( int keynum ); -const char *Key_IsBind( int keynum ); void Key_Event( int key, int down ); void Key_Init( void ); void Key_WriteBindings( file_t *f ); const char *Key_GetBinding( int keynum ); void Key_SetBinding( int keynum, const char *binding ); +const char *Key_LookupBinding( const char *pBinding ); void Key_ClearStates( void ); const char *Key_KeynumToString( int keynum ); -int Key_StringToKeynum( const char *str ); -int Key_GetKey( const char *binding ); void Key_EnumCmds_f( void ); void Key_SetKeyDest( int key_dest ); void Key_EnableTextInput( qboolean enable, qboolean force ); int Key_ToUpper( int key ); void OSK_Draw( void ); +// +// identification.c +// +void ID_Init( void ); +const char *ID_GetMD5( void ); + extern rgba_t g_color_table[8]; +extern triangleapi_t gTriApi; +extern net_api_t gNetApi; #endif//CLIENT_H diff --git a/engine/client/console.c b/engine/client/console.c index 21f76b67..c4d6a594 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -21,17 +21,20 @@ GNU General Public License for more details. #include "qfont.h" #include "wadfile.h" #include "input.h" +#include "utflib.h" -convar_t *con_notifytime; -convar_t *scr_conspeed; -convar_t *con_fontsize; -convar_t *con_charset; -convar_t *con_fontscale; -convar_t *con_fontnum; -convar_t *con_color; +static CVAR_DEFINE_AUTO( scr_conspeed, "600", FCVAR_ARCHIVE, "console moving speed" ); +static CVAR_DEFINE_AUTO( con_notifytime, "3", FCVAR_ARCHIVE, "notify time to live" ); +CVAR_DEFINE_AUTO( con_fontsize, "1", FCVAR_ARCHIVE, "console font number (0, 1 or 2)" ); +static CVAR_DEFINE_AUTO( con_fontrender, "2", FCVAR_ARCHIVE, "console font render mode (0: additive, 1: holes, 2: trans)" ); +static CVAR_DEFINE_AUTO( con_charset, "cp1251", FCVAR_ARCHIVE, "console font charset (only cp1251 supported now)" ); +static CVAR_DEFINE_AUTO( con_fontscale, "1.0", FCVAR_ARCHIVE, "scale font texture" ); +static CVAR_DEFINE_AUTO( con_fontnum, "-1", FCVAR_ARCHIVE, "console font number (0, 1 or 2), -1 for autoselect" ); +static CVAR_DEFINE_AUTO( con_color, "240 180 24", FCVAR_ARCHIVE, "set a custom console color" ); +static CVAR_DEFINE_AUTO( scr_drawversion, "1", FCVAR_ARCHIVE, "draw version in menu or screenshots, doesn't affect console" ); +static CVAR_DEFINE_AUTO( con_oldfont, "0", 0, "use legacy font from gfx.wad, might be missing or broken" ); static int g_codepage = 0; -static qboolean g_utf8 = false; static qboolean g_messagemode_privileged = true; @@ -81,10 +84,17 @@ typedef struct con_lineinfo_s double addtime; // notify stuff } con_lineinfo_t; +typedef struct history_line_s +{ + string buffer; + int cursor; + int scroll; +} history_line_t; + typedef struct con_history_s { - field_t lines[CON_HISTORY]; - field_t backup; + history_line_t lines[CON_HISTORY]; + history_line_t backup; int line; // the line being displayed from history buffer will be <= nextHistoryLine int next; // the last line in the history buffer, not masked } con_history_t; @@ -116,7 +126,7 @@ typedef struct // console fonts cl_font_t chars[CON_NUMFONTS];// fonts.wad/font1.fnt - cl_font_t *curFont, *lastUsedFont; + cl_font_t *curFont; // console input field_t input; @@ -138,8 +148,9 @@ typedef struct static console_t con; -void Con_ClearField( field_t *edit ); -void Field_CharEvent( field_t *edit, int ch ); +static void Con_ClearField( field_t *edit ); +static void Field_CharEvent( field_t *edit, int ch ); +static void Con_InvalidateFonts( void ); static void Con_LoadHistory( con_history_t *self ); static void Con_SaveHistory( con_history_t *self ); @@ -149,7 +160,7 @@ static void Con_SaveHistory( con_history_t *self ); Con_Clear_f ================ */ -void Con_Clear_f( void ) +static void Con_Clear_f( void ) { con.lines_count = 0; con.backscroll = 0; // go to end @@ -162,29 +173,28 @@ Con_SetColor */ static void Con_SetColor( void ) { - vec3_t color; int r, g, b; int num; - if( !FBitSet( con_color->flags, FCVAR_CHANGED )) + if( !FBitSet( con_color.flags, FCVAR_CHANGED )) return; - num = sscanf( con_color->string, "%i %i %i", &r, &g, &b ); + num = sscanf( con_color.string, "%i %i %i", &r, &g, &b ); switch( num ) { case 1: - Con_DefaultColor( r, r, r ); + Con_DefaultColor( r, r, r, false ); break; case 3: - Con_DefaultColor( r, g, b ); + Con_DefaultColor( r, g, b, false ); break; default: - Cvar_DirectSet( con_color, con_color->def_string ); + Cvar_DirectSet( &con_color, con_color.def_string ); break; } - ClearBits( con_color->flags, FCVAR_CHANGED ); + ClearBits( con_color.flags, FCVAR_CHANGED ); } /* @@ -205,7 +215,7 @@ void Con_ClearNotify( void ) Con_ClearTyping ================ */ -void Con_ClearTyping( void ) +static void Con_ClearTyping( void ) { Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; @@ -213,43 +223,12 @@ void Con_ClearTyping( void ) Cmd_AutoCompleteClear(); } -/* -============ -Con_StringLength - -skipped color prefixes -============ -*/ -int Con_StringLength( const char *string ) -{ - int len; - const char *p; - - if( !string ) return 0; - - len = 0; - p = string; - - while( *p ) - { - if( IsColorString( p )) - { - p += 2; - continue; - } - len++; - p++; - } - - return len; -} - /* ================ Con_MessageMode_f ================ */ -void Con_MessageMode_f( void ) +static void Con_MessageMode_f( void ) { g_messagemode_privileged = Cmd_CurrentCommandIsPrivileged(); @@ -265,7 +244,7 @@ void Con_MessageMode_f( void ) Con_MessageMode2_f ================ */ -void Con_MessageMode2_f( void ) +static void Con_MessageMode2_f( void ) { g_messagemode_privileged = Cmd_CurrentCommandIsPrivileged(); @@ -310,7 +289,7 @@ void Con_ToggleConsole_f( void ) Con_SetTimes_f ================ */ -void Con_SetTimes_f( void ) +static void Con_SetTimes_f( void ) { int newtimes; @@ -333,7 +312,7 @@ Notifies the console code about the current time went backwards) ================ */ -void Con_FixTimes( void ) +static void Con_FixTimes( void ) { double diff; int i; @@ -354,7 +333,7 @@ Con_DeleteLine Deletes the first line from the console history. ================ */ -void Con_DeleteLine( void ) +static void Con_DeleteLine( void ) { if( con.lines_count == 0 ) return; @@ -369,7 +348,7 @@ Con_DeleteLastLine Deletes the last line from the console history. ================ */ -void Con_DeleteLastLine( void ) +static void Con_DeleteLastLine( void ) { if( con.lines_count == 0 ) return; @@ -428,7 +407,7 @@ Con_AddLine Appends a given string as a new line to the console. ================ */ -void Con_AddLine( const char *line, int length, qboolean newline ) +static void Con_AddLine( const char *line, int length, qboolean newline ) { char *putpos; con_lineinfo_t *p; @@ -474,7 +453,7 @@ Con_CheckResize If the line width has changed, reformat the buffer. ================ */ -void Con_CheckResize( void ) +static void Con_CheckResize( void ) { int charWidth = 8; int i, width; @@ -493,9 +472,6 @@ void Con_CheckResize( void ) con.backscroll = 0; con.input.widthInChars = con.linewidth; - - for( i = 0; i < CON_HISTORY; i++ ) - con.history.lines[i].widthInChars = con.linewidth; } /* @@ -523,7 +499,7 @@ void Con_PageDown( int lines ) Con_Top ================ */ -void Con_Top( void ) +static void Con_Top( void ) { con.backscroll = CON_MAXLINES; } @@ -555,89 +531,9 @@ Con_FixedFont */ qboolean Con_FixedFont( void ) { - if( con.curFont && con.curFont->valid && con.curFont->type == FONT_FIXED ) - return true; - return false; + return CL_FixedFont( con.curFont ); } -static qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font ) -{ - int i, fontWidth; - - if( font->valid ) - return true; // already loaded - - if( !FS_FileExists( fontname, false )) - return false; - - // keep source to print directly into conback image - font->hFontTexture = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, TF_FONT|TF_KEEP_SOURCE ); - R_GetTextureParms( &fontWidth, NULL, font->hFontTexture ); - - if( font->hFontTexture && fontWidth != 0 ) - { - font->charHeight = fontWidth / 16 * con_fontscale->value; - font->type = FONT_FIXED; - - // build fixed rectangles - for( i = 0; i < 256; i++ ) - { - font->fontRc[i].left = (i * (fontWidth / 16)) % fontWidth; - font->fontRc[i].right = font->fontRc[i].left + fontWidth / 16; - font->fontRc[i].top = (i / 16) * (fontWidth / 16); - font->fontRc[i].bottom = font->fontRc[i].top + fontWidth / 16; - font->charWidths[i] = fontWidth / 16 * con_fontscale->value; - } - font->valid = true; - } - - return true; -} - -static qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font ) -{ - int i, fontWidth; - byte *buffer; - fs_offset_t length; - qfont_t *src; - - if( font->valid ) - return true; // already loaded - - if( !FS_FileExists( fontname, false )) - return false; - - font->hFontTexture = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, TF_FONT|TF_NEAREST ); - R_GetTextureParms( &fontWidth, NULL, font->hFontTexture ); - - // setup consolefont - if( font->hFontTexture && fontWidth != 0 ) - { - // half-life font with variable chars witdh - buffer = FS_LoadFile( fontname, &length, false ); - - if( buffer && length >= sizeof( qfont_t )) - { - src = (qfont_t *)buffer; - font->charHeight = src->rowheight * con_fontscale->value; - font->type = FONT_VARIABLE; - - // build rectangles - for( i = 0; i < 256; i++ ) - { - font->fontRc[i].left = (word)src->fontinfo[i].startoffset % fontWidth; - font->fontRc[i].right = font->fontRc[i].left + src->fontinfo[i].charwidth; - font->fontRc[i].top = (word)src->fontinfo[i].startoffset / fontWidth; - font->fontRc[i].bottom = font->fontRc[i].top + src->rowheight; - font->charWidths[i] = src->fontinfo[i].charwidth * con_fontscale->value; - } - font->valid = true; - } - if( buffer ) Mem_Free( buffer ); - } - - return true; -} /* ================ @@ -648,32 +544,45 @@ INTERNAL RESOURCE */ static void Con_LoadConsoleFont( int fontNumber, cl_font_t *font ) { - const char *path = NULL; - dword crc = 0; + qboolean success = false; + float scale = con_fontscale.value; - if( font->valid ) return; // already loaded + if( font->valid ) + return; // already loaded - // replace default fonts.wad textures by current charset's font - if( !CRC32_File( &crc, "fonts.wad" ) || crc == 0x3c0a0029 ) + if( con_oldfont.value ) { - const char *path2 = va("font%i_%s.fnt", fontNumber, Cvar_VariableString( "con_charset" ) ); - if( FS_FileExists( path2, false ) ) - path = path2; + success = Con_LoadVariableWidthFont( "gfx/conchars.fnt", font, scale, &con_fontrender, TF_FONT|TF_NEAREST ); } - - // loading conchars - if( Sys_CheckParm( "-oldfont" )) - Con_LoadVariableWidthFont( "gfx/conchars.fnt", font ); else { - if( !path ) - path = va( "fonts/font%i", fontNumber ); + string path; + dword crc = 0; - Con_LoadVariableWidthFont( path, font ); + // replace default fonts.wad textures by current charset's font + if( !CRC32_File( &crc, "fonts.wad" ) || crc == 0x3c0a0029 ) + { + if( Q_snprintf( path, sizeof( path ), + "font%i_%s.fnt", fontNumber, Cvar_VariableString( "con_charset" )) > 0 ) + { + success = Con_LoadVariableWidthFont( path, font, scale, &con_fontrender, TF_FONT|TF_NEAREST ); + } + } + + if( !success ) + { + Q_snprintf( path, sizeof( path ), "fonts/font%i", fontNumber ); + success = Con_LoadVariableWidthFont( path, font, scale, &con_fontrender, TF_FONT|TF_NEAREST ); + } } - // quake fixed font as fallback - if( !font->valid ) Con_LoadFixedWidthFont( "gfx/conchars", font ); + if( !success ) + { + // quake fixed font as fallback + // keep source to print directly into conback image + if( !Con_LoadFixedWidthFont( "gfx/conchars", font, scale, &con_fontrender, TF_FONT|TF_NEAREST|TF_KEEP_SOURCE )) + Con_DPrintf( S_ERROR "failed to load console font\n" ); + } } /* @@ -690,8 +599,8 @@ static void Con_LoadConchars( void ) Con_LoadConsoleFont( i, con.chars + i ); // select properly fontsize - if( con_fontnum->value >= 0 && con_fontnum->value <= CON_NUMFONTS - 1 ) - fontSize = con_fontnum->value; + if( con_fontnum.value >= 0 && con_fontnum.value <= CON_NUMFONTS - 1 ) + fontSize = con_fontnum.value; else if( refState.width <= 640 ) fontSize = 0; else if( refState.width >= 1280 ) @@ -702,22 +611,9 @@ static void Con_LoadConchars( void ) fontSize = CON_NUMFONTS - 1; // sets the current font - con.lastUsedFont = con.curFont = &con.chars[fontSize]; + con.curFont = &con.chars[fontSize]; } -// CP1251 table - -int table_cp1251[64] = { - 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, - 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, - 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, - 0x007F, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, - 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, - 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, - 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, - 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457 -}; - /* ============================ Con_UtfProcessChar @@ -727,84 +623,28 @@ Convert utf char to current font's single-byte encoding */ int Con_UtfProcessCharForce( int in ) { - static int m = -1, k = 0; //multibyte state - static int uc = 0; //unicode char + // TODO: get rid of global state where possible + static utfstate_t state = { 0 }; - if( !in ) - { - m = -1; - k = 0; - uc = 0; - return 0; - } + uint32_t ch = Q_DecodeUTF8( &state, in ); - // Get character length - if(m == -1) - { - uc = 0; - if( in >= 0xF8 ) - return 0; - else if( in >= 0xF0 ) - uc = in & 0x07, m = 3; - else if( in >= 0xE0 ) - uc = in & 0x0F, m = 2; - else if( in >= 0xC0 ) - uc = in & 0x1F, m = 1; - else if( in <= 0x7F) - return in; //ascii - // return 0 if we need more chars to decode one - k=0; - return 0; - } - // get more chars - else if( k <= m ) - { - uc <<= 6; - uc += in & 0x3F; - k++; - } - if( in > 0xBF || m < 0 ) - { - m = -1; - return 0; - } - if( k == m ) - { - k = m = -1; - if( g_codepage == 1251 ) - { - // cp1251 now - if( uc >= 0x0410 && uc <= 0x042F ) - return uc - 0x410 + 0xC0; - if( uc >= 0x0430 && uc <= 0x044F ) - return uc - 0x430 + 0xE0; - else - { - int i; - for( i = 0; i < 64; i++ ) - if( table_cp1251[i] == uc ) - return i + 0x80; - } - } - else if( g_codepage == 1252 ) - { - if( uc < 255 ) - return uc; - } + if( g_codepage == 1251 ) + return Q_UnicodeToCP1251( ch ); + if( g_codepage == 1252 ) + return Q_UnicodeToCP1252( ch ); - // not implemented yet - return '?'; - } - return 0; + return '?'; // not implemented yet } int GAME_EXPORT Con_UtfProcessChar( int in ) { - if( !g_utf8 ) + if( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence return in; - else - return Con_UtfProcessCharForce( in ); + + // otherwise, decode it and convert to selected codepage + return Con_UtfProcessCharForce( in ); } + /* ================= Con_UtfMoveLeft @@ -814,16 +654,22 @@ get position of previous printful char */ int Con_UtfMoveLeft( char *str, int pos ) { - int i, k = 0; - // int j; - if( !g_utf8 ) + utfstate_t state = { 0 }; + int k = 0; + int i; + + if( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence return pos - 1; - Con_UtfProcessChar( 0 ); - if(pos == 1) return 0; - for( i = 0; i < pos-1; i++ ) - if( Con_UtfProcessChar( (unsigned char)str[i] ) ) - k = i+1; - Con_UtfProcessChar( 0 ); + + if( pos == 1 ) + return 0; + + for( i = 0; i < pos - 1; i++ ) + { + if( Q_DecodeUTF8( &state, (byte)str[i] )) + k = i + 1; + } + return k; } @@ -836,17 +682,19 @@ get next of previous printful char */ int Con_UtfMoveRight( char *str, int pos, int length ) { + utfstate_t state = { 0 }; int i; - if( !g_utf8 ) + + if( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence return pos + 1; - Con_UtfProcessChar( 0 ); + for( i = pos; i <= length; i++ ) { - if( Con_UtfProcessChar( (unsigned char)str[i] ) ) - return i+1; + if( Q_DecodeUTF8( &state, (byte)str[i] )) + return i + 1; } - Con_UtfProcessChar( 0 ); - return pos+1; + + return pos + 1; } static void Con_DrawCharToConback( int num, const byte *conchars, byte *dest ) @@ -875,129 +723,25 @@ static void Con_DrawCharToConback( int num, const byte *conchars, byte *dest ) /* ==================== -Con_TextAdjustSize +Con_GetFont -draw charcters routine ==================== */ -static void Con_TextAdjustSize( int *x, int *y, int *w, int *h ) +cl_font_t *Con_GetFont( int num ) { - float xscale, yscale; - - if( !x && !y && !w && !h ) return; - - // scale for screen sizes - xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; - yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; - - if( x ) *x *= xscale; - if( y ) *y *= yscale; - if( w ) *w *= xscale; - if( h ) *h *= yscale; + num = bound( 0, num, CON_NUMFONTS - 1 ); + return &con.chars[num]; } /* ==================== -Con_DrawGenericChar +Con_GetCurFont -draw console single character ==================== */ -static int Con_DrawGenericChar( int x, int y, int number, rgba_t color ) +cl_font_t *Con_GetCurFont( void ) { - int width, height; - float s1, t1, s2, t2; - wrect_t *rc; - - number &= 255; - - if( !con.curFont || !con.curFont->valid ) - return 0; - - number = Con_UtfProcessChar( number ); - if( !number ) - return 0; - - if( y < -con.curFont->charHeight ) - return 0; - - rc = &con.curFont->fontRc[number]; - R_GetTextureParms( &width, &height, con.curFont->hFontTexture ); - - if( !width || !height ) - return con.curFont->charWidths[number]; - - // don't apply color to fixed fonts it's already colored - if( con.curFont->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, con.curFont->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8 - ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); - else ref.dllFuncs.Color4ub( 255, 255, 255, color[3] ); - - // calc rectangle - s1 = (float)rc->left / width; - t1 = (float)rc->top / height; - s2 = (float)rc->right / width; - t2 = (float)rc->bottom / height; - width = ( rc->right - rc->left ) * con_fontscale->value; - height = ( rc->bottom - rc->top ) * con_fontscale->value; - - if( clgame.ds.adjust_size ) - Con_TextAdjustSize( &x, &y, &width, &height ); - ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, con.curFont->hFontTexture ); - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); // don't forget reset color - - return con.curFont->charWidths[number]; -} - -/* -==================== -Con_SetFont - -choose font size -==================== -*/ -void Con_SetFont( int fontNum ) -{ - fontNum = bound( 0, fontNum, CON_NUMFONTS - 1 ); - con.curFont = &con.chars[fontNum]; -} - -/* -==================== -Con_RestoreFont - -restore auto-selected console font -(that based on screen resolution) -==================== -*/ -void Con_RestoreFont( void ) -{ - con.curFont = con.lastUsedFont; -} - -/* -==================== -Con_DrawCharacter - -client version of routine -==================== -*/ -int Con_DrawCharacter( int x, int y, int number, rgba_t color ) -{ - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); - return Con_DrawGenericChar( x, y, number, color ); -} - -/* -==================== -Con_DrawCharacterLen - -returns character sizes in screen pixels -==================== -*/ -void Con_DrawCharacterLen( int number, int *width, int *height ) -{ - if( width && con.curFont ) *width = con.curFont->charWidths[number]; - if( height && con.curFont ) *height = con.curFont->charHeight; + return con.curFont; } /* @@ -1009,105 +753,7 @@ compute string width and height in screen pixels */ void GAME_EXPORT Con_DrawStringLen( const char *pText, int *length, int *height ) { - int curLength = 0; - - if( !con.curFont ) - return; - if( height ) - *height = con.curFont->charHeight; - if (!length) - return; - - *length = 0; - - while( *pText ) - { - byte c = *pText; - - if( *pText == '\n' ) - { - pText++; - curLength = 0; - } - - // skip color strings they are not drawing - if( IsColorString( pText )) - { - pText += 2; - continue; - } - - - // Convert to unicode - c = Con_UtfProcessChar( c ); - - if( c ) - curLength += con.curFont->charWidths[c]; - - pText++; - - if( curLength > *length ) - *length = curLength; - } -} - -/* -================== -Con_DrawString - -Draws a multi-colored string, optionally forcing -to a fixed color. -================== -*/ -int Con_DrawGenericString( int x, int y, const char *string, rgba_t setColor, qboolean forceColor, int hideChar ) -{ - rgba_t color; - int drawLen = 0; - int numDraws = 0; - const char *s; - - if( !con.curFont ) return 0; // no font set - - Con_UtfProcessChar( 0 ); - - // draw the colored text - memcpy( color, setColor, sizeof( color )); - s = string; - - while( *s ) - { - if( *s == '\n' ) - { - s++; - if( !*s ) break; // at end the string - drawLen = 0; // begin new row - y += con.curFont->charHeight; - } - - if( IsColorString( s )) - { - if( !forceColor ) - { - memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color )); - color[3] = setColor[3]; - } - - s += 2; - numDraws++; - continue; - } - - // hide char for overstrike mode - if( hideChar == numDraws ) - drawLen += con.curFont->charWidths[*s]; - else drawLen += Con_DrawCharacter( x + drawLen, y, *s, color ); - - numDraws++; - s++; - } - - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - return drawLen; + CL_DrawStringLen( con.curFont, pText, length, height, FONT_DRAW_UTF8 ); } /* @@ -1117,12 +763,11 @@ Con_DrawString client version of routine ==================== */ -int Con_DrawString( int x, int y, const char *string, rgba_t setColor ) +int Con_DrawString( int x, int y, const char *string, const rgba_t setColor ) { - return Con_DrawGenericString( x, y, string, setColor, false, -1 ); + return CL_DrawString( x, y, string, setColor, con.curFont, FONT_DRAW_UTF8 ); } - /* ================ Con_Init @@ -1130,19 +775,20 @@ Con_Init */ void Con_Init( void ) { - int i; - if( host.type == HOST_DEDICATED ) return; // dedicated server already have console // must be init before startup video subsystem - scr_conspeed = Cvar_Get( "scr_conspeed", "600", FCVAR_ARCHIVE, "console moving speed" ); - con_notifytime = Cvar_Get( "con_notifytime", "3", FCVAR_ARCHIVE, "notify time to live" ); - con_fontsize = Cvar_Get( "con_fontsize", "1", FCVAR_ARCHIVE, "console font number (0, 1 or 2)" ); - con_charset = Cvar_Get( "con_charset", "cp1251", FCVAR_ARCHIVE, "console font charset (only cp1251 supported now)" ); - con_fontscale = Cvar_Get( "con_fontscale", "1.0", FCVAR_ARCHIVE, "scale font texture" ); - con_fontnum = Cvar_Get( "con_fontnum", "-1", FCVAR_ARCHIVE, "console font number (0, 1 or 2), -1 for autoselect" ); - con_color = Cvar_Get( "con_color", "240 180 24", FCVAR_ARCHIVE, "set a custom console color" ); + Cvar_RegisterVariable( &scr_conspeed ); + Cvar_RegisterVariable( &con_notifytime ); + Cvar_RegisterVariable( &con_fontsize ); + Cvar_RegisterVariable( &con_charset ); + Cvar_RegisterVariable( &con_fontscale ); + Cvar_RegisterVariable( &con_fontrender ); + Cvar_RegisterVariable( &con_fontnum ); + Cvar_RegisterVariable( &con_color ); + Cvar_RegisterVariable( &scr_drawversion ); + Cvar_RegisterVariable( &con_oldfont ); // init the console buffer con.bufsize = CON_TEXTSIZE; @@ -1201,12 +847,14 @@ If no console is visible, the notify window will pop up. */ void Con_Print( const char *txt ) { - static int cr_pending = 0; - static char buf[MAX_PRINT_MSG]; - qboolean norefresh = false; - static int lastlength = 0; - static qboolean inupdate; - static int bufpos = 0; + static qboolean cr_pending = false; + static qboolean colorstring = false; + static char buf[MAX_PRINT_MSG]; + static int lastlength = 0; + static int bufpos = 0; + static int charpos = 0; + + qboolean norefresh = false; int c, mask = 0; // client not running @@ -1232,9 +880,8 @@ void Con_Print( const char *txt ) if( cr_pending ) { Con_DeleteLastLine(); - cr_pending = 0; + cr_pending = false; } - c = *txt; switch( c ) @@ -1242,23 +889,46 @@ void Con_Print( const char *txt ) case '\0': break; case '\r': - Con_AddLine( buf, bufpos, true ); - lastlength = CON_LINES_LAST().length; - cr_pending = 1; - bufpos = 0; + if( txt[1] != '\n' ) + { + Con_AddLine( buf, bufpos, true ); + lastlength = CON_LINES_LAST().length; + cr_pending = true; + bufpos = 0; + charpos = 0; + } break; case '\n': Con_AddLine( buf, bufpos, true ); lastlength = CON_LINES_LAST().length; bufpos = 0; + charpos = 0; break; default: buf[bufpos++] = c | mask; - if(( bufpos >= sizeof( buf ) - 1 ) || bufpos >= ( con.linewidth - 1 )) + + if( IsColorString( txt )) + { + // first color string character + colorstring = true; + } + else if( colorstring ) + { + // second color string character + colorstring = false; + } + else + { + // not a color string, move char counter + charpos++; + } + + if(( bufpos >= sizeof( buf ) - 1 ) || charpos >= ( con.linewidth - 1 )) { Con_AddLine( buf, bufpos, true ); lastlength = CON_LINES_LAST().length; bufpos = 0; + charpos = 0; } break; } @@ -1274,6 +944,7 @@ void Con_Print( const char *txt ) Con_AddLine( buf, bufpos, lastlength != 0 ); lastlength = 0; bufpos = 0; + charpos = 0; } // pump messages to avoid window hanging @@ -1296,6 +967,27 @@ void Con_Print( const char *txt ) } } +/* +================ +Con_NXPrintfv + +Draw a single debug line with specified height, color and time to live +================ +*/ +static void Con_NXPrintfv( keydest_t key_dest, const con_nprint_t *info, const char *fmt, va_list va ) +{ + if( info->index < 0 || info->index >= ARRAYSIZE( con.notify )) + return; + + Q_vsnprintf( con.notify[info->index].szNotify, sizeof( con.notify[info->index].szNotify ), fmt, va ); + + // setup values + con.notify[info->index].key_dest = key_dest; + con.notify[info->index].expire = host.realtime + info->time_to_live; + MakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 ); + con.draw_notify = true; +} + /* ================ Con_NPrint @@ -1306,21 +998,16 @@ Draw a single debug line with specified height void GAME_EXPORT Con_NPrintf( int idx, const char *fmt, ... ) { va_list args; - - if( idx < 0 || idx >= MAX_DBG_NOTIFY ) - return; - - memset( con.notify[idx].szNotify, 0, MAX_STRING ); + con_nprint_t info = + { + .index = idx, + .time_to_live = 4.0f, + .color = { 1.0f, 1.0f, 1.0f }, + }; va_start( args, fmt ); - Q_vsnprintf( con.notify[idx].szNotify, MAX_STRING, fmt, args ); + Con_NXPrintfv( key_game, &info, fmt, args ); va_end( args ); - - // reset values - con.notify[idx].key_dest = key_game; - con.notify[idx].expire = host.realtime + 4.0f; - MakeRGBA( con.notify[idx].color, 255, 255, 255, 255 ); - con.draw_notify = true; } /* @@ -1336,20 +1023,9 @@ void GAME_EXPORT Con_NXPrintf( con_nprint_t *info, const char *fmt, ... ) if( !info ) return; - if( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) - return; - - memset( con.notify[info->index].szNotify, 0, MAX_STRING ); - va_start( args, fmt ); - Q_vsnprintf( con.notify[info->index].szNotify, MAX_STRING, fmt, args ); + Con_NXPrintfv( key_game, info, fmt, args ); va_end( args ); - - // setup values - con.notify[info->index].key_dest = key_game; - con.notify[info->index].expire = host.realtime + info->time_to_live; - MakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 ); - con.draw_notify = true; } /* @@ -1362,21 +1038,16 @@ Draw a single debug line with specified height (menu version) void GAME_EXPORT UI_NPrintf( int idx, const char *fmt, ... ) { va_list args; - - if( idx < 0 || idx >= MAX_DBG_NOTIFY ) - return; - - memset( con.notify[idx].szNotify, 0, MAX_STRING ); + con_nprint_t info = + { + .index = idx, + .time_to_live = 4.0f, + .color = { 1.0f, 1.0f, 1.0f }, + }; va_start( args, fmt ); - Q_vsnprintf( con.notify[idx].szNotify, MAX_STRING, fmt, args ); + Con_NXPrintfv( key_menu, &info, fmt, args ); va_end( args ); - - // reset values - con.notify[idx].key_dest = key_menu; - con.notify[idx].expire = host.realtime + 4.0f; - MakeRGBA( con.notify[idx].color, 255, 255, 255, 255 ); - con.draw_notify = true; } /* @@ -1392,20 +1063,9 @@ void GAME_EXPORT UI_NXPrintf( con_nprint_t *info, const char *fmt, ... ) if( !info ) return; - if( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) - return; - - memset( con.notify[info->index].szNotify, 0, MAX_STRING ); - va_start( args, fmt ); - Q_vsnprintf( con.notify[info->index].szNotify, MAX_STRING, fmt, args ); + Con_NXPrintfv( key_menu, info, fmt, args ); va_end( args ); - - // setup values - con.notify[info->index].key_dest = key_menu; - con.notify[info->index].expire = host.realtime + info->time_to_live; - MakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 ); - con.draw_notify = true; } /* @@ -1420,9 +1080,9 @@ EDIT FIELDS Con_ClearField ================ */ -void Con_ClearField( field_t *edit ) +static void Con_ClearField( field_t *edit ) { - memset( edit->buffer, 0, MAX_STRING ); + memset( edit->buffer, 0, sizeof( edit->buffer )); edit->cursor = 0; edit->scroll = 0; } @@ -1435,7 +1095,7 @@ Field_Set static void Field_Set( field_t *f, const char *string ) { f->scroll = 0; - f->cursor = Q_strncpy( f->buffer, string, MAX_STRING ); + f->cursor = Q_strncpy( f->buffer, string, sizeof( f->buffer )); } /* @@ -1443,7 +1103,7 @@ static void Field_Set( field_t *f, const char *string ) Field_Paste ================ */ -void Field_Paste( field_t *edit ) +static void Field_Paste( field_t *edit ) { char *cbd; int i, pasteLen; @@ -1479,7 +1139,7 @@ in-game talk, and menu fields Key events are used for non-printable characters, others are gotten from char events. ================= */ -void Field_KeyDownEvent( field_t *edit, int key ) +static void Field_KeyDownEvent( field_t *edit, int key ) { int len; @@ -1499,7 +1159,7 @@ void Field_KeyDownEvent( field_t *edit, int key ) return; } - if( key == K_BACKSPACE ) + if( key == K_BACKSPACE || key == K_X_BUTTON ) { if( edit->cursor > 0 ) { @@ -1511,7 +1171,7 @@ void Field_KeyDownEvent( field_t *edit, int key ) return; } - if( key == K_RIGHTARROW ) + if( key == K_RIGHTARROW || key == K_DPAD_RIGHT ) { if( edit->cursor < len ) edit->cursor = Con_UtfMoveRight( edit->buffer, edit->cursor, edit->widthInChars ); if( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) @@ -1519,7 +1179,7 @@ void Field_KeyDownEvent( field_t *edit, int key ) return; } - if( key == K_LEFTARROW ) + if( key == K_LEFTARROW || key == K_DPAD_LEFT ) { if( edit->cursor > 0 ) edit->cursor = Con_UtfMoveLeft( edit->buffer, edit->cursor ); if( edit->cursor < edit->scroll ) edit->scroll--; @@ -1550,7 +1210,7 @@ void Field_KeyDownEvent( field_t *edit, int key ) Field_CharEvent ================== */ -void Field_CharEvent( field_t *edit, int ch ) +static void Field_CharEvent( field_t *edit, int ch ) { int len; @@ -1611,75 +1271,35 @@ void Field_CharEvent( field_t *edit, int ch ) Field_DrawInputLine ================== */ -void Field_DrawInputLine( int x, int y, field_t *edit ) +static void Field_DrawInputLine( int x, int y, const field_t *edit ) { - int len, cursorChar; - int drawLen, hideChar = -1; - int prestep, curPos; - char str[MAX_SYSPATH]; - byte *colorDefault; + int curPos; + char str[MAX_SYSPATH]; + const byte *colorDefault = g_color_table[ColorIndex( COLOR_DEFAULT )]; + const int prestep = bound( 0, edit->scroll, sizeof( edit->buffer ) - 1 ); + const int drawLen = bound( 0, edit->widthInChars, sizeof( str )); + const int cursorCharPos = bound( 0, edit->cursor - prestep, sizeof( str )); - drawLen = edit->widthInChars; - len = Q_strlen( edit->buffer ) + 1; - colorDefault = g_color_table[ColorIndex( COLOR_DEFAULT )]; - - // guarantee that cursor will be visible - if( len <= drawLen ) - { - prestep = 0; - } - else - { - if( edit->scroll + drawLen > len ) - { - edit->scroll = len - drawLen; - if( edit->scroll < 0 ) edit->scroll = 0; - } - - prestep = edit->scroll; - } - - if( prestep + drawLen > len ) - drawLen = len - prestep; - - // extract characters from the field at - drawLen = Q_min( drawLen, MAX_SYSPATH - 1 ); - - memcpy( str, edit->buffer + prestep, drawLen ); - str[drawLen] = 0; - - // save char for overstrike - cursorChar = str[edit->cursor - prestep]; - - if( host.key_overstrike && cursorChar && !((int)( host.realtime * 4 ) & 1 )) - hideChar = edit->cursor - prestep; // skip this char + str[0] = 0; + Q_strncpy( str, edit->buffer + prestep, drawLen ); // draw it - Con_DrawGenericString( x, y, str, colorDefault, false, hideChar ); + CL_DrawString( x, y, str, colorDefault, con.curFont, FONT_DRAW_UTF8 ); // draw the cursor if((int)( host.realtime * 4 ) & 1 ) return; // off blink // calc cursor position - str[edit->cursor - prestep] = 0; - Con_DrawStringLen( str, &curPos, NULL ); - Con_UtfProcessChar( 0 ); + str[cursorCharPos] = 0; + CL_DrawStringLen( con.curFont, str, &curPos, NULL, FONT_DRAW_UTF8 ); - if( host.key_overstrike && cursorChar ) + if( host.key_overstrike ) { - // overstrike cursor -#if 0 - pglEnable( GL_BLEND ); - pglDisable( GL_ALPHA_TEST ); - pglBlendFunc( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); -#endif - Con_DrawGenericChar( x + curPos, y, cursorChar, colorDefault ); + CL_DrawCharacter( x + curPos, y, '|', colorDefault, con.curFont, 0 ); } else { - Con_UtfProcessChar( 0 ); - Con_DrawCharacter( x + curPos, y, '_', colorDefault ); + CL_DrawCharacter( x + curPos, y, '_', colorDefault, con.curFont, 0 ); } } @@ -1690,6 +1310,32 @@ CONSOLE HISTORY HANDLING ============================================================================= */ +/* +=================== +Con_HistoryFromField + +=================== +*/ +static void Con_HistoryFromField( history_line_t *dst, const field_t *src ) +{ + Q_strncpy( dst->buffer, src->buffer, sizeof( dst->buffer )); + dst->cursor = src->cursor; + dst->scroll = src->scroll; +} + +/* +=================== +Con_HistoryToField + +=================== +*/ +static void Con_HistoryToField( field_t *dst, const history_line_t *src ) +{ + Q_strncpy( dst->buffer, src->buffer, sizeof( dst->buffer )); + dst->cursor = src->cursor; + dst->scroll = src->scroll; +} + /* =================== Con_HistoryUp @@ -1699,12 +1345,14 @@ Con_HistoryUp static void Con_HistoryUp( con_history_t *self, field_t *in ) { if( self->line == self->next ) - self->backup = *in; + Con_HistoryFromField( &self->backup, in ); + else + Con_HistoryFromField( &self->lines[self->line % CON_HISTORY], in ); if(( self->next - self->line ) < CON_HISTORY ) self->line = Q_max( 0, self->line - 1 ); - *in = self->lines[self->line % CON_HISTORY]; + Con_HistoryToField( in, &self->lines[self->line % CON_HISTORY] ); } /* @@ -1715,10 +1363,13 @@ Con_HistoryDown */ static void Con_HistoryDown( con_history_t *self, field_t *in ) { + Con_HistoryFromField( &self->lines[self->line % CON_HISTORY], in ); + self->line = Q_min( self->next, self->line + 1 ); if( self->line == self->next ) - *in = self->backup; - else *in = self->lines[self->line % CON_HISTORY]; + Con_HistoryToField( in, &self->backup ); + else + Con_HistoryToField( in, &self->lines[self->line % CON_HISTORY] ); } /* @@ -1726,7 +1377,7 @@ static void Con_HistoryDown( con_history_t *self, field_t *in ) Con_HistoryAppend =================== */ -static void Con_HistoryAppend( con_history_t *self, field_t *from ) +static void Con_HistoryAppend( con_history_t *self, const field_t *from ) { int prevLine = Q_max( 0, self->line - 1 ); const char *buf = from->buffer; @@ -1747,42 +1398,50 @@ static void Con_HistoryAppend( con_history_t *self, field_t *from ) if( !Q_strcmp( from->buffer, self->lines[prevLine % CON_HISTORY].buffer )) return; - self->lines[self->next % CON_HISTORY] = *from; + Con_HistoryFromField( &self->lines[self->next % CON_HISTORY], from ); self->line = ++self->next; } static void Con_LoadHistory( con_history_t *self ) { - const byte *aFile = FS_LoadFile( "console_history.txt", NULL, true ); - const char *pLine, *pFile; - int i, len; - field_t *f; + file_t *fd; + int i; - if( !aFile ) + fd = FS_Open( "console_history.txt", "rb", true ); + + if( !fd ) return; - for( pFile = pLine = (char *)aFile; *pFile; pFile++ ) + while( !FS_Eof( fd )) { - if( *pFile != '\n' ) + history_line_t *f = &self->lines[self->next % CON_HISTORY]; + + FS_Gets( fd, f->buffer, sizeof( f->buffer )); + f->cursor = Q_strlen( f->buffer ); + f->scroll = 0; + + // skip empty lines + if( f->cursor == 0 ) continue; - Con_ClearField( &self->lines[self->next] ); - - len = Q_min( pFile - pLine + 1, sizeof( f->buffer )); - f = &self->lines[self->next % CON_HISTORY]; - f->widthInChars = con.linewidth; - f->cursor = len - 1; - Q_strncpy( f->buffer, pLine, len); + // skip repeating lines + if( self->next > 0 ) + { + const history_line_t *prev = &self->lines[(self->next - 1) % CON_HISTORY]; + if( !Q_stricmp( prev->buffer, f->buffer )) + continue; + } self->next++; - - pLine = pFile + 1; } + FS_Close( fd ); + for( i = self->next; i < CON_HISTORY; i++ ) { - Con_ClearField( &self->lines[i] ); - self->lines[i].widthInChars = con.linewidth; + history_line_t *f = &self->lines[i]; + + memset( f, 0, sizeof( *f )); } self->line = self->next; @@ -1793,13 +1452,25 @@ static void Con_SaveHistory( con_history_t *self ) int historyStart = self->next - CON_HISTORY, i; file_t *f; + // do not save history if nothing was executed + if( self->next == 0 ) + return; + if( historyStart < 0 ) historyStart = 0; - f = FS_Open( "console_history.txt", "w", true ); + f = FS_Open( "console_history.txt", "wb", true ); for( i = historyStart; i < self->next; i++ ) - FS_Printf( f, "%s\n", self->lines[i % CON_HISTORY].buffer ); + { + const char *s = self->lines[i % CON_HISTORY].buffer; + + // HACKHACK: don't save lines that have something that looks like a password + if( Q_stristr( s, "password" ) || Q_stristr( s, "_pw" )) + continue; + + FS_Printf( f, "%s\n", s ); + } FS_Close( f ); } @@ -1821,6 +1492,16 @@ Handles history and console scrollback */ void Key_Console( int key ) { + // exit the console by pressing MINUS on NSwitch + // or both Back(Select)/Start buttons for everyone else + if( key == K_BACK_BUTTON || key == K_START_BUTTON || key == K_ESCAPE ) + { + if( cls.state == ca_active && !cl.background ) + Key_SetKeyDest( key_game ); + else UI_SetActiveMenu( true ); + return; + } + // ctrl-L clears screen if( key == 'l' && Key_IsDown( K_CTRL )) { @@ -1828,8 +1509,8 @@ void Key_Console( int key ) return; } - // enter finishes the line - if( key == K_ENTER || key == K_KP_ENTER ) + // enter or A finish the line + if( key == K_ENTER || key == K_KP_ENTER || key == K_A_BUTTON ) { // backslash text are commands, else chat if( con.input.buffer[0] == '\\' || con.input.buffer[0] == '/' ) @@ -1841,6 +1522,9 @@ void Key_Console( int key ) Con_Printf( ">%s\n", con.input.buffer ); // copy line to history buffer + // just in case, remove all CR and LF characters pushing it to the history + // not sure how they get even added in the first place + COM_RemoveLineFeed( con.input.buffer, sizeof( con.input.buffer )); Con_HistoryAppend( &con.history, &con.input ); Con_ClearField( &con.input ); @@ -1856,7 +1540,7 @@ void Key_Console( int key ) } // command completion - if( key == K_TAB ) + if( key == K_TAB || key == K_L2_BUTTON ) { Con_CompleteCommand( &con.input ); Con_Bottom(); @@ -1877,13 +1561,13 @@ void Key_Console( int key ) } // console scrolling - if( key == K_PGUP ) + if( key == K_PGUP || key == K_DPAD_UP ) { Con_PageUp( 1 ); return; } - if( key == K_PGDN ) + if( key == K_PGDN || key == K_DPAD_DOWN ) { Con_PageDown( 1 ); return; @@ -1919,6 +1603,13 @@ void Key_Console( int key ) return; } + // enable the OSK with button press + if( key == K_Y_BUTTON ) + { + Key_EnableTextInput( true, true ); + return; + } + // pass to the normal editline routine Field_KeyDownEvent( &con.input, key ); } @@ -1934,14 +1625,14 @@ void Key_Message( int key ) { char buffer[MAX_SYSPATH]; - if( key == K_ESCAPE ) + if( key == K_ESCAPE || key == K_BACK_BUTTON ) { Key_SetKeyDest( key_game ); Con_ClearField( &con.chat ); return; } - if( key == K_ENTER || key == K_KP_ENTER ) + if( key == K_ENTER || key == K_KP_ENTER || key == K_A_BUTTON ) { if( con.chat.buffer[0] && cls.state == ca_active ) { @@ -1974,7 +1665,7 @@ Con_DrawInput The input line scrolls horizontally if typing goes beyond the right edge ================ */ -void Con_DrawInput( int lines ) +static void Con_DrawInput( int lines ) { int y; @@ -1983,7 +1674,7 @@ void Con_DrawInput( int lines ) return; y = lines - ( con.curFont->charHeight * 2 ); - Con_DrawCharacter( con.curFont->charWidths[' '], y, ']', g_color_table[7] ); + CL_DrawCharacter( con.curFont->charWidths[' '], y, ']', g_color_table[7], con.curFont, 0 ); Field_DrawInputLine( con.curFont->charWidths[' ']*2, y, &con.input ); } @@ -1994,32 +1685,39 @@ Con_DrawDebugLines Custom debug messages ================ */ -int Con_DrawDebugLines( void ) +static int Con_DrawDebugLines( void ) { + notify_t *notify = con.notify; int i, count = 0; int defaultX; int y = 20; + int fontTall; + + if( !con.curFont || !con.curFont->valid ) + return 0; defaultX = refState.width / 4; + fontTall = con.curFont->charHeight + 1; - for( i = 0; i < MAX_DBG_NOTIFY; i++ ) + for( i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ ) { - if( host.realtime < con.notify[i].expire && con.notify[i].key_dest == cls.key_dest ) - { - int x, len; - int fontTall = 0; + int x, len; - Con_DrawStringLen( con.notify[i].szNotify, &len, &fontTall ); - x = refState.width - Q_max( defaultX, len ) - 10; - fontTall += 1; + if( host.realtime > notify->expire ) + continue; - if( y + fontTall > refState.height - 20 ) - return count; + if( notify->key_dest != cls.key_dest ) + continue; - count++; - y = 20 + fontTall * i; - Con_DrawString( x, y, con.notify[i].szNotify, con.notify[i].color ); - } + Con_DrawStringLen( notify->szNotify, &len, NULL ); + x = refState.width - Q_max( defaultX, len ) - 10; + + if( y + fontTall > refState.height - 20 ) + return count; + + count++; + y += fontTall; + CL_DrawString( x, y, notify->szNotify, notify->color, con.curFont, FONT_DRAW_UTF8 | FONT_DRAW_NOLF ); } return count; @@ -2038,17 +1736,21 @@ void Con_DrawDebug( void ) string dlstring; int x, y; - if( scr_download->value != -1.0f ) + if( scr_download.value != -1.0f ) { + int length; Q_snprintf( dlstring, sizeof( dlstring ), "Downloading [%d remaining]: ^2%s^7 %5.1f%% time %.f secs", - host.downloadcount, host.downloadfile, scr_download->value, Sys_DoubleTime() - timeStart ); - x = refState.width - 500; + host.downloadcount, host.downloadfile, scr_download.value, Sys_DoubleTime() - timeStart ); + + Con_DrawStringLen( dlstring, &length, NULL ); + length = Q_max( length, 300 ); + x = refState.width - length * 1.05f; y = con.curFont->charHeight * 1.05f; Con_DrawString( x, y, dlstring, g_color_table[7] ); } else { - timeStart = Sys_DoubleTime(); + timeStart = host.realtime; } if( !host.allow_console || Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) @@ -2068,7 +1770,7 @@ Con_DrawNotify Draws the last few lines of output transparently over the game top ================ */ -void Con_DrawNotify( void ) +static void Con_DrawNotify( void ) { double time = cl.time; int i, x, y = 0; @@ -2079,11 +1781,11 @@ void Con_DrawNotify( void ) if( host.allow_console && ( !Cvar_VariableInteger( "cl_background" ) && !Cvar_VariableInteger( "sv_background" ))) { - for( i = CON_LINES_COUNT - con.num_times; i < CON_LINES_COUNT; i++ ) + for( i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ ) { con_lineinfo_t *l = &CON_LINES( i ); - if( l->addtime < ( time - con_notifytime->value )) + if( l->addtime < ( time - con_notifytime.value )) continue; Con_DrawString( x, y, l->start, g_color_table[7] ); @@ -2120,7 +1822,7 @@ If alpha is 0, the line is not drawn, but still wrapped and its height returned. ================ */ -int Con_DrawConsoleLine( int y, int lineno ) +static int Con_DrawConsoleLine( int y, int lineno ) { con_lineinfo_t *li = &CON_LINES( lineno ); @@ -2128,7 +1830,11 @@ int Con_DrawConsoleLine( int y, int lineno ) return 0; // this string will be shown only at notify if( y >= con.curFont->charHeight ) - Con_DrawGenericString( con.curFont->charWidths[' '], y, li->start, g_color_table[7], false, -1 ); + { + float x = con.curFont->charWidths[' ']; + + CL_DrawString( x, y, li->start, g_color_table[7], con.curFont, FONT_DRAW_UTF8 ); + } return con.curFont->charHeight; } @@ -2171,7 +1877,7 @@ Con_DrawConsole Draws the console with the solid background ================ */ -void Con_DrawSolidConsole( int lines ) +static void Con_DrawSolidConsole( int lines ) { int i, x, y; float fraction; @@ -2195,17 +1901,15 @@ void Con_DrawSolidConsole( int lines ) // draw current version memcpy( color, g_color_table[7], sizeof( color )); - Q_snprintf( curbuild, MAX_STRING, "%s %i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + Q_snprintf( curbuild, MAX_STRING, XASH_ENGINE_NAME " %i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); Con_DrawStringLen( curbuild, &stringLen, &charH ); - start = refState.width - stringLen; - stringLen = Con_StringLength( curbuild ); + start = refState.width - stringLen; fraction = lines / (float)refState.height; color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number - for( i = 0; i < stringLen; i++ ) - width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); + Con_DrawString( start, 0, curbuild, color ); // draw the text if( CON_LINES_COUNT > 0 ) @@ -2222,7 +1926,7 @@ void Con_DrawSolidConsole( int lines ) // draw red arrows to show the buffer is backscrolled for( x = 0; x < con.linewidth; x += 4 ) - Con_DrawCharacter(( x + 1 ) * start, y, '^', g_color_table[1] ); + CL_DrawCharacter( ( x + 1 ) * start, y, '^', g_color_table[1], con.curFont, 0 ); y -= con.curFont->charHeight; } x = lastline; @@ -2263,9 +1967,9 @@ void Con_DrawConsole( void ) if( cls.state == ca_connecting || cls.state == ca_connected ) { - if( !cl_allow_levelshots->value ) + if( !cl_allow_levelshots.value && !cls.timedemo ) { - if(( Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) && cls.key_dest != key_console ) + if( cls.key_dest != key_console && ( Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" ))) con.vislines = con.showlines = 0; else con.vislines = con.showlines = refState.height; } @@ -2273,7 +1977,7 @@ void Con_DrawConsole( void ) { con.showlines = 0; - if( host_developer.value >= DEV_EXTENDED ) + if( host_developer.value >= DEV_EXTENDED && !cls.timedemo ) Con_DrawNotify(); // draw notify lines } } @@ -2305,7 +2009,7 @@ void Con_DrawConsole( void ) { if( con.vislines ) Con_DrawSolidConsole( con.vislines ); - else if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message )) + else if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message ) && !cls.timedemo ) Con_DrawNotify(); // draw notify lines } break; @@ -2325,39 +2029,41 @@ void Con_DrawVersion( void ) { // draws the current build byte *color = g_color_table[7]; - int i, stringLen, width = 0, charH = 0; + int stringLen, charH = 0; int start, height = refState.height; - qboolean draw_version = false; string curbuild; - switch( cls.scrshot_action ) - { - case scrshot_normal: - case scrshot_snapshot: - draw_version = true; - break; - } + if( !scr_drawversion.value ) + return; - if( !host.force_draw_version ) + if( cls.key_dest == key_menu ) { - if(( cls.key_dest != key_menu && !draw_version ) || CL_IsDevOverviewMode() == 2 || net_graph->value ) + Q_snprintf( curbuild, sizeof( curbuild ), + "v%i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + } + else + { + qboolean draw_version; + + if( CL_IsDevOverviewMode() == 2 || net_graph.value ) return; + + draw_version = cls.scrshot_action == scrshot_normal + || cls.scrshot_action == scrshot_snapshot + || host.force_draw_version_time > host.realtime; + + if( !draw_version ) + return; + + Q_snprintf( curbuild, sizeof( curbuild ), + XASH_ENGINE_NAME " v%i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); } - if( host.force_draw_version_time > host.realtime ) - host.force_draw_version = false; - - if( host.force_draw_version || draw_version ) - Q_snprintf( curbuild, MAX_STRING, "%s v%i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); - else Q_snprintf( curbuild, MAX_STRING, "v%i/%s (%s-%s build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); - Con_DrawStringLen( curbuild, &stringLen, &charH ); start = refState.width - stringLen * 1.05f; - stringLen = Con_StringLength( curbuild ); height -= charH * 1.05f; - for( i = 0; i < stringLen; i++ ) - width += Con_DrawCharacter( start + width, height, curbuild[i], color ); + Con_DrawString( start, height, curbuild, color ); } /* @@ -2376,13 +2082,17 @@ void Con_RunConsole( void ) // decide on the destination height of the console if( host.allow_console && cls.key_dest == key_console ) { +#if XASH_MOBILE_PLATFORM + con.showlines = refState.height; // always full screen on mobile devices +#else if( cls.state < ca_active || cl.first_frame ) con.showlines = refState.height; // full screen else con.showlines = (refState.height >> 1); // half screen +#endif } else con.showlines = 0; // none visible - lines_per_frame = fabs( scr_conspeed->value ) * host.realframetime; + lines_per_frame = fabs( scr_conspeed.value ) * host.realframetime; if( con.showlines < con.vislines ) { @@ -2397,38 +2107,30 @@ void Con_RunConsole( void ) con.vislines = con.showlines; } - if( FBitSet( con_charset->flags, FCVAR_CHANGED ) || - FBitSet( con_fontscale->flags, FCVAR_CHANGED ) || - FBitSet( con_fontnum->flags, FCVAR_CHANGED ) || - FBitSet( cl_charset->flags, FCVAR_CHANGED ) ) + if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED )) { // update codepage parameters - if( !Q_stricmp( con_charset->string, "cp1251" )) + if( !Q_stricmp( con_charset.string, "cp1251" )) { g_codepage = 1251; } - else if( !Q_stricmp( con_charset->string, "cp1252" )) + else if( !Q_stricmp( con_charset.string, "cp1252" )) { g_codepage = 1252; } else { - Con_Printf( S_WARN "Unknown charset %s, defaulting to cp1252", con_charset->string ); - - Cvar_DirectSet( con_charset, "cp1252" ); - g_codepage = 1252; + g_codepage = 0; } - g_utf8 = !Q_stricmp( cl_charset->string, "utf-8" ); + cls.accept_utf8 = !Q_stricmp( cl_charset.string, "utf-8" ); Con_InvalidateFonts(); Con_LoadConchars(); - cls.creditsFont.valid = false; - SCR_LoadCreditsFont(); - ClearBits( con_charset->flags, FCVAR_CHANGED ); - ClearBits( con_fontnum->flags, FCVAR_CHANGED ); - ClearBits( con_fontscale->flags, FCVAR_CHANGED ); - ClearBits( cl_charset->flags, FCVAR_CHANGED ); - + ClearBits( con_charset.flags, FCVAR_CHANGED ); + ClearBits( con_fontnum.flags, FCVAR_CHANGED ); + ClearBits( con_fontscale.flags, FCVAR_CHANGED ); + ClearBits( cl_charset.flags, FCVAR_CHANGED ); + ClearBits( con_oldfont.flags, FCVAR_CHANGED ); } } @@ -2459,6 +2161,45 @@ void Con_CharEvent( int key ) } } +static int Con_LoadSimpleConback( const char *name, int flags ) +{ + int i; + + for( i = 0; i < 5; i++ ) + { + string path; + + switch( i ) + { + case 0: + Q_snprintf( path, sizeof( path ), "gfx/shell/%s.dds", name ); + break; + case 1: + Q_snprintf( path, sizeof( path ), "gfx/shell/%s.bmp", name ); + break; + case 2: + Q_snprintf( path, sizeof( path ), "gfx/shell/%s.tga", name ); + break; + case 3: + Q_snprintf( path, sizeof( path ), "cached/%s640", name ); + break; + case 4: + Q_snprintf( path, sizeof( path ), "cached/%s", name ); + break; + } + + if( g_fsapi.FileExists( path, false )) + { + int gl_texturenum = ref.dllFuncs.GL_LoadTexture( path, NULL, 0, flags ); + + if( gl_texturenum ) + return gl_texturenum; + } + } + + return 0; +} + /* ========= Con_VidInit @@ -2468,46 +2209,25 @@ INTERNAL RESOURCE */ void Con_VidInit( void ) { + const uint flags = TF_IMAGE|TF_ALLOW_NEAREST; + if( !con.historyLoaded ) { Con_LoadHistory( &con.history ); con.historyLoaded = true; } + if( Sys_CheckParm( "-oldfont" )) + Cvar_DirectSet( &con_oldfont, "1" ); + Con_LoadConchars(); Con_CheckResize(); + #if XASH_LOW_MEMORY - con.background = R_GetBuiltinTexture( REF_BLACK_TEXTURE ); + con.background = R_GetBuiltinTexture( REF_GRAY_TEXTURE ); #else // loading console image - if( host.allow_console ) - { - // trying to load truecolor image first - if( FS_FileExists( "gfx/shell/conback.bmp", false ) || FS_FileExists( "gfx/shell/conback.tga", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "gfx/shell/conback", NULL, 0, TF_IMAGE ); - - if( !con.background ) - { - if( FS_FileExists( "cached/conback640", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "cached/conback640", NULL, 0, TF_IMAGE ); - else if( FS_FileExists( "cached/conback", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "cached/conback", NULL, 0, TF_IMAGE ); - } - } - else - { - // trying to load truecolor image first - if( FS_FileExists( "gfx/shell/loading.bmp", false ) || FS_FileExists( "gfx/shell/loading.tga", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "gfx/shell/loading", NULL, 0, TF_IMAGE ); - - if( !con.background ) - { - if( FS_FileExists( "cached/loading640", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "cached/loading640", NULL, 0, TF_IMAGE ); - else if( FS_FileExists( "cached/loading", false )) - con.background = ref.dllFuncs.GL_LoadTexture( "cached/loading", NULL, 0, TF_IMAGE ); - } - } + con.background = Con_LoadSimpleConback( host.allow_console ? "conback" : "loading", flags ); if( !con.background ) // last chance - quake conback image { @@ -2563,8 +2283,10 @@ Con_InvalidateFonts */ void Con_InvalidateFonts( void ) { - memset( con.chars, 0, sizeof( con.chars )); - con.curFont = con.lastUsedFont = NULL; + int i; + for( i = 0; i < ARRAYSIZE( con.chars ); i++ ) + CL_FreeFont( &con.chars[i] ); + con.curFont = NULL; } /* @@ -2589,11 +2311,20 @@ Con_DefaultColor called from MainUI ========= */ -void GAME_EXPORT Con_DefaultColor( int r, int g, int b ) +void Con_DefaultColor( int r, int g, int b, qboolean gameui ) { r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); + + // gameui wants to override console color... check if it's not default + if( gameui && ( g_color_table[7][0] != r || g_color_table[7][1] != g || g_color_table[7][2] != b )) + { + // yes, different from default orange, disable con_color + SetBits( con_color.flags, FCVAR_READ_ONLY ); + ClearBits( con_color.flags, FCVAR_CHANGED ); + } + MakeRGBA( g_color_table[7], r, g, b, 255 ); } diff --git a/engine/client/gamma.c b/engine/client/gamma.c new file mode 100644 index 00000000..61e21332 --- /dev/null +++ b/engine/client/gamma.c @@ -0,0 +1,444 @@ +/* +gamma.c - gamma routines +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" +#include "xash3d_mathlib.h" +#include "enginefeatures.h" + +//----------------------------------------------------------------------------- +// Gamma conversion support +//----------------------------------------------------------------------------- +static qboolean gamma_rebuilt; +static byte texgammatable[256]; +static uint lightgammatable[1024]; +static uint lineargammatable[1024]; +static uint screengammatable[1024]; +static CVAR_DEFINE( v_direct, "direct", "0.9", 0, "direct studio lighting" ); +static CVAR_DEFINE( v_texgamma, "texgamma", "2.0", 0, "texgamma amount" ); +static CVAR_DEFINE( v_lightgamma, "lightgamma", "2.5", 0, "lightgamma amount" ); +static CVAR_DEFINE( v_brightness, "brightness", "0.0", FCVAR_ARCHIVE, "brightness factor" ); +static CVAR_DEFINE( v_gamma, "gamma", "2.5", FCVAR_ARCHIVE, "gamma amount" ); + +static void BuildGammaTable( const float gamma, const float brightness, const float texgamma, const float lightgamma ) +{ + float g1, g2, g3; + int i; + + if( gamma != 0.0 ) + g1 = 1.0 / gamma; + else g1 = 0.4; + + g2 = g1 * texgamma; + + if( brightness <= 0.0 ) + g3 = 0.125; + else if( brightness <= 1.0 ) + g3 = 0.125 - brightness * brightness * 0.075; + else + g3 = 0.05; + + for( i = 0; i < 256; i++ ) + { + double d = pow( i / 255.0, (double)g2 ); + int inf = d * 255.0; + texgammatable[i] = bound( 0, inf, 255 ); + } + + for( i = 0; i < 1024; i++ ) + { + double d; + float f = pow( i / 1023.0, (double)lightgamma ); + int inf; + + if( brightness > 1.0 ) + f *= brightness; + + if( f <= g3 ) + f = ( f / g3 ) * 0.125; + else + f = (( f - g3 ) / ( 1.0 - g3 )) * 0.875 + 0.125; + + d = pow( (double)f, (double)g1 ); // do not remove the cast, or tests fail + inf = d * 1023.0; + lightgammatable[i] = bound( 0, inf, 1023 ); + + // do these calculations in the same loop... + lineargammatable[i] = pow( i / 1023.0, (double)gamma ) * 1023.0; + screengammatable[i] = pow( i / 1023.0, 1.0 / gamma ) * 1023.0; + } +} + +static void V_ValidateGammaCvars( void ) +{ + if( v_gamma.value < 1.8f ) + Cvar_DirectSet( &v_gamma, "1.8" ); + else if( v_gamma.value > 3.0f ) + Cvar_DirectSet( &v_gamma, "3" ); + + if( v_texgamma.value < 1.8f ) + Cvar_DirectSet( &v_texgamma, "1.8" ); + else if( v_texgamma.value > 3.0f ) + Cvar_DirectSet( &v_texgamma, "3" ); + + if( v_lightgamma.value < 1.8f ) + Cvar_DirectSet( &v_lightgamma, "1.8" ); + else if( v_lightgamma.value > 3.0f ) + Cvar_DirectSet( &v_lightgamma, "3" ); + + if( v_brightness.value < 0.0f ) + Cvar_DirectSet( &v_brightness, "0" ); + else if( v_brightness.value > 3.0f ) + Cvar_DirectSet( &v_brightness, "3" ); +} + +void V_CheckGamma( void ) +{ + static qboolean dirty = false; + + // because these cvars were defined as archive + // but wasn't doing anything useful + // reset them into default values + // this might be removed after a while + if( v_direct.value == 1.0f || v_lightgamma.value == 1.0f ) + { + Cvar_DirectSet( &v_direct, "0.9" ); + Cvar_DirectSet( &v_lightgamma, "2.5" ); + } + + if( cls.scrshot_action == scrshot_envshot || cls.scrshot_action == scrshot_skyshot ) + { + dirty = true; // force recalculate next normal frame + BuildGammaTable( 1.8f, 0.0f, 2.0f, 2.5f ); + if( ref.initialized ) + ref.dllFuncs.R_GammaChanged( true ); + return; + } + + if( dirty || FBitSet( v_texgamma.flags|v_lightgamma.flags|v_brightness.flags|v_gamma.flags, FCVAR_CHANGED )) + { + V_ValidateGammaCvars(); + + dirty = false; + gamma_rebuilt = true; + + BuildGammaTable( v_gamma.value, v_brightness.value, v_texgamma.value, v_lightgamma.value ); + + // force refdll to recalculate lightmaps + if( ref.initialized ) + ref.dllFuncs.R_GammaChanged( false ); + } +} + +void V_CheckGammaEnd( void ) +{ + // don't reset changed flag if it was set during frame + // keep it for next frame + if( !gamma_rebuilt ) + return; + + gamma_rebuilt = false; + + // keep the flags until the end of frame so client.dll will catch these changes + if( FBitSet( v_texgamma.flags|v_lightgamma.flags|v_brightness.flags|v_gamma.flags, FCVAR_CHANGED )) + { + ClearBits( v_texgamma.flags, FCVAR_CHANGED ); + ClearBits( v_lightgamma.flags, FCVAR_CHANGED ); + ClearBits( v_brightness.flags, FCVAR_CHANGED ); + ClearBits( v_gamma.flags, FCVAR_CHANGED ); + } +} + +void V_Init( void ) +{ + Cvar_RegisterVariable( &v_texgamma ); + Cvar_RegisterVariable( &v_lightgamma ); + Cvar_RegisterVariable( &v_brightness ); + Cvar_RegisterVariable( &v_gamma ); + Cvar_RegisterVariable( &v_direct ); + + // force gamma init + SetBits( v_gamma.flags, FCVAR_CHANGED ); + V_CheckGamma(); +} + +byte TextureToGamma( byte b ) +{ + if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) + return b; + + return texgammatable[b]; +} + +byte LightToTexGamma( byte b ) +{ + if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) + return b; + + // 255 << 2 is 1020, impossible to overflow + return lightgammatable[b << 2] >> 2; +} + +uint ScreenGammaTable( uint b ) +{ + if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) + return b; + + if( unlikely( b >= ARRAYSIZE( screengammatable ))) + return 0; + + return screengammatable[b]; +} + +uint LinearGammaTable( uint b ) +{ + if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) + return b; + + if( unlikely( b >= ARRAYSIZE( lineargammatable ))) + return 0; + return lineargammatable[b]; +} + +intptr_t V_GetGammaPtr( int parm ) +{ + switch( parm ) + { + case PARM_GET_TEXGAMMATABLE_PTR: + return (intptr_t)texgammatable; + case PARM_GET_LIGHTGAMMATABLE_PTR: + return (intptr_t)lightgammatable; + case PARM_GET_SCREENGAMMATABLE_PTR: + return (intptr_t)screengammatable; + case PARM_GET_LINEARGAMMATABLE_PTR: + return (intptr_t)lineargammatable; + } + + return 0; +} + +#if XASH_ENGINE_TESTS +#include "tests.h" + +typedef struct precomputed_gamma_tables_s +{ + float gamma; + float brightness; + float texgamma; + float lightgamma; + byte texgammatable[256]; + int lightgammatable[1024]; + int lineargammatable[1024]; + int screengammatable[1024]; +} precomputed_gamma_tables_t; + +// put at the end of the file, to not confuse Qt Creator's parser +precomputed_gamma_tables_t *Test_GetGammaTables( int i ); + +static void Test_PrecomputedGammaTables( void ) +{ + precomputed_gamma_tables_t *data; + int i = 0; + + while(( data = Test_GetGammaTables( i ))) + { + int j; + + BuildGammaTable( data->gamma, data->brightness, data->texgamma, data->lightgamma ); + + for( j = 0; j < 1024; j++ ) + { + if( j < 256 ) + { + TASSERT_EQi( texgammatable[j], data->texgammatable[j] ); + } + + TASSERT_EQi( lightgammatable[j], data->lightgammatable[j] ); + TASSERT_EQi( lineargammatable[j], data->lineargammatable[j] ); + TASSERT_EQi( screengammatable[j], data->screengammatable[j] ); + } + i++; + } +} + +void Test_RunGamma( void ) +{ + TRUN( Test_PrecomputedGammaTables() ); +} + +precomputed_gamma_tables_t *Test_GetGammaTables( int i ) +{ + static precomputed_gamma_tables_t precomputed_data[] = { + { + .gamma = 2.5, + .brightness = 0.0, + .texgamma = 2.0, + .lightgamma = 2.5, + .texgammatable = { + 0, 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, + 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, 124, 125, 126, 127, 128, 129, 130, 131, 132, 132, 133, 134, 135, 136, 137, 138, + 139, 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, + 186, 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, + 229, 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 + }, + .lightgammatable = { + 0, 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, + 65, 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, + 124, 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, + 176, 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, + 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, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, + 280, 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, + 332, 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, + 384, 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, + 436, 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, + 488, 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, + 540, 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, + 593, 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, + 644, 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, + 696, 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, + 748, 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, + 801, 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, + 852, 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, + 904, 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, + 957, 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, + 1006, 1007, 1008, 1010, 1011, 1011, 1013, 1014, 1015, 1016, 1017, 1018, 1018, 1019, 1020, 1021, 1023 + }, + .lineargammatable = { + 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, 0, 1, 1, 1, 1, 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, 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, + 11, 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, + 25, 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, + 47, 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, + 77, 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, + 113, 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, + 151, 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, + 195, 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, + 247, 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, + 306, 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, + 373, 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, + 449, 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, + 532, 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, + 625, 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, + 726, 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, + 836, 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, + 956, 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 + }, + .screengammatable = { + 0, 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, + 310, 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, + 409, 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, + 482, 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, + 540, 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, + 591, 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, + 636, 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, + 676, 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, + 713, 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, + 748, 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, + 780, 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, + 810, 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, + 839, 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, + 866, 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, + 892, 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, + 917, 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, + 941, 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, + 964, 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, + 987, 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, + 1007, 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 + }, + }, { + .gamma = 2.2, + .brightness = 1.0, + .texgamma = 2.2, + .lightgamma = 2.4, + .texgammatable = { + 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, 65, + 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, + 124, 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, + 176, 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, + 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 + }, + .lightgammatable = { + 0, 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, + 78, 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, + 151, 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, + 223, 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, + 297, 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, + 373, 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, + 417, 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, + 451, 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, + 487, 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, + 527, 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, + 568, 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, + 612, 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, + 658, 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, + 704, 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, + 752, 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, + 801, 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, + 851, 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, + 901, 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, + 953, 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, + 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1023 + }, + .lineargammatable = { + 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, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, + 4, 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, + 17, 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, + 36, 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, + 63, 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, + 99, 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, + 134, 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, + 175, 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, + 222, 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, + 274, 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, + 333, 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, + 399, 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, + 470, 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, + 549, 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, + 633, 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, + 725, 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, + 823, 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, + 928, 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 + }, + .screengammatable = { + 0, 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, + 264, 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, + 361, 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, + 435, 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, + 495, 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, + 548, 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, + 596, 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, + 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, 676, 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, 700, 700, 701, 702, 702, 703, 704, 705, 705, 706, 707, 707, 708, 709, 709, 710, 711, 712, 712, 713, 714, 714, 715, 716, + 716, 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, + 752, 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, + 785, 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, + 817, 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, + 847, 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, + 876, 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, + 904, 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, + 931, 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, + 957, 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, + 982, 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, + 1005, 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 + }, + } + }; + + if( i < 0 || i >= ARRAYSIZE( precomputed_data )) + return NULL; + + return &precomputed_data[i]; +} +#endif diff --git a/engine/common/identification.c b/engine/client/identification.c similarity index 70% rename from engine/common/identification.c rename to engine/client/identification.c index cc5e50ba..d6bd328b 100644 --- a/engine/common/identification.c +++ b/engine/client/identification.c @@ -13,13 +13,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include #include "common.h" #include #if !XASH_WIN32 #include #endif + static char id_md5[33]; -static char id_customid[MAX_STRING]; /* ========================================================== @@ -35,7 +36,7 @@ static bloomfilter_t id; #define bf64_mask ((1U<<6)-1) -bloomfilter_t BloomFilter_Process( const char *buffer, int size ) +static bloomfilter_t BloomFilter_Process( const char *buffer, int size ) { dword crc32; bloomfilter_t value = 0; @@ -55,12 +56,12 @@ bloomfilter_t BloomFilter_Process( const char *buffer, int size ) return value; } -bloomfilter_t BloomFilter_ProcessStr( const char *buffer ) +static bloomfilter_t BloomFilter_ProcessStr( const char *buffer ) { return BloomFilter_Process( buffer, Q_strlen( buffer ) ); } -uint BloomFilter_Weight( bloomfilter_t value ) +static uint BloomFilter_Weight( bloomfilter_t value ) { int weight = 0; @@ -77,7 +78,7 @@ uint BloomFilter_Weight( bloomfilter_t value ) return weight; } -qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str ) +static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str ) { bloomfilter_t value = BloomFilter_ProcessStr( str ); @@ -94,9 +95,9 @@ IDENTIFICATION #define MAXBITS_GEN 30 #define MAXBITS_CHECK MAXBITS_GEN + 6 -qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ); +static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ); -void ID_BloomFilter_f( void ) +static void ID_BloomFilter_f( void ) { bloomfilter_t value = 0; int i; @@ -104,14 +105,14 @@ void ID_BloomFilter_f( void ) for( i = 1; i < Cmd_Argc(); i++ ) value |= BloomFilter_ProcessStr( Cmd_Argv( i ) ); - Msg( "%d %016llX\n", BloomFilter_Weight( value ), value ); + Msg( "%d %016"PRIX64"\n", BloomFilter_Weight( value ), value ); // test // for( i = 1; i < Cmd_Argc(); i++ ) // Msg( "%s: %d\n", Cmd_Argv( i ), BloomFilter_ContainsString( value, Cmd_Argv( i ) ) ); } -qboolean ID_VerifyHEX( const char *hex ) +static qboolean ID_VerifyHEX( const char *hex ) { uint chars = 0; char prev = 0; @@ -153,7 +154,7 @@ qboolean ID_VerifyHEX( const char *hex ) return false; } -void ID_VerifyHEX_f( void ) +static void ID_VerifyHEX_f( void ) { if( ID_VerifyHEX( Cmd_Argv( 1 ) ) ) Msg( "Good\n" ); @@ -162,7 +163,7 @@ void ID_VerifyHEX_f( void ) } #if XASH_LINUX -qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) +static qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) { int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY ); char buffer[1024], *pbuf, *pbuf2; @@ -198,7 +199,7 @@ qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) return true; } -qboolean ID_ValidateNetDevice( const char *dev ) +static qboolean ID_ValidateNetDevice( const char *dev ) { const char *prefix = "/sys/class/net"; byte *pfile; @@ -226,7 +227,7 @@ qboolean ID_ValidateNetDevice( const char *dev ) return true; } -int ID_ProcessNetDevices( bloomfilter_t *value ) +static int ID_ProcessNetDevices( bloomfilter_t *value ) { const char *prefix = "/sys/class/net"; DIR *dir; @@ -250,7 +251,7 @@ int ID_ProcessNetDevices( bloomfilter_t *value ) return count; } -int ID_CheckNetDevices( bloomfilter_t value ) +static int ID_CheckNetDevices( bloomfilter_t value ) { const char *prefix = "/sys/class/net"; @@ -278,19 +279,19 @@ int ID_CheckNetDevices( bloomfilter_t value ) return count; } -void ID_TestCPUInfo_f( void ) +static void ID_TestCPUInfo_f( void ) { bloomfilter_t value = 0; if( ID_ProcessCPUInfo( &value ) ) - Msg( "Got %016llX\n", value ); + Msg( "Got %016"PRIX64"\n", value ); else Msg( "Could not get serial\n" ); } #endif -qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) +static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) { int fd = open( path, O_RDONLY ); char buffer[256]; @@ -317,7 +318,7 @@ qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) } #if !XASH_WIN32 -int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) +static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) { DIR *dir; struct dirent *entry; @@ -337,7 +338,7 @@ int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postf return count; } -int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix ) +static int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix ) { DIR *dir; struct dirent *entry; @@ -360,14 +361,14 @@ int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix return count; } #else -int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) +static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) { HKEY hKey; - if( RegOpenKeyEx( hRootKey, subKey, 0, KEY_QUERY_VALUE, &hKey ) != ERROR_SUCCESS ) + if( RegOpenKeyExA( hRootKey, subKey, 0, KEY_QUERY_VALUE, &hKey ) != ERROR_SUCCESS ) return 0; - if( RegQueryValueEx( hKey, value, NULL, NULL, data, &cbData ) != ERROR_SUCCESS ) + if( RegQueryValueExA( hKey, value, NULL, NULL, data, &cbData ) != ERROR_SUCCESS ) { RegCloseKey( hKey ); return 0; @@ -376,13 +377,14 @@ int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD RegCloseKey( hKey ); return 1; } -int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData) + +static int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData ) { HKEY hKey; - if( RegCreateKey( hRootKey, subKey, &hKey ) != ERROR_SUCCESS ) + if( RegCreateKeyA( hRootKey, subKey, &hKey ) != ERROR_SUCCESS ) return 0; - if( RegSetValueEx( hKey, value, 0, dwType, data, cbData ) != ERROR_SUCCESS ) + if( RegSetValueExA( hKey, value, 0, dwType, data, cbData ) != ERROR_SUCCESS ) { RegCloseKey( hKey ); return 0; @@ -394,7 +396,7 @@ int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYT #define BUFSIZE 4096 -int ID_RunWMIC(char *buffer, const char *cmdline) +static int ID_RunWMIC( char *buffer, const wchar_t *cmdline ) { HANDLE g_IN_Rd = NULL; HANDLE g_IN_Wr = NULL; @@ -402,75 +404,92 @@ int ID_RunWMIC(char *buffer, const char *cmdline) HANDLE g_OUT_Wr = NULL; DWORD dwRead; BOOL bSuccess = FALSE; - SECURITY_ATTRIBUTES saAttr; + wchar_t *cmdline_copy; - STARTUPINFO si = {0}; - - PROCESS_INFORMATION pi = {0}; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; + PROCESS_INFORMATION pi = { 0 }; + SECURITY_ATTRIBUTES saAttr = + { + .nLength = sizeof( SECURITY_ATTRIBUTES ), + .bInheritHandle = TRUE, + .lpSecurityDescriptor = NULL, + }; CreatePipe( &g_IN_Rd, &g_IN_Wr, &saAttr, 0 ); CreatePipe( &g_OUT_Rd, &g_OUT_Wr, &saAttr, 0 ); SetHandleInformation( g_IN_Wr, HANDLE_FLAG_INHERIT, 0 ); - si.cb = sizeof(STARTUPINFO); - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = g_IN_Rd; - si.hStdOutput = g_OUT_Wr; - si.hStdError = g_OUT_Wr; - si.wShowWindow = SW_HIDE; - si.dwFlags |= STARTF_USESTDHANDLES; + STARTUPINFO si = + { + .cb = sizeof( STARTUPINFO ), + .dwFlags = STARTF_USESTDHANDLES, + .hStdInput = g_IN_Rd, + .hStdOutput = g_OUT_Wr, + .hStdError = g_OUT_Wr, + .wShowWindow = SW_HIDE, + .dwFlags = STARTF_USESTDHANDLES, + }; - CreateProcess( NULL, (char*)cmdline, NULL, NULL, true, CREATE_NO_WINDOW , NULL, NULL, &si, &pi ); + cmdline_copy = malloc( wcslen( cmdline ) * sizeof( *cmdline_copy )); - CloseHandle( g_OUT_Wr ); - CloseHandle( g_IN_Wr ); + if( !CreateProcessW( NULL, cmdline_copy, NULL, NULL, true, CREATE_NO_WINDOW, NULL, NULL, &si, &pi )) + goto err; WaitForSingleObject( pi.hProcess, 500 ); bSuccess = ReadFile( g_OUT_Rd, buffer, BUFSIZE, &dwRead, NULL ); buffer[BUFSIZE-1] = 0; + + TerminateProcess( pi.hProcess, 0 ); + + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + +err: + CloseHandle( g_IN_Wr ); + CloseHandle( g_OUT_Wr ); CloseHandle( g_IN_Rd ); CloseHandle( g_OUT_Rd ); + free( cmdline_copy ); + return bSuccess; } -int ID_ProcessWMIC( bloomfilter_t *value, const char *cmdline ) +static int ID_ProcessWMIC( bloomfilter_t *value, const wchar_t *cmdline ) { char buffer[BUFSIZE], token[BUFSIZE], *pbuf; int count = 0; - if( !ID_RunWMIC( buffer, cmdline ) ) + if( !ID_RunWMIC( buffer, cmdline )) return 0; + pbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header - while( pbuf = COM_ParseFile( pbuf, token, sizeof( token ) ) ) + while( pbuf = COM_ParseFile( pbuf, token, sizeof( token ))) { - if( !ID_VerifyHEX( token ) ) + if( !ID_VerifyHEX( token )) continue; *value |= BloomFilter_ProcessStr( token ); - count ++; + count++; } return count; } -int ID_CheckWMIC( bloomfilter_t value, const char *cmdline ) +static int ID_CheckWMIC( bloomfilter_t value, const wchar_t *cmdline ) { char buffer[BUFSIZE], token[BUFSIZE], *pbuf; int count = 0; - if( !ID_RunWMIC( buffer, cmdline ) ) + if( !ID_RunWMIC( buffer, cmdline )) return 0; + pbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header - while( pbuf = COM_ParseFile( pbuf, token, sizeof( token ) ) ) + while( pbuf = COM_ParseFile( pbuf, token, sizeof( token ))) { bloomfilter_t filter; - if( !ID_VerifyHEX( token ) ) + if( !ID_VerifyHEX( token )) continue; filter = BloomFilter_ProcessStr( token ); @@ -486,7 +505,11 @@ int ID_CheckWMIC( bloomfilter_t value, const char *cmdline ) char *IOS_GetUDID( void ); #endif -bloomfilter_t ID_GenerateRawId( void ) +#if XASH_PSVITA +int PSVita_GetPSID( char *buf, const size_t buflen ); +#endif + +static bloomfilter_t ID_GenerateRawId( void ) { bloomfilter_t value = 0; int count = 0; @@ -507,19 +530,27 @@ bloomfilter_t ID_GenerateRawId( void ) count += ID_ProcessNetDevices( &value ); #endif #if XASH_WIN32 - count += ID_ProcessWMIC( &value, "wmic path win32_physicalmedia get SerialNumber " ); - count += ID_ProcessWMIC( &value, "wmic bios get serialnumber " ); + count += ID_ProcessWMIC( &value, L"wmic path win32_physicalmedia get SerialNumber " ); + count += ID_ProcessWMIC( &value, L"wmic bios get serialnumber " ); #endif #if XASH_IOS { value |= BloomFilter_ProcessStr(IOS_GetUDID()); count ++; } +#endif +#if XASH_PSVITA + { + char data[16]; + PSVita_GetPSID( data, sizeof( data )); + value |= BloomFilter_Process( data, sizeof( data )); + count ++; + } #endif return value; } -uint ID_CheckRawId( bloomfilter_t filter ) +static uint ID_CheckRawId( bloomfilter_t filter ) { bloomfilter_t value = 0; int count = 0; @@ -543,8 +574,8 @@ uint ID_CheckRawId( bloomfilter_t filter ) #endif #if XASH_WIN32 - count += ID_CheckWMIC( filter, "wmic path win32_physicalmedia get SerialNumber" ); - count += ID_CheckWMIC( filter, "wmic bios get serialnumber" ); + count += ID_CheckWMIC( filter, L"wmic path win32_physicalmedia get SerialNumber" ); + count += ID_CheckWMIC( filter, L"wmic bios get serialnumber" ); #endif #if XASH_IOS @@ -554,8 +585,17 @@ uint ID_CheckRawId( bloomfilter_t filter ) value = 0; } #endif +#if XASH_PSVITA + { + char data[16]; + PSVita_GetPSID( data, sizeof( data )); + value = BloomFilter_Process( data, sizeof( data )); + count += (filter & value) == value; + value = 0; + } +#endif #if 0 - Msg( "ID_CheckRawId: %d\n", count ); + Msg( "%s: %d\n", __func__, count ); #endif return count; } @@ -563,7 +603,7 @@ uint ID_CheckRawId( bloomfilter_t filter ) #define SYSTEM_XOR_MASK 0x10331c2dce4c91db #define GAME_XOR_MASK 0x7ffc48fbac1711f1 -void ID_Check( void ) +static void ID_Check( void ) { uint weight = BloomFilter_Weight( id ); uint mincount = weight >> 2; @@ -575,7 +615,7 @@ void ID_Check( void ) { id = 0; #if 0 - Msg( "ID_Check(): fail %d\n", weight ); + Msg( "%s: fail %d\n", __func__, weight ); #endif return; } @@ -583,34 +623,18 @@ void ID_Check( void ) if( ID_CheckRawId( id ) < mincount ) id = 0; #if 0 - Msg( "ID_Check(): success %d\n", weight ); + Msg( "%s: success %d\n", __func__, weight ); #endif } const char *ID_GetMD5( void ) { - if( id_customid[0] ) - return id_customid; return id_md5; } -/* -=============== -ID_SetCustomClientID - -=============== -*/ -void GAME_EXPORT ID_SetCustomClientID( const char *id ) -{ - if( !id ) - return; - - Q_strncpy( id_customid, id, sizeof( id_customid ) ); -} - void ID_Init( void ) { - MD5Context_t hash = {0}; + MD5Context_t hash = { 0 }; byte md5[16]; int i; @@ -621,7 +645,7 @@ void ID_Init( void ) #endif #if XASH_ANDROID && !XASH_DEDICATED - sscanf( Android_LoadID(), "%016llX", &id ); + sscanf( Android_LoadID(), "%016"PRIX64, &id ); if( id ) { id ^= SYSTEM_XOR_MASK; @@ -631,9 +655,9 @@ void ID_Init( void ) #elif XASH_WIN32 { CHAR szBuf[MAX_PATH]; - ID_GetKeyData( HKEY_CURRENT_USER, "Software\\Xash3D\\", "xash_id", szBuf, MAX_PATH ); + ID_GetKeyData( HKEY_CURRENT_USER, "Software\\"XASH_ENGINE_NAME"\\", "xash_id", szBuf, MAX_PATH ); - sscanf(szBuf, "%016llX", &id); + sscanf(szBuf, "%016"PRIX64, &id); id ^= SYSTEM_XOR_MASK; ID_Check(); } @@ -649,7 +673,7 @@ void ID_Init( void ) cfg = fopen( va( "%s/.xash_id", home ), "r" ); if( cfg ) { - if( fscanf( cfg, "%016llX", &id ) > 0 ) + if( fscanf( cfg, "%016"PRIX64, &id ) > 0 ) { id ^= SYSTEM_XOR_MASK; ID_Check(); @@ -664,7 +688,7 @@ void ID_Init( void ) const char *buf = (const char*) FS_LoadFile( ".xash_id", NULL, false ); if( buf ) { - sscanf( buf, "%016llX", &id ); + sscanf( buf, "%016"PRIX64, &id ); id ^= GAME_XOR_MASK; ID_Check(); } @@ -677,15 +701,15 @@ void ID_Init( void ) MD5Final( (byte*)md5, &hash ); for( i = 0; i < 16; i++ ) - Q_sprintf( &id_md5[i*2], "%02hhx", md5[i] ); + Q_snprintf( &id_md5[i*2], sizeof( id_md5 ) - i * 2, "%02hhx", md5[i] ); #if XASH_ANDROID && !XASH_DEDICATED - Android_SaveID( va("%016llX", id^SYSTEM_XOR_MASK ) ); + Android_SaveID( va("%016"PRIX64, id^SYSTEM_XOR_MASK ) ); #elif XASH_WIN32 { CHAR Buf[MAX_PATH]; - sprintf( Buf, "%016llX", id^SYSTEM_XOR_MASK ); - ID_SetKeyData( HKEY_CURRENT_USER, "Software\\Xash3D\\", REG_SZ, "xash_id", Buf, Q_strlen(Buf) ); + sprintf( Buf, "%016"PRIX64, id^SYSTEM_XOR_MASK ); + ID_SetKeyData( HKEY_CURRENT_USER, "Software\\"XASH_ENGINE_NAME"\\", REG_SZ, "xash_id", Buf, Q_strlen(Buf) ); } #else { @@ -699,14 +723,14 @@ void ID_Init( void ) cfg = fopen( va( "%s/.xash_id", home ), "w" ); if( cfg ) { - fprintf( cfg, "%016llX", id^SYSTEM_XOR_MASK ); + fprintf( cfg, "%016"PRIX64, id^SYSTEM_XOR_MASK ); fclose( cfg ); } } } #endif - FS_WriteFile( ".xash_id", va("%016llX", id^GAME_XOR_MASK), 16 ); + FS_WriteFile( ".xash_id", va("%016"PRIX64, id^GAME_XOR_MASK), 16 ); #if 0 - Msg("MD5 id: %s\nRAW id:%016llX\n", id_md5, id ); + Msg("MD5 id: %s\nRAW id:%016"PRIX64"\n", id_md5, id ); #endif } diff --git a/engine/client/in_joy.c b/engine/client/in_joy.c index 1ee9443d..e76aff4d 100644 --- a/engine/client/in_joy.c +++ b/engine/client/in_joy.c @@ -43,23 +43,25 @@ static struct joy_axis_s short val; short prevval; } joyaxis[MAX_AXES] = { 0 }; -static byte currentbinding; // add posibility to remap keys, to place it in joykeys[] -convar_t *joy_enable; -static convar_t *joy_pitch; -static convar_t *joy_yaw; -static convar_t *joy_forward; -static convar_t *joy_side; -static convar_t *joy_found; -static convar_t *joy_index; -static convar_t *joy_lt_threshold; -static convar_t *joy_rt_threshold; -static convar_t *joy_side_deadzone; -static convar_t *joy_forward_deadzone; -static convar_t *joy_side_key_threshold; -static convar_t *joy_forward_key_threshold; -static convar_t *joy_pitch_deadzone; -static convar_t *joy_yaw_deadzone; -static convar_t *joy_axis_binding; +static qboolean joy_initialized; + +static CVAR_DEFINE_AUTO( joy_pitch, "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick pitch sensitivity" ); +static CVAR_DEFINE_AUTO( joy_yaw, "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick yaw sensitivity" ); +static CVAR_DEFINE_AUTO( joy_side, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick side sensitivity. Values from -1.0 to 1.0" ); +static CVAR_DEFINE_AUTO( joy_forward, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick forward sensitivity. Values from -1.0 to 1.0" ); +static CVAR_DEFINE_AUTO( joy_lt_threshold, "16384", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "left trigger threshold. Value from 0 to 32767"); +static CVAR_DEFINE_AUTO( joy_rt_threshold, "16384", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "right trigger threshold. Value from 0 to 32767" ); +static CVAR_DEFINE_AUTO( joy_side_key_threshold, "24576", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis key event emit threshold. Value from 0 to 32767" ); +static CVAR_DEFINE_AUTO( joy_forward_key_threshold, "24576", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis key event emit threshold. Value from 0 to 32767"); +static CVAR_DEFINE_AUTO( joy_side_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis deadzone. Value from 0 to 32767" ); +static CVAR_DEFINE_AUTO( joy_forward_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis deadzone. Value from 0 to 32767"); +static CVAR_DEFINE_AUTO( joy_pitch_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "pitch axis deadzone. Value from 0 to 32767"); +static CVAR_DEFINE_AUTO( joy_yaw_deadzone, DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "yaw axis deadzone. Value from 0 to 32767" ); +static CVAR_DEFINE_AUTO( joy_axis_binding, "sfpyrl", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "axis hardware id to engine inner axis binding, " + "s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger" ); +CVAR_DEFINE_AUTO( joy_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable joystick" ); +static CVAR_DEFINE_AUTO( joy_have_gyro, "0", FCVAR_READ_ONLY, "tells whether current active gamepad has gyroscope or not" ); +static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether current active gamepad gyroscope has been calibrated or not" ); /* ============ @@ -68,7 +70,30 @@ Joy_IsActive */ qboolean Joy_IsActive( void ) { - return joy_found->value && joy_enable->value; + return joy_enable.value; +} + +/* +=========== +Joy_SetCapabilities +=========== +*/ +void Joy_SetCapabilities( qboolean have_gyro ) +{ + Cvar_FullSet( joy_have_gyro.name, have_gyro ? "1" : "0", joy_have_gyro.flags ); +} + +/* +=========== +Joy_SetCalibrationState +=========== +*/ +void Joy_SetCalibrationState( joy_calibration_state_t state ) +{ + if( (int)joy_calibrated.value == state ) + return; + + Cvar_FullSet( joy_calibrated.name, va( "%d", state ), joy_calibrated.flags ); } /* @@ -78,7 +103,7 @@ Joy_HatMotionEvent DPad events ============ */ -void Joy_HatMotionEvent( byte hat, byte value ) +static void Joy_HatMotionEvent( int value ) { struct { @@ -93,9 +118,6 @@ void Joy_HatMotionEvent( byte hat, byte value ) }; int i; - if( !joy_found->value ) - return; - for( i = 0; i < ARRAYSIZE( keys ); i++ ) { if( value & keys[i].mask ) @@ -124,14 +146,14 @@ static void Joy_ProcessTrigger( const engineAxis_t engineAxis, short value ) { case JOY_AXIS_RT: trigButton = K_JOY2; - trigThreshold = joy_rt_threshold->value; + trigThreshold = joy_rt_threshold.value; break; case JOY_AXIS_LT: trigButton = K_JOY1; - trigThreshold = joy_lt_threshold->value; + trigThreshold = joy_lt_threshold.value; break; default: - Con_Reportf( S_ERROR "Joy_ProcessTrigger: invalid axis = %i", engineAxis ); + Con_Reportf( S_ERROR "%s: invalid axis = %i\n", __func__, engineAxis ); break; } @@ -158,12 +180,12 @@ static int Joy_GetHatValueForAxis( const engineAxis_t engineAxis ) switch( engineAxis ) { case JOY_AXIS_SIDE: - threshold = joy_side_key_threshold->value; + threshold = joy_side_key_threshold.value; negative = JOY_HAT_LEFT; positive = JOY_HAT_RIGHT; break; case JOY_AXIS_FWD: - threshold = joy_side_key_threshold->value; + threshold = joy_side_key_threshold.value; negative = JOY_HAT_UP; positive = JOY_HAT_DOWN; break; @@ -197,12 +219,12 @@ static void Joy_ProcessStick( const engineAxis_t engineAxis, short value ) switch( engineAxis ) { - case JOY_AXIS_FWD: deadzone = joy_forward_deadzone->value; break; - case JOY_AXIS_SIDE: deadzone = joy_side_deadzone->value; break; - case JOY_AXIS_PITCH: deadzone = joy_pitch_deadzone->value; break; - case JOY_AXIS_YAW: deadzone = joy_yaw_deadzone->value; break; + case JOY_AXIS_FWD: deadzone = joy_forward_deadzone.value; break; + case JOY_AXIS_SIDE: deadzone = joy_side_deadzone.value; break; + case JOY_AXIS_PITCH: deadzone = joy_pitch_deadzone.value; break; + case JOY_AXIS_YAW: deadzone = joy_yaw_deadzone.value; break; default: - Con_Reportf( S_ERROR "Joy_ProcessStick: invalid axis = %i", engineAxis ); + Con_Reportf( S_ERROR "%s: invalid axis = %i\n", __func__, engineAxis ); break; } @@ -215,14 +237,14 @@ static void Joy_ProcessStick( const engineAxis_t engineAxis, short value ) // fwd/side axis simulate hat movement if( ( engineAxis == JOY_AXIS_SIDE || engineAxis == JOY_AXIS_FWD ) && - ( CL_IsInMenu() || CL_IsInConsole() ) ) + ( cls.key_dest == key_menu || cls.key_dest == key_console )) { int val = 0; val |= Joy_GetHatValueForAxis( JOY_AXIS_SIDE ); val |= Joy_GetHatValueForAxis( JOY_AXIS_FWD ); - Joy_HatMotionEvent( 0, val ); + Joy_HatMotionEvent( val ); } } @@ -233,23 +255,9 @@ Joy_AxisMotionEvent Axis events ============= */ -void Joy_AxisMotionEvent( byte axis, short value ) +void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value ) { - if( !joy_found->value ) - return; - - if( axis >= MAX_AXES ) - { - Con_Reportf( "Only 6 axes is supported\n" ); - return; - } - - return Joy_KnownAxisMotionEvent( joyaxesmap[axis], value ); -} - -void Joy_KnownAxisMotionEvent( engineAxis_t engineAxis, short value ) -{ - if( engineAxis == JOY_AXIS_NULL ) + if( engineAxis >= JOY_AXIS_NULL ) return; if( value == joyaxis[engineAxis].val ) @@ -263,66 +271,14 @@ void Joy_KnownAxisMotionEvent( engineAxis_t engineAxis, short value ) /* ============= -Joy_BallMotionEvent +Joy_GyroEvent -Trackball events. UNDONE +Gyroscope events ============= */ -void Joy_BallMotionEvent( byte ball, short xrel, short yrel ) +void Joy_GyroEvent( vec3_t data ) { - //if( !joy_found->value ) - // return; -} -/* -============= -Joy_ButtonEvent - -Button events -============= -*/ -void Joy_ButtonEvent( byte button, byte down ) -{ - if( !joy_found->value ) - return; - - // generic game button code. - if( button > 32 ) - { - int origbutton = button; - button = ( button & 31 ) + K_AUX1; - - Con_Reportf( "Only 32 joybuttons is supported, converting %i button ID to %s\n", origbutton, Key_KeynumToString( button ) ); - } - else button += K_AUX1; - - Key_Event( button, down ); -} - -/* -============= -Joy_RemoveEvent - -Called when joystick is removed. For future expansion -============= -*/ -void Joy_RemoveEvent( void ) -{ - if( joy_found->value ) - Cvar_FullSet( "joy_found", "0", FCVAR_READ_ONLY ); -} - -/* -============= -Joy_RemoveEvent - -Called when joystick is removed. For future expansion -============= -*/ -void Joy_AddEvent( void ) -{ - if( joy_enable->value && !joy_found->value ) - Cvar_FullSet( "joy_found", "1", FCVAR_READ_ONLY ); } /* @@ -337,9 +293,9 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) if( !Joy_IsActive() ) return; - if( FBitSet( joy_axis_binding->flags, FCVAR_CHANGED ) ) + if( FBitSet( joy_axis_binding.flags, FCVAR_CHANGED ) ) { - const char *bind = joy_axis_binding->string; + const char *bind = joy_axis_binding.string; size_t i; for( i = 0; bind[i]; i++ ) @@ -356,19 +312,24 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ) } } - ClearBits( joy_axis_binding->flags, FCVAR_CHANGED ); + ClearBits( joy_axis_binding.flags, FCVAR_CHANGED ); } - *fw -= joy_forward->value * (float)joyaxis[JOY_AXIS_FWD ].val/(float)SHRT_MAX; // must be form -1.0 to 1.0 - *side += joy_side->value * (float)joyaxis[JOY_AXIS_SIDE].val/(float)SHRT_MAX; -#if !defined(XASH_SDL) - *dpitch += joy_pitch->value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; // abs axis rotate is frametime related - *dyaw -= joy_yaw->value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; -#else - // HACKHACK: SDL have inverted look axis. - *dpitch -= joy_pitch->value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; - *dyaw += joy_yaw->value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; -#endif + *fw -= joy_forward.value * (float)joyaxis[JOY_AXIS_FWD ].val/(float)SHRT_MAX; // must be form -1.0 to 1.0 + *side += joy_side.value * (float)joyaxis[JOY_AXIS_SIDE].val/(float)SHRT_MAX; + *dpitch -= joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; + *dyaw += joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime; +} + +static void Joy_CalibrateGyro_f( void ) +{ + if( !joy_have_gyro.value ) + { + Con_Printf( "Current active gamepad doesn't have gyroscope\n" ); + return; + } + + Platform_CalibrateGamepadGyro(); } /* @@ -380,40 +341,43 @@ Main init procedure */ void Joy_Init( void ) { - joy_pitch = Cvar_Get( "joy_pitch", "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick pitch sensitivity" ); - joy_yaw = Cvar_Get( "joy_yaw", "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick yaw sensitivity" ); - joy_side = Cvar_Get( "joy_side", "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick side sensitivity. Values from -1.0 to 1.0" ); - joy_forward = Cvar_Get( "joy_forward", "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick forward sensitivity. Values from -1.0 to 1.0" ); + Cmd_AddRestrictedCommand( "joy_calibrate_gyro", Joy_CalibrateGyro_f, "calibrate gamepad gyroscope. You must to put gamepad on stationary surface" ); - joy_lt_threshold = Cvar_Get( "joy_lt_threshold", "16384", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "left trigger threshold. Value from 0 to 32767"); - joy_rt_threshold = Cvar_Get( "joy_rt_threshold", "16384", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "right trigger threshold. Value from 0 to 32767" ); + Cvar_RegisterVariable( &joy_pitch ); + Cvar_RegisterVariable( &joy_yaw ); + Cvar_RegisterVariable( &joy_side ); + Cvar_RegisterVariable( &joy_forward ); + + Cvar_RegisterVariable( &joy_lt_threshold ); + Cvar_RegisterVariable( &joy_rt_threshold ); // emit a key event at 75% axis move - joy_side_key_threshold = Cvar_Get( "joy_side_key_threshold", "24576", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis key event emit threshold. Value from 0 to 32767" ); - joy_forward_key_threshold = Cvar_Get( "joy_forward_key_threshold", "24576", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis key event emit threshold. Value from 0 to 32767"); + Cvar_RegisterVariable( &joy_side_key_threshold ); + Cvar_RegisterVariable( &joy_forward_key_threshold ); // by default, we rely on deadzone detection come from system, but some glitchy devices report false deadzones - joy_side_deadzone = Cvar_Get( "joy_side_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis deadzone. Value from 0 to 32767" ); - joy_forward_deadzone = Cvar_Get( "joy_forward_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis deadzone. Value from 0 to 32767"); - joy_pitch_deadzone = Cvar_Get( "joy_pitch_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "pitch axis deadzone. Value from 0 to 32767"); - joy_yaw_deadzone = Cvar_Get( "joy_yaw_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "yaw axis deadzone. Value from 0 to 32767" ); + Cvar_RegisterVariable( &joy_side_deadzone ); + Cvar_RegisterVariable( &joy_forward_deadzone ); + Cvar_RegisterVariable( &joy_pitch_deadzone ); + Cvar_RegisterVariable( &joy_yaw_deadzone ); - joy_axis_binding = Cvar_Get( "joy_axis_binding", "sfpyrl", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "axis hardware id to engine inner axis binding, " - "s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger" ); - joy_found = Cvar_Get( "joy_found", "0", FCVAR_READ_ONLY, "is joystick is connected" ); - // we doesn't loaded config.cfg yet, so this cvar is not archive. - // change by +set joy_index in cmdline - joy_index = Cvar_Get( "joy_index", "0", FCVAR_READ_ONLY, "current active joystick" ); + Cvar_RegisterVariable( &joy_axis_binding ); - joy_enable = Cvar_Get( "joy_enable", "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable joystick" ); + Cvar_RegisterVariable( &joy_have_gyro ); + Cvar_RegisterVariable( &joy_calibrated ); + Cvar_RegisterVariable( &joy_enable ); - if( Sys_CheckParm( "-nojoy" )) + // renamed from -nojoy to -noenginejoy to not conflict with + // client.dll's joystick support + if( Sys_CheckParm( "-noenginejoy" )) { Cvar_FullSet( "joy_enable", "0", FCVAR_READ_ONLY ); return; } - Cvar_FullSet( "joy_found", va( "%d", Platform_JoyInit( joy_index->value )), FCVAR_READ_ONLY ); + Platform_JoyInit(); + + joy_initialized = true; } /* @@ -425,5 +389,5 @@ Shutdown joystick code */ void Joy_Shutdown( void ) { - Cvar_FullSet( "joy_found", 0, FCVAR_READ_ONLY ); + Platform_JoyShutdown(); } diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 50e7b6a4..5f74c471 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -45,16 +45,14 @@ typedef enum typedef struct touch_button_s { - // Touch button type: tap, stick or slider touchButtonType type; - // Field of button + // button coordinates float x1, y1, x2, y2; - // Button texture - int texture; + int gl_texturenum; rgba_t color; - char texturefile[256]; + char texture[256]; char command[256]; char name[32]; int finger; @@ -72,7 +70,7 @@ typedef struct touch_button_s typedef struct touchdefaultbutton_s { char name[32]; - char texturefile[256]; + char texture[256]; char command[256]; float x1, y1, x2, y2; rgba_t color; @@ -87,7 +85,7 @@ typedef struct touchbuttonlist_s touch_button_t *last; } touchbuttonlist_t; -struct touch_s +static struct touch_s { qboolean initialized; qboolean config_loaded; @@ -132,64 +130,92 @@ struct touch_s int whitetexture; int joytexture; // touch indicator qboolean configchanged; + float actual_aspect_ratio; // maximum aspect ratio from launch, or aspect ratio when entering editor + float config_aspect_ratio; // aspect ratio set by command from config or after entering editor } touch; // private to the engine flags #define TOUCH_FL_UNPRIVILEGED BIT( 10 ) -touchdefaultbutton_t g_DefaultButtons[256]; -int g_LastDefaultButton; +static touchdefaultbutton_t *g_DefaultButtons; +static size_t g_DefaultButtonsLength; -convar_t *touch_pitch; -convar_t *touch_yaw; -convar_t *touch_forwardzone; -convar_t *touch_sidezone; -convar_t *touch_nonlinear_look; -convar_t *touch_pow_mult; -convar_t *touch_pow_factor; -convar_t *touch_exp_mult; -convar_t *touch_grid_enable; -convar_t *touch_grid_count; -convar_t *touch_config_file; -convar_t *touch_enable; -convar_t *touch_in_menu; -convar_t *touch_joy_radius; -convar_t *touch_dpad_radius; -convar_t *touch_move_indicator; -convar_t *touch_highlight_r; -convar_t *touch_highlight_g; -convar_t *touch_highlight_b; -convar_t *touch_highlight_a; -convar_t *touch_precise_amount; -convar_t *touch_joy_texture; -convar_t *touch_emulate; +static CVAR_DEFINE_AUTO( touch_in_menu, "0", FCVAR_PRIVILEGED, "draw touch in menu (for internal use only)" ); +static CVAR_DEFINE_AUTO( touch_forwardzone, "0.06", FCVAR_FILTERABLE, "forward touch zone" ); +static CVAR_DEFINE_AUTO( touch_sidezone, "0.06", FCVAR_FILTERABLE, "side touch zone" ); +static CVAR_DEFINE_AUTO( touch_pitch, "90", FCVAR_FILTERABLE, "touch pitch sensitivity" ); +static CVAR_DEFINE_AUTO( touch_yaw, "120", FCVAR_FILTERABLE, "touch yaw sensitivity" ); +static CVAR_DEFINE_AUTO( touch_nonlinear_look, "0", FCVAR_FILTERABLE, "enable nonlinear touch look" ); +static CVAR_DEFINE_AUTO( touch_pow_factor, "1.3", FCVAR_FILTERABLE, "set > 1 to enable" ); +static CVAR_DEFINE_AUTO( touch_pow_mult, "400.0", FCVAR_FILTERABLE, "power multiplier, usually 200-1000" ); +static CVAR_DEFINE_AUTO( touch_exp_mult, "0", FCVAR_FILTERABLE, "exponent multiplier, usually 20-200, 0 to disable" ); +static CVAR_DEFINE_AUTO( touch_grid_count, "50", FCVAR_FILTERABLE, "touch grid count" ); +static CVAR_DEFINE_AUTO( touch_grid_enable, "1", FCVAR_FILTERABLE, "enable touch grid" ); +static CVAR_DEFINE_AUTO( touch_config_file, "touch.cfg", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "current touch profile file" ); +static CVAR_DEFINE_AUTO( touch_precise_amount, "0.5", FCVAR_FILTERABLE, "sensitivity multiplier for precise-look" ); +static CVAR_DEFINE_AUTO( touch_highlight_r, "1.0", 0, "highlight r color" ); +static CVAR_DEFINE_AUTO( touch_highlight_g, "1.0", 0, "highlight g color" ); +static CVAR_DEFINE_AUTO( touch_highlight_b, "1.0", 0, "highlight b color" ); +static CVAR_DEFINE_AUTO( touch_highlight_a, "1.0", 0, "highlight alpha" ); +static CVAR_DEFINE_AUTO( touch_dpad_radius, "1.0", FCVAR_FILTERABLE, "dpad radius multiplier" ); +static CVAR_DEFINE_AUTO( touch_joy_radius, "1.0", FCVAR_FILTERABLE, "joy radius multiplier" ); +static CVAR_DEFINE_AUTO( touch_move_indicator, "0.0", FCVAR_FILTERABLE, "indicate move events (0 to disable)" ); +static CVAR_DEFINE_AUTO( touch_joy_texture, "touch_default/joy", FCVAR_FILTERABLE, "texture for move indicator"); +static CVAR_DEFINE( touch_emulate, "_touch_emulate", "0", FCVAR_PRIVILEGED, "emulate touch with mouse" ); +CVAR_DEFINE_AUTO( touch_enable, DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable touch controls" ); // code looks smaller with it -#define B(x) (button->x) -#define SCR_W ((float)refState.width) -#define SCR_H ((float)refState.height) -#define TO_SCRN_Y(x) (refState.height * (x)) +#define TO_SCRN_Y(x) (refState.width * (x) * Touch_AspectRatio()) #define TO_SCRN_X(x) (refState.width * (x)) static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ); static void IN_TouchEditClear( void ); static void Touch_InitConfig( void ); +void Touch_NotifyResize( void ) +{ + if( refState.width && refState.height && ( !touch.configchanged || !touch.actual_aspect_ratio )) + { + float aspect_ratio = (float)refState.height / refState.width; + if( aspect_ratio < 0.99 && aspect_ratio > touch.actual_aspect_ratio ) + touch.actual_aspect_ratio = aspect_ratio; + } +} + +static inline float Touch_AspectRatio( void ) +{ + if( touch.config_aspect_ratio ) + return touch.config_aspect_ratio; + + if( touch.actual_aspect_ratio ) + return touch.actual_aspect_ratio; + + if( refState.width && refState.height ) + return (float)refState.height / refState.width; + + return 9.0f / 16.0f; +} + +static void Touch_ConfigAspectRatio_f( void ) +{ + touch.config_aspect_ratio = Q_atof( Cmd_Argv( 1 )); +} + + /* ========================== Touch_ExportButtonToConfig writes button data to config -returns 0 on success, non-zero on error ========================== */ -static inline int Touch_ExportButtonToConfig( file_t *f, touch_button_t *button, qboolean keepAspect ) +static void Touch_ExportButtonToConfig( file_t *f, const touch_button_t *button, qboolean keepAspect ) { string newCommand; int flags = button->flags; if( FBitSet( flags, TOUCH_FL_CLIENT )) - return 1; // skip temporary buttons + return; // skip temporary buttons if( FBitSet( flags, TOUCH_FL_DEF_SHOW )) ClearBits( flags, TOUCH_FL_HIDE ); @@ -197,21 +223,86 @@ static inline int Touch_ExportButtonToConfig( file_t *f, touch_button_t *button, if( FBitSet( flags, TOUCH_FL_DEF_HIDE )) SetBits( flags, TOUCH_FL_HIDE ); - Cmd_Escape( newCommand, B( command ), sizeof( newCommand )); + Cmd_Escape( newCommand, button->command, sizeof( newCommand )); - FS_Printf( f, "touch_addbutton \"%s\" \"%s\" \"%s\" %f %f %f %f %d %d %d %d %d", - B(name), B(texturefile), newCommand, - B(x1), B(y1), B(x2), B(y2), - B(color[0]), B(color[1]), B(color[2]), B(color[3]), flags ); + FS_Printf( f, "touch_addbutton \"%s\" \"%s\" \"%s\" %g %g %g %g %d %d %d %d %d", + button->name, button->texture, newCommand, + button->x1, button->y1, button->x2, button->y2, + button->color[0], button->color[1], button->color[2], button->color[3], + flags ); if( keepAspect ) { - float aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); - FS_Printf( f, " %f\n", aspect ); + float aspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( )); + FS_Printf( f, " %g\n", aspect ); } else FS_Printf( f, "\n" ); +} - return 0; +/* +================= +Touch_DumpConfig + +Dump config to file +================= +*/ +static qboolean Touch_DumpConfig( const char *name, const char *profilename ) +{ + file_t *f; + const touch_button_t *button; + + f = FS_Open( name, "w", true ); + + if( !f ) + { + Con_Printf( S_ERROR "Couldn't write %s.\n", name ); + return false; + } + + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + FS_Printf( f, "//\t\t\ttouchscreen config\n" ); + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename ); + FS_Printf( f, "\n// touch cvars\n" ); + FS_Printf( f, "\n// sensitivity settings\n" ); + FS_Printf( f, "touch_pitch \"%g\"\n", touch_pitch.value ); + FS_Printf( f, "touch_yaw \"%g\"\n", touch_yaw.value ); + FS_Printf( f, "touch_forwardzone \"%g\"\n", touch_forwardzone.value ); + FS_Printf( f, "touch_sidezone \"%g\"\n", touch_sidezone.value ); + FS_Printf( f, "touch_nonlinear_look \"%d\"\n", touch_nonlinear_look.value ? 1 : 0 ); + FS_Printf( f, "touch_pow_factor \"%g\"\n", touch_pow_factor.value ); + FS_Printf( f, "touch_pow_mult \"%g\"\n", touch_pow_mult.value ); + FS_Printf( f, "touch_exp_mult \"%g\"\n", touch_exp_mult.value ); + FS_Printf( f, "\n// grid settings\n" ); + FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count.value ); + FS_Printf( f, "touch_grid_enable \"%d\"\n", touch_grid_enable.value ? 1 : 0 ); + FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); + FS_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] ); + FS_Printf( f, "\n// highlight when pressed\n" ); + FS_Printf( f, "touch_highlight_r \"%g\"\n", touch_highlight_r.value ); + FS_Printf( f, "touch_highlight_g \"%g\"\n", touch_highlight_g.value ); + FS_Printf( f, "touch_highlight_b \"%g\"\n", touch_highlight_b.value ); + FS_Printf( f, "touch_highlight_a \"%g\"\n", touch_highlight_a.value ); + FS_Printf( f, "\n// _joy and _dpad options\n" ); + FS_Printf( f, "touch_dpad_radius \"%g\"\n", touch_dpad_radius.value ); + FS_Printf( f, "touch_joy_radius \"%g\"\n", touch_joy_radius.value ); + FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); + FS_Printf( f, "touch_precise_amount \"%g\"\n", touch_precise_amount.value ); + FS_Printf( f, "\n// enable/disable move indicator\n" ); + FS_Printf( f, "touch_move_indicator \"%g\"\n", touch_move_indicator.value ); + + FS_Printf( f, "\n// reset menu state when execing config\n" ); + FS_Printf( f, "touch_setclientonly 0\n" ); + FS_Printf( f, "\n// touch buttons\n" ); + FS_Printf( f, "touch_removeall\n" ); + FS_Printf( f, "touch_aspectratio %g\n", Touch_AspectRatio()); + + for( button = touch.list_user.first; button; button = button->next ) + Touch_ExportButtonToConfig( f, button, false ); + + FS_Close( f ); + return true; } /* @@ -223,7 +314,6 @@ save current touch configuration */ void Touch_WriteConfig( void ) { - file_t *f; string newconfigfile, oldconfigfile; if( !touch.list_user.first ) @@ -232,66 +322,19 @@ void Touch_WriteConfig( void ) if( Sys_CheckParm( "-nowriteconfig" ) || !touch.configchanged || !touch.config_loaded ) return; - Con_DPrintf( "Touch_WriteConfig(): %s\n", touch_config_file->string ); + Con_DPrintf( "%s: %s\n", __func__, touch_config_file.string ); - Q_snprintf( newconfigfile, sizeof( newconfigfile ), "%s.new", touch_config_file->string ); - Q_snprintf( oldconfigfile, sizeof( oldconfigfile ), "%s.bak", touch_config_file->string ); + Q_snprintf( newconfigfile, sizeof( newconfigfile ), "%s.new", touch_config_file.string ); + Q_snprintf( oldconfigfile, sizeof( oldconfigfile ), "%s.bak", touch_config_file.string ); - f = FS_Open( newconfigfile, "w", true ); - if( f ) + if( Touch_DumpConfig( newconfigfile, touch_config_file.string )) { - touch_button_t *button; - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t\ttouchscreen config\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\ntouch_config_file \"%s\"\n", touch_config_file->string ); - FS_Printf( f, "\n// touch cvars\n" ); - FS_Printf( f, "\n// sensitivity settings\n" ); - FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); - FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); - FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); - FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); - FS_Printf( f, "touch_nonlinear_look \"%d\"\n", CVAR_TO_BOOL(touch_nonlinear_look)); - FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); - FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); - FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); - FS_Printf( f, "\n// grid settings\n" ); - FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count->value ); - FS_Printf( f, "touch_grid_enable \"%d\"\n", CVAR_TO_BOOL(touch_grid_enable)); - FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); - FS_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] ); - FS_Printf( f, "\n// highlight when pressed\n" ); - FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); - FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); - FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); - FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); - FS_Printf( f, "\n// _joy and _dpad options\n" ); - FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); - FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); - FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); - FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); - FS_Printf( f, "\n// enable/disable move indicator\n" ); - FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator->value ); - - FS_Printf( f, "\n// reset menu state when execing config\n" ); - FS_Printf( f, "touch_setclientonly 0\n" ); - FS_Printf( f, "\n// touch buttons\n" ); - FS_Printf( f, "touch_removeall\n" ); - - for( button = touch.list_user.first; button; button = button->next ) - { - Touch_ExportButtonToConfig( f, button, false ); - } - - FS_Close( f ); - FS_Delete( oldconfigfile ); - FS_Rename( touch_config_file->string, oldconfigfile ); - FS_Delete( touch_config_file->string ); - FS_Rename( newconfigfile, touch_config_file->string ); + FS_Rename( touch_config_file.string, oldconfigfile ); + + FS_Delete( touch_config_file.string ); + FS_Rename( newconfigfile, touch_config_file.string ); } - else Con_Printf( S_ERROR "Couldn't write %s.\n", touch_config_file->string ); } /* @@ -303,8 +346,8 @@ export current touch configuration into profile */ static void Touch_ExportConfig_f( void ) { - file_t *f; const char *name; + string profilename; if( Cmd_Argc() != 2 ) { @@ -312,69 +355,25 @@ static void Touch_ExportConfig_f( void ) return; } - if( !touch.list_user.first ) return; + if( !touch.list_user.first ) + { + Con_Printf( "%s: nothing to export\n", __func__ ); + return; + } name = Cmd_Argv( 1 ); - Con_Reportf( "Exporting config to %s\n", name ); - f = FS_Open( name, "w", true ); - if( f ) + if( Q_strstr( name, "touch_presets/" )) { - string profilename, profilebase; - touch_button_t *button; + string profilebase; - if( Q_strstr( name, "touch_presets/" ) ) - { - COM_FileBase( name, profilebase ); - Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase ); - } - else Q_strncpy( profilename, name, sizeof( profilename )); - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t\ttouchscreen preset\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename ); - FS_Printf( f, "\n// touch cvars\n" ); - FS_Printf( f, "\n// sensitivity settings\n" ); - FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch->value ); - FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw->value ); - FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone->value ); - FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone->value ); - FS_Printf( f, "touch_nonlinear_look \"%d\"\n", CVAR_TO_BOOL(touch_nonlinear_look) ); - FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor->value ); - FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult->value ); - FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult->value ); - FS_Printf( f, "\n// grid settings\n" ); - FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count->value ); - FS_Printf( f, "touch_grid_enable \"%d\"\n", CVAR_TO_BOOL(touch_grid_enable) ); - FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" ); - FS_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] ); - FS_Printf( f, "\n// highlight when pressed\n" ); - FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r->value ); - FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g->value ); - FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b->value ); - FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a->value ); - FS_Printf( f, "\n// _joy and _dpad options\n" ); - FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius->value ); - FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius->value ); - FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" ); - FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount->value ); - FS_Printf( f, "\n// enable/disable move indicator\n" ); - FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator->value ); - - FS_Printf( f, "\n// reset menu state when execing config\n" ); - FS_Printf( f, "touch_setclientonly 0\n" ); - FS_Printf( f, "\n// touch buttons\n" ); - FS_Printf( f, "touch_removeall\n" ); - for( button = touch.list_user.first; button; button = button->next ) - { - Touch_ExportButtonToConfig( f, button, true ); - } - FS_Printf( f, "\n// round button coordinates to grid\n" ); - FS_Printf( f, "touch_roundall\n" ); - FS_Close( f ); + COM_FileBase( name, profilebase, sizeof( profilebase )); + Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase ); } - else Con_Printf( S_ERROR "Couldn't write %s.\n", name ); + else Q_strncpy( profilename, name, sizeof( profilename )); + + Con_Reportf( "Exporting config to \"%s\", profile name \"%s\"\n", name, profilename ); + Touch_DumpConfig( name, profilename ); } /* @@ -386,15 +385,20 @@ export current touch configuration into C code */ static void Touch_GenerateCode_f( void ) { - touch_button_t *button; - rgba_t c = {0,0,0,0}; + const touch_button_t *button; + rgba_t c = { 0 }; - if( !touch.list_user.first ) return; + if( !touch.list_user.first ) + { + Con_Printf( "%s: nothing to export\n", __func__ ); + return; + } for( button = touch.list_user.first; button; button = button->next ) { float aspect; int flags = button->flags; + int round; if( FBitSet( flags, TOUCH_FL_CLIENT )) continue; // skip temporary buttons @@ -405,15 +409,29 @@ static void Touch_GenerateCode_f( void ) if( FBitSet( flags, TOUCH_FL_DEF_HIDE )) SetBits( flags, TOUCH_FL_HIDE ); - aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); - if( memcmp( &c, &B(color), sizeof( rgba_t ) ) ) + aspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( )); + + if( memcmp( c, button->color, sizeof( c ))) { - Con_Printf( "unsigned char color[] = { %d, %d, %d, %d };\n", B(color[0]), B(color[1]), B(color[2]), B(color[3]) ); - memcpy( &c, &B(color), sizeof( rgba_t ) ); + Con_Printf( "unsigned char color[] = { %d, %d, %d, %d };\n", + button->color[0], button->color[1], button->color[2], button->color[3] ); + memcpy( c, button->color, sizeof( c )); } - Con_Printf( "TOUCH_ADDDEFAULT( \"%s\", \"%s\", \"%s\", %f, %f, %f, %f, color, %d, %f, %d );\n", - B(name), B(texturefile), B(command), - B(x1), B(y1), B(x2), B(y2), (B(type) == touch_command)?(fabs( aspect - 1.0f) < 0.0001)?2:1:0, aspect, flags ); + + if( button->type == touch_command ) + { + if( fabs( aspect - 1.0f ) < 0.001 ) + round = round_aspect; + else + round = round_grid; + } + else + round = round_none; + + Con_Printf( "TOUCH_ADDDEFAULT( \"%s\", \"%s\", \"%s\", %gf, %gf, %gf, %gf, color, %d, %g, %d );\n", + button->name, button->texture, button->command, + button->x1, button->y1, button->x2, button->y2, + round, aspect, flags ); } } @@ -421,29 +439,31 @@ static void Touch_RoundAll_f( void ) { touch_button_t *button; - if( !touch_grid_enable->value ) + if( !touch_grid_enable.value ) return; for( button = touch.list_user.first; button; button = button->next ) - IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) ); + IN_TouchCheckCoords( &button->x1, &button->y1, &button->x2, &button->y2 ); } static void Touch_ListButtons_f( void ) { touch_button_t *button; + Touch_InitConfig(); for( button = touch.list_user.first; button; button = button->next ) { - Con_Printf( "%s %s %s %f %f %f %f %d %d %d %d %d\n", - B(name), B(texturefile), B(command), - B(x1), B(y1), B(x2), B(y2), - B(color[0]), B(color[1]), B(color[2]), B(color[3]), B(flags) ); + Con_Printf( "%s %s %s %g %g %g %g %d %d %d %d %d\n", + button->name, button->texture, button->command, + button->x1, button->y1, button->x2, button->y2, + button->color[0], button->color[1], button->color[2], button->color[3], + button->flags ); - if( B(flags) & TOUCH_FL_CLIENT) + if( FBitSet( button->flags, TOUCH_FL_CLIENT )) continue; - UI_AddTouchButtonToList( B(name), B(texturefile), B(command),B(color), B(flags) ); + UI_AddTouchButtonToList( button->name, button->texture, button->command, button->color, button->flags ); } touch.configchanged = true; } @@ -460,55 +480,72 @@ static void Touch_Stroke_f( void ) MakeRGBA( touch.scolor, Q_atoi( Cmd_Argv( 2 ) ), Q_atoi( Cmd_Argv( 3 ) ), Q_atoi( Cmd_Argv( 4 ) ), Q_atoi( Cmd_Argv( 5 ) ) ); } -static touch_button_t *Touch_FindButton( touchbuttonlist_t *list, const char *name, qboolean privileged ) +static touch_button_t *Touch_FindNextNoPattern( touch_button_t *buttons, const char *name, qboolean privileged ) { - touch_button_t *button; + touch_button_t *b; - for( button = list->first; button; button = button->next ) + for( b = buttons; b; b = b->next ) { - if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) + if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED )) continue; - if( Q_strncmp( button->name, name, sizeof( button->name ))) - continue; - - return button; + if( !Q_strncmp( b->name, name, sizeof( b->name ))) + return b; } return NULL; } -static touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *name, qboolean privileged ) +static touch_button_t *Touch_FindButtonNoPattern( touchbuttonlist_t *list, const char *name, qboolean privileged ) { - touch_button_t *button; + return Touch_FindNextNoPattern( list->first, name, privileged ); +} - for( button = list->first; button; button = button->next ) +static touch_button_t *Touch_FindNext( touch_button_t *buttons, const char *name, qboolean privileged ) +{ + touch_button_t *b; + qboolean has_pattern = Q_strchr( name, '*' ) != NULL; + + if( !has_pattern ) + return Touch_FindNextNoPattern( buttons, name, privileged ); + + for( b = buttons; b; b = b->next ) { - if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) + if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED )) continue; - if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name ))) - { - return button; - } + if( Q_stricmpext( name, b->name )) + return b; } + return NULL; } + +static touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *name, qboolean privileged ) +{ + return Touch_FindNext( list->first, name, privileged ); +} + void Touch_SetClientOnly( byte state ) { - touch.clientonly = state; - host.mouse_visible = state; + // TODO: fix clash with vgui cursors + if( touch.clientonly == state ) + return; - touch.move_finger = touch.look_finger = -1; + touch.clientonly = state; + + touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; touch.forward = touch.side = 0; if( state ) { + Platform_SetCursorType( dc_arrow ); IN_DeactivateMouse(); } else { + Platform_SetCursorType( dc_none ); IN_ActivateMouse(); } } @@ -544,7 +581,6 @@ static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *nam Mem_Free( button ); } - } void Touch_RemoveButton( const char *name, qboolean privileged ) @@ -560,7 +596,7 @@ static void IN_TouchRemoveButton_f( void ) return; } - Touch_RemoveButton( Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged()); + Touch_RemoveButton( Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged( )); } static void Touch_ClearList( touchbuttonlist_t *list ) @@ -578,28 +614,26 @@ static void Touch_RemoveAll_f( void ) { IN_TouchEditClear(); Touch_ClearList( &touch.list_user ); + touch.config_aspect_ratio = 0.0f; } -static void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color ) +static void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color, qboolean privileged ) { - touch_button_t *button; + touch_button_t *b; - for( button = list->first; button; button = button->next ) - { - if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name ))) - MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); - } + for( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged )) + Vector4Copy( color, b->color ); } static void Touch_SetTexture( touchbuttonlist_t *list, const char *name, const char *texture, qboolean privileged ) { - touch_button_t *button = Touch_FindButton( list, name, privileged ); + touch_button_t *button = Touch_FindButtonNoPattern( list, name, privileged ); if( !button ) return; - button->texture = -1; // mark for texture load - Q_strncpy( button->texturefile, texture, sizeof( button->texturefile )); + button->gl_texturenum = -1; // mark for texture load + Q_strncpy( button->texture, texture, sizeof( button->texture )); } static void Touch_SetCommand( touch_button_t *button, const char *command ) @@ -614,26 +648,22 @@ static void Touch_SetCommand( touch_button_t *button, const char *command ) button->type = touch_joy; else if( !Q_strcmp( command, "_dpad" )) button->type = touch_dpad; - else if( Q_stricmpext( "_wheel *", command ) || Q_stricmpext( "_hwheel *", command )) + else if( !Q_strncmp( "_wheel ", command, 7 ) || !Q_strncmp( "_hwheel ", command, 8 )) button->type = touch_wheel; + else + button->type = touch_command; } void Touch_HideButtons( const char *name, byte hide, qboolean privileged ) { - touch_button_t *button; + touch_button_t *b; - for( button = touch.list_user.first; button; button = button->next) + for( b = Touch_FindFirst( &touch.list_user, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged )) { - if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) - continue; - - if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name ))) - { - if( hide ) - SetBits( button->flags, TOUCH_FL_HIDE ); - else - ClearBits( button->flags, TOUCH_FL_HIDE ); - } + if( hide ) + SetBits( b->flags, TOUCH_FL_HIDE ); + else + ClearBits( b->flags, TOUCH_FL_HIDE ); } } @@ -651,7 +681,7 @@ static void Touch_Hide_f( void ) return; } - Touch_HideButtons( Cmd_Argv( 1 ), true, Cmd_CurrentCommandIsPrivileged() ); + Touch_HideButtons( Cmd_Argv( 1 ), true, Cmd_CurrentCommandIsPrivileged( )); } static void Touch_Show_f( void ) @@ -662,24 +692,19 @@ static void Touch_Show_f( void ) return; } - Touch_HideButtons( Cmd_Argv( 1 ), false, Cmd_CurrentCommandIsPrivileged() ); + Touch_HideButtons( Cmd_Argv( 1 ), false, Cmd_CurrentCommandIsPrivileged( )); } static void Touch_FadeButtons( touchbuttonlist_t *list, const char *name, float speed, float end, float start, qboolean privileged ) { - touch_button_t *button; - for( button = list->first; button; button = button->next) - { - if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) - continue; + touch_button_t *b; - if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name ))) - { - if( start >= 0 ) - button->fade = start; - button->fadespeed = speed; - button->fadeend = end; - } + for( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged )) + { + if( start >= 0 ) + b->fade = start; + b->fadespeed = speed; + b->fadeend = end; } } @@ -698,29 +723,24 @@ static void Touch_Fade_f( void ) } Touch_FadeButtons( &touch.list_user,Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), Q_atof( Cmd_Argv( 3 )), - start, Cmd_CurrentCommandIsPrivileged() ); + start, Cmd_CurrentCommandIsPrivileged( )); } static void Touch_SetColor_f( void ) { - rgba_t color; if( Cmd_Argc() == 6 ) { - MakeRGBA( color, Q_atoi( Cmd_Argv(2) ), Q_atoi( Cmd_Argv(3) ), Q_atoi( Cmd_Argv(4) ), Q_atoi( Cmd_Argv(5) ) ); - Touch_SetColor( &touch.list_user, Cmd_Argv(1), color ); - return; + rgba_t color = { Q_atoi( Cmd_Argv( 2 )), Q_atoi( Cmd_Argv( 3 )), Q_atoi( Cmd_Argv( 4 )), Q_atoi( Cmd_Argv( 5 )) }; + Touch_SetColor( &touch.list_user, Cmd_Argv( 1 ), color, Cmd_CurrentCommandIsPrivileged( )); } - Con_Printf( S_USAGE "touch_setcolor
\n" ); + else Con_Printf( S_USAGE "touch_setcolor \n" ); } static void Touch_SetTexture_f( void ) { if( Cmd_Argc() == 3 ) - { - Touch_SetTexture( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ), Cmd_CurrentCommandIsPrivileged() ); - return; - } - Con_Printf( S_USAGE "touch_settexture \n" ); + Touch_SetTexture( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ), Cmd_CurrentCommandIsPrivileged( )); + else Con_Printf( S_USAGE "touch_settexture \n" ); } static void Touch_SetFlags_f( void ) @@ -728,79 +748,88 @@ static void Touch_SetFlags_f( void ) if( Cmd_Argc() == 3 ) { qboolean privileged = Cmd_CurrentCommandIsPrivileged(); + touch_button_t *button = Touch_FindButtonNoPattern( &touch.list_user, Cmd_Argv( 1 ), privileged ); - touch_button_t *button = Touch_FindButton( &touch.list_user, Cmd_Argv( 1 ), privileged ); if( button ) - { button->flags = ( privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT ) | Q_atoi( Cmd_Argv( 2 )); - } - return; + else + Con_Printf( S_ERROR "no such button" ); } - Con_Printf( S_USAGE "touch_setflags \n" ); + else Con_Printf( S_USAGE "touch_setflags \n" ); } static void Touch_SetCommand_f( void ) { if( Cmd_Argc() == 3 ) { - touch_button_t *button = Touch_FindButton( &touch.list_user, Cmd_Argv(1), Cmd_CurrentCommandIsPrivileged() ); + touch_button_t *button = Touch_FindButtonNoPattern( &touch.list_user, Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged( )); - if( !button ) - Con_Printf( S_ERROR "no such button" ); - else + if( button ) Touch_SetCommand( button, Cmd_Argv( 2 ) ); - - return; + else + Con_Printf( S_ERROR "no such button" ); } - Con_Printf( S_USAGE "touch_setcommand \n" ); + else Con_Printf( S_USAGE "touch_setcommand \n" ); } +static void Touch_LoadDefaults_f( void ); + static void Touch_ReloadConfig_f( void ) { touch.state = state_none; if( touch.edit ) touch.edit->finger = -1; + if( touch.selection ) touch.selection->finger = -1; + touch.edit = touch.selection = NULL; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; - Cbuf_AddText( va("exec %s\n", touch_config_file->string ) ); + if( touch_in_menu.value ) + Cvar_DirectSet( &touch_in_menu, "0" ); + + if( FS_FileExists( touch_config_file.string, true )) + Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file.string ); + else + { + Touch_LoadDefaults_f(); + touch.configchanged = true; + } } -static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, - const char *name, const char *texture, const char *command, - float x1, float y1, float x2, float y2, byte *color, - qboolean privileged ) +static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, const char *name, const char *texture, const char *command, + float x1, float y1, float x2, float y2, byte *color, qboolean privileged ) { - touch_button_t *button = Mem_Calloc( touch.mempool, sizeof( touch_button_t ) ); + touch_button_t *b = Mem_Calloc( touch.mempool, sizeof( *b )); - button->texture = -1; - Q_strncpy( button->texturefile, texture, sizeof( B( texturefile ))); - Q_strncpy( button->name, name, sizeof( B( name ))); - Touch_RemoveButtonFromList( list, name, privileged ); //replace if exist - button->x1 = x1; - button->y1 = y1; - button->x2 = x2; - button->y2 = y2; - button->flags = privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT; - MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); - button->command[0] = 0; - button->fade = 1; + Touch_RemoveButtonFromList( list, name, privileged ); // replace if exist - Touch_SetCommand( button, command ); + b->gl_texturenum = -1; + Q_strncpy( b->texture, texture, sizeof( b->texture )); + Q_strncpy( b->name, name, sizeof( b->name )); + b->x1 = x1; + b->y1 = y1; + b->x2 = x2; + b->y2 = y2; + Vector4Copy( color, b->color ); + b->fade = 1; - button->finger = -1; - button->next = NULL; - button->prev = list->last; - if( list->last ) - list->last->next = button; - list->last = button; + if( !privileged ) + SetBits( b->flags, TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT ); + + Touch_SetCommand( b, command ); + + b->finger = -1; + b->prev = list->last; + if( b->prev ) + b->prev->next = b; + list->last = b; if( !list->first ) - list->first = button; + list->first = b; - return button; + return b; } void 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 ) @@ -809,21 +838,22 @@ void Touch_AddClientButton( const char *name, const char *texture, const char *c if( !touch.initialized ) return; + if( round ) IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); + if( round == round_aspect ) - { - y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * aspect; - } + y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect; + button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true ); - button->flags |= flags | TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT; + SetBits( button->flags, TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT ); button->aspect = aspect; } static void Touch_LoadDefaults_f( void ) { int i; - for( i = 0; i < g_LastDefaultButton; i++ ) + for( i = 0; i < g_DefaultButtonsLength; i++ ) { touch_button_t *button; float x1 = g_DefaultButtons[i].x1, @@ -835,74 +865,86 @@ static void Touch_LoadDefaults_f( void ) if( g_DefaultButtons[i].aspect && g_DefaultButtons[i].round == round_aspect ) { - if( g_DefaultButtons[i].texturefile[0] == '#' ) - y2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth*2/SCR_H; + if( g_DefaultButtons[i].texture[0] == '#' ) + y2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth * 2.0f / refState.height; else - y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * g_DefaultButtons[i].aspect; + y2 = y1 + (( x2 - x1 ) / Touch_AspectRatio()) * g_DefaultButtons[i].aspect; } IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); - button = Touch_AddButton( &touch.list_user, g_DefaultButtons[i].name, g_DefaultButtons[i].texturefile, g_DefaultButtons[i].command, x1, y1, x2, y2, g_DefaultButtons[i].color, true ); - button->flags |= g_DefaultButtons[i].flags; + + button = 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 ); + SetBits( button->flags, g_DefaultButtons[i].flags ); button->aspect = g_DefaultButtons[i].aspect; } + touch.configchanged = true; } // Add default button from client -void 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 ) +void 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 ) { - touchdefaultbutton_t *button; + touchdefaultbutton_t *b; - if( g_LastDefaultButton >= 255 ) - return; + g_DefaultButtons = Mem_Realloc( touch.mempool, g_DefaultButtons, sizeof( *g_DefaultButtons ) * ( g_DefaultButtonsLength + 1 )); - button = g_DefaultButtons + g_LastDefaultButton; + b = &g_DefaultButtons[g_DefaultButtonsLength]; - Q_strncpy( B( name ), name, sizeof( B( name ))); - Q_strncpy( B( texturefile ), texturefile, sizeof( B( texturefile ))); - Q_strncpy( B( command ), command, sizeof( B( command ))); - B( x1 ) = x1; - B( y1 ) = y1; - B( x2 ) = x2; - B( y2 ) = y2; - MakeRGBA( B( color ), color[0], color[1], color[2], color[3] ); - B( round ) = round; - B( aspect ) = aspect; - B( flags ) = flags; - g_LastDefaultButton++; + Q_strncpy( b->name, name, sizeof( b->name )); + Q_strncpy( b->texture, texture, sizeof( b->texture )); + Q_strncpy( b->command, command, sizeof( b->command )); + b->x1 = x1; + b->y1 = y1; + b->x2 = x2; + b->y2 = y2; + Vector4Copy( color, b->color ); + b->round = round; + b->aspect = aspect; + b->flags = flags; + + g_DefaultButtonsLength++; } // Client may remove all default buttons from engine void Touch_ResetDefaultButtons( void ) { - g_LastDefaultButton = 0; + g_DefaultButtonsLength = 0; + + if( g_DefaultButtons ) + { + Mem_Free( g_DefaultButtons ); + g_DefaultButtons = NULL; + } } static void Touch_AddButton_f( void ) { - rgba_t color; - int argc = Cmd_Argc( ); + rgba_t color = { 255, 255, 255, 255 }; touch_button_t *button = NULL; - const char *name, *texture, *command; - float x1, y1, x2, y2; + const char *name, *command; + float x1 = 0.4f, y1 = 0.4f, x2 = 0.6f, y2 = 0.6f; qboolean privileged = Cmd_CurrentCommandIsPrivileged(); + string texture; - if( argc < 4 ) + if( Cmd_Argc( ) < 4 ) { Con_Printf( S_USAGE "touch_addbutton [ [ r g b a ] ]\n" ); return; } name = Cmd_Argv( 1 ); - texture = Cmd_Argv( 2 ); + Q_strncpy( texture, Cmd_Argv( 2 ), sizeof( texture )); command = Cmd_Argv( 3 ); - if( argc < 8 ) - { - x1 = y1 = 0.4f; - x2 = y2 = 0.6f; - } - else + // HACKHACK: old engine specifically used .tga for touch buttons + // and because new engine extras.pk3 don't have .tga textures + // (which instead were converted to .png) strip extension to let + // to let imagelib choose better format + // + // Remove this when old engine migration would be done + if( !Q_stricmp( COM_FileExtension( texture ), "tga" )) + COM_StripExtension( texture ); + + if( Cmd_Argc( ) >= 8 ) { x1 = Q_atof( Cmd_Argv( 4 )); y1 = Q_atof( Cmd_Argv( 5 )); @@ -910,47 +952,73 @@ static void Touch_AddButton_f( void ) y2 = Q_atof( Cmd_Argv( 7 )); } - if( argc < 12 ) + if( Cmd_Argc( ) >= 12 ) { - MakeRGBA( color, 255, 255, 255, 255 ); - } - else - { - MakeRGBA( color, - Q_atoi( Cmd_Argv( 8 )), - Q_atoi( Cmd_Argv( 9 )), - Q_atoi( Cmd_Argv( 10 )), - Q_atoi( Cmd_Argv( 11 ))); + color[0] = Q_atoi( Cmd_Argv( 8 )); + color[1] = Q_atoi( Cmd_Argv( 9 )); + color[2] = Q_atoi( Cmd_Argv( 10 )); + color[3] = Q_atoi( Cmd_Argv( 11 )); } button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, privileged ); - if( argc >= 13 ) - { + if( Cmd_Argc( ) >= 13 ) SetBits( button->flags, Q_atoi( Cmd_Argv( 12 ))); - } - if( argc >= 14 ) + if( Cmd_Argc( ) >= 14 ) { // Recalculate button coordinates aspect ratio // This is feature for distributed configs float aspect = Q_atof( Cmd_Argv( 13 )); if( aspect ) { - if( B( texturefile )[0] != '#' ) - B( y2 ) = B( y1 ) + ( B( x2 ) - B( x1 )) * ( SCR_W / SCR_H ) * aspect; - B( aspect ) = aspect; + if( button->texture[0] != '#' ) + button->y2 = button->y1 + (( button->x2 - button->x1 ) / Touch_AspectRatio( )) * aspect; + button->aspect = aspect; } } } static void Touch_EnableEdit_f( void ) { + float current_ratio = (float)refState.height / refState.width; + if( touch.state == state_none ) touch.state = state_edit; + touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; touch.move_button = NULL; touch.configchanged = true; + + /* try determine the best ratio + * User enters editor. Window now have correct size. Need to fix aspect ratio in some cases */ + // Case A: no config was loaded, touch was generated with lower height, but window was resized higher, reset it to actual size + if( touch.actual_aspect_ratio > current_ratio ) + touch.actual_aspect_ratio = current_ratio; + if( !touch.config_aspect_ratio ) + touch.config_aspect_ratio = touch.actual_aspect_ratio; + // Case B: config was loaded, but window may be resized later, so keep y coordinate as is + touch.actual_aspect_ratio = current_ratio; + + // convert coordinates to actual aspect ratio after it was updated + if( touch.config_aspect_ratio != touch.actual_aspect_ratio ) + { + touch_button_t *button; + + for( button = touch.list_user.first; button; button = button->next ) + { + button->y1 /= touch.actual_aspect_ratio / touch.config_aspect_ratio; + button->y2 /= touch.actual_aspect_ratio / touch.config_aspect_ratio; + + // clamp positions to make buttons visible by user + if( button->y2 > 1.0f ) + { + button->y1 -= button->y2 - 1.0f; + button->y2 -= button->y2 - 1.0f; + } + } + touch.config_aspect_ratio = touch.actual_aspect_ratio; + } } static void Touch_DisableEdit_f( void ) @@ -963,11 +1031,9 @@ static void Touch_DisableEdit_f( void ) touch.edit = touch.selection = NULL; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; - if( CVAR_TO_BOOL( touch_in_menu )) - { - Cvar_Set( "touch_in_menu", "0" ); - } - else if( cls.key_dest == key_game ) + if( touch_in_menu.value ) + Cvar_DirectSet( &touch_in_menu, "0" ); + else if( cls.key_dest == key_game ) Touch_WriteConfig(); } @@ -985,7 +1051,7 @@ static void Touch_DeleteProfile_f( void ) static void Touch_InitEditor( void ) { - float x = 0.1f * (SCR_H/SCR_W); + float x = 0.1f * (Touch_AspectRatio()); float y = 0.05f; touch_button_t *temp; rgba_t color; @@ -1017,19 +1083,22 @@ static void Touch_InitEditor( void ) void Touch_Init( void ) { rgba_t color; + if( touch.initialized ) return; + touch.mempool = Mem_AllocPool( "Touch" ); //touch.first = touch.last = NULL; - Con_Printf( "IN_TouchInit()\n"); - touch.move_finger = touch.resize_finger = touch.look_finger = touch.wheel_finger = -1; + Con_Printf( "%s()\n", __func__ ); + touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; touch.state = state_none; touch.showeditbuttons = true; touch.clientonly = false; touch.precision = false; MakeRGBA( touch.scolor, 255, 255, 255, 255 ); touch.swidth = 1; - g_LastDefaultButton = 0; + g_DefaultButtons = NULL; + g_DefaultButtonsLength = 0; touch.list_edit.first = touch.list_edit.last = NULL; touch.list_user.first = touch.list_user.last = NULL; @@ -1044,18 +1113,19 @@ void Touch_Init( void ) Touch_AddDefaultButton( "jump", "touch_default/jump", "+jump", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 ); Touch_AddDefaultButton( "attack", "touch_default/shoot", "+attack", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 ); Touch_AddDefaultButton( "attack2", "touch_default/shoot_alt", "+attack2", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 ); - Touch_AddDefaultButton( "loadquick", "touch_default/load", "loadquick", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 16 ); - Touch_AddDefaultButton( "savequick", "touch_default/save", "savequick", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 16 ); - Touch_AddDefaultButton( "messagemode", "touch_default/keyboard", "messagemode", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 8 ); + Touch_AddDefaultButton( "loadquick", "touch_default/load", "loadquick", 0.760000, 0.000000, 0.840000, 0.142222, color, 2, 1, 16 ); + Touch_AddDefaultButton( "savequick", "touch_default/save", "savequick", 0.840000, 0.000000, 0.920000, 0.142222, color, 2, 1, 16 ); + Touch_AddDefaultButton( "messagemode", "touch_default/keyboard", "messagemode", 0.840000, 0.000000, 0.920000, 0.142222, color, 2, 1, 8 ); Touch_AddDefaultButton( "reload", "touch_default/reload", "+reload", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 ); Touch_AddDefaultButton( "flashlight", "touch_default/flash_light_filled", "impulse 100", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 ); - Touch_AddDefaultButton( "scores", "touch_default/map", "+showscores", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 8 ); + Touch_AddDefaultButton( "scores", "touch_default/map", "+showscores", 0.760000, 0.000000, 0.840000, 0.142222, color, 2, 1, 8 ); Touch_AddDefaultButton( "show_numbers", "touch_default/show_weapons", "exec touch_default/numbers.cfg", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 ); Touch_AddDefaultButton( "duck", "touch_default/crouch", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 ); Touch_AddDefaultButton( "tduck", "touch_default/tduck", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 ); Touch_AddDefaultButton( "edit", "touch_default/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 ); - Touch_AddDefaultButton( "menu", "touch_default/menu", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 ); - + Touch_AddDefaultButton( "menu", "touch_default/menu", "cancelselect", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 ); + Touch_AddDefaultButton( "spray", "touch_default/spray", "impulse 201", 0.680000, 0.000000, 0.760000, 0.142222, color, 2, 1, 8 ); + Touch_AddDefaultButton( "voicechat", "touch_default/microphone", "+voicerecord", 0.780000, 0.817778, 0.860000, 0.960000, color, 2, 1, 8 ); Cmd_AddCommand( "touch_addbutton", Touch_AddButton_f, "add native touch button" ); Cmd_AddCommand( "touch_removebutton", IN_TouchRemoveButton_f, "remove native touch button" ); @@ -1072,48 +1142,46 @@ void Touch_Init( void ) Cmd_AddRestrictedCommand( "touch_loaddefaults", Touch_LoadDefaults_f, "generate config from defaults" ); Cmd_AddRestrictedCommand( "touch_roundall", Touch_RoundAll_f, "round all buttons coordinates to grid" ); Cmd_AddRestrictedCommand( "touch_exportconfig", Touch_ExportConfig_f, "export config keeping aspect ratio" ); - Cmd_AddCommand( "touch_set_stroke", Touch_Stroke_f, "set global stroke width and color" ); - Cmd_AddCommand( "touch_setclientonly", Touch_SetClientOnly_f, "when 1, only client buttons are shown" ); + Cmd_AddRestrictedCommand( "touch_set_stroke", Touch_Stroke_f, "set global stroke width and color" ); + Cmd_AddRestrictedCommand( "touch_setclientonly", Touch_SetClientOnly_f, "when 1, only client buttons are shown" ); Cmd_AddRestrictedCommand( "touch_reloadconfig", Touch_ReloadConfig_f, "load config, not saving changes" ); Cmd_AddRestrictedCommand( "touch_writeconfig", Touch_WriteConfig, "save current config" ); Cmd_AddRestrictedCommand( "touch_deleteprofile", Touch_DeleteProfile_f, "delete profile by name" ); Cmd_AddRestrictedCommand( "touch_generate_code", Touch_GenerateCode_f, "create code sample for mobility API" ); Cmd_AddCommand( "touch_fade", Touch_Fade_f, "start fade animation for selected buttons" ); Cmd_AddRestrictedCommand( "touch_toggleselection", Touch_ToggleSelection_f, "toggle vidibility on selected button in editor" ); + Cmd_AddRestrictedCommand( "touch_aspectratio", Touch_ConfigAspectRatio_f, "set current aspect ratio" ); // not saved, just runtime state for scripting - touch_in_menu = Cvar_Get( "touch_in_menu", "0", FCVAR_FILTERABLE, "draw touch in menu (for internal use only)" ); + Cvar_RegisterVariable( &touch_in_menu ); // sensitivity configuration - touch_forwardzone = Cvar_Get( "touch_forwardzone", "0.06", FCVAR_FILTERABLE, "forward touch zone" ); - touch_sidezone = Cvar_Get( "touch_sidezone", "0.06", FCVAR_FILTERABLE, "side touch zone" ); - touch_pitch = Cvar_Get( "touch_pitch", "90", FCVAR_FILTERABLE, "touch pitch sensitivity" ); - touch_yaw = Cvar_Get( "touch_yaw", "120", FCVAR_FILTERABLE, "touch yaw sensitivity" ); - touch_nonlinear_look = Cvar_Get( "touch_nonlinear_look", "0", FCVAR_FILTERABLE, "enable nonlinear touch look" ); - touch_pow_factor = Cvar_Get( "touch_pow_factor", "1.3", FCVAR_FILTERABLE, "set > 1 to enable" ); - touch_pow_mult = Cvar_Get( "touch_pow_mult", "400.0", FCVAR_FILTERABLE, "power multiplier, usually 200-1000" ); - touch_exp_mult = Cvar_Get( "touch_exp_mult", "0", FCVAR_FILTERABLE, "exponent multiplier, usually 20-200, 0 to disable" ); + Cvar_RegisterVariable( &touch_forwardzone ); + Cvar_RegisterVariable( &touch_sidezone ); + Cvar_RegisterVariable( &touch_pitch ); + Cvar_RegisterVariable( &touch_yaw ); + Cvar_RegisterVariable( &touch_nonlinear_look ); + Cvar_RegisterVariable( &touch_pow_factor ); + Cvar_RegisterVariable( &touch_pow_mult ); + Cvar_RegisterVariable( &touch_exp_mult ); // touch.cfg - touch_grid_count = Cvar_Get( "touch_grid_count", "50", FCVAR_FILTERABLE, "touch grid count" ); - touch_grid_enable = Cvar_Get( "touch_grid_enable", "1", FCVAR_FILTERABLE, "enable touch grid" ); - touch_config_file = Cvar_Get( "touch_config_file", "touch.cfg", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "current touch profile file" ); - touch_precise_amount = Cvar_Get( "touch_precise_amount", "0.5", FCVAR_FILTERABLE, "sensitivity multiplier for precise-look" ); - touch_highlight_r = Cvar_Get( "touch_highlight_r", "1.0", 0, "highlight r color" ); - touch_highlight_g = Cvar_Get( "touch_highlight_g", "1.0", 0, "highlight g color" ); - touch_highlight_b = Cvar_Get( "touch_highlight_b", "1.0", 0, "highlight b color" ); - touch_highlight_a = Cvar_Get( "touch_highlight_a", "1.0", 0, "highlight alpha" ); - touch_dpad_radius = Cvar_Get( "touch_dpad_radius", "1.0", FCVAR_FILTERABLE, "dpad radius multiplier" ); - touch_joy_radius = Cvar_Get( "touch_joy_radius", "1.0", FCVAR_FILTERABLE, "joy radius multiplier" ); - touch_move_indicator = Cvar_Get( "touch_move_indicator", "0.0", FCVAR_FILTERABLE, "indicate move events (0 to disable)" ); - touch_joy_texture = Cvar_Get( "touch_joy_texture", "touch_default/joy", FCVAR_FILTERABLE, "texture for move indicator"); + Cvar_RegisterVariable( &touch_grid_count ); + Cvar_RegisterVariable( &touch_grid_enable ); + Cvar_RegisterVariable( &touch_config_file ); + Cvar_RegisterVariable( &touch_precise_amount ); + Cvar_RegisterVariable( &touch_highlight_r ); + Cvar_RegisterVariable( &touch_highlight_g ); + Cvar_RegisterVariable( &touch_highlight_b ); + Cvar_RegisterVariable( &touch_highlight_a ); + Cvar_RegisterVariable( &touch_dpad_radius ); + Cvar_RegisterVariable( &touch_joy_radius ); + Cvar_RegisterVariable( &touch_move_indicator ); + Cvar_RegisterVariable( &touch_joy_texture ); // input devices cvar - touch_enable = Cvar_Get( "touch_enable", DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable touch controls" ); - touch_emulate = Cvar_Get( "touch_emulate", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "emulate touch with mouse" ); - - /// TODO: touch sdl platform - // SDL_SetHint( SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1" ); + Cvar_RegisterVariable( &touch_enable ); + Cvar_RegisterVariable( &touch_emulate ); touch.initialized = true; } @@ -1121,23 +1189,20 @@ void Touch_Init( void ) //int pfnGetScreenInfo( SCREENINFO *pscrinfo ); static void Touch_InitConfig( void ) { - if( !touch.initialized ) - return; - - if( !host.config_executed ) - return; - - if( touch.config_loaded ) + if( !touch.initialized || !host.config_executed || touch.config_loaded ) return; /// TODO: hud font //pfnGetScreenInfo( NULL ); //HACK: update hud screen parameters like iHeight - if( FS_FileExists( touch_config_file->string, true ) ) - Cbuf_AddText( va( "exec \"%s\"\n", touch_config_file->string ) ); - else Touch_LoadDefaults_f( ); + if( FS_FileExists( touch_config_file.string, true ) ) + { + Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file.string ); + Cbuf_Execute(); + } + else Touch_LoadDefaults_f(); Touch_InitEditor(); - touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture->string, NULL, 0, TF_NOMIPMAP ); + touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_NOMIPMAP ); touch.whitetexture = R_GetBuiltinTexture( REF_WHITE_TEXTURE ); touch.configchanged = false; touch.config_loaded = true; @@ -1157,59 +1222,77 @@ static qboolean Touch_IsVisible( touch_button_t *button ) return false; // skip nonclient buttons in clientonly mode if( touch.state >= state_edit ) - return true; //!!! Draw when editor is open + return true; // draw when editor is open if( FBitSet( button->flags, TOUCH_FL_HIDE )) return false; // skip hidden - if( FBitSet( button->flags, TOUCH_FL_SP ) && CL_GetMaxClients() != 1 ) - return false; // skip singleplayer(load, save) buttons in multiplayer - - if( FBitSet( button->flags, TOUCH_FL_MP ) && CL_GetMaxClients() == 1 ) - return false; // skip multiplayer buttons in singleplayer + if( cl.maxclients == 1 ) + { + if( FBitSet( button->flags, TOUCH_FL_MP )) + return false; // skip multiplayer buttons in singleplayer + } + else + { + if( FBitSet( button->flags, TOUCH_FL_SP )) + return false; // skip singleplayer(load, save) buttons in multiplayer + } return true; } -static void Touch_DrawTexture ( float x1, float y1, float x2, float y2, int texture, byte r, byte g, byte b, byte a ) +static void Touch_DrawTexture( float x1, float y1, float x2, float y2, int texture, byte *color ) { - if( x1 >= x2 ) + if( x1 >= x2 || y1 >= y2 ) return; - if( y1 >= y2 ) - return; - - ref.dllFuncs.Color4ub( r, g, b, a ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x1), - TO_SCRN_Y(y1), - TO_SCRN_X(x2 - x1), - TO_SCRN_Y(y2 - y1), + ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); + ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( x1 ), TO_SCRN_Y( y1 ), + TO_SCRN_X( x2 - x1 ), TO_SCRN_Y( y2 - y1 ), 0, 0, 1, 1, texture ); } -#define GRID_COUNT_X ((int)touch_grid_count->value) -#define GRID_COUNT_Y (((int)touch_grid_count->value) * SCR_H / SCR_W) -#define GRID_X (1.0f/GRID_COUNT_X) -#define GRID_Y (SCR_W/SCR_H/GRID_COUNT_X) -#define GRID_ROUND_X(x) ((float)round( x * GRID_COUNT_X ) / GRID_COUNT_X) -#define GRID_ROUND_Y(x) ((float)round( x * GRID_COUNT_Y ) / GRID_COUNT_Y) +#define GRID_COUNT_X ((int)touch_grid_count.value ) +#define GRID_COUNT_Y (((int)touch_grid_count.value ) * Touch_AspectRatio( )) +#define GRID_X ( 1.0f / GRID_COUNT_X ) +#define GRID_Y ( 1.0f / Touch_AspectRatio() / GRID_COUNT_X ) +#define GRID_ROUND_X( x ) ((float)round(( x ) * GRID_COUNT_X ) / GRID_COUNT_X ) +#define GRID_ROUND_Y( x ) ((float)round(( x ) * GRID_COUNT_Y ) / GRID_COUNT_Y ) static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ) { /// TODO: grid check here if( *x2 - *x1 < GRID_X * 2 ) *x2 = *x1 + GRID_X * 2; + if( *y2 - *y1 < GRID_Y * 2) *y2 = *y1 + GRID_Y * 2; + if( *x1 < 0 ) - *x2 -= *x1, *x1 = 0; + { + *x2 -= *x1; + *x1 = 0; + } + if( *y1 < 0 ) - *y2 -= *y1, *y1 = 0; + { + *y2 -= *y1; + *y1 = 0; + } + if( *y2 > 1 ) - *y1 -= *y2 - 1, *y2 = 1; + { + *y1 -= *y2 - 1; + *y2 = 1; + } + if( *x2 > 1 ) - *x1 -= *x2 - 1, *x2 = 1; - if( CVAR_TO_BOOL( touch_grid_enable )) + { + *x1 -= *x2 - 1; + *x2 = 1; + } + + if( touch_grid_enable.value ) { *x1 = GRID_ROUND_X( *x1 ); *x2 = GRID_ROUND_X( *x2 ); @@ -1220,27 +1303,33 @@ static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ) static float Touch_DrawCharacter( float x, float y, int number, float size ) { - float s1, s2, t1, t2, width, height; - int w, h; + float s1, s2, t1, t2, width, height; + int w, h; wrect_t *prc; + if( !cls.creditsFont.valid ) return 0; number &= 255; number = Con_UtfProcessChar( number ); + if( !number ) + return 0; + R_GetTextureParms( &w, &h, cls.creditsFont.hFontTexture ); prc = &cls.creditsFont.fontRc[number]; - s1 = ((float)prc->left) / (float)w; - t1 = ((float)prc->top) / (float)h; - s2 = ((float)prc->right) / (float)w; - t2 = ((float)prc->bottom) / (float)h; + s1 = prc->left / (float)w; + t1 = prc->top / (float)h; + s2 = prc->right / (float)w; + t2 = prc->bottom / (float)h; - width = ((float)( prc->right - prc->left )) / 1024.0f * size; - height = ((float)( prc->bottom - prc->top )) / 1024.0f * size; + width = ( prc->right - prc->left ) / 1024.0f * size; + height = ( prc->bottom - prc->top ) / 1024.0f * size; + + ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( x ), TO_SCRN_Y( y ), TO_SCRN_X( width ), TO_SCRN_X( height ), + s1, t1, s2, t2, cls.creditsFont.hFontTexture ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x), TO_SCRN_Y(y), TO_SCRN_X(width), TO_SCRN_X(height), s1, t1, s2, t2, cls.creditsFont.hFontTexture ); return width; } @@ -1249,6 +1338,8 @@ static float Touch_DrawText( float x1, float y1, float x2, float y2, const char float x = x1; float maxy = y2; float maxx; + float alpha = color[3] / 255.0f; + if( x2 ) maxx = x2 - cls.creditsFont.charWidths['M'] / 1024.0f * size; else @@ -1256,19 +1347,18 @@ static float Touch_DrawText( float x1, float y1, float x2, float y2, const char if( !cls.creditsFont.valid ) return GRID_X * 2; - Con_UtfProcessChar( 0 ); + Con_UtfProcessChar( 0 ); ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); // text is additive and alpha does not work - ref.dllFuncs.Color4ub( color[0] * ( (float)color[3] /255.0f ), color[1] * ( (float)color[3] /255.0f ), - color[2] * ( (float)color[3] /255.0f ), 255 ); + ref.dllFuncs.Color4ub( color[0] * alpha, color[1] * alpha, color[2] * alpha, 255 ); while( *s ) { - while( *s && ( *s != '\n' ) && ( *s != ';' ) && ( x1 < maxx ) ) + while( *s && ( *s != '\n' ) && ( *s != ';' ) && ( x1 < maxx )) x1 += Touch_DrawCharacter( x1, y1, *s++, size ); - y1 += cls.creditsFont.charHeight / 1024.f * size / SCR_H * SCR_W; + y1 += cls.creditsFont.charHeight / 1024.f * size / Touch_AspectRatio(); if( y1 >= maxy ) break; @@ -1277,92 +1367,114 @@ static float Touch_DrawText( float x1, float y1, float x2, float y2, const char s++; x1 = x; } - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); return x1; } static void Touch_DrawButtons( touchbuttonlist_t *list ) { - touch_button_t *button; + touch_button_t *b; - for( button = list->first; button; button = button->next ) + for( b = list->first; b; b = b->next ) { - if( Touch_IsVisible( button ) ) + if( Touch_IsVisible( b )) { rgba_t color; - MakeRGBA( color, B( color[0] ), B( color[1] ), B( color[2] ), B( color[3] ) ); - if( B( fadespeed ) ) - { - button->fade += B( fadespeed ) * host.frametime; - button->fade = bound( 0, B(fade), 1 ); - if( ( B( fade ) == 0 ) || ( B(fade) == 1 ) ) - B( fadespeed ) = 0; - if( ( ( B( fade ) >= B( fadeend ) ) && ( B( fadespeed ) > 0 ) ) || - ( ( B( fade ) <= B( fadeend ) ) && ( B( fadespeed ) < 0 ) ) ) - B( fadespeed ) = 0, B( fade ) = B( fadeend ) ; - } + Vector4Copy( b->color, color ); - if( ( B( finger ) != -1 ) && !FBitSet( B( flags ), TOUCH_FL_CLIENT ) ) + if( b->fadespeed ) { - color[0] = bound( 0,(float) color[0] * touch_highlight_r->value, 255 ); - color[1] = bound( 0,(float) color[1] * touch_highlight_g->value, 255 ); - color[2] = bound( 0,(float) color[2] * touch_highlight_b->value, 255 ); - color[3] = bound( 0,(float) color[3] * touch_highlight_a->value, 255 ); - } + b->fade += b->fadespeed * host.frametime; + b->fade = bound( 0, b->fade, 1 ); + if( b->fade == 0 || b->fade == 1 ) + b->fadespeed = 0; - color[3] *= B( fade ); - if( button->texturefile[0] == '#' ) - Touch_DrawText( touch.swidth/SCR_W + B(x1), touch.swidth/SCR_H + B(y1), B(x2), B(y2), button->texturefile + 1, color, B( aspect )?B(aspect):1 ); - else if( button->texturefile[0] ) - { - if( button->texture == -1 ) + if(( b->fade >= b->fadeend && b->fadespeed > 0 ) || ( b->fade <= b->fadeend && b->fadespeed < 0 )) { - button->texture = ref.dllFuncs.GL_LoadTexture( button->texturefile, NULL, 0, TF_NOMIPMAP ); + b->fadespeed = 0; + b->fade = b->fadeend; } - - if( FBitSet( B(flags), TOUCH_FL_DRAW_ADDITIVE )) - ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); - - Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), B(texture), color[0], color[1], color[2], color[3] ); - - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); } - if( FBitSet( B(flags), TOUCH_FL_STROKE )) + + if( b->finger != -1 && !FBitSet( b->flags, TOUCH_FL_CLIENT ) ) { - ref.dllFuncs.Color4ub( touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] * B( fade ) ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)), - TO_SCRN_Y(B(y1)), - touch.swidth, - TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth, - 0, 0, 1, 1, touch.whitetexture ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)) + touch.swidth, - TO_SCRN_Y(B(y1)), - TO_SCRN_X(B(x2)-B(x1)) - touch.swidth, - touch.swidth, - 0, 0, 1, 1, touch.whitetexture ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x2))-touch.swidth, - TO_SCRN_Y(B(y1)) + touch.swidth, - touch.swidth, - TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth, - 0, 0, 1, 1, touch.whitetexture ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)), - TO_SCRN_Y(B(y2))-touch.swidth, - TO_SCRN_X(B(x2)-B(x1)) - touch.swidth, - touch.swidth, - 0, 0, 1, 1, touch.whitetexture ); - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); + color[0] = bound( 0, color[0] * touch_highlight_r.value, 255 ); + color[1] = bound( 0, color[1] * touch_highlight_g.value, 255 ); + color[2] = bound( 0, color[2] * touch_highlight_b.value, 255 ); + color[3] = bound( 0, color[3] * touch_highlight_a.value, 255 ); + } + + color[3] *= b->fade; + + if( b->texture[0] == '#' ) + { + Touch_DrawText( + touch.swidth / (float)refState.width + b->x1, + touch.swidth / (float)refState.height + b->y1, + b->x2, b->y2, b->texture + 1, color, b->aspect ? b->aspect : 1 ); + } + else if( b->texture[0] ) + { + if( b->gl_texturenum == -1 ) + b->gl_texturenum = ref.dllFuncs.GL_LoadTexture( b->texture, NULL, 0, TF_IMAGE ); + + if( FBitSet( b->flags, TOUCH_FL_DRAW_ADDITIVE )) + ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); + else + ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); + + Touch_DrawTexture( b->x1, b->y1, b->x2, b->y2, b->gl_texturenum, color ); + } + + if( FBitSet( b->flags, TOUCH_FL_STROKE )) + { + rgba_t scolor; + const float x1_ = TO_SCRN_X( b->x1 ); + const float y1_ = TO_SCRN_Y( b->y1 ); + const float x2_ = TO_SCRN_X( b->x2 ); + const float y2_ = TO_SCRN_Y( b->y2 ); + const float swidth = touch.swidth; + + Vector4Copy( touch.scolor, scolor ); + scolor[3] *= b->fade; + + ref.dllFuncs.FillRGBA( kRenderTransTexture, + x1_, y1_, + swidth, y2_ - y1_ - swidth, + scolor[0], scolor[1], scolor[2], scolor[3] ); + + ref.dllFuncs.FillRGBA( kRenderTransTexture, + x2_ - swidth, y1_ + swidth, + swidth, y2_ - y1_ - swidth, + scolor[0], scolor[1], scolor[2], scolor[3] ); + + ref.dllFuncs.FillRGBA( kRenderTransTexture, + x1_ + swidth, y1_, + x2_ - x1_ - swidth, swidth, + scolor[0], scolor[1], scolor[2], scolor[3] ); + + ref.dllFuncs.FillRGBA( kRenderTransTexture, + x1_, y2_ - swidth, + x2_ - x1_ - swidth, swidth, + scolor[0], scolor[1], scolor[2], scolor[3] ); } } - if( touch.state >= state_edit && !( button->flags & TOUCH_FL_NOEDIT ) ) + + if( touch.state >= state_edit && !FBitSet( b->flags, TOUCH_FL_NOEDIT )) { rgba_t color; - if( !FBitSet( button->flags, TOUCH_FL_HIDE ) ) - Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 255, 255, 0, 32 ); + + if( !FBitSet( b->flags, TOUCH_FL_HIDE )) + MakeRGBA( color, 255, 255, 0, 32 ); else - Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 128, 128, 128, 128 ); - MakeRGBA( color, 255, 255,127, 255 ); - Con_DrawString( TO_SCRN_X( B(x1) ), TO_SCRN_Y( B(y1) ), B(name), color ); + MakeRGBA( color, 128, 128, 128, 128 ); + + ref.dllFuncs.FillRGBA( kRenderTransTexture, + TO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ), + TO_SCRN_X( b->x2 - b->x1 ), TO_SCRN_Y( b->y2 - b->y1 ), color[0], color[1], color[2], color[3] ); + + MakeRGBA( color, 255, 255, 127, 255 ); + Con_DrawString( TO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ), b->name, color ); } } @@ -1370,60 +1482,45 @@ static void Touch_DrawButtons( touchbuttonlist_t *list ) void Touch_Draw( void ) { - touch_button_t *button; + if( !touch.initialized || ( !touch_enable.value && !touch.clientonly )) + return; - if( !touch.initialized || (!CVAR_TO_BOOL(touch_enable) && !touch.clientonly) ) + if( cls.key_dest != key_game && !touch_in_menu.value ) return; Touch_InitConfig(); - if( cls.key_dest != key_game && !CVAR_TO_BOOL(touch_in_menu) ) - return; - ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); - if( touch.state >= state_edit && CVAR_TO_BOOL(touch_grid_enable) ) + if( touch.state >= state_edit && touch_grid_enable.value ) { float x; - if( CVAR_TO_BOOL(touch_in_menu) ) - Touch_DrawTexture( 0, 0, 1, 1, touch.whitetexture, 32, 32, 32, 255 ); + + if( touch_in_menu.value ) + ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 32, 32, 32, 255 ); else - Touch_DrawTexture( 0, 0, 1, 1, touch.whitetexture, 0, 0, 0, 112 ); - ref.dllFuncs.Color4ub( 0, 224, 224, 112 ); - for( x = 0; x < 1 ; x += GRID_X ) - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x), - 0, - 1, - TO_SCRN_Y(1), - 0, 0, 1, 1, touch.whitetexture ); - for( x = 0; x < 1 ; x += GRID_Y ) - ref.dllFuncs.R_DrawStretchPic( 0, - TO_SCRN_Y(x), - TO_SCRN_X(1), - 1, - 0, 0, 1, 1, touch.whitetexture ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 0, 0, 0, 112 ); + + for( x = 0.0f; x < 1.0f; x += GRID_X ) + ref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( x ), 0, 1, TO_SCRN_Y( 1 ), 0, 224, 224, 112 ); + + for( x = 0.0f; x < 1.0f; x += GRID_Y ) + ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, TO_SCRN_Y( x ), TO_SCRN_X( 1 ), 1, 0, 224, 224, 112 ); } Touch_DrawButtons( &touch.list_user ); if( touch.state >= state_edit ) { - rgba_t color; - - MakeRGBA( color, 255, 255, 255, 255 ); - if( touch.edit ) { - float x1 = touch.edit->x1, - y1 = touch.edit->y1, - x2 = touch.edit->x2, - y2 = touch.edit->y2; + float x1 = touch.edit->x1, y1 = touch.edit->y1, x2 = touch.edit->x2, y2 = touch.edit->y2; IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); - Touch_DrawTexture( x1, y1, x2, y2, touch.whitetexture, 0, 255, 0, 32 ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( x1 ), TO_SCRN_Y( y1 ), + TO_SCRN_X( x2 - x1 ), TO_SCRN_Y( y2 - y1 ), 0, 255, 0, 32 ); } - Touch_DrawTexture( 0, 0, GRID_X, GRID_Y, touch.whitetexture, 255, 255, 255, 64 ); - + ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, TO_SCRN_X( GRID_X ), TO_SCRN_Y( GRID_Y ), 255, 255, 255, 64 ); if( touch.showeditbuttons ) Touch_DrawButtons( &touch.list_edit ); @@ -1431,59 +1528,62 @@ void Touch_Draw( void ) /// TODO: move to mainui if( touch.selection ) { - button = touch.selection; - Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 255, 0, 0, 64 ); + char text[MAX_VA_STRING]; + rgba_t color = { 255, 255, 255, 255 }; + const touch_button_t *b = touch.selection; - Con_DrawString( 0, TO_SCRN_Y(GRID_Y * 11), "Selection:", color ); - Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*12), "Name: ", color ), - TO_SCRN_Y(GRID_Y*12), B(name), color ); - Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*13), "Texture: ", color ), - TO_SCRN_Y(GRID_Y*13), B(texturefile), color ); - Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*14), "Command: ", color ), - TO_SCRN_Y(GRID_Y*14), B(command), color ); + ref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( b->x1 ), TO_SCRN_Y( b->y1 ), + TO_SCRN_X( b->x2 - b->x1 ), TO_SCRN_Y( b->y2 - b->y1 ), 255, 0, 0, 64 ); + + Q_snprintf( text, sizeof( text ), "Selection:\nName: %s\nTexture: %s\nCommand: %s", b->name, b->texture, b->command ); + + Con_DrawString( 0, TO_SCRN_Y( GRID_Y * 11 ), text, color ); } } - ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - - if( ( touch.move_finger != -1 ) && touch.move_button && touch_move_indicator->value ) + if( touch.move_finger != -1 && touch.move_button && touch_move_indicator.value > 0.0f ) { - float width; - float height; - if( FBitSet( touch_joy_texture->flags, FCVAR_CHANGED ) ) + float width, height; + float size = touch_move_indicator.value; + + if( FBitSet( touch_joy_texture.flags, FCVAR_CHANGED ) ) { - ClearBits( touch_joy_texture->flags, FCVAR_CHANGED ); - touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture->string, NULL, 0, TF_NOMIPMAP ); + ClearBits( touch_joy_texture.flags, FCVAR_CHANGED ); + touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_IMAGE ); } + if( touch.move_button->type == touch_move ) { - width = touch_sidezone->value; - height = touch_forwardzone->value; + width = touch_sidezone.value; + height = touch_forwardzone.value; } else { width = (touch.move_button->x2 - touch.move_button->x1)/2; height = (touch.move_button->y2 - touch.move_button->y1)/2; } + + ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); ref.dllFuncs.Color4ub( 255, 255, 255, 128 ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( touch.move_start_x - GRID_X * touch_move_indicator->value ), - TO_SCRN_Y( touch.move_start_y - GRID_Y * touch_move_indicator->value ), - TO_SCRN_X( GRID_X * 2 * touch_move_indicator->value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator->value ), 0, 0, 1, 1, touch.joytexture ); + ref.dllFuncs.R_DrawStretchPic( + TO_SCRN_X( touch.move_start_x - GRID_X * size ), + TO_SCRN_Y( touch.move_start_y - GRID_Y * size ), + TO_SCRN_X( GRID_X * 2 * size ), + TO_SCRN_Y( GRID_Y * 2 * size ), + 0, 0, 1, 1, touch.joytexture ); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); - ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * touch_move_indicator->value ), - TO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * touch_move_indicator->value ), - TO_SCRN_X( GRID_X * 2 * touch_move_indicator->value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator->value ), 0, 0, 1, 1, touch.joytexture ); - + ref.dllFuncs.R_DrawStretchPic( + TO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * size ), + TO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * size ), + TO_SCRN_X( GRID_X * 2 * size ), + TO_SCRN_Y( GRID_Y * 2 * size ), + 0, 0, 1, 1, touch.joytexture ); } - } // clear move and selection state static void IN_TouchEditClear( void ) { - // allow keep move/look fingers when doing touch_removeall - //touch.move_finger = touch.look_finger = -1; - if( touch.state < state_edit ) return; @@ -1503,24 +1603,23 @@ static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, { if( type == event_up ) // shutdown button move { - touch_button_t *button = touch.edit; - IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) ); + touch_button_t *b = touch.edit; + + IN_TouchCheckCoords( &b->x1, &b->y1, &b->x2, &b->y2 ); IN_TouchEditClear(); - if( button->type == touch_command ) - { - touch.selection = button; - // update "hide" editor button - touch.hidebutton->texture = -1; - touch.hidebutton->flags &= ~TOUCH_FL_HIDE; + touch.selection = b; - if( FBitSet( button->flags, TOUCH_FL_HIDE )) - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_show" ); - else - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_hide" ); - } + // update "hide" editor button + touch.hidebutton->gl_texturenum = -1; + ClearBits( touch.hidebutton->flags, TOUCH_FL_HIDE ); + + if( FBitSet( b->flags, TOUCH_FL_HIDE )) + Q_strncpy( touch.hidebutton->texture, "touch_default/edit_show", sizeof( touch.hidebutton->texture )); + else + Q_strncpy( touch.hidebutton->texture, "touch_default/edit_hide", sizeof( touch.hidebutton->texture )); } - if( type == event_motion ) // shutdown button move + else if( type == event_motion ) // shutdown button move { touch.edit->y1 += dy; touch.edit->y2 += dy; @@ -1533,18 +1632,14 @@ static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, if( type == event_down ) // enable resizing { if( touch.resize_finger == -1 ) - { touch.resize_finger = fingerID; - } } - if( type == event_up ) // disable resizing + else if( type == event_up ) // disable resizing { if( touch.resize_finger == fingerID ) - { touch.resize_finger = -1; - } } - if( type == event_motion ) // perform resizing + else if( type == event_motion ) // perform resizing { if( touch.resize_finger == fingerID ) { @@ -1555,7 +1650,7 @@ static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, } } -static void Touch_Motion( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +static void Touch_Motion( int fingerID, float x, float y, float dx, float dy ) { // process wheel if( fingerID == touch.wheel_finger ) @@ -1568,41 +1663,52 @@ static void Touch_Motion( touchEventType type, int fingerID, float x, float y, f touch.wheel_count++; touch.wheel_amount = 0; } + if( touch.wheel_amount < -0.1f ) { Cbuf_AddText( touch.wheel_up ); touch.wheel_count++; touch.wheel_amount = 0; } + return; } // walk if( fingerID == touch.move_finger ) { - // check bounds - if( touch_forwardzone->value <= 0 ) - Cvar_SetValue( "touch_forwardzone", 0.5 ); - if( touch_sidezone->value <= 0 ) - Cvar_SetValue( "touch_sidezone", 0.3 ); + const touch_button_t *b = touch.move_button; - if( !touch.move_button || touch.move_button->type == touch_move ) + if( !b || b->type == touch_move ) { + // check bounds + if( touch_forwardzone.value <= 0 ) + Cvar_DirectSet( &touch_forwardzone, "0.5" ); + + if( touch_sidezone.value <= 0 ) + Cvar_DirectSet( &touch_sidezone, "0.3" ); + // move relative to touch start - touch.forward = ( touch.move_start_y - y ) / touch_forwardzone->value; - touch.side = ( x - touch.move_start_x ) / touch_sidezone->value; + touch.forward = ( touch.move_start_y - y ) / touch_forwardzone.value; + touch.side = ( x - touch.move_start_x ) / touch_sidezone.value; } - else if( touch.move_button->type == touch_joy ) + else { // move relative to joy center - touch.forward = ( ( touch.move_button->y2 + touch.move_button->y1 ) - y * 2 ) / ( touch.move_button->y2 - touch.move_button->y1 ) * touch_joy_radius->value; - touch.side = ( x * 2 - ( touch.move_button->x2 + touch.move_button->x1 ) ) / ( touch.move_button->x2 - touch.move_button->x1 ) * touch_joy_radius->value; - } - else if( touch.move_button->type == touch_dpad ) - { - // like joy, but without acceleration. useful for bhop - touch.forward = round( ( (touch.move_button->y2 + touch.move_button->y1) - y * 2 ) / ( touch.move_button->y2 - touch.move_button->y1 ) * touch_dpad_radius->value ); - touch.side = round( ( x * 2 - (touch.move_button->x2 + touch.move_button->x1) ) / ( touch.move_button->x2 - touch.move_button->x1 ) * touch_dpad_radius->value ); + touch.forward = (( b->y2 + b->y1 ) - y * 2 ) / ( b->y2 - b->y1 ); + touch.side = ( x * 2 - ( b->x2 + b->x1 )) / ( b->x2 - b->x1 ); + + if( b->type == touch_joy ) + { + touch.forward *= touch_joy_radius.value; + touch.side *= touch_joy_radius.value; + } + else if( b->type == touch_dpad ) + { + // like joy, but without acceleration. useful for bhop + touch.forward = round( touch.forward * touch_dpad_radius.value ); + touch.side = round( touch.side * touch_dpad_radius.value ); + } } touch.forward = bound( -1, touch.forward, 1 ); @@ -1613,9 +1719,12 @@ static void Touch_Motion( touchEventType type, int fingerID, float x, float y, f if( fingerID == touch.look_finger ) { if( touch.precision ) - dx *= touch_precise_amount->value, dy *= touch_precise_amount->value; + { + dx *= touch_precise_amount.value; + dy *= touch_precise_amount.value; + } - if( CVAR_TO_BOOL(touch_nonlinear_look) ) + if( touch_nonlinear_look.value ) { float dabs, dcos, dsin; @@ -1628,271 +1737,263 @@ static void Touch_Motion( touchEventType type, int fingerID, float x, float y, f dcos = dx / dabs; dsin = dy / dabs; - if( touch_exp_mult->value > 1 ) - dabs = ( exp( dabs * touch_exp_mult->value ) - 1 ) / touch_exp_mult->value; + if( touch_exp_mult.value > 1 ) + dabs = ( exp( dabs * touch_exp_mult.value ) - 1 ) / touch_exp_mult.value; - if( touch_pow_mult->value > 1 && touch_pow_factor->value > 1 ) - dabs = pow( dabs * touch_pow_mult->value, touch_pow_factor->value ) / touch_pow_mult->value; + if( touch_pow_mult.value > 1 && touch_pow_factor.value > 1 ) + dabs = pow( dabs * touch_pow_mult.value, touch_pow_factor.value ) / touch_pow_mult.value; dx = dabs * dcos; dy = dabs * dsin; } // prevent breaking engine/client with bad values - if( IS_NAN( dx ) || IS_NAN( dy ) ) + if( IS_NAN( dx ) || IS_NAN( dy )) return; // accumulate - touch.yaw -= dx * touch_yaw->value, touch.pitch += dy * touch_pitch->value; + touch.yaw -= dx * touch_yaw.value; + touch.pitch += dy * touch_pitch.value; } } -static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y, float dx, float dy ) +static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y ) { touch_button_t *button; qboolean result = false; + if( type != event_down && type != event_up ) + return false; + // run from end(front) to start(back) for( button = list->last; button; button = button->prev ) { // skip invisible buttons - if( !Touch_IsVisible( button ) ) + if( !Touch_IsVisible( button )) continue; if( type == event_down ) { // button bounds check - if( ( x > button->x1 && - x < button->x2 ) && - ( y < button->y2 && - y > button->y1 ) ) + if( x < button->x1 || x > button->x2 || y < button->y1 || y > button->y2 ) + continue; + + button->finger = fingerID; + + if( button->type == touch_command ) { - button->finger = fingerID; + char command[256]; - if( button->type == touch_command ) + // command down: just execute command + Q_snprintf( command, sizeof( command ), "%s\n", button->command ); + if( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) + Cbuf_AddFilteredText( command ); + else Cbuf_AddText( command ); + + // increase precision + if( FBitSet( button->flags, TOUCH_FL_PRECISION )) + touch.precision = true; + + result = true; + } + else if( button->type == touch_wheel ) + { + string command; + + touch.wheel_finger = fingerID; + touch.wheel_amount = touch.wheel_count = 0; + + Cmd_TokenizeString( button->command ); + + touch.wheel_horizontal = !Q_strcmp( Cmd_Argv( 0 ), "_hwheel" ); + Q_snprintf( touch.wheel_up, sizeof( touch.wheel_up ), "%s\n", Cmd_Argv( 1 )); + Q_snprintf( touch.wheel_down, sizeof( touch.wheel_down ), "%s\n", Cmd_Argv( 2 )); + Q_snprintf( touch.wheel_end, sizeof( touch.wheel_end ), "%s\n", Cmd_Argv( 3 )); + if( Q_snprintf( command, sizeof( command ), "%s\n", Cmd_Argv( 4 )) > 1 ) { - char command[256]; - - // command down: just execute command - Q_snprintf( command, sizeof( command ), "%s\n", button->command ); - if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED )) + if( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) Cbuf_AddFilteredText( command ); else Cbuf_AddText( command ); - - // increase precision - if( FBitSet( B(flags), TOUCH_FL_PRECISION )) - touch.precision = true; - - result = true; + touch.wheel_count++; } - if( button->type == touch_wheel ) + // increase precision + if( FBitSet( button->flags, TOUCH_FL_PRECISION )) + touch.precision = true; + + result = true; + } + // initialize motion when player touched motion zone + else if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) + { + if( touch.move_finger !=-1 ) { - string command; - touch.wheel_finger = fingerID; - touch.wheel_amount = touch.wheel_count = 0; - - Cmd_TokenizeString( button->command ); - - touch.wheel_horizontal = !Q_strcmp( Cmd_Argv( 0 ), "_hwheel" ); - - Q_snprintf( touch.wheel_up, sizeof( touch.wheel_up ), "%s\n", Cmd_Argv( 1 ) ); - Q_snprintf( touch.wheel_down, sizeof( touch.wheel_down ), "%s\n", Cmd_Argv( 2 ) ); - Q_snprintf( touch.wheel_end, sizeof( touch.wheel_end ), "%s\n", Cmd_Argv( 3 ) ); - if( Q_snprintf( command, sizeof( command ), "%s\n", Cmd_Argv( 4 ) ) > 1) - { - if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED )) - Cbuf_AddFilteredText( command ); - else Cbuf_AddText( command ); - touch.wheel_count++; - } - - // increase precision - if( FBitSet( B(flags), TOUCH_FL_PRECISION )) - touch.precision = true; - - result = true; + // prevent initializing move while already moving + // revert finger switch, leave first finger + button->finger = touch.move_finger; + continue; } - // initialize motion when player touched motion zone - if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) + result = true; + + if( touch.look_finger == fingerID ) { - if( touch.move_finger !=-1 ) + touch_button_t *newbutton; + + // this is an error, try recover + touch.move_finger = touch.look_finger = -1; + + // player touched touch_move with enabled look mode + // and same finger id. release all move triggers + for( newbutton = list->first; newbutton; newbutton = newbutton->next ) { - // prevent initializing move while already moving - // revert finger switch, leave first finger - button->finger = touch.move_finger; - continue; + if( newbutton->type == touch_move || newbutton->type == touch_look ) + newbutton->finger = -1; } - result = true; - - if( touch.look_finger == fingerID ) - { - touch_button_t *newbutton; - - // this is an error, try recover - touch.move_finger = touch.look_finger = -1; - - // player touched touch_move with enabled look mode - // and same finger id. release all move triggers - for( newbutton = list->first; newbutton; newbutton = newbutton->next ) - if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1; - - Con_DPrintf( S_ERROR "Touch: touch_move on look finger %d!\n", fingerID ); - continue; - } - - // initialize move mode - touch.move_finger = fingerID; - touch.move_button = button; - - if( touch.move_button->type == touch_move ) - { - // initial position is first touch - touch.move_start_x = x; - touch.move_start_y = y; - } - else if( touch.move_button->type == touch_joy ) - { - // initial position is button center - touch.move_start_y = (touch.move_button->y2 + touch.move_button->y1) / 2; - touch.move_start_x = (touch.move_button->x2 + touch.move_button->x1) / 2; - - // start move instanly - touch.forward = ((touch.move_button->y2 + touch.move_button->y1) - y * 2) / (touch.move_button->y2 - touch.move_button->y1); - touch.side = (x * 2 - (touch.move_button->x2 + touch.move_button->x1)) / (touch.move_button->x2 - touch.move_button->x1); - } - else if( touch.move_button->type == touch_dpad ) - { - // dame as joy, but round - touch.move_start_y = (touch.move_button->y2 + touch.move_button->y1) / 2; - touch.move_start_x = (touch.move_button->x2 + touch.move_button->x1) / 2; - - // start move instanly - touch.forward = round(((touch.move_button->y2 + touch.move_button->y1) - y * 2) / (touch.move_button->y2 - touch.move_button->y1)); - touch.side = round((x * 2 - (touch.move_button->x2 + touch.move_button->x1)) / (touch.move_button->x2 - touch.move_button->x1)); - } + Con_DPrintf( S_ERROR "Touch: touch_move on look finger %d!\n", fingerID ); + continue; } - // initialize look - if( button->type == touch_look ) + // initialize move mode + touch.move_finger = fingerID; + touch.move_button = button; + + if( button->type == touch_move ) { - if( touch.look_finger !=-1 ) + // initial position is first touch + touch.move_start_x = x; + touch.move_start_y = y; + } + else + { + // initial position is button center + touch.move_start_y = ( button->y2 + button->y1 ) / 2; + touch.move_start_x = ( button->x2 + button->x1 ) / 2; + + // start move instanly + touch.forward = (( button->y2 + button->y1 ) - y * 2 ) / ( button->y2 - button->y1 ); + touch.side = (x * 2 - ( button->x2 + button->x1 )) / ( button->x2 - button->x1 ); + + // same as joy, but round + if( button->type == touch_dpad ) { - // prevent initializing look while already looking - // revert finger switch, leave first finger - button->finger = touch.look_finger; - continue; + touch.forward = round( touch.forward ); + touch.side = round( touch.side ); } - - result = true; - - if( touch.move_finger == fingerID ) - { - touch_button_t *newbutton; - - // this is an error, try recover - touch.move_finger = touch.look_finger = -1; - - // player touched touch_move with enabled look mode - // and same finger id. release all move triggers - for( newbutton = list->first; newbutton; newbutton = newbutton->next ) - if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1; - - Con_Printf( S_ERROR "touch: touch_look on move finger %d!\n", fingerID ); - continue; - } - - touch.look_finger = fingerID; } } - } + // initialize look + else if( button->type == touch_look ) + { + if( touch.look_finger !=-1 ) + { + // prevent initializing look while already looking + // revert finger switch, leave first finger + button->finger = touch.look_finger; + continue; + } - if( type == event_up ) + result = true; + + if( touch.move_finger == fingerID ) + { + touch_button_t *newbutton; + + // this is an error, try recover + touch.move_finger = touch.look_finger = -1; + + // player touched touch_move with enabled look mode + // and same finger id. release all move triggers + for( newbutton = list->first; newbutton; newbutton = newbutton->next ) + { + if( newbutton->type == touch_move || newbutton->type == touch_look ) + newbutton->finger = -1; + } + + Con_Printf( S_ERROR "touch: touch_look on move finger %d!\n", fingerID ); + continue; + } + + touch.look_finger = fingerID; + } + } + else if( type == event_up ) { // no bounds check here. // button released when finger released - if( fingerID == button->finger ) + if( fingerID != button->finger ) + continue; + + button->finger = -1; + + // handle +command, replace by -command + if( button->type == touch_command ) { - button->finger = -1; - - // handle +command, replace by -command - if( button->type == touch_command ) + if( button->command[0] == '+' ) { - if( button->command[0] == '+' ) - { - char command[256]; + char command[256]; - Q_snprintf( command, sizeof( command ), "%s\n", button->command ); - - command[0] = '-'; - - if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED )) - Cbuf_AddFilteredText( command ); - else Cbuf_AddText( command ); - } - - // disable precision mode - if( FBitSet( B(flags), TOUCH_FL_PRECISION )) - touch.precision = false; - - result = true; + Q_snprintf( command, sizeof( command ), "-%s\n", &button->command[1] ); + if( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) + Cbuf_AddFilteredText( command ); + else Cbuf_AddText( command ); } - // handle wheel end - if( button->type == touch_wheel ) - { - if( touch.wheel_count ) - { - if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED )) - Cbuf_AddFilteredText( touch.wheel_end ); - else Cbuf_AddText( touch.wheel_end ); - } + // disable precision mode + if( FBitSet( button->flags, TOUCH_FL_PRECISION )) + touch.precision = false; - // disable precision mode - if( B(flags) & TOUCH_FL_PRECISION ) - touch.precision = false; - - touch.wheel_finger = -1; - - result = true; - } - - // release motion buttons - if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) - { - touch.move_finger = -1; - touch.forward = touch.side = 0; - touch.move_button = NULL; - } - - // release look buttons - if( button->type == touch_look ) - { - touch.look_finger = -1; - } + result = true; } + // handle wheel end + else if( button->type == touch_wheel ) + { + if( touch.wheel_count ) + { + if( FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED )) + Cbuf_AddFilteredText( touch.wheel_end ); + else Cbuf_AddText( touch.wheel_end ); + } + + // disable precision mode + if( FBitSet( button->flags, TOUCH_FL_PRECISION )) + touch.precision = false; + + touch.wheel_finger = -1; + + result = true; + } + // release motion buttons + else if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad ) + { + touch.move_finger = -1; + touch.forward = touch.side = 0; + touch.move_button = NULL; + } + // release look buttons + else if( button->type == touch_look ) + touch.look_finger = -1; } } return result; } -static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y, float dx, float dy ) +static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y ) { touch_button_t *button; // edit buttons are on y1 if( type == event_down ) { - if( (x < GRID_X) && (y < GRID_Y) ) + if( x < GRID_X && y < GRID_Y ) { - touch.showeditbuttons ^= true; + touch.showeditbuttons = !touch.showeditbuttons; return true; } - if( touch.showeditbuttons && Touch_ButtonPress( &touch.list_edit, type, fingerID, x, y, dx, dy ) ) + if( touch.showeditbuttons && Touch_ButtonPress( &touch.list_edit, type, fingerID, x, y )) return true; } @@ -1901,22 +2002,19 @@ static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, fl { if( type == event_down ) { - if( ( x > button->x1 && - x < button->x2 ) && - ( y < button->y2 && - y > button->y1 ) ) + if( x > button->x1 && x < button->x2 && y > button->y1 && y < button->y2 ) { button->finger = fingerID; // do not edit NOEDIT buttons if( FBitSet( button->flags, TOUCH_FL_NOEDIT )) - continue; + continue; touch.edit = button; touch.selection = NULL; // make button last to bring it up - if( ( button->next ) && ( button->type == touch_command ) ) + if( button->next && button->type == touch_command ) { if( button->prev ) button->prev->next = button->next; @@ -1933,9 +2031,12 @@ static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, fl return true; } } + if( type == event_up ) + { if( fingerID == button->finger ) button->finger = -1; + } } if( type == event_down ) @@ -1952,35 +2053,45 @@ static int Touch_ControlsEvent( touchEventType type, int fingerID, float x, floa if( touch.state == state_edit_move ) { Touch_EditMove( type, fingerID, x, y, dx, dy ); - return 1; + return true; } - if( touch.state == state_edit && Touch_ButtonEdit( type, fingerID, x, y, dx, dy ) ) + if( touch.state == state_edit && Touch_ButtonEdit( type, fingerID, x, y )) return true; - if( Touch_ButtonPress( &touch.list_user, type, fingerID, x, y, dx, dy ) ) + if( Touch_ButtonPress( &touch.list_user, type, fingerID, x, y )) return true; if( type == event_motion ) - Touch_Motion( type, fingerID, x, y, dx, dy ); + Touch_Motion( fingerID, x, y, dx, dy ); return true; } int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) { - +// Con_Printf("%f %f\n", TO_SCRN_X(x), TO_SCRN_Y(y)); // simulate menu mouse click - if( cls.key_dest != key_game && !CVAR_TO_BOOL(touch_in_menu) ) + if( cls.key_dest != key_game && !touch_in_menu.value ) { - touch.move_finger = touch.resize_finger = touch.look_finger = -1; + touch.move_finger = touch.resize_finger = touch.look_finger = touch.wheel_finger = -1; // Hack for keyboard, hope it help + // a1ba: this is absolutely horrible if( cls.key_dest == key_console || cls.key_dest == key_message ) { - Key_EnableTextInput( true, true ); + static float x1 = 0.0f; + x1 += dx; + + if( type == event_up ) // don't show keyboard on every tap + { + Key_EnableTextInput( true, true ); + x1 = 0.0f; + } + if( cls.key_dest == key_console ) { static float y1 = 0; y1 += dy; if( dy > 0.4f ) Con_Bottom(); + if( y1 > 0.01f ) { Con_PageUp( 1 ); @@ -1995,41 +2106,59 @@ int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx // exit of console area if( type == event_down && x < 0.1f && y > 0.9f ) - Cbuf_AddText( "escape\n" ); + { + if( cls.key_dest == key_console ) + Key_Console( K_ESCAPE ); + else + Key_Message( K_ESCAPE ); + return 0; + } + + // swipe from edge to exit console/chat + if(( x > 0.7f && x1 < -0.1f ) || ( x < 0.3f && x1 > 0.1f )) + { + if( cls.key_dest == key_console ) + Key_Console( K_ESCAPE ); + else + Key_Message( K_ESCAPE ); + x1 = 0.0f; + return 0; + } } - UI_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) ); + UI_MouseMove( x * refState.width, y * refState.height ); + //MsgDev( D_NOTE, "touch %d %d\n", TO_SCRN_X(x), TO_SCRN_Y(y) ); if( type == event_down ) Key_Event( K_MOUSE1, true ); + if( type == event_up ) Key_Event( K_MOUSE1, false ); + return 0; } if( VGui_IsActive() ) { - VGui_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) ); + VGui_MouseMove( x * refState.width, y * refState.height ); - if( type != event_motion ) - VGui_KeyEvent( K_MOUSE1, type == event_down ? 1 : 0 ); - - // allow scoreboard scroll - if( host.mouse_visible && type == event_motion ) - return 0; + switch( type ) + { + case event_down: + VGui_MouseEvent( K_MOUSE1, 1 ); + break; + case event_up: + VGui_MouseEvent( K_MOUSE1, 0 ); + break; + default: + break; + } } - if( !touch.initialized || (!CVAR_TO_BOOL(touch_enable) && !touch.clientonly) ) - { -#if 0 - if( type == event_down ) - Key_Event( K_MOUSE1, true ); - if( type == event_up ) - Key_Event( K_MOUSE1, false ); - Android_AddMove( dx * (float)refState.width, dy * (float)refState.height ); -#endif - return 0; - } + if( !touch.initialized || ( !touch_enable.value && !touch.clientonly )) + return false; + + y *= (float)refState.height / refState.width / Touch_AspectRatio(); if( clgame.dllFuncs.pfnTouchEvent && clgame.dllFuncs.pfnTouchEvent( type, fingerID, x, y, dx, dy ) ) return true; @@ -2048,35 +2177,54 @@ void Touch_GetMove( float *forward, float *side, float *yaw, float *pitch ) void Touch_KeyEvent( int key, int down ) { - int xi, yi; - float x, y; static float lx, ly; + static int kidNamedFinger = -1; + touchEventType event; + float x, y; + int finger, xi, yi; - if( !CVAR_TO_BOOL(touch_emulate) ) + if( !Touch_WantVisibleCursor( )) + return; + + if( !key ) { - if( CVAR_TO_BOOL(touch_enable) ) + if( kidNamedFinger < 0 ) return; - if( !touch.clientonly ) - return; + finger = kidNamedFinger; + event = event_motion; + } + else + { + finger = key == K_MOUSE1 ? 0 : 1; + if( down ) + { + event = event_down; + kidNamedFinger = finger; + } + else + { + event = event_up; + kidNamedFinger = -1; + } } Platform_GetMousePos( &xi, &yi ); - x = xi/SCR_W; - y = yi/SCR_H; + x = xi / (float)refState.width; + y = yi / (float)refState.height; - if( cls.key_dest == key_menu && down < 2 && key == K_MOUSE1 ) - { - UI_MouseMove( xi, yi ); - UI_KeyEvent( key, down ); - } + // Con_DPrintf( "event %d %.2f %.2f %.2f %.2f\n", event, x, y, x - lx, y - ly ); - if( down == 1 ) - Touch_ControlsEvent( event_down, key == K_MOUSE1?0:1, x, y, 0, 0 ); - else - Touch_ControlsEvent( down? event_motion: event_up, key == K_MOUSE1?0:1, x, y, x-lx, y-ly ); - lx = x, ly = y; + IN_TouchEvent( event, finger, x, y, x - lx, y - ly ); + + lx = x; + ly = y; +} + +qboolean Touch_WantVisibleCursor( void ) +{ + return ( touch_enable.value && touch_emulate.value ) || touch.clientonly || touch_in_menu.value; } void Touch_Shutdown( void ) diff --git a/engine/client/input.c b/engine/client/input.c index 69c41ca8..fab83f8c 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -13,15 +13,17 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#if XASH_SDL == 2 +#include +#elif XASH_SDL == 3 +#include +#endif + #include "common.h" #include "input.h" #include "client.h" #include "vgui_draw.h" - -#if XASH_SDL -#include -#endif - +#include "cursor_type.h" #include "platform/platform.h" void* in_mousecursor; @@ -36,18 +38,17 @@ static struct inputstate_s float lastpitch, lastyaw; } inputstate; -extern convar_t *vid_fullscreen; -convar_t *m_pitch; -convar_t *m_yaw; +CVAR_DEFINE_AUTO( m_pitch, "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse pitch value" ); +CVAR_DEFINE_AUTO( m_yaw, "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse yaw value" ); +CVAR_DEFINE_AUTO( m_ignore, DEFAULT_M_IGNORE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "ignore mouse events" ); +static CVAR_DEFINE_AUTO( look_filter, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "filter look events making it smoother" ); +static CVAR_DEFINE_AUTO( m_rawinput, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable mouse raw input" ); -convar_t *m_ignore; -convar_t *cl_forwardspeed; -convar_t *cl_sidespeed; -convar_t *cl_backspeed; -convar_t *look_filter; -convar_t *m_rawinput; +static CVAR_DEFINE_AUTO( cl_forwardspeed, "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default forward move speed" ); +static CVAR_DEFINE_AUTO( cl_backspeed, "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default back move speed" ); +static CVAR_DEFINE_AUTO( cl_sidespeed, "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default side move speed" ); -static qboolean s_bRawInput, s_bMouseGrab; +static CVAR_DEFINE_AUTO( m_grab_debug, "0", FCVAR_PRIVILEGED, "show debug messages on mouse state change" ); /* ================ @@ -60,10 +61,10 @@ uint IN_CollectInputDevices( void ) { uint ret = 0; - if( !m_ignore->value ) // no way to check is mouse connected, so use cvar only + if( !m_ignore.value ) // no way to check is mouse connected, so use cvar only ret |= INPUT_DEVICE_MOUSE; - if( CVAR_TO_BOOL(touch_enable) ) + if( touch_enable.value ) ret |= INPUT_DEVICE_TOUCH; if( Joy_IsActive() ) // connected or enabled @@ -88,19 +89,19 @@ player is connected to the server */ void IN_LockInputDevices( qboolean lock ) { - extern convar_t *joy_enable; // private to input system + extern convar_t joy_enable; // private to input system if( lock ) { - SetBits( m_ignore->flags, FCVAR_READ_ONLY ); - SetBits( joy_enable->flags, FCVAR_READ_ONLY ); - SetBits( touch_enable->flags, FCVAR_READ_ONLY ); + SetBits( m_ignore.flags, FCVAR_READ_ONLY ); + SetBits( joy_enable.flags, FCVAR_READ_ONLY ); + SetBits( touch_enable.flags, FCVAR_READ_ONLY ); } else { - ClearBits( m_ignore->flags, FCVAR_READ_ONLY ); - ClearBits( joy_enable->flags, FCVAR_READ_ONLY ); - ClearBits( touch_enable->flags, FCVAR_READ_ONLY ); + ClearBits( m_ignore.flags, FCVAR_READ_ONLY ); + ClearBits( joy_enable.flags, FCVAR_READ_ONLY ); + ClearBits( touch_enable.flags, FCVAR_READ_ONLY ); } } @@ -110,14 +111,15 @@ void IN_LockInputDevices( qboolean lock ) IN_StartupMouse =========== */ -void IN_StartupMouse( void ) +static void IN_StartupMouse( void ) { - m_ignore = Cvar_Get( "m_ignore", DEFAULT_M_IGNORE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "ignore mouse events" ); + Cvar_RegisterVariable( &m_ignore ); - m_pitch = Cvar_Get( "m_pitch", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse pitch value" ); - m_yaw = Cvar_Get( "m_yaw", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse yaw value" ); - look_filter = Cvar_Get( "look_filter", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "filter look events making it smoother" ); - m_rawinput = Cvar_Get( "m_rawinput", "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable mouse raw input" ); + Cvar_RegisterVariable( &m_pitch ); + Cvar_RegisterVariable( &m_yaw ); + Cvar_RegisterVariable( &look_filter ); + Cvar_RegisterVariable( &m_rawinput ); + Cvar_RegisterVariable( &m_grab_debug ); // You can use -nomouse argument to prevent using mouse from client // -noenginemouse will disable all mouse input @@ -126,11 +128,6 @@ void IN_StartupMouse( void ) in_mouseinitialized = true; } -void GAME_EXPORT IN_SetCursor( void *hCursor ) -{ - // stub -} - /* =========== IN_MouseSavePos @@ -173,7 +170,31 @@ Called when key_dest is changed */ void IN_ToggleClientMouse( int newstate, int oldstate ) { - if( newstate == oldstate ) return; + if( newstate == oldstate ) + return; + + // since SetCursorType controls cursor visibility + // execute it first, and then check mouse grab state + if( newstate == key_menu || newstate == key_console ) + { + Platform_SetCursorType( dc_arrow ); + +#if XASH_USE_EVDEV + Evdev_SetGrab( false ); +#endif + } + else + { + Platform_SetCursorType( dc_none ); + +#if XASH_USE_EVDEV + Evdev_SetGrab( true ); +#endif + } + + // don't leave the user without cursor if they enabled m_ignore + if( m_ignore.value ) + return; if( oldstate == key_game ) { @@ -183,84 +204,89 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) { IN_ActivateMouse(); } +} - if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) +void IN_SetRelativeMouseMode( qboolean set ) +{ + static qboolean s_bRawInput; + qboolean verbose = m_grab_debug.value ? true : false; + + if( set && !s_bRawInput ) { -#if XASH_ANDROID - Android_ShowMouse( true ); -#endif -#ifdef XASH_USE_EVDEV - Evdev_SetGrab( false ); -#endif +#if XASH_SDL >= 2 + SDL_GetRelativeMouseState( NULL, NULL ); +#if XASH_SDL == 2 + SDL_SetRelativeMouseMode( SDL_TRUE ); +#else // XASH_SDL != 2 + SDL_SetWindowRelativeMouseMode( host.hWnd, true ); +#endif // XASH_SDL != 2 +#endif // XASH_SDL >= 2 + s_bRawInput = true; + if( verbose ) + Con_Printf( "%s: true\n", __func__ ); } - else + else if( !set && s_bRawInput ) { -#if XASH_ANDROID - Android_ShowMouse( false ); -#endif -#ifdef XASH_USE_EVDEV - Evdev_SetGrab( true ); -#endif +#if XASH_SDL >= 2 + SDL_GetRelativeMouseState( NULL, NULL ); +#if XASH_SDL == 2 + SDL_SetRelativeMouseMode( SDL_FALSE ); +#else // XASH_SDL != 2 + SDL_SetWindowRelativeMouseMode( host.hWnd, false ); +#endif // XASH_SDL != 2 +#endif // XASH_SDL >= 2 + + s_bRawInput = false; + if( verbose ) + Con_Printf( "%s: false\n", __func__ ); } } -void IN_CheckMouseState( qboolean active ) +void IN_SetMouseGrab( qboolean set ) { + static qboolean s_bMouseGrab; + qboolean verbose = m_grab_debug.value ? true : false; + + if( set && !s_bMouseGrab ) + { + Platform_SetMouseGrab( true ); + + s_bMouseGrab = true; + if( verbose ) + Con_Printf( "%s: true\n", __func__ ); + } + else if( !set && s_bMouseGrab ) + { + Platform_SetMouseGrab( false ); + + s_bMouseGrab = false; + if( verbose ) + Con_Printf( "%s: false\n", __func__ ); + } +} + +static void IN_CheckMouseState( qboolean active ) +{ + qboolean use_raw_input; + #if XASH_WIN32 - qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl || clgame.dllFuncs.pfnLookEvent; + use_raw_input = ( m_rawinput.value && clgame.client_dll_uses_sdl ) || clgame.dllFuncs.pfnLookEvent != NULL; #else - qboolean useRawInput = true; // always use SDL code + use_raw_input = true; // always use SDL code #endif - if( active && useRawInput && !host.mouse_visible && cls.state == ca_active ) - { - if( !s_bRawInput ) - { -#if XASH_SDL == 2 - SDL_GetRelativeMouseState( NULL, NULL ); - SDL_SetRelativeMouseMode( SDL_TRUE ); -#endif + if( m_ignore.value ) + active = false; - // Con_Printf( "Enable relative mode\n" ); - s_bRawInput = true; - } - } + if( active && use_raw_input && !host.mouse_visible && cls.state == ca_active ) + IN_SetRelativeMouseMode( true ); else - { - if( s_bRawInput ) - { -#if XASH_SDL == 2 - SDL_GetRelativeMouseState( NULL, NULL ); - SDL_SetRelativeMouseMode( SDL_FALSE ); -#endif - // Con_Printf( "Disable relative mode\n" ); - s_bRawInput = false; - } - } + IN_SetRelativeMouseMode( false ); if( active && !host.mouse_visible && cls.state == ca_active ) - { - if( !s_bMouseGrab ) - { -#if XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); -#endif - // Con_Printf( "Enable grab\n" ); - s_bMouseGrab = true; - } - } + IN_SetMouseGrab( true ); else - { - if( s_bMouseGrab ) - { -#if XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif - - // Con_Printf( "Disable grab\n" ); - s_bMouseGrab = false; - } - } + IN_SetMouseGrab( false ); } /* @@ -306,27 +332,27 @@ void IN_DeactivateMouse( void ) IN_MouseMove ================ */ -void IN_MouseMove( void ) +static void IN_MouseMove( void ) { - POINT current_pos; + int x, y; if( !in_mouseinitialized ) return; + if( Touch_WantVisibleCursor( )) + { + // touch emulation overrides all input + Touch_KeyEvent( 0, 0 ); + return; + } + // find mouse movement - Platform_GetMousePos( ¤t_pos.x, ¤t_pos.y ); + Platform_GetMousePos( &x, &y ); - VGui_MouseMove( current_pos.x, current_pos.y ); - - // HACKHACK: show cursor in UI, as mainui doesn't call - // platform-dependent SetCursor anymore -#if XASH_SDL - if( UI_IsVisible() ) - SDL_ShowCursor( SDL_TRUE ); -#endif + VGui_MouseMove( x, y ); // if the menu is visible, move the menu cursor - UI_MouseMove( current_pos.x, current_pos.y ); + UI_MouseMove( x, y ); } /* @@ -336,8 +362,6 @@ IN_MouseEvent */ void IN_MouseEvent( int key, int down ) { - int i; - if( !in_mouseinitialized ) return; @@ -345,10 +369,15 @@ void IN_MouseEvent( int key, int down ) SetBits( in_mstate, BIT( key )); else ClearBits( in_mstate, BIT( key )); - if( cls.key_dest == key_game ) + // touch emulation overrides all input + if( Touch_WantVisibleCursor( )) + { + Touch_KeyEvent( K_MOUSE1 + key, down ); + } + else if( cls.key_dest == key_game ) { // perform button actions - VGui_KeyEvent( K_MOUSE1 + key, down ); + VGui_MouseEvent( K_MOUSE1 + key, down ); // don't do Key_Event here // client may override IN_MouseEvent @@ -363,6 +392,23 @@ void IN_MouseEvent( int key, int down ) } } +/* +============== +IN_MWheelEvent + +direction is negative for wheel down, otherwise wheel up +============== +*/ +void IN_MWheelEvent( int y ) +{ + int b = y > 0 ? K_MWHEELUP : K_MWHEELDOWN; + + VGui_MWheelEvent( y ); + + Key_Event( b, true ); + Key_Event( b, false ); +} + /* =========== IN_Shutdown @@ -372,7 +418,7 @@ void IN_Shutdown( void ) { IN_DeactivateMouse( ); -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_Shutdown(); #endif @@ -387,9 +433,9 @@ IN_Init */ void IN_Init( void ) { - cl_forwardspeed = Cvar_Get( "cl_forwardspeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default forward move speed" ); - cl_backspeed = Cvar_Get( "cl_backspeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default back move speed" ); - cl_sidespeed = Cvar_Get( "cl_sidespeed", "400", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_FILTERABLE, "Default side move speed" ); + Cvar_RegisterVariable( &cl_forwardspeed ); + Cvar_RegisterVariable( &cl_backspeed ); + Cvar_RegisterVariable( &cl_sidespeed ); if( !Host_IsDedicated() ) { @@ -399,7 +445,7 @@ void IN_Init( void ) Touch_Init(); -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_Init(); #endif } @@ -426,8 +472,8 @@ static void IN_JoyAppendMove( usercmd_t *cmd, float forwardmove, float sidemove { static uint moveflags = T | S; - if( forwardmove ) cmd->forwardmove = forwardmove * cl_forwardspeed->value; - if( sidemove ) cmd->sidemove = sidemove * cl_sidespeed->value; + if( forwardmove ) cmd->forwardmove = forwardmove * cl_forwardspeed.value; + if( sidemove ) cmd->sidemove = sidemove * cl_sidespeed.value; if( forwardmove ) { @@ -502,10 +548,10 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y { float x, y; Platform_MouseMove( &x, &y ); - *pitch += y * m_pitch->value; - *yaw -= x * m_yaw->value; + *pitch += y * m_pitch.value; + *yaw -= x * m_yaw.value; -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV IN_EvdevMove( yaw, pitch ); #endif } @@ -513,7 +559,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y Joy_FinalizeMove( forward, side, yaw, pitch ); Touch_GetMove( forward, side, yaw, pitch ); - if( look_filter->value ) + if( look_filter.value ) { *pitch = ( inputstate.lastpitch + *pitch ) / 2; *yaw = ( inputstate.lastyaw + *yaw ) / 2; @@ -530,10 +576,9 @@ IN_EngineAppendMove Called from cl_main.c after generating command in client ================ */ -void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) +void IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active ) { float forward, side, pitch, yaw; - usercmd_t *cmd = cmd1; if( clgame.dllFuncs.pfnLookEvent ) return; @@ -561,9 +606,9 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) } } -void IN_Commands( void ) +static void IN_Commands( void ) { -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV IN_EvdevFrame(); #endif @@ -571,7 +616,7 @@ void IN_Commands( void ) { float forward = 0, side = 0, pitch = 0, yaw = 0; - IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ) ); + IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !m_ignore.value ); if( cls.key_dest == key_game ) { @@ -595,8 +640,6 @@ Called every frame, even if not generating commands */ void Host_InputFrame( void ) { - Sys_SendKeyEvents (); - IN_Commands(); IN_MouseMove(); diff --git a/engine/client/input.h b/engine/client/input.h index 32ad2b6b..138cbc68 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -25,6 +25,7 @@ INPUT */ #include "keydefs.h" +#include "usercmd.h" // // input.c @@ -34,19 +35,22 @@ void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); void IN_MouseEvent( int key, int down ); +void IN_MWheelEvent( int direction ); void IN_ActivateMouse( void ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); void IN_MouseRestorePos( void ); void IN_ToggleClientMouse( int newstate, int oldstate ); -void IN_SetCursor( void *hCursor ); uint IN_CollectInputDevices( void ); void IN_LockInputDevices( qboolean lock ); -void IN_EngineAppendMove( float frametime, void *cmd, qboolean active ); +void IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active ); -extern convar_t *m_yaw; -extern convar_t *m_pitch; +void IN_SetRelativeMouseMode( qboolean set ); +void IN_SetMouseGrab( qboolean set ); + +extern convar_t m_yaw; +extern convar_t m_pitch; // // in_touch.c // @@ -57,8 +61,7 @@ typedef enum event_motion } touchEventType; -extern convar_t *touch_enable; -extern convar_t *touch_emulate; +extern convar_t touch_enable; void Touch_Draw( void ); void Touch_SetClientOnly( byte state ); @@ -73,6 +76,8 @@ void Touch_GetMove( float * forward, float *side, float *yaw, float *pitch ); void Touch_ResetDefaultButtons( void ); int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ); void Touch_KeyEvent( int key, int down ); +qboolean Touch_WantVisibleCursor( void ); +void Touch_NotifyResize( void ); // // in_joy.c @@ -101,18 +106,21 @@ typedef enum engineAxis_e JOY_AXIS_NULL } engineAxis_t; +typedef enum joy_calibration_state_s +{ + JOY_NOT_CALIBRATED = 0, + JOY_CALIBRATING, + JOY_FAILED_TO_CALIBRATE, + JOY_CALIBRATED +} joy_calibration_state_t; + qboolean Joy_IsActive( void ); -void Joy_HatMotionEvent( byte hat, byte value ); -void Joy_AxisMotionEvent( byte axis, short value ); -void Joy_KnownAxisMotionEvent( engineAxis_t engineAxis, short value ); -void Joy_BallMotionEvent( byte ball, short xrel, short yrel ); -void Joy_ButtonEvent( byte button, byte down ); -void Joy_AddEvent( void ); -void Joy_RemoveEvent( void ); +void Joy_SetCapabilities( qboolean have_gyro ); +void Joy_SetCalibrationState( joy_calibration_state_t state ); +void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value ); +void Joy_GyroEvent( vec3_t data ); void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw ); void Joy_Init( void ); void Joy_Shutdown( void ); -void Joy_EnableTextInput(qboolean enable, qboolean force); - #endif//INPUT_H diff --git a/engine/client/keys.c b/engine/client/keys.c index 682f44ad..ee88f06d 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -34,13 +34,13 @@ typedef struct keyname_s const char *binding; // default bind } keyname_t; -enginekey_t keys[256]; +static enginekey_t keys[256]; -keyname_t keynames[] = +static const keyname_t keynames[] = { {"TAB", K_TAB, "" }, {"ENTER", K_ENTER, "" }, -{"ESCAPE", K_ESCAPE, "escape" }, // hardcoded +{"ESCAPE", K_ESCAPE, "cancelselect" }, // hardcoded {"SPACE", K_SPACE, "+jump" }, {"BACKSPACE", K_BACKSPACE, "" }, {"UPARROW", K_UPARROW, "+forward" }, @@ -51,7 +51,7 @@ keyname_t keynames[] = {"CTRL", K_CTRL, "+attack" }, {"SHIFT", K_SHIFT, "+speed" }, {"CAPSLOCK", K_CAPSLOCK, "" }, -{"SCROLLOCK", K_SCROLLOCK, "" }, +{"SCROLLOCK", K_SCROLLLOCK, "" }, {"F1", K_F1, "cmd help" }, {"F2", K_F2, "menu_savegame" }, {"F3", K_F3, "menu_loadgame" }, @@ -104,9 +104,9 @@ keyname_t keynames[] = {"B_BUTTON", K_B_BUTTON, "+use"}, {"X_BUTTON", K_X_BUTTON, "+reload"}, {"Y_BUTTON", K_Y_BUTTON, "impulse 100"}, // Flashlight -{"BACK", K_BACK_BUTTON, "cancelselect"}, // Menu +{"BACK", K_BACK_BUTTON, "pause"}, // Menu {"MODE", K_MODE_BUTTON, ""}, -{"START", K_START_BUTTON, "pause"}, +{"START", K_START_BUTTON, "cancelselect"}, {"STICK1", K_LSTICK, "+speed"}, {"STICK2", K_RSTICK, "+duck"}, {"L1_BUTTON", K_L1_BUTTON, "+duck"}, @@ -123,13 +123,13 @@ keyname_t keynames[] = {"JOY4" , K_JOY4 , ""}, {"C_BUTTON", K_C_BUTTON, ""}, {"Z_BUTTON", K_Z_BUTTON, ""}, -{"AUX20", K_AUX20, ""}, // generic -{"AUX21", K_AUX21, ""}, -{"AUX22", K_AUX22, ""}, -{"AUX23", K_AUX23, ""}, -{"AUX24", K_AUX24, ""}, -{"AUX25", K_AUX25, ""}, -{"AUX26", K_AUX26, ""}, +{"MISC_BUTTON", K_MISC_BUTTON, ""}, +{"PADDLE1", K_PADDLE1_BUTTON, ""}, +{"PADDLE2", K_PADDLE2_BUTTON, ""}, +{"PADDLE3", K_PADDLE3_BUTTON, ""}, +{"PADDLE4", K_PADDLE4_BUTTON, ""}, +{"TOUCHPAD", K_TOUCHPAD, ""}, +{"AUX26", K_AUX26, ""}, // generic {"AUX27", K_AUX27, ""}, {"AUX28", K_AUX28, ""}, {"AUX29", K_AUX29, ""}, @@ -139,13 +139,12 @@ keyname_t keynames[] = // raw semicolon seperates commands {"SEMICOLON", ';', "" }, -{NULL, 0, NULL }, }; static void OSK_EnableTextInput( qboolean enable, qboolean force ); static qboolean OSK_KeyEvent( int key, int down ); -static convar_t *osk_enable; -static convar_t *key_rotate; +static CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "enable built-in on-screen keyboard" ); +static CVAR_DEFINE_AUTO( key_rotate, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "rotate arrow keys (0-3)" ); /* =================== @@ -159,18 +158,6 @@ int GAME_EXPORT Key_IsDown( int keynum ) return keys[keynum].down; } -/* -=================== -Key_GetBind -=================== -*/ -const char *Key_IsBind( int keynum ) -{ - if( keynum == -1 || !keys[keynum].binding ) - return NULL; - return keys[keynum].binding; -} - /* =================== Key_StringToKeynum @@ -184,48 +171,25 @@ the K_* names are matched up. to be configured even if they don't have defined names. =================== */ -int Key_StringToKeynum( const char *str ) +static int Key_StringToKeynum( const char *str ) { - keyname_t *kn; + int i; - if( !str || !str[0] ) return -1; - if( !str[1] ) return str[0]; + if( !str || !str[0] ) + return -1; + + if( !str[1] ) + return str[0]; // check for hex code if( str[0] == '0' && str[1] == 'x' && Q_strlen( str ) == 4 ) - { - int n1, n2; - - n1 = str[2]; - if( n1 >= '0' && n1 <= '9' ) - { - n1 -= '0'; - } - else if( n1 >= 'a' && n1 <= 'f' ) - { - n1 = n1 - 'a' + 10; - } - else n1 = 0; - - n2 = str[3]; - if( n2 >= '0' && n2 <= '9' ) - { - n2 -= '0'; - } - else if( n2 >= 'a' && n2 <= 'f' ) - { - n2 = n2 - 'a' + 10; - } - else n2 = 0; - - return n1 * 16 + n2; - } + return COM_Nibble( str[2] ) << 4 | COM_Nibble( str[3] ); // scan for a text match - for( kn = keynames; kn->name; kn++ ) + for( i = 0; i < ARRAYSIZE( keynames ); i++ ) { - if( !Q_stricmp( str, kn->name )) - return kn->keynum; + if( !Q_stricmp( str, keynames[i].name )) + return keynames[i].keynum; } return -1; @@ -241,15 +205,17 @@ given keynum. */ const char *Key_KeynumToString( int keynum ) { - keyname_t *kn; static char tinystr[5]; int i, j; - if ( keynum == -1 ) return ""; - if ( keynum < 0 || keynum > 255 ) return ""; + if( keynum == -1 ) + return ""; + + if( keynum < 0 || keynum > 255 ) + return ""; // check for printable ascii (don't use quote) - if( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' && keynum != K_SCROLLOCK ) + if( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' && keynum != K_SCROLLLOCK ) { tinystr[0] = keynum; tinystr[1] = 0; @@ -257,10 +223,10 @@ const char *Key_KeynumToString( int keynum ) } // check for a key string - for( kn = keynames; kn->name; kn++ ) + for( i = 0; i < ARRAYSIZE( keynames ); i++ ) { - if( keynum == kn->keynum ) - return kn->name; + if( keynum == keynames[i].keynum ) + return keynames[i].name; } // make a hex string @@ -313,7 +279,7 @@ const char *Key_GetBinding( int keynum ) Key_GetKey =================== */ -int Key_GetKey( const char *pBinding ) +static int Key_GetKey( const char *pBinding ) { int i, len; const char *p; @@ -339,12 +305,23 @@ int Key_GetKey( const char *pBinding ) return -1; } +/* +============= +Key_LookupBinding + +============= +*/ +const char *Key_LookupBinding( const char *pBinding ) +{ + return Key_KeynumToString( Key_GetKey( pBinding )); +} + /* =================== Key_Unbind_f =================== */ -void Key_Unbind_f( void ) +static void Key_Unbind_f( void ) { int b; @@ -362,6 +339,12 @@ void Key_Unbind_f( void ) return; } + if( b == K_ESCAPE ) + { + Con_Printf( "Can't unbind ESCAPE key\n" ); + return; + } + Key_SetBinding( b, "" ); } @@ -370,18 +353,19 @@ void Key_Unbind_f( void ) Key_Unbindall_f =================== */ -void Key_Unbindall_f( void ) +static void Key_Unbindall_f( void ) { int i; - for( i = 0; i < 256; i++ ) + for( i = 0; i < ARRAYSIZE( keys ); i++ ) { if( keys[i].binding ) Key_SetBinding( i, "" ); } // set some defaults - Key_SetBinding( K_ESCAPE, "escape" ); + Key_SetBinding( K_ESCAPE, "cancelselect" ); + Key_SetBinding( K_START_BUTTON, "cancelselect" ); } /* @@ -389,21 +373,20 @@ void Key_Unbindall_f( void ) Key_Reset_f =================== */ -void Key_Reset_f( void ) +static void Key_Reset_f( void ) { - keyname_t *kn; int i; // clear all keys first - for( i = 0; i < 256; i++ ) + for( i = 0; i < ARRAYSIZE( keys ); i++ ) { if( keys[i].binding ) Key_SetBinding( i, "" ); } // apply default values - for( kn = keynames; kn->name; kn++ ) - Key_SetBinding( kn->keynum, kn->binding ); + for( i = 0; i < ARRAYSIZE( keynames ); i++ ) + Key_SetBinding( keynames[i].keynum, keynames[i].binding ); } /* @@ -411,7 +394,7 @@ void Key_Reset_f( void ) Key_Bind_f =================== */ -void Key_Bind_f( void ) +static void Key_Bind_f( void ) { char cmd[1024]; int i, c, b; @@ -445,8 +428,8 @@ void Key_Bind_f( void ) for( i = 2; i < c; i++ ) { - Q_strcat( cmd, Cmd_Argv( i )); - if( i != ( c - 1 )) Q_strcat( cmd, " " ); + Q_strncat( cmd, Cmd_Argv( i ), sizeof( cmd )); + if( i != ( c - 1 )) Q_strncat( cmd, " ", sizeof( cmd )); } Key_SetBinding( b, cmd ); @@ -484,7 +467,7 @@ Key_Bindlist_f ============ */ -void Key_Bindlist_f( void ) +static void Key_Bindlist_f( void ) { int i; @@ -511,7 +494,7 @@ Key_Init */ void Key_Init( void ) { - keyname_t *kn; + int i; // register our functions Cmd_AddRestrictedCommand( "bind", Key_Bind_f, "binds a command to the specified key in bindmap" ); @@ -522,10 +505,11 @@ void Key_Init( void ) Cmd_AddCommand( "makehelp", Key_EnumCmds_f, "write help.txt that contains all console cvars and cmds" ); // setup default binding. "unbindall" from config.cfg will be reset it - for( kn = keynames; kn->name; kn++ ) Key_SetBinding( kn->keynum, kn->binding ); + for( i = 0; i < ARRAYSIZE( keynames ); i++ ) + Key_SetBinding( keynames[i].keynum, keynames[i].binding ); - osk_enable = Cvar_Get( "osk_enable", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable built-in on-screen keyboard" ); - key_rotate = Cvar_Get( "key_rotate", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "rotate arrow keys (0-3)" ); + Cvar_RegisterVariable( &osk_enable ); + Cvar_RegisterVariable( &key_rotate ); } @@ -534,7 +518,7 @@ void Key_Init( void ) Key_AddKeyCommands =================== */ -void Key_AddKeyCommands( int key, const char *kb, qboolean down ) +static void Key_AddKeyCommands( int key, const char *kb, qboolean down ) { char button[1024]; char *buttonPtr; @@ -552,8 +536,8 @@ void Key_AddKeyCommands( int key, const char *kb, qboolean down ) if( button[0] == '+' ) { // button commands add keynum as a parm - if( down ) Q_sprintf( cmd, "%s %i\n", button, key ); - else Q_sprintf( cmd, "-%s %i\n", button + 1, key ); + if( down ) Q_snprintf( cmd, sizeof( cmd ), "%s %i\n", button, key ); + else Q_snprintf( cmd, sizeof( cmd ), "-%s %i\n", button + 1, key ); Cbuf_AddText( cmd ); } else if( down ) @@ -564,7 +548,7 @@ void Key_AddKeyCommands( int key, const char *kb, qboolean down ) } buttonPtr = button; - while(( kb[i] <= ' ' || kb[i] == ';' ) && kb[i] != 0 ) + while((((byte)kb[i]) <= ' ' || kb[i] == ';' ) && kb[i] != 0 ) i++; } @@ -601,7 +585,7 @@ static qboolean Key_IsAllowedAutoRepeat( int key ) static int Key_Rotate( int key ) { - if( key_rotate->value == 1.0f ) // CW + if( key_rotate.value == 1.0f ) // CW { if( key == K_UPARROW ) key = K_LEFTARROW; @@ -613,7 +597,7 @@ static int Key_Rotate( int key ) key = K_RIGHTARROW; } - else if( key_rotate->value == 3.0f ) // CCW + else if( key_rotate.value == 3.0f ) // CCW { if( key == K_UPARROW ) key = K_RIGHTARROW; @@ -625,7 +609,7 @@ static int Key_Rotate( int key ) key = K_LEFTARROW; } - else if( key_rotate->value == 2.0f ) + else if( key_rotate.value == 2.0f ) { if( key == K_UPARROW ) key = K_DOWNARROW; @@ -712,7 +696,6 @@ void GAME_EXPORT Key_Event( int key, int down ) } VGui_KeyEvent( key, down ); - Touch_KeyEvent( key, down ); // console key is hardcoded, so the user can never unbind it if( key == '`' || key == '~' ) @@ -731,10 +714,10 @@ void GAME_EXPORT Key_Event( int key, int down ) switch( cls.key_dest ) { case key_game: - if( CVAR_TO_BOOL( gl_showtextures )) + if( r_showtextures.value ) { // close texture atlas - Cvar_SetValue( "r_showtextures", 0.0f ); + Cvar_DirectSet( &r_showtextures, "0" ); return; } else if( host.mouse_visible && cls.state != ca_cinematic ) @@ -743,35 +726,31 @@ void GAME_EXPORT Key_Event( int key, int down ) return; // handled in client.dll } break; - case key_message: - Key_Message( key ); - return; - case key_console: - if( cls.state == ca_active && !cl.background ) - Key_SetKeyDest( key_game ); - else UI_SetActiveMenu( true ); - return; - case key_menu: - UI_KeyEvent( key, true ); - return; - default: return; + default: + break; } } if( cls.key_dest == key_menu ) { - // only non printable keys passed - if( !gameui.use_text_api ) - Key_EnableTextInput( true, false ); - //pass printable chars for old menus - if( !gameui.use_text_api && !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ) ) + // classic Xash3D menus don't have an extension that tells engine + // to enable text input + if( !gameui.use_extended_api ) { - if( Key_IsDown( K_SHIFT ) ) + // we don't know if menu wants text input or not + // enable it unconditionally + Key_EnableTextInput( true, false ); + + // pass this key to the menu, if printable + if( !host.textmode && down && ( key >= 32 ) && ( key <= 'z' )) { - key += 'A' - 'a'; + if( Key_IsDown( K_SHIFT )) + key += 'A' - 'a'; + + UI_CharEvent( key ); } - UI_CharEvent( key ); } + UI_KeyEvent( key, down ); return; } @@ -809,7 +788,7 @@ Key_EnableTextInput */ void Key_EnableTextInput( qboolean enable, qboolean force ) { - if( CVAR_TO_BOOL( osk_enable ) ) + if( osk_enable.value ) { OSK_EnableTextInput( enable, force ); return; @@ -842,7 +821,9 @@ void GAME_EXPORT Key_SetKeyDest( int key_dest ) cls.key_dest = key_menu; break; case key_console: +#if !XASH_NSWITCH && !XASH_PSVITA // if we don't disable this, pops up the keyboard during load Key_EnableTextInput( true, false ); +#endif cls.key_dest = key_console; break; case key_message: @@ -850,7 +831,7 @@ void GAME_EXPORT Key_SetKeyDest( int key_dest ) cls.key_dest = key_message; break; default: - Host_Error( "Key_SetKeyDest: wrong destination (%i)\n", key_dest ); + Host_Error( "%s: wrong destination (%i)\n", __func__, key_dest ); break; } } @@ -865,11 +846,14 @@ void GAME_EXPORT Key_ClearStates( void ) int i; // don't clear keys during changelevel - if( cls.changelevel ) return; + if( cls.changelevel ) + return; for( i = 0; i < 256; i++ ) { - if( keys[i].down ) + if( i >= K_MOUSE1 && i <= K_MOUSE5 ) + IN_MouseEvent( i - K_MOUSE1, false ); + else Key_Event( i, false ); keys[i].down = 0; @@ -919,17 +903,16 @@ A helper function if platform input doesn't support text mode properly */ int Key_ToUpper( int keynum ) { - keynum = Q_toupper( keynum ); if( keynum == '-' ) - keynum = '_'; + return '_'; if( keynum == '=' ) - keynum = '+'; + return '+'; if( keynum == ';' ) - keynum = ':'; + return ':'; if( keynum == '\'' ) - keynum = '"'; + return '"'; - return keynum; + return Q_toupper( keynum ); } /* On-screen keyboard: @@ -1005,7 +988,7 @@ struct osk_s static qboolean OSK_KeyEvent( int key, int down ) { - if( !osk.enable || !CVAR_TO_BOOL( osk_enable ) ) + if( !osk.enable || !osk_enable.value ) return false; if( osk.sending ) @@ -1065,18 +1048,16 @@ static qboolean OSK_KeyEvent( int key, int down ) break; } - if( !Q_stricmp( cl_charset->string, "utf-8" ) ) - ch = (unsigned char)osk.curbutton.val; - else - ch = Con_UtfProcessCharForce( (unsigned char)osk.curbutton.val ); + ch = (byte)osk.curbutton.val; + + // do not pass UTF-8 sequence into the engine, convert it here + if( !cls.accept_utf8 ) + ch = Con_UtfProcessCharForce( ch ); if( !ch ) break; - Con_CharEvent( ch ); - if( cls.key_dest == key_menu ) - UI_CharEvent ( ch ); - + CL_CharEvent( ch ); break; } } @@ -1116,7 +1097,7 @@ static qboolean OSK_KeyEvent( int key, int down ) /* ============= -Joy_EnableTextInput +OSK_EnableTextInput Enables built-in IME ============= @@ -1131,7 +1112,6 @@ static void OSK_EnableTextInput( qboolean enable, qboolean force ) { osk.curlayout = 0; osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x]; - } } @@ -1149,7 +1129,7 @@ Draw button with symbol on it */ static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height ) { - char str[] = {symb & 255, 0}; + cl_font_t *font = Con_GetCurFont(); byte color[] = { 255, 255, 255, 255 }; int x1 = x * refState.width, y1 = y * refState.height, @@ -1157,14 +1137,15 @@ static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float h = height * refState.height; if( symb == osk.curbutton.val ) - { - ref.dllFuncs.FillRGBABlend( x1, y1, w, h, 255, 160, 0, 100 ); - } + ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 ); if( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) ) return; - Con_DrawCharacter( x1 + 1, y1, symb, color ); + CL_DrawCharacter( + x1 + width * 0.4 * refState.width, + y1 + height * 0.4 * refState.height, + symb, color, font, 0 ); } /* @@ -1178,7 +1159,11 @@ static void OSK_DrawSpecialButton( const char *name, float x, float y, float wid { byte color[] = { 0, 255, 0, 255 }; - Con_DrawString( x * refState.width, y * refState.height, name, color ); + Con_DrawString( + x * refState.width + width * 0.4 * refState.width, + y * refState.height + height * 0.4 * refState.height, + name, + color ); } @@ -1195,11 +1180,11 @@ void OSK_Draw( void ) float x, y; int i, j; - if( !osk.enable || !CVAR_TO_BOOL(osk_enable) || !osk.curbutton.val ) + if( !osk.enable || !osk_enable.value || !osk.curbutton.val ) return; // draw keyboard - ref.dllFuncs.FillRGBABlend( X_START * refState.width, Y_START * refState.height, + ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height, X_STEP * MAX_OSK_ROWS * refState.width, Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 ); diff --git a/engine/client/mod_dbghulls.c b/engine/client/mod_dbghulls.c index e1c7e6f2..6db4b461 100644 --- a/engine/client/mod_dbghulls.c +++ b/engine/client/mod_dbghulls.c @@ -1,6 +1,8 @@ /* mod_dbghulls.c - loading & handling world and brushmodels Copyright (C) 2016 Uncle Mike +Copyright (C) 2005 Kevin Shanahan +Copyright (C) 1996-1997 Id Software, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -74,7 +76,7 @@ static void free_winding( winding_t *w ) { // simple sentinel by Carmack if( *(unsigned *)w == 0xDEADC0DE ) - Host_Error( "free_winding: freed a freed winding\n" ); + Host_Error( "%s: freed a freed winding\n", __func__ ); *(unsigned *)w = 0xDEADC0DE; free( w ); } @@ -154,7 +156,7 @@ static winding_t *winding_for_plane( const mplane_t *p ) vup[0] = 1; break; default: - Host_Error( "BaseWindingForPlane: no axis found\n" ); + Host_Error( "%s: no axis found\n", __func__ ); return NULL; } @@ -468,16 +470,16 @@ out_free: * This is a stack of the clipnodes we have traversed * "sides" indicates which side we went down each time */ -static mclipnode_t *node_stack[MAX_CLIPNODE_DEPTH]; +static int node_stack[MAX_CLIPNODE_DEPTH]; static int side_stack[MAX_CLIPNODE_DEPTH]; static uint node_stack_depth; -static void push_node( mclipnode_t *node, int side ) +static void push_node( int nodenum, int side ) { if( node_stack_depth == MAX_CLIPNODE_DEPTH ) Host_Error( "node stack overflow\n" ); - node_stack[node_stack_depth] = node; + node_stack[node_stack_depth] = nodenum; side_stack[node_stack_depth] = side; node_stack_depth++; } @@ -500,22 +502,27 @@ static void free_hull_polys( hullnode_t *hull_polys ) } } -static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model ); +static void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model ); -static void do_hull_recursion( hull_t *hull, mclipnode_t *node, int side, hullnode_t *polys, hull_model_t *model ) +static void do_hull_recursion( hull_t *hull, int nodenum, int side, hullnode_t *polys, hull_model_t *model ) { winding_t *w, *next; + int childnum; - if( node->children[side] >= 0 ) + if( world.version == QBSP2_VERSION ) + childnum = hull->clipnodes32[nodenum].children[side]; + else + childnum = hull->clipnodes16[nodenum].children[side]; + + if( childnum >= 0 ) { - mclipnode_t *child = hull->clipnodes + node->children[side]; - push_node( node, side ); - hull_windings_r( hull, child, polys, model ); + push_node( nodenum, side ); + hull_windings_r( hull, childnum, polys, model ); pop_node(); } else { - switch( node->children[side] ) + switch( childnum ) { case CONTENTS_EMPTY: case CONTENTS_WATER: @@ -540,20 +547,25 @@ static void do_hull_recursion( hull_t *hull, mclipnode_t *node, int side, hullno } break; default: - Host_Error( "bad contents: %i\n", node->children[side] ); + Host_Error( "bad contents: %i\n", childnum ); break; } } } -static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model ) +static void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model ) { - mplane_t *plane = hull->planes + node->planenum; + mplane_t *plane; hullnode_t frontlist = LIST_HEAD_INIT( frontlist ); hullnode_t backlist = LIST_HEAD_INIT( backlist ); winding_t *w, *next, *front, *back; int i; + if( world.version == QBSP2_VERSION ) + plane = hull->planes + hull->clipnodes32[nodenum].planenum; + else + plane = hull->planes + hull->clipnodes16[nodenum].planenum; + list_for_each_entry_safe( w, next, polys, chain ) { // PARANIOA - PAIR CHECK @@ -599,7 +611,13 @@ static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, for( i = 0; w && i < node_stack_depth; i++ ) { - mplane_t *p = hull->planes + node_stack[i]->planenum; + mplane_t *p; + + if( world.version == QBSP2_VERSION ) + p = hull->planes + hull->clipnodes32[node_stack[i]].planenum; + else + p = hull->planes + hull->clipnodes16[node_stack[i]].planenum; + w = winding_clip( w, p, false, side_stack[i], 0.00001 ); } @@ -623,8 +641,8 @@ static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, Con_Printf( S_WARN "new winding was clipped away!\n" ); } - do_hull_recursion( hull, node, 0, &frontlist, model ); - do_hull_recursion( hull, node, 1, &backlist, model ); + do_hull_recursion( hull, nodenum, 0, &frontlist, model ); + do_hull_recursion( hull, nodenum, 1, &backlist, model ); } static void remove_paired_polys( hull_model_t *model ) @@ -653,13 +671,13 @@ static void make_hull_windings( hull_t *hull, hull_model_t *model ) if( hull->planes != NULL ) { - hull_windings_r( hull, hull->clipnodes + hull->firstclipnode, &head, model ); + hull_windings_r( hull, hull->firstclipnode, &head, model ); remove_paired_polys( model ); } Con_Reportf( "%i hull polys\n", model->num_polys ); } -void Mod_InitDebugHulls( void ) +static void Mod_InitDebugHulls( model_t *loadmodel ) { int i; @@ -675,16 +693,19 @@ void Mod_InitDebugHulls( void ) } } -void Mod_CreatePolygonsForHull( int hullnum ) +static void Mod_CreatePolygonsForHull( int hullnum ) { model_t *mod = cl.worldmodel; double start, end; char name[8]; int i; - if( hullnum < 1 || hullnum > 3 ) + if( hullnum < 0 || hullnum >= MAX_MAP_HULLS ) return; + if( !world.num_hull_models ) + Mod_InitDebugHulls( mod ); // FIXME: build hulls for separate bmodels (shells, medkits etc) + Con_Printf( "generating polygons for hull %u...\n", hullnum ); start = Sys_DoubleTime(); @@ -701,6 +722,59 @@ void Mod_CreatePolygonsForHull( int hullnum ) Con_Printf( "build time %.3f secs\n", end - start ); } +static void R_DrawHull( hull_model_t *hull ) +{ + winding_t *poly; + + ref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE )); + ref.dllFuncs.TriRenderMode( kRenderNormal ); + list_for_each_entry( poly, &hull->polys, chain ) + { + int i; + + srand((unsigned int)poly ); + ref.dllFuncs.Color4ub( rand() & 255, rand() & 255, rand() & 255, 255 ); + + ref.dllFuncs.Begin( TRI_POLYGON ); + for( i = 0; i < poly->numpoints; i++ ) + ref.dllFuncs.Vertex3fv( poly->p[i] ); + ref.dllFuncs.End(); + } +} + +void R_DrawWorldHull( void ) +{ + if( r_showhull.value <= 0.0f ) + return; + + if( FBitSet( r_showhull.flags, FCVAR_CHANGED )) + { + int val = r_showhull.value; + if( val > 3 ) val = 0; + Mod_CreatePolygonsForHull( val ); + ClearBits( r_showhull.flags, FCVAR_CHANGED ); + } + + R_DrawHull( &world.hull_models[0] ); +} + +void R_DrawModelHull( model_t *mod ) +{ + int i; + + if( r_showhull.value <= 0.0f ) + return; + + if( !mod || mod->name[0] != '*' ) + return; + + i = atoi( mod->name + 1 ); + if( i < 1 || i >= world.num_hull_models ) + return; + + R_DrawHull( &world.hull_models[i] ); +} + void Mod_ReleaseHullPolygons( void ) { int i; diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 2687a509..2c7b0472 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -8,28 +8,139 @@ struct ref_state_s ref; ref_globals_t refState; -convar_t *gl_vsync; -convar_t *gl_showtextures; -convar_t *r_decals; -convar_t *r_adjust_fov; -convar_t *r_showtree; -convar_t *gl_msaa_samples; -convar_t *gl_clear; -convar_t *r_refdll; +static const char* r_skyBoxSuffix[SKYBOX_MAX_SIDES] = { "rt", "bk", "lf", "ft", "up", "dn" }; -void R_GetTextureParms( int *w, int *h, int texnum ) +CVAR_DEFINE_AUTO( gl_vsync, "1", FCVAR_ARCHIVE, "enable vertical syncronization" ); +CVAR_DEFINE_AUTO( r_showtextures, "0", FCVAR_CHEAT, "show all uploaded textures" ); +CVAR_DEFINE_AUTO( r_adjust_fov, "1", FCVAR_ARCHIVE, "making FOV adjustment for wide-screens" ); +CVAR_DEFINE_AUTO( r_decals, "4096", FCVAR_ARCHIVE, "sets the maximum number of decals" ); +CVAR_DEFINE_AUTO( gl_msaa_samples, "0", FCVAR_GLCONFIG, "samples number for multisample anti-aliasing" ); +CVAR_DEFINE_AUTO( gl_clear, "0", FCVAR_ARCHIVE, "clearing screen after each frame" ); +CVAR_DEFINE_AUTO( r_showtree, "0", FCVAR_ARCHIVE, "build the graph of visible BSP tree" ); +static CVAR_DEFINE_AUTO( r_refdll, "", FCVAR_RENDERINFO, "choose renderer implementation, if supported" ); +static CVAR_DEFINE_AUTO( r_refdll_loaded, "", FCVAR_READ_ONLY, "currently loaded renderer" ); + +// there is no need to expose whole host and cl structs into the renderer +// but we still need to update timings accurately as possible +// this looks horrible but the only other option would be passing four +// time pointers and then it's looks even worse with dereferences everywhere +#define STATIC_OFFSET_CHECK( s1, s2, field, base, msg ) \ + STATIC_ASSERT( offsetof( s1, field ) == offsetof( s2, field ) - offsetof( s2, base ), msg ) +#define REF_CLIENT_CHECK( field ) \ + STATIC_OFFSET_CHECK( ref_client_t, client_t, field, time, "broken ref_client_t offset" ); \ + STATIC_ASSERT_( szchk_##__LINE__, sizeof(((ref_client_t *)0)->field ) == sizeof( cl.field ), "broken ref_client_t size" ) +#define REF_HOST_CHECK( field ) \ + STATIC_OFFSET_CHECK( ref_host_t, host_parm_t, field, realtime, "broken ref_client_t offset" ); \ + STATIC_ASSERT_( szchk_##__LINE__, sizeof(((ref_host_t *)0)->field ) == sizeof( host.field ), "broken ref_client_t size" ) + +REF_CLIENT_CHECK( time ); +REF_CLIENT_CHECK( oldtime ); +REF_CLIENT_CHECK( viewentity ); +REF_CLIENT_CHECK( playernum ); +REF_CLIENT_CHECK( maxclients ); +REF_CLIENT_CHECK( models ); +REF_CLIENT_CHECK( paused ); +REF_CLIENT_CHECK( simorg ); +REF_HOST_CHECK( realtime ); +REF_HOST_CHECK( frametime ); +REF_HOST_CHECK( features ); + +static qboolean CheckSkybox( const char *name, char out[SKYBOX_MAX_SIDES][MAX_STRING] ) { - if( w ) *w = REF_GET_PARM( PARM_TEX_WIDTH, texnum ); - if( h ) *h = REF_GET_PARM( PARM_TEX_HEIGHT, texnum ); + static const char *skybox_ext[3] = { "dds", "tga", "bmp" }; + static const char *skybox_delim[2] = { "", "_" }; // no space for HL style, underscore for Q1 style + int i; + + // search for skybox images + for( i = 0; i < ARRAYSIZE( skybox_ext ); i++ ) + { + int j; + + for( j = 0; j < ARRAYSIZE( skybox_delim ); j++ ) + { + int k, num_checked_sides = 0; + + for( k = 0; k < SKYBOX_MAX_SIDES; k++ ) + { + char sidename[MAX_VA_STRING]; + + Q_snprintf( sidename, sizeof( sidename ), "%s%s%s.%s", name, skybox_delim[j], r_skyBoxSuffix[k], skybox_ext[i] ); + if( g_fsapi.FileExists( sidename, false )) + { + Q_strncpy( out[k], sidename, sizeof( out[k] )); + num_checked_sides++; + } + } + + if( num_checked_sides == SKYBOX_MAX_SIDES ) + return true; // image exists + } + } + + return false; } -/* -================ -GL_FreeImage +void R_SetupSky( const char *name ) +{ + string loadname; + char sidenames[SKYBOX_MAX_SIDES][MAX_STRING]; + int skyboxTextures[SKYBOX_MAX_SIDES] = { 0 }; + int i, len; + qboolean result; + + if( !COM_CheckString( name )) + { + ref.dllFuncs.R_SetupSky( NULL ); // unload skybox + return; + } + + Q_snprintf( loadname, sizeof( loadname ), "gfx/env/%s", name ); + COM_StripExtension( loadname ); + + // kill the underline suffix to find them manually later + len = Q_strlen( loadname ); + + if( loadname[len - 1] == '_' ) + loadname[len - 1] = '\0'; + result = CheckSkybox( loadname, sidenames ); + + // to prevent infinite recursion if default skybox was missed + if( !result && Q_stricmp( name, DEFAULT_SKYBOX_NAME )) + { + Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", name ); + R_SetupSky( DEFAULT_SKYBOX_NAME ); // force to default + return; + } + + ref.dllFuncs.R_SetupSky( NULL ); // unload skybox + Con_DPrintf( "SKY: " ); + + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) + { + skyboxTextures[i] = ref.dllFuncs.GL_LoadTexture( sidenames[i], NULL, 0, TF_CLAMP|TF_SKY ); + + if( !skyboxTextures[i] ) + break; + + Con_DPrintf( "%s%s%s", name, r_skyBoxSuffix[i], i != 5 ? ", " : ". " ); + } + + if( i == SKYBOX_MAX_SIDES ) + { + SetBits( world.flags, FWORLD_CUSTOM_SKYBOX ); + Con_DPrintf( "done\n" ); + ref.dllFuncs.R_SetupSky( skyboxTextures ); + return; // loaded + } + + Con_DPrintf( "^2failed\n" ); + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) + { + if( skyboxTextures[i] ) + ref.dllFuncs.GL_FreeTexture( skyboxTextures[i] ); + } +} -Frees image by name -================ -*/ void GAME_EXPORT GL_FreeImage( const char *name ) { int texnum; @@ -41,21 +152,10 @@ void GAME_EXPORT GL_FreeImage( const char *name ) ref.dllFuncs.GL_FreeTexture( texnum ); } -void R_UpdateRefState( void ) -{ - refState.time = cl.time; - refState.oldtime = cl.oldtime; - refState.realtime = host.realtime; - refState.frametime = host.frametime; -} - void GL_RenderFrame( const ref_viewpass_t *rvp ) { - R_UpdateRefState(); - VectorCopy( rvp->vieworigin, refState.vieworg ); VectorCopy( rvp->viewangles, refState.viewangles ); - AngleVectors( refState.viewangles, refState.vforward, refState.vright, refState.vup ); ref.dllFuncs.GL_RenderFrame( rvp ); } @@ -65,9 +165,25 @@ static intptr_t pfnEngineGetParm( int parm, int arg ) return CL_RenderGetParm( parm, arg, false ); // prevent recursion } -static world_static_t *pfnGetWorld( void ) +static cvar_t *pfnCvar_Get( const char *szName, const char *szValue, int flags, const char *description ) { - return &world; + return (cvar_t *)Cvar_Get( szName, szValue, flags | FCVAR_REFDLL, description ); +} + +static void pfnCvar_RegisterVariable( convar_t *var ) +{ + SetBits( var->flags, FCVAR_REFDLL ); + Cvar_RegisterVariable( var ); +} + +static void pfnCvar_FullSet( const char *var_name, const char *value, int flags ) +{ + Cvar_FullSet( var_name, value, flags | FCVAR_REFDLL ); +} + +static int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description ) +{ + return Cmd_AddCommandEx( cmd_name, function, description, CMD_REFDLL, __func__ ); } static void pfnStudioEvent( const mstudioevent_t *event, const cl_entity_t *e ) @@ -75,23 +191,13 @@ static void pfnStudioEvent( const mstudioevent_t *event, const cl_entity_t *e ) clgame.dllFuncs.pfnStudioEvent( event, e ); } -static efrag_t* pfnGetEfragsFreeList( void ) -{ - return clgame.free_efrags; -} - -static void pfnSetEfragsFreeList( efrag_t *list ) -{ - clgame.free_efrags = list; -} - static model_t *pfnGetDefaultSprite( enum ref_defaultsprite_e spr ) { switch( spr ) { case REF_DOT_SPRITE: return cl_sprite_dot; case REF_CHROME_SPRITE: return cl_sprite_shell; - default: Host_Error( "GetDefaultSprite: unknown sprite %d\n", spr ); + default: Host_Error( "%s: unknown sprite %d\n", __func__, spr ); } return NULL; } @@ -104,29 +210,15 @@ static void *pfnMod_Extradata( int type, model_t *m ) case mod_studio: return Mod_StudioExtradata( m ); case mod_sprite: // fallthrough case mod_brush: return NULL; - default: Host_Error( "Mod_Extradata: unknown type %d\n", type ); + default: Host_Error( "%s: unknown type %d\n", __func__, type ); } return NULL; } -static model_t *pfnMod_GetCurrentLoadingModel( void ) +static void CL_ExtraUpdate( void ) { - return loadmodel; -} - -static void pfnMod_SetCurrentLoadingModel( model_t *m ) -{ - loadmodel = m; -} - -static void pfnGetPredictedOrigin( vec3_t v ) -{ - VectorCopy( cl.simorg, v ); -} - -static color24 *pfnCL_GetPaletteColor( int color ) // clgame.palette[color] -{ - return &clgame.palette[color]; + clgame.dllFuncs.IN_Accumulate(); + S_ExtraUpdate(); } static void pfnCL_GetScreenInfo( int *width, int *height ) // clgame.scrInfo, ptrs may be NULL @@ -178,11 +270,6 @@ static int pfnGetStudioModelInterface( int version, struct r_studio_interface_s 0; } -static poolhandle_t pfnImage_GetPool( void ) -{ - return host.imagepool; -} - static const bpc_desc_t *pfnImage_GetPFDesc( int idx ) { return &PFDesc[idx]; @@ -203,47 +290,34 @@ static screenfade_t *pfnRefGetScreenFade( void ) return &clgame.fade; } -/* -=============== -R_DoResetGamma -gamma will be reset for -some type of screenshots -=============== -*/ -static qboolean R_DoResetGamma( void ) -{ - switch( cls.scrshot_action ) - { - case scrshot_envshot: - case scrshot_skyshot: - return true; - default: - return false; - } -} - static qboolean R_Init_Video_( const int type ) { host.apply_opengl_config = true; - Cbuf_AddText( va( "exec %s.cfg", ref.dllFuncs.R_GetConfigName())); + Cbuf_AddTextf( "exec %s.cfg", ref.dllFuncs.R_GetConfigName()); Cbuf_Execute(); host.apply_opengl_config = false; return R_Init_Video( type ); } -static ref_api_t gEngfuncs = +static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node ) +{ + // FIXME: get rid of this on next RefAPI update + return Mod_PointInLeaf( p, node, cl.models[1] ); +} + +static const ref_api_t gEngfuncs = { pfnEngineGetParm, - (void*)Cvar_Get, + pfnCvar_Get, (void*)Cvar_FindVarExt, Cvar_VariableValue, Cvar_VariableString, Cvar_SetValue, Cvar_Set, - (void*)Cvar_RegisterVariable, - Cvar_FullSet, + pfnCvar_RegisterVariable, + pfnCvar_FullSet, Cmd_AddRefCommand, Cmd_RemoveCommand, @@ -266,22 +340,16 @@ static ref_api_t gEngfuncs = Con_DrawString, CL_DrawCenterPrint, - CL_GetLocalPlayer, - CL_GetViewModel, - CL_GetEntityByIndex, R_BeamGetEntity, CL_GetWaterEntity, CL_AddVisibleEntity, Mod_SampleSizeForFace, Mod_BoxVisible, - pfnGetWorld, - Mod_PointInLeaf, - Mod_CreatePolygonsForHull, + pfnMod_PointInLeaf, + R_DrawWorldHull, + R_DrawModelHull, - R_StudioSlerpBones, - R_StudioCalcBoneQuaternion, - R_StudioCalcBonePosition, R_StudioGetAnim, pfnStudioEvent, @@ -295,14 +363,9 @@ static ref_api_t gEngfuncs = Mod_ForName, pfnMod_Extradata, - CL_ModelHandle, - pfnMod_GetCurrentLoadingModel, - pfnMod_SetCurrentLoadingModel, + CL_EntitySetRemapColors, CL_GetRemapInfoForEntity, - CL_AllocRemapInfo, - CL_FreeRemapInfo, - CL_UpdateRemapInfo, CL_ExtraUpdate, Host_Error, @@ -310,9 +373,6 @@ static ref_api_t gEngfuncs = COM_RandomFloat, COM_RandomLong, pfnRefGetScreenFade, - CL_TextMessageGet, - pfnGetPredictedOrigin, - pfnCL_GetPaletteColor, pfnCL_GetScreenInfo, pfnSetLocalLightLevel, Sys_CheckParm, @@ -346,23 +406,15 @@ static ref_api_t gEngfuncs = SW_LockBuffer, SW_UnlockBuffer, - BuildGammaTable, - LightToTexGamma, - R_DoResetGamma, - - CL_GetLightStyle, - CL_GetDynamicLight, - CL_GetEntityLight, R_FatPVS, GL_GetOverviewParms, Sys_DoubleTime, pfnGetPhysent, pfnTraceSurface, - PM_TraceLine, + PM_CL_TraceLine, CL_VisTraceLine, CL_TraceLine, - pfnGetMoveVars, Image_AddCmdFlags, Image_SetForceFlags, @@ -374,7 +426,6 @@ static ref_api_t gEngfuncs = FS_CopyImage, FS_FreeImage, Image_SetMDLPointer, - pfnImage_GetPool, pfnImage_GetPFDesc, pfnDrawNormalTriangles, @@ -382,6 +433,8 @@ static ref_api_t gEngfuncs = &clgame.drawFuncs, &g_fsapi, + + R_GetWindowHandle, }; static void R_UnloadProgs( void ) @@ -393,14 +446,14 @@ static void R_UnloadProgs( void ) Cvar_FullSet( "host_refloaded", "0", FCVAR_READ_ONLY ); + Cvar_Unlink( FCVAR_RENDERINFO | FCVAR_GLCONFIG | FCVAR_REFDLL ); + Cmd_Unlink( CMD_REFDLL ); + COM_FreeLibrary( ref.hInstance ); ref.hInstance = NULL; memset( &refState, 0, sizeof( refState )); memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs )); - - Cvar_Unlink( FCVAR_RENDERINFO | FCVAR_GLCONFIG ); - Cmd_Unlink( CMD_REFDLL ); } static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src ) @@ -429,48 +482,41 @@ static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src static qboolean R_LoadProgs( const char *name ) { - extern triangleapi_t gTriApi; static ref_api_t gpEngfuncs; REFAPI GetRefAPI; // single export if( ref.hInstance ) R_UnloadProgs(); FS_AllowDirectPaths( true ); - if( !(ref.hInstance = COM_LoadLibrary( name, false, true ) )) + if( !( ref.hInstance = COM_LoadLibrary( name, false, true ))) { FS_AllowDirectPaths( false ); - Con_Reportf( "R_LoadProgs: can't load renderer library %s: %s\n", name, COM_GetLibraryError() ); + Con_Reportf( "%s: can't load renderer library %s: %s\n", __func__, name, COM_GetLibraryError() ); return false; } FS_AllowDirectPaths( false ); - if( !( GetRefAPI = (REFAPI)COM_GetProcAddress( ref.hInstance, GET_REF_API )) ) + if( !( GetRefAPI = (REFAPI)COM_GetProcAddress( ref.hInstance, GET_REF_API ))) { - COM_FreeLibrary( ref.hInstance ); - Con_Reportf( "R_LoadProgs: can't find GetRefAPI entry point in %s\n", name ); - ref.hInstance = NULL; + Con_Reportf( "%s: can't find GetRefAPI entry point in %s\n", __func__, name ); return false; } // make local copy of engfuncs to prevent overwrite it with user dll - memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); + gpEngfuncs = gEngfuncs; - if( !GetRefAPI( REF_API_VERSION, &ref.dllFuncs, &gpEngfuncs, &refState )) + if( GetRefAPI( REF_API_VERSION, &ref.dllFuncs, &gpEngfuncs, &refState ) != REF_API_VERSION ) { - COM_FreeLibrary( ref.hInstance ); - Con_Reportf( "R_LoadProgs: can't init renderer API: wrong version\n" ); - ref.hInstance = NULL; + Con_Reportf( "%s: can't init renderer API: wrong version\n", __func__ ); return false; } refState.developer = host_developer.value; - if( !ref.dllFuncs.R_Init( ) ) + if( !ref.dllFuncs.R_Init( )) { - COM_FreeLibrary( ref.hInstance ); - Con_Reportf( "R_LoadProgs: can't init renderer!\n" ); //, ref.dllFuncs.R_GetInitError() ); - ref.hInstance = NULL; + Con_Reportf( "%s: can't init renderer!\n", __func__ ); //, ref.dllFuncs.R_GetInitError() ); return false; } @@ -507,30 +553,28 @@ static void R_GetRendererName( char *dest, size_t size, const char *opt ) { if( !Q_strstr( opt, "." OS_LIB_EXT )) { - const char *format; - #ifdef XASH_INTERNAL_GAMELIBS - if( !Q_strcmp( opt, "ref_" )) - format = "%s"; - else - format = "ref_%s"; + #define FMT1 "%s" + #define FMT2 "ref_%s" #else - if( !Q_strcmp( opt, "ref_" )) - format = OS_LIB_PREFIX "%s." OS_LIB_EXT; - else - format = OS_LIB_PREFIX "ref_%s." OS_LIB_EXT; + #define FMT1 OS_LIB_PREFIX "%s." OS_LIB_EXT + #define FMT2 OS_LIB_PREFIX "ref_%s." OS_LIB_EXT #endif - Q_snprintf( dest, size, format, opt ); - + if( !Q_strncmp( opt, "ref_", 4 )) + Q_snprintf( dest, size, FMT1, opt ); + else + Q_snprintf( dest, size, FMT2, opt ); +#undef FMT1 +#undef FMT2 } else { // full path - Q_strcpy( dest, opt ); + Q_strncpy( dest, opt, size ); } } -static qboolean R_LoadRenderer( const char *refopt ) +static qboolean R_LoadRenderer( const char *refopt, qboolean quiet ) { string refdll; @@ -541,10 +585,12 @@ static qboolean R_LoadRenderer( const char *refopt ) if( !R_LoadProgs( refdll )) { R_Shutdown(); - Sys_Warn( S_ERROR "Can't initialize %s renderer!\n", refdll ); + if( !quiet ) + Sys_Warn( S_ERROR "Can't initialize %s renderer!\n", refdll ); return false; } + Cvar_FullSet( "r_refdll_loaded", refopt, FCVAR_READ_ONLY ); Con_Reportf( "Renderer %s initialized\n", refdll ); return true; @@ -563,87 +609,87 @@ static void SetWidthAndHeightFromCommandLine( void ) return; } - R_SaveVideoMode( width, height, width, height ); + R_SaveVideoMode( width, height, width, height, false ); } static void SetFullscreenModeFromCommandLine( void ) { -#if !XASH_MOBILE_PLATFORM - if ( Sys_CheckParm("-fullscreen") ) - { - Cvar_Set( "fullscreen", "1" ); - } - else if ( Sys_CheckParm( "-windowed" ) ) - { - Cvar_Set( "fullscreen", "0" ); - } -#endif + if( Sys_CheckParm( "-borderless" )) + Cvar_DirectSet( &vid_fullscreen, "2" ); + else if( Sys_CheckParm( "-fullscreen" )) + Cvar_DirectSet( &vid_fullscreen, "1" ); + else if( Sys_CheckParm( "-windowed" )) + Cvar_DirectSet( &vid_fullscreen, "0" ); } -void R_CollectRendererNames( void ) +static void R_CollectRendererNames( void ) { - const char *renderers[] = DEFAULT_RENDERERS; - int i, cur; - - cur = 0; - for( i = 0; i < DEFAULT_RENDERERS_LEN; i++ ) + // ordering is important! + static const char *short_names[] = { - string temp; - void *dll, *pfn; +#if XASH_REF_GL_ENABLED + "gl", +#endif +#if XASH_REF_NANOGL_ENABLED + "gles1", +#endif +#if XASH_REF_GLWES_ENABLED + "gles2", +#endif +#if XASH_REF_GL4ES_ENABLED + "gl4es", +#endif +#if XASH_REF_GLES3COMPAT_ENABLED + "gles3compat", +#endif +#if XASH_REF_SOFT_ENABLED + "soft", +#endif + }; - R_GetRendererName( temp, sizeof( temp ), renderers[i] ); + // ordering is important here too! + static const char *long_names[ARRAYSIZE( short_names )] = + { +#if XASH_REF_GL_ENABLED + "OpenGL", +#endif +#if XASH_REF_NANOGL_ENABLED + "GLES1 (NanoGL)", +#endif +#if XASH_REF_GLWES_ENABLED + "GLES2 (gl-wes-v2)", +#endif +#if XASH_REF_GL4ES_ENABLED + "GL4ES", +#endif +#if XASH_REF_GLES3COMPAT_ENABLED + "GLES3 (gl2_shim)", +#endif +#if XASH_REF_SOFT_ENABLED + "Software", +#endif + }; - dll = COM_LoadLibrary( temp, false, true ); - if( !dll ) - { - Con_Reportf( "R_CollectRendererNames: can't load library %s: %s\n", temp, COM_GetLibraryError() ); - continue; - } - - pfn = COM_GetProcAddress( dll, GET_REF_API ); - if( !pfn ) - { - Con_Reportf( "R_CollectRendererNames: can't find API entry point in %s\n", temp ); - COM_FreeLibrary( dll ); - continue; - } - - Q_strncpy( ref.shortNames[cur], renderers[i], sizeof( ref.shortNames[cur] )); - - pfn = COM_GetProcAddress( dll, GET_REF_HUMANREADABLE_NAME ); - if( !pfn ) // just in case - { - Con_Reportf( "R_CollectRendererNames: can't find GetHumanReadableName export in %s\n", temp ); - Q_strncpy( ref.readableNames[cur], renderers[i], sizeof( ref.readableNames[cur] )); - } - else - { - REF_HUMANREADABLE_NAME GetHumanReadableName = (REF_HUMANREADABLE_NAME)pfn; - - GetHumanReadableName( ref.readableNames[cur], sizeof( ref.readableNames[cur] )); - } - - Con_Printf( "Found renderer %s: %s\n", ref.shortNames[cur], ref.readableNames[cur] ); - - cur++; - COM_FreeLibrary( dll ); - } - ref.numRenderers = cur; + ref.num_renderers = ARRAYSIZE( short_names ); + ref.short_names = short_names; + ref.long_names = long_names; } qboolean R_Init( void ) { qboolean success = false; - string requested; + string requested_cmdline; + string requested_cvar; - gl_vsync = Cvar_Get( "gl_vsync", "0", FCVAR_ARCHIVE, "enable vertical syncronization" ); - gl_showtextures = Cvar_Get( "r_showtextures", "0", FCVAR_CHEAT, "show all uploaded textures" ); - r_adjust_fov = Cvar_Get( "r_adjust_fov", "1", FCVAR_ARCHIVE, "making FOV adjustment for wide-screens" ); - r_decals = Cvar_Get( "r_decals", "4096", FCVAR_ARCHIVE, "sets the maximum number of decals" ); - gl_msaa_samples = Cvar_Get( "gl_msaa_samples", "0", FCVAR_GLCONFIG, "samples number for multisample anti-aliasing" ); - gl_clear = Cvar_Get( "gl_clear", "0", FCVAR_ARCHIVE, "clearing screen after each frame" ); - r_showtree = Cvar_Get( "r_showtree", "0", FCVAR_ARCHIVE, "build the graph of visible BSP tree" ); - r_refdll = Cvar_Get( "r_refdll", "", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "choose renderer implementation, if supported" ); + Cvar_RegisterVariable( &gl_vsync ); + Cvar_RegisterVariable( &r_showtextures ); + Cvar_RegisterVariable( &r_adjust_fov ); + Cvar_RegisterVariable( &r_decals ); + Cvar_RegisterVariable( &gl_msaa_samples ); + Cvar_RegisterVariable( &gl_clear ); + Cvar_RegisterVariable( &r_showtree ); + Cvar_RegisterVariable( &r_refdll ); + Cvar_RegisterVariable( &r_refdll_loaded ); // cvars that are expected to exist Cvar_Get( "r_speeds", "0", FCVAR_ARCHIVE, "shows renderer speeds" ); @@ -664,6 +710,7 @@ qboolean R_Init( void ) // cvars that are expected to exist by client.dll // refdll should just get pointer to them + Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "compatibility cvar, does nothing" ); Cvar_Get( "r_drawentities", "1", FCVAR_CHEAT, "render entities" ); Cvar_Get( "cl_himodels", "1", FCVAR_ARCHIVE, "draw high-resolution player models in multiplayer" ); @@ -682,36 +729,54 @@ qboolean R_Init( void ) // 1. Command line `-ref` argument. // 2. `ref_dll` cvar. // 3. Detected renderers in `DEFAULT_RENDERERS` order. - requested[0] = '\0'; - if( !Sys_GetParmFromCmdLine( "-ref", requested ) && COM_CheckString( r_refdll->string ) ) - // r_refdll is set to empty by default, so we can change hardcoded defaults just in case - Q_strncpy( requested, r_refdll->string, sizeof( requested ) ); + requested_cmdline[0] = 0; + requested_cvar[0] = 0; - if ( requested[0] ) - success = R_LoadRenderer( requested ); + if( Sys_GetParmFromCmdLine( "-ref", requested_cmdline )) + success = R_LoadRenderer( requested_cmdline, false ); + + if( !success && COM_CheckString( r_refdll.string ) && Q_stricmp( requested_cmdline, r_refdll.string )) + { + Q_strncpy( requested_cvar, r_refdll.string, sizeof( requested_cvar )); + + // do not show scary messages to user if renderer set in config cannot be loaded + // as game data could be copied from one platform to another, where this renderer + // might not be supported (ref_gl on Android for example) + success = R_LoadRenderer( requested_cvar, !host_developer.value ); + } if( !success ) { int i; - // cycle through renderers that we collected in CollectRendererNames - for( i = 0; i < ref.numRenderers; i++ ) + for( i = 0; i < ref.num_renderers; i++ ) { // skip renderer that was requested but failed to load - if( !Q_strcmp( requested, ref.shortNames[i] ) ) + if( !Q_strcmp( requested_cmdline, ref.short_names[i] )) continue; - success = R_LoadRenderer( ref.shortNames[i] ); + if( !Q_strcmp( requested_cvar, ref.short_names[i] )) + continue; + + // do not show bruteforcing attempts, however, warn user about falling back + // to software mode + if( !Q_strcmp( "soft", ref.short_names[i] ) && !host_developer.value ) + Sys_Warn( "Can't initialize any hardware accelerated renderer. Falling back to software rendering...\n" ); + + success = R_LoadRenderer( ref.short_names[i], !host_developer.value ); - // yay, found working one if( success ) + { + // remember last valid renderer + Cvar_DirectSet( &r_refdll, ref.short_names[i] ); break; + } } } if( !success ) { - Host_Error( "Can't initialize any renderer. Check your video drivers!" ); + Sys_Error( "Can't initialize any renderer. Check your video drivers!\n" ); return false; } diff --git a/engine/client/ref_common.h b/engine/client/ref_common.h index a8ee1405..c5b15d14 100644 --- a/engine/client/ref_common.h +++ b/engine/client/ref_common.h @@ -18,41 +18,45 @@ GNU General Public License for more details. #include "ref_api.h" -#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ( cl.playernum + 1 ) && e->player ) +#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ( cl.playernum + 1 ) && e->player ) struct ref_state_s { - qboolean initialized; - - HINSTANCE hInstance; + HINSTANCE hInstance; + qboolean initialized; + int num_renderers; ref_interface_t dllFuncs; - int numRenderers; - string shortNames[DEFAULT_RENDERERS_LEN]; - string readableNames[DEFAULT_RENDERERS_LEN]; + // depends on build configuration + const char **short_names; + const char **long_names; }; extern struct ref_state_s ref; extern ref_globals_t refState; // handy API wrappers -void R_GetTextureParms( int *w, int *h, int texnum ); #define REF_GET_PARM( parm, arg ) ref.dllFuncs.RefGetParm( (parm), (arg) ) #define GL_LoadTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), false ) #define GL_UpdateTextureInternal( name, pic, flags ) ref.dllFuncs.GL_LoadTextureFromBuffer( (name), (pic), (flags), true ) #define R_GetBuiltinTexture( name ) ref.dllFuncs.GL_LoadTexture( (name), 0, 0, 0 ) +static inline void R_GetTextureParms( int *w, int *h, int texnum ) +{ + if( w ) *w = REF_GET_PARM( PARM_TEX_WIDTH, texnum ); + if( h ) *h = REF_GET_PARM( PARM_TEX_HEIGHT, texnum ); +} + void GL_RenderFrame( const struct ref_viewpass_s *rvp ); +void R_SetupSky( const char *name ); + // common engine and renderer cvars -extern convar_t *r_decals; -extern convar_t *r_adjust_fov; -extern convar_t *gl_clear; +extern convar_t r_decals; +extern convar_t r_adjust_fov; +extern convar_t gl_clear; qboolean R_Init( void ); void R_Shutdown( void ); -void R_UpdateRefState( void ); - -extern triangleapi_t gTriApi; #endif // REF_COMMON_H diff --git a/engine/client/s_dsp.c b/engine/client/s_dsp.c index d0bbca0b..01cb7471 100644 --- a/engine/client/s_dsp.c +++ b/engine/client/s_dsp.c @@ -1,6 +1,7 @@ /* s_dsp.c - digital signal processing algorithms for audio FX Copyright (C) 2009 Uncle Mike +Copyright (C) 2016-2024 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -78,7 +79,7 @@ typedef struct dly_s int *lpdelayline; } dly_t; -const sx_preset_t rgsxpre[] = +static const sx_preset_t rgsxpre[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -115,7 +116,7 @@ const sx_preset_t rgsxpre[] = // 0x0045dca8 enginegl.exe // SHA256: 42383d32cd712e59ee2c1bd78b7ba48814e680e7026c4223e730111f34a60d66 -const sx_preset_t rgsxpre_hlalpha052[] = +static const sx_preset_t rgsxpre_hlalpha052[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -150,51 +151,51 @@ const sx_preset_t rgsxpre_hlalpha052[] = { 0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05 }, // 28 }; -const sx_preset_t *ptable = rgsxpre; +static const sx_preset_t *ptable = rgsxpre; // cvars -convar_t *dsp_off; // disable dsp -convar_t *roomwater_type; // water room_type -convar_t *room_type; // current room type -convar_t *hisound; // DSP quality +static CVAR_DEFINE_AUTO( dsp_off, "0", FCVAR_ARCHIVE, "disable DSP processing (deprecated)" ); +static CVAR_DEFINE_AUTO( room_off, "0", FCVAR_ARCHIVE, "disable DSP processing (GoldSrc compatible cvar)" ); +static CVAR_DEFINE_AUTO( dsp_coeff_table, "0", FCVAR_ARCHIVE, "select DSP coefficient table: 0 for release or 1 for alpha 0.52" ); +static CVAR_DEFINE_AUTO( room_type, "0", 0, "current room type preset" ); + +static CVAR_DEFINE( roomwater_type, "waterroom_type", "14", 0, "water room type" ); +static CVAR_DEFINE( hisound, "room_hires", "2", FCVAR_ARCHIVE, "dsp quality. 1 for 22k, 2 for 44k(recommended) and 3 for 96k" ); // underwater/special fx modulations -convar_t *sxmod_mod; -convar_t *sxmod_lowpass; +static CVAR_DEFINE( sxmod_mod, "room_mod", "0", 0, "stereo amptitude modulation for room" ); +static CVAR_DEFINE( sxmod_lowpass, "room_lp", "0", 0, "for water fx, lowpass for entire room" ); // stereo delay(no feedback) -convar_t *sxste_delay; // straight left delay +static CVAR_DEFINE( sxste_delay, "room_left", "0", 0, "left channel delay time" ); // mono reverb -convar_t *sxrvb_lp; // lowpass -convar_t *sxrvb_feedback; // reverb decay. Higher -- longer -convar_t *sxrvb_size; // room size. Higher -- larger +static CVAR_DEFINE( sxrvb_lp, "room_rvblp", "1", 0, "reverb: low pass filtering level" ); +static CVAR_DEFINE( sxrvb_feedback, "room_refl", "0", 0, "reverb: decay time" ); +static CVAR_DEFINE( sxrvb_size, "room_size", "0", 0, "reverb: initial reflection size" ); // mono delay -convar_t *sxdly_lp; // lowpass -convar_t *sxdly_feedback; // cycles -convar_t *sxdly_delay; // current delay in seconds +static CVAR_DEFINE( sxdly_lp, "room_dlylp", "1", 0, "mono delay: low pass filtering level" ); +static CVAR_DEFINE( sxdly_feedback, "room_feedback", "0.2", 0, "mono delay: decay time" ); +static CVAR_DEFINE( sxdly_delay, "room_delay", "0.8", 0, "mono delay: delay time" ); -convar_t *dsp_room; // for compability - -convar_t *dsp_coeff_table; // use release or 0.52 style -int idsp_dma_speed; +static int idsp_dma_speed; int idsp_room; -int room_typeprev; +static int room_typeprev; // routines -int sxamodl, sxamodr; // amplitude modulation values -int sxamodlt, sxamodrt; // modulation targets -int sxmod1cur, sxmod2cur; -int sxmod1, sxmod2; -int sxhires; +static int sxamodl, sxamodr; // amplitude modulation values +static int sxamodlt, sxamodrt; // modulation targets +static int sxmod1cur, sxmod2cur; +static int sxmod1, sxmod2; +static int sxhires; -portable_samplepair_t *paintto = NULL; +static portable_samplepair_t *paintto = NULL; -dly_t rgsxdly[MAXDLY]; // stereo is last -int rgsxlp[MAXLP]; +static dly_t rgsxdly[MAXDLY]; // stereo is last +static int rgsxlp[MAXLP]; -void SX_Profiling_f( void ); +static void SX_Profiling_f( void ); /* ============ @@ -202,14 +203,12 @@ SX_ReloadRoomFX ============ */ -void SX_ReloadRoomFX( void ) +static void SX_ReloadRoomFX( void ) { - if( !dsp_room ) return; // not initialized - - SetBits( sxste_delay->flags, FCVAR_CHANGED ); - SetBits( sxrvb_feedback->flags, FCVAR_CHANGED ); - SetBits( sxdly_delay->flags, FCVAR_CHANGED ); - SetBits( room_type->flags, FCVAR_CHANGED ); + SetBits( sxste_delay.flags, FCVAR_CHANGED ); + SetBits( sxrvb_feedback.flags, FCVAR_CHANGED ); + SetBits( sxdly_delay.flags, FCVAR_CHANGED ); + SetBits( room_type.flags, FCVAR_CHANGED ); } /* @@ -227,36 +226,34 @@ void SX_Init( void ) sxamodr = sxamodl = sxamodrt = sxamodlt = 255; idsp_dma_speed = SOUND_11k; - hisound = Cvar_Get( "room_hires", "2", FCVAR_ARCHIVE, "dsp quality. 1 for 22k, 2 for 44k(recommended) and 3 for 96k" ); + Cvar_RegisterVariable( &hisound ); sxhires = 2; sxmod1cur = sxmod1 = 350 * ( idsp_dma_speed / SOUND_11k ); sxmod2cur = sxmod2 = 450 * ( idsp_dma_speed / SOUND_11k ); - dsp_off = Cvar_Get( "dsp_off", "0", FCVAR_ARCHIVE, "disable DSP processing" ); - dsp_coeff_table = Cvar_Get( "dsp_coeff_table", "0", FCVAR_ARCHIVE, "select DSP coefficient table: 0 for release or 1 for alpha 0.52" ); + Cvar_RegisterVariable( &dsp_off ); + Cvar_RegisterVariable( &room_off ); + Cvar_RegisterVariable( &dsp_coeff_table ); - roomwater_type = Cvar_Get( "waterroom_type", "14", 0, "water room type" ); - room_type = Cvar_Get( "room_type", "0", 0, "current room type preset" ); + Cvar_RegisterVariable( &roomwater_type ); + Cvar_RegisterVariable( &room_type ); - sxmod_lowpass = Cvar_Get( "room_lp", "0", 0, "for water fx, lowpass for entire room" ); - sxmod_mod = Cvar_Get( "room_mod", "0", 0, "stereo amptitude modulation for room" ); + Cvar_RegisterVariable( &sxmod_lowpass ); + Cvar_RegisterVariable( &sxmod_mod ); - sxrvb_size = Cvar_Get( "room_size", "0", 0, "reverb: initial reflection size" ); - sxrvb_feedback = Cvar_Get( "room_refl", "0", 0, "reverb: decay time" ); - sxrvb_lp = Cvar_Get( "room_rvblp", "1", 0, "reverb: low pass filtering level" ); + Cvar_RegisterVariable( &sxrvb_size ); + Cvar_RegisterVariable( &sxrvb_feedback ); + Cvar_RegisterVariable( &sxrvb_lp ); - sxdly_delay = Cvar_Get( "room_delay", "0.8", 0, "mono delay: delay time" ); - sxdly_feedback = Cvar_Get( "room_feedback", "0.2", 0, "mono delay: decay time" ); - sxdly_lp = Cvar_Get( "room_dlylp", "1", 0, "mono delay: low pass filtering level" ); + Cvar_RegisterVariable( &sxdly_delay ); + Cvar_RegisterVariable( &sxdly_feedback ); + Cvar_RegisterVariable( &sxdly_lp ); - sxste_delay = Cvar_Get( "room_left", "0", 0, "left channel delay time" ); + Cvar_RegisterVariable( &sxste_delay ); Cmd_AddCommand( "dsp_profile", SX_Profiling_f, "dsp stress-test, first argument is room_type" ); - // for compability - dsp_room = room_type; - SX_ReloadRoomFX(); } @@ -267,7 +264,7 @@ DLY_Free Free memory allocated for DSP =========== */ -void DLY_Free( int idelay ) +static void DLY_Free( int idelay ) { Assert( idelay >= 0 && idelay < MAXDLY ); @@ -303,7 +300,7 @@ DLY_Init Initialize dly =========== */ -int DLY_Init( int idelay, float delay ) +static int DLY_Init( int idelay, float delay ) { dly_t *cur; @@ -315,7 +312,7 @@ int DLY_Init( int idelay, float delay ) cur = &rgsxdly[idelay]; cur->cdelaysamplesmax = ((int)(delay * idsp_dma_speed) << sxhires) + 1; - cur->lpdelayline = (int *)Z_Calloc( cur->cdelaysamplesmax * sizeof( int )); + cur->lpdelayline = (int *)Mem_Calloc( sndpool, cur->cdelaysamplesmax * sizeof( int )); cur->xfade = 0; // init modulation @@ -339,7 +336,7 @@ DLY_MovePointer Checks overflow and moves pointer ============ */ -_inline void DLY_MovePointer( dly_t *dly ) +static void DLY_MovePointer( dly_t *dly ) { if( ++dly->idelayinput >= dly->cdelaysamplesmax ) dly->idelayinput = 0; @@ -355,12 +352,12 @@ DLY_CheckNewStereoDelayVal Update stereo processor settings if we are in new room ============= */ -void DLY_CheckNewStereoDelayVal( void ) +static void DLY_CheckNewStereoDelayVal( void ) { dly_t *const dly = &rgsxdly[STEREODLY]; - float delay = sxste_delay->value; + float delay = sxste_delay.value; - if( !FBitSet( sxste_delay->flags, FCVAR_CHANGED )) + if( !FBitSet( sxste_delay.flags, FCVAR_CHANGED )) return; if( delay == 0 ) @@ -403,7 +400,7 @@ DLY_DoStereoDelay Do stereo processing ============= */ -void DLY_DoStereoDelay( int count ) +static void DLY_DoStereoDelay( int count ) { int delay, samplexf; dly_t *const dly = &rgsxdly[STEREODLY]; @@ -468,12 +465,12 @@ DLY_CheckNewDelayVal Update delay processor settings if we are in new room ============= */ -void DLY_CheckNewDelayVal( void ) +static void DLY_CheckNewDelayVal( void ) { - float delay = sxdly_delay->value; + float delay = sxdly_delay.value; dly_t *const dly = &rgsxdly[MONODLY]; - if( FBitSet( sxdly_delay->flags, FCVAR_CHANGED )) + if( FBitSet( sxdly_delay.flags, FCVAR_CHANGED )) { if( delay == 0 ) { @@ -503,8 +500,8 @@ void DLY_CheckNewDelayVal( void ) } } - dly->lp = sxdly_lp->value; - dly->delayfeedback = 255 * sxdly_feedback->value; + dly->lp = sxdly_lp.value; + dly->delayfeedback = 255 * sxdly_feedback.value; } /* @@ -514,7 +511,7 @@ DLY_DoDelay Do delay processing ============= */ -void DLY_DoDelay( int count ) +static void DLY_DoDelay( int count ) { dly_t *const dly = &rgsxdly[MONODLY]; portable_samplepair_t *paint = paintto; @@ -565,7 +562,7 @@ RVB_SetUpDly Set up dly for reverb =========== */ -void RVB_SetUpDly( int pos, float delay, int kmod ) +static void RVB_SetUpDly( int pos, float delay, int kmod ) { int samples; @@ -601,13 +598,13 @@ RVB_CheckNewReverbVal Update reverb settings if we are in new room =========== */ -void RVB_CheckNewReverbVal( void ) +static void RVB_CheckNewReverbVal( void ) { dly_t *const dly1 = &rgsxdly[REVERBPOS]; dly_t *const dly2 = &rgsxdly[REVERBPOS + 1]; - float delay = sxrvb_size->value; + float delay = sxrvb_size.value; - if( FBitSet( sxrvb_size->flags, FCVAR_CHANGED )) + if( FBitSet( sxrvb_size.flags, FCVAR_CHANGED )) { if( delay == 0.0f ) { @@ -616,13 +613,13 @@ void RVB_CheckNewReverbVal( void ) } else { - RVB_SetUpDly( REVERBPOS, sxrvb_size->value, 500 ); - RVB_SetUpDly( REVERBPOS+1, sxrvb_size->value * 0.71f, 700 ); + RVB_SetUpDly( REVERBPOS, sxrvb_size.value, 500 ); + RVB_SetUpDly( REVERBPOS+1, sxrvb_size.value * 0.71f, 700 ); } } - dly1->lp = dly2->lp = sxrvb_lp->value; - dly1->delayfeedback = dly2->delayfeedback = (int)(255 * sxrvb_feedback->value); + dly1->lp = dly2->lp = sxrvb_lp.value; + dly1->delayfeedback = dly2->delayfeedback = (int)(255 * sxrvb_feedback.value); } /* @@ -632,7 +629,7 @@ RVB_DoReverbForOneDly Do reverberation for one dly =========== */ -int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair ) +static int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair ) { int delay; int samplexf; @@ -706,7 +703,7 @@ RVB_DoReverb Do reverberation processing =========== */ -void RVB_DoReverb( int count ) +static void RVB_DoReverb( int count ) { dly_t *const dly1 = &rgsxdly[REVERBPOS]; dly_t *const dly2 = &rgsxdly[REVERBPOS+1]; @@ -723,7 +720,7 @@ void RVB_DoReverb( int count ) voutm = RVB_DoReverbForOneDly( dly1, vlr, paint ); voutm += RVB_DoReverbForOneDly( dly2, vlr, paint ); - if( dsp_coeff_table->value == 1.0f ) + if( dsp_coeff_table.value == 1.0f ) voutm /= 6; // alpha else voutm = (11 * voutm) >> 6; @@ -739,18 +736,18 @@ RVB_DoAMod Do amplification modulation processing =========== */ -void RVB_DoAMod( int count ) +static void RVB_DoAMod( int count ) { portable_samplepair_t *paint = paintto; - if( !sxmod_lowpass->value && !sxmod_mod->value ) + if( !sxmod_lowpass.value && !sxmod_mod.value ) return; for( ; count; count--, paint++ ) { portable_samplepair_t res = *paint; - if( sxmod_lowpass->value ) + if( sxmod_lowpass.value ) { res.left = rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + res.left; res.right = rgsxlp[5] + rgsxlp[6] + rgsxlp[7] + rgsxlp[8] + rgsxlp[9] + res.right; @@ -772,7 +769,7 @@ void RVB_DoAMod( int count ) rgsxlp[8] = rgsxlp[9]; } - if( sxmod_mod->value ) + if( sxmod_mod.value ) { if( --sxmod1cur < 0 ) sxmod1cur = sxmod1; @@ -812,13 +809,9 @@ DSP_Process (xash dsp interface) =========== */ -void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ) +void DSP_Process( portable_samplepair_t *pbfront, int sampleCount ) { - if( dsp_off->value ) - return; - - // don't process DSP while in menu - if( cls.key_dest == key_menu || !sampleCount ) + if( dsp_off.value || room_off.value || !sampleCount ) return; // preset is already installed by CheckNewDspPresets @@ -839,7 +832,7 @@ DSP_ClearState */ void DSP_ClearState( void ) { - Cvar_SetValue( "room_type", 0.0f ); + Cvar_DirectSet( &room_type, "0" ); SX_ReloadRoomFX(); } @@ -852,12 +845,12 @@ CheckNewDspPresets */ void CheckNewDspPresets( void ) { - if( dsp_off->value != 0.0f ) + if( dsp_off.value || room_off.value ) return; - if( FBitSet( dsp_coeff_table->flags, FCVAR_CHANGED )) + if( FBitSet( dsp_coeff_table.flags, FCVAR_CHANGED )) { - switch( (int)dsp_coeff_table->value ) + switch( (int)dsp_coeff_table.value ) { case 0: // release ptable = rgsxpre; @@ -873,20 +866,20 @@ void CheckNewDspPresets( void ) SX_ReloadRoomFX(); room_typeprev = -1; - ClearBits( dsp_coeff_table->flags, FCVAR_CHANGED ); + ClearBits( dsp_coeff_table.flags, FCVAR_CHANGED ); } if( s_listener.waterlevel > 2 ) - idsp_room = roomwater_type->value; - else idsp_room = room_type->value; + idsp_room = roomwater_type.value; + else idsp_room = room_type.value; // don't pass invalid presets idsp_room = bound( 0, idsp_room, MAX_ROOM_TYPES ); - if( FBitSet( hisound->flags, FCVAR_CHANGED )) + if( FBitSet( hisound.flags, FCVAR_CHANGED )) { - sxhires = hisound->value; - ClearBits( hisound->flags, FCVAR_CHANGED ); + sxhires = hisound.value; + ClearBits( hisound.flags, FCVAR_CHANGED ); } if( idsp_room == room_typeprev && idsp_room == 0 ) @@ -898,15 +891,15 @@ void CheckNewDspPresets( void ) cur = ptable + idsp_room; - Cvar_SetValue( "room_lp", cur->room_lp ); - Cvar_SetValue( "room_mod", cur->room_mod ); - Cvar_SetValue( "room_size", cur->room_size ); - Cvar_SetValue( "room_refl", cur->room_refl ); - Cvar_SetValue( "room_rvblp", cur->room_rvblp ); - Cvar_SetValue( "room_delay", cur->room_delay ); - Cvar_SetValue( "room_feedback", cur->room_feedback ); - Cvar_SetValue( "room_dlylp", cur->room_dlylp ); - Cvar_SetValue( "room_left", cur->room_left ); + Cvar_DirectSetValue( &sxmod_lowpass, cur->room_lp ); + Cvar_DirectSetValue( &sxmod_mod, cur->room_mod ); + Cvar_DirectSetValue( &sxrvb_size, cur->room_size ); + Cvar_DirectSetValue( &sxrvb_feedback, cur->room_refl ); + Cvar_DirectSetValue( &sxrvb_lp, cur->room_rvblp ); + Cvar_DirectSetValue( &sxdly_delay, cur->room_delay ); + Cvar_DirectSetValue( &sxdly_feedback, cur->room_feedback ); + Cvar_DirectSetValue( &sxdly_lp, cur->room_dlylp ); + Cvar_DirectSetValue( &sxste_delay, cur->room_left ); } room_typeprev = idsp_room; @@ -915,15 +908,15 @@ void CheckNewDspPresets( void ) DLY_CheckNewDelayVal( ); DLY_CheckNewStereoDelayVal(); - ClearBits( sxrvb_size->flags, FCVAR_CHANGED ); - ClearBits( sxdly_delay->flags, FCVAR_CHANGED ); - ClearBits( sxste_delay->flags, FCVAR_CHANGED ); + ClearBits( sxrvb_size.flags, FCVAR_CHANGED ); + ClearBits( sxdly_delay.flags, FCVAR_CHANGED ); + ClearBits( sxste_delay.flags, FCVAR_CHANGED ); } -void SX_Profiling_f( void ) +static void SX_Profiling_f( void ) { portable_samplepair_t testbuffer[512]; - float oldroom = room_type->value; + float oldroom = room_type.value; double start, end; int i, calls; @@ -935,7 +928,7 @@ void SX_Profiling_f( void ) if( Cmd_Argc() > 1 ) { - Cvar_SetValue( "room_type", Q_atof( Cmd_Argv( 1 ))); + Cvar_DirectSetValue( &room_type, Q_atof( Cmd_Argv( 1 ))); SX_ReloadRoomFX(); CheckNewDspPresets(); // we just need idsp_room immediately, for message below } @@ -945,7 +938,7 @@ void SX_Profiling_f( void ) start = Sys_DoubleTime(); for( calls = 10000; calls; calls-- ) { - DSP_Process( idsp_room, testbuffer, 512 ); + DSP_Process( testbuffer, 512 ); } end = Sys_DoubleTime(); @@ -953,7 +946,7 @@ void SX_Profiling_f( void ) if( Cmd_Argc() > 1 ) { - Cvar_SetValue( "room_type", oldroom ); + Cvar_DirectSetValue( &room_type, oldroom ); SX_ReloadRoomFX(); CheckNewDspPresets(); } diff --git a/engine/client/s_load.c b/engine/client/s_load.c index da657038..ac018434 100644 --- a/engine/client/s_load.c +++ b/engine/client/s_load.c @@ -14,6 +14,7 @@ GNU General Public License for more details. */ #include "common.h" +#include "client.h" #include "sound.h" // during registration it is possible to have more sounds @@ -28,7 +29,6 @@ static sfx_t s_knownSfx[MAX_SFX]; static sfx_t *s_sfxHashList[MAX_SFX_HASH]; static string s_sentenceImmediateName; // keep dummy sentence name qboolean s_registering = false; -int s_registration_sequence = 0; /* ================= @@ -52,9 +52,12 @@ void S_SoundList_f( void ) { totalSize += sc->size; - if( sc->loopStart >= 0 ) Con_Printf( "L" ); - else Con_Printf( " " ); - if( sfx->name[0] == '*' ) + if( FBitSet( sc->flags, SOUND_LOOPED )) + Con_Printf( "L" ); + else + Con_Printf( " " ); + + if( sfx->name[0] == '*' || !Q_strncmp( sfx->name, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 )) Con_Printf( " (%2db) %s : %s\n", sc->width * 8, Q_memprint( sc->size ), sfx->name ); else Con_Printf( " (%2db) %s : " DEFAULT_SOUNDPATH "%s\n", sc->width * 8, Q_memprint( sc->size ), sfx->name ); totalSfx++; @@ -104,17 +107,18 @@ S_CreateDefaultSound */ static wavdata_t *S_CreateDefaultSound( void ) { - wavdata_t *sc; + wavdata_t *sc; + uint samples = SOUND_DMA_SPEED; + uint channels = 1; + uint width = 2; + size_t size = samples * width * channels; - sc = Mem_Calloc( sndpool, sizeof( wavdata_t )); - - sc->width = 2; - sc->channels = 1; - sc->loopStart = -1; + sc = Mem_Calloc( sndpool, sizeof( wavdata_t ) + size ); + sc->width = width; + sc->channels = channels; sc->rate = SOUND_DMA_SPEED; - sc->samples = SOUND_DMA_SPEED; - sc->size = sc->samples * sc->width * sc->channels; - sc->buffer = Mem_Calloc( sndpool, sc->size ); + sc->samples = samples; + sc->size = size; return sc; } @@ -141,8 +145,8 @@ wavdata_t *S_LoadSound( sfx_t *sfx ) if( Q_stricmp( sfx->name, "*default" )) { // load it from disk - if( s_warn_late_precache.value > 0 && CL_Active() ) - Con_Printf( S_WARN "S_LoadSound: late precache of %s\n", sfx->name ); + if( s_warn_late_precache.value > 0 && cls.state == ca_active ) + Con_Printf( S_WARN "%s: late precache of %s\n", __func__, sfx->name ); if( sfx->name[0] == '*' ) sc = FS_LoadSound( sfx->name + 1, NULL, 0 ); @@ -152,11 +156,11 @@ wavdata_t *S_LoadSound( sfx_t *sfx ) if( !sc ) sc = S_CreateDefaultSound(); if( sc->rate < SOUND_11k ) // some bad sounds - Sound_Process( &sc, SOUND_11k, sc->width, SOUND_RESAMPLE ); + Sound_Process( &sc, SOUND_11k, sc->width, sc->channels, SOUND_RESAMPLE ); else if( sc->rate > SOUND_11k && sc->rate < SOUND_22k ) // some bad sounds - Sound_Process( &sc, SOUND_22k, sc->width, SOUND_RESAMPLE ); - else if( sc->rate > SOUND_22k && sc->rate <= SOUND_32k ) // some bad sounds - Sound_Process( &sc, SOUND_44k, sc->width, SOUND_RESAMPLE ); + Sound_Process( &sc, SOUND_22k, sc->width, sc->channels, SOUND_RESAMPLE ); + else if( sc->rate > SOUND_22k && sc->rate != SOUND_44k ) // some bad sounds + Sound_Process( &sc, SOUND_44k, sc->width, sc->channels, SOUND_RESAMPLE ); sfx->cache = sc; @@ -199,7 +203,7 @@ sfx_t *S_FindName( const char *pname, int *pfInCache ) *pfInCache = ( sfx->cache != NULL ) ? true : false; } // prolonge registration - sfx->servercount = s_registration_sequence; + sfx->servercount = cl.servercount; return sfx; } } @@ -218,8 +222,8 @@ sfx_t *S_FindName( const char *pname, int *pfInCache ) sfx = &s_knownSfx[i]; memset( sfx, 0, sizeof( *sfx )); if( pfInCache ) *pfInCache = false; - Q_strncpy( sfx->name, name, MAX_STRING ); - sfx->servercount = s_registration_sequence; + Q_strncpy( sfx->name, name, sizeof( sfx->name )); + sfx->servercount = cl.servercount; sfx->hashValue = COM_HashKey( sfx->name, MAX_SFX_HASH ); // link it in @@ -273,7 +277,6 @@ void S_BeginRegistration( void ) { int i; - s_registration_sequence++; snd_ambient = false; // check for automatic ambient sounds @@ -309,7 +312,7 @@ void S_EndRegistration( void ) if( !sfx->name[0] || !Q_stricmp( sfx->name, "*default" )) continue; // don't release default sound - if( sfx->servercount != s_registration_sequence ) + if( sfx->servercount != cl.servercount ) S_FreeSound( sfx ); // don't need this sound } @@ -349,7 +352,7 @@ sound_t S_RegisterSound( const char *name ) sfx = S_FindName( name, NULL ); if( !sfx ) return -1; - sfx->servercount = s_registration_sequence; + sfx->servercount = cl.servercount; if( !s_registering ) S_LoadSound( sfx ); return sfx - s_knownSfx; @@ -378,7 +381,7 @@ S_InitSounds void S_InitSounds( void ) { // create unused 0-entry - Q_strncpy( s_knownSfx->name, "*default", MAX_QPATH ); + Q_strncpy( s_knownSfx->name, "*default", sizeof( s_knownSfx->name )); s_knownSfx->hashValue = COM_HashKey( s_knownSfx->name, MAX_SFX_HASH ); s_knownSfx->hashNext = s_sfxHashList[s_knownSfx->hashValue]; s_sfxHashList[s_knownSfx->hashValue] = s_knownSfx; diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 5a77e24f..e04e7126 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "pm_local.h" #include "platform/platform.h" -#define SND_CLIP_DISTANCE 1000.0f - dma_t dma; poolhandle_t sndpool; static soundfade_t soundfade; @@ -64,6 +62,12 @@ float S_GetMasterVolume( void ) { float scale = 1.0f; + if( host.status == HOST_NOFOCUS && snd_mute_losefocus.value != 0.0f ) + { + // we return zero volume to keep sounds running + return 0.0f; + } + if( !s_listener.inmenu && soundfade.percent != 0 ) { scale = bound( 0.0f, soundfade.percent / 100.0f, 1.0f ); @@ -91,7 +95,7 @@ void S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime S_IsClient ================= */ -qboolean S_IsClient( int entnum ) +static qboolean S_IsClient( int entnum ) { return ( entnum == s_listener.entnum ); } @@ -124,7 +128,7 @@ void S_FreeChannel( channel_t *ch ) S_UpdateSoundFade ================= */ -void S_UpdateSoundFade( void ) +static void S_UpdateSoundFade( void ) { float f, totaltime, elapsed; @@ -159,7 +163,7 @@ void S_UpdateSoundFade( void ) } // spline it. - f = SimpleSpline( f ); + f = -( cos( M_PI * f ) - 1 ) / 2; f = bound( 0.0f, f, 1.0f ); soundfade.percent = soundfade.initial_percent * f; @@ -184,7 +188,7 @@ override any other sound playing on the same channel (see code comments below fo exceptions). ================= */ -qboolean SND_FStreamIsPlaying( sfx_t *sfx ) +static qboolean SND_FStreamIsPlaying( sfx_t *sfx ) { int ch_idx; @@ -197,6 +201,65 @@ qboolean SND_FStreamIsPlaying( sfx_t *sfx ) return false; } +/* +================= +SND_GetChannelTimeLeft + +TODO: this function needs to be removed after whole sound subsystem rewrite +================= +*/ +static int SND_GetChannelTimeLeft( const channel_t *ch ) +{ + int remaining; + + if( ch->pMixer.finished || !ch->sfx || !ch->sfx->cache ) + return 0; + + if( ch->isSentence ) // sentences are special, count all remaining words + { + int i; + + if( !ch->currentWord ) + return 0; + + // current word + remaining = ch->currentWord->forcedEndSample - ch->currentWord->sample; + + // here we count all remaining words, stopping if no sfx or sound file is available + // see VOX_LoadWord + for( i = ch->wordIndex + 1; i < ARRAYSIZE( ch->words ); i++ ) + { + wavdata_t *sc; + int end; + + // don't continue with broken sentences + if( !ch->words[i].sfx ) + break; + + if( !( sc = S_LoadSound( ch->words[i].sfx ))) + break; + + end = ch->words[i].end; + + if( end ) + remaining += sc->samples * 0.01f * end; + else remaining += sc->samples; + } + } + else + { + int curpos; + int samples; + + // handle position looping + samples = ch->sfx->cache->samples; + curpos = S_ConvertLoopedPosition( ch->sfx->cache, ch->pMixer.sample, ch->use_loop ); + remaining = bound( 0, samples - curpos, samples ); + } + + return remaining; +} + /* ================= SND_PickDynamicChannel @@ -246,11 +309,7 @@ channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean continue; // try to pick the sound with the least amount of data left to play - timeleft = 0; - if( ch->sfx ) - { - timeleft = 1; // ch->end - paintedtime - } + timeleft = SND_GetChannelTimeLeft( ch ); if( timeleft < life_left ) { @@ -267,7 +326,7 @@ channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean // don't restart looping sounds for the same entity wavdata_t *sc = channels[first_to_die].sfx->cache; - if( sc && sc->loopStart != -1 ) + if( sc && FBitSet( sc->flags, SOUND_LOOPED )) { channel_t *ch = &channels[first_to_die]; @@ -321,7 +380,7 @@ channel_t *SND_PickStaticChannel( const vec3_t pos, sfx_t *sfx ) // no empty slots, alloc a new static sound channel if( total_channels == MAX_CHANNELS ) { - Con_DPrintf( S_ERROR "S_PickStaticChannel: no free channels\n" ); + Con_DPrintf( S_ERROR "%s: no free channels\n", __func__ ); return NULL; } @@ -344,7 +403,7 @@ returns TRUE if sound was altered, returns FALSE if sound was not found (sound is not playing) ================= */ -int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int flags ) +static int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int flags ) { channel_t *ch; int i; @@ -402,7 +461,7 @@ int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int S_SpatializeChannel ================= */ -void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float gain, float dot, float dist ) +static void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float gain, float dot, float dist ) { float lscale, rscale, scale; @@ -425,7 +484,7 @@ void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float g SND_Spatialize ================= */ -void SND_Spatialize( channel_t *ch ) +static void SND_Spatialize( channel_t *ch ) { vec3_t source_vec; float dist, dot, gain = 1.0f; @@ -442,7 +501,7 @@ void SND_Spatialize( channel_t *ch ) pSource = ch->sfx->cache; - if( ch->use_loop && pSource && pSource->loopStart != -1 ) + if( ch->use_loop && pSource && FBitSet( pSource->flags, SOUND_LOOPED )) looping = true; if( !ch->staticsound ) @@ -463,8 +522,11 @@ void SND_Spatialize( channel_t *ch ) dist = VectorNormalizeLength( source_vec ); dot = DotProduct( s_listener.right, source_vec ); - // don't pan sounds with no attenuation - if( ch->dist_mult <= 0.0f ) dot = 0.0f; + if( !FBitSet( host.bugcomp, BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE )) + { + // don't pan sounds with no attenuation + if( ch->dist_mult <= 0.0f ) dot = 0.0f; + } // fill out channel volumes for single location S_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, gain, dot, dist * ch->dist_mult ); @@ -580,7 +642,7 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv if( !target_chan->leftvol && !target_chan->rightvol ) { // looping sounds don't use this optimization because they should stick around until they're killed. - if( !sfx->cache || sfx->cache->loopStart == -1 ) + if( !sfx->cache || !FBitSet( sfx->cache->flags, SOUND_LOOPED )) { // if this is a streaming sound, play the whole thing. if( chan != CHAN_STREAM ) @@ -593,20 +655,6 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv // Init client entity mouth movement vars SND_InitMouth( ent, chan ); - - for( ch_idx = NUM_AMBIENTS, check = channels + NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++, check++) - { - if( check == target_chan ) continue; - - if( check->sfx == sfx && !check->pMixer.sample ) - { - // skip up to 0.1 seconds of audio - int skip = COM_RandomLong( 0, (long)( 0.1f * check->sfx->cache->rate )); - - S_SetSampleStart( check, sfx->cache, skip ); - break; - } - } } /* @@ -846,7 +894,7 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size ) VectorCopy( channels[i].origin, pout->origin ); pout->volume = (float)channels[i].master_vol / 255.0f; pout->attenuation = channels[i].dist_mult * SND_CLIP_DISTANCE; - pout->looping = ( channels[i].use_loop && channels[i].sfx->cache->loopStart != -1 ); + pout->looping = ( channels[i].use_loop && FBitSet( channels[i].sfx->cache->flags, SOUND_LOOPED )); pout->pitch = channels[i].basePitch; pout->channel = channels[i].entchannel; pout->wordIndex = channels[i].wordIndex; @@ -881,7 +929,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) if( !channels[i].sfx || !channels[i].sfx->name[0] || !Q_stricmp( channels[i].sfx->name, "*default" )) continue; // don't serialize default sounds - looped = ( channels[i].use_loop && channels[i].sfx->cache->loopStart != -1 ); + looped = ( channels[i].use_loop && FBitSet( channels[i].sfx->cache->flags, SOUND_LOOPED )); if( channels[i].entchannel == CHAN_STATIC && looped && !Host_IsQuakeCompatible()) continue; // never serialize static looped sounds. It will be restoring in game code @@ -912,7 +960,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) S_InitAmbientChannels =================== */ -void S_InitAmbientChannels( void ) +static void S_InitAmbientChannels( void ) { int ambient_channel; channel_t *chan; @@ -934,7 +982,7 @@ void S_InitAmbientChannels( void ) S_UpdateAmbientSounds =================== */ -void S_UpdateAmbientSounds( void ) +static void S_UpdateAmbientSounds( void ) { mleaf_t *leaf; float vol; @@ -946,7 +994,7 @@ void S_UpdateAmbientSounds( void ) // calc ambient sound levels if( !cl.worldmodel ) return; - leaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes ); + leaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes, cl.worldmodel ); if( !leaf || !s_ambient_level.value ) { @@ -1006,6 +1054,8 @@ rawchan_t *S_FindRawChannel( int entnum, qboolean create ) size_t raw_samples = 0; rawchan_t *ch; + if( !sndpool ) return NULL; // sound is not active + if( !entnum ) return NULL; // world is unused // check for replacement sound, or find the best one to replace @@ -1045,7 +1095,7 @@ rawchan_t *S_FindRawChannel( int entnum, qboolean create ) if( !raw_channels[best] ) { raw_samples = MAX_RAW_SAMPLES; - raw_channels[best] = Mem_Calloc( sndpool, sizeof( *ch ) + sizeof( portable_samplepair_t ) * ( raw_samples - 1 )); + raw_channels[best] = Mem_Calloc( sndpool, sizeof( *ch ) + sizeof( portable_samplepair_t ) * raw_samples ); } ch = raw_channels[best]; @@ -1061,7 +1111,7 @@ rawchan_t *S_FindRawChannel( int entnum, qboolean create ) S_RawSamplesStereo =================== */ -static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ) +uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ) { uint fracstep, samplefrac; uint src, dst; @@ -1143,130 +1193,6 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan ch->leftvol = ch->rightvol = snd_vol; } -/* -=================== -S_RawSamples -=================== -*/ -void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ) -{ - int snd_vol = 128; - - if( entnum < 0 ) snd_vol = 256; // bg track or movie track - - S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); -} - -/* -=================== -S_PositionedRawSamples -=================== -*/ -void S_StreamAviSamples( void *Avi, int entnum, float fvol, float attn, float synctime ) -{ - int bufferSamples; - int fileSamples; - byte raw[MAX_RAW_SAMPLES]; - float duration = 0.0f; - int r, fileBytes; - rawchan_t *ch = NULL; - - if( !dma.initialized || s_listener.paused || !CL_IsInGame( )) - return; - - if( entnum < 0 || entnum >= GI->max_edicts ) - return; - - if( !( ch = S_FindRawChannel( entnum, true ))) - return; - - if( ch->sound_info.rate == 0 ) - { - if( !AVI_GetAudioInfo( Avi, &ch->sound_info )) - return; // no audiotrack - } - - ch->master_vol = bound( 0, fvol * 255, 255 ); - ch->dist_mult = (attn / SND_CLIP_DISTANCE); - - // see how many samples should be copied into the raw buffer - if( ch->s_rawend < soundtime ) - ch->s_rawend = soundtime; - - // position is changed, synchronization is lost etc - if( fabs( ch->oldtime - synctime ) > s_mixahead.value ) - ch->sound_info.loopStart = AVI_TimeToSoundPosition( Avi, synctime * 1000 ); - ch->oldtime = synctime; // keep actual time - - while( ch->s_rawend < soundtime + ch->max_samples ) - { - wavdata_t *info = &ch->sound_info; - - bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); - - // decide how much data needs to be read from the file - fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); - if( fileSamples <= 1 ) return; // no more samples need - - // our max buffer size - fileBytes = fileSamples * ( info->width * info->channels ); - - if( fileBytes > sizeof( raw )) - { - fileBytes = sizeof( raw ); - fileSamples = fileBytes / ( info->width * info->channels ); - } - - // read audio stream - r = AVI_GetAudioChunk( Avi, raw, info->loopStart, fileBytes ); - info->loopStart += r; // advance play position - - if( r < fileBytes ) - { - fileBytes = r; - fileSamples = r / ( info->width * info->channels ); - } - - if( r > 0 ) - { - // add to raw buffer - ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, - fileSamples, info->rate, info->width, info->channels, raw ); - } - else break; // no more samples for this frame - } -} - -/* -=================== -S_GetRawSamplesLength -=================== -*/ -uint S_GetRawSamplesLength( int entnum ) -{ - rawchan_t *ch; - - if( !( ch = S_FindRawChannel( entnum, false ))) - return 0; - - return ch->s_rawend <= paintedtime ? 0 : (float)(ch->s_rawend - paintedtime) * DMA_MSEC_PER_SAMPLE; -} - -/* -=================== -S_ClearRawChannel -=================== -*/ -void S_ClearRawChannel( int entnum ) -{ - rawchan_t *ch; - - if( !( ch = S_FindRawChannel( entnum, false ))) - return; - - ch->s_rawend = 0; -} - /* =================== S_FreeIdleRawChannels @@ -1286,10 +1212,15 @@ static void S_FreeIdleRawChannels( void ) if( ch->s_rawend >= paintedtime ) continue; - - if ( ch->entnum > 0 ) + + if( ch->entnum > 0 ) + { SND_ForceCloseMouth( ch->entnum ); + if( ch->entnum <= MAX_CLIENTS ) + Voice_StopChannel( ch->entnum ); + } + if(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC ) { raw_channels[i] = NULL; @@ -1396,7 +1327,7 @@ static void S_FreeRawChannels( void ) S_ClearBuffer ================== */ -void S_ClearBuffer( void ) +static void S_ClearBuffer( void ) { S_ClearRawChannels(); @@ -1495,7 +1426,7 @@ static int S_GetSoundtime( void ) } //============================================================================= -void S_UpdateChannels( void ) +static void S_UpdateChannels( void ) { uint endtime; int samps; @@ -1582,11 +1513,10 @@ void SND_UpdateSound( void ) // release raw-channels that no longer used more than 10 secs S_FreeIdleRawChannels(); - VectorCopy( cl.simvel, s_listener.velocity ); s_listener.frametime = (cl.time - cl.oldtime); s_listener.waterlevel = cl.local.waterlevel; s_listener.active = CL_IsInGame(); - s_listener.inmenu = CL_IsInMenu(); + s_listener.inmenu = cls.key_dest == key_menu; s_listener.paused = cl.paused; // update general area ambient sound sources @@ -1671,7 +1601,6 @@ void SND_UpdateSound( void ) } S_StreamBackgroundTrack (); - S_StreamSoundTrack (); // mix some sound S_UpdateChannels (); @@ -1684,7 +1613,7 @@ console functions =============================================================================== */ -void S_Play_f( void ) +static void S_Play_f( void ) { if( Cmd_Argc() == 1 ) { @@ -1695,7 +1624,7 @@ void S_Play_f( void ) S_StartLocalSound( Cmd_Argv( 1 ), VOL_NORM, false ); } -void S_Play2_f( void ) +static void S_Play2_f( void ) { int i = 1; @@ -1712,7 +1641,7 @@ void S_Play2_f( void ) } } -void S_PlayVol_f( void ) +static void S_PlayVol_f( void ) { if( Cmd_Argc() == 1 ) { @@ -1738,7 +1667,7 @@ static void S_Say( const char *name, qboolean reliable ) S_StartLocalSound( sentence, 1.0f, reliable ); } -void S_Say_f( void ) +static void S_Say_f( void ) { if( Cmd_Argc() == 1 ) { @@ -1749,7 +1678,7 @@ void S_Say_f( void ) S_Say( Cmd_Argv( 1 ), false ); } -void S_SayReliable_f( void ) +static void S_SayReliable_f( void ) { if( Cmd_Argc() == 1 ) { @@ -1765,7 +1694,7 @@ void S_SayReliable_f( void ) S_Music_f ================= */ -void S_Music_f( void ) +static void S_Music_f( void ) { int c = Cmd_Argc(); @@ -1787,8 +1716,12 @@ void S_Music_f( void ) for( i = 0; i < 2; i++ ) { - const char *intro_path = va( "media/%s.%s", intro, ext[i] ); - const char *main_path = va( "media/%s.%s", main, ext[i] ); + char intro_path[MAX_VA_STRING]; + char main_path[MAX_VA_STRING]; + char track_path[MAX_VA_STRING]; + + Q_snprintf( intro_path, sizeof( intro_path ), "media/%s.%s", intro, ext[i] ); + Q_snprintf( main_path, sizeof( main_path ), "media/%s.%s", main, ext[i] ); if( FS_FileExists( intro_path, false ) && FS_FileExists( main_path, false )) { @@ -1796,7 +1729,10 @@ void S_Music_f( void ) S_StartBackgroundTrack( intro, main, 0, false ); break; } - else if( FS_FileExists( va( "media/%s.%s", track, ext[i] ), false )) + + Q_snprintf( track_path, sizeof( track_path ), "media/%s.%s", track, ext[i] ); + + if( FS_FileExists( track_path, false )) { // single non-looped theme S_StartBackgroundTrack( track, NULL, 0, false ); @@ -1822,7 +1758,7 @@ void S_Music_f( void ) S_StopSound_f ================= */ -void S_StopSound_f( void ) +static void S_StopSound_f( void ) { S_StopAllSounds( true ); } @@ -1832,7 +1768,7 @@ void S_StopSound_f( void ) S_SoundFade_f ================= */ -void S_SoundFade_f( void ) +static void S_SoundFade_f( void ) { int c = Cmd_Argc(); float fadeTime = 5.0f; @@ -1866,11 +1802,11 @@ void S_SoundInfo_f( void ) S_VoiceRecordStart_f ================= */ -void S_VoiceRecordStart_f( void ) +static void S_VoiceRecordStart_f( void ) { - if( cls.state != ca_active || cls.legacymode ) + if( cls.state != ca_active ) return; - + Voice_RecordStart(); } @@ -1879,11 +1815,11 @@ void S_VoiceRecordStart_f( void ) S_VoiceRecordStop_f ================= */ -void S_VoiceRecordStop_f( void ) +static void S_VoiceRecordStop_f( void ) { if( cls.state != ca_active || !Voice_IsRecording() ) return; - + CL_AddVoiceToDatagram(); Voice_RecordStop(); } @@ -1895,12 +1831,6 @@ S_Init */ qboolean S_Init( void ) { - if( Sys_CheckParm( "-nosound" )) - { - Con_Printf( "Audio: Disabled\n" ); - return false; - } - Cvar_RegisterVariable( &s_volume ); Cvar_RegisterVariable( &s_musicvolume ); Cvar_RegisterVariable( &s_mixahead ); @@ -1914,11 +1844,18 @@ qboolean S_Init( void ) Cvar_RegisterVariable( &s_samplecount ); Cvar_RegisterVariable( &s_warn_late_precache ); + if( Sys_CheckParm( "-nosound" )) + { + Con_Printf( "Audio: Disabled\n" ); + return false; + } + Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" ); Cmd_AddCommand( "play2", S_Play2_f, "playing a group of specified sound files" ); // nehahra stuff Cmd_AddCommand( "playvol", S_PlayVol_f, "playing a specified sound file with specified volume" ); Cmd_AddCommand( "stopsound", S_StopSound_f, "stop all sounds" ); - Cmd_AddCommand( "music", S_Music_f, "starting a background track" ); + // HLU SDK have command with the same name + Cmd_AddCommandWithFlags( "music", S_Music_f, "starting a background track", CMD_OVERRIDABLE ); Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" ); Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" ); Cmd_AddCommand( "s_fade", S_SoundFade_f, "fade all sounds then stop all" ); @@ -1927,14 +1864,15 @@ qboolean S_Init( void ) Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" ); Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" ); + sndpool = Mem_AllocPool( "Sound Zone" ); dma.backendName = "None"; - if( !SNDDMA_Init( ) ) + if( !SNDDMA_Init( )) { Con_Printf( "Audio: sound system can't be initialized\n" ); + Mem_FreePool( &sndpool ); return false; } - sndpool = Mem_AllocPool( "Sound Zone" ); soundtime = 0; paintedtime = 0; @@ -1961,7 +1899,8 @@ void S_Shutdown( void ) Cmd_RemoveCommand( "play" ); Cmd_RemoveCommand( "playvol" ); Cmd_RemoveCommand( "stopsound" ); - Cmd_RemoveCommand( "music" ); + if( Cmd_Exists( "music" )) + Cmd_RemoveCommand( "music" ); Cmd_RemoveCommand( "soundlist" ); Cmd_RemoveCommand( "s_info" ); Cmd_RemoveCommand( "s_fade" ); diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index 721f05fa..fe28f6df 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -17,30 +17,58 @@ GNU General Public License for more details. #include "sound.h" #include "client.h" -#define IPAINTBUFFER 0 -#define IROOMBUFFER 1 -#define ISTREAMBUFFER 2 +enum +{ + IPAINTBUFFER = 0, + IROOMBUFFER, + ISTREAMBUFFER, + IVOICEBUFFER, + CPAINTBUFFERS, +}; -#define FILTERTYPE_NONE 0 -#define FILTERTYPE_LINEAR 1 -#define FILTERTYPE_CUBIC 2 +enum +{ + FILTERTYPE_NONE = 0, + FILTERTYPE_LINEAR, + FILTERTYPE_CUBIC, +}; #define CCHANVOLUMES 2 -#define SND_SCALE_BITS 7 -#define SND_SCALE_SHIFT (8 - SND_SCALE_BITS) -#define SND_SCALE_LEVELS (1 << SND_SCALE_BITS) +#define SND_SCALE_BITS 7 +#define SND_SCALE_SHIFT ( 8 - SND_SCALE_BITS ) +#define SND_SCALE_LEVELS ( 1 << SND_SCALE_BITS ) -portable_samplepair_t *g_curpaintbuffer; -portable_samplepair_t streambuffer[(PAINTBUFFER_SIZE+1)]; -portable_samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)]; -portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)]; -portable_samplepair_t facingbuffer[(PAINTBUFFER_SIZE+1)]; -portable_samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)]; -paintbuffer_t paintbuffers[CPAINTBUFFERS]; +// sound mixing buffer +#define CPAINTFILTERMEM 3 +#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer -int snd_scaletable[SND_SCALE_LEVELS][256]; +// fixed point stuff for real-time resampling +#define FIX_BITS 28 +#define FIX_SCALE ( 1 << FIX_BITS ) +#define FIX_MASK (( 1 << FIX_BITS ) - 1 ) +#define FIX_FLOAT( a ) ((int)(( a ) * FIX_SCALE )) +#define FIX( a ) (((int)( a )) << FIX_BITS ) +#define FIX_INTPART( a ) (((int)( a )) >> FIX_BITS ) +#define FIX_FRACPART( a ) (( a ) & FIX_MASK ) +typedef struct +{ + qboolean factive; // if true, mix to this paintbuffer using flags + portable_samplepair_t *pbuf; // front stereo mix buffer, for 2 or 4 channel mixing + int ifilter; // current filter memory buffer to use for upsampling pass + portable_samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM]; +} paintbuffer_t; + +static portable_samplepair_t *g_curpaintbuffer; +static portable_samplepair_t streambuffer[(PAINTBUFFER_SIZE+1)]; +static portable_samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)]; +static portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)]; +static portable_samplepair_t voicebuffer[(PAINTBUFFER_SIZE+1)]; +static portable_samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)]; +static paintbuffer_t paintbuffers[CPAINTBUFFERS]; + +static int snd_scaletable[SND_SCALE_LEVELS][256]; void S_InitScaletable( void ) { int i, j; @@ -58,7 +86,7 @@ S_TransferPaintBuffer =================== */ -void S_TransferPaintBuffer( int endtime ) +static void S_TransferPaintBuffer( int endtime ) { int *snd_p, snd_linear_count; int lpos, lpaintedtime; @@ -67,7 +95,7 @@ void S_TransferPaintBuffer( int endtime ) dword *pbuf; pbuf = (dword *)dma.buffer; - snd_p = (int *)PAINTBUFFER; + snd_p = (int *)g_curpaintbuffer; lpaintedtime = paintedtime; sampleMask = ((dma.samples >> 1) - 1); @@ -111,27 +139,20 @@ void S_TransferPaintBuffer( int endtime ) //=============================================================================== // Activate a paintbuffer. All active paintbuffers are mixed in parallel within // MIX_MixChannelsToPaintbuffer, according to flags -_inline void MIX_ActivatePaintbuffer( int ipaintbuffer ) +static void MIX_ActivatePaintbuffer( int ipaintbuffer ) { Assert( ipaintbuffer < CPAINTBUFFERS ); paintbuffers[ipaintbuffer].factive = true; } -// don't mix into this paintbuffer -_inline void MIX_DeactivatePaintbuffer( int ipaintbuffer ) -{ - Assert( ipaintbuffer < CPAINTBUFFERS ); - paintbuffers[ipaintbuffer].factive = false; -} - -_inline void MIX_SetCurrentPaintbuffer( int ipaintbuffer ) +static void MIX_SetCurrentPaintbuffer( int ipaintbuffer ) { Assert( ipaintbuffer < CPAINTBUFFERS ); g_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf; Assert( g_curpaintbuffer != NULL ); } -_inline int MIX_GetCurrentPaintbufferIndex( void ) +static int MIX_GetCurrentPaintbufferIndex( void ) { int i; @@ -143,7 +164,7 @@ _inline int MIX_GetCurrentPaintbufferIndex( void ) return 0; } -_inline paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) +static paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) { int ipaint = MIX_GetCurrentPaintbufferIndex(); @@ -152,7 +173,7 @@ _inline paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) } // Don't mix into any paintbuffers -_inline void MIX_DeactivateAllPaintbuffers( void ) +static void MIX_DeactivateAllPaintbuffers( void ) { int i; @@ -161,7 +182,7 @@ _inline void MIX_DeactivateAllPaintbuffers( void ) } // set upsampling filter indexes back to 0 -_inline void MIX_ResetPaintbufferFilterCounters( void ) +static void MIX_ResetPaintbufferFilterCounters( void ) { int i; @@ -169,20 +190,14 @@ _inline void MIX_ResetPaintbufferFilterCounters( void ) paintbuffers[i].ifilter = FILTERTYPE_NONE; } -_inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) -{ - Assert( ipaintbuffer < CPAINTBUFFERS ); - paintbuffers[ipaintbuffer].ifilter = 0; -} - // return pointer to front paintbuffer pbuf, given index -_inline portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer ) +static portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer ) { Assert( ipaintbuffer < CPAINTBUFFERS ); return paintbuffers[ipaintbuffer].pbuf; } -_inline paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint ) +static paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint ) { Assert( ipaint < CPAINTBUFFERS ); return &paintbuffers[ipaint]; @@ -203,6 +218,7 @@ void MIX_InitAllPaintbuffers( void ) paintbuffers[IPAINTBUFFER].pbuf = paintbuffer; paintbuffers[IROOMBUFFER].pbuf = roombuffer; paintbuffers[ISTREAMBUFFER].pbuf = streambuffer; + paintbuffers[IVOICEBUFFER].pbuf = voicebuffer; MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); } @@ -214,7 +230,7 @@ CHANNEL MIXING =============================================================================== */ -void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) +static void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) { int *lscale, *rscale; int i, data; @@ -230,7 +246,7 @@ void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, in } } -void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) +static void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) { int *lscale, *rscale; uint left, right; @@ -250,7 +266,7 @@ void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, } } -void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) +static void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) { int left, right; int i, data; @@ -265,7 +281,7 @@ void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, } } -void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) +static void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) { uint *data; int left, right; @@ -286,11 +302,11 @@ void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData } } -void S_Mix8MonoTimeCompress( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) +static void S_Mix8MonoTimeCompress( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) { } -void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) +static void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; @@ -322,7 +338,7 @@ void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inpu } } -void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount ) +static void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; @@ -348,7 +364,7 @@ void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int in } } -void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) +static void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; @@ -370,7 +386,7 @@ void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int in } } -void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) +static void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; @@ -392,7 +408,7 @@ void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int } } -void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount, int timecompress ) +static void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount, int timecompress ) { int pvol[CCHANVOLUMES]; paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); @@ -509,7 +525,7 @@ int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outRate, int ou return outOffset - startingOffset; } -qboolean S_ShouldContinueMixing( channel_t *ch ) +static qboolean S_ShouldContinueMixing( channel_t *ch ) { if( ch->isSentence ) { @@ -530,12 +546,13 @@ qboolean S_ShouldContinueMixing( channel_t *ch ) // this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed. // if( endtime - paintedtime ) is not aligned on boundaries of 4, // we'll miss data if outputRate < SOUND_DMA_SPEED! -void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) +static void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) { channel_t *ch; wavdata_t *pSource; int i, sampleCount; qboolean bZeroVolume; + qboolean local = Host_IsLocalGame(); // mix each channel into paintbuffer ch = channels; @@ -562,7 +579,7 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) { // play, playvol } - else if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound ) + else if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound && local ) { // play only local sounds, keep pause for other continue; @@ -580,6 +597,7 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) // Don't mix sound data for sounds with zero volume. If it's a non-looping sound, // just remove the sound when its volume goes to zero. + bZeroVolume = !ch->leftvol && !ch->rightvol; if( !bZeroVolume ) @@ -589,7 +607,7 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) bZeroVolume = true; } - if( !pSource || ( bZeroVolume && pSource->loopStart == -1 )) + if( !pSource || ( bZeroVolume && !FBitSet( pSource->flags, SOUND_LOOPED ))) { if( !pSource ) { @@ -621,7 +639,7 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) ch->pitch *= ( sys_timescale.value + 1 ) / 2; - if( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE )) + if( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM )) { if( pSource->width == 1 ) SND_MoveMouth8( ch, pSource, sampleCount ); @@ -642,7 +660,7 @@ void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) } // pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer -_inline portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem ) +static portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem ) { // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples if( i == -1 ) return (&(pfiltermem[0])); @@ -660,7 +678,7 @@ _inline portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *p // if NULL then perform no filtering. // count: how many samples to upsample. will become count*2 samples in buffer, in place. -void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +static void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) { // implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples. @@ -681,7 +699,8 @@ void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t // c = (x1 - xm1) / 2; // y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0; - int i, upCount = count << 1; + int i; + const int upCount = Q_min( count << 1, PAINTBUFFER_SIZE ); int a, b, c; int xm1, x0, x1, x2; portable_samplepair_t *psamp0; @@ -690,8 +709,6 @@ void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t portable_samplepair_t *psamp3; int outpos = 0; - Assert( upCount <= PAINTBUFFER_SIZE ); - // pfiltermem holds 6 samples from previous buffer pass // process 'count' samples for( i = 0; i < count; i++) @@ -733,7 +750,8 @@ void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t // write out interpolated sample, increment output counter temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0; - Assert( outpos <= ( sizeof( temppaintbuffer ) / sizeof( temppaintbuffer[0] ))); + if( outpos > ARRAYSIZE( temppaintbuffer )) + break; } Assert( cfltmem >= 3 ); @@ -744,8 +762,7 @@ void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t pfiltermem[2] = pbuffer[upCount - 1]; // copy temppaintbuffer back into paintbuffer - for( i = 0; i < upCount; i++ ) - pbuffer[i] = temppaintbuffer[i]; + memcpy( pbuffer, temppaintbuffer, sizeof( *pbuffer ) * upCount ); } // pass forward over passed in buffer and linearly interpolate all odd samples @@ -753,7 +770,7 @@ void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t // prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR // if NULL then perform no filtering. // count: how many samples to upsample. will become count*2 samples in buffer, in place. -void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +static void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) { int i, upCount = count<<1; @@ -782,7 +799,7 @@ void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_ // if NULL then perform no filtering. // cfltmem: max number of sample pairs filter can use // filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter. -void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) +static void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) { int upCount = count<<1; int i, j; @@ -835,7 +852,7 @@ void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters ) // mixes pbuf1 + pbuf2 into pbuf3, count samples // fgain is output gain 0-1.0 // NOTE: pbuf3 may equal pbuf1 or pbuf2! -void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain ) +static void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain ) { portable_samplepair_t *pbuf1, *pbuf2, *pbuf3; int i, gain; @@ -851,6 +868,14 @@ void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgai pbuf2 = paintbuffers[ibuf2].pbuf; pbuf3 = paintbuffers[ibuf3].pbuf; + if( !gain ) + { + // do not mix buf2 into buf3, just copy + if( pbuf1 != pbuf3 ) + memcpy( pbuf3, pbuf1, sizeof( *pbuf1 ) * count ); + return; + } + // destination buffer stereo - average n chans down to stereo // destination 2ch: @@ -868,7 +893,7 @@ void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgai } } -void MIX_CompressPaintbuffer( int ipaint, int count ) +static void MIX_CompressPaintbuffer( int ipaint, int count ) { portable_samplepair_t *pbuf; paintbuffer_t *ppaint; @@ -884,7 +909,7 @@ void MIX_CompressPaintbuffer( int ipaint, int count ) } } -void S_MixUpsample( int sampleCount, int filtertype ) +static void S_MixUpsample( int sampleCount, int filtertype ) { paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); int ifilter = ppaint->ifilter; @@ -897,43 +922,14 @@ void S_MixUpsample( int sampleCount, int filtertype ) ppaint->ifilter++; } -void MIX_MixStreamBuffer( int end ) +static void MIX_MixRawSamplesBuffer( int end ) { - portable_samplepair_t *pbuf; - rawchan_t *ch; + portable_samplepair_t *pbuf, *roombuf, *streambuf, *voicebuf; + uint i, j, stop; - pbuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER ); - ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, false ); - - // clear the paint buffer - if( s_listener.paused || !ch || ch->s_rawend < paintedtime ) - { - memset( pbuf, 0, (end - paintedtime) * sizeof( portable_samplepair_t )); - } - else - { - int i, stop; - - // copy from the streaming sound source - stop = (end < ch->s_rawend) ? end : ch->s_rawend; - - for( i = paintedtime; i < stop; i++ ) - { - pbuf[i-paintedtime].left = ( ch->rawsamples[i & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8; - pbuf[i-paintedtime].right = ( ch->rawsamples[i & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8; - } - - for( ; i < end; i++ ) - pbuf[i-paintedtime].left = pbuf[i-paintedtime].right = 0; - } -} - -void MIX_MixRawSamplesBuffer( int end ) -{ - portable_samplepair_t *pbuf; - uint i, j, stop; - - pbuf = MIX_GetCurrentPaintbufferPtr()->pbuf; + roombuf = MIX_GetPFrontFromIPaint( IROOMBUFFER ); + streambuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER ); + voicebuf = MIX_GetPFrontFromIPaint( IVOICEBUFFER ); if( s_listener.paused ) return; @@ -941,16 +937,30 @@ void MIX_MixRawSamplesBuffer( int end ) for( i = 0; i < MAX_RAW_CHANNELS; i++ ) { // copy from the streaming sound source - rawchan_t *ch = raw_channels[i]; + rawchan_t *ch = raw_channels[i]; + qboolean stream; + qboolean is_voice; - // background track should be mixing into another buffer - if( !ch || ch->entnum == S_RAW_SOUND_BACKGROUNDTRACK ) + if( !ch ) continue; // not audible if( !ch->leftvol && !ch->rightvol ) continue; + is_voice = (ch->entnum > 0 && ch->entnum <= MAX_CLIENTS) || + (ch->entnum == VOICE_LOOPBACK_INDEX) || + (ch->entnum == VOICE_LOCALCLIENT_INDEX); + + stream = ch->entnum == S_RAW_SOUND_BACKGROUNDTRACK || CL_IsPlayerIndex( ch->entnum ); + + if( is_voice ) + pbuf = voicebuf; + else if( stream ) + pbuf = streambuf; + else + pbuf = roombuf; + stop = (end < ch->s_rawend) ? end : ch->s_rawend; for( j = paintedtime; j < stop; j++ ) @@ -959,8 +969,12 @@ void MIX_MixRawSamplesBuffer( int end ) pbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8; } - if ( ch->entnum > 0 ) - SND_MoveMouthRaw( ch, &ch->rawsamples[paintedtime & ( ch->max_samples - 1 )], stop - paintedtime ); + if( ch->entnum > 0 ) + { + int pos = paintedtime & ( ch->max_samples - 1 ); + + SND_MoveMouthRaw( ch, &ch->rawsamples[pos], bound( 0, ch->max_samples - pos, stop - paintedtime )); + } } } @@ -968,11 +982,8 @@ void MIX_MixRawSamplesBuffer( int end ) // IROOMBUFFER, IFACINGBUFFER, IFACINGAWAY // dsp fx are then applied to these buffers by the caller. // caller also remixes all into final IPAINTBUFFER output. -void MIX_UpsampleAllPaintbuffers( int end, int count ) +static void MIX_UpsampleAllPaintbuffers( int end, int count ) { - // process stream buffer - MIX_MixStreamBuffer( end ); - // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction // These buffers are facing, facingaway, room // These 3 mixed buffers are then each upsampled to 22khz. @@ -1014,7 +1025,6 @@ void MIX_UpsampleAllPaintbuffers( int end, int count ) #endif // mix raw samples from the video streams - MIX_SetCurrentPaintbuffer( IROOMBUFFER ); MIX_MixRawSamplesBuffer( end ); MIX_DeactivateAllPaintbuffers(); @@ -1043,13 +1053,17 @@ void MIX_PaintChannels( int endtime ) MIX_UpsampleAllPaintbuffers( end, count ); // process all sounds with DSP - DSP_Process( idsp_room, MIX_GetPFrontFromIPaint( IROOMBUFFER ), count ); + if( cls.key_dest != key_menu ) + DSP_Process( MIX_GetPFrontFromIPaint( IROOMBUFFER ), count ); // add music or soundtrack from movie (no dsp) MIX_MixPaintbuffers( IPAINTBUFFER, IROOMBUFFER, IPAINTBUFFER, count, S_GetMasterVolume() ); // add music or soundtrack from movie (no dsp) - MIX_MixPaintbuffers( IPAINTBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, S_GetMusicVolume() ); + MIX_MixPaintbuffers( IPAINTBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, 1.0f ); + + // voice chat + MIX_MixPaintbuffers( IPAINTBUFFER, IVOICEBUFFER, IPAINTBUFFER, count, 1.0f ); // clip all values > 16 bit down to 16 bit MIX_CompressPaintbuffer( IPAINTBUFFER, count ); diff --git a/engine/client/s_mouth.c b/engine/client/s_mouth.c index 3b93c8c8..716fe612 100644 --- a/engine/client/s_mouth.c +++ b/engine/client/s_mouth.c @@ -24,17 +24,7 @@ void SND_InitMouth( int entnum, int entchannel ) { if(( entchannel == CHAN_VOICE || entchannel == CHAN_STREAM ) && entnum > 0 ) { - cl_entity_t *clientEntity; - - // init mouth movement vars - clientEntity = CL_GetEntityByIndex( entnum ); - - if( clientEntity ) - { - clientEntity->mouth.mouthopen = 0; - clientEntity->mouth.sndcount = 0; - clientEntity->mouth.sndavg = 0; - } + SND_ForceInitMouth( entnum ); } } @@ -42,15 +32,7 @@ void SND_CloseMouth( channel_t *ch ) { if( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM ) { - cl_entity_t *clientEntity; - - clientEntity = CL_GetEntityByIndex( ch->entnum ); - - if( clientEntity ) - { - // shut mouth - clientEntity->mouth.mouthopen = 0; - } + SND_ForceCloseMouth( ch->entnum ); } } @@ -157,7 +139,7 @@ void SND_ForceInitMouth( int entnum ) clientEntity = CL_GetEntityByIndex( entnum ); - if ( clientEntity ) + if( clientEntity ) { clientEntity->mouth.mouthopen = 0; clientEntity->mouth.sndavg = 0; @@ -171,7 +153,7 @@ void SND_ForceCloseMouth( int entnum ) clientEntity = CL_GetEntityByIndex( entnum ); - if ( clientEntity ) + if( clientEntity ) clientEntity->mouth.mouthopen = 0; } @@ -214,4 +196,4 @@ void SND_MoveMouthRaw( rawchan_t *ch, portable_samplepair_t *pData, int count ) pMouth->sndavg = 0; pMouth->sndcount = 0; } -} \ No newline at end of file +} diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c index 10ed064a..fbc24efa 100644 --- a/engine/client/s_stream.c +++ b/engine/client/s_stream.c @@ -16,6 +16,7 @@ GNU General Public License for more details. #include "common.h" #include "sound.h" #include "client.h" +#include "soundlib.h" static bg_track_t s_bgTrack; static musicfade_t musicfade; // controlled by game dlls @@ -57,6 +58,12 @@ float S_GetMusicVolume( void ) { float scale = 1.0f; + if( host.status == HOST_NOFOCUS && snd_mute_losefocus.value != 0.0f ) + { + // we return zero volume to keep sounds running + return 0.0f; + } + if( !s_listener.inmenu && musicfade.percent != 0 ) { scale = bound( 0.0f, musicfade.percent / 100.0f, 1.0f ); @@ -95,7 +102,8 @@ void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack, int else Q_strncpy( s_bgTrack.loopName, mainTrack, sizeof( s_bgTrack.loopName )); // open stream - s_bgTrack.stream = FS_OpenStream( va( "media/%s", introTrack )); + s_bgTrack.stream = FS_OpenStream( introTrack ); + Q_strncpy( s_bgTrack.current, introTrack, sizeof( s_bgTrack.current )); memset( &musicfade, 0, sizeof( musicfade )); // clear any soundfade s_bgTrack.source = cls.key_dest; @@ -141,7 +149,7 @@ S_StreamGetCurrentState save\restore code ================= */ -qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ) +qboolean S_StreamGetCurrentState( char *currentTrack, size_t currentTrackSize, char *loopTrack, size_t loopTrackSize, int *position ) { if( !s_bgTrack.stream ) return false; // not active @@ -149,15 +157,15 @@ qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *posi if( currentTrack ) { if( s_bgTrack.current[0] ) - Q_strncpy( currentTrack, s_bgTrack.current, MAX_STRING ); - else Q_strncpy( currentTrack, "*", MAX_STRING ); // no track + Q_strncpy( currentTrack, s_bgTrack.current, currentTrackSize ); + else Q_strncpy( currentTrack, "*", currentTrackSize ); // no track } if( loopTrack ) { if( s_bgTrack.loopName[0] ) - Q_strncpy( loopTrack, s_bgTrack.loopName, MAX_STRING ); - else Q_strncpy( loopTrack, "*", MAX_STRING ); // no track + Q_strncpy( loopTrack, s_bgTrack.loopName, loopTrackSize ); + else Q_strncpy( loopTrack, "*", loopTrackSize ); // no track } if( position ) @@ -205,7 +213,7 @@ void S_StreamBackgroundTrack( void ) while( ch->s_rawend < soundtime + ch->max_samples ) { - wavdata_t *info = FS_StreamInfo( s_bgTrack.stream ); + const stream_t *info = s_bgTrack.stream; bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); @@ -234,7 +242,8 @@ void S_StreamBackgroundTrack( void ) if( r > 0 ) { // add to raw buffer - S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_BACKGROUNDTRACK ); + int music_vol = (int)(255.0f * S_GetMusicVolume()); + S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol ); } else { @@ -242,7 +251,7 @@ void S_StreamBackgroundTrack( void ) if( s_bgTrack.loopName[0] ) { FS_FreeStream( s_bgTrack.stream ); - s_bgTrack.stream = FS_OpenStream( va( "media/%s", s_bgTrack.loopName )); + s_bgTrack.stream = FS_OpenStream( s_bgTrack.loopName ); Q_strncpy( s_bgTrack.current, s_bgTrack.loopName, sizeof( s_bgTrack.current )); if( !s_bgTrack.stream ) return; @@ -279,66 +288,3 @@ void S_StopStreaming( void ) if( !dma.initialized ) return; s_listener.streaming = false; } - -/* -================= -S_StreamSoundTrack -================= -*/ -void S_StreamSoundTrack( void ) -{ - int bufferSamples; - int fileSamples; - byte raw[MAX_RAW_SAMPLES]; - int r, fileBytes; - rawchan_t *ch = NULL; - - if( !dma.initialized || !s_listener.streaming || s_listener.paused ) - return; - - ch = S_FindRawChannel( S_RAW_SOUND_SOUNDTRACK, true ); - - Assert( ch != NULL ); - - // see how many samples should be copied into the raw buffer - if( ch->s_rawend < soundtime ) - ch->s_rawend = soundtime; - - while( ch->s_rawend < soundtime + ch->max_samples ) - { - wavdata_t *info = SCR_GetMovieInfo(); - - if( !info ) break; // bad soundtrack? - - bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); - - // decide how much data needs to be read from the file - fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); - if( fileSamples <= 1 ) return; // no more samples need - - // our max buffer size - fileBytes = fileSamples * ( info->width * info->channels ); - - if( fileBytes > sizeof( raw )) - { - fileBytes = sizeof( raw ); - fileSamples = fileBytes / ( info->width * info->channels ); - } - - // read audio stream - r = SCR_GetAudioChunk( raw, fileBytes ); - - if( r < fileBytes ) - { - fileBytes = r; - fileSamples = r / ( info->width * info->channels ); - } - - if( r > 0 ) - { - // add to raw buffer - S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_SOUNDTRACK ); - } - else break; // no more samples for this frame - } -} diff --git a/engine/client/s_utils.c b/engine/client/s_utils.c index 0026334a..0f2a8d3e 100644 --- a/engine/client/s_utils.c +++ b/engine/client/s_utils.c @@ -16,204 +16,6 @@ GNU General Public License for more details. #include "common.h" #include "sound.h" -// hardcoded macros to test for zero crossing -#define ZERO_X_8( b ) (( b ) < 2 && ( b ) > -2 ) -#define ZERO_X_16( b ) (( b ) < 512 && ( b ) > -512 ) - -//----------------------------------------------------------------------------- -// Purpose: Search backward for a zero crossing starting at sample -// Input : sample - starting point -// Output : position of zero crossing -//----------------------------------------------------------------------------- -int S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample ) -{ - if( pWaveData == NULL ) - return sample; - - if( pWaveData->type == WF_PCMDATA ) - { - int sampleSize; - - sampleSize = pWaveData->width * pWaveData->channels; - - // this can never be zero -- other functions divide by this. - // This should never happen, but avoid crashing - if( sampleSize <= 0 ) sampleSize = 1; - - if( pWaveData->width == 1 ) - { - signed char *pData = (signed char *)(pWaveData->buffer + sample * sampleSize); - qboolean zero = false; - - if( pWaveData->channels == 1 ) - { - while( sample > 0 && !zero ) - { - if( ZERO_X_8( *pData )) - { - zero = true; - } - else - { - sample--; - pData--; - } - } - } - else - { - while( sample > 0 && !zero ) - { - if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] )) - { - zero = true; - } - else - { - sample--; - pData--; - } - } - } - } - else - { - short *pData = (short *)(pWaveData->buffer + sample * sampleSize); - qboolean zero = false; - - if( pWaveData->channels == 1 ) - { - while( sample > 0 && !zero ) - { - if( ZERO_X_16(*pData )) - { - zero = true; - } - else - { - pData--; - sample--; - } - } - } - else - { - while( sample > 0 && !zero ) - { - if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] )) - { - zero = true; - } - else - { - sample--; - pData--; - } - } - } - } - } - - return sample; -} - -//----------------------------------------------------------------------------- -// Purpose: Search forward for a zero crossing -// Input : sample - starting point -// Output : position of found zero crossing -//----------------------------------------------------------------------------- -int S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample ) -{ - if( pWaveData == NULL ) - return sample; - - if( pWaveData->type == WF_PCMDATA ) - { - int sampleSize; - - sampleSize = pWaveData->width * pWaveData->channels; - - // this can never be zero -- other functions divide by this. - // This should never happen, but avoid crashing - if( sampleSize <= 0 ) sampleSize = 1; - - if( pWaveData->width == 1 ) // 8-bit - { - signed char *pData = (signed char *)(pWaveData->buffer + sample * sampleSize); - qboolean zero = false; - - if( pWaveData->channels == 1 ) - { - while( sample < pWaveData->samples && !zero ) - { - if( ZERO_X_8( *pData )) - { - zero = true; - } - else - { - sample++; - pData++; - } - } - } - else - { - while( sample < pWaveData->samples && !zero ) - { - if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] )) - { - zero = true; - } - else - { - sample++; - pData++; - } - } - } - } - else - { - short *pData = (short *)(pWaveData->buffer + sample * sampleSize); - qboolean zero = false; - - if( pWaveData->channels == 1 ) - { - while( sample > 0 && !zero ) - { - if( ZERO_X_16( *pData )) - { - zero = true; - } - else - { - pData++; - sample++; - } - } - } - else - { - while( sample > 0 && !zero ) - { - if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] )) - { - zero = true; - } - else - { - sample++; - pData++; - } - } - } - } - } - - return sample; -} - //----------------------------------------------------------------------------- // Purpose: wrap the position wrt looping // Input : samplePosition - absolute position @@ -225,7 +27,7 @@ int S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean us // convert to a position within the loop // At the end of the loop, we return a short buffer, and subsequent call // will loop back and get the rest of the buffer - if( pSource->loopStart >= 0 && samplePosition >= pSource->samples && use_loop ) + if( FBitSet( pSource->flags, SOUND_LOOPED ) && samplePosition >= pSource->samples && use_loop ) { // size of loop int loopSize = pSource->samples - pSource->loopStart; @@ -279,28 +81,3 @@ int S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int s return sampleCount; } - -// move the current position to newPosition -void S_SetSampleStart( channel_t *pChan, wavdata_t *pSource, int newPosition ) -{ - if( pSource ) - newPosition = S_ZeroCrossingAfter( pSource, newPosition ); - - pChan->pMixer.sample = newPosition; -} - -// end playback at newEndPosition -void S_SetSampleEnd( channel_t *pChan, wavdata_t *pSource, int newEndPosition ) -{ - // forced end of zero means play the whole sample - if( !newEndPosition ) newEndPosition = 1; - - if( pSource ) - newEndPosition = S_ZeroCrossingBefore( pSource, newEndPosition ); - - // past current position? limit. - if( newEndPosition < pChan->pMixer.sample ) - newEndPosition = pChan->pMixer.sample; - - pChan->pMixer.forcedEndSample = newEndPosition; -} diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index 73818943..76c649bd 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -16,13 +16,128 @@ GNU General Public License for more details. #include "common.h" #include "sound.h" #include "const.h" -#include "sequence.h" #include +#define TRIM_SCAN_MAX 255 +#define TRIM_SAMPLES_BELOW_8 2 +#define TRIM_SAMPLES_BELOW_16 512 // 65k * 2 / 256 + +#define CVOXFILESENTENCEMAX 4096 + static int cszrawsentences = 0; static char *rgpszrawsentence[CVOXFILESENTENCEMAX]; static const char *voxperiod = "_period", *voxcomma = "_comma"; +static qboolean S_ShouldTrimSample8( const int8_t *buf, int channels ) +{ + if( abs( buf[0] ) > TRIM_SAMPLES_BELOW_8 ) + return false; + + if( channels >= 2 && abs( buf[1] ) > TRIM_SAMPLES_BELOW_8 ) + return false; + + return true; +} + +static qboolean S_ShouldTrimSample16( const int16_t *buf, int channels ) +{ + if( abs( buf[0] ) > TRIM_SAMPLES_BELOW_16 ) + return false; + + if( channels >= 2 && abs( buf[1] ) > TRIM_SAMPLES_BELOW_16 ) + return false; + + return true; +} + +static int S_TrimStart( const wavdata_t *wav, int start ) +{ + size_t channels = wav->channels, width = wav->width, i; + + if( wav->type != WF_PCMDATA ) + return start; + + if( width == 1 ) + { + const int8_t *data = (const int8_t *)&wav->buffer[channels * width * start]; + + for( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ ) + { + if( !S_ShouldTrimSample8( data, wav->channels )) + break; + + start += channels; + data += channels; + } + } + else if( width == 2 ) + { + const int16_t *data = (const int16_t *)&wav->buffer[channels * width * start]; + + for( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ ) + { + if( !S_ShouldTrimSample16( data, wav->channels )) + break; + + start += channels; + data += channels; + } + } + + return start; +} + +static int S_TrimEnd( const wavdata_t *wav, int end ) +{ + size_t channels = wav->channels, width = wav->width, i; + + if( wav->type != WF_PCMDATA ) + return end; + + if( width == 1 ) + { + const int8_t *data = (const int8_t *)&wav->buffer[channels * width * ( end - 1 )]; + + for( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ ) + { + if( !S_ShouldTrimSample8( data, wav->channels )) + break; + + end -= channels; + data -= channels; + } + } + else if( width == 2 ) + { + const int16_t *data = (const int16_t *)&wav->buffer[channels * width * ( end - 1 )]; + + for( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ ) + { + if( !S_ShouldTrimSample16( data, wav->channels )) + break; + + end -= channels; + data -= channels; + } + } + + return end; +} + +static void S_TrimStartEndTimes( channel_t *ch, wavdata_t *wav, int start, int end ) +{ + ch->pMixer.sample = start = S_TrimStart( wav, start ); + + // don't overrun the buffer while trimming end + if( end == 0 ) + end = wav->samples - wav->channels; + + if( end < start ) + end = start; + + ch->pMixer.forcedEndSample = S_TrimEnd( wav, end ); +} + // return number of samples mixed int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset ) { @@ -79,11 +194,7 @@ void VOX_LoadWord( channel_t *ch ) if( end <= start ) end = 0; - if( start ) - S_SetSampleStart( ch, data, start * 0.01f * samples ); - - if( end ) - S_SetSampleEnd( ch, data, end * 0.01f * samples ); + S_TrimStartEndTimes( ch, data, start * 0.01f * samples, end * 0.01f * samples ); } void VOX_FreeWord( channel_t *ch ) @@ -93,7 +204,7 @@ void VOX_FreeWord( channel_t *ch ) ch->currentWord = NULL; memset( &ch->pMixer, 0, sizeof( ch->pMixer )); - if( !word->sfx && !word->fKeepCached ) + if( !word->sfx || word->fKeepCached ) return; FS_FreeSound( word->sfx->cache ); @@ -137,12 +248,18 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize ) const char *p; int len; + // HACKHACK: some modders send strings like "/fvox/_period four" + // which should get parsed as "_period four" said by fvox + // it might be incorrect but ignore first slash here for now + if( psz[0] == '/' ) + psz++; + // search / backwards p = Q_strrchr( psz, '/' ); if( !p ) { - Q_strcpy( szpath, "vox/" ); + Q_strncpy( szpath, "vox/", nsize ); return psz; } @@ -150,7 +267,7 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize ) if( len > nsize ) { - Con_Printf( "VOX_GetDirectory: invalid directory in: %s\n", psz ); + Con_Printf( "%s: invalid directory in: %s\n", __func__, psz ); return NULL; } @@ -165,23 +282,11 @@ static const char *VOX_LookupString( const char *pszin ) int i = -1, len; const char *c; - // check if we are a CSCZ or immediate sentence + // check if we are an immediate sentence if( *pszin == '#' ) { - // Q_atoi is too smart and allows negative values - // so check with Q_isdigit beforehand - if( Q_isdigit( pszin + 1 )) - { - sentenceEntry_s *sentenceEntry; - i = Q_atoi( pszin + 1 ); - if(( sentenceEntry = Sequence_GetSentenceByIndex( i ))) - return sentenceEntry->data; - } - else - { - // immediate sentence, probably coming from "speak" command - return pszin + 1; - } + // immediate sentence, probably coming from "speak" command + return pszin + 1; } // check if we received an index @@ -353,22 +458,20 @@ static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, qboolean fF void VOX_LoadSound( channel_t *ch, const char *pszin ) { - char buffer[512], szpath[32], pathbuffer[64]; - char *rgpparseword[CVOXWORDMAX]; + char buffer[512] = { 0 }, szpath[32] = { 0 }; + char *rgpparseword[CVOXWORDMAX] = { 0 }; const char *psz; int i, j; if( !pszin ) return; - memset( buffer, 0, sizeof( buffer )); - memset( rgpparseword, 0, sizeof( rgpparseword )); - psz = VOX_LookupString( pszin ); if( !psz ) { - Con_Printf( "VOX_LoadSound: no sentence named %s\n", pszin ); + // sometimes modders remove sentences but entities continue to use them, so it's a warning, not an error + Con_Printf( S_WARN "%s: no sentence named %s\n", __func__, pszin ); return; } @@ -376,26 +479,31 @@ void VOX_LoadSound( channel_t *ch, const char *pszin ) if( !psz ) { - Con_Printf( "VOX_LoadSound: failed getting directory for %s\n", pszin ); + Con_Printf( S_ERROR "%s: failed getting directory for %s\n", __func__, pszin ); return; } if( Q_strlen( psz ) >= sizeof( buffer ) ) { - Con_Printf( "VOX_LoadSound: sentence is too long %s", psz ); + Con_Printf( S_ERROR "%s: sentence is too long %s\n", __func__, psz ); return; } Q_strncpy( buffer, psz, sizeof( buffer )); VOX_ParseString( buffer, rgpparseword ); - j = 0; - for( i = 0; rgpparseword[i]; i++ ) + for( i = 0, j = 0; i < CVOXWORDMAX && rgpparseword[i]; i++ ) { + char pathbuffer[MAX_SYSPATH]; + if( !VOX_ParseWordParams( rgpparseword[i], &ch->words[j], i == 0 )) continue; - Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] ); + if( Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s", szpath, rgpparseword[i] ) < 0 ) + { + Con_Printf( S_ERROR "%s: path to word in sentence %s is too long\n", __func__, pszin ); + return; + } ch->words[j].sfx = S_FindName( pathbuffer, &ch->words[j].fKeepCached ); @@ -449,7 +557,7 @@ static void VOX_ReadSentenceFile_( byte *buf, fs_offset_t size ) int index = cszrawsentences; int size = strlen( name ) + strlen( value ) + 2; - rgpszrawsentence[index] = Mem_Malloc( host.mempool, size ); + rgpszrawsentence[index] = Mem_Malloc( sndpool, size ); memcpy( rgpszrawsentence[index], name, size ); rgpszrawsentence[index][size - 1] = 0; cszrawsentences++; @@ -496,8 +604,8 @@ static void Test_VOX_GetDirectory( void ) { "", "", "vox/", "bark bark", "bark bark", "vox/", - "barney/meow", "meow", "barney/" - + "barney/meow", "meow", "barney/", + "/fvox/_period", "_period", "fvox/", }; int i; diff --git a/engine/client/sound.h b/engine/client/sound.h index 19f9eead..07b316ba 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -20,210 +20,183 @@ extern poolhandle_t sndpool; #include "xash3d_mathlib.h" -// sound engine rate defines -#define SOUND_DMA_SPEED 44100 // hardware playback rate -#define SOUND_11k 11025 // 11khz sample rate -#define SOUND_16k 16000 // 16khz sample rate -#define SOUND_22k 22050 // 22khz sample rate -#define SOUND_32k 32000 // 32khz sample rate -#define SOUND_44k 44100 // 44khz sample rate -#define DMA_MSEC_PER_SAMPLE ((float)(1000.0 / SOUND_DMA_SPEED)) +#define XASH_AUDIO_CD_QUALITY 1 // some platforms might need this -// fixed point stuff for real-time resampling -#define FIX_BITS 28 -#define FIX_SCALE (1 << FIX_BITS) -#define FIX_MASK ((1 << FIX_BITS)-1) -#define FIX_FLOAT(a) ((int)((a) * FIX_SCALE)) -#define FIX(a) (((int)(a)) << FIX_BITS) -#define FIX_INTPART(a) (((int)(a)) >> FIX_BITS) -#define FIX_FRACTION(a,b) (FIX(a)/(b)) -#define FIX_FRACPART(a) ((a) & FIX_MASK) +// sound engine rate defines +#if XASH_AUDIO_CD_QUALITY +#define SOUND_11k 11025 // 11khz sample rate +#define SOUND_22k 22050 // 22khz sample rate +#define SOUND_44k 44100 // 44khz sample rate +#else // XASH_AUDIO_CD_QUALITY +#define SOUND_11k 12000 // 11khz sample rate +#define SOUND_22k 24000 // 22khz sample rate +#define SOUND_44k 48000 // 44khz sample rate +#endif // XASH_AUDIO_CD_QUALITY + +#define SOUND_DMA_SPEED SOUND_44k // hardware playback rate // NOTE: clipped sound at 32760 to avoid overload -#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x ))) +#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x ))) -#define PAINTBUFFER_SIZE 1024 // 44k: was 512 -#define PAINTBUFFER (g_curpaintbuffer) -#define CPAINTBUFFERS 3 +#define PAINTBUFFER_SIZE 1024 // 44k: was 512 -// sound mixing buffer -#define CPAINTFILTERMEM 3 -#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer - -#define S_RAW_SOUND_IDLE_SEC 10 // time interval for idling raw sound before it's freed -#define S_RAW_SOUND_BACKGROUNDTRACK -2 -#define S_RAW_SOUND_SOUNDTRACK -1 -#define S_RAW_SAMPLES_PRECISION_BITS 14 - -#define CIN_FRAMETIME (1.0f / 30.0f) +#define S_RAW_SOUND_IDLE_SEC 10 // time interval for idling raw sound before it's freed +#define S_RAW_SOUND_BACKGROUNDTRACK -2 +#define S_RAW_SOUND_SOUNDTRACK -1 +#define S_RAW_SAMPLES_PRECISION_BITS 14 typedef struct { - int left; - int right; + int left; + int right; } portable_samplepair_t; -typedef struct -{ - qboolean factive; // if true, mix to this paintbuffer using flags - portable_samplepair_t *pbuf; // front stereo mix buffer, for 2 or 4 channel mixing - int ifilter; // current filter memory buffer to use for upsampling pass - portable_samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM]; -} paintbuffer_t; - typedef struct sfx_s { - char name[MAX_QPATH]; - wavdata_t *cache; + char name[MAX_QPATH]; + wavdata_t *cache; - int servercount; - uint hashValue; - struct sfx_s *hashNext; + int servercount; + uint hashValue; + struct sfx_s *hashNext; } sfx_t; -extern portable_samplepair_t paintbuffer[]; -extern portable_samplepair_t roombuffer[]; -extern portable_samplepair_t temppaintbuffer[]; -extern portable_samplepair_t *g_curpaintbuffer; -extern paintbuffer_t paintbuffers[]; - // structure used for fading in and out client sound volume. typedef struct { - float initial_percent; - float percent; // how far to adjust client's volume down by. - float starttime; // GetHostTime() when we started adjusting volume - float fadeouttime; // # of seconds to get to faded out state - float holdtime; // # of seconds to hold - float fadeintime; // # of seconds to restore + float initial_percent; + float percent; // how far to adjust client's volume down by. + float starttime; // GetHostTime() when we started adjusting volume + float fadeouttime; // # of seconds to get to faded out state + float holdtime; // # of seconds to hold + float fadeintime; // # of seconds to restore } soundfade_t; typedef struct { - float percent; + float percent; } musicfade_t; typedef struct snd_format_s { - unsigned int speed; - unsigned int width; - unsigned int channels; + uint speed; + byte width; + byte channels; } snd_format_t; typedef struct { - snd_format_t format; - int samples; // mono samples in buffer - int samplepos; // in mono samples - byte *buffer; - qboolean initialized; // sound engine is active - const char *backendName; + snd_format_t format; + int samples; // mono samples in buffer + int samplepos; // in mono samples + qboolean initialized; // sound engine is active + byte *buffer; + const char *backendName; } dma_t; #include "vox.h" typedef struct { - double sample; - - wavdata_t *pData; - double forcedEndSample; - qboolean finished; + double sample; + wavdata_t *pData; + double forcedEndSample; + qboolean finished; } mixer_t; typedef struct rawchan_s { - int entnum; - int master_vol; - int leftvol; // 0-255 left volume - int rightvol; // 0-255 right volume - float dist_mult; // distance multiplier (attenuation/clipK) - vec3_t origin; // only use if fixed_origin is set - volatile uint s_rawend; - wavdata_t sound_info; // advance play position - float oldtime; // catch time jumps - size_t max_samples; // buffer length - portable_samplepair_t rawsamples[1]; // variable sized + int entnum; + int master_vol; + int leftvol; // 0-255 left volume + int rightvol; // 0-255 right volume + float dist_mult; // distance multiplier (attenuation/clipK) + vec3_t origin; // only use if fixed_origin is set + volatile uint s_rawend; + float oldtime; // catch time jumps + size_t max_samples; // buffer length + portable_samplepair_t rawsamples[]; // variable sized } rawchan_t; typedef struct channel_s { - char name[16]; // keept sentence name - sfx_t *sfx; // sfx number + char name[16]; // keep sentence name + sfx_t *sfx; // sfx number - int leftvol; // 0-255 left volume - int rightvol; // 0-255 right volume + int leftvol; // 0-255 left volume + int rightvol; // 0-255 right volume - int entnum; // entity soundsource - int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.) - vec3_t origin; // only use if fixed_origin is set - float dist_mult; // distance multiplier (attenuation/clipK) - int master_vol; // 0-255 master volume - qboolean isSentence; // bit who indicated sentence - int basePitch; // base pitch percent (100% is normal pitch playback) - float pitch; // real-time pitch after any modulation or shift by dynamic data - qboolean use_loop; // don't loop default and local sounds - qboolean staticsound; // use origin instead of fetching entnum's origin - qboolean localsound; // it's a local menu sound (not looped, not paused) - mixer_t pMixer; + int entnum; // entity soundsource + int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.) + vec3_t origin; // only use if fixed_origin is set + float dist_mult; // distance multiplier (attenuation/clipK) + int master_vol; // 0-255 master volume + int basePitch; // base pitch percent (100% is normal pitch playback) + float pitch; // real-time pitch after any modulation or shift by dynamic data + qboolean use_loop; // don't loop default and local sounds + qboolean staticsound; // use origin instead of fetching entnum's origin + qboolean localsound; // it's a local menu sound (not looped, not paused) + mixer_t pMixer; // sentence mixer - int wordIndex; - mixer_t *currentWord; // NULL if sentence is finished - voxword_t words[CVOXWORDMAX]; + qboolean isSentence; // bit indicating sentence + int wordIndex; + mixer_t *currentWord; // NULL if sentence is finished + voxword_t words[CVOXWORDMAX]; } channel_t; typedef struct { - vec3_t origin; // simorg + view_ofs - vec3_t velocity; - vec3_t forward; - vec3_t right; - vec3_t up; + vec3_t origin; // simorg + view_ofs + vec3_t forward; + vec3_t right; + vec3_t up; - int entnum; - int waterlevel; - float frametime; // used for sound fade - qboolean active; - qboolean inmenu; // listener in-menu ? - qboolean paused; - qboolean streaming; // playing AVI-file - qboolean stream_paused; // pause only background track + int entnum; + int waterlevel; + float frametime; // used for sound fade + qboolean active; + qboolean inmenu; // listener in-menu ? + qboolean paused; + qboolean streaming; // playing AVI-file + qboolean stream_paused; // pause only background track } listener_t; typedef struct { - string current; // a currently playing track - string loopName; // may be empty - stream_t *stream; - int source; // may be game, menu, etc + string current; // a currently playing track + string loopName; // may be empty + stream_t *stream; + int source; // may be game, menu, etc } bg_track_t; +typedef int sound_t; + //==================================================================== -#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) -#define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp -#define MAX_RAW_CHANNELS 48 -#define MAX_RAW_SAMPLES 8192 +#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) +#define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp +#define MAX_RAW_CHANNELS 48 +#define MAX_RAW_SAMPLES 16384 +#define SND_CLIP_DISTANCE 1000.0f -extern sound_t ambient_sfx[NUM_AMBIENTS]; -extern qboolean snd_ambient; -extern channel_t channels[MAX_CHANNELS]; -extern rawchan_t *raw_channels[MAX_RAW_CHANNELS]; -extern int total_channels; -extern int paintedtime; -extern int soundtime; -extern listener_t s_listener; -extern int idsp_room; -extern dma_t dma; +extern sound_t ambient_sfx[NUM_AMBIENTS]; +extern qboolean snd_ambient; +extern channel_t channels[MAX_CHANNELS]; +extern rawchan_t *raw_channels[MAX_RAW_CHANNELS]; +extern int total_channels; +extern int paintedtime; +extern int soundtime; +extern listener_t s_listener; +extern int idsp_room; +extern dma_t dma; -extern convar_t s_musicvolume; -extern convar_t s_lerping; -extern convar_t *dsp_off; -extern convar_t s_test; // cvar to testify new effects +extern convar_t s_musicvolume; +extern convar_t s_lerping; +extern convar_t s_test; // cvar to test new effects extern convar_t s_samplecount; -extern convar_t snd_mute_losefocus; extern convar_t s_warn_late_precache; +extern convar_t snd_mute_losefocus; void S_InitScaletable( void ); wavdata_t *S_LoadSound( sfx_t *sfx ); @@ -256,8 +229,7 @@ void S_InitSounds( void ); void SX_Init( void ); void SX_Free( void ); void CheckNewDspPresets( void ); -void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ); -float DSP_GetGain( int idsp ); +void DSP_Process( portable_samplepair_t *pbfront, int sampleCount ); void DSP_ClearState( void ); qboolean S_Init( void ); @@ -272,12 +244,10 @@ int S_GetCurrentStaticSounds( soundlist_t *pout, int size ); int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ); sfx_t *S_GetSfxByHandle( sound_t handle ); rawchan_t *S_FindRawChannel( int entnum, qboolean create ); +uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ); void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ); -void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); void S_StopSound( int entnum, int channel, const char *soundname ); void S_UpdateFrame( struct ref_viewpass_s *rvp ); -uint S_GetRawSamplesLength( int entnum ); -void S_ClearRawChannel( int entnum ); void S_StopAllSounds( qboolean ambient ); void S_FreeSounds( void ); @@ -295,9 +265,7 @@ void SND_ForceCloseMouth( int entnum ); // // s_stream.c // -void S_StreamSoundTrack( void ); void S_StreamBackgroundTrack( void ); -qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); void S_PrintBackgroundTrackState( void ); void S_FadeMusicVolume( float fadePercent ); @@ -306,9 +274,8 @@ void S_FadeMusicVolume( float fadePercent ); // int S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample ); int S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample ); +int S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean use_loop ); int S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop ); -void S_SetSampleStart( channel_t *pChan, wavdata_t *pSource, int newPosition ); -void S_SetSampleEnd( channel_t *pChan, wavdata_t *pSource, int newEndPosition ); // // s_vox.c diff --git a/engine/common/soundlib/libmpg/dct36.c b/engine/client/soundlib/libmpg/dct36.c similarity index 100% rename from engine/common/soundlib/libmpg/dct36.c rename to engine/client/soundlib/libmpg/dct36.c diff --git a/engine/common/soundlib/libmpg/dct64.c b/engine/client/soundlib/libmpg/dct64.c similarity index 100% rename from engine/common/soundlib/libmpg/dct64.c rename to engine/client/soundlib/libmpg/dct64.c diff --git a/engine/common/soundlib/libmpg/fmt123.h b/engine/client/soundlib/libmpg/fmt123.h similarity index 100% rename from engine/common/soundlib/libmpg/fmt123.h rename to engine/client/soundlib/libmpg/fmt123.h diff --git a/engine/common/soundlib/libmpg/format.c b/engine/client/soundlib/libmpg/format.c similarity index 97% rename from engine/common/soundlib/libmpg/format.c rename to engine/client/soundlib/libmpg/format.c index 75e3c204..fb40aa7f 100644 --- a/engine/common/soundlib/libmpg/format.c +++ b/engine/client/soundlib/libmpg/format.c @@ -56,20 +56,20 @@ static int good_enc( const int enc ) return FALSE; } -void mpg123_rates( const long **list, size_t *number ) +static void mpg123_rates( const long **list, size_t *number ) { if( number != NULL ) *number = sizeof( my_rates ) / sizeof( long ); if( list != NULL ) *list = my_rates; } // now that's a bit tricky... One build of the library knows only a subset of the encodings. -void mpg123_encodings( const int **list, size_t *number ) +static void mpg123_encodings( const int **list, size_t *number ) { if( number != NULL ) *number = sizeof( good_encodings ) / sizeof( int ); if( list != NULL ) *list = good_encodings; } -int mpg123_encsize( int encoding ) +static int mpg123_encsize( int encoding ) { return sizeof( short ); } @@ -364,7 +364,7 @@ int mpg123_format( mpg123_handle_t *mh, long rate, int channels, int encodings ) return r; } -int mpg123_format_support( mpg123_handle_t *mh, long rate, int encoding ) +static int mpg123_format_support( mpg123_handle_t *mh, long rate, int encoding ) { if( mh == NULL ) return 0; diff --git a/engine/common/soundlib/libmpg/frame.c b/engine/client/soundlib/libmpg/frame.c similarity index 99% rename from engine/common/soundlib/libmpg/frame.c rename to engine/client/soundlib/libmpg/frame.c index 6344b6d6..7a714993 100644 --- a/engine/common/soundlib/libmpg/frame.c +++ b/engine/client/soundlib/libmpg/frame.c @@ -393,7 +393,7 @@ void frame_exit( mpg123_handle_t *fr ) bc_cleanup( &fr->rdat.buffer ); } -int mpg123_framedata( mpg123_handle_t *mh, ulong *header, byte **bodydata, size_t *bodybytes ) +static int mpg123_framedata( mpg123_handle_t *mh, ulong *header, byte **bodydata, size_t *bodybytes ) { if( mh == NULL ) return MPG123_BAD_HANDLE; @@ -512,7 +512,7 @@ mpg_off_t frame_index_find( mpg123_handle_t *fr, mpg_off_t want_frame, mpg_off_t return gopos; } -mpg_off_t frame_ins2outs( mpg123_handle_t *fr, mpg_off_t ins ) +static mpg_off_t frame_ins2outs( mpg123_handle_t *fr, mpg_off_t ins ) { mpg_off_t outs = 0; diff --git a/engine/common/soundlib/libmpg/frame.h b/engine/client/soundlib/libmpg/frame.h similarity index 100% rename from engine/common/soundlib/libmpg/frame.h rename to engine/client/soundlib/libmpg/frame.h diff --git a/engine/common/soundlib/libmpg/getbits.h b/engine/client/soundlib/libmpg/getbits.h similarity index 94% rename from engine/common/soundlib/libmpg/getbits.h rename to engine/client/soundlib/libmpg/getbits.h index 350d5ab1..a4e5f165 100644 --- a/engine/common/soundlib/libmpg/getbits.h +++ b/engine/client/soundlib/libmpg/getbits.h @@ -47,6 +47,10 @@ static uint getbits( mpg123_handle_t *fr, int number_of_bits ) { ulong rval; + if( (long)(fr->wordpointer-fr->bsbuf)*8 + + fr->bitindex+number_of_bits > (long)fr->framesize*8 ) + return 0; + rval = fr->wordpointer[0]; rval <<= 8; rval |= fr->wordpointer[1]; diff --git a/engine/common/soundlib/libmpg/huffman.h b/engine/client/soundlib/libmpg/huffman.h similarity index 100% rename from engine/common/soundlib/libmpg/huffman.h rename to engine/client/soundlib/libmpg/huffman.h diff --git a/engine/common/soundlib/libmpg/index.c b/engine/client/soundlib/libmpg/index.c similarity index 100% rename from engine/common/soundlib/libmpg/index.c rename to engine/client/soundlib/libmpg/index.c diff --git a/engine/common/soundlib/libmpg/index.h b/engine/client/soundlib/libmpg/index.h similarity index 100% rename from engine/common/soundlib/libmpg/index.h rename to engine/client/soundlib/libmpg/index.h diff --git a/engine/common/soundlib/libmpg/layer3.c b/engine/client/soundlib/libmpg/layer3.c similarity index 99% rename from engine/common/soundlib/libmpg/layer3.c rename to engine/client/soundlib/libmpg/layer3.c index e4e1228c..5b1b7adb 100644 --- a/engine/common/soundlib/libmpg/layer3.c +++ b/engine/client/soundlib/libmpg/layer3.c @@ -35,10 +35,10 @@ static float tan1_1[16]; static float tan2_1[16]; static float tan1_2[16]; static float tan2_2[16]; -static float pow1_1[2][16]; -static float pow2_1[2][16]; -static float pow1_2[2][16]; -static float pow2_2[2][16]; +static float pow1_1[2][32]; +static float pow2_1[2][32]; +static float pow1_2[2][32]; +static float pow2_2[2][32]; static int mapbuf0[9][152]; static int mapbuf1[9][156]; static int mapbuf2[9][44]; @@ -217,7 +217,10 @@ void init_layer3( void ) tan2_1[i] = DOUBLE_TO_REAL_15( 1.0 / (1.0 + t)); tan1_2[i] = DOUBLE_TO_REAL_15( M_SQRT2 * t / (1.0 + t)); tan2_2[i] = DOUBLE_TO_REAL_15( M_SQRT2 / (1.0 + t)); + } + for( i = 0; i < 32; i++ ) + { for( j = 0; j < 2; j++ ) { double base = pow( 2.0, -0.25 * (j + 1.0)); diff --git a/engine/common/soundlib/libmpg/libmpg.c b/engine/client/soundlib/libmpg/libmpg.c similarity index 97% rename from engine/common/soundlib/libmpg/libmpg.c rename to engine/client/soundlib/libmpg/libmpg.c index 3397dc0d..05127440 100644 --- a/engine/common/soundlib/libmpg/libmpg.c +++ b/engine/client/soundlib/libmpg/libmpg.c @@ -87,7 +87,7 @@ int open_mpeg_stream( void *mpg, void *file, pfread f_read, pfseek f_seek, wavin if( !mh || !sc ) return 0; - ret = mpg123_replace_reader_handle( mh, (void *)f_read, (void *)f_seek, NULL ); + ret = mpg123_replace_reader_handle( mh, f_read, f_seek, NULL ); if( ret != MPG123_OK ) return 0; diff --git a/engine/common/soundlib/libmpg/libmpg.dsp b/engine/client/soundlib/libmpg/libmpg.dsp similarity index 100% rename from engine/common/soundlib/libmpg/libmpg.dsp rename to engine/client/soundlib/libmpg/libmpg.dsp diff --git a/engine/common/soundlib/libmpg/libmpg.h b/engine/client/soundlib/libmpg/libmpg.h similarity index 85% rename from engine/common/soundlib/libmpg/libmpg.h rename to engine/client/soundlib/libmpg/libmpg.h index 34f6e23b..da12c1c7 100644 --- a/engine/common/soundlib/libmpg/libmpg.h +++ b/engine/client/soundlib/libmpg/libmpg.h @@ -34,9 +34,15 @@ typedef struct int playtime; // stream size in milliseconds } wavinfo_t; +#ifdef _MSC_VER // a1ba: MSVC6 don't have ssize_t +typedef long mpg_ssize_t; +#else +typedef ssize_t mpg_ssize_t; +#endif + // custom stdio -typedef long (*pfread)( void *handle, void *buf, size_t count ); -typedef long (*pfseek)( void *handle, long offset, int whence ); +typedef mpg_ssize_t (*pfread)( void *handle, void *buf, size_t count ); +typedef fs_offset_t (*pfseek)( void *handle, fs_offset_t offset, int whence ); extern void *create_decoder( int *error ); extern int feed_mpeg_header( void *mpg, const byte *data, long bufsize, long streamsize, wavinfo_t *sc ); diff --git a/engine/common/soundlib/libmpg/mpeghead.h b/engine/client/soundlib/libmpg/mpeghead.h similarity index 100% rename from engine/common/soundlib/libmpg/mpeghead.h rename to engine/client/soundlib/libmpg/mpeghead.h diff --git a/engine/common/soundlib/libmpg/mpg123.c b/engine/client/soundlib/libmpg/mpg123.c similarity index 98% rename from engine/common/soundlib/libmpg/mpg123.c rename to engine/client/soundlib/libmpg/mpg123.c index 03968d48..c5b48c6c 100644 --- a/engine/common/soundlib/libmpg/mpg123.c +++ b/engine/client/soundlib/libmpg/mpg123.c @@ -15,6 +15,7 @@ GNU General Public License for more details. #include "mpg123.h" #include "sample.h" +#include "libmpg.h" static int initialized = 0; @@ -83,7 +84,7 @@ mpg123_handle_t *mpg123_parnew( mpg123_parm_t *mp, int *error ) return fr; } -int mpg123_par( mpg123_parm_t *mp, enum mpg123_parms key, long val ) +static int mpg123_par( mpg123_parm_t *mp, enum mpg123_parms key, long val ) { int ret = MPG123_OK; @@ -189,7 +190,7 @@ int mpg123_param( mpg123_handle_t *mh, enum mpg123_parms key, long val ) } } -int mpg123_close( mpg123_handle_t *mh ) +static int mpg123_close( mpg123_handle_t *mh ) { if( mh == NULL ) return MPG123_BAD_HANDLE; @@ -263,7 +264,7 @@ int mpg123_replace_reader_handle( mpg123_handle_t *mh, mpg_ssize_t (*fread)( voi // a) a new choice of decoder // b) a changed native format of the MPEG stream // ... calls are only valid after parsing some MPEG frame! -int decode_update( mpg123_handle_t *mh ) +static int decode_update( mpg123_handle_t *mh ) { long native_rate; int b; @@ -320,13 +321,13 @@ int decode_update( mpg123_handle_t *mh ) return 0; } -size_t mpg123_safe_buffer( void ) +static size_t mpg123_safe_buffer( void ) { // real is the largest possible output return sizeof( float ) * 2 * 1152; } -size_t mpg123_outblock( mpg123_handle_t *mh ) +static size_t mpg123_outblock( mpg123_handle_t *mh ) { // try to be helpful and never return zero output block size. if( mh != NULL && mh->outblock > 0 ) @@ -697,7 +698,7 @@ int mpg123_getformat( mpg123_handle_t *mh, int *rate, int *channels, int *encodi return MPG123_OK; } -int mpg123_scan( mpg123_handle_t *mh ) +static int mpg123_scan( mpg123_handle_t *mh ) { mpg_off_t track_frames = 0; mpg_off_t track_samples = 0; @@ -963,8 +964,10 @@ const char *mpg123_plain_strerror( int errcode ) } } -const char *get_error( mpg123_handle_t *mh ) +const char *get_error( void *handle ) { + mpg123_handle_t *mh = handle; + if( !mh ) return mpg123_plain_strerror( MPG123_BAD_HANDLE ); return mpg123_plain_strerror( mh->err ); } diff --git a/engine/common/soundlib/libmpg/mpg123.h b/engine/client/soundlib/libmpg/mpg123.h similarity index 99% rename from engine/common/soundlib/libmpg/mpg123.h rename to engine/client/soundlib/libmpg/mpg123.h index 6db8b3f7..0f9a8857 100644 --- a/engine/common/soundlib/libmpg/mpg123.h +++ b/engine/client/soundlib/libmpg/mpg123.h @@ -32,6 +32,7 @@ typedef struct mpg123_handle_s mpg123_handle_t; #include #include "fmt123.h" #include STDINT_H +#include "xash3d_types.h" #ifndef FALSE #define FALSE 0 @@ -50,7 +51,7 @@ typedef unsigned char byte; typedef unsigned short word; typedef unsigned long ulong; typedef unsigned int uint; -typedef long mpg_off_t; +typedef fs_offset_t mpg_off_t; #ifdef _MSC_VER // a1ba: MSVC6 don't have ssize_t typedef long mpg_ssize_t; diff --git a/engine/common/soundlib/libmpg/parse.c b/engine/client/soundlib/libmpg/parse.c similarity index 99% rename from engine/common/soundlib/libmpg/parse.c rename to engine/client/soundlib/libmpg/parse.c index 8ef2db2d..b84d472b 100644 --- a/engine/common/soundlib/libmpg/parse.c +++ b/engine/client/soundlib/libmpg/parse.c @@ -68,7 +68,7 @@ void set_pointer( mpg123_handle_t *fr, long backstep ) fr->bitindex = 0; } -int frame_bitrate( mpg123_handle_t *fr ) +static int frame_bitrate( mpg123_handle_t *fr ) { return tabsel_123[fr->lsf][fr->lay-1][fr->bitrate_index]; } @@ -643,7 +643,7 @@ static int forget_head_shift( mpg123_handle_t *fr, ulong *newheadp, int forget ) // returns: 0: bad or just unparseable tag // 1: good, (possibly) new tag info // <0: reader error (may need more data feed, try again) -int parse_new_id3( mpg123_handle_t *fr, ulong first4bytes ) +static int parse_new_id3( mpg123_handle_t *fr, ulong first4bytes ) { byte buf[6]; ulong length=0; diff --git a/engine/common/soundlib/libmpg/reader.c b/engine/client/soundlib/libmpg/reader.c similarity index 98% rename from engine/common/soundlib/libmpg/reader.c rename to engine/client/soundlib/libmpg/reader.c index db8d825f..8cc1283d 100644 --- a/engine/common/soundlib/libmpg/reader.c +++ b/engine/client/soundlib/libmpg/reader.c @@ -853,11 +853,21 @@ int open_feed( mpg123_handle_t *fr ) return 0; } +static mpg_ssize_t read_mpgtypes( int fd, void *buf, size_t count ) +{ + return read( fd, buf, count ); +} + +static mpg_off_t lseek_mpgtypes( int fd, mpg_off_t offset, int whence ) +{ + return lseek( fd, offset, whence ); +} + static int default_init( mpg123_handle_t *fr ) { fr->rdat.fdread = plain_read; - fr->rdat.read = fr->rdat.r_read != NULL ? fr->rdat.r_read : read; - fr->rdat.lseek = fr->rdat.r_lseek != NULL ? fr->rdat.r_lseek : lseek; + fr->rdat.read = fr->rdat.r_read != NULL ? fr->rdat.r_read : read_mpgtypes; + fr->rdat.lseek = fr->rdat.r_lseek != NULL ? fr->rdat.r_lseek : lseek_mpgtypes; fr->rdat.filelen = get_fileinfo( fr ); fr->rdat.filepos = 0; diff --git a/engine/common/soundlib/libmpg/reader.h b/engine/client/soundlib/libmpg/reader.h similarity index 100% rename from engine/common/soundlib/libmpg/reader.h rename to engine/client/soundlib/libmpg/reader.h diff --git a/engine/common/soundlib/libmpg/sample.h b/engine/client/soundlib/libmpg/sample.h similarity index 100% rename from engine/common/soundlib/libmpg/sample.h rename to engine/client/soundlib/libmpg/sample.h diff --git a/engine/common/soundlib/libmpg/synth.c b/engine/client/soundlib/libmpg/synth.c similarity index 97% rename from engine/common/soundlib/libmpg/synth.c rename to engine/client/soundlib/libmpg/synth.c index bf1ca981..7e0960ec 100644 --- a/engine/common/soundlib/libmpg/synth.c +++ b/engine/client/soundlib/libmpg/synth.c @@ -25,7 +25,7 @@ GNU General Public License for more details. else { *(samples) = REAL_TO_SHORT( sum ); } // main synth function, uses the plain dct64 -int synth_1to1( float *bandPtr, int channel, mpg123_handle_t *fr, int final ) +static int synth_1to1( float *bandPtr, int channel, mpg123_handle_t *fr, int final ) { static const int step = 2; short *samples = (short *) (fr->buffer.data + fr->buffer.fill); @@ -147,7 +147,7 @@ static int synth_stereo( float *bandPtr_l, float *bandPtr_r, mpg123_handle_t *fr } // mono to stereo synth, wrapping over synth_1to1 -int synth_1to1_m2s(float *bandPtr, mpg123_handle_t *fr ) +static int synth_1to1_m2s(float *bandPtr, mpg123_handle_t *fr ) { byte *samples = fr->buffer.data; int i, ret; @@ -165,7 +165,7 @@ int synth_1to1_m2s(float *bandPtr, mpg123_handle_t *fr ) } // mono synth, wrapping over synth_1to1 -int synth_1to1_mono( float *bandPtr, mpg123_handle_t *fr ) +static int synth_1to1_mono( float *bandPtr, mpg123_handle_t *fr ) { short samples_tmp[BLOCK]; short *tmp1 = samples_tmp; diff --git a/engine/common/soundlib/libmpg/synth.h b/engine/client/soundlib/libmpg/synth.h similarity index 100% rename from engine/common/soundlib/libmpg/synth.h rename to engine/client/soundlib/libmpg/synth.h diff --git a/engine/common/soundlib/libmpg/tabinit.c b/engine/client/soundlib/libmpg/tabinit.c similarity index 100% rename from engine/common/soundlib/libmpg/tabinit.c rename to engine/client/soundlib/libmpg/tabinit.c diff --git a/engine/client/soundlib/ogg_filestream.c b/engine/client/soundlib/ogg_filestream.c new file mode 100644 index 00000000..566168ef --- /dev/null +++ b/engine/client/soundlib/ogg_filestream.c @@ -0,0 +1,60 @@ +/* +ogg_filestream.c - helper struct for working with Ogg files +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "ogg_filestream.h" +#include "soundlib.h" +#include + +size_t OggFilestream_Read( void *ptr, size_t blockSize, size_t nmemb, void *datasource ) +{ + ogg_filestream_t *filestream = (ogg_filestream_t *)datasource; + size_t remain = filestream->filesize - filestream->position; + size_t dataSize = blockSize * nmemb; + + // reads as many blocks as fits in remaining memory + if( dataSize > remain ) + dataSize = remain - remain % blockSize; + + memcpy( ptr, filestream->buffer + filestream->position, dataSize ); + filestream->position += dataSize; + return dataSize / blockSize; +} + +int OggFilestream_Seek( void *datasource, int64_t offset, int whence ) +{ + int64_t position; + ogg_filestream_t *filestream = (ogg_filestream_t *)datasource; + + if( whence == SEEK_SET ) + position = offset; + else if( whence == SEEK_CUR ) + position = offset + filestream->position; + else if( whence == SEEK_END ) + position = offset + filestream->filesize; + else + return -1; + + if( position < 0 || position > filestream->filesize ) + return -1; + + filestream->position = position; + return 0; +} + +long OggFilestream_Tell( void *datasource ) +{ + ogg_filestream_t *filestream = (ogg_filestream_t *)datasource; + return filestream->position; +} diff --git a/engine/client/soundlib/ogg_filestream.h b/engine/client/soundlib/ogg_filestream.h new file mode 100644 index 00000000..2e8205ea --- /dev/null +++ b/engine/client/soundlib/ogg_filestream.h @@ -0,0 +1,41 @@ +/* +ogg_filestream.h - helper struct for working with Ogg files +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef OGG_FILESTREAM_H +#define OGG_FILESTREAM_H + +#include "xash3d_types.h" + +typedef struct ogg_filestream_s +{ + const char *name; + const byte *buffer; + size_t filesize; + size_t position; +} ogg_filestream_t; + +size_t OggFilestream_Read( void *ptr, size_t blockSize, size_t nmemb, void *datasource ); +int OggFilestream_Seek( void *datasource, int64_t offset, int whence ); +long OggFilestream_Tell( void *datasource ); + +static inline void OggFilestream_Init( ogg_filestream_t *filestream, const char *name, const byte *buffer, size_t filesize ) +{ + filestream->name = name; + filestream->buffer = buffer; + filestream->filesize = filesize; + filestream->position = 0; +} + +#endif // OGG_FILESTREAM_H diff --git a/engine/common/soundlib/snd_main.c b/engine/client/soundlib/snd_main.c similarity index 70% rename from engine/common/soundlib/snd_main.c rename to engine/client/soundlib/snd_main.c index 94b4ff9d..57761ae0 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/client/soundlib/snd_main.c @@ -15,10 +15,7 @@ GNU General Public License for more details. #include "soundlib.h" -// global sound variables -sndlib_t sound; - -void Sound_Reset( void ) +static void Sound_Reset( void ) { // reset global variables sound.width = sound.rate = 0; @@ -30,19 +27,22 @@ void Sound_Reset( void ) sound.size = 0; } -wavdata_t *SoundPack( void ) +static MALLOC_LIKE( FS_FreeSound, 1 ) wavdata_t *SoundPack( void ) { - wavdata_t *pack = Mem_Calloc( host.soundpool, sizeof( wavdata_t )); + wavdata_t *pack = Mem_Malloc( host.soundpool, sizeof( *pack ) + sound.size ); - pack->buffer = sound.wav; - pack->width = sound.width; - pack->rate = sound.rate; - pack->type = sound.type; pack->size = sound.size; pack->loopStart = sound.loopstart; pack->samples = sound.samples; - pack->channels = sound.channels; + pack->type = sound.type; pack->flags = sound.flags; + pack->rate = sound.rate; + pack->width = sound.width; + pack->channels = sound.channels; + memcpy( pack->buffer, sound.wav, sound.size ); + + Mem_Free( sound.wav ); + sound.wav = NULL; return pack; } @@ -56,21 +56,19 @@ loading and unpack to wav any known sound */ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) { - const char *ext = COM_FileExtension( filename ); - string path, loadname; - qboolean anyformat = true; - fs_offset_t filesize = 0; - const loadwavfmt_t *format; - byte *f; + const char *ext = COM_FileExtension( filename ); + string loadname; + qboolean anyformat = true; + const loadwavfmt_t *format; Sound_Reset(); // clear old sounddata Q_strncpy( loadname, filename, sizeof( loadname )); - if( Q_stricmp( ext, "" )) + if( COM_CheckStringEmpty( ext )) { // we needs to compare file extension with list of supported formats // and be sure what is real extension, not a filename with dot - for( format = sound.loadformats; format && format->formatstring; format++ ) + for( format = sound.loadformats; format && format->ext; format++ ) { if( !Q_stricmp( format->ext, ext )) { @@ -86,26 +84,42 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) goto load_internal; // now try all the formats in the selected list - for( format = sound.loadformats; format && format->formatstring; format++) + for( format = sound.loadformats; format && format->ext; format++) { if( anyformat || !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + qboolean success = false; + fs_offset_t filesize = 0; + byte *f; + string path; + + Q_snprintf( path, sizeof( path ), DEFAULT_SOUNDPATH "%s.%s", loadname, format->ext ); + f = FS_LoadFile( path, &filesize, false ); if( f && filesize > 0 ) { - if( format->loadfunc( path, f, filesize )) - { - Mem_Free(f); // release buffer - return SoundPack(); // loaded - } - else Mem_Free(f); // release buffer + success = format->loadfunc( path, f, filesize ); + Mem_Free( f ); // release buffer } + + if( success ) + return SoundPack(); // loaded + + Q_snprintf( path, sizeof( path ), "%s.%s", loadname, format->ext ); + f = FS_LoadFile( path, &filesize, false ); + if( f && filesize > 0 ) + { + success = format->loadfunc( path, f, filesize ); + Mem_Free( f ); // release buffer + } + + if( success ) + return SoundPack(); } } load_internal: - for( format = sound.loadformats; format && format->formatstring; format++ ) + for( format = sound.loadformats; format && format->ext; format++ ) { if( anyformat || !Q_stricmp( ext, format->ext )) { @@ -118,7 +132,7 @@ load_internal: } if( filename[0] != '#' ) - Con_DPrintf( S_WARN "FS_LoadSound: couldn't load \"%s\"\n", loadname ); + Con_DPrintf( S_WARN "%s: couldn't load \"%s\"\n", __func__, loadname ); return NULL; } @@ -133,7 +147,6 @@ free WAV buffer void FS_FreeSound( wavdata_t *pack ) { if( !pack ) return; - if( pack->buffer ) Mem_Free( pack->buffer ); Mem_Free( pack ); } @@ -147,19 +160,19 @@ open and reading basic info from sound stream stream_t *FS_OpenStream( const char *filename ) { const char *ext = COM_FileExtension( filename ); - string path, loadname; + string loadname; qboolean anyformat = true; const streamfmt_t *format; - stream_t *stream; + stream_t *stream = NULL; Sound_Reset(); // clear old streaminfo Q_strncpy( loadname, filename, sizeof( loadname )); - if( Q_stricmp( ext, "" )) + if( COM_CheckStringEmpty( ext )) { // we needs to compare file extension with list of supported formats // and be sure what is real extension, not a filename with dot - for( format = sound.streamformat; format && format->formatstring; format++ ) + for( format = sound.streamformat; format && format->ext; format++ ) { if( !Q_stricmp( format->ext, ext )) { @@ -171,11 +184,14 @@ stream_t *FS_OpenStream( const char *filename ) } // now try all the formats in the selected list - for( format = sound.streamformat; format && format->formatstring; format++) + for( format = sound.streamformat; format && format->ext; format++) { if( anyformat || !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + string path; + + Q_snprintf( path, sizeof( path ), "%s.%s", loadname, format->ext ); + if(( stream = format->openfunc( path )) != NULL ) { stream->format = format; @@ -184,36 +200,18 @@ stream_t *FS_OpenStream( const char *filename ) } } - Con_Reportf( "FS_OpenStream: couldn't open \"%s\"\n", loadname ); + // compatibility with original Xash3D, try media/ folder + if( Q_strncmp( filename, "media/", sizeof( "media/" ) - 1 )) + { + Q_snprintf( loadname, sizeof( loadname ), "media/%s", filename ); + stream = FS_OpenStream( loadname ); + } + else + { + Con_Reportf( "%s: couldn't open \"%s\" or \"%s\"\n", __func__, filename + 6, filename ); + } - return NULL; -} - -/* -================ -FS_StreamInfo - -get basic stream info -================ -*/ -wavdata_t *FS_StreamInfo( stream_t *stream ) -{ - static wavdata_t info; - - if( !stream ) return NULL; - - // fill structure - info.loopStart = -1; - info.rate = stream->rate; - info.width = stream->width; - info.channels = stream->channels; - info.flags = SOUND_STREAM; - info.size = stream->size; - info.buffer = NULL; - info.samples = 0; // not actual for streams - info.type = stream->type; - - return &info; + return stream; } /* @@ -280,8 +278,8 @@ void FS_FreeStream( stream_t *stream ) } #if XASH_ENGINE_TESTS - #define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ); \ int EXPORT export( const uint8_t *Data, size_t Size ) \ { \ wavdata_t *wav; \ diff --git a/engine/client/soundlib/snd_mp3.c b/engine/client/soundlib/snd_mp3.c new file mode 100644 index 00000000..fe492a69 --- /dev/null +++ b/engine/client/soundlib/snd_mp3.c @@ -0,0 +1,456 @@ +/* +snd_mp3.c - mp3 format loading and streaming +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "soundlib.h" +#include "libmpg/libmpg.h" + +#pragma pack( push, 1 ) +typedef struct did3v2_header_s +{ + char ident[3]; // must be "ID3" + uint8_t major_ver; // must be 4 + uint8_t minor_ver; // must be 0 + uint8_t flags; + uint32_t length; // size of extended header, padding and frames +} did3v2_header_t; +STATIC_CHECK_SIZEOF( did3v2_header_t, 10, 10 ); + +typedef struct did3v2_extended_header_s +{ + uint32_t length; + uint8_t flags_length; + uint8_t flags[1]; +} did3v2_extended_header_t; +STATIC_CHECK_SIZEOF( did3v2_extended_header_t, 6, 6 ); + +typedef struct did3v2_frame_s +{ + char frame_id[4]; + uint32_t length; + uint8_t flags[2]; +} did3v2_frame_t; +STATIC_CHECK_SIZEOF( did3v2_frame_t, 10, 10 ); +#pragma pack( pop ) + +typedef enum did3v2_header_flags_e +{ + ID3V2_HEADER_UNSYHCHRONIZATION = BIT( 7U ), + ID3V2_HEADER_EXTENDED_HEADER = BIT( 6U ), + ID3V2_HEADER_EXPERIMENTAL = BIT( 5U ), + ID3V2_HEADER_FOOTER_PRESENT = BIT( 4U ), +} did3v2_header_flags_t; + +#define CHECK_IDENT( ident, b0, b1, b2 ) ((( ident )[0]) == ( b0 ) && (( ident )[1]) == ( b1 ) && (( ident )[2]) == ( b2 )) +#define CHECK_FRAME_ID( ident, b0, b1, b2, b3 ) ( CHECK_IDENT( ident, b0, b1, b2 ) && (( ident )[3]) == ( b3 )) + +static uint32_t Sound_ParseSynchInteger( uint32_t v ) +{ + uint32_t res = 0; + + // read as big endian + res |= (( v >> 24 ) & 0x7f ) << 0; + res |= (( v >> 16 ) & 0x7f ) << 7; + res |= (( v >> 8 ) & 0x7f ) << 14; + res |= (( v >> 0 ) & 0x7f ) << 21; + + return res; +} + +static void Sound_HandleCustomID3Comment( const char *key, const char *value ) +{ + if( !Q_strcmp( key, "LOOP_START" ) || !Q_strcmp( key, "LOOPSTART" )) + { + sound.loopstart = Q_atoi( value ); + SetBits( sound.flags, SOUND_LOOPED ); + } + // unknown comment is not an error +} + +static qboolean Sound_ParseID3Frame( const did3v2_frame_t *frame, const byte *buffer, size_t frame_length ) +{ + if( CHECK_FRAME_ID( frame->frame_id, 'T', 'X', 'X', 'X' )) + { + string key, value; + int32_t key_len, value_len; + + if( buffer[0] == 0x00 || buffer[0] == 0x03 ) + { + key_len = Q_strncpy( key, &buffer[1], sizeof( key )); + value_len = frame_length - (1 + key_len + 1); + if( value_len <= 0 || value_len >= sizeof( value )) + { + Con_Printf( S_ERROR "%s: invalid TXXX description, possibly broken file.\n", __func__ ); + return false; + } + + memcpy( value, &buffer[1 + key_len + 1], value_len ); + value[value_len + 1] = 0; + + Sound_HandleCustomID3Comment( key, value ); + } + else + { + if( buffer[0] == 0x01 || buffer[0] == 0x02 ) // UTF-16 with BOM + Con_Printf( S_ERROR "%s: UTF-16 encoding is unsupported. Use UTF-8 or ISO-8859!\n", __func__ ); + else + Con_Printf( S_ERROR "%s: unknown TXXX tag encoding %d, possibly broken file.\n", __func__, buffer[0] ); + return false; + } + } + + return true; +} + +static qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize ) +{ + const did3v2_header_t *header = (const did3v2_header_t *)buffer; + const byte *buffer_begin = buffer; + uint32_t tag_length; + + if( filesize < sizeof( *header )) + return false; + + buffer += sizeof( *header ); + + // support only id3v2 + if( !CHECK_IDENT( header->ident, 'I', 'D', '3' )) + { + // old id3v1 header found + if( CHECK_IDENT( header->ident, 'T', 'A', 'G' )) + Con_Printf( S_ERROR "%s: ID3v1 is not supported! Convert to ID3v2.4!\n", __func__ ); + + return true; // missing tag header is not an error + } + + // support only latest id3 v2.4 + if( header->major_ver != 4 || header->minor_ver == 0xff ) + { + Con_Printf( S_ERROR "%s: invalid ID3v2 tag version 2.%d.%d. Convert to ID3v2.4!\n", __func__, header->major_ver, header->minor_ver ); + return false; + } + + tag_length = Sound_ParseSynchInteger( header->length ); + if( tag_length > filesize - sizeof( *header )) + { + Con_Printf( S_ERROR "%s: invalid tag length %u, possibly broken file.\n", __func__, tag_length ); + return false; + } + + // just skip extended header + if( FBitSet( header->flags, ID3V2_HEADER_EXTENDED_HEADER )) + { + const did3v2_extended_header_t *ext_header = (const did3v2_extended_header_t *)buffer; + uint32_t ext_length = Sound_ParseSynchInteger( ext_header->length ); + + if( ext_length > tag_length ) + { + Con_Printf( S_ERROR "%s: invalid extended header length %u, possibly broken file.\n", __func__, ext_length ); + return false; + } + + buffer += ext_length; + } + + while( buffer - buffer_begin < tag_length ) + { + const did3v2_frame_t *frame = (const did3v2_frame_t *)buffer; + uint32_t frame_length = Sound_ParseSynchInteger( frame->length ); + + if( frame_length > tag_length ) + { + Con_Printf( S_ERROR "%s: invalid frame length %u, possibly broken file.\n", __func__, frame_length ); + return false; + } + + buffer += sizeof( *frame ); + + // parse can fail, but it's ok to continue + Sound_ParseID3Frame( frame, buffer, frame_length ); + + buffer += frame_length; + } + + return true; +} + +#if XASH_ENGINE_TESTS +int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size ); +int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size ) +{ + memset( &sound, 0, sizeof( sound )); + Sound_ParseID3Tag( Data, Size ); + return 0; +} +#endif + +/* +================================================================= + + MPEG decompression + +================================================================= +*/ +qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + void *mpeg; + size_t pos = 0; + size_t bytesWrite = 0; + byte out[OUTBUF_SIZE]; + size_t outsize, padsize; + int ret; + wavinfo_t sc; + + // load the file + if( !buffer || filesize < FRAME_SIZE ) + return false; + + // couldn't create decoder + if(( mpeg = create_decoder( &ret )) == NULL ) + return false; + + if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg )); + + // trying to read header + if( !feed_mpeg_header( mpeg, buffer, FRAME_SIZE, filesize, &sc )) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, name, get_error( mpeg )); + close_decoder( mpeg ); + return false; + } + + sound.channels = sc.channels; + sound.rate = sc.rate; + sound.width = 2; // always 16-bit PCM + sound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes + padsize = sound.size % FRAME_SIZE; + pos += FRAME_SIZE; // evaluate pos + + if( !Sound_ParseID3Tag( buffer, filesize )) + { + Con_DPrintf( S_WARN "%s: (%s) failed to extract LOOP_START tag\n", __func__, name ); + } + + if( !sound.size ) + { + // bad mpeg file ? + Con_DPrintf( S_ERROR "%s: (%s) is probably corrupted\n", __func__, name ); + close_decoder( mpeg ); + return false; + } + + // add sentinel make sure we not overrun + sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size + padsize ); + sound.type = WF_PCMDATA; + + // decompress mpg into pcm wav format + while( bytesWrite < sound.size ) + { + int size; + + if( feed_mpeg_stream( mpeg, NULL, 0, out, &outsize ) != MP3_OK && outsize <= 0 ) + { + const byte *data = buffer + pos; + int bufsize; + + // if there are no bytes remainig so we can decompress the new frame + if( pos + FRAME_SIZE > filesize ) + bufsize = ( filesize - pos ); + else bufsize = FRAME_SIZE; + pos += bufsize; + + if( feed_mpeg_stream( mpeg, data, bufsize, out, &outsize ) != MP3_OK ) + break; // there was end of the stream + } + + if( bytesWrite + outsize > sound.size ) + size = ( sound.size - bytesWrite ); + else size = outsize; + + memcpy( &sound.wav[bytesWrite], out, size ); + bytesWrite += size; + } + + sound.samples = bytesWrite / ( sound.width * sound.channels ); + close_decoder( mpeg ); + + return true; +} + +static fs_offset_t FS_SeekMpg( void *file, fs_offset_t offset, int whence ) +{ + return g_fsapi.Seek((file_t *)file, offset, whence ) == -1 ? -1 : g_fsapi.Tell((file_t *)file ); +} + +static mpg_ssize_t FS_ReadMpg( void *file, void *buf, size_t count ) +{ + return g_fsapi.Read((file_t *)file, buf, count ); +} + +/* +================= +Stream_OpenMPG +================= +*/ +stream_t *Stream_OpenMPG( const char *filename ) +{ + stream_t *stream; + void *mpeg; + file_t *file; + int ret; + wavinfo_t sc; + + file = FS_Open( filename, "rb", false ); + if( !file ) return NULL; + + // at this point we have valid stream + stream = Mem_Calloc( host.soundpool, sizeof( stream_t )); + stream->file = file; + stream->pos = 0; + + // couldn't create decoder + if(( mpeg = create_decoder( &ret )) == NULL ) + { + Con_DPrintf( S_ERROR "%s: couldn't create decoder: %s\n", __func__, get_error( mpeg ) ); + Mem_Free( stream ); + FS_Close( file ); + return NULL; + } + + if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg )); + + // trying to open stream and read header + if( !open_mpeg_stream( mpeg, file, FS_ReadMpg, FS_SeekMpg, &sc )) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, filename, get_error( mpeg )); + close_decoder( mpeg ); + Mem_Free( stream ); + FS_Close( file ); + + return NULL; + } + + stream->buffsize = 0; // how many samples left from previous frame + stream->channels = sc.channels; + stream->rate = sc.rate; + stream->width = 2; // always 16 bit + stream->ptr = mpeg; + stream->type = WF_MPGDATA; + + return stream; +} + +/* +================= +Stream_ReadMPG + +assume stream is valid +================= +*/ +int Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer ) +{ + // buffer handling + int bytesWritten = 0; + void *mpg; + + mpg = stream->ptr; + + while( 1 ) + { + byte *data; + int outsize; + + if( !stream->buffsize ) + { + if( read_mpeg_stream( mpg, (byte*)stream->temp, &stream->pos ) != MP3_OK ) + break; // there was end of the stream + } + + // check remaining size + if( bytesWritten + stream->pos > needBytes ) + outsize = ( needBytes - bytesWritten ); + else outsize = stream->pos; + + // copy raw sample to output buffer + data = (byte *)buffer + bytesWritten; + memcpy( data, &stream->temp[stream->buffsize], outsize ); + bytesWritten += outsize; + stream->pos -= outsize; + stream->buffsize += outsize; + + // continue from this sample on a next call + if( bytesWritten >= needBytes ) + return bytesWritten; + + stream->buffsize = 0; // no bytes remaining + } + + return 0; +} + +/* +================= +Stream_SetPosMPG + +assume stream is valid +================= +*/ +int Stream_SetPosMPG( stream_t *stream, int newpos ) +{ + if( set_stream_pos( stream->ptr, newpos ) != -1 ) + { + // flush any previous data + stream->buffsize = 0; + return true; + } + + // failed to seek for some reasons + return false; +} + +/* +================= +Stream_GetPosMPG + +assume stream is valid +================= +*/ +int Stream_GetPosMPG( stream_t *stream ) +{ + return get_stream_pos( stream->ptr ); +} + +/* +================= +Stream_FreeMPG + +assume stream is valid +================= +*/ +void Stream_FreeMPG( stream_t *stream ) +{ + if( stream->ptr ) + { + close_decoder( stream->ptr ); + stream->ptr = NULL; + } + + if( stream->file ) + { + FS_Close( stream->file ); + stream->file = NULL; + } + + Mem_Free( stream ); +} diff --git a/engine/client/soundlib/snd_ogg_opus.c b/engine/client/soundlib/snd_ogg_opus.c new file mode 100644 index 00000000..7289a941 --- /dev/null +++ b/engine/client/soundlib/snd_ogg_opus.c @@ -0,0 +1,335 @@ +/* +snd_ogg_opus.c - loading and streaming of Ogg audio format with Opus codec +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include "soundlib.h" +#include "crtlib.h" +#include "ogg_filestream.h" + +typedef struct opus_streaming_ctx_s +{ + file_t *file; + OggOpusFile *of; +} opus_streaming_ctx_t; + +static int OpusCallback_Read( void *datasource, byte *ptr, int nbytes ) +{ + return OggFilestream_Read( ptr, 1, nbytes, datasource ); +} + +static opus_int64 OpusCallback_Tell( void *datasource ) +{ + return OggFilestream_Tell( datasource ); +} + +static int FS_ReadOggOpus( void *datasource, byte *ptr, int nbytes ) +{ + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource; + return g_fsapi.Read( ctx->file, ptr, nbytes ); +} + +static int FS_SeekOggOpus( void *datasource, int64_t offset, int whence ) +{ + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource; + return g_fsapi.Seek( ctx->file, offset, whence ); +} + +static opus_int64 FS_TellOggOpus( void *datasource ) +{ + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)datasource; + return g_fsapi.Tell( ctx->file ); +} + +static const OpusFileCallbacks op_callbacks_membuf = { + OpusCallback_Read, + OggFilestream_Seek, + OpusCallback_Tell, + NULL +}; + +static const OpusFileCallbacks op_callbacks_fs = { + FS_ReadOggOpus, + FS_SeekOggOpus, + FS_TellOggOpus, + NULL +}; + +/* +================================================================= + + Ogg Opus decompression & streaming + +================================================================= +*/ +static void Sound_ScanOpusComments( const OggOpusFile *of ) +{ + const char *value; + const OpusTags *tags = op_tags( of, -1 ); + if( tags ) + { + if(( value = opus_tags_query( tags, "LOOPSTART", 0 ))) + { + sound.loopstart = Q_atoi( value ); + SetBits( sound.flags, SOUND_LOOPED ); + } + else if(( value = opus_tags_query( tags, "LOOP_START", 0 ))) + { + sound.loopstart = Q_atoi( value ); + SetBits( sound.flags, SOUND_LOOPED ); + } + } +} + +static const char *Opus_GetErrorDesc( int errorCode ) +{ + switch( errorCode ) + { + case OP_FALSE: + return "request failed"; + case OP_EOF: + return "end of file reached"; + case OP_HOLE: + return "there was a hole in the stream"; + case OP_EREAD: + return "read error occurred"; + case OP_EFAULT: + return "fault issue occurred"; + case OP_EIMPL: + return "feature not implemented"; + case OP_EINVAL: + return "invalid argument"; + case OP_ENOTFORMAT: + return "not a valid file format"; + case OP_EBADHEADER: + return "bad header"; + case OP_EVERSION: + return "version mismatch"; + case OP_EBADPACKET: + return "bad packet"; + case OP_EBADLINK: + return "bad link found"; + case OP_ENOSEEK: + return "bitstream not seekable"; + case OP_EBADTIMESTAMP: + return "invalid timestamp"; + default: + return "unknown error"; + } +} + +qboolean Sound_LoadOggOpus( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + int ret; + ogg_filestream_t file; + OggOpusFile *of; + const OpusHead *opusHead; + size_t written = 0; + + if( !buffer ) + return false; + + OggFilestream_Init( &file, name, buffer, filesize ); + of = op_open_callbacks( &file, &op_callbacks_membuf, NULL, 0, &ret ); + if( !of ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, file.name, Opus_GetErrorDesc( ret )); + return false; + } + + opusHead = op_head( of, -1 ); + if( opusHead->channel_count < 1 || opusHead->channel_count > 2 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): unsuppored channels count\n", __func__, file.name ); + return false; + } + + // according to OggOpus specification, sound always encoded at 48kHz sample rate + // but this isn't a problem, engine can do resampling + sound.channels = opusHead->channel_count; + sound.rate = 48000; + sound.width = 2; // always 16-bit PCM + sound.type = WF_PCMDATA; + sound.samples = op_pcm_total( of, -1 ); + sound.size = sound.samples * sound.width * sound.channels; + sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size ); + + // skip undesired samples before playing sound + if(( ret = op_pcm_seek( of, opusHead->pre_skip )) < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to pre-skip (%s): %s\n", __func__, file.name, Opus_GetErrorDesc( ret )); + return false; + } + + SetBits( sound.flags, SOUND_RESAMPLE ); + Sound_ScanOpusComments( of ); + + while(( ret = op_read( of, (opus_int16 *)( sound.wav + written ), ( sound.size - written ) / sound.width, NULL )) != 0 ) + { + if( ret < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to read (%s): %s\n", __func__, file.name, Opus_GetErrorDesc( ret )); + return false; + } + written += ret * sound.width * sound.channels; + } + + op_free( of ); + return true; +} + +stream_t *Stream_OpenOggOpus( const char *filename ) +{ + int ret; + stream_t *stream; + opus_streaming_ctx_t *ctx; + const OpusHead *opusHead; + + ctx = (opus_streaming_ctx_t *)Mem_Calloc( host.soundpool, sizeof( opus_streaming_ctx_t )); + ctx->file = FS_Open( filename, "rb", false ); + if( !ctx->file ) + { + Mem_Free( ctx ); + return NULL; + } + + stream = (stream_t *)Mem_Calloc( host.soundpool, sizeof( stream_t )); + stream->file = ctx->file; + stream->pos = 0; + + ctx->of = op_open_callbacks( ctx, &op_callbacks_fs, NULL, 0, &ret ); + if( !ctx->of ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, filename, Opus_GetErrorDesc( ret )); + FS_Close( ctx->file ); + Mem_Free( stream ); + Mem_Free( ctx ); + return NULL; + } + + opusHead = op_head( ctx->of, -1 ); + if( opusHead->channel_count < 1 || opusHead->channel_count > 2 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): unsuppored channels count\n", __func__, filename ); + op_free( ctx->of ); + FS_Close( ctx->file ); + Mem_Free( stream ); + Mem_Free( ctx ); + return NULL; + } + + // skip undesired samples before playing sound + if(( ret = op_pcm_seek( ctx->of, opusHead->pre_skip )) < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to pre-skip (%s): %s\n", __func__, filename, Opus_GetErrorDesc( ret )); + op_free( ctx->of ); + FS_Close( ctx->file ); + Mem_Free( stream ); + Mem_Free( ctx ); + return NULL; + } + + stream->buffsize = 0; // how many samples left from previous frame + stream->channels = opusHead->channel_count; + stream->rate = 48000; // that's fixed at 48kHz for Opus format + stream->width = 2; // always 16 bit + stream->ptr = ctx; + stream->type = WF_OPUSDATA; + + return stream; +} + +int Stream_ReadOggOpus( stream_t *stream, int needBytes, void *buffer ) +{ + int bytesWritten = 0; + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr; + + while( 1 ) + { + int ret; + byte *data; + int outsize; + + if( !stream->buffsize ) + { + ret = op_read( ctx->of, (opus_int16 *)stream->temp, OUTBUF_SIZE / stream->width, NULL ); + if( ret == 0 ) + break; // end of file + else if( ret < 0 ) + Con_DPrintf( S_ERROR "%s: error during read: %s\n", __func__, Opus_GetErrorDesc( ret )); + else + stream->pos = ret * stream->width * stream->channels; + } + + // check remaining size + if( bytesWritten + stream->pos > needBytes ) + outsize = ( needBytes - bytesWritten ); + else + outsize = stream->pos; + + // copy raw sample to output buffer + data = (byte *)buffer + bytesWritten; + memcpy( data, &stream->temp[stream->buffsize], outsize ); + bytesWritten += outsize; + stream->pos -= outsize; + stream->buffsize += outsize; + + // continue from this sample on a next call + if( bytesWritten >= needBytes ) + return bytesWritten; + + stream->buffsize = 0; // no bytes remaining + } + + return 0; +} + +int Stream_SetPosOggOpus( stream_t *stream, int newpos ) +{ + int ret; + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr; + if(( ret = op_raw_seek( ctx->of, newpos )) == 0 ) + { + stream->buffsize = 0; // flush any previous data + return true; + } + Con_DPrintf( S_ERROR "%s: error during seek: %s\n", __func__, Opus_GetErrorDesc( ret )); + return false; // failed to seek +} + +int Stream_GetPosOggOpus( stream_t *stream ) +{ + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr; + return op_raw_tell( ctx->of ); +} + +void Stream_FreeOggOpus( stream_t *stream ) +{ + if( stream->ptr ) + { + opus_streaming_ctx_t *ctx = (opus_streaming_ctx_t *)stream->ptr; + op_free( ctx->of ); + Mem_Free( stream->ptr ); + stream->ptr = NULL; + } + + if( stream->file ) + { + FS_Close( stream->file ); + stream->file = NULL; + } + + Mem_Free( stream ); +} diff --git a/engine/client/soundlib/snd_ogg_vorbis.c b/engine/client/soundlib/snd_ogg_vorbis.c new file mode 100644 index 00000000..2c337d4c --- /dev/null +++ b/engine/client/soundlib/snd_ogg_vorbis.c @@ -0,0 +1,305 @@ +/* +snd_ogg_vorbis.c - loading and streaming of Ogg audio format with Vorbis codec +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include "soundlib.h" +#include "crtlib.h" +#include "xash3d_mathlib.h" +#include "ogg_filestream.h" + +typedef struct vorbis_streaming_ctx_s +{ + file_t *file; + OggVorbis_File vf; +} vorbis_streaming_ctx_t; + +static size_t FS_ReadOggVorbis( void *ptr, size_t blockSize, size_t nmemb, void *datasource ) +{ + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource; + return g_fsapi.Read( ctx->file, ptr, blockSize * nmemb ); +} + +static int FS_SeekOggVorbis( void *datasource, int64_t offset, int whence ) +{ + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource; + return g_fsapi.Seek( ctx->file, offset, whence ); +} + +static long FS_TellOggVorbis( void *datasource ) +{ + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)datasource; + return g_fsapi.Tell( ctx->file ); +} + +static const ov_callbacks ov_callbacks_membuf = { + OggFilestream_Read, + OggFilestream_Seek, + NULL, + OggFilestream_Tell +}; + +static const ov_callbacks ov_callbacks_fs = { + FS_ReadOggVorbis, + FS_SeekOggVorbis, + NULL, + FS_TellOggVorbis +}; + +/* +================================================================= + + Ogg Vorbis decompression & streaming + +================================================================= +*/ +static const char *Vorbis_GetErrorDesc( int errorCode ) +{ + switch( errorCode ) + { + case OV_EOF: + return "end of file"; + case OV_HOLE: + return "compressed data sync lost"; + case OV_EBADHEADER: + return "invalid header"; + case OV_EINVAL: + return "invalid argument"; + case OV_ENOTVORBIS: + return "not a Vorbis data"; + case OV_EBADLINK: + return "link corrupted"; + case OV_EFAULT: + return "internal error"; + case OV_EIMPL: + return "not implemented"; + case OV_EBADPACKET: + return "invalid packet"; + case OV_EVERSION: + return "version mismatch"; + case OV_ENOSEEK: + return "bitstream not seekable"; + case OV_ENOTAUDIO: + return "not an audio data"; + case OV_EREAD: + return "read error"; + default: + return "unknown error"; + } +} + +static void Sound_ScanVorbisComments( OggVorbis_File *vf ) +{ + const char *value; + vorbis_comment *vc = ov_comment( vf, -1 ); + if( vc ) + { + if(( value = vorbis_comment_query( vc, "LOOPSTART", 0 ))) + { + sound.loopstart = Q_atoi( value ); + SetBits( sound.flags, SOUND_LOOPED ); + } + else if(( value = vorbis_comment_query( vc, "LOOP_START", 0 ))) + { + sound.loopstart = Q_atoi( value ); + SetBits( sound.flags, SOUND_LOOPED ); + } + } +} + +qboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + long ret; + int section; + size_t written = 0; + vorbis_info *info; + ogg_filestream_t file; + OggVorbis_File vorbisFile; + + if( !buffer ) + return false; + + OggFilestream_Init( &file, name, buffer, filesize ); + if(( ret = ov_open_callbacks( &file, &vorbisFile, NULL, 0, ov_callbacks_membuf )) < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, file.name, Vorbis_GetErrorDesc( ret )); + return false; + } + + info = ov_info( &vorbisFile, -1 ); + if( info->channels < 1 || info->channels > 2 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): unsuppored channels count\n", __func__, file.name ); + return false; + } + + sound.channels = info->channels; + sound.rate = info->rate; + sound.width = 2; // always 16-bit PCM + sound.type = WF_PCMDATA; + sound.samples = ov_pcm_total( &vorbisFile, -1 ); + sound.size = sound.samples * sound.width * sound.channels; + sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size ); + + SetBits( sound.flags, SOUND_RESAMPLE ); + Sound_ScanVorbisComments( &vorbisFile ); + + while(( ret = ov_read( &vorbisFile, (char *)sound.wav + written, sound.size - written, 0, sound.width, 1, §ion )) != 0 ) + { + if( ret < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, file.name, Vorbis_GetErrorDesc( ret )); + return false; + } + written += ret; + } + + ov_clear( &vorbisFile ); + return true; +} + +stream_t *Stream_OpenOggVorbis( const char *filename ) +{ + int ret; + stream_t *stream; + vorbis_info *info; + vorbis_streaming_ctx_t *ctx; + + ctx = (vorbis_streaming_ctx_t *)Mem_Calloc( host.soundpool, sizeof( vorbis_streaming_ctx_t )); + ctx->file = FS_Open( filename, "rb", false ); + if( !ctx->file ) + { + Mem_Free( ctx ); + return NULL; + } + + stream = (stream_t *)Mem_Calloc( host.soundpool, sizeof( stream_t )); + stream->file = ctx->file; + stream->pos = 0; + + if(( ret = ov_open_callbacks( ctx, &ctx->vf, NULL, 0, ov_callbacks_fs )) < 0 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): %s\n", __func__, filename, Vorbis_GetErrorDesc( ret )); + FS_Close( ctx->file ); + Mem_Free( stream ); + Mem_Free( ctx ); + return NULL; + } + + info = ov_info( &ctx->vf, -1 ); + if( info->channels < 1 || info->channels > 2 ) + { + Con_DPrintf( S_ERROR "%s: failed to load (%s): unsuppored channels count\n", __func__, filename ); + FS_Close( ctx->file ); + Mem_Free( stream ); + Mem_Free( ctx ); + return NULL; + } + + stream->buffsize = 0; // how many samples left from previous frame + stream->channels = info->channels; + stream->rate = info->rate; + stream->width = 2; // always 16 bit + stream->ptr = ctx; + stream->type = WF_VORBISDATA; + + return stream; +} + +int Stream_ReadOggVorbis( stream_t *stream, int needBytes, void *buffer ) +{ + int section; + int bytesWritten = 0; + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr; + + while( 1 ) + { + byte *data; + int outsize; + + if( !stream->buffsize ) + { + stream->pos = ov_read( &ctx->vf, (char *)stream->temp, OUTBUF_SIZE, 0, stream->width, 1, §ion ); + if( stream->pos == 0 ) + { + break; // end of file + } + else if( stream->pos < 0 ) + { + Con_DPrintf( S_ERROR "%s: error during read: %s\n", __func__, Vorbis_GetErrorDesc( stream->pos )); + } + } + + // check remaining size + if( bytesWritten + stream->pos > needBytes ) + outsize = ( needBytes - bytesWritten ); + else + outsize = stream->pos; + + // copy raw sample to output buffer + data = (byte *)buffer + bytesWritten; + memcpy( data, &stream->temp[stream->buffsize], outsize ); + bytesWritten += outsize; + stream->pos -= outsize; + stream->buffsize += outsize; + + // continue from this sample on a next call + if( bytesWritten >= needBytes ) + return bytesWritten; + + stream->buffsize = 0; // no bytes remaining + } + + return 0; +} + +int Stream_SetPosOggVorbis( stream_t *stream, int newpos ) +{ + int ret; + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr; + if(( ret = ov_raw_seek_lap( &ctx->vf, newpos )) == 0 ) + { + stream->buffsize = 0; // flush any previous data + return true; + } + Con_DPrintf( S_ERROR "%s: error during seek: %s\n", __func__, Vorbis_GetErrorDesc( ret )); + return false; // failed to seek +} + +int Stream_GetPosOggVorbis( stream_t *stream ) +{ + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr; + return ov_raw_tell( &ctx->vf ); +} + +void Stream_FreeOggVorbis( stream_t *stream ) +{ + if( stream->ptr ) + { + vorbis_streaming_ctx_t *ctx = (vorbis_streaming_ctx_t *)stream->ptr; + ov_clear( &ctx->vf ); + Mem_Free( stream->ptr ); + stream->ptr = NULL; + } + + if( stream->file ) + { + FS_Close( stream->file ); + stream->file = NULL; + } + + Mem_Free( stream ); +} diff --git a/engine/common/soundlib/snd_wav.c b/engine/client/soundlib/snd_wav.c similarity index 68% rename from engine/common/soundlib/snd_wav.c rename to engine/client/soundlib/snd_wav.c index 97c268e3..e0e6e0a7 100644 --- a/engine/common/soundlib/snd_wav.c +++ b/engine/client/soundlib/snd_wav.c @@ -1,6 +1,7 @@ /* snd_wav.c - wav format load & save Copyright (C) 2010 Uncle Mike +Copyright (C) 2023 FTEQW developers This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,6 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include #include "soundlib.h" static const byte *iff_data; @@ -21,6 +23,11 @@ static const byte *iff_end; static const byte *iff_lastChunk; static int iff_chunkLen; +static int IsFourCC( const void *ptr, const void *fourcc ) +{ + return 0 == memcmp( ptr, fourcc, 4 ); +} + /* ================= GetLittleShort @@ -60,32 +67,55 @@ static int GetLittleLong( void ) FindNextChunk ================= */ -static void FindNextChunk( const char *name ) +static void FindNextChunk( const char *filename, const char *name ) { while( 1 ) { - iff_dataPtr = iff_lastChunk; + ptrdiff_t remaining = iff_end - iff_lastChunk; - if( iff_dataPtr >= iff_end ) + if( remaining < 8 ) { - // didn't find the chunk iff_dataPtr = NULL; return; } - iff_dataPtr += 4; - iff_chunkLen = GetLittleLong(); + iff_dataPtr = iff_lastChunk + 4; + remaining -= 8; + iff_chunkLen = GetLittleLong(); if( iff_chunkLen < 0 ) { iff_dataPtr = NULL; return; } - iff_dataPtr -= 8; - iff_lastChunk = iff_dataPtr + 8 + ((iff_chunkLen + 1) & ~1); + if( iff_chunkLen > remaining ) + { + // only print this warning if selected chunk is truncated + // + // otherwise this warning becomes misleading because some + // idiot programs like CoolEdit (i.e. Adobe Audition) don't always + // respect pad byte. The file isn't actually truncated, it just + // can't be reliably parsed as a whole - if( !Q_strncmp( (const char *)iff_dataPtr, name, 4 )) + if( IsFourCC( iff_lastChunk, "RIFF" ) + || IsFourCC( iff_lastChunk, "fmt " ) + || IsFourCC( iff_lastChunk, "cue " ) + || IsFourCC( iff_lastChunk, "LIST" ) + || IsFourCC( iff_lastChunk, "data" )) + { + Con_DPrintf( "%s: '%s' truncated by %zi bytes\n", __func__, filename, iff_chunkLen - remaining ); + } + iff_chunkLen = remaining; + } + + remaining -= iff_chunkLen; + iff_dataPtr -= 8; + + iff_lastChunk = iff_dataPtr + 8 + iff_chunkLen; + if(( iff_chunkLen & 1 ) && remaining ) + iff_lastChunk++; + if( IsFourCC( iff_dataPtr, name )) return; } } @@ -95,10 +125,10 @@ static void FindNextChunk( const char *name ) FindChunk ================= */ -static void FindChunk( const char *name ) +static void FindChunk( const char *filename, const char *name ) { iff_lastChunk = iff_data; - FindNextChunk( name ); + FindNextChunk( filename, name ); } /* @@ -106,7 +136,7 @@ static void FindChunk( const char *name ) StreamFindNextChunk ============ */ -qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_chunk ) +static qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_chunk ) { char chunkName[4]; int iff_chunk_len; @@ -119,15 +149,18 @@ qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_chunk ) return false; // didn't find the chunk FS_Seek( file, 4, SEEK_CUR ); - FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )); + if( FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )) != sizeof( iff_chunk_len )) + return false; + if( iff_chunk_len < 0 ) return false; // didn't find the chunk FS_Seek( file, -8, SEEK_CUR ); *last_chunk = FS_Tell( file ) + 8 + (( iff_chunk_len + 1 ) & ~1 ); - FS_Read( file, chunkName, 4 ); + if( FS_Read( file, chunkName, sizeof( chunkName )) != sizeof( chunkName )) + return false; - if( !Q_strncmp( chunkName, name, 4 )) + if( IsFourCC( chunkName, name )) return true; } @@ -151,21 +184,21 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi iff_end = buffer + filesize; // find "RIFF" chunk - FindChunk( "RIFF" ); + FindChunk( name, "RIFF" ); - if( !( iff_dataPtr && !Q_strncmp( (const char *)iff_dataPtr + 8, "WAVE", 4 ))) + if( !iff_dataPtr || !IsFourCC( iff_dataPtr + 8, "WAVE" )) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'RIFF/WAVE' chunks\n", name ); + Con_DPrintf( S_ERROR "%s: %s missing 'RIFF/WAVE' chunks\n", __func__, name ); return false; } // get "fmt " chunk iff_data = iff_dataPtr + 12; - FindChunk( "fmt " ); + FindChunk( name, "fmt " ); if( !iff_dataPtr ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'fmt ' chunk\n", name ); + Con_DPrintf( S_ERROR "%s: %s missing 'fmt ' chunk\n", __func__, name ); return false; } @@ -176,7 +209,7 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi { if( fmt != 85 ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: %s not a microsoft PCM format\n", name ); + Con_DPrintf( S_ERROR "%s: %s not a microsoft PCM format\n", __func__, name ); return false; } else @@ -189,7 +222,7 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi sound.channels = GetLittleShort(); if( sound.channels != 1 && sound.channels != 2 ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: only mono and stereo WAV files supported (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: only mono and stereo WAV files supported (%s)\n", __func__, name ); return false; } @@ -201,22 +234,23 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi if( sound.width != 1 && sound.width != 2 ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: only 8 and 16 bit WAV files supported (%s)\n", __func__, name ); return false; } // get cue chunk - FindChunk( "cue " ); + FindChunk( name, "cue " ); - if( iff_dataPtr ) + if( iff_dataPtr && iff_end - iff_dataPtr >= 36 ) { iff_dataPtr += 32; sound.loopstart = GetLittleLong(); - FindNextChunk( "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker + SetBits( sound.flags, SOUND_LOOPED ); + FindNextChunk( name, "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker - if( iff_dataPtr ) + if( iff_dataPtr && iff_end - iff_dataPtr >= 32 ) { - if( !Q_strncmp( (const char *)iff_dataPtr + 28, "mark", 4 )) + if( IsFourCC( iff_dataPtr + 28, "mark" )) { // this is not a proper parse, but it works with CoolEdit... iff_dataPtr += 24; @@ -226,16 +260,16 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi } else { - sound.loopstart = -1; + sound.loopstart = 0; sound.samples = 0; } // find data chunk - FindChunk( "data" ); + FindChunk( name, "data" ); if( !iff_dataPtr ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'data' chunk\n", name ); + Con_DPrintf( S_ERROR "%s: %s missing 'data' chunk\n", __func__, name ); return false; } @@ -246,7 +280,7 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi { if( samples < sound.samples ) { - Con_DPrintf( S_ERROR "Sound_LoadWAV: %s has a bad loop length\n", name ); + Con_DPrintf( S_ERROR "%s: %s has a bad loop length\n", __func__, name ); return false; } } @@ -254,7 +288,7 @@ qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesi if( sound.samples <= 0 ) { - Con_Reportf( S_ERROR "Sound_LoadWAV: file with %i samples (%s)\n", sound.samples, name ); + Con_Reportf( S_ERROR "%s: file with %i samples (%s)\n", __func__, sound.samples, name ); return false; } @@ -326,25 +360,33 @@ stream_t *Stream_OpenWAV( const char *filename ) // find "RIFF" chunk if( !StreamFindNextChunk( file, "RIFF", &last_chunk )) { - Con_DPrintf( S_ERROR "Stream_OpenWAV: %s missing RIFF chunk\n", filename ); + Con_DPrintf( S_ERROR "%s: %s missing RIFF chunk\n", __func__, filename ); FS_Close( file ); return NULL; } - FS_Read( file, chunkName, 4 ); - if( !Q_strncmp( chunkName, "WAVE", 4 )) + FS_Seek( file, 4, SEEK_CUR ); + + if( FS_Read( file, chunkName, 4 ) != 4 ) { - Con_DPrintf( S_ERROR "Stream_OpenWAV: %s missing WAVE chunk\n", filename ); + Con_DPrintf( S_ERROR "%s: %s missing WAVE chunk, truncated\n", __func__, filename ); + FS_Close( file ); + return false; + } + + if( !IsFourCC( chunkName, "WAVE" )) + { + Con_DPrintf( S_ERROR "%s: %s missing WAVE chunk\n", __func__, filename ); FS_Close( file ); return NULL; } // get "fmt " chunk - iff_data = FS_Tell( file ) + 4; + iff_data = FS_Tell( file ); last_chunk = iff_data; if( !StreamFindNextChunk( file, "fmt ", &last_chunk )) { - Con_DPrintf( S_ERROR "Stream_OpenWAV: %s missing 'fmt ' chunk\n", filename ); + Con_DPrintf( S_ERROR "%s: %s missing 'fmt ' chunk\n", __func__, filename ); FS_Close( file ); return NULL; } @@ -354,7 +396,7 @@ stream_t *Stream_OpenWAV( const char *filename ) FS_Read( file, &t, sizeof( t )); if( t != 1 ) { - Con_DPrintf( S_ERROR "Stream_OpenWAV: %s not a microsoft PCM format\n", filename ); + Con_DPrintf( S_ERROR "%s: %s not a microsoft PCM format\n", __func__, filename ); FS_Close( file ); return NULL; } @@ -375,7 +417,7 @@ stream_t *Stream_OpenWAV( const char *filename ) last_chunk = iff_data; if( !StreamFindNextChunk( file, "data", &last_chunk )) { - Con_DPrintf( S_ERROR "Stream_OpenWAV: %s missing 'data' chunk\n", filename ); + Con_DPrintf( S_ERROR "%s: %s missing 'data' chunk\n", __func__, filename ); FS_Close( file ); return NULL; } diff --git a/engine/client/titles.c b/engine/client/titles.c index 4442ef76..fd6f5ff3 100644 --- a/engine/client/titles.c +++ b/engine/client/titles.c @@ -218,6 +218,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) client_textmessage_t textMessages[MAX_MESSAGES]; int i, nameHeapSize, textHeapSize, messageSize, nameOffset; int messageCount, lastNamePos; + size_t textHeapSizeRemaining; lastNamePos = 0; lineNumber = 0; @@ -249,10 +250,10 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) if( IsEndOfText( trim )) { - Con_Reportf( "TextMessage: unexpected '}' found, line %d\n", lineNumber ); + Con_Reportf( "%s: unexpected '}' found, line %d\n", __func__, lineNumber ); return; } - Q_strcpy( currentName, trim ); + Q_strncpy( currentName, trim, sizeof( currentName )); break; case MSGFILE_TEXT: if( IsEndOfText( trim )) @@ -262,11 +263,11 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) // save name on name heap if( lastNamePos + length > 32768 ) { - Con_Reportf( "TextMessage: error while parsing!\n" ); + Con_Reportf( "%s: error while parsing!\n", __func__ ); return; } - Q_strcpy( nameHeap + lastNamePos, currentName ); + Q_strncpy( nameHeap + lastNamePos, currentName, sizeof( nameHeap ) - lastNamePos ); // terminate text in-place in the memory file // (it's temporary memory that will be deleted) @@ -285,7 +286,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) } if( IsStartOfText( trim )) { - Con_Reportf( "TextMessage: unexpected '{' found, line %d\n", lineNumber ); + Con_Reportf( "%s: unexpected '{' found, line %d\n", __func__, lineNumber ); return; } break; @@ -301,7 +302,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) } } - Con_Reportf( "TextMessage: parsed %d text messages\n", messageCount ); + Con_Reportf( "%s: parsed %d text messages\n", __func__, messageCount ); nameHeapSize = lastNamePos; textHeapSize = 0; @@ -329,19 +330,24 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) // copy text & fixup pointers + textHeapSizeRemaining = textHeapSize; pCurrentText = pNameHeap + nameHeapSize; for( i = 0; i < messageCount; i++ ) { + size_t currentTextSize = Q_strlen( clgame.titles[i].pMessage ) + 1; + clgame.titles[i].pName = pNameHeap; // adjust name pointer (parallel buffer) - Q_strcpy( pCurrentText, clgame.titles[i].pMessage ); // copy text over + Q_strncpy( pCurrentText, clgame.titles[i].pMessage, textHeapSizeRemaining ); // copy text over clgame.titles[i].pMessage = pCurrentText; + pNameHeap += Q_strlen( pNameHeap ) + 1; - pCurrentText += Q_strlen( pCurrentText ) + 1; + pCurrentText += currentTextSize; + textHeapSizeRemaining -= currentTextSize; } if(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize )) - Con_DPrintf( S_ERROR "TextMessage: overflow text message buffer!\n" ); + Con_DPrintf( S_ERROR "%s: overflow text message buffer!\n", __func__ ); clgame.numTitles = messageCount; } diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index 56f456f1..b68ab399 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -23,240 +23,381 @@ GNU General Public License for more details. #include "input.h" #include "platform/platform.h" -static enum VGUI_KeyCode s_pVirtualKeyTrans[256]; -static VGUI_DefaultCursor s_currentCursor; -static HINSTANCE s_pVGuiSupport; // vgui_support library -static convar_t *vgui_utf8 = NULL; +#define VGUI_MAX_TEXTURES 1024 -void GAME_EXPORT *VGUI_EngineMalloc(size_t size) +typedef struct vgui_reusable_texture_s +{ + int gl_texturenum; + byte hash[16]; +} vgui_reusable_texture_t; + +typedef struct vgui_static_s +{ + qboolean initialized; + VGUI_DefaultCursor cursor; + vguiapi_t dllFuncs; + + vgui_reusable_texture_t *textures; + int texture_id; + int max_textures; + int bound_texture; + byte color[4]; + qboolean enable_texture; + + HINSTANCE hInstance; + + poolhandle_t mempool; + + enum VGUI_KeyCode virtualKeyTrans[256]; +} vgui_static_t; + +static vgui_static_t vgui = { + false, -1 +}; +static CVAR_DEFINE_AUTO( vgui_utf8, "0", FCVAR_ARCHIVE, "enable utf-8 support for vgui text" ); + +static void GAME_EXPORT VGUI_DrawInit( void ) +{ + if( vgui.mempool ) + Mem_EmptyPool( vgui.mempool ); + else vgui.mempool = Mem_AllocPool( "VGui Support Pool" ); + + vgui.textures = NULL; + + memset( vgui.color, 0, sizeof( vgui.color )); + vgui.texture_id = 0; + vgui.bound_texture = 0; + vgui.max_textures = 0; + vgui.enable_texture = true; +} + +static void GAME_EXPORT VGUI_DrawShutdown( void ) +{ + int i; + + for( i = 1; i < vgui.texture_id; i++ ) + ref.dllFuncs.GL_FreeTexture( vgui.textures[i].gl_texturenum ); + + Mem_FreePool( &vgui.mempool ); + vgui.textures = NULL; + + memset( vgui.color, 0, sizeof( vgui.color )); + vgui.texture_id = 0; + vgui.bound_texture = 0; + vgui.max_textures = 0; +} + +static int GAME_EXPORT VGUI_GenerateTexture( void ) +{ + // allocate new + if( vgui.texture_id + 1 >= vgui.max_textures ) + { + if( vgui.max_textures + VGUI_MAX_TEXTURES >= VGUI_MAX_TEXTURES * VGUI_MAX_TEXTURES ) + { + // in theory it might look up texture that hasn't been bound for a while and + // reuse that but it will eventually overwrite some important textures anyway + Con_Printf( S_ERROR "%s: Refusing resizing VGUI textures array due to memory leak\n", __func__ ); + return vgui.texture_id; + } + + vgui.max_textures += VGUI_MAX_TEXTURES; + + // this potentially might leak memory if VGUI is used incorrectly! + // (like in Cry of Fear) + vgui.textures = Mem_Realloc( vgui.mempool, vgui.textures, sizeof( *vgui.textures ) * vgui.max_textures ); + + // warn mod developer + if( vgui.max_textures >= VGUI_MAX_TEXTURES * 4 ) + Con_Printf( S_ERROR "%s: Potential memory leak in VGUI code is detected!\n", __func__ ); + } + + return ++vgui.texture_id; +} + +static void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height ) +{ + rgbdata_t r_image = { 0 }; + char texName[32]; + MD5Context_t ctx; + byte hash[16]; + + if( id <= 0 || id >= vgui.max_textures || width <= 0 || height <= 0 ) + { + Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id ); + return; + } + + // need to do this as some mods tend to upload same texture over and over + // exhausing engine-wide limit on textures and leaking vram + MD5Init( &ctx ); + MD5Update( &ctx, buffer, width * height * 4 ); + MD5Final( hash, &ctx ); + + // it's a new texture, try to find a copy + if( vgui.textures[id].gl_texturenum == 0 ) + { + int i; + + for( i = 1; i < vgui.texture_id; i++ ) + { + if( vgui.textures[i].gl_texturenum != 0 && !memcmp( vgui.textures[i].hash, hash, sizeof( hash ))) + { + // copy data to new texture id + vgui.textures[id] = vgui.textures[i]; + return; + } + } + } + + Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); + + r_image.width = width; + r_image.height = height; + r_image.type = PF_RGBA_32; + r_image.size = width * height * 4; + r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + r_image.buffer = (byte*)buffer; + + vgui.textures[id].gl_texturenum = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE ); + memcpy( vgui.textures[id].hash, hash, sizeof( hash )); +} + +static void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height ) +{ + // nothing uses it, it can be removed + Host_Error( "%s: deprecated\n", __func__ ); + +} + +static void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) +{ + // nothing uses it, it can be removed + Host_Error( "%s: deprecated\n", __func__ ); +} + +static void GAME_EXPORT VGUI_BindTexture( int id ) +{ + if( id <= 0 || id >= vgui.max_textures || !vgui.textures[id].gl_texturenum ) + id = 1; // NOTE: same as bogus index 2700 in GoldSrc + + ref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id].gl_texturenum ); + vgui.bound_texture = id; +} + +static void GAME_EXPORT VGUI_GetTextureSizes( int *w, int *h ) +{ + int texnum; + + if( vgui.bound_texture ) + texnum = vgui.textures[vgui.bound_texture].gl_texturenum; + else + texnum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); + + R_GetTextureParms( w, h, texnum ); +} + +static void GAME_EXPORT VGUI_SetupDrawingRect( int *pColor ) +{ + ref.dllFuncs.VGUI_SetupDrawing( true ); + Vector4Set( vgui.color, pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +} + +static void GAME_EXPORT VGUI_SetupDrawingText( int *pColor ) +{ + ref.dllFuncs.VGUI_SetupDrawing( false ); + Vector4Set( vgui.color, pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +} + +static void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ) +{ + float x, y, w, h; + + if( !ul || !lr ) + return; + + x = ul->point[0]; + y = ul->point[1]; + w = lr->point[0] - x; + h = lr->point[1] - y; + + SPR_AdjustSize( &x, &y, &w, &h ); + + if( vgui.enable_texture ) + { + float s1, s2, t1, t2; + + s1 = ul->coord[0]; + t1 = ul->coord[1]; + s2 = lr->coord[0]; + t2 = lr->coord[1]; + + ref.dllFuncs.Color4ub( vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] ); + ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture].gl_texturenum ); + } + else + { + ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, w, h, vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] ); + } +} + +static void GAME_EXPORT VGUI_EnableTexture( qboolean enable ) +{ + vgui.enable_texture = enable; +} + +static void GAME_EXPORT *VGUI_EngineMalloc( size_t size ) { return Z_Malloc( size ); } -qboolean GAME_EXPORT VGUI_IsInGame( void ) +static qboolean GAME_EXPORT VGUI_IsInGame( void ) { return cls.state == ca_active && cls.key_dest == key_game; } -void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) +static void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) { float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; int x, y; Platform_GetMousePos( &x, &y ); - *_x = x / xscale, *_y = y / yscale; + *_x = x / xscale; + *_y = y / yscale; } -void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) +static void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) { - Platform_SetCursorType( cursor ); - s_currentCursor = cursor; + if( vgui.cursor != cursor ) + Platform_SetCursorType( cursor ); } -byte GAME_EXPORT VGUI_GetColor( int i, int j) +static byte GAME_EXPORT VGUI_GetColor( int i, int j ) { return g_color_table[i][j]; } -// Define and initialize vgui API -int GAME_EXPORT VGUI_UtfProcessChar( int in ) +static int GAME_EXPORT VGUI_UtfProcessChar( int in ) { - if( CVAR_TO_BOOL( vgui_utf8 )) + if( vgui_utf8.value ) return Con_UtfProcessCharForce( in ); - else - return in; + return in; } -vguiapi_t vgui = -{ - false, // Not initialized yet - NULL, // VGUI_DrawInit, - NULL, // VGUI_DrawShutdown, - NULL, // VGUI_SetupDrawingText, - NULL, // VGUI_SetupDrawingRect, - NULL, // VGUI_SetupDrawingImage, - NULL, // VGUI_BindTexture, - NULL, // VGUI_EnableTexture, - NULL, // VGUI_CreateTexture, - NULL, // VGUI_UploadTexture, - NULL, // VGUI_UploadTextureBlock, - NULL, // VGUI_DrawQuad, - NULL, // VGUI_GetTextureSizes, - NULL, // VGUI_GenerateTexture, - VGUI_EngineMalloc, - VGUI_CursorSelect, - VGUI_GetColor, - VGUI_IsInGame, - NULL, - VGUI_GetMousePos, - VGUI_UtfProcessChar, - Platform_GetClipboardText, - Platform_SetClipboardText, - Platform_GetKeyModifiers, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL -}; - qboolean VGui_IsActive( void ) { return vgui.initialized; } -void VGui_FillAPIFromRef( vguiapi_t *to, const ref_interface_t *from ) +void VGui_RegisterCvars( void ) { - to->DrawInit = from->VGUI_DrawInit; - to->DrawShutdown = from->VGUI_DrawShutdown; - to->SetupDrawingText = from->VGUI_SetupDrawingText; - to->SetupDrawingRect = from->VGUI_SetupDrawingRect; - to->SetupDrawingImage = from->VGUI_SetupDrawingImage; - to->BindTexture = from->VGUI_BindTexture; - to->EnableTexture = from->VGUI_EnableTexture; - to->CreateTexture = from->VGUI_CreateTexture; - to->UploadTexture = from->VGUI_UploadTexture; - to->UploadTextureBlock = from->VGUI_UploadTextureBlock; - to->DrawQuad = from->VGUI_DrawQuad; - to->GetTextureSizes = from->VGUI_GetTextureSizes; - to->GenerateTexture = from->VGUI_GenerateTexture; + Cvar_RegisterVariable( &vgui_utf8 ); +} + +static const vguiapi_t gEngfuncs = +{ + false, // Not initialized yet + VGUI_DrawInit, // VGUI_DrawInit, + VGUI_DrawShutdown, // VGUI_DrawShutdown, + VGUI_SetupDrawingText, // VGUI_SetupDrawingText, + VGUI_SetupDrawingRect, // VGUI_SetupDrawingRect, + VGUI_SetupDrawingText, // VGUI_SetupDrawingImage, (same as text) + VGUI_BindTexture, // VGUI_BindTexture, + VGUI_EnableTexture, // VGUI_EnableTexture, + VGUI_CreateTexture, // VGUI_CreateTexture, + VGUI_UploadTexture, // VGUI_UploadTexture, + VGUI_UploadTextureBlock, // VGUI_UploadTextureBlock, + VGUI_DrawQuad, // VGUI_DrawQuad, + VGUI_GetTextureSizes, // VGUI_GetTextureSizes, + VGUI_GenerateTexture, // VGUI_GenerateTexture, + VGUI_EngineMalloc, + VGUI_CursorSelect, + VGUI_GetColor, + VGUI_IsInGame, + Key_EnableTextInput, + VGUI_GetMousePos, + VGUI_UtfProcessChar, + Platform_GetClipboardText, + Platform_SetClipboardText, + Platform_GetKeyModifiers, +}; + +qboolean VGui_LoadProgs( HINSTANCE hInstance ) +{ + void (*F)( vguiapi_t* ); + qboolean client = hInstance != NULL; + + vgui.dllFuncs = gEngfuncs; + + // not loading interface from client.dll, load vgui_support.dll instead + if( !client ) + { + string vguiloader, vguilib; + + // HACKHACK: try to load path from custom path + // to support having different versions of VGUI + if( Sys_GetParmFromCmdLine( "-vguilib", vguilib ) && !COM_LoadLibrary( vguilib, false, false )) + { + Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s", COM_GetLibraryError()); + } + + if( !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) + { + Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, sizeof( vguiloader )); + } + + hInstance = vgui.hInstance = COM_LoadLibrary( vguiloader, false, false ); + + if( !vgui.hInstance ) + { + if( FS_FileExists( vguiloader, false )) + Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); + else Con_Reportf( "%s: not found\n", __func__ ); + + return false; + } + } + + // try legacy API first + F = COM_GetProcAddress( hInstance, client ? "InitVGUISupportAPI" : "InitAPI" ); + + if( F ) + { + F( &vgui.dllFuncs ); + + vgui.initialized = vgui.dllFuncs.initialized = true; + Con_Reportf( "%s: initialized legacy API in %s module\n", __func__, client ? "client" : "support" ); + + return true; + } + + Con_Reportf( S_ERROR "%s: Failed to find VGUI support API entry point in %s module\n", __func__, client ? "client" : "support" ); + return false; } /* ================ VGui_Startup -Load vgui_support library and call VGui_Startup ================ */ -void VGui_Startup( const char *clientlib, int width, int height ) +void VGui_Startup( int width, int height ) { - static qboolean failed = false; - - void (*F) ( vguiapi_t * ); - char vguiloader[256]; - char vguilib[256]; - - vguiloader[0] = vguilib[0] = '\0'; - - if( failed ) + // vgui not initialized from both support and client modules, skip + if( !vgui.initialized ) return; - if( !vgui.initialized ) - { - vgui_utf8 = Cvar_Get( "vgui_utf8", "0", FCVAR_ARCHIVE, "enable utf-8 support for vgui text" ); + height = Q_max( 480, height ); - VGui_FillAPIFromRef( &vgui, &ref.dllFuncs ); + if( width <= 640 ) width = 640; + else if( width <= 800 ) width = 800; + else if( width <= 1024 ) width = 1024; + else if( width <= 1152 ) width = 1152; + else if( width <= 1280 ) width = 1280; + else if( width <= 1600 ) width = 1600; - s_pVGuiSupport = COM_LoadLibrary( clientlib, false, false ); - - if( s_pVGuiSupport ) - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitVGUISupportAPI" ); - if( F ) - { - F( &vgui ); - vgui.initialized = true; - Con_Reportf( "vgui_support: found internal client support\n" ); - } - else - { - COM_FreeLibrary( s_pVGuiSupport ); - } - } - - if( !vgui.initialized ) - { - // HACKHACK: load vgui with correct path first if specified. - // it will be reused while resolving vgui support and client deps - if( Sys_GetParmFromCmdLine( "-vguilib", vguilib )) - { - if( Q_strstr( vguilib, ".dll" )) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - else - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - if( !COM_LoadLibrary( vguilib, false, false )) - Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError() ); - } - - if( Q_strstr( clientlib, ".dll" )) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - - if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - - if( !s_pVGuiSupport ) - { - s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); - } - - if( !s_pVGuiSupport ) - { - if( FS_FileExists( vguiloader, false )) - { - Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); - } - else - { - Con_Reportf( "vgui_support: not found\n" ); - } - failed = true; - } - else - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); - if( F ) - { - F( &vgui ); - vgui.initialized = true; - } - else - { - Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); - failed = true; - COM_FreeLibrary( s_pVGuiSupport ); - } - } - } - } - - if( height < 480 ) - height = 480; - - if( width <= 640 ) - width = 640; - else if( width <= 800 ) - width = 800; - else if( width <= 1024 ) - width = 1024; - else if( width <= 1152 ) - width = 1152; - else if( width <= 1280 ) - width = 1280; - else if( width <= 1600 ) - width = 1600; -#ifdef XASH_DLL_LOADER - else if ( Q_strstr( vguiloader, ".dll" ) ) - width = 1600; -#endif - - - if( vgui.initialized ) - { - //host.mouse_visible = true; - vgui.Startup( width, height ); - } - else if ( COM_CheckString( clientlib ) ) - { - failed = true; - } + if( vgui.dllFuncs.Startup ) + vgui.dllFuncs.Startup( width, height ); } @@ -270,214 +411,224 @@ Unload vgui_support library and call VGui_Shutdown */ void VGui_Shutdown( void ) { - if( vgui.Shutdown ) - vgui.Shutdown(); + if( vgui.dllFuncs.Shutdown ) + vgui.dllFuncs.Shutdown(); - if( s_pVGuiSupport ) - COM_FreeLibrary( s_pVGuiSupport ); - s_pVGuiSupport = NULL; + if( vgui.hInstance ) + COM_FreeLibrary( vgui.hInstance ); - vgui.initialized = false; + // drop pointers to now unloaded vgui_support + vgui.dllFuncs = gEngfuncs; + vgui.hInstance = NULL; } -void VGUI_InitKeyTranslationTable( void ) +static void VGUI_InitKeyTranslationTable( void ) { - static qboolean bInitted = false; + static qboolean initialized = false; - if( bInitted ) - return; - bInitted = true; + if( initialized ) return; + + initialized = true; // set virtual key translation table - memset( s_pVirtualKeyTrans, -1, sizeof( s_pVirtualKeyTrans ) ); + memset( vgui.virtualKeyTrans, -1, sizeof( vgui.virtualKeyTrans ) ); - s_pVirtualKeyTrans['0'] = KEY_0; - s_pVirtualKeyTrans['1'] = KEY_1; - s_pVirtualKeyTrans['2'] = KEY_2; - s_pVirtualKeyTrans['3'] = KEY_3; - s_pVirtualKeyTrans['4'] = KEY_4; - s_pVirtualKeyTrans['5'] = KEY_5; - s_pVirtualKeyTrans['6'] = KEY_6; - s_pVirtualKeyTrans['7'] = KEY_7; - s_pVirtualKeyTrans['8'] = KEY_8; - s_pVirtualKeyTrans['9'] = KEY_9; - s_pVirtualKeyTrans['A'] = s_pVirtualKeyTrans['a'] = KEY_A; - s_pVirtualKeyTrans['B'] = s_pVirtualKeyTrans['b'] = KEY_B; - s_pVirtualKeyTrans['C'] = s_pVirtualKeyTrans['c'] = KEY_C; - s_pVirtualKeyTrans['D'] = s_pVirtualKeyTrans['d'] = KEY_D; - s_pVirtualKeyTrans['E'] = s_pVirtualKeyTrans['e'] = KEY_E; - s_pVirtualKeyTrans['F'] = s_pVirtualKeyTrans['f'] = KEY_F; - s_pVirtualKeyTrans['G'] = s_pVirtualKeyTrans['g'] = KEY_G; - s_pVirtualKeyTrans['H'] = s_pVirtualKeyTrans['h'] = KEY_H; - s_pVirtualKeyTrans['I'] = s_pVirtualKeyTrans['i'] = KEY_I; - s_pVirtualKeyTrans['J'] = s_pVirtualKeyTrans['j'] = KEY_J; - s_pVirtualKeyTrans['K'] = s_pVirtualKeyTrans['k'] = KEY_K; - s_pVirtualKeyTrans['L'] = s_pVirtualKeyTrans['l'] = KEY_L; - s_pVirtualKeyTrans['M'] = s_pVirtualKeyTrans['m'] = KEY_M; - s_pVirtualKeyTrans['N'] = s_pVirtualKeyTrans['n'] = KEY_N; - s_pVirtualKeyTrans['O'] = s_pVirtualKeyTrans['o'] = KEY_O; - s_pVirtualKeyTrans['P'] = s_pVirtualKeyTrans['p'] = KEY_P; - s_pVirtualKeyTrans['Q'] = s_pVirtualKeyTrans['q'] = KEY_Q; - s_pVirtualKeyTrans['R'] = s_pVirtualKeyTrans['r'] = KEY_R; - s_pVirtualKeyTrans['S'] = s_pVirtualKeyTrans['s'] = KEY_S; - s_pVirtualKeyTrans['T'] = s_pVirtualKeyTrans['t'] = KEY_T; - s_pVirtualKeyTrans['U'] = s_pVirtualKeyTrans['u'] = KEY_U; - s_pVirtualKeyTrans['V'] = s_pVirtualKeyTrans['v'] = KEY_V; - s_pVirtualKeyTrans['W'] = s_pVirtualKeyTrans['w'] = KEY_W; - s_pVirtualKeyTrans['X'] = s_pVirtualKeyTrans['x'] = KEY_X; - s_pVirtualKeyTrans['Y'] = s_pVirtualKeyTrans['y'] = KEY_Y; - s_pVirtualKeyTrans['Z'] = s_pVirtualKeyTrans['z'] = KEY_Z; + // TODO: engine keys are not enough here! + // make crossplatform way to pass SDL keys here - s_pVirtualKeyTrans[K_KP_5 - 5] = KEY_PAD_0; - s_pVirtualKeyTrans[K_KP_5 - 4] = KEY_PAD_1; - s_pVirtualKeyTrans[K_KP_5 - 3] = KEY_PAD_2; - s_pVirtualKeyTrans[K_KP_5 - 2] = KEY_PAD_3; - s_pVirtualKeyTrans[K_KP_5 - 1] = KEY_PAD_4; - s_pVirtualKeyTrans[K_KP_5 - 0] = KEY_PAD_5; - s_pVirtualKeyTrans[K_KP_5 + 1] = KEY_PAD_6; - s_pVirtualKeyTrans[K_KP_5 + 2] = KEY_PAD_7; - s_pVirtualKeyTrans[K_KP_5 + 3] = KEY_PAD_8; - s_pVirtualKeyTrans[K_KP_5 + 4] = KEY_PAD_9; - s_pVirtualKeyTrans[K_KP_SLASH] = KEY_PAD_DIVIDE; - s_pVirtualKeyTrans['*'] = KEY_PAD_MULTIPLY; - s_pVirtualKeyTrans[K_KP_MINUS] = KEY_PAD_MINUS; - s_pVirtualKeyTrans[K_KP_PLUS] = KEY_PAD_PLUS; - s_pVirtualKeyTrans[K_KP_ENTER] = KEY_PAD_ENTER; - //s_pVirtualKeyTrans[K_KP_DECIMAL] = KEY_PAD_DECIMAL; - s_pVirtualKeyTrans['['] = KEY_LBRACKET; - s_pVirtualKeyTrans[']'] = KEY_RBRACKET; - s_pVirtualKeyTrans[';'] = KEY_SEMICOLON; - s_pVirtualKeyTrans['\''] = KEY_APOSTROPHE; - s_pVirtualKeyTrans['`'] = KEY_BACKQUOTE; - s_pVirtualKeyTrans[','] = KEY_COMMA; - s_pVirtualKeyTrans['.'] = KEY_PERIOD; - s_pVirtualKeyTrans[K_KP_SLASH] = KEY_SLASH; - s_pVirtualKeyTrans['\\'] = KEY_BACKSLASH; - s_pVirtualKeyTrans['-'] = KEY_MINUS; - s_pVirtualKeyTrans['='] = KEY_EQUAL; - s_pVirtualKeyTrans[K_ENTER] = KEY_ENTER; - s_pVirtualKeyTrans[K_SPACE] = KEY_SPACE; - s_pVirtualKeyTrans[K_BACKSPACE] = KEY_BACKSPACE; - s_pVirtualKeyTrans[K_TAB] = KEY_TAB; - s_pVirtualKeyTrans[K_CAPSLOCK] = KEY_CAPSLOCK; - s_pVirtualKeyTrans[K_KP_NUMLOCK] = KEY_NUMLOCK; - s_pVirtualKeyTrans[K_ESCAPE] = KEY_ESCAPE; - //s_pVirtualKeyTrans[K_KP_SCROLLLOCK] = KEY_SCROLLLOCK; - s_pVirtualKeyTrans[K_INS] = KEY_INSERT; - s_pVirtualKeyTrans[K_DEL] = KEY_DELETE; - s_pVirtualKeyTrans[K_HOME] = KEY_HOME; - s_pVirtualKeyTrans[K_END] = KEY_END; - s_pVirtualKeyTrans[K_PGUP] = KEY_PAGEUP; - s_pVirtualKeyTrans[K_PGDN] = KEY_PAGEDOWN; - s_pVirtualKeyTrans[K_PAUSE] = KEY_BREAK; - //s_pVirtualKeyTrans[K_SHIFT] = KEY_RSHIFT; - s_pVirtualKeyTrans[K_SHIFT] = KEY_LSHIFT; // SHIFT -> left SHIFT - //s_pVirtualKeyTrans[SDLK_RALT] = KEY_RALT; - s_pVirtualKeyTrans[K_ALT] = KEY_LALT; // ALT -> left ALT - //s_pVirtualKeyTrans[SDLK_RCTRL] = KEY_RCONTROL; - s_pVirtualKeyTrans[K_CTRL] = KEY_LCONTROL; // CTRL -> left CTRL - s_pVirtualKeyTrans[K_WIN] = KEY_LWIN; - //s_pVirtualKeyTrans[SDLK_APPLICATION] = KEY_RWIN; - //s_pVirtualKeyTrans[K_WIN] = KEY_APP; - s_pVirtualKeyTrans[K_UPARROW] = KEY_UP; - s_pVirtualKeyTrans[K_LEFTARROW] = KEY_LEFT; - s_pVirtualKeyTrans[K_DOWNARROW] = KEY_DOWN; - s_pVirtualKeyTrans[K_RIGHTARROW] = KEY_RIGHT; - s_pVirtualKeyTrans[K_F1] = KEY_F1; - s_pVirtualKeyTrans[K_F2] = KEY_F2; - s_pVirtualKeyTrans[K_F3] = KEY_F3; - s_pVirtualKeyTrans[K_F4] = KEY_F4; - s_pVirtualKeyTrans[K_F5] = KEY_F5; - s_pVirtualKeyTrans[K_F6] = KEY_F6; - s_pVirtualKeyTrans[K_F7] = KEY_F7; - s_pVirtualKeyTrans[K_F8] = KEY_F8; - s_pVirtualKeyTrans[K_F9] = KEY_F9; - s_pVirtualKeyTrans[K_F10] = KEY_F10; - s_pVirtualKeyTrans[K_F11] = KEY_F11; - s_pVirtualKeyTrans[K_F12] = KEY_F12; + vgui.virtualKeyTrans['0'] = KEY_0; + vgui.virtualKeyTrans['1'] = KEY_1; + vgui.virtualKeyTrans['2'] = KEY_2; + vgui.virtualKeyTrans['3'] = KEY_3; + vgui.virtualKeyTrans['4'] = KEY_4; + vgui.virtualKeyTrans['5'] = KEY_5; + vgui.virtualKeyTrans['6'] = KEY_6; + vgui.virtualKeyTrans['7'] = KEY_7; + vgui.virtualKeyTrans['8'] = KEY_8; + vgui.virtualKeyTrans['9'] = KEY_9; + vgui.virtualKeyTrans['A'] = vgui.virtualKeyTrans['a'] = KEY_A; + vgui.virtualKeyTrans['B'] = vgui.virtualKeyTrans['b'] = KEY_B; + vgui.virtualKeyTrans['C'] = vgui.virtualKeyTrans['c'] = KEY_C; + vgui.virtualKeyTrans['D'] = vgui.virtualKeyTrans['d'] = KEY_D; + vgui.virtualKeyTrans['E'] = vgui.virtualKeyTrans['e'] = KEY_E; + vgui.virtualKeyTrans['F'] = vgui.virtualKeyTrans['f'] = KEY_F; + vgui.virtualKeyTrans['G'] = vgui.virtualKeyTrans['g'] = KEY_G; + vgui.virtualKeyTrans['H'] = vgui.virtualKeyTrans['h'] = KEY_H; + vgui.virtualKeyTrans['I'] = vgui.virtualKeyTrans['i'] = KEY_I; + vgui.virtualKeyTrans['J'] = vgui.virtualKeyTrans['j'] = KEY_J; + vgui.virtualKeyTrans['K'] = vgui.virtualKeyTrans['k'] = KEY_K; + vgui.virtualKeyTrans['L'] = vgui.virtualKeyTrans['l'] = KEY_L; + vgui.virtualKeyTrans['M'] = vgui.virtualKeyTrans['m'] = KEY_M; + vgui.virtualKeyTrans['N'] = vgui.virtualKeyTrans['n'] = KEY_N; + vgui.virtualKeyTrans['O'] = vgui.virtualKeyTrans['o'] = KEY_O; + vgui.virtualKeyTrans['P'] = vgui.virtualKeyTrans['p'] = KEY_P; + vgui.virtualKeyTrans['Q'] = vgui.virtualKeyTrans['q'] = KEY_Q; + vgui.virtualKeyTrans['R'] = vgui.virtualKeyTrans['r'] = KEY_R; + vgui.virtualKeyTrans['S'] = vgui.virtualKeyTrans['s'] = KEY_S; + vgui.virtualKeyTrans['T'] = vgui.virtualKeyTrans['t'] = KEY_T; + vgui.virtualKeyTrans['U'] = vgui.virtualKeyTrans['u'] = KEY_U; + vgui.virtualKeyTrans['V'] = vgui.virtualKeyTrans['v'] = KEY_V; + vgui.virtualKeyTrans['W'] = vgui.virtualKeyTrans['w'] = KEY_W; + vgui.virtualKeyTrans['X'] = vgui.virtualKeyTrans['x'] = KEY_X; + vgui.virtualKeyTrans['Y'] = vgui.virtualKeyTrans['y'] = KEY_Y; + vgui.virtualKeyTrans['Z'] = vgui.virtualKeyTrans['z'] = KEY_Z; + + vgui.virtualKeyTrans[K_KP_5 - 5] = KEY_PAD_0; + vgui.virtualKeyTrans[K_KP_5 - 4] = KEY_PAD_1; + vgui.virtualKeyTrans[K_KP_5 - 3] = KEY_PAD_2; + vgui.virtualKeyTrans[K_KP_5 - 2] = KEY_PAD_3; + vgui.virtualKeyTrans[K_KP_5 - 1] = KEY_PAD_4; + vgui.virtualKeyTrans[K_KP_5 - 0] = KEY_PAD_5; + vgui.virtualKeyTrans[K_KP_5 + 1] = KEY_PAD_6; + vgui.virtualKeyTrans[K_KP_5 + 2] = KEY_PAD_7; + vgui.virtualKeyTrans[K_KP_5 + 3] = KEY_PAD_8; + vgui.virtualKeyTrans[K_KP_5 + 4] = KEY_PAD_9; + vgui.virtualKeyTrans[K_KP_SLASH] = KEY_PAD_DIVIDE; + vgui.virtualKeyTrans['*'] = KEY_PAD_MULTIPLY; + vgui.virtualKeyTrans[K_KP_MINUS] = KEY_PAD_MINUS; + vgui.virtualKeyTrans[K_KP_PLUS] = KEY_PAD_PLUS; + vgui.virtualKeyTrans[K_KP_ENTER] = KEY_PAD_ENTER; + vgui.virtualKeyTrans[K_KP_NUMLOCK] = KEY_NUMLOCK; + vgui.virtualKeyTrans['['] = KEY_LBRACKET; + vgui.virtualKeyTrans[']'] = KEY_RBRACKET; + vgui.virtualKeyTrans[';'] = KEY_SEMICOLON; + vgui.virtualKeyTrans['`'] = KEY_BACKQUOTE; + vgui.virtualKeyTrans[','] = KEY_COMMA; + vgui.virtualKeyTrans['.'] = KEY_PERIOD; + vgui.virtualKeyTrans['-'] = KEY_MINUS; + vgui.virtualKeyTrans['='] = KEY_EQUAL; + vgui.virtualKeyTrans['/'] = KEY_SLASH; + vgui.virtualKeyTrans['\\'] = KEY_BACKSLASH; + vgui.virtualKeyTrans['\''] = KEY_APOSTROPHE; + vgui.virtualKeyTrans[K_TAB] = KEY_TAB; + vgui.virtualKeyTrans[K_ENTER] = KEY_ENTER; + vgui.virtualKeyTrans[K_SPACE] = KEY_SPACE; + vgui.virtualKeyTrans[K_CAPSLOCK] = KEY_CAPSLOCK; + vgui.virtualKeyTrans[K_BACKSPACE] = KEY_BACKSPACE; + vgui.virtualKeyTrans[K_ESCAPE] = KEY_ESCAPE; + vgui.virtualKeyTrans[K_INS] = KEY_INSERT; + vgui.virtualKeyTrans[K_DEL] = KEY_DELETE; + vgui.virtualKeyTrans[K_HOME] = KEY_HOME; + vgui.virtualKeyTrans[K_END] = KEY_END; + vgui.virtualKeyTrans[K_PGUP] = KEY_PAGEUP; + vgui.virtualKeyTrans[K_PGDN] = KEY_PAGEDOWN; + vgui.virtualKeyTrans[K_PAUSE] = KEY_BREAK; + vgui.virtualKeyTrans[K_SHIFT] = KEY_LSHIFT; // SHIFT -> left SHIFT + vgui.virtualKeyTrans[K_ALT] = KEY_LALT; // ALT -> left ALT + vgui.virtualKeyTrans[K_CTRL] = KEY_LCONTROL; // CTRL -> left CTRL + vgui.virtualKeyTrans[K_WIN] = KEY_LWIN; + vgui.virtualKeyTrans[K_UPARROW] = KEY_UP; + vgui.virtualKeyTrans[K_LEFTARROW] = KEY_LEFT; + vgui.virtualKeyTrans[K_DOWNARROW] = KEY_DOWN; + vgui.virtualKeyTrans[K_RIGHTARROW] = KEY_RIGHT; + vgui.virtualKeyTrans[K_F1] = KEY_F1; + vgui.virtualKeyTrans[K_F2] = KEY_F2; + vgui.virtualKeyTrans[K_F3] = KEY_F3; + vgui.virtualKeyTrans[K_F4] = KEY_F4; + vgui.virtualKeyTrans[K_F5] = KEY_F5; + vgui.virtualKeyTrans[K_F6] = KEY_F6; + vgui.virtualKeyTrans[K_F7] = KEY_F7; + vgui.virtualKeyTrans[K_F8] = KEY_F8; + vgui.virtualKeyTrans[K_F9] = KEY_F9; + vgui.virtualKeyTrans[K_F10] = KEY_F10; + vgui.virtualKeyTrans[K_F11] = KEY_F11; + vgui.virtualKeyTrans[K_F12] = KEY_F12; } -enum VGUI_KeyCode VGUI_MapKey( int keyCode ) +static enum VGUI_KeyCode VGUI_MapKey( int keyCode ) { VGUI_InitKeyTranslationTable(); - if( keyCode < 0 || keyCode >= (int)sizeof( s_pVirtualKeyTrans ) / (int)sizeof( s_pVirtualKeyTrans[0] )) - { - //Assert( false ); - return (enum VGUI_KeyCode)-1; - } - else - { - return s_pVirtualKeyTrans[keyCode]; - } + if( keyCode >= 0 && keyCode < ARRAYSIZE( vgui.virtualKeyTrans )) + return vgui.virtualKeyTrans[keyCode]; + + return (enum VGUI_KeyCode)-1; } -void VGui_KeyEvent( int key, int down ) +void VGui_MouseEvent( int key, int clicks ) { - if( !vgui.initialized ) + enum VGUI_MouseAction mact; + enum VGUI_MouseCode code; + + if( !vgui.dllFuncs.Mouse ) return; switch( key ) { - case K_MOUSE1: - if( down && host.mouse_visible ) { - Key_EnableTextInput(true, false); - } - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_LEFT ); - return; - case K_MOUSE2: - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_RIGHT ); - return; - case K_MOUSE3: - vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_MIDDLE ); - return; - case K_MWHEELDOWN: - vgui.Mouse( MA_WHEEL, 1 ); - return; - case K_MWHEELUP: - vgui.Mouse( MA_WHEEL, -1 ); - return; - default: - break; + case K_MOUSE1: code = MOUSE_LEFT; break; + case K_MOUSE2: code = MOUSE_RIGHT; break; + case K_MOUSE3: code = MOUSE_MIDDLE; break; + default: return; } - if( down == 2 ) - vgui.Key( KA_TYPED, VGUI_MapKey( key ) ); + if( clicks >= 2 ) + mact = MA_DOUBLE; + else if( clicks == 1 ) + mact = MA_PRESSED; else - vgui.Key( down?KA_PRESSED:KA_RELEASED, VGUI_MapKey( key ) ); - //Msg("VGui_KeyEvent %d %d %d\n", key, VGUI_MapKey( key ), down ); + mact = MA_RELEASED; + + vgui.dllFuncs.Mouse( mact, code ); +} + +void VGui_MWheelEvent( int y ) +{ + if( !vgui.dllFuncs.Mouse ) + return; + + vgui.dllFuncs.Mouse( MA_WHEEL, y ); +} + +void VGui_KeyEvent( int key, int down ) +{ + enum VGUI_KeyCode code; + + if( !vgui.dllFuncs.Key ) + return; + + if(( code = VGUI_MapKey( key )) < 0 ) + return; + + if( down ) + { + vgui.dllFuncs.Key( KA_PRESSED, code ); + vgui.dllFuncs.Key( KA_TYPED, code ); + } + else vgui.dllFuncs.Key( KA_RELEASED, code ); } void VGui_MouseMove( int x, int y ) { - float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; - float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; - if( vgui.initialized ) - vgui.MouseMove( x / xscale, y / yscale ); + if( vgui.dllFuncs.MouseMove ) + { + float xscale = (float)refState.width / (float)clgame.scrInfo.iWidth; + float yscale = (float)refState.height / (float)clgame.scrInfo.iHeight; + vgui.dllFuncs.MouseMove( x / xscale, y / yscale ); + } } void VGui_Paint( void ) { - if( vgui.initialized ) - vgui.Paint(); + if( vgui.dllFuncs.Paint ) + vgui.dllFuncs.Paint(); } -void VGui_RunFrame( void ) +void VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType ) { - //stub + vgui.cursor = cursorType; } - void *GAME_EXPORT VGui_GetPanel( void ) { - if( vgui.initialized ) - return vgui.GetPanel(); + if( vgui.dllFuncs.GetPanel ) + return vgui.dllFuncs.GetPanel(); return NULL; } void VGui_ReportTextInput( const char *text ) { - if ( vgui.initialized ) - vgui.TextInput( text ); + if( vgui.dllFuncs.TextInput ) + vgui.dllFuncs.TextInput( text ); } + diff --git a/engine/client/vgui/vgui_draw.h b/engine/client/vgui/vgui_draw.h index dd5a4ad2..583c0fdc 100644 --- a/engine/client/vgui/vgui_draw.h +++ b/engine/client/vgui/vgui_draw.h @@ -16,25 +16,22 @@ GNU General Public License for more details. #ifndef VGUI_DRAW_H #define VGUI_DRAW_H -#ifdef __cplusplus -extern "C" { -#endif - -#include "port.h" - // // vgui_draw.c // -void VGui_Startup( const char *clientlib, int width, int height ); +void VGui_RegisterCvars( void ); +qboolean VGui_LoadProgs( HINSTANCE hInstance ); +void VGui_Startup( int width, int height ); void VGui_Shutdown( void ); void VGui_Paint( void ); void VGui_RunFrame( void ); +void VGui_MouseEvent( int key, int clicks ); +void VGui_MWheelEvent( int y ); void VGui_KeyEvent( int key, int down ); void VGui_MouseMove( int x, int y ); qboolean VGui_IsActive( void ); void *VGui_GetPanel( void ); void VGui_ReportTextInput( const char *text ); -#ifdef __cplusplus -} -#endif -#endif//VGUI_DRAW_H +void VGui_UpdateInternalCursorState( VGUI_DefaultCursor cursorType ); + +#endif // VGUI_DRAW_H diff --git a/engine/client/vid_common.c b/engine/client/vid_common.c index 1f1d1a74..1dbb11ae 100644 --- a/engine/client/vid_common.c +++ b/engine/client/vid_common.c @@ -20,35 +20,20 @@ GNU General Public License for more details. #include "vid_common.h" #include "platform/platform.h" -#define WINDOW_NAME XASH_ENGINE_NAME " Window" // Half-Life -convar_t *vid_displayfrequency; -convar_t *vid_fullscreen; -convar_t *vid_mode; -convar_t *vid_brightness; -convar_t *vid_gamma; -convar_t *vid_highdpi; +static CVAR_DEFINE_AUTO( vid_mode, "0", FCVAR_RENDERINFO, "current video mode index (used only for storage)" ); +static CVAR_DEFINE_AUTO( vid_rotate, "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen rotation (0-3)" ); +static CVAR_DEFINE_AUTO( vid_scale, "1.0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "pixel scale" ); + +CVAR_DEFINE_AUTO( vid_highdpi, "1", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable High-DPI mode" ); +CVAR_DEFINE_AUTO( vid_maximized, "0", FCVAR_RENDERINFO, "window maximized state, read-only" ); +CVAR_DEFINE( vid_fullscreen, "fullscreen", DEFAULT_FULLSCREEN, FCVAR_RENDERINFO|FCVAR_VIDRESTART, "fullscreen state (0 windowed, 1 fullscreen, 2 borderless)" ); +CVAR_DEFINE( window_width, "width", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen width" ); +CVAR_DEFINE( window_height, "height", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen height" ); +CVAR_DEFINE( window_xpos, "_window_xpos", "-1", FCVAR_RENDERINFO, "window position by horizontal" ); +CVAR_DEFINE( window_ypos, "_window_ypos", "-1", FCVAR_RENDERINFO, "window position by vertical" ); glwstate_t glw_state; -convar_t *window_xpos; -convar_t *window_ypos; - -convar_t *vid_rotate; -convar_t *vid_scale; - -/* -================= -VID_StartupGamma -================= -*/ -void VID_StartupGamma( void ) -{ - BuildGammaTable( vid_gamma->value, vid_brightness->value ); - Con_Reportf( "VID_StartupGamma: gamma %g brightness %g\n", vid_gamma->value, vid_brightness->value ); - ClearBits( vid_brightness->flags, FCVAR_CHANGED ); - ClearBits( vid_gamma->flags, FCVAR_CHANGED ); -} - /* ================= VID_InitDefaultResolution @@ -67,19 +52,31 @@ void VID_InitDefaultResolution( void ) R_SaveVideoMode ================= */ -void R_SaveVideoMode( int w, int h , int render_w, int render_h ) +void R_SaveVideoMode( int w, int h, int render_w, int render_h, qboolean maximized ) { + if( !w || !h || !render_w || !render_h ) + { + host.renderinfo_changed = false; + return; + } + host.window_center_x = w / 2; host.window_center_y = h / 2; Cvar_SetValue( "width", w ); Cvar_SetValue( "height", h ); + Cvar_DirectSet( &vid_maximized, maximized ? "1" : "0" ); + + // immediately drop changed state or we may trigger + // video subsystem to reapply settings + host.renderinfo_changed = false; + + if( refState.width == render_w && refState.height == render_h ) + return; refState.width = render_w; refState.height = render_h; - host.renderinfo_changed = false; - // check for 4:3 or 5:4 if( render_w * 3 != render_h * 4 && render_w * 4 != render_h * 5 ) refState.wideScreen = true; @@ -96,7 +93,7 @@ VID_GetModeString const char *VID_GetModeString( int vid_mode ) { vidmode_t *vidmode; - if( vid_mode < 0 || vid_mode > R_MaxVideoModes() ) + if( vid_mode < 0 || vid_mode >= R_MaxVideoModes() ) return NULL; if( !( vidmode = R_GetVideoMode( vid_mode ) ) ) @@ -114,11 +111,11 @@ check vid modes and fullscreen */ void VID_CheckChanges( void ) { - if( FBitSet( cl_allow_levelshots->flags, FCVAR_CHANGED )) + if( FBitSet( cl_allow_levelshots.flags, FCVAR_CHANGED )) { //GL_FreeTexture( cls.loadingBar ); SCR_RegisterTextures(); // reload 'lambda' image - ClearBits( cl_allow_levelshots->flags, FCVAR_CHANGED ); + ClearBits( cl_allow_levelshots.flags, FCVAR_CHANGED ); } if( host.renderinfo_changed ) @@ -135,6 +132,36 @@ void VID_CheckChanges( void ) } } +/* +=============== +VID_SetDisplayTransform + +notify ref dll about screen transformations +=============== +*/ +void VID_SetDisplayTransform( int *render_w, int *render_h ) +{ + uint rotate = vid_rotate.value; + + if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale.value, vid_scale.value )) + { + if( rotate & 1 ) + { + int swap = *render_w; + + *render_w = *render_h; + *render_h = swap; + } + + *render_h /= vid_scale.value; + *render_w /= vid_scale.value; + } + else + { + Con_Printf( S_WARN "failed to setup screen transform\n" ); + } +} + static void VID_Mode_f( void ) { int w, h; @@ -148,7 +175,7 @@ static void VID_Mode_f( void ) vidmode = R_GetVideoMode( Q_atoi( Cmd_Argv( 1 )) ); if( !vidmode ) { - Con_Print( S_ERROR "unable to set mode, backend returned null" ); + Con_Printf( S_ERROR "unable to set mode, backend returned null" ); return; } @@ -167,30 +194,28 @@ static void VID_Mode_f( void ) return; } - R_ChangeDisplaySettings( w, h, Cvar_VariableInteger( "fullscreen" ) ); + R_ChangeDisplaySettings( w, h, bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 )); } void VID_Init( void ) { // system screen width and height (don't suppose for change from console at all) - Cvar_Get( "width", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen width" ); - Cvar_Get( "height", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen height" ); + Cvar_RegisterVariable( &window_width ); + Cvar_RegisterVariable( &window_height ); - window_xpos = Cvar_Get( "_window_xpos", "130", FCVAR_RENDERINFO, "window position by horizontal" ); - window_ypos = Cvar_Get( "_window_ypos", "48", FCVAR_RENDERINFO, "window position by vertical" ); - - vid_gamma = Cvar_Get( "gamma", "2.5", FCVAR_ARCHIVE, "gamma amount" ); - vid_brightness = Cvar_Get( "brightness", "0.0", FCVAR_ARCHIVE, "brightness factor" ); - vid_displayfrequency = Cvar_Get ( "vid_displayfrequency", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "fullscreen refresh rate" ); - vid_fullscreen = Cvar_Get( "fullscreen", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable fullscreen mode" ); - vid_mode = Cvar_Get( "vid_mode", "0", FCVAR_RENDERINFO, "current video mode index (used just for storage)" ); - vid_highdpi = Cvar_Get( "vid_highdpi", "1", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable High-DPI mode" ); - vid_rotate = Cvar_Get( "vid_rotate", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "screen rotation (0-3)" ); - vid_scale = Cvar_Get( "vid_scale", "1.0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "pixel scale" ); + Cvar_RegisterVariable( &vid_mode ); + Cvar_RegisterVariable( &vid_highdpi ); + Cvar_RegisterVariable( &vid_rotate ); + Cvar_RegisterVariable( &vid_scale ); + Cvar_RegisterVariable( &vid_fullscreen ); + Cvar_RegisterVariable( &vid_maximized ); + Cvar_RegisterVariable( &window_xpos ); + Cvar_RegisterVariable( &window_ypos ); // a1ba: planned to be named vid_mode for compability // but supported mode list is filled by backends, so numbers are not portable any more Cmd_AddRestrictedCommand( "vid_setmode", VID_Mode_f, "display video mode" ); + V_Init(); // init gamma R_Init(); // init renderer } diff --git a/engine/client/vid_common.h b/engine/client/vid_common.h index 1014419e..8b9d0d3f 100644 --- a/engine/client/vid_common.h +++ b/engine/client/vid_common.h @@ -9,6 +9,13 @@ typedef struct vidmode_s int height; } vidmode_t; +typedef enum window_mode_e +{ + WINDOW_MODE_WINDOWED = 0, + WINDOW_MODE_FULLSCREEN, + WINDOW_MODE_BORDERLESS, + WINDOW_MODE_COUNT, +} window_mode_t; typedef struct { @@ -21,8 +28,6 @@ typedef struct qboolean initialized; // OpenGL subsystem started qboolean extended; // extended context allows to GL_Debug qboolean software; - - } glwstate_t; extern glwstate_t glw_state; @@ -30,16 +35,18 @@ extern glwstate_t glw_state; #define VID_MIN_HEIGHT 200 #define VID_MIN_WIDTH 320 -extern convar_t *vid_fullscreen; -extern convar_t *vid_displayfrequency; -extern convar_t *vid_highdpi; -extern convar_t *vid_rotate; -extern convar_t *vid_scale; +extern convar_t vid_fullscreen; +extern convar_t vid_maximized; +extern convar_t vid_highdpi; +extern convar_t window_width; +extern convar_t window_height; +extern convar_t window_xpos; +extern convar_t window_ypos; +extern convar_t gl_msaa_samples; -extern convar_t *gl_msaa_samples; -void R_SaveVideoMode( int w, int h, int render_w, int render_h ); +void R_SaveVideoMode( int w, int h, int render_w, int render_h, qboolean maximized ); +void VID_SetDisplayTransform( int *render_w, int *render_h ); void VID_CheckChanges( void ); const char *VID_GetModeString( int vid_mode ); -void VID_StartupGamma( void ); #endif // VID_COMMON diff --git a/engine/client/voice.c b/engine/client/voice.c index 7d5ef8f0..579cbcdd 100644 --- a/engine/client/voice.c +++ b/engine/client/voice.c @@ -16,20 +16,80 @@ GNU General Public License for more details. #define CUSTOM_MODES 1 // required to correctly link with Opus Custom #include +#include #include "common.h" #include "client.h" #include "voice.h" +#include "crclib.h" voice_state_t voice = { 0 }; -CVAR_DEFINE_AUTO( voice_enable, "1", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "enable voice chat" ); +static CVAR_DEFINE_AUTO( voice_enable, "1", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, "enable voice chat" ); CVAR_DEFINE_AUTO( voice_loopback, "0", FCVAR_PRIVILEGED, "loopback voice back to the speaker" ); -CVAR_DEFINE_AUTO( voice_scale, "1.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "incoming voice volume scale" ); -CVAR_DEFINE_AUTO( voice_avggain, "0.5", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (average)" ); -CVAR_DEFINE_AUTO( voice_maxgain, "5.0", FCVAR_PRIVILEGED|FCVAR_ARCHIVE, "automatic voice gain control (maximum)" ); -CVAR_DEFINE_AUTO( voice_inputfromfile, "0", FCVAR_PRIVILEGED, "input voice from voice_input.wav" ); +static CVAR_DEFINE_AUTO( voice_scale, "1.0", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, "incoming voice volume scale" ); +static CVAR_DEFINE_AUTO( voice_transmit_scale, "1.0", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, "outcoming voice volume scale" ); +static CVAR_DEFINE_AUTO( voice_avggain, "0.5", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, "automatic voice gain control (average)" ); +static CVAR_DEFINE_AUTO( voice_maxgain, "5.0", FCVAR_PRIVILEGED | FCVAR_ARCHIVE, "automatic voice gain control (maximum)" ); +static CVAR_DEFINE_AUTO( voice_inputfromfile, "0", FCVAR_PRIVILEGED, "input voice from voice_input.wav" ); -static void Voice_ApplyGainAdjust( int16_t *samples, int count ); +static void Voice_StartChannel( uint samples, byte *data, int entnum ); + +/* +=============================================================================== + + UTILITY FUNCTIONS + +=============================================================================== +*/ + +/* +========================= +Voice_IsGoldSrcMode + +Check if codec is GoldSrc mode +========================= +*/ +static qboolean Voice_IsGoldSrcMode( const char *codec ) +{ + // check for an empty codec string + // if the server does not use ReVoice or VTC + // it will send an empty codec + // however, decoding is still possible + // if the voice data contains Opus + return( COM_CheckString( codec ) == 0 || Q_strstr( codec, "voice_speex" ) != NULL ); +} + +/* +========================= +Voice_IsOpusCustomMode + +Check if codec is Opus Custom mode +========================= +*/ +static qboolean Voice_IsOpusCustomMode( const char *codec ) +{ + return Q_strcmp( codec, VOICE_OPUS_CUSTOM_CODEC ) == 0; +} + +/* +========================= +Voice_GetBitrateForQuality + +Get bitrate for given quality level +========================= +*/ +static int Voice_GetBitrateForQuality( int quality, qboolean goldsrc ) +{ + switch( quality ) + { + case 1: return 6000; // 6 kbps + case 2: return 12000; // 12 kbps + case 3: return 24000; // 24 kbps + case 4: return 36000; // 36 kbps + case 5: return 48000; // 48 kbps + default: return 36000; // default + } +} /* =============================================================================== @@ -41,30 +101,61 @@ static void Voice_ApplyGainAdjust( int16_t *samples, int count ); /* ========================= -Voice_InitOpusDecoder +Voice_InitCustomMode +Initialize Opus Custom mode ========================= */ -static qboolean Voice_InitOpusDecoder( void ) +static qboolean Voice_InitCustomMode( void ) { - int err; + int err = 0; - voice.width = sizeof( opus_int16 ); + voice.width = sizeof( int16_t ); voice.samplerate = VOICE_OPUS_CUSTOM_SAMPLERATE; voice.frame_size = VOICE_OPUS_CUSTOM_FRAME_SIZE; voice.custom_mode = opus_custom_mode_create( SOUND_44k, voice.frame_size, &err ); + if( !voice.custom_mode ) { Con_Printf( S_ERROR "Can't create Opus Custom mode: %s\n", opus_strerror( err )); return false; } - voice.decoder = opus_custom_decoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); - if( !voice.decoder ) + return true; +} + +/* +========================= +Voice_InitOpusDecoder + +Initialize Opus decoders for clients +========================= +*/ +static qboolean Voice_InitOpusDecoder( void ) +{ + int err = 0; + + for( int i = 0; i < cl.maxclients; i++ ) { - Con_Printf( S_ERROR "Can't create Opus encoder: %s\n", opus_strerror( err )); - return false; + if( voice.goldsrc ) + { + voice.gs_decoders[i] = opus_decoder_create( GS_DEFAULT_SAMPLE_RATE, VOICE_PCM_CHANNELS, &err ); + if( !voice.gs_decoders[i] ) + { + Con_Printf( S_ERROR "Can't create GoldSrc Opus decoder for %i: %s\n", i, opus_strerror( err )); + return false; + } + } + else + { + voice.decoders[i] = opus_custom_decoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); + if( !voice.decoders[i] ) + { + Con_Printf( S_ERROR "Can't create Custom Opus decoder for %i: %s\n", i, opus_strerror( err )); + return false; + } + } } return true; @@ -74,36 +165,34 @@ static qboolean Voice_InitOpusDecoder( void ) ========================= Voice_InitOpusEncoder +Initialize Opus encoder with quality settings ========================= */ static qboolean Voice_InitOpusEncoder( int quality ) { - int err; + int err = 0; + int bitrate = Voice_GetBitrateForQuality( quality, voice.goldsrc ); - voice.encoder = opus_custom_encoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); - if( !voice.encoder ) + if( voice.goldsrc ) { - Con_Printf( S_ERROR "Can't create Opus encoder: %s\n", opus_strerror( err )); - return false; + voice.gs_encoder = opus_encoder_create( GS_DEFAULT_SAMPLE_RATE, VOICE_PCM_CHANNELS, OPUS_APPLICATION_VOIP, &err ); + if( !voice.gs_encoder ) + { + Con_Printf( S_ERROR "Can't create GoldSrc Opus encoder: %s\n", opus_strerror( err )); + return false; + } + opus_encoder_ctl( voice.gs_encoder, OPUS_SET_DTX( 1 )); + opus_encoder_ctl( voice.gs_encoder, OPUS_SET_BITRATE( bitrate )); } - - switch( quality ) + else { - case 1: // 6 kbps - opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 6000 )); - break; - case 2: // 12 kbps - opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 12000 )); - break; - case 4: // 64 kbps - opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 64000 )); - break; - case 5: // 96 kbps - opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 96000 )); - break; - default: // 36 kbps - opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( 36000 )); - break; + voice.encoder = opus_custom_encoder_create( voice.custom_mode, VOICE_PCM_CHANNELS, &err ); + if( !voice.encoder ) + { + Con_Printf( S_ERROR "Can't create Opus encoder: %s\n", opus_strerror( err )); + return false; + } + opus_custom_encoder_ctl( voice.encoder, OPUS_SET_BITRATE( bitrate )); } return true; @@ -113,14 +202,29 @@ static qboolean Voice_InitOpusEncoder( int quality ) ========================= Voice_ShutdownOpusDecoder +Cleanup Opus decoders ========================= */ static void Voice_ShutdownOpusDecoder( void ) { - if( voice.decoder ) + for( int i = 0; i < MAX_CLIENTS; i++ ) { - opus_custom_decoder_destroy( voice.decoder ); - voice.decoder = NULL; + if( voice.goldsrc ) + { + if( voice.gs_decoders[i] ) + { + opus_decoder_destroy( voice.gs_decoders[i] ); + voice.gs_decoders[i] = NULL; + } + } + else + { + if( voice.decoders[i] ) + { + opus_custom_decoder_destroy( voice.decoders[i] ); + voice.decoders[i] = NULL; + } + } } } @@ -128,16 +232,38 @@ static void Voice_ShutdownOpusDecoder( void ) ========================= Voice_ShutdownOpusEncoder +Cleanup Opus encoder ========================= */ static void Voice_ShutdownOpusEncoder( void ) { - if( voice.encoder ) + if( voice.goldsrc ) { - opus_custom_encoder_destroy( voice.encoder ); - voice.encoder = NULL; + if( voice.gs_encoder ) + { + opus_encoder_destroy( voice.gs_encoder ); + voice.gs_encoder = NULL; + } } + else + { + if( voice.encoder ) + { + opus_custom_encoder_destroy( voice.encoder ); + voice.encoder = NULL; + } + } +} +/* +========================= +Voice_ShutdownCustomMode + +Cleanup Opus Custom mode +========================= +*/ +static void Voice_ShutdownCustomMode( void ) +{ if( voice.custom_mode ) { opus_custom_mode_destroy( voice.custom_mode ); @@ -145,10 +271,155 @@ static void Voice_ShutdownOpusEncoder( void ) } } +/* +========================= +Voice_InitGoldSrcMode + +Initialize GoldSrc voice mode +========================= +*/ +static qboolean Voice_InitGoldSrcMode( int quality ) +{ + voice.goldsrc = true; + voice.autogain.block_size = 128; + voice.width = sizeof( int16_t ); + voice.samplerate = GS_DEFAULT_SAMPLE_RATE; + voice.frame_size = GS_DEFAULT_FRAME_SIZE; + + if( !Voice_InitOpusDecoder()) + { + Con_Printf( S_ERROR "Can't create GoldSrc decoders, voice chat is disabled.\n" ); + return false; + } + + if( !Voice_InitOpusEncoder( quality )) + { + Con_Printf( S_WARN "Other players will not be able to hear you.\n" ); + return false; + } + + return true; +} + +/* +========================= +Voice_InitOpusCustomMode + +Initialize Opus Custom voice mode +========================= +*/ +static qboolean Voice_InitOpusCustomMode( int quality ) +{ + voice.goldsrc = false; + voice.autogain.block_size = 128; + voice.samplerate = VOICE_OPUS_CUSTOM_SAMPLERATE; + voice.frame_size = VOICE_OPUS_CUSTOM_FRAME_SIZE; + voice.width = sizeof( int16_t ); + + if( !Voice_InitCustomMode() || !Voice_InitOpusDecoder()) + { + Con_Printf( S_ERROR "Can't create Opus Custom decoders, voice chat is disabled.\n" ); + return false; + } + + if( !Voice_InitOpusEncoder( quality )) + { + Con_Printf( S_WARN "Other players will not be able to hear you.\n" ); + return false; + } + + return true; +} + +/* +========================= +Voice_ShutdownGoldSrcMode + +Cleanup GoldSrc mode +========================= +*/ +static void Voice_ShutdownGoldSrcMode( void ) +{ + Voice_ShutdownOpusDecoder(); + Voice_ShutdownOpusEncoder(); +} + +/* +========================= +Voice_ShutdownOpusCustomMode + +Cleanup Opus Custom mode +========================= +*/ +static void Voice_ShutdownOpusCustomMode( void ) +{ + Voice_ShutdownOpusDecoder(); + Voice_ShutdownOpusEncoder(); + Voice_ShutdownCustomMode(); +} + +/* +=============================================================================== + + VOICE PROCESSING + +=============================================================================== +*/ + +/* +========================= +Voice_ApplyGainAdjust + +Apply automatic gain control to voice samples +========================= +*/ +static void Voice_ApplyGainAdjust( int16_t *samples, int count, float scale ) +{ + float gain, modifiedMax; + int average, blockOffset = 0; + + for( ;; ) + { + int i, localMax = 0, localSum = 0; + int blockSize = Q_min( count - blockOffset, voice.autogain.block_size ); + + if( blockSize < 1 ) + break; + + for( i = 0; i < blockSize; ++i ) + { + int sample = samples[blockOffset + i]; + int absSample = abs( sample ); + + if( absSample > localMax ) + localMax = absSample; + + localSum += absSample; + gain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier; + samples[blockOffset + i] = bound( SHRT_MIN, (int)( sample * gain ), SHRT_MAX ); + } + + if( blockOffset % voice.autogain.block_size == 0 ) + { + average = localSum / blockSize; + modifiedMax = average + ( localMax - average ) * voice_avggain.value; + + voice.autogain.current_gain = voice.autogain.next_gain * scale; + voice.autogain.next_gain = Q_min((float)SHRT_MAX / ( modifiedMax > 1 ? modifiedMax : 1 ), voice_maxgain.value ) * scale; + if( blockSize > 1 ) + voice.autogain.gain_multiplier = ( voice.autogain.next_gain - voice.autogain.current_gain ) / ( blockSize - 1 ); + else + voice.autogain.gain_multiplier = 0.0f; + } + blockOffset += blockSize; + } +} + /* ========================= Voice_GetOpusCompressedData +Get compressed voice data for Opus Custom mode ========================= */ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) @@ -158,10 +429,12 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) if( voice.input_file ) { - uint numbytes; - double updateInterval; + uint numbytes; + double updateInterval, curtime = Sys_DoubleTime(); + + updateInterval = curtime - voice.start_time; + voice.start_time = curtime; - updateInterval = cl.mtime[0] - cl.mtime[1]; numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS; numbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos ); numbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos ); @@ -178,26 +451,21 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) { int bytes; -#if 1 if( !voice.input_file ) - { - // adjust gain before encoding, but only for input from voice - Voice_ApplyGainAdjust((opus_int16*)(voice.input_buffer + ofs), voice.frame_size); - } -#endif + Voice_ApplyGainAdjust((int16_t *)( voice.input_buffer + ofs ), voice.frame_size, voice_transmit_scale.value ); - bytes = opus_custom_encode( voice.encoder, (const opus_int16 *)( voice.input_buffer + ofs ), - voice.frame_size, out + size + sizeof( uint16_t ), maxsize ); + bytes = opus_custom_encode( voice.encoder, (const int16_t *)( voice.input_buffer + ofs ), + voice.frame_size, out + size + sizeof( uint16_t ), maxsize ); if( bytes > 0 ) { // write compressed frame size - *((uint16_t *)&out[size]) = bytes; + *((uint16_t *)&out[size] ) = LittleShort( bytes ); size += bytes + sizeof( uint16_t ); maxsize -= bytes + sizeof( uint16_t ); - (*frames)++; + ( *frames )++; } else { @@ -209,10 +477,8 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) if( ofs ) { fs_offset_t remaining = voice.input_buffer_pos - ofs; - // move remaining samples to the beginning of buffer memmove( voice.input_buffer, voice.input_buffer + ofs, remaining ); - voice.input_buffer_pos = remaining; } @@ -222,6 +488,284 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) return size; } +/* +========================= +Voice_GetGSCompressedData + +Get compressed voice data for GoldSrc mode +========================= +*/ +static uint Voice_GetGSCompressedData( byte *out, uint maxsize, uint *frames ) +{ + uint ofs = 0, size = 0; + const uint frame_size_samples = GS_DEFAULT_FRAME_SIZE; + const uint frame_size_bytes = GS_DEFAULT_FRAME_SIZE * voice.width; + static uint16_t sequence = 0; + + if( voice.input_file ) + { + uint numbytes; + double updateInterval, curtime = Sys_DoubleTime(); + + updateInterval = curtime - voice.start_time; + voice.start_time = curtime; + + numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS; + numbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos ); + numbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos ); + + memcpy( voice.input_buffer + voice.input_buffer_pos, voice.input_file->buffer + voice.input_file_pos, numbytes ); + voice.input_buffer_pos += numbytes; + voice.input_file_pos += numbytes; + } + + if( !voice.input_file ) + VoiceCapture_Lock( true ); + + *frames = 0; + + while( voice.input_buffer_pos - ofs >= frame_size_bytes ) + { + int bytes; + int is_silence = 1; + int16_t *samples = (int16_t *)( voice.input_buffer + ofs ); + + for( uint i = 0; i < frame_size_samples; ++i ) + { + if( samples[i] != 0 ) + { + is_silence = 0; + break; + } + } + if( is_silence ) + { + ofs += frame_size_bytes; + continue; + } + + if( !voice.input_file ) + Voice_ApplyGainAdjust( samples, frame_size_samples, voice_transmit_scale.value ); + + bytes = opus_encode( voice.gs_encoder, samples, + frame_size_samples, out + size + 4, maxsize - 4 ); + + if( bytes > 0 ) + { + *(uint16_t *)( out + size ) = LittleShort( bytes ); + *(uint16_t *)( out + size + 2 ) = LittleShort( sequence++ ); + + size += bytes + sizeof( uint32_t ); + maxsize -= bytes + sizeof( uint32_t ); + + ( *frames )++; + } + else + { + Con_Printf( S_ERROR "%s: failed to encode frame: %s\n", __func__, opus_strerror( bytes )); + } + + ofs += frame_size_bytes; + } + + if( ofs ) + { + fs_offset_t remaining = voice.input_buffer_pos - ofs; + memmove( voice.input_buffer, voice.input_buffer + ofs, remaining ); + voice.input_buffer_pos = remaining; + } + + if( !voice.input_file ) + VoiceCapture_Lock( false ); + + return size; +} + +/* +========================= +Voice_ProcessGSData + +Process GoldSrc voice data and return number of samples +========================= +*/ +static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size ) +{ + uint32_t crc_in_packet; + uint32_t crc; + size_t offset; + int16_t pcm[GS_MAX_DECOMPRESSED_SAMPLES]; + size_t output_samples; + uint16_t sample_rate; + uint8_t vpc_type; + uint16_t data_len; + OpusDecoder *decoder; + size_t opus_offset; + int decoded; + size_t silence_samples; + uint16_t frame_size; + + if( !data || ent <= 0 || ent > cl.maxclients ) + return 0; + + crc_in_packet = LittleLong( *(uint32_t *)( data + size - sizeof( uint32_t ))); + crc = CRC32_INIT_VALUE; + CRC32_ProcessBuffer( &crc, data, size - sizeof( uint32_t )); + crc = CRC32_Final( crc ); + + if( crc != crc_in_packet ) + { + Con_Printf( S_WARN "Voice packet CRC32 mismatch\n" ); + return 0; + } + + offset = sizeof( uint64_t ); + output_samples = 0; + + if( offset >= size - sizeof( uint32_t ) || data[offset] != GS_VPC_SETSAMPLERATE ) + { + Con_Printf( S_WARN "Invalid voice packet type: %d\n", data[offset] ); + return 0; + } + offset++; + + if( offset + sizeof( uint32_t ) > size - sizeof( uint32_t )) + return 0; + + sample_rate = LittleShort( *(uint16_t *)( data + offset )); + offset += sizeof( uint16_t ); + + vpc_type = data[offset++]; + data_len = LittleShort( *(uint16_t *)( data + offset )); + offset += sizeof( uint16_t ); + + if( offset + data_len > size - sizeof( uint32_t )) + { + Con_Printf( S_WARN "Voice packet data_len out of bounds\n" ); + return 0; + } + + if( vpc_type == GS_VPC_VDATA_OPUS_PLC ) + { + decoder = voice.gs_decoders[ent - 1]; + if( !decoder ) + { + Con_Printf( S_WARN "No decoder available for entity %d\n", ent ); + return 0; + } + opus_offset = 0; + while( opus_offset + sizeof( uint32_t ) <= data_len ) + { + frame_size = LittleShort( *(uint16_t *)( data + offset + opus_offset )); + opus_offset += sizeof( uint32_t ); + // if frame size is 0, it means silence + if( frame_size == 0 ) + { + if( output_samples + VOICE_DEFAULT_SILENCE_FRAME_SIZE > GS_MAX_DECOMPRESSED_SAMPLES ) + { + Con_Printf( S_WARN "Voice buffer overflow\n" ); + return 0; + } + memset( pcm + output_samples, 0, VOICE_DEFAULT_SILENCE_FRAME_SIZE * sizeof( int16_t )); + output_samples += VOICE_DEFAULT_SILENCE_FRAME_SIZE; + continue; + } + if( frame_size == 0xFFFF ) + { + opus_decoder_ctl( decoder, OPUS_RESET_STATE ); + break; + } + if( opus_offset + frame_size > data_len ) + { + Con_Printf( S_WARN "Opus frame size exceeds data length\n" ); + return 0; + } + decoded = opus_decode( decoder, data + offset + opus_offset, frame_size, + pcm + output_samples, voice.frame_size, 0 ); + if( decoded < 0 ) + { + Con_Printf( S_WARN "Opus decode error: %s\n", opus_strerror( decoded )); + return 0; + } + output_samples += decoded; + opus_offset += frame_size; + } + } + else if( vpc_type == GS_VPC_VDATA_SILENCE ) + { + silence_samples = data_len / 2; + if( silence_samples > GS_MAX_DECOMPRESSED_SAMPLES ) + { + Con_Printf( S_WARN "Silence data too large\n" ); + return 0; + } + memset( pcm, 0, silence_samples * sizeof( int16_t )); + output_samples = silence_samples; + } + else + { + Con_Printf( S_WARN "Unsupported voice data type: %d\n", vpc_type ); + return 0; + } + + if( output_samples > 0 ) + Voice_StartChannel( output_samples, (byte *)pcm, ent ); + + return output_samples; +} + +/* +========================= +Voice_CreateGSVoicePacket + +Create GoldSrc voice packet +========================= +*/ +static uint Voice_CreateGSVoicePacket( byte *out, const byte *voice_data, uint voice_size ) +{ + uint offset = 0; + uint32_t crc; + + if( !out || !voice_data || voice_size == 0 ) + { + Con_Printf( S_WARN "Invalid voice data for packet creation\n" ); + return 0; + } + + if( cls.steamid ) + { + memcpy( out + offset, cls.steamid, 8 ); + } + else + { + memset( out + offset, 0, 8 ); // fallback: 0 + } + + offset += sizeof( uint64_t ); + + out[offset] = GS_VPC_SETSAMPLERATE; + offset++; + + *(uint16_t *)( out + offset ) = LittleShort( GS_DEFAULT_SAMPLE_RATE ); + offset += sizeof( uint16_t ); + + out[offset] = GS_VPC_VDATA_OPUS_PLC; + offset++; + + *(uint16_t *)( out + offset ) = LittleShort( voice_size ); + offset += sizeof( uint16_t ); + + memcpy( out + offset, voice_data, voice_size ); + offset += voice_size; + + crc = CRC32_INIT_VALUE; + CRC32_ProcessBuffer( &crc, out, offset ); + crc = CRC32_Final( crc ); + *(uint32_t *)( out + offset ) = LittleLong( crc ); + offset += sizeof( uint32_t ); + + return offset; +} + /* =============================================================================== @@ -230,58 +774,11 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames ) =============================================================================== */ -/* -========================= -Voice_ApplyGainAdjust - -========================= -*/ -static void Voice_ApplyGainAdjust( int16_t *samples, int count ) -{ - float gain, modifiedMax; - int average, adjustedSample, blockOffset = 0; - - for( ;; ) - { - int i, localMax = 0, localSum = 0; - int blockSize = Q_min( count - ( blockOffset + voice.autogain.block_size ), voice.autogain.block_size ); - - if( blockSize < 1 ) - break; - - for( i = 0; i < blockSize; ++i ) - { - int sample = samples[blockOffset + i]; - int absSample = abs( sample ); - - if( absSample > localMax ) - localMax = absSample; - - localSum += absSample; - - gain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier; - adjustedSample = Q_min( SHRT_MAX, Q_max(( int )( sample * gain ), SHRT_MIN )); - samples[blockOffset + i] = adjustedSample; - } - - if( blockOffset % voice.autogain.block_size == 0 ) - { - average = localSum / blockSize; - modifiedMax = average + ( localMax - average ) * voice_avggain.value; - - voice.autogain.current_gain = voice.autogain.next_gain * voice_scale.value; - voice.autogain.next_gain = Q_min( (float)SHRT_MAX / modifiedMax, voice_maxgain.value ) * voice_scale.value; - voice.autogain.gain_multiplier = ( voice.autogain.next_gain - voice.autogain.current_gain ) / ( voice.autogain.block_size - 1 ); - } - blockOffset += blockSize; - } -} - /* ========================= Voice_Status -Notify user dll aboit voice transmission +Notify user dll about voice transmission ========================= */ static void Voice_Status( int entindex, qboolean bTalking ) @@ -319,7 +816,7 @@ Sends notification to user dll and zeroes timeouts for this client ========================= */ -void Voice_StatusAck( voice_status_t *status, int playerIndex ) +static void Voice_StatusAck( voice_status_t *status, int playerIndex ) { if( !status->talking_ack ) Voice_Status( playerIndex, true ); @@ -332,6 +829,7 @@ void Voice_StatusAck( voice_status_t *status, int playerIndex ) ========================= Voice_IsRecording +Check if voice is currently recording ========================= */ qboolean Voice_IsRecording( void ) @@ -343,6 +841,7 @@ qboolean Voice_IsRecording( void ) ========================= Voice_RecordStop +Stop voice recording ========================= */ void Voice_RecordStop( void ) @@ -366,21 +865,25 @@ void Voice_RecordStop( void ) ========================= Voice_RecordStart +Start voice recording ========================= */ void Voice_RecordStart( void ) { Voice_RecordStop(); + if( !voice.initialized ) + return; + if( voice_inputfromfile.value ) { voice.input_file = FS_LoadSound( "voice_input.wav", NULL, 0 ); if( voice.input_file ) { - Sound_Process( &voice.input_file, voice.samplerate, voice.width, SOUND_RESAMPLE ); + Sound_Process( &voice.input_file, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, SOUND_RESAMPLE ); voice.input_file_pos = 0; - + voice.start_time = Sys_DoubleTime(); voice.is_recording = true; } @@ -391,10 +894,10 @@ void Voice_RecordStart( void ) } } - if( !Voice_IsRecording() ) + if( !Voice_IsRecording( ) && voice.device_opened ) voice.is_recording = VoiceCapture_Activate( true ); - if( Voice_IsRecording() ) + if( Voice_IsRecording()) Voice_Status( VOICE_LOCALCLIENT_INDEX, true ); } @@ -418,14 +921,14 @@ void Voice_Disconnect( void ) voice.local.talking_ack = false; } - for( i = 0; i < MAX_CLIENTS; i++ ) - { - if( voice.players_status[i].talking_ack ) - { - Voice_Status( i, false ); - voice.players_status[i].talking_ack = false; - } - } + for( i = 1; i <= MAX_CLIENTS; i++ ) + Voice_Status( i, false ); + + VoiceCapture_Shutdown(); + voice.device_opened = false; + + + Voice_ShutdownOpusDecoder(); } /* @@ -438,7 +941,33 @@ Feed the decoded data to engine sound subsystem static void Voice_StartChannel( uint samples, byte *data, int entnum ) { SND_ForceInitMouth( entnum ); - S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, 255 ); + S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 )); + Voice_Status( entnum, true ); +} + +/* +========================= +Voice_StopChannel + +Called by mixer when channel idles +========================= +*/ +void Voice_StopChannel( int entnum ) +{ + Voice_Status( entnum, false ); +} + + +/* +========================= +Voice_AddIncomingData + +Received encoded voice data, decode it +========================= +*/ +void Voice_LoopbackAck( void ) +{ + Voice_StatusAck( &voice.local, VOICE_LOOPBACK_INDEX ); } /* @@ -450,38 +979,52 @@ Received encoded voice data, decode it */ void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ) { + const int playernum = ent - 1; int samples = 0; int ofs = 0; + voice_status_t *status = NULL; - if( !voice.decoder ) + if( !voice.initialized || !voice_enable.value ) return; - // decode frame by frame - for( ;; ) + if( voice.goldsrc ) { - int frame_samples; - uint16_t compressed_size; - - // no compressed size mark - if( ofs + sizeof( uint16_t ) > size ) - break; - - compressed_size = *(const uint16_t *)(data + ofs); - ofs += sizeof( uint16_t ); - - // no frame data - if( ofs + compressed_size > size ) - break; - - frame_samples = opus_custom_decode( voice.decoder, data + ofs, compressed_size, - (opus_int16*)voice.decompress_buffer + samples, voice.frame_size ); - - ofs += compressed_size; - samples += frame_samples; + // Voice_ProcessGSData handles Voice_StartChannel internally + Voice_ProcessGSData( ent, (const uint8_t *)data, size ); } + else + { + // Validate player index and decoder + if( playernum < 0 || playernum >= cl.maxclients || !voice.decoders[playernum] ) + return; - if( samples > 0 ) - Voice_StartChannel( samples, voice.decompress_buffer, ent ); + // decode frame by frame + for( ;; ) + { + int frame_samples; + uint16_t compressed_size; + + // no compressed size mark + if( ofs + sizeof( uint16_t ) > size ) + break; + + compressed_size = *(const uint16_t *)( data + ofs ); + ofs += sizeof( uint16_t ); + + // no frame data + if( ofs + compressed_size > size ) + break; + + frame_samples = opus_custom_decode( voice.decoders[playernum], data + ofs, compressed_size, + (int16_t *)voice.decompress_buffer + samples, voice.frame_size ); + + ofs += compressed_size; + samples += frame_samples; + } + + if( samples > 0 ) + Voice_StartChannel( samples, voice.decompress_buffer, ent ); + } } /* @@ -493,20 +1036,39 @@ Encode our voice data and send it to server */ void CL_AddVoiceToDatagram( void ) { - uint size, frames = 0; + byte buffer[VOICE_MAX_DATA_SIZE]; + uint size, frames; - if( cls.state != ca_active || !Voice_IsRecording() || !voice.encoder ) + if( cls.state != ca_active || !voice.device_opened || !Voice_IsRecording()) return; - - size = Voice_GetOpusCompressedData( voice.output_buffer, sizeof( voice.output_buffer ), &frames ); + if( voice.goldsrc ) + { + if( !voice.gs_encoder ) + return; + + size = Voice_GetGSCompressedData( buffer, sizeof( buffer ), &frames ); + if( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 ) + { + uint packet_size = Voice_CreateGSVoicePacket( voice.compress_buffer, buffer, size ); + MSG_BeginClientCmd( &cls.datagram, clc_voicedata ); + MSG_WriteShort( &cls.datagram, packet_size ); + MSG_WriteBytes( &cls.datagram, voice.compress_buffer, packet_size ); + } + return; + } + + if( !voice.encoder ) + return; + + size = Voice_GetOpusCompressedData( voice.compress_buffer, sizeof( voice.compress_buffer ), &frames ); if( size > 0 && MSG_GetNumBytesLeft( &cls.datagram ) >= size + 32 ) { MSG_BeginClientCmd( &cls.datagram, clc_voicedata ); MSG_WriteByte( &cls.datagram, voice_loopback.value != 0 ); MSG_WriteByte( &cls.datagram, frames ); MSG_WriteShort( &cls.datagram, size ); - MSG_WriteBytes( &cls.datagram, voice.output_buffer, size ); + MSG_WriteBytes( &cls.datagram, voice.compress_buffer, size ); } } @@ -522,6 +1084,7 @@ void Voice_RegisterCvars( void ) Cvar_RegisterVariable( &voice_enable ); Cvar_RegisterVariable( &voice_loopback ); Cvar_RegisterVariable( &voice_scale ); + Cvar_RegisterVariable( &voice_transmit_scale ); Cvar_RegisterVariable( &voice_avggain ); Cvar_RegisterVariable( &voice_maxgain ); Cvar_RegisterVariable( &voice_inputfromfile ); @@ -539,44 +1102,64 @@ static void Voice_Shutdown( void ) int i; Voice_RecordStop(); - Voice_ShutdownOpusEncoder(); - Voice_ShutdownOpusDecoder(); + + if( voice.goldsrc ) + Voice_ShutdownGoldSrcMode(); + else + Voice_ShutdownOpusCustomMode(); + VoiceCapture_Shutdown(); if( voice.local.talking_ack ) Voice_Status( VOICE_LOOPBACK_INDEX, false ); - for( i = 0; i < MAX_CLIENTS; i++ ) - { - if( voice.players_status[i].talking_ack ) - Voice_Status( i, false ); - } + for( i = 1; i <= MAX_CLIENTS; i++ ) + Voice_Status( i, false ); - memset( &voice, 0, sizeof( voice )); + voice.initialized = false; + voice.is_recording = false; + voice.device_opened = false; + voice.goldsrc = false; + voice.start_time = 0.0; + voice.samplerate = 0; + voice.frame_size = 0; + voice.width = 0; + + voice.input_buffer_pos = 0; + voice.input_file_pos = 0; + memset( voice.input_buffer, 0, sizeof( voice.input_buffer )); + memset( voice.compress_buffer, 0, sizeof( voice.compress_buffer )); + memset( voice.decompress_buffer, 0, sizeof( voice.decompress_buffer )); + memset( &voice.local, 0, sizeof( voice.local )); + memset( &voice.autogain, 0, sizeof( voice.autogain )); } /* ========================= Voice_Idle -Run timeout for all clients +Run timeout for clients ========================= */ void Voice_Idle( double frametime ) { int i; - if( FBitSet( voice_enable.flags, FCVAR_CHANGED ) && !voice_enable.value ) + if( FBitSet( voice_enable.flags, FCVAR_CHANGED )) { - Voice_Shutdown(); - return; + ClearBits( voice_enable.flags, FCVAR_CHANGED ); + + if( voice_enable.value ) + { + if( cls.state == ca_active ) + Voice_Init( voice.codec, voice.quality, false ); + } + else + Voice_Shutdown(); } // update local player status first Voice_StatusTimeout( &voice.local, VOICE_LOOPBACK_INDEX, frametime ); - - for( i = 0; i < MAX_CLIENTS; i++ ) - Voice_StatusTimeout( &voice.players_status[i], i, frametime ); } /* @@ -586,46 +1169,46 @@ Voice_Init Initialize the voice subsystem ========================= */ -qboolean Voice_Init( const char *pszCodecName, int quality ) +qboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit ) { + Q_strncpy( voice.codec, pszCodecName, sizeof( voice.codec )); + voice.quality = quality; + if( !voice_enable.value ) return false; - if( Q_strcmp( pszCodecName, VOICE_OPUS_CUSTOM_CODEC )) + if( preinit ) + return true; + + if( Voice_IsGoldSrcMode( pszCodecName )) { - Con_Printf( S_ERROR "Server requested unsupported codec: %s\n", pszCodecName ); - return false; + if( !Voice_InitGoldSrcMode( quality )) + { + Voice_Shutdown(); + return false; + } } - - // reinitialize only if codec parameters are different - if( Q_strcmp( voice.codec, pszCodecName ) && voice.quality != quality ) - Voice_Shutdown(); - - voice.autogain.block_size = 128; - - if( !Voice_InitOpusDecoder( )) + else if( Voice_IsOpusCustomMode( pszCodecName )) { - // no reason to init encoder and open audio device - // if we can't hear other players - Con_Printf( S_ERROR "Voice chat disabled.\n" ); + if( !Voice_InitOpusCustomMode( quality )) + { + Voice_Shutdown(); + return false; + } + } + else + { + // unsupported codec + Con_Printf( S_WARN "Server requested unsupported voice codec: %s\n", pszCodecName ); Voice_Shutdown(); return false; } - // we can hear others players, so it's fine to fail now - voice.initialized = true; - Q_strncpy( voice.codec, pszCodecName, sizeof( voice.codec )); + voice.device_opened = VoiceCapture_Init(); - if( !Voice_InitOpusEncoder( quality )) - { - Con_Printf( S_WARN "Other players will not be able to hear you.\n" ); - return false; - } - - voice.quality = quality; - - if( !VoiceCapture_Init( )) + if( !voice.device_opened ) Con_Printf( S_WARN "No microphone is available.\n" ); + voice.initialized = true; return true; } diff --git a/engine/client/voice.h b/engine/client/voice.h index f1d878cb..832d2d6a 100644 --- a/engine/client/voice.h +++ b/engine/client/voice.h @@ -24,44 +24,79 @@ GNU General Public License for more details. typedef struct OpusCustomEncoder OpusCustomEncoder; typedef struct OpusCustomDecoder OpusCustomDecoder; typedef struct OpusCustomMode OpusCustomMode; +typedef struct OpusEncoder OpusEncoder; +typedef struct OpusDecoder OpusDecoder; -#define VOICE_LOOPBACK_INDEX (-2) -#define VOICE_LOCALCLIENT_INDEX (-1) +#define VOICE_LOOPBACK_INDEX ( -2 ) +#define VOICE_LOCALCLIENT_INDEX ( -1 ) -#define VOICE_PCM_CHANNELS 1 // always mono +#define VOICE_PCM_CHANNELS 1 // always mono +#define VOICE_MAX_DATA_SIZE 8192 +#define VOICE_MAX_GS_DATA_SIZE 4096 // never change these parameters when using opuscustom -#define VOICE_OPUS_CUSTOM_SAMPLERATE SOUND_44k +#define VOICE_OPUS_CUSTOM_SAMPLERATE 44100 // must follow opus custom requirements // also be divisible with MAX_RAW_SAMPLES #define VOICE_OPUS_CUSTOM_FRAME_SIZE 1024 -#define VOICE_OPUS_CUSTOM_CODEC "opus_custom_44k_512" +#define VOICE_OPUS_CUSTOM_CODEC "opus_custom_44k_512" // a1ba: do not change, we don't have any re-encoding support now -#define VOICE_DEFAULT_CODEC VOICE_OPUS_CUSTOM_CODEC +#define VOICE_DEFAULT_CODEC VOICE_OPUS_CUSTOM_CODEC +#define VOICE_DEFAULT_SILENCE_FRAME_SIZE 160 + +// GoldSrc voice configuration +#define GS_MAX_DECOMPRESSED_SAMPLES 32768 +#define GS_DEFAULT_SAMPLE_RATE 24000 +#define GS_DEFAULT_FRAME_SIZE 480 + +// VPC (Voice Packet Control) types +enum gs_vpc_type +{ + GS_VPC_VDATA_SILENCE = 0, + GS_VPC_VDATA_MILES = 1, + GS_VPC_VDATA_SPEEX = 2, + GS_VPC_VDATA_RAW = 3, + GS_VPC_VDATA_SILK = 4, + GS_VPC_VDATA_OPUS_PLC = 6, + GS_VPC_SETSAMPLERATE = 11, + GS_VPC_UNKNOWN = 10 +}; typedef struct voice_status_s { qboolean talking_ack; - double talking_timeout; + double talking_timeout; } voice_status_t; +typedef struct voice_autogain_s +{ + int block_size; + float current_gain; + float next_gain; + float gain_multiplier; +} voice_autogain_t; + typedef struct voice_state_s { - string codec; - int quality; + string codec; + int quality; + qboolean goldsrc; qboolean initialized; qboolean is_recording; - double start_time; + qboolean device_opened; + double start_time; - voice_status_t local; - voice_status_t players_status[MAX_CLIENTS]; + voice_status_t local; // opus stuff OpusCustomMode *custom_mode; OpusCustomEncoder *encoder; - OpusCustomDecoder *decoder; + OpusCustomDecoder *decoders[MAX_CLIENTS]; + + OpusEncoder *gs_encoder; + OpusDecoder *gs_decoders[MAX_CLIENTS]; // audio info uint width; @@ -70,35 +105,31 @@ typedef struct voice_state_s // buffers byte input_buffer[MAX_RAW_SAMPLES]; - byte output_buffer[MAX_RAW_SAMPLES]; + byte compress_buffer[MAX_RAW_SAMPLES]; byte decompress_buffer[MAX_RAW_SAMPLES]; - fs_offset_t input_buffer_pos; // in bytes + fs_offset_t input_buffer_pos; // in bytes // input from file - wavdata_t *input_file; - fs_offset_t input_file_pos; // in bytes + wavdata_t *input_file; + fs_offset_t input_file_pos; // in bytes - // automatic gain control - struct { - int block_size; - float current_gain; - float next_gain; - float gain_multiplier; - } autogain; + voice_autogain_t autogain; } voice_state_t; extern voice_state_t voice; -void CL_AddVoiceToDatagram( void ); +extern convar_t voice_loopback; +void CL_AddVoiceToDatagram( void ); void Voice_RegisterCvars( void ); -qboolean Voice_Init( const char *pszCodecName, int quality ); +qboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit ); void Voice_Idle( double frametime ); qboolean Voice_IsRecording( void ); void Voice_RecordStop( void ); void Voice_RecordStart( void ); void Voice_Disconnect( void ); void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames ); -void Voice_StatusAck( voice_status_t *status, int playerIndex ); +void Voice_StopChannel( int entnum ); +void Voice_LoopbackAck( void ); // sends VOICE_LOOPBACK_INDEX to client, gets disabled on timeout #endif // VOICE_H diff --git a/engine/client/vox.h b/engine/client/vox.h index 56967959..7432801a 100644 --- a/engine/client/vox.h +++ b/engine/client/vox.h @@ -16,22 +16,20 @@ GNU General Public License for more details. #ifndef VOX_H #define VOX_H -#define CVOXWORDMAX 64 -#define CVOXFILESENTENCEMAX 4096 - -#define SENTENCE_INDEX -99999 // unique sentence index +#define CVOXWORDMAX 64 +#define SENTENCE_INDEX -99999 // unique sentence index typedef struct voxword_s { - int volume; // increase percent, ie: 125 = 125% increase - int pitch; // pitch shift up percent - int start; // offset start of wave percent - int end; // offset end of wave percent - int cbtrim; // end of wave after being trimmed to 'end' - int fKeepCached; // 1 if this word was already in cache before sentence referenced it - int samplefrac; // if pitch shifting, this is position into wav * 256 - int timecompress; // % of wave to skip during playback (causes no pitch shift) - sfx_t *sfx; // name and cache pointer + int volume; // increase percent, ie: 125 = 125% increase + int pitch; // pitch shift up percent + int start; // offset start of wave percent + int end; // offset end of wave percent + int cbtrim; // end of wave after being trimmed to 'end' + int fKeepCached; // 1 if this word was already in cache before sentence referenced it + int samplefrac; // if pitch shifting, this is position into wav * 256 + int timecompress; // % of wave to skip during playback (causes no pitch shift) + sfx_t *sfx; // name and cache pointer } voxword_t; struct channel_s; diff --git a/engine/common/base_cmd.c b/engine/common/base_cmd.c index efdf718e..714cdd1d 100644 --- a/engine/common/base_cmd.c +++ b/engine/common/base_cmd.c @@ -17,9 +17,22 @@ GNU General Public License for more details. #include "base_cmd.h" #include "cdll_int.h" -// TODO: use another hash function, as COM_HashKey depends on string length -#define HASH_SIZE 128 // 128 * 4 * 4 == 2048 bytes +#define HASH_SIZE 64 // 64 * 4 * 4 == 1024 bytes + +typedef struct base_command_hashmap_s base_command_hashmap_t; + +struct base_command_hashmap_s +{ + base_command_t *basecmd; // base command: cvar, alias or command + base_command_hashmap_t *next; + base_command_type_e type; // type for faster searching + char name[]; // key for searching +}; + static base_command_hashmap_t *hashed_cmds[HASH_SIZE]; +static poolhandle_t basecmd_pool; + +#define BaseCmd_HashKey( x ) COM_HashKey( name, HASH_SIZE ) /* ============ @@ -28,13 +41,29 @@ BaseCmd_FindInBucket Find base command in bucket ============ */ -base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ) +static base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ) { - base_command_hashmap_t *i = bucket; - for( ; i && ( i->type != type || Q_stricmp( name, i->name ) ); // filter out - i = i->next ); + base_command_hashmap_t *i; - return i; + for( i = bucket; i != NULL; i = i->next ) + { + int cmp; + + if( i->type != type ) + continue; + + cmp = Q_stricmp( i->name, name ); + + if( cmp < 0 ) + continue; + + if( cmp > 0 ) + break; + + return i; + } + + return NULL; } /* @@ -44,9 +73,9 @@ BaseCmd_GetBucket Get bucket which contain basecmd by given name ============ */ -base_command_hashmap_t *BaseCmd_GetBucket( const char *name ) +static base_command_hashmap_t *BaseCmd_GetBucket( const char *name ) { - return hashed_cmds[ COM_HashKey( name, HASH_SIZE ) ]; + return hashed_cmds[ BaseCmd_HashKey( name ) ]; } /* @@ -73,35 +102,40 @@ BaseCmd_Find Find every type of base command and write into arguments ============ */ -void BaseCmd_FindAll(const char *name, base_command_t **cmd, base_command_t **alias, base_command_t **cvar) +void BaseCmd_FindAll( const char *name, cmd_t **cmd, cmdalias_t **alias, convar_t **cvar ) { base_command_hashmap_t *base = BaseCmd_GetBucket( name ); base_command_hashmap_t *i = base; - ASSERT( cmd && alias && cvar ); - - *cmd = *alias = *cvar = NULL; + *cmd = NULL; + *alias = NULL; + *cvar = NULL; for( ; i; i = i->next ) { - if( !Q_stricmp( i->name, name ) ) + int cmp = Q_stricmp( i->name, name ); + + if( cmp < 0 ) + continue; + + if( cmp > 0 ) + break; + + switch( i->type ) { - switch( i->type ) - { - case HM_CMD: - *cmd = i->basecmd; - break; - case HM_CMDALIAS: - *alias = i->basecmd; - break; - case HM_CVAR: - *cvar = i->basecmd; - break; - default: break; - } + case HM_CMD: + *cmd = (cmd_t *)i->basecmd; + break; + case HM_CMDALIAS: + *alias = (cmdalias_t *)i->basecmd; + break; + case HM_CVAR: + *cvar = (convar_t *)i->basecmd; + break; + default: + break; } } - } /* @@ -113,41 +147,24 @@ Add new typed base command to hashmap */ void BaseCmd_Insert( base_command_type_e type, base_command_t *basecmd, const char *name ) { - uint hash = COM_HashKey( name, HASH_SIZE ); - base_command_hashmap_t *elem; + base_command_hashmap_t *elem, *cur, *find; + uint hash = BaseCmd_HashKey( name ); + size_t len = Q_strlen( name ); - elem = Z_Malloc( sizeof( base_command_hashmap_t ) ); + elem = Mem_Malloc( basecmd_pool, sizeof( base_command_hashmap_t ) + len + 1 ); elem->basecmd = basecmd; elem->type = type; - elem->name = name; - elem->next = hashed_cmds[hash]; - hashed_cmds[hash] = elem; -} + Q_strncpy( elem->name, name, len + 1 ); -/* -============ -BaseCmd_Replace + // link the variable in alphanumerical order + for( cur = NULL, find = hashed_cmds[hash]; + find && Q_stricmp( find->name, elem->name ) < 0; + cur = find, find = find->next ); -Used in case, when basecmd has been registered, but gamedll wants to register it's own -============ -*/ -qboolean BaseCmd_Replace( base_command_type_e type, base_command_t *basecmd, const char *name ) -{ - base_command_hashmap_t *i = BaseCmd_GetBucket( name ); + if( cur ) cur->next = elem; + else hashed_cmds[hash] = elem; - for( ; i && ( i->type != type || Q_stricmp( name, i->name ) ) ; // filter out - i = i->next ); - - if( !i ) - { - Con_Reportf( S_ERROR "BaseCmd_Replace: couldn't find %s\n", name); - return false; - } - - i->basecmd = basecmd; - i->name = name; // may be freed after - - return true; + elem->next = find; } /* @@ -159,16 +176,30 @@ Remove base command from hashmap */ void BaseCmd_Remove( base_command_type_e type, const char *name ) { - uint hash = COM_HashKey( name, HASH_SIZE ); + uint hash = BaseCmd_HashKey( name ); base_command_hashmap_t *i, *prev; - for( prev = NULL, i = hashed_cmds[hash]; i && - ( Q_strcmp( i->name, name ) || i->type != type); // filter out - prev = i, i = i->next ); + for( prev = NULL, i = hashed_cmds[hash]; i != NULL; prev = i, i = i->next ) + { + int cmp; + + if( i->type != type ) + continue; + + cmp = Q_stricmp( i->name, name ); + + if( cmp < 0 ) + continue; + + if( cmp > 0 ) + i = NULL; + + break; + } if( !i ) { - Con_Reportf( S_ERROR "Couldn't find %s in buckets\n", name ); + Con_Reportf( S_ERROR "%s: Couldn't find %s in buckets\n", __func__, name ); return; } @@ -189,9 +220,15 @@ initialize base command hashmap system */ void BaseCmd_Init( void ) { + basecmd_pool = Mem_AllocPool( "BaseCmd" ); memset( hashed_cmds, 0, sizeof( hashed_cmds ) ); } +void BaseCmd_Shutdown( void ) +{ + Mem_FreePool( &basecmd_pool ); +} + /* ============ BaseCmd_Stats_f @@ -221,23 +258,27 @@ void BaseCmd_Stats_f( void ) if( len > maxsize ) maxsize = len; + } - Con_Printf( "Base command stats:\n"); - Con_Printf( "Bucket minimal length: %d\n", minsize ); - Con_Printf( "Bucket maximum length: %d\n", maxsize ); - Con_Printf( "Empty buckets: %d\n", empty ); + Con_Printf( "min length: %d, max length: %d, empty: %d\n", minsize, maxsize, empty ); } +typedef struct +{ + qboolean valid; + int lookups; +} basecmd_test_stats_t; + static void BaseCmd_CheckCvars( const char *key, const char *value, const void *unused, void *ptr ) { - base_command_t *v = BaseCmd_Find( HM_CVAR, key ); - qboolean *invalid = ptr; + basecmd_test_stats_t *stats = ptr; - if( !v ) + stats->lookups++; + if( !BaseCmd_Find( HM_CVAR, key )) { Con_Printf( "Cvar %s is missing in basecmd\n", key ); - *invalid = true; + stats->valid = false; } } @@ -250,38 +291,51 @@ testing order matches cbuf execute */ void BaseCmd_Test_f( void ) { - void *cmd; - cmdalias_t *a; - qboolean invalid = false; + basecmd_test_stats_t stats; + double start, end, dt; + int i; - // Cmd_LookupCmds don't allows to check alias, so just iterate - for( a = Cmd_AliasGetList(); a; a = a->next ) + stats.valid = true; + stats.lookups = 0; + + start = Sys_DoubleTime() * 1000; + + for( i = 0; i < 1000; i++ ) { - base_command_t *v = BaseCmd_Find( HM_CMDALIAS, a->name ); + cmdalias_t *a; + void *cmd; - if( !v ) + // Cmd_LookupCmds don't allows to check alias, so just iterate + for( a = Cmd_AliasGetList(); a; a = a->next, stats.lookups++ ) { - Con_Printf( "Alias %s is missing in basecmd\n", a->name ); - invalid = true; + if( !BaseCmd_Find( HM_CMDALIAS, a->name )) + { + Con_Printf( "Alias %s is missing in basecmd\n", a->name ); + stats.valid = false; + } } + + for( cmd = Cmd_GetFirstFunctionHandle(); cmd; + cmd = Cmd_GetNextFunctionHandle( cmd ), stats.lookups++ ) + { + if( !BaseCmd_Find( HM_CMD, Cmd_GetName( cmd ))) + { + Con_Printf( "Command %s is missing in basecmd\n", Cmd_GetName( cmd )); + stats.valid = false; + } + } + + Cvar_LookupVars( 0, NULL, &stats.valid, (setpair_t)BaseCmd_CheckCvars ); } - for( cmd = Cmd_GetFirstFunctionHandle(); cmd; - cmd = Cmd_GetNextFunctionHandle( cmd ) ) - { - base_command_t *v = BaseCmd_Find( HM_CMD, Cmd_GetName( cmd ) ); + end = Sys_DoubleTime() * 1000; - if( !v ) - { - Con_Printf( "Command %s is missing in basecmd\n", Cmd_GetName( cmd ) ); - invalid = true; - } - } + dt = end - start; - Cvar_LookupVars( 0, NULL, &invalid, (setpair_t)BaseCmd_CheckCvars ); - - if( !invalid ) - { + if( !stats.valid ) Con_Printf( "BaseCmd is valid\n" ); - } + + Con_Printf( "Test took %.3f ms, %d lookups, %.3f us/lookup\n", dt, stats.lookups, dt / stats.lookups * 1000 ); + + BaseCmd_Stats_f(); } diff --git a/engine/common/base_cmd.h b/engine/common/base_cmd.h index f507c9e1..8faf06c9 100644 --- a/engine/common/base_cmd.h +++ b/engine/common/base_cmd.h @@ -17,12 +17,13 @@ GNU General Public License for more details. #ifndef BASE_CMD_H #define BASE_CMD_H -// TODO: Find cases when command hashmap works incorrect -// and maybe disable it #define XASH_HASHED_VARS #ifdef XASH_HASHED_VARS +#include "common.h" +#include "cdll_int.h" + typedef enum base_command_type { HM_DONTCARE = 0, @@ -33,23 +34,11 @@ typedef enum base_command_type typedef void base_command_t; -typedef struct base_command_hashmap_s -{ - base_command_t *basecmd; // base command: cvar, alias or command - const char *name; // key for searching - base_command_type_e type; // type for faster searching - struct base_command_hashmap_s *next; -} base_command_hashmap_t; - - void BaseCmd_Init( void ); -base_command_hashmap_t *BaseCmd_GetBucket( const char *name ); -base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ); +void BaseCmd_Shutdown( void ); base_command_t *BaseCmd_Find( base_command_type_e type, const char *name ); -void BaseCmd_FindAll( const char *name, - base_command_t **cmd, base_command_t **alias, base_command_t **cvar ); +void BaseCmd_FindAll( const char *name, cmd_t **cmd, cmdalias_t **alias, convar_t **cvar ); void BaseCmd_Insert ( base_command_type_e type, base_command_t *basecmd, const char *name ); -qboolean BaseCmd_Replace( base_command_type_e type, base_command_t *basecmd, const char *name ); // only if same name void BaseCmd_Remove ( base_command_type_e type, const char *name ); void BaseCmd_Stats_f( void ); // to be registered later void BaseCmd_Test_f( void ); // to be registered later diff --git a/engine/common/cfgscript.c b/engine/common/cfgscript.c index 56bd8006..b42a598e 100644 --- a/engine/common/cfgscript.c +++ b/engine/common/cfgscript.c @@ -25,7 +25,7 @@ typedef enum T_COUNT } cvartype_t; -const char *cvartypes[] = { NULL, "BOOL", "NUMBER", "LIST", "STRING" }; +static const char *const cvartypes[] = { NULL, "BOOL", "NUMBER", "LIST", "STRING" }; typedef struct parserstate_s { @@ -52,7 +52,7 @@ CSCR_ExpectString Return true if next token is pExpext and skip it =================== */ -qboolean CSCR_ExpectString( parserstate_t *ps, const char *pExpect, qboolean skip, qboolean error ) +static qboolean CSCR_ExpectString( parserstate_t *ps, const char *pExpect, qboolean skip, qboolean error ) { char *tmp = COM_ParseFile( ps->buf, ps->token, sizeof( ps->token )); @@ -75,7 +75,7 @@ CSCR_ParseType Determine script variable type =================== */ -cvartype_t CSCR_ParseType( parserstate_t *ps ) +static cvartype_t CSCR_ParseType( parserstate_t *ps ) { int i; @@ -96,7 +96,7 @@ cvartype_t CSCR_ParseType( parserstate_t *ps ) CSCR_ParseSingleCvar ========================= */ -qboolean CSCR_ParseSingleCvar( parserstate_t *ps, scrvardef_t *result ) +static qboolean CSCR_ParseSingleCvar( parserstate_t *ps, scrvardef_t *result ) { // read the name ps->buf = COM_ParseFile( ps->buf, result->name, sizeof( result->name )); @@ -170,7 +170,7 @@ CSCR_ParseHeader Check version and seek to first cvar name ====================== */ -qboolean CSCR_ParseHeader( parserstate_t *ps ) +static qboolean CSCR_ParseHeader( parserstate_t *ps ) { if( !CSCR_ExpectString( ps, "VERSION", false, true )) return false; diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 9eeb1c68..f13550d6 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -24,19 +24,29 @@ GNU General Public License for more details. typedef struct { - byte *data; - int cursize; - int maxsize; + byte *const data; + const int maxsize; + int cursize; } cmdbuf_t; -qboolean cmd_wait; -cmdbuf_t cmd_text, filteredcmd_text; -byte cmd_text_buf[MAX_CMD_BUFFER]; -byte filteredcmd_text_buf[MAX_CMD_BUFFER]; -cmdalias_t *cmd_alias; -uint cmd_condition; -int cmd_condlevel; +static qboolean cmd_wait; +static byte cmd_text_buf[MAX_CMD_BUFFER]; +static byte filteredcmd_text_buf[MAX_CMD_BUFFER]; +static cmdbuf_t cmd_text = +{ + .data = cmd_text_buf, + .maxsize = ARRAYSIZE( cmd_text_buf ), +}; +static cmdbuf_t filteredcmd_text = +{ + .data = filteredcmd_text_buf, + .maxsize = ARRAYSIZE( filteredcmd_text_buf ), +}; +static cmdalias_t *cmd_alias; +static uint cmd_condition; +static int cmd_condlevel; static qboolean cmd_currentCommandIsPrivileged; +static poolhandle_t cmd_pool; static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPrivileged ); @@ -48,20 +58,6 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr ============================================================================= */ -/* -============ -Cbuf_Init -============ -*/ -void Cbuf_Init( void ) -{ - cmd_text.data = cmd_text_buf; - filteredcmd_text.data = filteredcmd_text_buf; - - filteredcmd_text.maxsize = cmd_text.maxsize = MAX_CMD_BUFFER; - filteredcmd_text.cursize = cmd_text.cursize = 0; -} - /* ============ Cbuf_Clear @@ -79,14 +75,14 @@ void Cbuf_Clear( void ) Cbuf_GetSpace ============ */ -void *Cbuf_GetSpace( cmdbuf_t *buf, int length ) +static void *Cbuf_GetSpace( cmdbuf_t *buf, int length ) { void *data; if(( buf->cursize + length ) > buf->maxsize ) { buf->cursize = 0; - Host_Error( "Cbuf_GetSpace: overflow\n" ); + Host_Error( "%s: overflow\n", __func__ ); } data = buf->data + buf->cursize; @@ -120,6 +116,18 @@ void Cbuf_AddText( const char *text ) Cbuf_AddTextToBuffer( &cmd_text, text ); } +void Cbuf_AddTextf( const char *fmt, ... ) +{ + va_list va; + char buf[MAX_VA_STRING]; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + Cbuf_AddText( buf ); +} + /* ============ Cbuf_AddFilteredText @@ -135,28 +143,33 @@ void Cbuf_AddFilteredText( const char *text ) Cbuf_InsertText Adds command text immediately after the current command -Adds a \n to the text ============ */ -static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text ) +static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text, size_t len, size_t requested_len ) { - int l = Q_strlen( text ); - - if(( buf->cursize + l ) >= buf->maxsize ) + if(( buf->cursize + requested_len ) >= buf->maxsize ) { - Con_Reportf( S_WARN "Cbuf_InsertText: overflow\n" ); + Con_Reportf( S_WARN "%s: overflow\n", __func__ ); } else { - memmove( buf->data + l, buf->data, buf->cursize ); - memcpy( buf->data, text, l ); - buf->cursize += l; + memmove( buf->data + len, buf->data, buf->cursize ); + memcpy( buf->data, text, len ); + buf->cursize += len; } } +void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len ) +{ + // sometimes we need to insert more data than we have + // but also prevent overflow + Cbuf_InsertTextToBuffer( &cmd_text, text, len, requested_len ); +} + void Cbuf_InsertText( const char *text ) { - Cbuf_InsertTextToBuffer( &cmd_text, text ); + size_t l = Q_strlen( text ); + Cbuf_InsertTextToBuffer( &cmd_text, text, l, l ); } /* @@ -164,7 +177,7 @@ void Cbuf_InsertText( const char *text ) Cbuf_Execute ============ */ -void Cbuf_ExecuteCommandsFromBuffer( cmdbuf_t *buf, qboolean isPrivileged, int cmdsToExecute ) +static void Cbuf_ExecuteCommandsFromBuffer( cmdbuf_t *buf, qboolean isPrivileged, int cmdsToExecute ) { char *text; char line[MAX_CMD_LINE]; @@ -212,7 +225,7 @@ void Cbuf_ExecuteCommandsFromBuffer( cmdbuf_t *buf, qboolean isPrivileged, int c if( i >= ( MAX_CMD_LINE - 1 )) { - Con_DPrintf( S_ERROR "Cbuf_Execute: command string owerflow\n" ); + Con_DPrintf( S_ERROR "%s: command string owerflow\n", __func__ ); line[0] = 0; } else @@ -256,10 +269,16 @@ Cbuf_Execute void Cbuf_Execute( void ) { Cbuf_ExecuteCommandsFromBuffer( &cmd_text, true, -1 ); - // a1ba: unlimited commands for filtered buffer per frame + + // a1ba: goldsrc limits unprivileged commands per frame to 1 here // I don't see any sense in restricting that at this moment // but in future we may limit this - Cbuf_ExecuteCommandsFromBuffer( &filteredcmd_text, false, -1 ); + + // a1ba: there is little to no sense limit privileged commands in + // local game, as client runs server code anyway + // do this for singleplayer only though, to make it easier to catch + // possible bugs during local multiplayer testing + Cbuf_ExecuteCommandsFromBuffer( &filteredcmd_text, SV_Active() && SV_GetMaxClients() == 1, -1 ); } /* @@ -344,7 +363,7 @@ hl.exe -dev 3 +map c1a0d hl.exe -nosound -game bshift =============== */ -void Cmd_StuffCmds_f( void ) +static void Cmd_StuffCmds_f( void ) { host.stuffcmds_pending = true; } @@ -358,7 +377,7 @@ next frame. This allows commands like: bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" ============ */ -void Cmd_Wait_f( void ) +static void Cmd_Wait_f( void ) { cmd_wait = true; } @@ -370,12 +389,12 @@ Cmd_Echo_f Just prints the rest of the line to the console =============== */ -void Cmd_Echo_f( void ) +static void Cmd_Echo_f( void ) { int i; for( i = 1; i < Cmd_Argc(); i++ ) - Con_Printf( "%s", Cmd_Argv( i )); + Con_Printf( "%s ", Cmd_Argv( i )); Con_Printf( "\n" ); } @@ -386,7 +405,7 @@ Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) =============== */ -void Cmd_Alias_f( void ) +static void Cmd_Alias_f( void ) { cmdalias_t *a; char cmd[MAX_CMD_LINE]; @@ -414,7 +433,7 @@ void Cmd_Alias_f( void ) { if( !Q_strcmp( s, a->name )) { - Z_Free( a->value ); + Mem_Free( a->value ); break; } } @@ -423,7 +442,7 @@ void Cmd_Alias_f( void ) { cmdalias_t *cur, *prev; - a = Z_Malloc( sizeof( cmdalias_t )); + a = Mem_Malloc( cmd_pool, sizeof( cmdalias_t )); Q_strncpy( a->name, s, sizeof( a->name )); @@ -451,7 +470,7 @@ void Cmd_Alias_f( void ) } Q_strncat( cmd, "\n", sizeof( cmd )); - a->value = copystring( cmd ); + a->value = copystringpool( cmd_pool, cmd ); } /* @@ -505,54 +524,19 @@ static void Cmd_UnAlias_f ( void ) ============================================================================= */ -typedef struct cmd_s +struct cmd_s { - struct cmd_s *next; - char *name; - xcommand_t function; - int flags; - char *desc; -} cmd_t; - -static int cmd_argc; -static const char *cmd_args = NULL; -static char *cmd_argv[MAX_CMD_TOKENS]; -static char cmd_tokenized[MAX_CMD_BUFFER]; // will have 0 bytes inserted -static cmd_t *cmd_functions; // possible commands to execute - -/* -============ -Cmd_Argc -============ -*/ -int GAME_EXPORT Cmd_Argc( void ) -{ - return cmd_argc; -} - -/* -============ -Cmd_Argv -============ -*/ -const char *Cmd_Argv( int arg ) -{ - if((uint)arg >= cmd_argc ) - return ""; - return cmd_argv[arg]; -} - -/* -============ -Cmd_Args -============ -*/ -const char *Cmd_Args( void ) -{ - return cmd_args; -} - + cmd_t *next; + char *name; + xcommand_t function; + int flags; + char desc[]; +}; +int cmd_argc; +const char *cmd_args = NULL; +char *cmd_argv[MAX_CMD_TOKENS]; +static cmd_t *cmd_functions; // possible commands to execute /* =========================== @@ -560,8 +544,6 @@ Client exports =========================== */ - - /* ============ Cmd_AliasGetList @@ -619,7 +601,7 @@ void Cmd_TokenizeString( const char *text ) // clear the args from the last string for( i = 0; i < cmd_argc; i++ ) - Z_Free( cmd_argv[i] ); + Mem_Free( cmd_argv[i] ); cmd_argc = 0; // clear previous args cmd_args = NULL; @@ -629,7 +611,7 @@ void Cmd_TokenizeString( const char *text ) while( 1 ) { // skip whitespace up to a /n - while( *text && *text <= ' ' && *text != '\r' && *text != '\n' ) + while( *text && ((byte)*text ) <= ' ' && *text != '\r' && *text != '\n' ) text++; if( *text == '\n' || *text == '\r' ) @@ -653,7 +635,7 @@ void Cmd_TokenizeString( const char *text ) if( cmd_argc < MAX_CMD_TOKENS ) { - cmd_argv[cmd_argc] = copystring( cmd_token ); + cmd_argv[cmd_argc] = copystringpool( cmd_pool, cmd_token ); cmd_argc++; } } @@ -664,14 +646,14 @@ void Cmd_TokenizeString( const char *text ) Cmd_AddCommandEx ============ */ -static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcommand_t function, - const char *cmd_desc, int iFlags ) +int Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd_desc, int iFlags, const char *funcname ) { - cmd_t *cmd, *cur, *prev; + cmd_t *cmd, *cur, *prev; + size_t desc_len; if( !COM_CheckString( cmd_name )) { - Con_Reportf( S_ERROR "%s: NULL name\n", funcname ); + Con_Reportf( S_ERROR "%s: NULL name\n", funcname ); return 0; } @@ -682,17 +664,35 @@ static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcomman return 0; } - // fail if the command already exists - if( Cmd_Exists( cmd_name )) + // fail if the command already exists and cannot be overriden + cmd = Cmd_Exists( cmd_name ); + if( cmd ) { - Con_DPrintf( S_ERROR "%s: %s already defined\n", funcname, cmd_name ); - return 0; + // some mods register commands that share the name with some engine's commands + // when they aren't critical to keep engine running, we can let mods to override them + // unfortunately, we lose original command this way + if( FBitSet( cmd->flags, CMD_OVERRIDABLE )) + { + desc_len = Q_strlen( cmd->desc ) + 1; + Q_strncpy( cmd->desc, cmd_desc, desc_len ); + cmd->function = function; + cmd->flags = iFlags; + + Con_DPrintf( S_WARN "%s: %s already defined but is allowed to be overriden\n", funcname, cmd_name ); + return 1; + } + else + { + Con_DPrintf( "%s%s: %s already defined\n", cmd->function == function ? S_WARN : S_ERROR, funcname, cmd_name ); + return 0; + } } // use a small malloc to avoid zone fragmentation - cmd = Z_Malloc( sizeof( cmd_t ) ); - cmd->name = copystring( cmd_name ); - cmd->desc = copystring( cmd_desc ); + desc_len = Q_strlen( cmd_desc ) + 1; + cmd = Mem_Malloc( cmd_pool, sizeof( cmd_t ) + desc_len ); + cmd->name = copystringpool( cmd_pool, cmd_name ); + Q_strncpy( cmd->desc, cmd_desc, desc_len ); cmd->function = function; cmd->flags = iFlags; @@ -710,75 +710,6 @@ static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcomman return 1; } -/* -============ -Cmd_AddCommand -============ -*/ -void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) -{ - Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, cmd_desc, 0 ); -} - - -/* -============ -Cmd_AddRestrictedCommand -============ -*/ -void Cmd_AddRestrictedCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) -{ - Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, cmd_desc, CMD_PRIVILEGED ); -} - -/* -============ -Cmd_AddServerCommand -============ -*/ -void GAME_EXPORT Cmd_AddServerCommand( const char *cmd_name, xcommand_t function ) -{ - Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "server command", CMD_SERVERDLL ); -} - -/* -============ -Cmd_AddClientCommand -============ -*/ -int GAME_EXPORT Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ) -{ - int flags = CMD_CLIENTDLL; - - // a1ba: try to mitigate outdated client.dll vulnerabilities - if( !Q_stricmp( cmd_name, "motd_write" )) - { - flags |= CMD_PRIVILEGED; - } - - return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "client command", flags ); -} - -/* -============ -Cmd_AddGameUICommand -============ -*/ -int GAME_EXPORT Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ) -{ - return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, "gameui command", CMD_GAMEUIDLL ); -} - -/* -============ -Cmd_AddRefCommand -============ -*/ -int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description ) -{ - return Cmd_AddCommandEx( __FUNCTION__, cmd_name, function, description, CMD_REFDLL ); -} - /* ============ Cmd_RemoveCommand @@ -808,9 +739,6 @@ void GAME_EXPORT Cmd_RemoveCommand( const char *cmd_name ) if( cmd->name ) Mem_Free( cmd->name ); - if( cmd->desc ) - Mem_Free( cmd->desc ); - Mem_Free( cmd ); return; } @@ -847,19 +775,18 @@ void Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback ) Cmd_Exists ============ */ -qboolean Cmd_Exists( const char *cmd_name ) +cmd_t *Cmd_Exists( const char *cmd_name ) { #if defined(XASH_HASHED_VARS) - return BaseCmd_Find( HM_CMD, cmd_name ) != NULL; + return BaseCmd_Find( HM_CMD, cmd_name ); #else cmd_t *cmd; - for( cmd = cmd_functions; cmd; cmd = cmd->next ) { if( !Q_strcmp( cmd_name, cmd->name )) - return true; + return cmd; } - return false; + return NULL; #endif } @@ -870,7 +797,7 @@ Cmd_If_f Compare and et condition bit if true ============ */ -void Cmd_If_f( void ) +static void Cmd_If_f( void ) { // reset bit first cmd_condition &= ~BIT( cmd_condlevel ); @@ -931,14 +858,14 @@ Cmd_Else_f Invert condition bit ============ */ -void Cmd_Else_f( void ) +static void Cmd_Else_f( void ) { cmd_condition ^= BIT( cmd_condlevel ); } static qboolean Cmd_ShouldAllowCommand( cmd_t *cmd, qboolean isPrivileged ) { - const char *prefixes[] = { "cl_", "gl_", "r_", "m_", "hud_" }; + const char *prefixes[] = { "cl_", "gl_", "r_", "m_", "hud_", "joy_", "con_", "scr_" }; int i; // always allow local commands @@ -984,7 +911,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr cmd_condlevel = 0; // cvar value substitution - if( CVAR_TO_BOOL( cmd_scripting )) + if( cmd_scripting.value && isPrivileged ) { while( *text ) { @@ -1004,7 +931,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr *ptoken++ = *text++; *ptoken = 0; - len += Q_strncpy( pcmd, Cvar_VariableString( token ), MAX_CMD_LINE - len ); + len += Q_strncpy( pcmd, Cvar_VariableString( token ), sizeof( token ) - len ); pcmd = command + len; if( !*text ) break; @@ -1031,30 +958,27 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr if( !Cmd_Argc( )) return; // no tokens -#if defined(XASH_HASHED_VARS) - BaseCmd_FindAll( cmd_argv[0], - (base_command_t**)&cmd, - (base_command_t**)&a, - (base_command_t**)&cvar ); +#if defined( XASH_HASHED_VARS ) + BaseCmd_FindAll( cmd_argv[0], &cmd, &a, &cvar ); #endif if( !host.apply_game_config ) { +#if !defined( XASH_HASHED_VARS ) // check aliases - if( !a ) // if not found in basecmd + for( a = cmd_alias; a; a = a->next ) { - for( a = cmd_alias; a; a = a->next ) - { - if( !Q_stricmp( cmd_argv[0], a->name )) - break; - } + if( !Q_stricmp( cmd_argv[0], a->name )) + break; } +#endif if( a ) { + size_t len = Q_strlen( a->value ); Cbuf_InsertTextToBuffer( isPrivileged ? &cmd_text : &filteredcmd_text, - a->value ); + a->value, len, len ); return; } } @@ -1062,14 +986,13 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr // special mode for restore game.dll archived cvars if( !host.apply_game_config || !Q_strcmp( cmd_argv[0], "exec" )) { - if( !cmd || !cmd->function ) // if not found in basecmd +#if !defined( XASH_HASHED_VARS ) + for( cmd = cmd_functions; cmd; cmd = cmd->next ) { - for( cmd = cmd_functions; cmd; cmd = cmd->next ) - { - if( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function ) - break; - } + if( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function ) + break; } +#endif // check functions if( cmd && cmd->function ) @@ -1150,13 +1073,13 @@ void Cmd_ForwardToServer( void ) str[0] = 0; if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) { - Q_strcat( str, Cmd_Argv( 0 )); - Q_strcat( str, " " ); + Q_strncat( str, Cmd_Argv( 0 ), sizeof( str )); + Q_strncat( str, " ", sizeof( str )); } if( Cmd_Argc() > 1 ) - Q_strcat( str, Cmd_Args( )); - else Q_strcat( str, "\n" ); + Q_strncat( str, Cmd_Args( ), sizeof( str )); + else Q_strncat( str, "\n", sizeof( str )); MSG_WriteString( &cls.netchan.message, str ); } @@ -1167,7 +1090,7 @@ void Cmd_ForwardToServer( void ) Cmd_List_f ============ */ -void Cmd_List_f( void ) +static void Cmd_List_f( void ) { cmd_t *cmd; int i = 0; @@ -1208,13 +1131,13 @@ void Cmd_Unlink( int group ) cmd_t **prev; int count = 0; - if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, CMD_SERVERDLL )) + if( FBitSet( group, CMD_SERVERDLL ) && Cvar_VariableInteger( "host_gameloaded" )) return; - if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, CMD_CLIENTDLL )) + if( FBitSet( group, CMD_CLIENTDLL ) && Cvar_VariableInteger( "host_clientloaded" )) return; - if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, CMD_GAMEUIDLL )) + if( FBitSet( group, CMD_GAMEUIDLL ) && Cvar_VariableInteger( "host_gameuiloaded" )) return; prev = &cmd_functions; @@ -1238,7 +1161,6 @@ void Cmd_Unlink( int group ) *prev = cmd->next; if( cmd->name ) Mem_Free( cmd->name ); - if( cmd->desc ) Mem_Free( cmd->desc ); Mem_Free( cmd ); count++; @@ -1254,21 +1176,21 @@ static void Cmd_Apropos_f( void ) cmdalias_t *alias; const char *partial; int count = 0; - qboolean ispattern; + char buf[MAX_VA_STRING]; - if( Cmd_Argc() > 1 ) - { - partial = Cmd_Args(); - } - else + if( Cmd_Argc() < 2 ) { Msg( "apropos what?\n" ); return; } - ispattern = partial && Q_strpbrk( partial, "*?" ); - if( !ispattern ) - partial = va( "*%s*", partial ); + partial = Cmd_Args(); + + if( !Q_strpbrk( partial, "*?" )) + { + Q_snprintf( buf, sizeof( buf ), "*%s*", partial ); + partial = buf; + } for( var = (convar_t*)Cvar_GetList(); var; var = var->next ) { @@ -1335,6 +1257,50 @@ void Cmd_Null_f( void ) { } +/* +============= +Cmd_MakePrivileged_f +============= +*/ +static void Cmd_MakePrivileged_f( void ) +{ + const char *s = Cmd_Argv( 1 ); + convar_t *cv; + cmd_t *cmd; + cmdalias_t *alias; + + if( Cmd_Argc( ) != 2 ) + { + Con_Printf( S_USAGE "make_privileged \n" ); + return; + } + +#if defined( XASH_HASHED_VARS ) + BaseCmd_FindAll( s, &cmd, &alias, &cv ); +#else + cmd = Cmd_Exists( s ); + cv = Cvar_FindVar( s ); +#endif + + if( !cv && !cmd ) + { + Con_Printf( "Nothing was found.\n" ); + return; + } + + if( cv ) + { + SetBits( cv->flags, FCVAR_PRIVILEGED ); + Con_Printf( "Cvar %s set to be privileged\n", cv->name ); + } + + if( cmd ) + { + SetBits( cmd->flags, CMD_PRIVILEGED ); + Con_Printf( "Command %s set to be privileged\n", cmd->name ); + } +} + /* ========== Cmd_Escape @@ -1345,7 +1311,7 @@ inserts escape sequences void Cmd_Escape( char *newCommand, const char *oldCommand, int len ) { int c; - int scripting = CVAR_TO_BOOL( cmd_scripting ); + int scripting = cmd_scripting.value; while( (c = *oldCommand++) && len > 1 ) { @@ -1376,8 +1342,7 @@ Cmd_Init */ void Cmd_Init( void ) { - Cbuf_Init(); - + cmd_pool = Mem_AllocPool( "Console Commands" ); cmd_functions = NULL; cmd_condition = 0; cmd_alias = NULL; @@ -1395,8 +1360,10 @@ void Cmd_Init( void ) #endif // XASH_DEDICATED Cmd_AddRestrictedCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); Cmd_AddRestrictedCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); - Cmd_AddCommand( "if", Cmd_If_f, "compare and set condition bits" ); - Cmd_AddCommand( "else", Cmd_Else_f, "invert condition bit" ); + Cmd_AddRestrictedCommand( "if", Cmd_If_f, "compare and set condition bits" ); + Cmd_AddRestrictedCommand( "else", Cmd_Else_f, "invert condition bit" ); + + Cmd_AddRestrictedCommand( "make_privileged", Cmd_MakePrivileged_f, "makes command or variable privileged (protected from access attempts from server)" ); #if defined(XASH_HASHED_VARS) Cmd_AddCommand( "basecmd_stats", BaseCmd_Stats_f, "print info about basecmd usage" ); @@ -1404,6 +1371,11 @@ void Cmd_Init( void ) #endif } +void Cmd_Shutdown( void ) +{ + Mem_FreePool( &cmd_pool ); +} + #if XASH_ENGINE_TESTS #include "tests.h" diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index 32c5c0a1..d9614117 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -17,21 +17,28 @@ GNU General Public License for more details. #define COM_STRINGS_H // default colored message headers -#define S_NOTE "^2Note:^7 " -#define S_WARN "^3Warning:^7 " -#define S_ERROR "^1Error:^7 " -#define S_USAGE "Usage: " +#define S_BLACK "^0" +#define S_RED "^1" +#define S_GREEN "^2" +#define S_YELLOW "^3" +#define S_BLUE "^4" +#define S_CYAN "^5" +#define S_MAGENTA "^6" +#define S_DEFAULT "^7" -#define S_OPENGL_NOTE "^2OpenGL Note:^7 " -#define S_OPENGL_WARN "^3OpenGL Warning:^7 " -#define S_OPENGL_ERROR "^3OpenGL Error:^7 " +#define S_NOTE S_GREEN "Note: " S_DEFAULT +#define S_WARN S_YELLOW "Warning: " S_DEFAULT +#define S_ERROR S_RED "Error: " S_DEFAULT +#define S_USAGE "Usage: " +#define S_USAGE_INDENT "\t" + +#define S_OPENGL_NOTE S_GREEN "OpenGL Note: " S_DEFAULT +#define S_OPENGL_WARN S_YELLOW "OpenGL Warning: " S_DEFAULT +#define S_OPENGL_ERROR S_RED "OpenGL Error: " S_DEFAULT // end game final default message #define DEFAULT_ENDGAME_MESSAGE "The End" -// path to the hash-pak that contain custom player decals -#define CUSTOM_RES_PATH "custom.hpk" - // path to default playermodel in GoldSrc #define DEFAULT_PLAYER_PATH_HALFLIFE "models/player.mdl" @@ -45,30 +52,31 @@ GNU General Public License for more details. #define DEFAULT_EXTERNAL_PALETTE "gfx/palette.pal" -// path to folders where placed all sounds +// path to sound files #define DEFAULT_SOUNDPATH "sound/" -// path store saved games +// path to saved games #define DEFAULT_SAVE_DIRECTORY "save/" +// path to download games +#define DEFAULT_DOWNLOADED_DIRECTORY "downloaded/" + +// path to user mod directory +#define DEFAULT_CUSTOM_DIRECTORY "custom/" + // fallback to this skybox -#define DEFAULT_SKYBOX_PATH "gfx/env/desert" +#define DEFAULT_SKYBOX_NAME "desert" // playlist for startup videos #define DEFAULT_VIDEOLIST_PATH "media/StartupVids.txt" #define CVAR_GLCONFIG_DESCRIPTION "enable or disable %s" -#define DEFAULT_BSP_BUILD_ERROR "%s can't be loaded in this build. Please rebuild engine with enabled SUPPORT_BSP2_FORMAT\n" - #define DEFAULT_UPDATE_PAGE "https://github.com/FWGS/xash3d-fwgs/releases/latest" #define XASH_ENGINE_NAME "Xash3D FWGS" -#define XASH_VERSION "0.20" // engine current version +#define XASH_DEDICATED_SERVER_NAME "XashDS" +#define XASH_VERSION "0.21" // engine current version #define XASH_COMPAT_VERSION "0.99" // version we are based on -// renderers order is important, software is always a last chance fallback -#define DEFAULT_RENDERERS { "gl", "gles1", "gles2", "gl4es", "soft" } -#define DEFAULT_RENDERERS_LEN 5 - #endif//COM_STRINGS_H diff --git a/engine/common/common.c b/engine/common/common.c index 684718dd..09d23018 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -22,20 +22,22 @@ GNU General Public License for more details. #include "const.h" #include "client.h" #include "library.h" -#include "sequence.h" -static const char *file_exts[] = +static const char *const file_exts[] = { - "cfg", - "lst", - "exe", - "vbs", - "com", - "bat", - "dll", - "ini", - "log", - "sys", + // ban text files that don't make sense as resource + "cfg", "lst", "ini", "log", + + // ban Windows code + "exe", "vbs", "com", "bat", + "dll", "sys", "ps1", + + // ban common unix code + // NOTE: in unix anything can be executed as long it has access flag + "so", "sh", "dylib", + + // ban mobile archives + "apk", "ipa", }; #ifdef _DEBUG @@ -104,7 +106,7 @@ static float fran1( void ) return temp; } -void COM_SetRandomSeed( int lSeed ) +void GAME_EXPORT COM_SetRandomSeed( int lSeed ) { if( lSeed ) idum = lSeed; else idum = -time( NULL ); @@ -151,6 +153,30 @@ int GAME_EXPORT COM_RandomLong( int lLow, int lHigh ) return lLow + (n % x); } +/* +============ +va + +does a varargs printf into a temp buffer, +so I don't need to have varargs versions +of all text functions. +============ +*/ +char *va( const char *format, ... ) +{ + va_list argptr; + static char string[16][MAX_VA_STRING], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 15; + va_start( argptr, format ); + Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); + va_end( argptr ); + + return s; +} + /* =============================================================================== @@ -192,21 +218,32 @@ typedef struct int window_size; } lzss_state_t; -qboolean LZSS_IsCompressed( const byte *source ) +qboolean LZSS_IsCompressed( const byte *source, size_t input_len ) { - lzss_header_t *phdr = (lzss_header_t *)source; + const lzss_header_t *phdr; + + if( input_len <= sizeof( lzss_header_t )) + return 0; + + phdr = (const lzss_header_t *)source; if( phdr && phdr->id == LZSS_ID ) return true; return false; } -uint LZSS_GetActualSize( const byte *source ) +uint LZSS_GetActualSize( const byte *source, size_t input_len ) { - lzss_header_t *phdr = (lzss_header_t *)source; + const lzss_header_t *phdr; + + if( input_len <= sizeof( lzss_header_t )) + return 0; + + phdr = (const lzss_header_t *)source; if( phdr && phdr->id == LZSS_ID ) return phdr->size; + return 0; } @@ -214,7 +251,7 @@ static void LZSS_BuildHash( lzss_state_t *state, const byte *source ) { lzss_list_t *list; lzss_node_t *node; - unsigned int targetindex = (uint)source & ( state->window_size - 1 ); + unsigned int targetindex = (uintptr_t)source & ( state->window_size - 1 ); node = &state->hash_node[targetindex]; @@ -243,7 +280,7 @@ static void LZSS_BuildHash( lzss_state_t *state, const byte *source ) list->start = node; } -byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *pOutputBuf, uint *pOutputSize ) +static byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *pOutputBuf, uint *pOutputSize ) { byte *pStart = pOutputBuf; // allocate the output buffer, compressed buffer is expected to be less, caller will free byte *pEnd = pStart + input_length - sizeof( lzss_header_t ) - 8; // prevent compression failure @@ -330,6 +367,8 @@ byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, if( pOutput >= pEnd ) { // compression is worse, abandon + state->hash_table = NULL; + state->hash_node = NULL; return NULL; } } @@ -338,6 +377,8 @@ byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, { // unexpected failure Assert( 0 ); + state->hash_table = NULL; + state->hash_node = NULL; return NULL; } @@ -363,12 +404,12 @@ byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize ) { - byte *pStart = (byte *)malloc( inputLength ); - byte *pFinal = NULL; - lzss_state_t state; + byte *pStart = (byte *)malloc( inputLength ); + byte *pFinal = NULL; + lzss_state_t state = { .window_size = LZSS_WINDOW_SIZE }; - memset( &state, 0, sizeof( state )); - state.window_size = LZSS_WINDOW_SIZE; + if( !pStart ) + return NULL; pFinal = LZSS_CompressNoAlloc( &state, pInput, inputLength, pStart, pOutputSize ); @@ -381,14 +422,21 @@ byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize ) return pStart; } -uint LZSS_Decompress( const byte *pInput, byte *pOutput ) +uint LZSS_Decompress( const byte *pInput, byte *pOutput, size_t input_len, size_t output_len ) { uint totalBytes = 0; int getCmdByte = 0; int cmdByte = 0; - uint actualSize = LZSS_GetActualSize( pInput ); + uint actualSize; + const byte *pInputEnd = pInput + input_len - 1; // thanks to nillerusr for the fix! + byte *pOrigOutput = pOutput; - if( !actualSize ) + if( input_len <= sizeof( lzss_header_t )) + return 0; + + actualSize = LZSS_GetActualSize( pInput, input_len ); + + if( !actualSize || actualSize > output_len ) return 0; pInput += sizeof( lzss_header_t ); @@ -396,15 +444,24 @@ uint LZSS_Decompress( const byte *pInput, byte *pOutput ) while( 1 ) { if( !getCmdByte ) + { + if( pInput > pInputEnd ) + return 0; + cmdByte = *pInput++; + } getCmdByte = ( getCmdByte + 1 ) & 0x07; if( cmdByte & 0x01 ) { - int position = *pInput++ << LZSS_LOOKSHIFT; + int position; int i, count; byte *pSource; + if( pInput > pInputEnd ) + return 0; + + position = *pInput++ << LZSS_LOOKSHIFT; position |= ( *pInput >> LZSS_LOOKSHIFT ); count = ( *pInput++ & 0x0F ) + 1; @@ -412,12 +469,19 @@ uint LZSS_Decompress( const byte *pInput, byte *pOutput ) break; pSource = pOutput - position - 1; + + if( totalBytes + count > output_len || pSource < pOrigOutput ) + return 0; + for( i = 0; i < count; i++ ) *pOutput++ = *pSource++; totalBytes += count; } else { + if( totalBytes + 1 > output_len || pInput > pInputEnd ) + return 0; + *pOutput++ = *pInput++; totalBytes++; } @@ -508,50 +572,6 @@ int GAME_EXPORT COM_FileSize( const char *filename ) return FS_FileSize( filename, false ); } -/* -============= -COM_AddAppDirectoryToSearchPath - -============= -*/ -void GAME_EXPORT COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ) -{ - FS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH ); -} - -/* -=========== -COM_ExpandFilename - -Finds the file in the search path, copies over the name with the full path name. -This doesn't search in the pak file. -=========== -*/ -int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ) -{ - const char *path; - char result[MAX_SYSPATH]; - - if( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 ) - return 0; - - // filename examples: - // media\sierra.avi - D:\Xash3D\valve\media\sierra.avi - // models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl - if(( path = FS_GetDiskPath( fileName, false )) != NULL ) - { - Q_sprintf( result, "%s/%s", host.rootdir, path ); - - // check for enough room - if( Q_strlen( result ) > nameOutBufferSize ) - return 0; - - Q_strncpy( nameOutBuffer, result, nameOutBufferSize ); - return 1; - } - return 0; -} - /* ============= COM_TrimSpace @@ -640,7 +660,7 @@ COM_MemFgets ============= */ -char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ) +char *GAME_EXPORT COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ) { int i, last, stop; @@ -694,7 +714,7 @@ Cache_Check consistency check ==================== */ -void *Cache_Check( poolhandle_t mempool, cache_user_t *c ) +void *GAME_EXPORT Cache_Check( poolhandle_t mempool, cache_user_t *c ) { if( !c->data ) return NULL; @@ -711,10 +731,10 @@ COM_LoadFileForMe ============= */ -byte* GAME_EXPORT COM_LoadFileForMe( const char *filename, int *pLength ) +byte *GAME_EXPORT COM_LoadFileForMe( const char *filename, int *pLength ) { string name; - byte *file, *pfile; + byte *pfile; fs_offset_t iLength; if( !COM_CheckString( filename )) @@ -727,21 +747,9 @@ byte* GAME_EXPORT COM_LoadFileForMe( const char *filename, int *pLength ) Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); - pfile = FS_LoadFile( name, &iLength, false ); + pfile = g_fsapi.LoadFileMalloc( name, &iLength, false ); if( pLength ) *pLength = (int)iLength; - if( pfile ) - { - file = malloc( iLength + 1 ); - if( file != NULL ) - { - memcpy( file, pfile, iLength ); - file[iLength] = '\0'; - } - Mem_Free( pfile ); - pfile = file; - } - return pfile; } @@ -751,7 +759,7 @@ COM_LoadFile ============= */ -byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ) +byte *GAME_EXPORT COM_LoadFile( const char *filename, int usehunk, int *pLength ) { return COM_LoadFileForMe( filename, pLength ); } @@ -786,25 +794,6 @@ void GAME_EXPORT COM_FreeFile( void *buffer ) free( buffer ); } -/* -============= -COM_NormalizeAngles - -============= -*/ -void COM_NormalizeAngles( vec3_t angles ) -{ - int i; - - for( i = 0; i < 3; i++ ) - { - if( angles[i] > 180.0f ) - angles[i] -= 360.0f; - else if( angles[i] < -180.0f ) - angles[i] += 360.0f; - } -} - /* ============= pfnGetModelType @@ -837,63 +826,6 @@ void GAME_EXPORT pfnGetModelBounds( model_t *mod, float *mins, float *maxs ) } } -/* -============= -pfnCvar_RegisterServerVariable - -standard path to register game variable -============= -*/ -void GAME_EXPORT pfnCvar_RegisterServerVariable( cvar_t *variable ) -{ - if( variable != NULL ) - SetBits( variable->flags, FCVAR_EXTDLL ); - Cvar_RegisterVariable( (convar_t *)variable ); -} - -/* -============= -pfnCvar_RegisterEngineVariable - -use with precaution: this cvar will NOT unlinked -after game.dll is unloaded -============= -*/ -void GAME_EXPORT pfnCvar_RegisterEngineVariable( cvar_t *variable ) -{ - Cvar_RegisterVariable( (convar_t *)variable ); -} - -/* -============= -pfnCvar_RegisterVariable - -============= -*/ -cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ) -{ - // a1ba: try to mitigate outdated client.dll vulnerabilities - if( !Q_stricmp( szName, "motdfile" )) - flags |= FCVAR_PRIVILEGED; - - if( FBitSet( flags, FCVAR_GLCONFIG )) - return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); - return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( flags|FCVAR_CLIENTDLL )); -} - -/* -============= -pfnCvar_RegisterVariable - -============= -*/ -cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ) -{ - if( FBitSet( flags, FCVAR_GLCONFIG )) - return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); - return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( flags|FCVAR_GAMEUIDLL )); -} - /* ============= pfnCVarGetPointer @@ -901,49 +833,37 @@ pfnCVarGetPointer can return NULL ============= */ -cvar_t *pfnCVarGetPointer( const char *szVarName ) +cvar_t *GAME_EXPORT pfnCVarGetPointer( const char *szVarName ) { return (cvar_t *)Cvar_FindVar( szVarName ); } /* ============= -pfnCVarDirectSet - -allow to set cvar directly -============= -*/ -void GAME_EXPORT pfnCVarDirectSet( cvar_t *var, const char *szValue ) -{ - Cvar_DirectSet( (convar_t *)var, szValue ); -} - -/* -============= -COM_CompareFileTime +pfnCompareFileTime ============= */ -int GAME_EXPORT COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ) +int GAME_EXPORT pfnCompareFileTime( const char *path1, const char *path2, int *retval ) { - int bRet = 0; + int t1, t2; + *retval = 0; - *iCompare = 0; + if( !path1 || !path2 ) + return 0; - if( filename1 && filename2 ) - { - int ft1 = FS_FileTime( filename1, false ); - int ft2 = FS_FileTime( filename2, false ); + if(( t1 = g_fsapi.FileTime( path1, false )) == -1 ) + return 0; - // one of files is missing - if( ft1 == -1 || ft2 == -1 ) - return bRet; + if(( t2 = g_fsapi.FileTime( path2, false )) == -1 ) + return 0; - *iCompare = Host_CompareFileTime( ft1, ft2 ); - bRet = 1; - } + if( t1 < t2 ) + *retval = -1; + else if( t1 > t2 ) + *retval = 1; - return bRet; + return 1; } /* @@ -977,53 +897,66 @@ float GAME_EXPORT pfnTime( void ) return (float)Sys_DoubleTime(); } -/* -============= -pfnGetGameDir - -============= -*/ -void GAME_EXPORT pfnGetGameDir( char *szGetGameDir ) -{ - if( !szGetGameDir ) return; - Q_strcpy( szGetGameDir, GI->gamefolder ); -} - qboolean COM_IsSafeFileToDownload( const char *filename ) { char lwrfilename[4096]; - const char *first, *last; + const char *last; const char *ext; + size_t len; int i; if( !COM_CheckString( filename )) return false; + ext = COM_FileExtension( filename ); + len = Q_strlen( filename ); + + // only allow extensionless files that start with !MD5 if( !Q_strncmp( filename, "!MD5", 4 )) + { + if( COM_CheckStringEmpty( ext )) + return false; + + len = Q_strlen( filename ); + + if( len != 36 ) + return false; + + for( i = 4; i < len; i++ ) + { + if(( filename[i] >= '0' && filename[i] <= '9' ) || + ( filename[i] >= 'A' && filename[i] <= 'F' )) + continue; + + return false; + } + return true; + } + + for( i = 0; i < len; i++ ) + { + if( !isprint( filename[i] )) + return false; + } Q_strnlwr( filename, lwrfilename, sizeof( lwrfilename )); + ext = COM_FileExtension( lwrfilename ); - if( Q_strpbrk( lwrfilename, "\\:~" ) || Q_strstr( lwrfilename, ".." ) ) + if( Q_strpbrk( lwrfilename, "\\:~" ) || Q_strstr( lwrfilename, ".." )) return false; if( lwrfilename[0] == '/' ) return false; - first = Q_strchr( lwrfilename, '.' ); last = Q_strrchr( lwrfilename, '.' ); - if( first == NULL || last == NULL ) + if( last == NULL ) return false; - if( first != last ) + if( Q_strlen( last ) != 4 ) return false; - if( Q_strlen( first ) != 4 ) - return false; - - ext = COM_FileExtension( lwrfilename ); - for( i = 0; i < ARRAYSIZE( file_exts ); i++ ) { if( !Q_stricmp( ext, file_exts[i] )) @@ -1035,13 +968,15 @@ qboolean COM_IsSafeFileToDownload( const char *filename ) char *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline ) { + size_t size; char *b; if( !s ) return NULL; if( !mempool ) mempool = host.mempool; - b = _Mem_Alloc( mempool, Q_strlen( s ) + 1, false, filename, fileline ); - Q_strcpy( b, s ); + size = Q_strlen( s ) + 1; + b = _Mem_Alloc( mempool, size, false, filename, fileline ); + Q_strncpy( b, s, size ); return b; } @@ -1064,10 +999,9 @@ used by CS:CZ */ void *GAME_EXPORT pfnSequenceGet( const char *fileName, const char *entryName ) { - Msg( "Sequence_Get: file %s, entry %s\n", fileName, entryName ); + Msg( "%s: file %s, entry %s\n", __func__, fileName, entryName ); - - return Sequence_Get( fileName, entryName ); + return NULL; } /* @@ -1079,9 +1013,9 @@ used by CS:CZ */ void *GAME_EXPORT pfnSequencePickSentence( const char *groupName, int pickMethod, int *picked ) { - Msg( "Sequence_PickSentence: group %s, pickMethod %i\n", groupName, pickMethod ); + Msg( "%s: group %s, pickMethod %i\n", __func__, groupName, pickMethod ); - return Sequence_PickSentence( groupName, pickMethod, picked ); + return NULL; } @@ -1092,30 +1026,7 @@ pfnIsCareerMatch used by CS:CZ (client stub) ============= */ -int GAME_EXPORT GAME_EXPORT pfnIsCareerMatch( void ) -{ - return 0; -} - -/* -============= -pfnRegisterTutorMessageShown - -only exists in PlayStation version -============= -*/ -void GAME_EXPORT pfnRegisterTutorMessageShown( int mid ) -{ -} - -/* -============= -pfnGetTimesTutorMessageShown - -only exists in PlayStation version -============= -*/ -int GAME_EXPORT pfnGetTimesTutorMessageShown( int mid ) +int GAME_EXPORT pfnIsCareerMatch( void ) { return 0; } @@ -1157,30 +1068,80 @@ void GAME_EXPORT pfnResetTutorMessageDecayData( void ) #include "tests.h" +#ifdef USE_ASAN +#include +#endif + +static void Test_LZSS( void ) +{ + char poison1[8192]; + byte in[256]; + char poison2[8192]; + byte out[256]; + char poison3[8192]; + + lzss_header_t *hdr = (lzss_header_t *)in; + uint result; + + const byte compressed[] = + { + 0x4c, 0x5a, 0x53, 0x53, 0x1a, 0x00, 0x00, 0x00, 0x00, + 0x44, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6c, 0x00, + 0x69, 0x6b, 0x65, 0x20, 0x77, 0x68, 0x61, 0x74, 0x41, + 0x00, 0xd4, 0x73, 0x65, 0x65, 0x3f, 0x00, 0x00, 0x00, + }; + const char decompressed[] = "Do you like what you see?"; + +#ifdef USING_ASAN + ASAN_POISON_MEMORY_REGION( poison1, sizeof( poison1 )); + ASAN_POISON_MEMORY_REGION( poison2, sizeof( poison2 )); + ASAN_POISON_MEMORY_REGION( poison3, sizeof( poison3 )); +#endif + + hdr->size = sizeof( in ) - sizeof( *hdr ); + hdr->id = LZSS_ID; + + memset( in + sizeof( *hdr ), 0xff, sizeof( in ) - sizeof( *hdr )); + result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out )); + TASSERT_EQi( result, 0 ); + + memset( in + sizeof( *hdr ), 0x00, sizeof( in ) - sizeof( *hdr )); + result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out )); + TASSERT_EQi( result, 0 ); + + hdr->size = 1; + hdr->id = LZSS_ID; + result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out )); + TASSERT_EQi( result, 0 ); + + hdr->size = 999; + hdr->id = LZSS_ID; + result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out )); + TASSERT_EQi( result, 0 ); + + hdr->size = sizeof( in ) - sizeof( *hdr ); + hdr->id = 0xa1ba; + result = LZSS_Decompress( in, out, sizeof( in ), sizeof( out )); + TASSERT_EQi( result, 0 ); + + result = LZSS_Decompress( compressed, out, sizeof( compressed ), sizeof( out )); + TASSERT_EQi( result, 26 ); + TASSERT_STR( out, decompressed ); +} + void Test_RunCommon( void ) { - char *file = (char *)"q asdf \"qwerty\" \"f \\\"f\" meowmeow\n// comment \"stuff ignored\"\nbark"; - int len; - char buf[5]; + Msg( "Checking COM_IsSafeFileToDownload...\n" ); - Msg( "Checking COM_ParseFile...\n" ); + TASSERT_EQi( COM_IsSafeFileToDownload( "models/bsg_props/[hl-lab.ru]bush.mdl" ), true ); + TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5AAB5E8B307672DA86FBD10AC302BC732" ), true ); + TASSERT_EQi( COM_IsSafeFileToDownload( "!MD56f1ffd8c96bd64c9c27955309f6ecfe6" ), false ); + TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5AAB5E8B307672DA86FBD10AC302B.exe" ), false ); + TASSERT_EQi( COM_IsSafeFileToDownload( "!MD5/../../valve/resource/GameMenu.res" ), false ); + TASSERT_EQi( COM_IsSafeFileToDownload( "not-a-virus-trust-me.bat" ), false ); + TASSERT_EQi( COM_IsSafeFileToDownload( "a-texture.png" ), true ); - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "q" ) && len == 1); - - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4); - - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1); - - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4); - - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "meow" ) && len == -1); - - file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); - TASSERT( !Q_strcmp( buf, "bark" ) && len == 4); + Msg( "Checking LZSS_Decompress...\n" ); + Test_LZSS(); } #endif diff --git a/engine/common/common.h b/engine/common/common.h index 3099e436..38657f73 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -76,23 +76,14 @@ XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time -enum +enum dev_level_e { DEV_NONE = 0, DEV_NORMAL, DEV_EXTENDED }; -enum -{ - D_INFO = 1, // "-dev 1", shows various system messages - D_WARN, // "-dev 2", shows not critical system warnings - D_ERROR, // "-dev 3", shows critical warnings - D_REPORT, // "-dev 4", special case for game reports - D_NOTE // "-dev 5", show system notifications for engine developers -}; - -typedef enum +typedef enum instance_e { HOST_NORMAL, // listen server, singleplayer HOST_DEDICATED, @@ -108,15 +99,19 @@ typedef enum #include "com_model.h" #include "com_strings.h" #include "crtlib.h" +#define FSCALLBACK_OVERRIDE_OPEN +#define FSCALLBACK_OVERRIDE_LOADFILE +#define FSCALLBACK_OVERRIDE_MALLOC_LIKE +#include "fscallback.h" #include "cvar.h" #include "con_nprint.h" #include "crclib.h" #include "ref_api.h" -#include "fscallback.h" // PERFORMANCE INFO -#define MIN_FPS 20.0f // host minimum fps value for maxfps. -#define MAX_FPS 200.0f // upper limit for maxfps. +#define MIN_FPS 20.0f // host minimum fps value for maxfps. +#define MAX_FPS_SOFT 200.0f // soft limit for maxfps. +#define MAX_FPS_HARD 1000.0f // multiplayer hard limit for maxfps. #define HOST_FPS 100.0f // multiplayer games typical fps #define MAX_FRAMETIME 0.25f @@ -142,9 +137,14 @@ typedef enum #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif +#define MAX_SERVERINFO_STRING 512 // server handles too many settings. expand to 1024? +#define MAX_PRINT_MSG 8192 // how many symbols can handle single call of Con_Printf or Con_DPrintf +#define MAX_TOKEN 2048 // parse token length +#define MAX_USERMSG_LENGTH 2048 // don't modify it's relies on a client-side definitions + #define GameState (&host.game) -#define FORCE_DRAW_VERSION_TIME 5.0f // draw version for 5 seconds +#define FORCE_DRAW_VERSION_TIME 5.0 // draw version for 5 seconds #ifdef _DEBUG void DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ); @@ -153,19 +153,22 @@ void DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, #define Assert( f ) #endif -extern convar_t *gl_vsync; -extern convar_t *scr_loading; -extern convar_t *scr_download; -extern convar_t *cmd_scripting; -extern convar_t *sv_maxclients; -extern convar_t *cl_allow_levelshots; -extern convar_t *vid_displayfrequency; +extern convar_t gl_vsync; +extern convar_t scr_loading; +extern convar_t scr_download; +extern convar_t cmd_scripting; +extern convar_t host_allow_materials; extern convar_t host_developer; -extern convar_t *host_limitlocal; -extern convar_t *host_framerate; -extern convar_t *host_maxfps; +extern convar_t host_limitlocal; +extern convar_t host_maxfps; +extern convar_t fps_override; extern convar_t sys_timescale; extern convar_t cl_filterstuffcmd; +extern convar_t rcon_password; +extern convar_t hpk_custom_file; +extern convar_t con_gamemaps; + +#define Mod_AllowMaterials() ( host_allow_materials.value != 0.0f && !FBitSet( host.features, ENGINE_DISABLE_HDTEXTURES )) /* ============================================================== @@ -182,16 +185,7 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct sysinfo_s -{ - string exeName; // exe.filename - string rcName; // .rc script name - string basedirName; // name of base directory - string gamedll; - string clientlib; -} sysinfo_t; - -typedef enum +typedef enum host_status_e { HOST_INIT = 0, // initalize operations HOST_FRAME, // host running @@ -202,7 +196,7 @@ typedef enum HOST_CRASHED // an exception handler called } host_status_t; -typedef enum +typedef enum host_state_e { STATE_RUNFRAME = 0, STATE_LOAD_LEVEL, @@ -211,7 +205,7 @@ typedef enum STATE_GAME_SHUTDOWN, } host_state_t; -typedef struct +typedef struct game_status_e { host_state_t curstate; host_state_t nextstate; @@ -222,7 +216,7 @@ typedef struct qboolean newGame; // unload the server.dll before start a new map } game_status_t; -typedef enum +typedef enum keydest_e { key_console = 0, key_game, @@ -230,7 +224,7 @@ typedef enum key_message } keydest_t; -typedef enum +typedef enum rdtype_e { RD_NONE = 0, RD_CLIENT, @@ -240,7 +234,7 @@ typedef enum #include "net_ws.h" // console field -typedef struct +typedef struct field_e { string buffer; int cursor; @@ -258,7 +252,7 @@ typedef struct host_redirect_s int lines; } host_redirect_t; -typedef struct +typedef struct soundlist_e { char name[MAX_QPATH]; short entnum; @@ -273,192 +267,119 @@ typedef struct double forcedEnd; } soundlist_t; -typedef struct -{ - char model[MAX_QPATH]; // curstate.modelindex = SV_ModelIndex - vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. - short clientIndex; - float fadeSpeed; - float bounceFactor; - byte hitSound; - qboolean high_priority; - float x, y, z; - int flags; - float time; - - // base state - vec3_t velocity; // baseline.origin - vec3_t avelocity; // baseline.angles - int fadeamount; // baseline.renderamt - float sparklife; // baseline.framerate - float thinkTime; // baseline.scale - - // current state - vec3_t origin; // entity.origin - vec3_t angles; // entity.angles - float renderamt; // curstate.renderamt - color24 rendercolor; // curstate.rendercolor - int rendermode; // curstate.rendermode - int renderfx; // curstate.renderfx - float framerate; // curstate.framerate - float frame; // curstate.frame - byte body; // curstate.body - byte skin; // curstate.skin - float scale; // curstate.scale -} tentlist_t; - typedef enum bugcomp_e { - // default mode, we assume that user dlls are not relying on engine bugs - BUGCOMP_OFF, + // reverts fix for pfnPEntityOfEntIndex for bug compatibility with GoldSrc + BUGCOMP_PENTITYOFENTINDEX_FLAG = BIT( 0 ), - // GoldSrc mode, user dlls are relying on GoldSrc specific bugs - // but fixing them may break regular Xash games - BUGCOMP_GOLDSRC, + // rewrites mod's attempts to write GoldSrc-specific messages into Xash protocol + // (new wrappers are added by request) + BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG = BIT( 1 ), + + // makes sound with no attenuation spatialized, like in GoldSrc + BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE = BIT( 2 ), + + // returns full path to the game directory in server's pfnGetGameDir call + BUGCOMP_GET_GAME_DIR_FULL_PATH = BIT( 3 ), } bugcomp_t; typedef struct host_parm_s { - HINSTANCE hInst; - HANDLE hMutex; + // ==== shared through RefAPI's ref_host_t + double realtime; // host.curtime + double frametime; // time between engine frames + uint features; // custom features that enables by mod-maker request + // ==== shared through RefAPI's ref_host_t - host_status_t status; // global host state - game_status_t game; // game manager - uint type; // running at - jmp_buf abortframe; // abort current frame - dword errorframe; // to prevent multiple host error - poolhandle_t mempool; // static mempool for misc allocations - string finalmsg; // server shutdown final message - string downloadfile; // filename to be downloading - int downloadcount; // how many files remain to downloading - char deferred_cmd[128]; // deferred commands - host_redirect_t rd; // remote console + host_status_t status; // global host state + game_status_t game; // game manager + instance_t type; // running at + poolhandle_t mempool; // static mempool for misc allocations + poolhandle_t imagepool; // imagelib mempool + poolhandle_t soundpool; // soundlib mempool + string downloadfile; // filename to be downloading + int downloadcount; // how many files remain to downloading + char deferred_cmd[128];// deferred commands + + host_redirect_t rd; // remote console + + void *hWnd; // main window // command line parms - int argc; - char **argv; + char **argv; + int argc; - double realtime; // host.curtime - double frametime; // time between engine frames - double realframetime; // for some system events, e.g. console animations + uint framecount; // global framecount + uint errorframe; // to prevent multiple host error + uint32_t bugcomp; // bug compatibility level, for very "special" games + double realframetime; // for some system events, e.g. console animations + double starttime; // measure time to first frame + double pureframetime; // count of sleeps can be inserted between frames + double force_draw_version_time; - uint framecount; // global framecount + char draw_decals[MAX_DECALS][MAX_QPATH]; // list of unique decal indexes + vec3_t player_mins[MAX_MAP_HULLS]; // 4 hulls allowed + vec3_t player_maxs[MAX_MAP_HULLS]; // 4 hulls allowed - // list of unique decal indexes - char draw_decals[MAX_DECALS][MAX_QPATH]; + // for CL_{Push,Pop}TraceBounds + vec3_t player_mins_backup[MAX_MAP_HULLS]; + vec3_t player_maxs_backup[MAX_MAP_HULLS]; + qboolean trace_bounds_pushed; - vec3_t player_mins[MAX_MAP_HULLS]; // 4 hulls allowed - vec3_t player_maxs[MAX_MAP_HULLS]; // 4 hulls allowed - - void* hWnd; // main window - qboolean allow_console; // allow console in dev-mode or multiplayer game - qboolean allow_console_init; // initial value to allow the console - qboolean key_overstrike; // key overstrike mode - qboolean stuffcmds_pending; // should execute stuff commands - qboolean allow_cheats; // this host will allow cheating - qboolean con_showalways; // show console always (developer and dedicated) - qboolean change_game; // initialize when game is changed - qboolean mouse_visible; // vgui override cursor control - qboolean shutdown_issued; // engine is shutting down - qboolean force_draw_version; // used when fraps is loaded - float force_draw_version_time; - qboolean apply_game_config; // when true apply only to game cvars and ignore all other commands - qboolean apply_opengl_config;// when true apply only to opengl cvars and ignore all other commands - qboolean config_executed; // a bit who indicated was config.cfg already executed e.g. from valve.rc - int sv_cvars_restored; // count of restored server cvars - qboolean crashed; // set to true if crashed - qboolean daemonized; - qboolean enabledll; - qboolean textmode; + qboolean allow_console; // allow console in dev-mode or multiplayer game + qboolean allow_console_init; // initial value to allow the console + qboolean key_overstrike; // key overstrike mode + qboolean stuffcmds_pending; // should execute stuff commands + qboolean allow_cheats; // this host will allow cheating + qboolean change_game; // initialize when game is changed + qboolean mouse_visible; // vgui override cursor control (never change outside Platform_SetCursorType!) + qboolean shutdown_issued; // engine is shutting down + qboolean apply_game_config; // when true apply only to game cvars and ignore all other commands + qboolean apply_opengl_config; // when true apply only to opengl cvars and ignore all other commands + qboolean config_executed; // a bit who indicated was config.cfg already executed e.g. from valve.rc +#if XASH_DLL_LOADER + qboolean enabledll; +#endif + qboolean textmode; // some settings were changed and needs to global update - qboolean userinfo_changed; - qboolean movevars_changed; - qboolean renderinfo_changed; - - char rootdir[MAX_OSPATH]; // member root directory - char rodir[MAX_OSPATH]; // readonly root - char gamefolder[MAX_QPATH]; // it's a default gamefolder - poolhandle_t imagepool; // imagelib mempool - poolhandle_t soundpool; // soundlib mempool - - uint features; // custom features that enables by mod-maker request + qboolean userinfo_changed; + qboolean movevars_changed; + qboolean renderinfo_changed; // for IN_MouseMove() easy access - int window_center_x; - int window_center_y; - - struct decallist_s *decalList; // used for keep decals, when renderer is restarted or changed - int numdecals; - - // bug compatibility level, for very "special" games - bugcomp_t bugcomp; - + int window_center_x; + int window_center_y; + string gamedll; + string clientlib; + string menulib; } host_parm_t; extern host_parm_t host; -extern sysinfo_t SI; -#define CMD_SERVERDLL BIT( 0 ) // added by server.dll -#define CMD_CLIENTDLL BIT( 1 ) // added by client.dll -#define CMD_GAMEUIDLL BIT( 2 ) // added by GameUI.dll -#define CMD_PRIVILEGED BIT( 3 ) // only available in privileged mode -#define CMD_FILTERABLE BIT( 4 ) // filtered in unprivileged mode if cl_filterstuffcmd is 1 -#define CMD_REFDLL BIT( 5 ) // added by ref.dll +#define CMD_SERVERDLL BIT( 0 ) // added by server.dll +#define CMD_CLIENTDLL BIT( 1 ) // added by client.dll +#define CMD_GAMEUIDLL BIT( 2 ) // added by GameUI.dll +#define CMD_PRIVILEGED BIT( 3 ) // only available in privileged mode +#define CMD_FILTERABLE BIT( 4 ) // filtered in unprivileged mode if cl_filterstuffcmd is 1 +#define CMD_REFDLL BIT( 5 ) // added by ref.dll +#define CMD_OVERRIDABLE BIT( 6 ) // can be removed by DLLs if name matches typedef void (*xcommand_t)( void ); -// -// filesystem_engine.c -// -qboolean FS_LoadProgs( void ); -void FS_Init( void ); -void FS_Shutdown( void ); - -// -// cmd.c -// -void Cbuf_Init( void ); -void Cbuf_Clear( void ); -void Cbuf_AddText( const char *text ); -void Cbuf_AddFilteredText( const char *text ); -void Cbuf_InsertText( const char *text ); -void Cbuf_ExecStuffCmds( void ); -void Cbuf_Execute (void); -qboolean Cmd_CurrentCommandIsPrivileged( void ); -int Cmd_Argc( void ); -const char *Cmd_Args( void ); -const char *Cmd_Argv( int arg ); -void Cmd_Init( void ); -void Cmd_Unlink( int group ); -void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ); -void Cmd_AddRestrictedCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ); -void Cmd_AddServerCommand( const char *cmd_name, xcommand_t function ); -int Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ); -int Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ); -int Cmd_AddRefCommand( const char *cmd_name, xcommand_t function, const char *description ); -void Cmd_RemoveCommand( const char *cmd_name ); -qboolean Cmd_Exists( const char *cmd_name ); -void Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback ); -int Cmd_ListMaps( search_t *t , char *lastmapname, size_t len ); -qboolean Cmd_GetMapList( const char *s, char *completedname, int length ); -qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ); -qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ); -void Cmd_TokenizeString( const char *text ); -void Cmd_ExecuteString( const char *text ); -void Cmd_ForwardToServer( void ); -void Cmd_Escape( char *newCommand, const char *oldCommand, int len ); - // // zone.c // void Memory_Init( void ); -void *_Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); -void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); -poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ); +void _Mem_Free( void *data, const char *filename, int fileline ); +void *_Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 3 ) WARN_UNUSED_RESULT; +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; +poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) + WARN_UNUSED_RESULT; void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ); void _Mem_EmptyPool( poolhandle_t poolptr, const char *filename, int fileline ); -void _Mem_Free( void *data, const char *filename, int fileline ); void _Mem_Check( const char *filename, int fileline ); qboolean Mem_IsAllocatedExt( poolhandle_t poolptr, void *data ); void Mem_PrintList( size_t minallocationsize ); @@ -474,6 +395,92 @@ void Mem_PrintStats( void ); #define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) #define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) +// +// filesystem_engine.c +// +void FS_Init( const char *basedir ); +void FS_Shutdown( void ); +void *FS_GetNativeObject( const char *obj ); +int FS_Close( file_t *file ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) + MALLOC_LIKE( FS_Close, 1 ) WARN_UNUSED_RESULT; +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; +void FS_Rescan_f( void ); +void FS_LoadGameInfo( void ); +void FS_SaveVFSConfig( void ); + +// +// cmd.c +// +typedef struct cmd_s cmd_t; + +static inline int GAME_EXPORT Cmd_Argc( void ) +{ + extern int cmd_argc; + return cmd_argc; +} + +static inline const char *GAME_EXPORT RETURNS_NONNULL Cmd_Argv( int arg ) +{ + extern int cmd_argc; + extern char *cmd_argv[MAX_CMD_TOKENS]; + + if((uint)arg >= cmd_argc ) + return ""; + return cmd_argv[arg]; +} + +static inline const char *GAME_EXPORT RETURNS_NONNULL Cmd_Args( void ) +{ + extern const char *cmd_args; + + return cmd_args; +} + +void Cbuf_Clear( void ); +void Cbuf_AddText( const char *text ); +void Cbuf_AddTextf( const char *text, ... ) FORMAT_CHECK( 1 ); +void Cbuf_AddFilteredText( const char *text ); +void Cbuf_InsertText( const char *text ); +void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len ); +void Cbuf_ExecStuffCmds( void ); +void Cbuf_Execute (void); +qboolean Cmd_CurrentCommandIsPrivileged( void ); +void Cmd_Init( void ); +void Cmd_Shutdown( void ); +void Cmd_Unlink( int group ); +int Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd_desc, int flags, const char *funcname ); + +static inline int Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) +{ + return Cmd_AddCommandEx( cmd_name, function, cmd_desc, 0, __func__ ); +} + +static inline int Cmd_AddRestrictedCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) +{ + return Cmd_AddCommandEx( cmd_name, function, cmd_desc, CMD_PRIVILEGED, __func__ ); +} + +static inline int Cmd_AddCommandWithFlags( const char *cmd_name, xcommand_t function, const char *cmd_desc, int flags ) +{ + return Cmd_AddCommandEx( cmd_name, function, cmd_desc, flags, __func__ ); +} + +void Cmd_RemoveCommand( const char *cmd_name ); +cmd_t *Cmd_Exists( const char *cmd_name ); +void Cmd_LookupCmds( void *buffer, void *ptr, setpair_t callback ); +int Cmd_ListMaps( search_t *t , char *lastmapname, size_t len ); +void Cmd_TokenizeString( const char *text ); +void Cmd_ExecuteString( const char *text ); +void Cmd_ForwardToServer( void ); +void Cmd_Escape( char *newCommand, const char *oldCommand, int len ); + + // // imagelib // @@ -483,20 +490,19 @@ void Image_Setup( void ); void Image_Init( void ); void Image_Shutdown( void ); void Image_AddCmdFlags( uint flags ); -rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ); -qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ); -rgbdata_t *FS_CopyImage( rgbdata_t *in ); void FS_FreeImage( rgbdata_t *pack ); +rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) MALLOC_LIKE( FS_FreeImage, 1 ) WARN_UNUSED_RESULT; +qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ); +rgbdata_t *FS_CopyImage( rgbdata_t *in ) MALLOC_LIKE( FS_FreeImage, 1 ) WARN_UNUSED_RESULT; extern const bpc_desc_t PFDesc[]; // image get pixelformat qboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, float reserved ); void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int pal_size ); -void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size ); void Image_SetForceFlags( uint flags ); // set image force flags on loading -size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ); qboolean Image_CustomPalette( void ); void Image_ClearForceFlags( void ); void Image_SetMDLPointer( byte *p ); void Image_CheckPaletteQ1( void ); + /* ======================================================================== @@ -505,62 +511,56 @@ internal sound format typically expanded to wav buffer ======================================================================== */ -typedef enum +typedef enum sndformat_e { WF_UNKNOWN = 0, WF_PCMDATA, WF_MPGDATA, + WF_VORBISDATA, + WF_OPUSDATA, WF_TOTALCOUNT, // must be last } sndformat_t; -// soundlib global settings -typedef enum -{ - SL_USE_LERPING = BIT(0), // lerping sounds during resample - SL_KEEP_8BIT = BIT(1), // don't expand 8bit sounds automatically up to 16 bit - SL_ALLOW_OVERWRITE = BIT(2), // allow to overwrite stored sounds -} slFlags_t; - // wavdata output flags -typedef enum +typedef enum sndFlags_e { // wavdata->flags SOUND_LOOPED = BIT( 0 ), // this is looped sound (contain cue markers) SOUND_STREAM = BIT( 1 ), // this is a streaminfo, not a real sound // Sound_Process manipulation flags - SOUND_RESAMPLE = BIT(12), // resample sound to specified rate - SOUND_CONVERT16BIT = BIT(13), // change sound resolution from 8 bit to 16 + SOUND_RESAMPLE = BIT( 12 ), // resample sound to specified rate } sndFlags_t; -typedef struct +typedef struct wavdata_s { - word rate; // num samples per second (e.g. 11025 - 11 khz) - byte width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) - byte channels; // num channels (1 - mono, 2 - stereo) - int loopStart; // offset at this point sound will be looping while playing more than only once - int samples; // total samplecount in wav - uint type; // compression type - uint flags; // misc sound flags - byte *buffer; // sound buffer - size_t size; // for bounds checking + size_t size; // for bounds checking + uint loopStart; // offset at this point sound will be looping while playing more than only once + uint samples; // total samplecount in wav + uint type; // compression type + uint flags; // misc sound flags + word rate; // num samples per second (e.g. 11025 - 11 khz) + byte width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) + byte channels; // num channels (1 - mono, 2 - stereo) + byte buffer[]; // sound buffer } wavdata_t; // // soundlib // +typedef struct stream_s stream_t; void Sound_Init( void ); void Sound_Shutdown( void ); -wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ); void FS_FreeSound( wavdata_t *pack ); -stream_t *FS_OpenStream( const char *filename ); -wavdata_t *FS_StreamInfo( stream_t *stream ); +void FS_FreeStream( stream_t *stream ); +wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) MALLOC_LIKE( FS_FreeSound, 1 ) WARN_UNUSED_RESULT; +stream_t *FS_OpenStream( const char *filename ) MALLOC_LIKE( FS_FreeStream, 1 ) WARN_UNUSED_RESULT; int FS_ReadStream( stream_t *stream, int bytes, void *buffer ); int FS_SetStreamPos( stream_t *stream, int newpos ); int FS_GetStreamPos( stream_t *stream ); -void FS_FreeStream( stream_t *stream ); -qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); +qboolean Sound_Process( wavdata_t **wav, int rate, int width, int channels, uint flags ); uint Sound_GetApproxWavePlayLen( const char *filepath ); +qboolean Sound_SupportedFileFormat( const char *fileext ); // // host.c @@ -568,24 +568,19 @@ uint Sound_GetApproxWavePlayLen( const char *filepath ); typedef void( *pfnChangeGame )( const char *progname ); qboolean Host_IsQuakeCompatible( void ); -void EXPORT Host_Shutdown( void ); +void Host_ShutdownWithReason( const char *reason ); int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); -int Host_CompareFileTime( int ft1, int ft2 ); -void Host_NewInstance( const char *name, const char *finalmsg ); -void Host_EndGame( qboolean abort, const char *message, ... ) _format( 2 ); +void Host_EndGame( qboolean abort, const char *message, ... ) FORMAT_CHECK( 2 ); void Host_AbortCurrentFrame( void ) NORETURN; void Host_WriteServerConfig( const char *name ); void Host_WriteOpenGLConfig( void ); void Host_WriteVideoConfig( void ); void Host_WriteConfig( void ); -qboolean Host_IsLocalGame( void ); -qboolean Host_IsLocalClient( void ); -void Host_ShutdownServer( void ); -void Host_Error( const char *error, ... ) _format( 1 ); -void Host_PrintEngineFeatures( void ); -void Host_Frame( float time ); -void Host_InitDecals( void ); +void Host_Error( const char *error, ... ) FORMAT_CHECK( 1 ); +void Host_ValidateEngineFeatures( uint32_t mask, uint32_t features ); +void Host_Frame( double time ); void Host_Credits( void ); +void Host_ExitInMain( void ); // // host_state.c @@ -595,7 +590,7 @@ void COM_NewGame( char const *pMapName ); void COM_LoadLevel( char const *pMapName, qboolean background ); void COM_LoadGame( char const *pSaveFileName ); void COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background ); -void COM_Frame( float time ); +void COM_Frame( double time ); /* ============================================================== @@ -604,11 +599,19 @@ CLIENT / SERVER SYSTEMS ============================================================== */ +#if !XASH_DEDICATED void CL_Init( void ); void CL_Shutdown( void ); void Host_ClientBegin( void ); void Host_ClientFrame( void ); int CL_Active( void ); +#else +static inline void CL_Init( void ) { } +static inline void CL_Shutdown( void ) { } +static inline void Host_ClientBegin( void ) { Cbuf_Execute(); } +static inline void Host_ClientFrame( void ) { } +static inline int CL_Active( void ) { return 0; } +#endif void SV_Init( void ); void SV_Shutdown( const char *finalmsg ); @@ -623,14 +626,11 @@ qboolean SV_Active( void ); ============================================================== */ -void pfnCvar_RegisterServerVariable( cvar_t *variable ); -void pfnCvar_RegisterEngineVariable( cvar_t *variable ); -cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ); -cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ); char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ); void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ); +byte COM_Nibble( char c ); int COM_SaveFile( const char *filename, const void *data, int len ); -byte* COM_LoadFileForMe( const char *filename, int *pLength ); +byte *COM_LoadFileForMe( const char *filename, int *pLength ) MALLOC_LIKE( free, 1 ); qboolean COM_IsSafeFileToDownload( const char *filename ); cvar_t *pfnCVarGetPointer( const char *szVarName ); int pfnDrawConsoleString( int x, int y, char *string ); @@ -639,19 +639,17 @@ void pfnDrawConsoleStringLen( const char *pText, int *length, int *height ); void *Cache_Check( poolhandle_t mempool, struct cache_user_s *c ); void COM_TrimSpace( const char *source, char *dest ); void pfnGetModelBounds( model_t *mod, float *mins, float *maxs ); -void pfnCVarDirectSet( cvar_t *var, const char *szValue ); int COM_CheckParm( char *parm, char **ppnext ); -void pfnGetGameDir( char *szGetGameDir ); -int pfnDecalIndex( const char *m ); int pfnGetModelType( model_t *mod ); int pfnIsMapValid( char *filename ); -void Con_Reportf( const char *szFmt, ... ) _format( 1 ); -void Con_DPrintf( const char *fmt, ... ) _format( 1 ); -void Con_Printf( const char *szFmt, ... ) _format( 1 ); +void Con_Reportf( const char *szFmt, ... ) FORMAT_CHECK( 1 ); +void Con_DPrintf( const char *fmt, ... ) FORMAT_CHECK( 1 ); +void Con_Printf( const char *szFmt, ... ) FORMAT_CHECK( 1 ); int pfnNumberOfEntities( void ); int pfnIsInGame( void ); float pfnTime( void ); #define copystring( s ) _copystring( host.mempool, s, __FILE__, __LINE__ ) +#define copystringpool( pool, s ) _copystring( pool, s, __FILE__, __LINE__ ) #define SV_CopyString( s ) _copystring( svgame.stringspool, s, __FILE__, __LINE__ ) #define freestring( s ) if( s != NULL ) { Mem_Free( s ); s = NULL; } char *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline ); @@ -662,8 +660,6 @@ void *pfnSequencePickSentence( const char *groupName, int pickMethod, int *picke int pfnIsCareerMatch( void ); // Decay engfuncs (stubs) -int pfnGetTimesTutorMessageShown( int mid ); -void pfnRegisterTutorMessageShown( int mid ); void pfnConstructTutorMessageDecayBuffer( int *buffer, int buflen ); void pfnProcessTutorMessageDecayBuffer( int *buffer, int bufferLength ); void pfnResetTutorMessageDecayData( void ); @@ -684,10 +680,11 @@ void pfnResetTutorMessageDecayData( void ); // // con_utils.c // -qboolean Cmd_AutocompleteName( const char *source, int arg, char *buffer, size_t bufsize ); void Con_CompleteCommand( field_t *field ); void Cmd_AutoComplete( char *complete_string ); void Cmd_AutoCompleteClear( void ); +void Host_InitializeConfig( file_t *f, const char *config, const char *description ); +void Host_FinalizeConfig( file_t *f, const char *config ); // // custom.c @@ -705,6 +702,7 @@ int CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename ); // // hpak.c // +const char *COM_ResourceTypeFromIndex( int index ); void HPAK_Init( void ); qboolean HPAK_GetDataPointer( const char *filename, struct resource_s *pRes, byte **buffer, int *size ); qboolean HPAK_ResourceForHash( const char *filename, byte *hash, struct resource_s *pRes ); @@ -725,63 +723,66 @@ void HPAK_FlushHostQueue( void ); #define INPUT_DEVICE_JOYSTICK (1<<2) #define INPUT_DEVICE_VR (1<<3) + +typedef enum connprotocol_e +{ + PROTO_CURRENT = 0, // Xash3D 49 + PROTO_LEGACY, // Xash3D 48 + PROTO_QUAKE, // Quake 15 + PROTO_GOLDSRC, // GoldSrc 48 +} connprotocol_t; + // shared calls struct physent_s; struct sv_client_s; typedef struct sizebuf_s sizebuf_t; -qboolean CL_IsInGame( void ); -qboolean CL_IsInMenu( void ); -qboolean CL_IsInConsole( void ); -qboolean CL_IsThirdPerson( void ); -qboolean CL_IsIntermission( void ); +int SV_GetMaxClients( void ); + +#if !XASH_DEDICATED qboolean CL_Initialized( void ); -char *CL_Userinfo( void ); -void CL_LegacyUpdateInfo( void ); -void CL_CharEvent( int key ); +qboolean CL_IsInGame( void ); +qboolean CL_IsInConsole( void ); +qboolean CL_IsIntermission( void ); qboolean CL_DisableVisibility( void ); -int CL_PointContents( const vec3_t point ); -byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ); -int CL_GetDemoComment( const char *demoname, char *comment ); -void COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ); -int COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); +qboolean CL_IsRecordDemo( void ); +qboolean CL_IsPlaybackDemo( void ); +qboolean UI_CreditsActive( void ); +int CL_GetMaxClients( void ); +#else +static inline qboolean CL_Initialized( void ) { return false; } +static inline qboolean CL_IsInGame( void ) { return true; } // always true for dedicated +static inline qboolean CL_IsInConsole( void ) { return false; } +static inline qboolean CL_IsIntermission( void ) { return false; } +static inline qboolean CL_DisableVisibility( void ) { return false; } +static inline qboolean CL_IsRecordDemo( void ) { return false; } +static inline qboolean CL_IsPlaybackDemo( void ) { return false; } +static inline qboolean UI_CreditsActive( void ) { return false; } +static inline int CL_GetMaxClients( void ) { return SV_GetMaxClients(); } +#endif + +char *CL_Userinfo( void ); +void CL_CharEvent( int key ); +byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ) MALLOC_LIKE( free, 1 ); struct cmd_s *Cmd_GetFirstFunctionHandle( void ); struct cmd_s *Cmd_GetNextFunctionHandle( struct cmd_s *cmd ); struct cmdalias_s *Cmd_AliasGetList( void ); const char *Cmd_GetName( struct cmd_s *cmd ); -struct pmtrace_s *PM_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ); -void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ); -void SV_StartMusic( const char *curtrack, const char *looptrack, int position ); -void SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ); -void Log_Printf( const char *fmt, ... ) _format( 1 ); -void SV_BroadcastCommand( const char *fmt, ... ) _format( 1 ); -qboolean SV_RestoreCustomDecal( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent ); -void SV_BroadcastPrintf( struct sv_client_s *ignore, const char *fmt, ... ) _format( 2 ); -int R_CreateDecalList( struct decallist_s *pList ); -void R_ClearAllDecals( void ); +void Log_Printf( const char *fmt, ... ) FORMAT_CHECK( 1 ); +void SV_BroadcastCommand( const char *fmt, ... ) FORMAT_CHECK( 1 ); +void SV_BroadcastPrintf( struct sv_client_s *ignore, const char *fmt, ... ) FORMAT_CHECK( 2 ); void CL_ClearStaticEntities( void ); -qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); -struct cl_entity_s *CL_GetEntityByIndex( int index ); -void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) _format( 2 ); +qboolean S_StreamGetCurrentState( char *currentTrack, size_t currentTrackSize, char *loopTrack, size_t loopTrackSize, int *position ); +void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void CL_UpdateInfo( const char *key, const char *value ); void CL_HudMessage( const char *pMessage ); const char *CL_MsgInfo( int cmd ); void SV_DrawDebugTriangles( void ); void SV_DrawOrthoTriangles( void ); double CL_GetDemoFramerate( void ); -qboolean UI_CreditsActive( void ); void CL_StopPlayback( void ); -void CL_ExtraUpdate( void ); -int CL_GetMaxClients( void ); -int SV_GetMaxClients( void ); -qboolean CL_IsRecordDemo( void ); -qboolean CL_IsTimeDemo( void ); -qboolean CL_IsPlaybackDemo( void ); -qboolean CL_IsBackgroundDemo( void ); -qboolean CL_IsBackgroundMap( void ); qboolean SV_Initialized( void ); -qboolean CL_LoadProgs( const char *name ); void CL_ProcessFile( qboolean successfully_received, const char *filename ); int SV_GetSaveComment( const char *savename, char *comment ); -qboolean SV_NewGame( const char *mapName, qboolean loadGame ); void SV_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); void CL_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); void SV_SysError( const char *error_string ); @@ -789,14 +790,10 @@ void SV_ShutdownGame( void ); void SV_ExecLoadLevel( void ); void SV_ExecLoadGame( void ); void SV_ExecChangeLevel( void ); -void SV_InitGameProgs( void ); -void SV_FreeGameProgs( void ); void CL_WriteMessageHistory( void ); -void CL_SendCmd( void ); void CL_Disconnect( void ); void CL_ClearEdicts( void ); void CL_Crashed( void ); -qboolean CL_NextDemo( void ); char *SV_Serverinfo( void ); void CL_Drop( void ); void Con_Init( void ); @@ -804,43 +801,76 @@ void SCR_Init( void ); void SCR_UpdateScreen( void ); void SCR_BeginLoadingPlaque( qboolean is_background ); void SCR_CheckStartupVids( void ); -int SCR_GetAudioChunk( char *rawdata, int length ); -wavdata_t *SCR_GetMovieInfo( void ); void SCR_Shutdown( void ); void Con_Print( const char *txt ); -void Con_NPrintf( int idx, const char *fmt, ... ) _format( 2 ); -void Con_NXPrintf( con_nprint_t *info, const char *fmt, ... ) _format( 2 ); -void UI_NPrintf( int idx, const char *fmt, ... ) _format( 2 ); -void UI_NXPrintf( con_nprint_t *info, const char *fmt, ... ) _format( 2 ); -const char *Info_ValueForKey( const char *s, const char *key ); +void Con_NPrintf( int idx, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void Con_NXPrintf( con_nprint_t *info, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void UI_NPrintf( int idx, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void UI_NXPrintf( con_nprint_t *info, const char *fmt, ... ) FORMAT_CHECK( 2 ); +const char *Info_ValueForKey( const char *s, const char *key ) RETURNS_NONNULL NONNULL; void Info_RemovePrefixedKeys( char *start, char prefix ); qboolean Info_RemoveKey( char *s, const char *key ); qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize ); +qboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... ) FORMAT_CHECK( 4 ); qboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize ); qboolean Info_IsValid( const char *s ); void Info_WriteVars( file_t *f ); void Info_Print( const char *s ); -void Cmd_WriteVariables( file_t *f ); int Cmd_CheckMapsList( int fRefresh ); void COM_SetRandomSeed( int lSeed ); int COM_RandomLong( int lMin, int lMax ); float COM_RandomFloat( float fMin, float fMax ); -qboolean LZSS_IsCompressed( const byte *source ); -uint LZSS_GetActualSize( const byte *source ); +qboolean LZSS_IsCompressed( const byte *source, size_t input_len ); +uint LZSS_GetActualSize( const byte *source, size_t input_len ); byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize ); -uint LZSS_Decompress( const byte *pInput, byte *pOutput ); +uint LZSS_Decompress( const byte *pInput, byte *pOutput, size_t input_len, size_t output_len ); void GL_FreeImage( const char *name ); void VID_InitDefaultResolution( void ); void VID_Init( void ); void UI_SetActiveMenu( qboolean fActive ); void UI_ShowConnectionWarning( void ); void Cmd_Null_f( void ); -void Rcon_Print( const char *pMsg ); +void Rcon_Print( host_redirect_t *rd, const char *pMsg ); qboolean COM_ParseVector( char **pfile, float *v, size_t size ); -void COM_NormalizeAngles( vec3_t angles ); int COM_FileSize( const char *filename ); void COM_FreeFile( void *buffer ); -int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); +int pfnCompareFileTime( const char *path1, const char *path2, int *retval ); +char *va( const char *format, ... ) FORMAT_CHECK( 1 ) RETURNS_NONNULL; +qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer ); + +static inline void COM_NormalizeAngles( vec3_t angles ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( angles[i] > 180.0f ) + angles[i] -= 360.0f; + else if( angles[i] < -180.0f ) + angles[i] += 360.0f; + } +} + +#if !XASH_DEDICATED +connprotocol_t CL_Protocol( void ); +#else +static inline connprotocol_t CL_Protocol( void ) +{ + return PROTO_CURRENT; +} +#endif + +static inline qboolean Host_IsLocalGame( void ) +{ + if( SV_Active( )) + return SV_GetMaxClients() == 1 ? true : false; + return CL_GetMaxClients() == 1 ? true : false; +} + +static inline qboolean Host_IsLocalClient( void ) +{ + return CL_Initialized( ) && SV_Initialized( ) ? true : false; +} // soundlib shared exports qboolean S_Init( void ); @@ -851,16 +881,14 @@ void S_StopBackgroundTrack( void ); void S_StopAllSounds( qboolean ambient ); // gamma routines -void BuildGammaTable( float gamma, float brightness ); byte LightToTexGamma( byte b ); -byte TextureToGamma( byte b ); - -// -// identification.c -// -void ID_Init( void ); -const char *ID_GetMD5( void ); -void GAME_EXPORT ID_SetCustomClientID( const char *id ); +byte TextureToGamma( byte ); +uint ScreenGammaTable( uint ); +uint LinearGammaTable( uint ); +void V_Init( void ); +void V_CheckGamma( void ); +void V_CheckGammaEnd( void ); +intptr_t V_GetGammaPtr( int parm ); // // masterlist.c @@ -869,6 +897,48 @@ void NET_InitMasters( void ); void NET_SaveMasters( void ); qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ); qboolean NET_IsMasterAdr( netadr_t adr ); +void NET_MasterHeartbeat( void ); +void NET_MasterClear( void ); +void NET_MasterShutdown( void ); +qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat ); + +// +// munge.c +// +void COM_Munge( byte *data, size_t len, int seq ); +void COM_UnMunge( byte *data, size_t len, int seq ); +void COM_Munge2( byte *data, size_t len, int seq ); +void COM_UnMunge2( byte *data, size_t len, int seq ); +void COM_Munge3( byte *data, size_t len, int seq ); +void COM_UnMunge3( byte *data, size_t len, int seq ); + +// +// sounds.c +// +typedef enum soundlst_group_e +{ + BouncePlayerShell = 0, + BounceWeaponShell, + BounceConcrete, + BounceGlass, + BounceMetal, + BounceFlesh, + BounceWood, + Ricochet, + Explode, + PlayerWaterEnter, + PlayerWaterExit, + EntityWaterEnter, + EntityWaterExit, + + SoundList_Groups // must be last +} soundlst_group_t; + +int SoundList_Count( soundlst_group_t group ); +const char *SoundList_GetRandom( soundlst_group_t group ); +const char *SoundList_Get( soundlst_group_t group, int idx ); +void SoundList_Init( void ); +void SoundList_Shutdown( void ); #ifdef REF_DLL #error "common.h in ref_dll" diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index 8bafcb31..06cea9db 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -18,8 +18,6 @@ GNU General Public License for more details. #include "const.h" #include "kbutton.h" -extern convar_t *con_gamemaps; - #define CON_MAXCMDS 4096 // auto-complete intermediate list typedef struct autocomplete_list_s @@ -76,12 +74,13 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) compiler[0] = '\0'; generator[0] = '\0'; - f = FS_Open( t->filenames[i], "rb", con_gamemaps->value ); + f = FS_Open( t->filenames[i], "rb", con_gamemaps.value ); if( f ) { - dheader_t *header; + dheader_t *header; dextrahdr_t *hdrext; + dlump_t entities; memset( buf, 0, sizeof( buf )); FS_Read( f, buf, sizeof( buf )); @@ -89,10 +88,10 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) ver = header->version; // check all the lumps and some other errors - if( Mod_TestBmodelLumps( t->filenames[i], buf, true )) + if( Mod_TestBmodelLumps( f, t->filenames[i], buf, true, &entities )) { - lumpofs = header->lumps[LUMP_ENTITIES].fileofs; - lumplen = header->lumps[LUMP_ENTITIES].filelen; + lumpofs = entities.fileofs; + lumplen = entities.filelen; ver = header->version; } @@ -100,8 +99,7 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) if( hdrext->id == IDEXTRAHEADER ) version = hdrext->version; Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); - COM_StripExtension( entfilename ); - COM_DefaultExtension( entfilename, ".ent" ); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); ents = (char *)FS_LoadFile( entfilename, NULL, true ); if( !ents && lumplen >= 10 ) @@ -145,7 +143,7 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) } if( f ) FS_Close(f); - COM_FileBase( t->filenames[i], mapname ); + COM_FileBase( t->filenames[i], mapname, sizeof( mapname )); switch( ver ) { @@ -184,16 +182,16 @@ Cmd_GetMapList Prints or complete map filename ===================================== */ -qboolean Cmd_GetMapList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetMapList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; int i, nummaps; - t = FS_Search( va( "maps/%s*.bsp", s ), true, con_gamemaps->value ); + t = FS_Search( va( "maps/%s*.bsp", s ), true, con_gamemaps.value ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -220,7 +218,7 @@ Cmd_GetDemoList Prints or complete demo filename ===================================== */ -qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -230,7 +228,7 @@ qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.dem", s ), true, true ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -240,7 +238,7 @@ qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "dem" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numdems++; } @@ -267,7 +265,7 @@ Cmd_GetMovieList Prints or complete movie filename ===================================== */ -qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -276,7 +274,7 @@ qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) t = FS_Search( va( "media/%s*.avi", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -286,7 +284,7 @@ qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "avi" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); nummovies++; } @@ -314,7 +312,7 @@ Cmd_GetMusicList Prints or complete background track filename ===================================== */ -qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -323,7 +321,7 @@ qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) t = FS_Search( va( "media/%s*.*", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -335,7 +333,7 @@ qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) if( Q_stricmp( ext, "wav" ) && Q_stricmp( ext, "mp3" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numtracks++; } @@ -362,16 +360,16 @@ Cmd_GetSavesList Prints or complete savegame filename ===================================== */ -qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; int i, numsaves; - t = FS_Search( va( "%s%s*.sav", DEFAULT_SAVE_DIRECTORY, s ), true, true ); // lookup only in gamedir + t = FS_Search( va( DEFAULT_SAVE_DIRECTORY "%s*.sav", s ), true, true ); // lookup only in gamedir if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -381,7 +379,7 @@ qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "sav" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numsaves++; } @@ -409,7 +407,7 @@ Cmd_GetConfigList Prints or complete .cfg filename ===================================== */ -qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -418,7 +416,7 @@ qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.cfg", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -428,7 +426,7 @@ qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "cfg" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numconfigs++; } @@ -456,7 +454,7 @@ Cmd_GetSoundList Prints or complete sound filename ===================================== */ -qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -465,7 +463,7 @@ qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s%s*.*", DEFAULT_SOUNDPATH, s ), true, false ); if( !t ) return false; - Q_strncpy( matchbuf, t->filenames[0] + sizeof( DEFAULT_SOUNDPATH ) - 1, MAX_STRING ); + Q_strncpy( matchbuf, t->filenames[0] + sizeof( DEFAULT_SOUNDPATH ) - 1, sizeof( matchbuf )); COM_StripExtension( matchbuf ); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); @@ -478,7 +476,7 @@ qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) if( Q_stricmp( ext, "wav" ) && Q_stricmp( ext, "mp3" )) continue; - Q_strncpy( matchbuf, t->filenames[i] + sizeof( DEFAULT_SOUNDPATH ) - 1, MAX_STRING ); + Q_strncpy( matchbuf, t->filenames[i] + sizeof( DEFAULT_SOUNDPATH ) - 1, sizeof( matchbuf )); COM_StripExtension( matchbuf ); Con_Printf( "%16s\n", matchbuf ); numsounds++; @@ -500,7 +498,6 @@ qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) return true; } -#if !XASH_DEDICATED /* ===================================== Cmd_GetItemsList @@ -508,8 +505,9 @@ Cmd_GetItemsList Prints or complete item classname (weapons only) ===================================== */ -qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) { +#if !XASH_DEDICATED search_t *t; string matchbuf; int i, numitems; @@ -518,17 +516,17 @@ qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s/%s*.txt", clgame.itemspath, s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; - for(i = 0, numitems = 0; i < t->numfilenames; i++) + for( i = 0, numitems = 0; i < t->numfilenames; i++ ) { if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "txt" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numitems++; } @@ -546,6 +544,8 @@ qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) } } return true; +#endif // !XASH_DEDICATED + return false; } /* @@ -555,8 +555,9 @@ Cmd_GetKeysList Autocomplete for bind command ===================================== */ -qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) { +#if !XASH_DEDICATED size_t i, numkeys; string keys[256]; string matchbuf; @@ -570,7 +571,7 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) const char *keyname = Key_KeynumToString( i ); if(( *s == '*' ) || !Q_strnicmp( keyname, s, len)) - Q_strcpy( keys[numkeys++], keyname ); + Q_strncpy( keys[numkeys++], keyname, sizeof( keys[0] )); } if( !numkeys ) return false; @@ -585,7 +586,7 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) Con_Printf( "%16s\n", matchbuf ); } - Con_Printf( "\n^3 %lu keys found.\n", numkeys ); + Con_Printf( "\n^3 %zu keys found.\n", numkeys ); if( completedname && length ) { @@ -597,8 +598,9 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) } return true; +#endif // !XASH_DEDICATED + return false; } -#endif // XASH_DEDICATED /* =============== @@ -606,13 +608,20 @@ Con_AddCommandToList =============== */ -static void Con_AddCommandToList( const char *s, const char *unused1, const char *unused2, void *_autocompleteList ) +static void Con_AddCommandToList( const char *s, const char *value, const void *ptoggle, void *_autocompleteList ) { con_autocomplete_t *list = (con_autocomplete_t*)_autocompleteList; + qboolean toggle = ptoggle != NULL && *(qboolean *)ptoggle; if( *s == '@' ) return; // never show system cvars or cmds if( list->matchCount >= CON_MAXCMDS ) return; // list is full + if( toggle ) + { + if( Q_strcmp( value, "0" ) && Q_strcmp( value, "1" )) + return; // exclude non-toggable cvars + } + if( Q_strnicmp( s, list->completionString, Q_strlen( list->completionString ) ) ) return; // no match @@ -636,13 +645,11 @@ Cmd_GetCommandsList Autocomplete for bind command ===================================== */ -qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetCommandsAndCvarsList( const char *s, char *completedname, int length, qboolean cmds, qboolean cvars, qboolean toggle ) { size_t i; string matchbuf; - con_autocomplete_t list; // local autocomplete list - - memset( &list, 0, sizeof( list )); + con_autocomplete_t list = { 0 }; // local autocomplete list list.completionString = s; @@ -654,8 +661,16 @@ qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length ) return false; // find matching commands and variables - Cmd_LookupCmds( NULL, &list, (setpair_t)Con_AddCommandToList ); - Cvar_LookupVars( 0, NULL, &list, (setpair_t)Con_AddCommandToList ); + if( cvars ) + { + Cvar_LookupVars( 0, &toggle, &list, (setpair_t)Con_AddCommandToList ); + } + + if( cmds ) + { + toggle = false; + Cmd_LookupCmds( &toggle, &list, (setpair_t)Con_AddCommandToList ); + } if( !list.matchCount ) return false; Q_strncpy( matchbuf, list.cmds[0], sizeof( matchbuf )); @@ -671,7 +686,7 @@ qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length ) Con_Printf( "%16s\n", matchbuf ); } - Con_Printf( "\n^3 %i commands found.\n", list.matchCount ); + Con_Printf( "\n^3 %i %s found.\n", list.matchCount, cmds ? "commands" : "variables" ); if( completedname && length ) { @@ -693,6 +708,30 @@ qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length ) return true; } +/* +===================================== +Cmd_GetCommandsList + +Autocomplete for bind command +===================================== +*/ +static qboolean Cmd_GetCommandsList( const char *s, char *completedname, int length ) +{ + return Cmd_GetCommandsAndCvarsList( s, completedname, length, true, true, false ); +} + +/* +===================================== +Cmd_GetCvarList + +Autocomplete for bind command +===================================== +*/ +static qboolean Cmd_GetCvarsList( const char *s, char *completedname, int length ) +{ + qboolean toggle = !Q_stricmp( Cmd_Argv( 0 ), "toggle" ); + return Cmd_GetCommandsAndCvarsList( s, completedname, length, false, true, toggle ); +} /* ===================================== @@ -701,7 +740,7 @@ Cmd_GetCustomList Prints or complete .HPK filenames ===================================== */ -qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) { search_t *t; string matchbuf; @@ -710,7 +749,7 @@ qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.hpk", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -720,7 +759,7 @@ qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "hpk" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numitems++; } @@ -747,35 +786,31 @@ Cmd_GetGameList Prints or complete gamedir name ===================================== */ -qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) { int i, numgamedirs; string gamedirs[MAX_MODS]; string matchbuf; int len; - // stand-alone games doesn't have cmd "game" - if( !Cmd_Exists( "game" )) - return false; - // compare gamelist with current keyword len = Q_strlen( s ); for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); + Q_strncpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder, sizeof( gamedirs[0] )); } if( !numgamedirs ) return false; - Q_strncpy( matchbuf, gamedirs[0], MAX_STRING ); + Q_strncpy( matchbuf, gamedirs[0], sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( numgamedirs == 1 ) return true; for( i = 0; i < numgamedirs; i++ ) { - Q_strncpy( matchbuf, gamedirs[i], MAX_STRING ); + Q_strncpy( matchbuf, gamedirs[i], sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); } @@ -800,7 +835,7 @@ Cmd_GetCDList Prints or complete CD command name ===================================== */ -qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) +static qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) { int i, numcdcommands; string cdcommands[8]; @@ -825,18 +860,18 @@ qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) for( i = 0, numcdcommands = 0; i < 8; i++ ) { if(( *s == '*' ) || !Q_strnicmp( cd_command[i], s, len)) - Q_strcpy( cdcommands[numcdcommands++], cd_command[i] ); + Q_strncpy( cdcommands[numcdcommands++], cd_command[i], sizeof( cdcommands[0] )); } if( !numcdcommands ) return false; - Q_strncpy( matchbuf, cdcommands[0], MAX_STRING ); + Q_strncpy( matchbuf, cdcommands[0], sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( numcdcommands == 1 ) return true; for( i = 0; i < numcdcommands; i++ ) { - Q_strncpy( matchbuf, cdcommands[i], MAX_STRING ); + Q_strncpy( matchbuf, cdcommands[i], sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); } @@ -854,12 +889,13 @@ qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) return true; } -qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) +static qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) { qboolean use_filter = false; byte buf[MAX_SYSPATH]; string mpfilter; char *buffer; + size_t buffersize; string result; int i, size; search_t *t; @@ -869,7 +905,7 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) return true; // exist // setup mpfilter - size = Q_snprintf( mpfilter, sizeof( mpfilter ), "maps/%s", GI->mp_filter ); + Q_snprintf( mpfilter, sizeof( mpfilter ), "maps/%s", GI->mp_filter ); t = FS_Search( "maps/*.bsp", false, onlyingamedir ); if( !t ) @@ -882,7 +918,8 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) return false; } - buffer = Mem_Calloc( host.mempool, t->numfilenames * 2 * sizeof( result )); + buffersize = t->numfilenames * 2 * sizeof( result ); + buffer = Mem_Calloc( host.mempool, buffersize ); use_filter = COM_CheckStringEmpty( GI->mp_filter ) ? true : false; for( i = 0; i < t->numfilenames; i++ ) @@ -898,31 +935,29 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) continue; f = FS_Open( t->filenames[i], "rb", onlyingamedir ); - COM_FileBase( t->filenames[i], mapname ); + COM_FileBase( t->filenames[i], mapname, sizeof( mapname )); if( f ) { - int num_spawnpoints = 0; - dheader_t *header; + qboolean have_spawnpoints = false; + dlump_t entities; memset( buf, 0, MAX_SYSPATH ); FS_Read( f, buf, MAX_SYSPATH ); - header = (dheader_t *)buf; // check all the lumps and some other errors - if( !Mod_TestBmodelLumps( t->filenames[i], buf, true )) + if( !Mod_TestBmodelLumps( f, t->filenames[i], buf, true, &entities )) { FS_Close( f ); continue; } // after call Mod_TestBmodelLumps we gurantee what map is valid - lumpofs = header->lumps[LUMP_ENTITIES].fileofs; - lumplen = header->lumps[LUMP_ENTITIES].filelen; + lumpofs = entities.fileofs; + lumplen = entities.filelen; Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); - COM_StripExtension( entfilename ); - COM_DefaultExtension( entfilename, ".ent" ); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); ents = (char *)FS_LoadFile( entfilename, NULL, true ); if( !ents && lumplen >= 10 ) @@ -939,13 +974,24 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) char token[MAX_TOKEN]; qboolean worldspawn = true; - Q_strncpy( message, "No Title", MAX_STRING ); + Q_strncpy( message, "No Title", sizeof( message )); pfile = ents; while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( token[0] == '}' && worldspawn ) + { worldspawn = false; + + // if mod has mp_filter set up, then it's a mod that + // might not have valid mp_entity set in GI + // if mod is multiplayer only, assume all maps are valid + if( use_filter || GI->gamemode == GAME_MULTIPLAYER_ONLY ) + { + have_spawnpoints = true; + break; + } + } else if( !Q_strcmp( token, "message" ) && worldspawn ) { // get the message contents @@ -954,21 +1000,27 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) else if( !Q_strcmp( token, "classname" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - if( !Q_strcmp( token, GI->mp_entity ) || use_filter ) - num_spawnpoints++; + + if( !Q_strcmp( token, GI->mp_entity )) + { + have_spawnpoints = true; + break; + } } - if( num_spawnpoints ) break; // valid map + + if( have_spawnpoints ) + break; // valid map } Mem_Free( ents ); } if( f ) FS_Close( f ); - if( num_spawnpoints ) + if( have_spawnpoints ) { // format: mapname "maptitle"\n - Q_sprintf( result, "%s \"%s\"\n", mapname, message ); - Q_strcat( buffer, result ); // add new string + Q_snprintf( result, sizeof( result ), "%s \"%s\"\n", mapname, message ); + Q_strncat( buffer, result, buffersize ); // add new string } } } @@ -999,35 +1051,38 @@ int GAME_EXPORT Cmd_CheckMapsList( int fRefresh ) return Cmd_CheckMapsList_R( fRefresh, true ); } -autocomplete_list_t cmd_list[] = +// keep this sorted +static const autocomplete_list_t cmd_list[] = { -{ "map_background", 1, Cmd_GetMapList }, +{ "bind", 1, Cmd_GetKeysList }, +{ "bind", 2, Cmd_GetCommandsList }, +{ "cd", 1, Cmd_GetCDList }, { "changelevel2", 1, Cmd_GetMapList }, { "changelevel", 1, Cmd_GetMapList }, -{ "playdemo", 1, Cmd_GetDemoList, }, -{ "timedemo", 1, Cmd_GetDemoList, }, -{ "playvol", 1, Cmd_GetSoundList }, -{ "hpkval", 1, Cmd_GetCustomList }, -{ "hpklist", 1, Cmd_GetCustomList }, -{ "hpkextract", 1, Cmd_GetCustomList }, -{ "entpatch", 1, Cmd_GetMapList }, -{ "music", 1, Cmd_GetMusicList, }, -{ "movie", 1, Cmd_GetMovieList }, -{ "exec", 1, Cmd_GetConfigList }, -#if !XASH_DEDICATED -{ "give", 1, Cmd_GetItemsList }, { "drop", 1, Cmd_GetItemsList }, -{ "bind", 1, Cmd_GetKeysList }, -{ "unbind", 1, Cmd_GetKeysList }, -{ "bind", 2, Cmd_GetCommandsList }, -#endif +{ "entpatch", 1, Cmd_GetMapList }, +{ "exec", 1, Cmd_GetConfigList }, { "game", 1, Cmd_GetGamesList }, -{ "save", 1, Cmd_GetSavesList }, +{ "give", 1, Cmd_GetItemsList }, +{ "hpkextract", 1, Cmd_GetCustomList }, +{ "hpklist", 1, Cmd_GetCustomList }, +{ "hpkval", 1, Cmd_GetCustomList }, +{ "listdemo", 1, Cmd_GetDemoList, }, { "load", 1, Cmd_GetSavesList }, -{ "play", 1, Cmd_GetSoundList }, { "map", 1, Cmd_GetMapList }, -{ "cd", 1, Cmd_GetCDList }, -{ NULL }, // termiantor +{ "map_background", 1, Cmd_GetMapList }, +{ "movie", 1, Cmd_GetMovieList }, +{ "mp3", 1, Cmd_GetCDList }, +{ "music", 1, Cmd_GetMusicList, }, +{ "play", 1, Cmd_GetSoundList }, +{ "playdemo", 1, Cmd_GetDemoList, }, +{ "playvol", 1, Cmd_GetSoundList }, +{ "reset", 1, Cmd_GetCvarsList }, +{ "save", 1, Cmd_GetSavesList }, +{ "set", 1, Cmd_GetCvarsList }, +{ "timedemo", 1, Cmd_GetDemoList }, +{ "toggle", 1, Cmd_GetCvarsList }, +{ "unbind", 1, Cmd_GetKeysList }, }; /* @@ -1039,10 +1094,14 @@ compare first argument with string */ static qboolean Cmd_CheckName( const char *name ) { - if( !Q_stricmp( Cmd_Argv( 0 ), name )) + const char *p = Cmd_Argv( 0 ); + + if( !Q_stricmp( p, name )) return true; - if( !Q_stricmp( Cmd_Argv( 0 ), va( "\\%s", name ))) + + if( p[0] == '\\' && !Q_stricmp( &p[1], name )) return true; + return false; } @@ -1054,14 +1113,14 @@ Autocomplete filename for various cmds ============ */ -qboolean Cmd_AutocompleteName( const char *source, int arg, char *buffer, size_t bufsize ) +static qboolean Cmd_AutocompleteName( const char *source, int arg, char *buffer, size_t bufsize ) { - autocomplete_list_t *list; + int i; - for( list = cmd_list; list->name; list++ ) + for( i = 0; i < ARRAYSIZE( cmd_list ); i++ ) { - if( list->arg == arg && Cmd_CheckName( list->name )) - return list->func( source, buffer, bufsize ); + if( cmd_list[i].arg == arg && Cmd_CheckName( cmd_list[i].name )) + return cmd_list[i].func( source, buffer, bufsize ); } return false; @@ -1144,6 +1203,7 @@ void Con_CompleteCommand( field_t *field ) { field_t temp; string filename; + qboolean toggle = false; qboolean nextcmd; int i; @@ -1178,12 +1238,12 @@ void Con_CompleteCommand( field_t *field ) con.shortestMatch[0] = 0; // find matching commands and variables - Cmd_LookupCmds( NULL, &con, (setpair_t)Con_AddCommandToList ); - Cvar_LookupVars( 0, NULL, &con, (setpair_t)Con_AddCommandToList ); + Cmd_LookupCmds( &toggle, &con, (setpair_t)Con_AddCommandToList ); + Cvar_LookupVars( 0, &toggle, &con, (setpair_t)Con_AddCommandToList ); if( !con.matchCount ) return; // no matches - memcpy( &temp, con.completionField, sizeof( field_t ) ); + temp = *con.completionField; // autocomplete second arg if( (Cmd_Argc() >= 2) || ((Cmd_Argc() == 1) && nextcmd) ) @@ -1324,8 +1384,9 @@ static void Cmd_WriteHelp(const char *name, const char *unused, const char *desc { int length; - if( !desc || !Q_strcmp( desc, "" )) + if( !COM_CheckString( desc )) return; // ignore fantom cmds + if( name[0] == '+' || name[0] == '-' ) return; // key bindings @@ -1337,12 +1398,19 @@ static void Cmd_WriteHelp(const char *name, const char *unused, const char *desc if( length == 0 ) FS_Printf( f, "%s \"%s\"\n", name, desc ); } -void Cmd_WriteOpenGLVariables( file_t *f ) +static void Cmd_WriteOpenGLVariables( file_t *f ) { Cvar_LookupVars( FCVAR_GLCONFIG, NULL, f, (setpair_t)Cmd_WriteOpenGLCvar ); } -#if !XASH_DEDICATED +void Host_InitializeConfig( file_t *f, const char *config, const char *description ) +{ + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + FS_Printf( f, "//\t\t%s - %s\n", config, description ); + FS_Printf( f, "//=======================================================================\n" ); +} + void Host_FinalizeConfig( file_t *f, const char *config ) { string backup, newcfg; @@ -1354,10 +1422,11 @@ void Host_FinalizeConfig( file_t *f, const char *config ) FS_Close( f ); FS_Delete( backup ); FS_Rename( config, backup ); - FS_Delete( config ); FS_Rename( newcfg, config ); } + +#if !XASH_DEDICATED /* =============== Host_WriteConfig @@ -1377,11 +1446,8 @@ void Host_WriteConfig( void ) f = FS_Open( "config.cfg.new", "w", false ); if( f ) { - Con_Reportf( "Host_WriteConfig()\n" ); - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\t\t\tCopyright XashXT Group & Flying With Gauss %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t\tconfig.cfg - archive of cvars\n" ); - FS_Printf( f, "//=======================================================================\n" ); + Con_Reportf( "%s()\n", __func__ ); + Host_InitializeConfig( f, "config.cfg", "archive of cvars" ); Key_WriteBindings( f ); Cvar_WriteVariables( f, FCVAR_ARCHIVE ); Info_WriteVars( f ); @@ -1405,6 +1471,8 @@ void Host_WriteConfig( void ) else Con_DPrintf( S_ERROR "Couldn't write config.cfg.\n" ); NET_SaveMasters(); + + FS_SaveVFSConfig(); } /* @@ -1421,17 +1489,12 @@ void GAME_EXPORT Host_WriteServerConfig( const char *name ) Q_snprintf( newconfigfile, MAX_STRING, "%s.new", name ); - SV_InitGameProgs(); // collect user variables - // FIXME: move this out until menu parser is done CSCR_LoadDefaultCVars( "settings.scr" ); if(( f = FS_Open( newconfigfile, "w", false )) != NULL ) { - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\t\t\tCopyright XashXT Group & Flying With Gauss %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\tgame.cfg - multiplayer server temporare config\n" ); - FS_Printf( f, "//=======================================================================\n" ); + Host_InitializeConfig( f, "game.cfg", "multiplayer server temporary config" ); Cvar_WriteVariables( f, FCVAR_SERVER ); CSCR_WriteGameCVars( f, "settings.scr" ); @@ -1439,8 +1502,6 @@ void GAME_EXPORT Host_WriteServerConfig( const char *name ) Host_FinalizeConfig( f, name ); } else Con_DPrintf( S_ERROR "Couldn't write %s.\n", name ); - - SV_FreeGameProgs(); // release progs with all variables } /* @@ -1464,12 +1525,8 @@ void Host_WriteOpenGLConfig( void ) f = FS_Open( va( "%s.new", name ), "w", false ); if( f ) { - Con_Reportf( "Host_WriteGLConfig()\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\t\t\tCopyright XashXT Group & Flying With Gauss %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t %s - archive of renderer implementation cvars\n", name ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "\n" ); + Con_Reportf( "%s()\n", __func__ ); + Host_InitializeConfig( f, name, "archive of renderer implementation cvars" ); Cmd_WriteOpenGLVariables( f ); Host_FinalizeConfig( f, name ); @@ -1494,11 +1551,8 @@ void Host_WriteVideoConfig( void ) f = FS_Open( "video.cfg.new", "w", false ); if( f ) { - Con_Reportf( "Host_WriteVideoConfig()\n" ); - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\t\t\tCopyright XashXT Group & Flying With Gauss %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\tvideo.cfg - archive of renderer variables\n"); - FS_Printf( f, "//=======================================================================\n" ); + Con_Reportf( "%s()\n", __func__ ); + Host_InitializeConfig( f, "video.cfg", "archive of renderer variables" ); Cvar_WriteVariables( f, FCVAR_RENDERINFO ); Host_FinalizeConfig( f, "video.cfg" ); } @@ -1521,11 +1575,7 @@ void Key_EnumCmds_f( void ) f = FS_Open( "../help.txt", "w", false ); if( f ) { - FS_Printf( f, "//=======================================================================\n"); - FS_Printf( f, "//\t\t\tCopyright XashXT Group & Flying With Gauss %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\thelp.txt - xash commands and console variables\n"); - FS_Printf( f, "//=======================================================================\n"); - + Host_InitializeConfig( f, "help.txt", "xash commands and console variables" ); FS_Printf( f, "\n\n\t\t\tconsole variables\n\n"); Cvar_LookupVars( 0, NULL, f, (setpair_t)Cmd_WriteHelp ); FS_Printf( f, "\n\n\t\t\tconsole commands\n\n"); diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c deleted file mode 100644 index fac7474a..00000000 --- a/engine/common/crashhandler.c +++ /dev/null @@ -1,478 +0,0 @@ -/* -crashhandler.c - advanced crashhandler -Copyright (C) 2016 Mittorn - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ -#define _GNU_SOURCE - -#include "common.h" - -/* -================ -Sys_Crash - -Crash handler, called from system -================ -*/ -#if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP || XASH_CRASHHANDLER == CRASHHANDLER_WIN32 - -#if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP - -#pragma comment( lib, "dbghelp" ) - -#include -#include -#include - -#ifndef XASH_SDL -typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; -#endif - -static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) -{ - DWORD_PTR baseAddress = 0; - static HMODULE *moduleArray; - static unsigned int moduleCount; - LPBYTE moduleArrayBytes; - DWORD bytesRequired; - int i; - - if( len < 3 ) - return 0; - - if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) - { - if ( bytesRequired ) - { - moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); - - if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) - { - moduleCount = bytesRequired / sizeof( HMODULE ); - moduleArray = (HMODULE *)moduleArrayBytes; - } - } - } - - for( i = 0; i < moduleCount; i++ ) - { - MODULEINFO info; - GetModuleInformation( process, moduleArray[i], &info, sizeof(MODULEINFO) ); - - if( ( address > info.lpBaseOfDll ) && - ( (DWORD64)address < (DWORD64)info.lpBaseOfDll + (DWORD64)info.SizeOfImage ) ) - return GetModuleBaseName( process, moduleArray[i], name, len ); - } - return Q_snprintf( name, len, "???" ); -} - -static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) -{ - char message[1024]; - int len = 0; - size_t i; - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - IMAGEHLP_LINE64 line; - DWORD dline = 0; - DWORD options; - CONTEXT context; - STACKFRAME64 stackframe; - DWORD image; - - memcpy( &context, pInfo->ContextRecord, sizeof( CONTEXT )); - - options = SymGetOptions(); - options |= SYMOPT_DEBUG; - options |= SYMOPT_LOAD_LINES; - SymSetOptions( options ); - - SymInitialize( process, NULL, TRUE ); - - ZeroMemory( &stackframe, sizeof( STACKFRAME64 )); - -#ifdef _M_IX86 - image = IMAGE_FILE_MACHINE_I386; - stackframe.AddrPC.Offset = context.Eip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Ebp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Esp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_X64 - image = IMAGE_FILE_MACHINE_AMD64; - stackframe.AddrPC.Offset = context.Rip; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.Rsp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.Rsp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif _M_IA64 - image = IMAGE_FILE_MACHINE_IA64; - stackframe.AddrPC.Offset = context.StIIP; - stackframe.AddrPC.Mode = AddrModeFlat; - stackframe.AddrFrame.Offset = context.IntSp; - stackframe.AddrFrame.Mode = AddrModeFlat; - stackframe.AddrBStore.Offset = context.RsBSP; - stackframe.AddrBStore.Mode = AddrModeFlat; - stackframe.AddrStack.Offset = context.IntSp; - stackframe.AddrStack.Mode = AddrModeFlat; -#elif -#error -#endif - len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", - pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); - if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len, "Exception: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - for( i = 0; i < 25; i++ ) - { - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - BOOL result = StackWalk64( - image, process, thread, - &stackframe, &context, NULL, - SymFunctionTableAccess64, SymGetModuleBase64, NULL); - DWORD64 displacement = 0; - - if( !result ) - break; - - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; - - len += Q_snprintf( message + len, 1024 - len, "% 2d %p", - i, (void*)stackframe.AddrPC.Offset ); - if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ) ) - { - len += Q_snprintf( message + len, 1024 - len, " %s ", symbol->Name ); - } - if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) - { - len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", - (char*)line.FileName, (int)line.LineNumber, (int)dline); - } - len += Q_snprintf( message + len, 1024 - len, "("); - len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); - len += Q_snprintf( message + len, 1024 - len, ")\n"); - } - -#if XASH_SDL == 2 - if( host.type != HOST_DEDICATED ) // let system to restart server automaticly - SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); -#endif - - Sys_PrintLog( message ); - - SymCleanup( process ); -} -#endif /* XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP */ - -LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; -static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) -{ - // save config - if( host.status != HOST_CRASHED ) - { - // check to avoid recursive call - host.crashed = true; - -#if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP - Sys_StackTrace( pInfo ); -#else - Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); -#endif - - if( host.type == HOST_NORMAL ) - CL_Crashed(); // tell client about crash - else host.status = HOST_CRASHED; - - if( host_developer.value <= 0 ) - { - // no reason to call debugger in release build - just exit - Sys_Quit(); - return EXCEPTION_CONTINUE_EXECUTION; - } - - // all other states keep unchanged to let debugger find bug - Sys_DestroyConsole(); - } - - if( oldFilter ) - return oldFilter( pInfo ); - return EXCEPTION_CONTINUE_EXECUTION; -} - -void Sys_SetupCrashHandler( void ) -{ - SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors - oldFilter = SetUnhandledExceptionFilter( Sys_Crash ); - host.hInst = GetModuleHandle( NULL ); -} - -void Sys_RestoreCrashHandler( void ) -{ - // restore filter - if( oldFilter ) SetUnhandledExceptionFilter( oldFilter ); -} - -#elif XASH_CRASHHANDLER == CRASHHANDLER_UCONTEXT -// Posix signal handler - -#include "library.h" - -#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX -#define HAVE_UCONTEXT_H 1 -#endif - -#ifdef HAVE_UCONTEXT_H -#include -#endif - -#include -#include - -#ifdef XASH_DYNAMIC_DLADDR -static int d_dladdr( void *sym, Dl_info *info ) -{ - static int (*dladdr_real) ( void *sym, Dl_info *info ); - - if( !dladdr_real ) - dladdr_real = dlsym( (void*)(size_t)(-1), "dladdr" ); - - memset( info, 0, sizeof( *info ) ); - - if( !dladdr_real ) - return -1; - - return dladdr_real( sym, info ); -} -#define dladdr d_dladdr -#endif - -static int Sys_PrintFrame( char *buf, int len, int i, void *addr ) -{ - Dl_info dlinfo; - if( len <= 0 ) - return 0; // overflow - - if( dladdr( addr, &dlinfo )) - { - if( dlinfo.dli_sname ) - return Q_snprintf( buf, len, "%2d: %p <%s+%lu> (%s)\n", i, addr, dlinfo.dli_sname, - (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address - else - return Q_snprintf( buf, len, "%2d: %p (%s)\n", i, addr, dlinfo.dli_fname ); // print module and address - } - else - return Q_snprintf( buf, len, "%2d: %p\n", i, addr ); // print only address -} - -struct sigaction oldFilter; - -#define STACK_BACKTRACE_STR "Stack backtrace:\n" -#define STACK_DUMP_STR "Stack dump:\n" - -#define STACK_BACKTRACE_STR_LEN (sizeof( STACK_BACKTRACE_STR ) - 1) -#define STACK_DUMP_STR_LEN (sizeof( STACK_DUMP_STR ) - 1) -#define ALIGN( x, y ) (((uintptr_t) (x) + ((y)-1)) & ~((y)-1)) - -static void Sys_Crash( int signal, siginfo_t *si, void *context) -{ - void *pc = NULL, **bp = NULL, **sp = NULL; // this must be set for every OS! - char message[8192]; - int len, logfd, i = 0; - size_t pagesize; - -#if XASH_OPENBSD - struct sigcontext *ucontext = (struct sigcontext*)context; -#else - ucontext_t *ucontext = (ucontext_t*)context; -#endif - -#if XASH_AMD64 - #if XASH_FREEBSD - pc = (void*)ucontext->uc_mcontext.mc_rip; - bp = (void**)ucontext->uc_mcontext.mc_rbp; - sp = (void**)ucontext->uc_mcontext.mc_rsp; - #elif XASH_NETBSD - pc = (void*)ucontext->uc_mcontext.__gregs[REG_RIP]; - bp = (void**)ucontext->uc_mcontext.__gregs[REG_RBP]; - sp = (void**)ucontext->uc_mcontext.__gregs[REG_RSP]; - #elif XASH_OPENBSD - pc = (void*)ucontext->sc_rip; - bp = (void**)ucontext->sc_rbp; - sp = (void**)ucontext->sc_rsp; - #else - pc = (void*)ucontext->uc_mcontext.gregs[REG_RIP]; - bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP]; - sp = (void**)ucontext->uc_mcontext.gregs[REG_RSP]; - #endif -#elif XASH_X86 - #if XASH_FREEBSD - pc = (void*)ucontext->uc_mcontext.mc_eip; - bp = (void**)ucontext->uc_mcontext.mc_ebp; - sp = (void**)ucontext->uc_mcontext.mc_esp; - #elif XASH_NETBSD - pc = (void*)ucontext->uc_mcontext.__gregs[REG_EIP]; - bp = (void**)ucontext->uc_mcontext.__gregs[REG_EBP]; - sp = (void**)ucontext->uc_mcontext.__gregs[REG_ESP]; - #elif XASH_OPENBSD - pc = (void*)ucontext->sc_eip; - bp = (void**)ucontext->sc_ebp; - sp = (void**)ucontext->sc_esp; - #else - pc = (void*)ucontext->uc_mcontext.gregs[REG_EIP]; - bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP]; - sp = (void**)ucontext->uc_mcontext.gregs[REG_ESP]; - #endif -#elif XASH_ARM && XASH_64BIT - pc = (void*)ucontext->uc_mcontext.pc; - bp = (void*)ucontext->uc_mcontext.regs[29]; - sp = (void*)ucontext->uc_mcontext.sp; -#elif XASH_ARM - pc = (void*)ucontext->uc_mcontext.arm_pc; - bp = (void*)ucontext->uc_mcontext.arm_fp; - sp = (void*)ucontext->uc_mcontext.arm_sp; -#endif - - // safe actions first, stack and memory may be corrupted - len = Q_snprintf( message, sizeof( message ), "Ver: %s %s (build %i-%s, %s-%s)\n", - XASH_ENGINE_NAME, XASH_VERSION, Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); - -#if !XASH_BSD - len += 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 ); -#else - len += 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 ); -#endif - - write( STDERR_FILENO, message, len ); - - // flush buffers before writing directly to descriptors - fflush( stdout ); - fflush( stderr ); - - // now get log fd and write trace directly to log - logfd = Sys_LogFileNo(); - write( logfd, message, len ); - - if( pc && bp && sp ) - { - size_t pagesize = sysconf( _SC_PAGESIZE ); - // try to print backtrace - write( STDERR_FILENO, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - write( logfd, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - Q_strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); - len += STACK_BACKTRACE_STR_LEN; - -// false on success, true on failure -#define try_allow_read(pointer, pagesize) \ - ((mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE | PROT_EXEC ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_EXEC ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ | PROT_WRITE ) == -1) && \ - ( mprotect( (char *)ALIGN( (pointer), (pagesize) ), (pagesize), PROT_READ ) == -1)) - - do - { - int line = Sys_PrintFrame( message + len, sizeof( message ) - len, ++i, pc); - write( STDERR_FILENO, message + len, line ); - write( logfd, message + len, line ); - len += line; - //if( !dladdr(bp,0) ) break; // only when bp is in module - if( try_allow_read( bp, pagesize ) ) - break; - if( try_allow_read( bp[0], pagesize ) ) - break; - pc = bp[1]; - bp = (void**)bp[0]; - } - while( bp && i < 128 ); - - // try to print stack - write( STDERR_FILENO, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - write( logfd, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - Q_strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); - len += STACK_DUMP_STR_LEN; - - if( !try_allow_read( sp, pagesize ) ) - { - for( i = 0; i < 32; i++ ) - { - int line = Sys_PrintFrame( message + len, sizeof( message ) - len, i, sp[i] ); - write( STDERR_FILENO, message + len, line ); - write( logfd, message + len, line ); - len += line; - } - } - -#undef try_allow_read - } - - // put MessageBox as Sys_Error - Msg( "%s\n", message ); -#ifdef XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif - MSGBOX( message ); - - // log saved, now we can try to save configs and close log correctly, it may crash - if( host.type == HOST_NORMAL ) - CL_Crashed(); - host.status = HOST_CRASHED; - host.crashed = true; - - Sys_Quit(); -} - -void Sys_SetupCrashHandler( void ) -{ - struct sigaction act; - act.sa_sigaction = Sys_Crash; - act.sa_flags = SA_SIGINFO | SA_ONSTACK; - sigaction( SIGSEGV, &act, &oldFilter ); - sigaction( SIGABRT, &act, &oldFilter ); - sigaction( SIGBUS, &act, &oldFilter ); - sigaction( SIGILL, &act, &oldFilter ); -} - -void Sys_RestoreCrashHandler( void ) -{ - sigaction( SIGSEGV, &oldFilter, NULL ); - sigaction( SIGABRT, &oldFilter, NULL ); - sigaction( SIGBUS, &oldFilter, NULL ); - sigaction( SIGILL, &oldFilter, NULL ); -} - -#elif XASH_CRASHHANDLER == CRASHHANDLER_NULL - -void Sys_SetupCrashHandler( void ) -{ - // stub -} - -void Sys_RestoreCrashHandler( void ) -{ - // stub -} -#endif diff --git a/engine/common/custom.c b/engine/common/custom.c index cf9d0431..22f03d6f 100644 --- a/engine/common/custom.c +++ b/engine/common/custom.c @@ -16,10 +16,27 @@ GNU General Public License for more details. #include "common.h" #include "custom.h" #include "ref_common.h" +#include "hpak.h" // be aware of HPK limits -qboolean CustomDecal_Validate( void *raw, int nFileSize ) +static rgbdata_t *CustomDecal_LoadImage( const char *path, void *raw, int size ) { - rgbdata_t *test = FS_LoadImage( "#logo.bmp", raw, nFileSize ); + const char *testname; + + // this way we limit file types + if( !Q_stricmp( COM_FileExtension( path ), "png" )) + testname = "#logo.png"; + else if( !Q_stricmp( COM_FileExtension( path ), "wad" )) + testname = "#logo.wad"; + else testname = "#logo.bmp"; + + Image_SetForceFlags( IL_LOAD_PLAYER_DECAL ); + + return FS_LoadImage( testname, raw, size ); +} + +static qboolean CustomDecal_Validate( const char *path, void *raw, int nFileSize ) +{ + rgbdata_t *test = CustomDecal_LoadImage( path, raw, nFileSize ); if( test ) { @@ -79,7 +96,7 @@ qboolean COM_CreateCustomization( customization_t *pListHead, resource_t *pResou if( FBitSet( flags, FCUST_FROMHPAK )) { - if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, pResource, (byte **)&pCust->pBuffer, NULL )) + if( !HPAK_GetDataPointer( hpk_custom_file.string, pResource, (byte **)&pCust->pBuffer, NULL )) bError = true; } else @@ -97,21 +114,25 @@ qboolean COM_CreateCustomization( customization_t *pListHead, resource_t *pResou { pCust->resource.playernum = playernumber; - if( CustomDecal_Validate( pCust->pBuffer, pResource->nDownloadSize )) + if( CustomDecal_Validate( pResource->szFileName, pCust->pBuffer, pResource->nDownloadSize )) { if( !FBitSet( flags, FCUST_IGNOREINIT )) { - if( pResource->nDownloadSize >= (1 * 1024) && pResource->nDownloadSize <= ( 16 * 1024 )) + if( pResource->nDownloadSize >= HPAK_ENTRY_MIN_SIZE && pResource->nDownloadSize <= HPAK_ENTRY_MAX_SIZE ) { pCust->bTranslated = true; pCust->nUserData1 = 0; - pCust->nUserData2 = 1; + pCust->nUserData2 = 7; if( !FBitSet( flags, FCUST_WIPEDATA )) - pCust->pInfo = FS_LoadImage( "#logo.bmp", pCust->pBuffer, pCust->resource.nDownloadSize ); + pCust->pInfo = CustomDecal_LoadImage( pResource->szFileName, pCust->pBuffer, pCust->resource.nDownloadSize ); else pCust->pInfo = NULL; if( nLumps ) *nLumps = 1; } + else + { + Con_Printf( S_WARN "Ignoring custom decal \"%s\": wrong size (%i bytes)\n", pResource->szFileName, pResource->nDownloadSize ); + } } } } diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 752a6846..36bc513d 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -18,10 +18,38 @@ GNU General Public License for more details. #include "base_cmd.h" #include "eiface.h" // ARRAYSIZE -convar_t *cvar_vars = NULL; // head of list -convar_t *cmd_scripting; +static convar_t *cvar_vars = NULL; // head of list +static poolhandle_t cvar_pool; +CVAR_DEFINE_AUTO( cmd_scripting, "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "enable simple condition checking and variable operations" ); -CVAR_DEFINE_AUTO( cl_filterstuffcmd, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); +typedef struct cvar_filter_quirks_s +{ + const char *gamedir; // gamedir to enable for + const char *cvars; // list of cvars should be excluded from filter +} cvar_filter_quirks_t; + +#ifdef HACKS_RELATED_HLMODS +static const cvar_filter_quirks_t cvar_filter_quirks[] = +{ + // EXAMPLE: + //{ + // "valve", + // "test;test1;test100" + //}, + { + "ricochet", + "r_drawviewmodel", + }, + { + "dod", + "cl_dodmusic" // Day of Defeat Beta 1.3 cvar + }, +}; +#endif + +static const cvar_filter_quirks_t *cvar_active_filter_quirks = NULL; + +CVAR_DEFINE_AUTO( cl_filterstuffcmd, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); /* ============ @@ -43,15 +71,14 @@ find the specified variable by name */ convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ) { - // TODO: ignore group for cvar -#if defined(XASH_HASHED_VARS) - return (convar_t *)BaseCmd_Find( HM_CVAR, var_name ); -#else - convar_t *var; + convar_t *var; if( !var_name ) return NULL; +#if defined(XASH_HASHED_VARS) // TODO: ignore_group + var = BaseCmd_Find( HM_CVAR, var_name ); +#else for( var = cvar_vars; var; var = var->next ) { if( ignore_group && FBitSet( ignore_group, var->flags )) @@ -60,9 +87,13 @@ convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ) if( !Q_stricmp( var_name, var->name )) return var; } - - return NULL; #endif + + // HACKHACK: HL25 compatibility + if( !var && !Q_stricmp( var_name, "gl_widescreen_yfov" )) + var = Cvar_FindVarExt( "r_adjust_fov", ignore_group ); + + return var; } /* @@ -72,10 +103,16 @@ Cvar_BuildAutoDescription build cvar auto description that based on the setup flags ============ */ -const char *Cvar_BuildAutoDescription( int flags ) +const char *Cvar_BuildAutoDescription( const char *szName, int flags ) { static char desc[256]; + if( FBitSet( flags, FCVAR_GLCONFIG )) + { + Q_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, szName ); + return desc; + } + desc[0] = '\0'; if( FBitSet( flags, FCVAR_EXTDLL )) @@ -116,11 +153,13 @@ static qboolean Cvar_UpdateInfo( convar_t *var, const char *value, qboolean noti { if( FBitSet( var->flags, FCVAR_USERINFO )) { - if ( Host_IsDedicated() ) + if( Host_IsDedicated() ) { // g-cont. this is a very strange behavior... - Info_SetValueForKey( SV_Serverinfo(), var->name, value, MAX_SERVERINFO_STRING ), - SV_BroadcastCommand( "fullserverinfo \"%s\"\n", SV_Serverinfo( )); + char *info = SV_Serverinfo(); + + Info_SetValueForKey( info, var->name, value, MAX_SERVERINFO_STRING ), + SV_BroadcastCommand( "fullserverinfo \"%s\"\n", info ); } #if !XASH_DEDICATED else @@ -129,8 +168,7 @@ static qboolean Cvar_UpdateInfo( convar_t *var, const char *value, qboolean noti return false; // failed to change value // time to update server copy of userinfo - CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", var->name, value ); - CL_LegacyUpdateInfo(); + CL_UpdateInfo( var->name, value ); } #endif } @@ -162,7 +200,7 @@ Cvar_ValidateString deal with userinfo etc ============ */ -const char *Cvar_ValidateString( convar_t *var, const char *value ) +static const char *Cvar_ValidateString( convar_t *var, const char *value ) { const char *pszValue; static char szNew[MAX_STRING]; @@ -178,7 +216,7 @@ const char *Cvar_ValidateString( convar_t *var, const char *value ) int len = 0; // step through the string, only copying back in characters that are printable - while( *pszValue && len < MAX_STRING ) + while( *pszValue && len < ( MAX_STRING - 1 )) { if( ((byte)*pszValue) < 32 ) { @@ -196,7 +234,7 @@ const char *Cvar_ValidateString( convar_t *var, const char *value ) if( !COM_CheckStringEmpty( szNew ) ) Q_strncpy( szNew, "empty", sizeof( szNew )); } - if( FBitSet( var->flags, FCVAR_NOEXTRAWHITEPACE )) + if( FBitSet( var->flags, FCVAR_NOEXTRAWHITESPACE )) { char *szVal = szNew; int len = 0; @@ -220,6 +258,33 @@ const char *Cvar_ValidateString( convar_t *var, const char *value ) return pszValue; } +/* +============ +Cvar_ValidateVarName +============ +*/ +static qboolean Cvar_ValidateVarName( const char *s, qboolean isvalue ) +{ + if( !s ) + return false; + if( Q_strchr( s, '\\' ) && !isvalue ) + return false; + if( Q_strchr( s, '\"' )) + return false; + if( Q_strchr( s, ';' ) && !isvalue ) + return false; + return true; +} + +static void Cvar_Free( convar_t *var ) +{ + freestring( var->name ); + freestring( var->string ); + freestring( var->def_string ); + freestring( var->desc ); + Mem_Free( var ); +} + /* ============ Cvar_UnlinkVar @@ -227,7 +292,7 @@ Cvar_UnlinkVar unlink the variable ============ */ -int Cvar_UnlinkVar( const char *var_name, int group ) +static int Cvar_UnlinkVar( const char *var_name, int group ) { int count = 0; convar_t **prev; @@ -259,17 +324,13 @@ int Cvar_UnlinkVar( const char *var_name, int group ) #endif // unlink variable from list - freestring( var->string ); *prev = var->next; // only allocated cvars can throw these fields if( FBitSet( var->flags, FCVAR_ALLOCATED )) - { - freestring( var->name ); - freestring( var->def_string ); - freestring( var->desc ); - Mem_Free( var ); - } + Cvar_Free( var ); + else + freestring( var->string ); count++; } @@ -368,11 +429,12 @@ convar_t *Cvar_Get( const char *name, const char *value, int flags, const char * // which executed from the config file. So we don't need to // change value here: we *already* have actual value from config. // in other cases we need to rewrite them - if( Q_strcmp( var->desc, "" )) + if( COM_CheckStringEmpty( var->desc )) { // directly set value - freestring( var->string ); - var->string = copystring( value ); + size_t len = Q_strlen( value ) + 1; + var->string = Mem_Realloc( cvar_pool, var->string, len ); + Q_strncpy( var->string, value, len ); var->value = Q_atof( var->string ); SetBits( var->flags, flags ); @@ -388,22 +450,25 @@ convar_t *Cvar_Get( const char *name, const char *value, int flags, const char * if( FBitSet( var->flags, FCVAR_ALLOCATED ) && Q_strcmp( var_desc, var->desc )) { + size_t len = Q_strlen( var_desc ) + 1; + if( !FBitSet( flags, FCVAR_GLCONFIG )) Con_Reportf( "%s change description from %s to %s\n", var->name, var->desc, var_desc ); + // update description if needs - freestring( var->desc ); - var->desc = copystring( var_desc ); + var->desc = Mem_Realloc( cvar_pool, var->desc, len ); + Q_strncpy( var->desc, var_desc, len ); } return var; } // allocate a new cvar - var = Z_Malloc( sizeof( *var )); - var->name = copystring( name ); - var->string = copystring( value ); - var->def_string = copystring( value ); - var->desc = copystring( var_desc ); + var = Mem_Malloc( cvar_pool, sizeof( *var )); + var->name = copystringpool( cvar_pool, name ); + var->string = copystringpool( cvar_pool, value ); + var->def_string = copystringpool( cvar_pool, value ); + var->desc = copystringpool( cvar_pool, var_desc ); var->value = Q_atof( var->string ); var->flags = flags|FCVAR_ALLOCATED; @@ -428,6 +493,23 @@ convar_t *Cvar_Get( const char *name, const char *value, int flags, const char * return var; } +/* +============ +Cvar_Getf +============ +*/ +convar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) +{ + char value[MAX_VA_STRING]; + va_list args; + + va_start( args, format ); + Q_vsnprintf( value, sizeof( value ), format, args ); + va_end( args ); + + return Cvar_Get( var_name, value, flags, description ); +} + /* ============ Cvar_RegisterVariable @@ -472,7 +554,7 @@ void Cvar_RegisterVariable( convar_t *var ) if( FBitSet( var->flags, FCVAR_EXTENDED )) var->def_string = var->string; // just swap pointers - var->string = copystring( var->string ); + var->string = copystringpool( cvar_pool, var->string ); var->value = Q_atof( var->string ); // find the supposed position in chain (alphanumerical order) @@ -495,6 +577,123 @@ void Cvar_RegisterVariable( convar_t *var ) #endif } +static qboolean Cvar_CanSet( const convar_t *cv ) +{ + if( FBitSet( cv->flags, FCVAR_READ_ONLY )) + { + Con_Printf( "%s is read-only.\n", cv->name ); + return false; + } + + if( FBitSet( cv->flags, FCVAR_CHEAT ) && !host.allow_cheats ) + { + Con_Printf( "%s is cheat protected.\n", cv->name ); + return false; + } + + // just tell user about deferred changes + if( FBitSet( cv->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( ))) + Con_Printf( "%s will be changed upon restarting.\n", cv->name ); + + return true; +} + +/* +============ +Cvar_Set2 +============ +*/ +static convar_t *Cvar_Set2( const char *var_name, const char *value ) +{ + convar_t *var; + qboolean dll_variable = false; + qboolean force = false; + const char *fixed_string; + size_t fixed_string_len; + + if( !Cvar_ValidateVarName( var_name, false )) + { + Con_DPrintf( S_ERROR "Invalid cvar name string: %s\n", var_name ); + return NULL; + } + + var = Cvar_FindVar( var_name ); + if( !var ) + { + // if cvar not found, create it + return Cvar_Get( var_name, value, FCVAR_USER_CREATED, NULL ); + } + else + { + if( !Cmd_CurrentCommandIsPrivileged( )) + { + if( FBitSet( var->flags, FCVAR_PRIVILEGED )) + { + Con_Printf( "%s is priveleged.\n", var->name ); + return var; + } + + if( cl_filterstuffcmd.value > 0.0f && FBitSet( var->flags, FCVAR_FILTERABLE )) + { + Con_Printf( "%s is filterable.\n", var->name ); + return var; + } + } + } + + // use this check to prevent acessing for unexisting fields + // for cvar_t: latched_string, description, etc + dll_variable = FBitSet( var->flags, FCVAR_EXTDLL ); + + // check value + if( !value ) + { + if( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) + { + Con_Printf( "%s has no default value and can't be reset.\n", var->name ); + return var; + } + + if( dll_variable ) + value = "0"; + else + value = var->def_string; // reset to default value + } + + if( !Q_strcmp( value, var->string )) + return var; + + // any latched values not allowed for game cvars + if( dll_variable ) + force = true; + + if( !force ) + { + if( !Cvar_CanSet( var )) + return var; + } + + fixed_string = Cvar_ValidateString( var, value ); + + // nothing to change + if( !Q_strcmp( fixed_string, var->string )) + return var; + + // fill it cls.userinfo, svs.serverinfo + if( !Cvar_UpdateInfo( var, fixed_string, true )) + return var; + + // and finally change the cvar itself + fixed_string_len = Q_strlen( fixed_string ) + 1; + var->string = Mem_Realloc( cvar_pool, var->string, fixed_string_len ); + Q_strncpy( var->string, fixed_string, fixed_string_len ); + var->value = Q_atof( var->string ); + + // tell engine about changes + Cvar_Changed( var ); + return var; +} + /* ============ Cvar_DirectSet @@ -502,38 +701,26 @@ Cvar_DirectSet way to change value for many cvars ============ */ -void Cvar_DirectSet( convar_t *var, const char *value ) +void GAME_EXPORT Cvar_DirectSet( convar_t *var, const char *value ) { - const char *pszValue; + const char *fixed_string; + size_t fixed_string_len; - if( !var ) return; // ??? + if( unlikely( !var )) return; // ??? // lookup for registration - if( CVAR_CHECK_SENTINEL( var ) || ( var->next == NULL && !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))) + if( unlikely( CVAR_CHECK_SENTINEL( var ) || ( var->next == NULL && !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )))) { // need to registering cvar fisrt Cvar_RegisterVariable( var ); // ok, register it + + // lookup for registration again + if( var != Cvar_FindVar( var->name )) + return; // how this possible? } - // lookup for registration again - if( var != Cvar_FindVar( var->name )) - return; // how this possible? - - if( FBitSet( var->flags, FCVAR_READ_ONLY )) - { - Con_Printf( "%s is read-only.\n", var->name ); + if( !Cvar_CanSet( var )) return; - } - - if( FBitSet( var->flags, FCVAR_CHEAT ) && !host.allow_cheats ) - { - Con_Printf( "%s is cheat protected.\n", var->name ); - return; - } - - // just tell user about deferred changes - if( FBitSet( var->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( ))) - Con_Printf( "%s will be changed upon restarting.\n", var->name ); // check value if( !value ) @@ -547,25 +734,44 @@ void Cvar_DirectSet( convar_t *var, const char *value ) value = var->def_string; // reset to default value } - pszValue = Cvar_ValidateString( var, value ); + fixed_string = Cvar_ValidateString( var, value ); // nothing to change - if( !Q_strcmp( pszValue, var->string )) + if( !Q_strcmp( fixed_string, var->string )) return; // fill it cls.userinfo, svs.serverinfo - if( !Cvar_UpdateInfo( var, pszValue, true )) + if( !Cvar_UpdateInfo( var, fixed_string, true )) return; - // and finally changed the cvar itself - freestring( var->string ); - var->string = copystring( pszValue ); + // and finally change the cvar itself + fixed_string_len = Q_strlen( fixed_string ) + 1; + var->string = Mem_Realloc( cvar_pool, var->string, fixed_string_len ); + Q_strncpy( var->string, fixed_string, fixed_string_len ); var->value = Q_atof( var->string ); // tell engine about changes Cvar_Changed( var ); } +/* +============ +Cvar_DirectSetValue + +functionally is the same as Cvar_SetValue but for direct cvar access +============ +*/ +void Cvar_DirectSetValue( convar_t *var, float value ) +{ + char val[32]; + + if( fabs( value - (int)value ) < 0.000001 ) + Q_snprintf( val, sizeof( val ), "%d", (int)value ); + else Q_snprintf( val, sizeof( val ), "%f", value ); + + Cvar_DirectSet( var, val ); +} + /* ============ Cvar_FullSet @@ -575,7 +781,8 @@ can set any protected cvars */ void Cvar_FullSet( const char *var_name, const char *value, int flags ) { - convar_t *var = Cvar_FindVar( var_name ); + convar_t *var = Cvar_FindVar( var_name ); + size_t len = Q_strlen( value ) + 1; if( !var ) { @@ -583,8 +790,8 @@ void Cvar_FullSet( const char *var_name, const char *value, int flags ) return; } - freestring( var->string ); - var->string = copystring( value ); + var->string = Mem_Realloc( cvar_pool, var->string, len ); + Q_strncpy( var->string, value, len ); var->value = Q_atof( var->string ); SetBits( var->flags, flags ); @@ -604,7 +811,7 @@ void GAME_EXPORT Cvar_Set( const char *var_name, const char *value ) if( !var_name ) { // there is an error in C code if this happens - Con_Printf( "Cvar_Set: passed NULL variable name\n" ); + Con_Printf( "%s: passed NULL variable name\n", __func__ ); return; } @@ -613,7 +820,7 @@ void GAME_EXPORT Cvar_Set( const char *var_name, const char *value ) if( !var ) { // there is an error in C code if this happens - Con_Printf( "Cvar_Set: variable '%s' not found\n", var_name ); + Con_Printf( "%s: variable '%s' not found\n", __func__, var_name ); return; } @@ -658,7 +865,7 @@ float GAME_EXPORT Cvar_VariableValue( const char *var_name ) if( !var_name ) { // there is an error in C code if this happens - Con_Printf( "Cvar_VariableValue: passed NULL variable name\n" ); + Con_Printf( "%s: passed NULL variable name\n", __func__ ); return 0.0f; } @@ -695,7 +902,7 @@ const char *Cvar_VariableString( const char *var_name ) if( !var_name ) { // there is an error in C code if this happens - Con_Printf( "Cvar_VariableString: passed NULL variable name\n" ); + Con_Printf( "%s: passed NULL variable name\n", __func__ ); return ""; } @@ -763,9 +970,19 @@ static void Cvar_SetGL( const char *name, const char *value ) Cvar_FullSet( name, value, FCVAR_GLCONFIG ); } +static int ShouldSetCvar_splitstr_handler( char *prev, char *next, void *userdata ) +{ + size_t len = next - prev; + + if( !Q_strnicmp( prev, userdata, len )) + return 1; + + return 0; +} + static qboolean Cvar_ShouldSetCvar( convar_t *v, qboolean isPrivileged ) { - const char *prefixes[] = { "cl_", "gl_", "m_", "r_", "hud_" }; + const char *prefixes[] = { "cl_", "gl_", "m_", "r_", "hud_", "joy_", "con_", "scr_" }; int i; if( isPrivileged ) @@ -777,6 +994,14 @@ static qboolean Cvar_ShouldSetCvar( convar_t *v, qboolean isPrivileged ) if( cl_filterstuffcmd.value <= 0.0f ) return true; + // check if game-specific filter exceptions should be applied + // TODO: for cmd exceptions, make generic function + if( cvar_active_filter_quirks ) + { + if( Q_splitstr((char *)cvar_active_filter_quirks->cvars, ';', v->name, ShouldSetCvar_splitstr_handler )) + return true; + } + if( FBitSet( v->flags, FCVAR_FILTERABLE )) return false; @@ -805,9 +1030,11 @@ qboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged ) return true; } +#if !defined( XASH_HASHED_VARS ) // check variables - if( !v ) // already found in basecmd - v = Cvar_FindVar( Cmd_Argv( 0 )); + v = Cvar_FindVar( Cmd_Argv( 0 )); +#endif + if( !v ) return false; @@ -840,8 +1067,6 @@ qboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged ) else { Cvar_DirectSet( v, Cmd_Argv( 1 )); - if( host.apply_game_config ) - host.sv_cvars_restored++; return true; } } @@ -872,7 +1097,7 @@ Cvar_Toggle_f Toggles a cvar for easy single key binding ============ */ -void Cvar_Toggle_f( void ) +static void Cvar_Toggle_f( void ) { int v; @@ -884,7 +1109,41 @@ void Cvar_Toggle_f( void ) v = !Cvar_VariableInteger( Cmd_Argv( 1 )); - Cvar_Set( Cmd_Argv( 1 ), va( "%i", v )); + Cvar_Set( Cmd_Argv( 1 ), v ? "1" : "0" ); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +static void Cvar_Set_f( void ) +{ + int i, c, l = 0, len; + char combined[MAX_CMD_TOKENS]; + + c = Cmd_Argc(); + if( c < 3 ) + { + Msg( S_USAGE "set \n" ); + return; + } + combined[0] = 0; + + for( i = 2; i < c; i++ ) + { + len = Q_strlen( Cmd_Argv(i) + 1 ); + if( l + len >= MAX_CMD_TOKENS - 2 ) + break; + Q_strncat( combined, Cmd_Argv( i ), sizeof( combined )); + if( i != c-1 ) Q_strncat( combined, " ", sizeof( combined )); + l += len; + } + + Cvar_Set2( Cmd_Argv( 1 ), combined ); } /* @@ -894,10 +1153,8 @@ Cvar_SetGL_f As Cvar_Set, but also flags it as glconfig ============ */ -void Cvar_SetGL_f( void ) +static void Cvar_SetGL_f( void ) { - convar_t *var; - if( Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "setgl \n" ); @@ -912,7 +1169,7 @@ void Cvar_SetGL_f( void ) Cvar_Reset_f ============ */ -void Cvar_Reset_f( void ) +static void Cvar_Reset_f( void ) { if( Cmd_Argc() != 2 ) { @@ -928,11 +1185,10 @@ void Cvar_Reset_f( void ) Cvar_List_f ============ */ -void Cvar_List_f( void ) +static void Cvar_List_f( void ) { convar_t *var; const char *match = NULL; - char *value; int count = 0; size_t matchlen = 0; @@ -944,19 +1200,24 @@ void Cvar_List_f( void ) for( var = cvar_vars; var; var = var->next ) { + char value[MAX_VA_STRING]; + char *p; + if( var->name[0] == '@' ) continue; // never shows system cvars if( match && !Q_strnicmpext( match, var->name, matchlen )) continue; - if( Q_colorstr( var->string )) - value = va( "\"%s\"", var->string ); - else value = va( "\"^2%s^7\"", var->string ); + p = Q_strchr( var->string, '^' ); + + if( IsColorString( p )) + Q_snprintf( value, sizeof( value ), "\"%s\"", var->string ); + else Q_snprintf( value, sizeof( value ), "\"^2%s^7\"", var->string ); if( FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, var->desc ); - else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->flags )); + else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->name, var->flags )); count++; } @@ -964,6 +1225,20 @@ void Cvar_List_f( void ) Con_Printf( "\n%i cvars\n", count ); } +static qboolean Cvar_ValidateUnlinkGroup( int group ) +{ + if( FBitSet( group, FCVAR_EXTDLL ) && !Cvar_VariableInteger( "host_gameloaded" )) + return false; + + if( FBitSet( group, FCVAR_CLIENTDLL ) && !Cvar_VariableInteger( "host_clientloaded" )) + return false; + + if( FBitSet( group, FCVAR_GAMEUIDLL ) && !Cvar_VariableInteger( "host_gameuiloaded" )) + return false; + + return true; +} + /* ============ Cvar_Unlink @@ -975,19 +1250,95 @@ void Cvar_Unlink( int group ) { int count; - if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, FCVAR_EXTDLL )) - return; - - if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, FCVAR_CLIENTDLL )) - return; - - if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, FCVAR_GAMEUIDLL )) + if( !Cvar_ValidateUnlinkGroup( group )) return; count = Cvar_UnlinkVar( NULL, group ); Con_Reportf( "unlink %i cvars\n", count ); } +pending_cvar_t *Cvar_PrepareToUnlink( int group ) +{ + pending_cvar_t *list = NULL; + pending_cvar_t *tail = NULL; + convar_t *cv; + + for( cv = cvar_vars; cv != NULL; cv = cv->next ) + { + size_t namelen; + pending_cvar_t *p; + + if( !FBitSet( cv->flags, group )) + continue; + + namelen = Q_strlen( cv->name ) + 1; + p = Mem_Malloc( cvar_pool, sizeof( *list ) + namelen ); + p->next = NULL; + p->cv_cur = cv; + p->cv_next = cv->next; + p->cv_allocated = FBitSet( cv->flags, FCVAR_ALLOCATED ) ? true : false; + Q_strncpy( p->cv_name, cv->name, namelen ); + + if( list == NULL ) + list = p; + else + tail->next = p; + + tail = p; + } + + return list; +} + +void Cvar_UnlinkPendingCvars( pending_cvar_t *list ) +{ + int count = 0; + + while( list != NULL ) + { + pending_cvar_t *next = list->next; + convar_t *cv_prev, *cv; + + for( cv_prev = NULL, cv = cvar_vars; cv != NULL; cv_prev = cv, cv = cv->next ) + { + if( cv == list->cv_cur ) + break; + } + + if( cv == NULL ) + { + Con_Reportf( "%s: can't find %s in variable list\n", __func__, list->cv_name ); + Mem_Free( list ); + list = next; + continue; + } + + // unlink cvar from list + BaseCmd_Remove( HM_CVAR, list->cv_name ); + if( cv_prev != NULL ) + cv_prev->next = list->cv_next; + else cvar_vars = list->cv_next; + + if( list->cv_allocated ) + Cvar_Free( list->cv_cur ); + else + { + // TODO: can't free cvar string here because + // it's not safe to access cv_cur and + // can't save string pointer because it could've been changed + // and pointer to it is already lost + // freestring( list->cv_string ); + } + + // now free pending cvar + Mem_Free( list ); + list = next; + count++; + } + + Con_Reportf( "unlink %i cvars\n", count ); +} + /* ============ Cvar_Init @@ -997,17 +1348,44 @@ Reads in all archived cvars */ void Cvar_Init( void ) { + cvar_pool = Mem_AllocPool( "Console Variables" ); cvar_vars = NULL; - cmd_scripting = Cvar_Get( "cmd_scripting", "0", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "enable simple condition checking and variable operations" ); - Cvar_RegisterVariable (&host_developer); // early registering for dev + cvar_active_filter_quirks = NULL; + Cvar_RegisterVariable( &cmd_scripting ); + Cvar_RegisterVariable( &host_developer ); // early registering for dev Cvar_RegisterVariable( &cl_filterstuffcmd ); - Cmd_AddRestrictedCommand( "setgl", Cvar_SetGL_f, "change the value of a opengl variable" ); // OBSOLETE Cmd_AddRestrictedCommand( "toggle", Cvar_Toggle_f, "toggles a console variable's values (use for more info)" ); Cmd_AddRestrictedCommand( "reset", Cvar_Reset_f, "reset any type variable to initial value" ); + Cmd_AddCommand( "set", Cvar_Set_f, "create or change the value of a console variable" ); Cmd_AddCommand( "cvarlist", Cvar_List_f, "display all console variables beginning with the specified prefix" ); } +void Cvar_Shutdown( void ) +{ + Mem_FreePool( &cvar_pool ); +} + +/* +============ +Cvar_PostFSInit + +============ +*/ +void Cvar_PostFSInit( void ) +{ + int i; + + for( i = 0; i < ARRAYSIZE( cvar_filter_quirks ); i++ ) + { + if( !Q_stricmp( cvar_filter_quirks[i].gamedir, GI->gamefolder )) + { + cvar_active_filter_quirks = &cvar_filter_quirks[i]; + break; + } + } +} + #if XASH_ENGINE_TESTS #include "tests.h" diff --git a/engine/common/cvar.h b/engine/common/cvar.h index ad2dbb44..3960fbc2 100644 --- a/engine/common/cvar.h +++ b/engine/common/cvar.h @@ -18,65 +18,47 @@ GNU General Public License for more details. #include "cvardef.h" -#ifdef XASH_64BIT -#define CVAR_SENTINEL 0xDEADBEEFDEADBEEF -#else -#define CVAR_SENTINEL 0xDEADBEEF -#endif -#define CVAR_CHECK_SENTINEL( cv ) ((uintptr_t)(cv)->next == CVAR_SENTINEL) - -// NOTE: if this is changed, it must be changed in cvardef.h too -typedef struct convar_s +// As some mods dynamically allocate cvars and free them without notifying the engine +// let's construct a list of cvars that must be removed +typedef struct pending_cvar_s { - // this part shared with cvar_t - char *name; - char *string; - int flags; - float value; - struct convar_s *next; + struct pending_cvar_s *next; - // this part unique for convar_t - char *desc; // variable descrition info - char *def_string; // keep pointer to initial value -} convar_t; + convar_t *cv_cur; // preserve the data that might get freed + convar_t *cv_next; + qboolean cv_allocated; // if it's allocated by us, it's safe to access cv_cur + char cv_name[]; +} pending_cvar_t; -// cvar internal flags -#define FCVAR_RENDERINFO (1<<16) // save to a seperate config called video.cfg -#define FCVAR_READ_ONLY (1<<17) // cannot be set by user at all, and can't be requested by CvarGetPointer from game dlls -#define FCVAR_EXTENDED (1<<18) // this is convar_t (sets on registration) -#define FCVAR_ALLOCATED (1<<19) // this convar_t is fully dynamic allocated (include description) -#define FCVAR_VIDRESTART (1<<20) // recreate the window is cvar with this flag was changed -#define FCVAR_TEMPORARY (1<<21) // these cvars holds their values and can be unlink in any time -#define FCVAR_MOVEVARS (1<<22) // this cvar is a part of movevars_t struct that shared between client and server - -#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \ - convar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, (char*)cvdesc, NULL } - -#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) \ - CVAR_DEFINE( cv, #cv, cvstr, cvflags, cvdesc ) - -#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) +typedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs ); cvar_t *Cvar_GetList( void ); #define Cvar_FindVar( name ) Cvar_FindVarExt( name, 0 ) convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ); void Cvar_RegisterVariable( convar_t *var ); convar_t *Cvar_Get( const char *var_name, const char *value, int flags, const char *description ); +convar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) FORMAT_CHECK( 4 ); void Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback ); void Cvar_FullSet( const char *var_name, const char *value, int flags ); void Cvar_DirectSet( convar_t *var, const char *value ); +void Cvar_DirectSetValue( convar_t *var, float value ); void Cvar_Set( const char *var_name, const char *value ); void Cvar_SetValue( const char *var_name, float value ); -const char *Cvar_BuildAutoDescription( int flags ); +const char *Cvar_BuildAutoDescription( const char *szName, int flags ) RETURNS_NONNULL; float Cvar_VariableValue( const char *var_name ); int Cvar_VariableInteger( const char *var_name ); -const char *Cvar_VariableString( const char *var_name ); +const char *Cvar_VariableString( const char *var_name ) RETURNS_NONNULL; void Cvar_WriteVariables( file_t *f, int group ); qboolean Cvar_Exists( const char *var_name ); void Cvar_Reset( const char *var_name ); void Cvar_SetCheatState( void ); qboolean Cvar_CommandWithPrivilegeCheck( convar_t *v, qboolean isPrivileged ); void Cvar_Init( void ); +void Cvar_Shutdown( void ); +void Cvar_PostFSInit( void ); void Cvar_Unlink( int group ); +pending_cvar_t *Cvar_PrepareToUnlink( int group ); +void Cvar_UnlinkPendingCvars( pending_cvar_t *pending_cvars ); + #endif//CVAR_H diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index 07b77a50..b3d894a6 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -16,141 +16,37 @@ GNU General Public License for more details. #include "common.h" #include "xash3d_mathlib.h" #include "ref_api.h" +#include "server.h" ref_globals_t refState; -const char *svc_strings[256] = +const char *CL_MsgInfo( int cmd ) { - "svc_bad", - "svc_nop", - "svc_disconnect", - "svc_changing", - "svc_version", - "svc_setview", - "svc_sound", - "svc_time", - "svc_print", - "svc_stufftext", - "svc_setangle", - "svc_serverdata", - "svc_lightstyle", - "svc_updateuserinfo", - "svc_deltatable", - "svc_clientdata", - "svc_stopsound", - "svc_updatepings", - "svc_particle", - "svc_restoresound", - "svc_spawnstatic", - "svc_event_reliable", - "svc_spawnbaseline", - "svc_temp_entity", - "svc_setpause", - "svc_signonnum", - "svc_centerprint", - "svc_event", - "svc_soundindex", - "svc_ambientsound", - "svc_intermission", - "svc_modelindex", - "svc_cdtrack", - "svc_serverinfo", - "svc_eventindex", - "svc_weaponanim", - "svc_bspdecal", - "svc_roomtype", - "svc_addangle", - "svc_usermessage", - "svc_packetentities", - "svc_deltapacketentities", - "svc_chokecount", - "svc_resourcelist", - "svc_deltamovevars", - "svc_customization", - "svc_unused46", - "svc_crosshairangle", - "svc_soundfade", - "svc_unused49", - "svc_unused50", - "svc_director", - "svc_studiodecal", - "svc_unused53", - "svc_unused54", - "svc_unused55", - "svc_unused56", - "svc_querycvarvalue", - "svc_querycvarvalue2", - "svc_exec", - "svc_unused60", - "svc_unused61", - "svc_unused62", - "svc_unused63", -}; + static string sz; -void CL_ProcessFile( qboolean successfully_received, const char *filename ) -{ + Q_strncpy( sz, "???", sizeof( sz )); -} + if( cmd >= 0 && cmd <= svc_lastmsg ) + { + // get engine message name + const char *svc_string = svc_strings[cmd]; -int GAME_EXPORT CL_Active( void ) -{ - return false; -} - -qboolean CL_Initialized( void ) -{ - return false; -} - -qboolean CL_IsInGame( void ) -{ - return true; // always active for dedicated servers -} - -qboolean CL_IsInMenu( void ) -{ - return false; -} - -qboolean CL_IsInConsole( void ) -{ - return false; -} - -qboolean CL_IsIntermission( void ) -{ - return false; -} - -qboolean CL_IsPlaybackDemo( void ) -{ - return false; -} - -qboolean CL_IsRecordDemo( void ) -{ - return false; -} - - -qboolean CL_DisableVisibility( void ) -{ - return false; -} - -qboolean CL_IsBackgroundDemo( void ) -{ - return false; -} - -qboolean CL_IsBackgroundMap( void ) -{ - return false; -} - -void CL_Init( void ) -{ + Q_strncpy( sz, svc_string, sizeof( sz )); + } + else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) + { + int i; + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + if( svgame.msg[i].number == cmd ) + { + Q_strncpy( sz, svgame.msg[i].name, sizeof( sz )); + break; + } + } + } + return sz; } void Key_Init( void ) @@ -188,16 +84,6 @@ void CL_WriteMessageHistory( void ) } -void Host_ClientBegin( void ) -{ - Cbuf_Execute(); -} - -void Host_ClientFrame( void ) -{ - -} - void Host_InputFrame( void ) { } @@ -212,30 +98,11 @@ void Con_Init( void ) } -void R_ClearAllDecals( void ) -{ - -} -int R_CreateDecalList( struct decallist_s *pList ) -{ - return 0; -} - void GAME_EXPORT S_StopSound(int entnum, int channel, const char *soundname) { } -int S_GetCurrentStaticSounds( soundlist_t *pout, int size ) -{ - return 0; -} - -int GAME_EXPORT CL_GetMaxClients( void ) -{ - return 0; -} - void IN_TouchInitConfig( void ) { @@ -246,11 +113,6 @@ void CL_Disconnect( void ) } -void CL_Shutdown( void ) -{ - -} - void R_ClearStaticEntities( void ) { @@ -261,11 +123,6 @@ void Host_Credits( void ) } -qboolean UI_CreditsActive( void ) -{ - return false; -} - void S_StopBackgroundTrack( void ) { @@ -276,11 +133,6 @@ void SCR_BeginLoadingPlaque( qboolean is_background ) } -int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) -{ - return 0; -} - void S_StopAllSounds( qboolean ambient ) { @@ -296,11 +148,6 @@ void GAME_EXPORT Con_NXPrintf( struct con_nprint_s *info, const char *fmt, ... } -const byte *GL_TextureData( unsigned int texnum ) -{ - return NULL; -} - void SCR_CheckStartupVids( void ) { @@ -325,4 +172,19 @@ void CL_Crashed( void ) { } +void CL_HudMessage( const char *pMessage ) +{ + +} + +byte TextureToGamma( byte b ) +{ + return b; +} + +byte LightToTexGamma( byte b ) +{ + return b; +} + #endif // XASH_DEDICATED diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index 6d3ff03e..28795ff9 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -1,6 +1,9 @@ - /* +/* filesystem.c - game filesystem based on DP fs +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,17 +16,151 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#if XASH_SDL == 2 +#include // SDL_GetBasePath +#elif XASH_SDL == 3 +#include +#endif + +#include #include "common.h" #include "library.h" +#include "platform/platform.h" + +static CVAR_DEFINE_AUTO( fs_mount_hd, "0", FCVAR_PRIVILEGED, "mount high definition content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_lv, "0", FCVAR_PRIVILEGED, "mount low violence models content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_addon, "0", FCVAR_PRIVILEGED, "mount addon content folder" ); +static CVAR_DEFINE_AUTO( fs_mount_l10n, "0", FCVAR_PRIVILEGED, "mount localization content folder" ); +static CVAR_DEFINE_AUTO( ui_language, "english", FCVAR_PRIVILEGED, "selected game language" ); fs_api_t g_fsapi; fs_globals_t *FI; +static pfnCreateInterface_t fs_pfnCreateInterface; static HINSTANCE fs_hInstance; -static void FS_Rescan_f( void ) +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) { - FS_Rescan(); + return g_fsapi.Search( pattern, caseinsensitive, gamedironly ); +} + +int FS_Close( file_t *file ) +{ + return g_fsapi.Close( file ); +} + +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) +{ + return g_fsapi.Open( filepath, mode, gamedironly ); +} + +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) +{ + return g_fsapi.LoadFile( path, filesizeptr, gamedironly ); +} + +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) +{ + return g_fsapi.LoadDirectFile( path, filesizeptr ); +} + +static void COM_StripDirectorySlash( char *pname ) +{ + size_t len; + + len = Q_strlen( pname ); + if( len > 0 && pname[len - 1] == '/' ) + pname[len - 1] = 0; +} + +void *FS_GetNativeObject( const char *obj ) +{ + if( fs_pfnCreateInterface ) + return fs_pfnCreateInterface( obj, NULL ); + + return NULL; +} + +static uint32_t FS_MountFlags( void ) +{ + uint32_t flags = 0; + + // FIXME: VFS shouldn't care about this, allow engine to mount gamedirs + if( fs_mount_lv.value ) SetBits( flags, FS_MOUNT_LV ); + if( fs_mount_hd.value ) SetBits( flags, FS_MOUNT_HD ); + if( fs_mount_addon.value ) SetBits( flags, FS_MOUNT_ADDON ); + if( fs_mount_l10n.value ) SetBits( flags, FS_MOUNT_L10N ); + + return flags; +} + +void FS_Rescan_f( void ) +{ + g_fsapi.Rescan( FS_MountFlags(), ui_language.string ); +} + +static void FS_LoadVFSConfig( const char *gamedir ) +{ + string parm; + + if( Host_IsDedicated( )) + return; + + Cbuf_AddTextf( "exec %s/vfs.cfg\n", gamedir ); + Cbuf_Execute(); + + if( Sys_GetParmFromCmdLine( "-language", parm )) + { + Cvar_DirectSet( &ui_language, parm ); + Cvar_DirectSet( &fs_mount_l10n, "1" ); + } + + ClearBits( fs_mount_hd.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_lv.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_l10n.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_addon.flags, FCVAR_CHANGED ); + ClearBits( ui_language.flags, FCVAR_CHANGED ); +} + +void FS_SaveVFSConfig( void ) +{ + file_t *f; + + if( !FBitSet( fs_mount_hd.flags|fs_mount_lv.flags|fs_mount_l10n.flags|fs_mount_addon.flags|ui_language.flags, FCVAR_CHANGED )) + { + Con_Reportf( "%s: no need to save vfs.cfg\n", __func__ ); + return; + } + + Con_Printf( "%s()\n", __func__ ); + + f = FS_Open( "vfs.cfg.new", "w", true ); + if( !f ) + { + Con_Printf( S_ERROR "%s: couldn't open vfs.cfg for write\n", __func__ ); + return; + } + + FS_Printf( f, "%s \"%d\"\n", fs_mount_hd.name, (int)fs_mount_hd.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_lv.name, (int)fs_mount_lv.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_l10n.name, (int)fs_mount_l10n.value ); + FS_Printf( f, "%s \"%d\"\n", fs_mount_addon.name, (int)fs_mount_addon.value ); + FS_Printf( f, "%s \"%s\"\n", ui_language.name, ui_language.string ); + + Host_FinalizeConfig( f, "vfs.cfg" ); + + ClearBits( fs_mount_hd.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_lv.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_l10n.flags, FCVAR_CHANGED ); + ClearBits( fs_mount_addon.flags, FCVAR_CHANGED ); + ClearBits( ui_language.flags, FCVAR_CHANGED ); +} + +void FS_LoadGameInfo( void ) +{ + FS_LoadVFSConfig( g_fsapi.Gamedir( )); + + g_fsapi.LoadGameInfo( FS_MountFlags(), ui_language.string ); } static void FS_ClearPaths_f( void ) @@ -36,7 +173,12 @@ static void FS_Path_f_( void ) FS_Path_f(); } -static fs_interface_t fs_memfuncs = +static void FS_MakeGameInfo_f( void ) +{ + g_fsapi.MakeGameInfo(); +} + +static const fs_interface_t fs_memfuncs = { Con_Printf, Con_DPrintf, @@ -48,21 +190,28 @@ static fs_interface_t fs_memfuncs = _Mem_Alloc, _Mem_Realloc, _Mem_Free, + + Sys_GetNativeObject, }; static void FS_UnloadProgs( void ) { - COM_FreeLibrary( fs_hInstance ); - fs_hInstance = 0; + if( fs_hInstance ) + { + COM_FreeLibrary( fs_hInstance ); + fs_hInstance = 0; + } } #ifdef XASH_INTERNAL_GAMELIBS #define FILESYSTEM_STDIO_DLL "filesystem_stdio" +#elif XASH_ANDROID +#define FILESYSTEM_STDIO_DLL "libfilesystem_stdio.so" #else #define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT #endif -qboolean FS_LoadProgs( void ) +static qboolean FS_LoadProgs( void ) { const char *name = FILESYSTEM_STDIO_DLL; FSAPI GetFSAPI; @@ -71,65 +220,179 @@ qboolean FS_LoadProgs( void ) if( !fs_hInstance ) { - Host_Error( "FS_LoadProgs: can't load filesystem library %s: %s\n", name, COM_GetLibraryError() ); + Sys_Error( "%s: can't load filesystem library %s: %s\n", __func__, name, COM_GetLibraryError() ); return false; } if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API ))) { FS_UnloadProgs(); - Host_Error( "FS_LoadProgs: can't find GetFSAPI entry point in %s\n", name ); + Sys_Error( "%s: can't find GetFSAPI entry point in %s\n", __func__, name ); return false; } - if( !GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs )) + if( GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs ) != FS_API_VERSION ) { FS_UnloadProgs(); - Host_Error( "FS_LoadProgs: can't initialize filesystem API: wrong version\n" ); + Sys_Error( "%s: can't initialize filesystem API: wrong version\n", __func__ ); return false; } - Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); + if( !( fs_pfnCreateInterface = (pfnCreateInterface_t)COM_GetProcAddress( fs_hInstance, "CreateInterface" ))) + { + FS_UnloadProgs(); + Sys_Error( "%s: can't find CreateInterface entry point in %s\n", __func__, name ); + return false; + } + Con_DPrintf( "%s: filesystem_stdio successfully loaded\n", __func__ ); return true; } +static qboolean FS_DetermineRootDirectory( char *out, size_t size ) +{ + const char *path = getenv( "XASH3D_BASEDIR" ); + + if( COM_CheckString( path )) + { + Q_strncpy( out, path, size ); + return true; + } + +#if TARGET_OS_IOS + Q_strncpy( out, IOS_GetDocsDir(), size ); + return true; +#elif XASH_ANDROID && XASH_SDL + path = SDL_AndroidGetExternalStoragePath(); + if( path != NULL ) + { + Q_strncpy( out, path, size ); + return true; + } + Sys_Error( "couldn't determine Android external storage path: %s", SDL_GetError( )); + return false; +#elif XASH_PSVITA + if( PSVita_GetBasePath( out, size )) + return true; + Sys_Error( "couldn't find %s data directory", XASH_ENGINE_NAME ); + return false; +#elif ( XASH_SDL >= 2 ) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2 + path = SDL_GetBasePath(); + +#if XASH_APPLE + if( path != NULL && Q_stristr( path, ".app" )) + { + SDL_free((void *)path ); + path = SDL_GetPrefPath( NULL, XASH_ENGINE_NAME ); + } +#endif + + if( path != NULL ) + { + Q_strncpy( out, path, size ); + SDL_free((void *)path ); + return true; + } + +#if XASH_POSIX || XASH_WIN32 + if( getcwd( out, size )) + return true; + Sys_Error( "couldn't determine current directory: %s, getcwd: %s", SDL_GetError(), strerror( errno )); +#else // !( XASH_POSIX || XASH_WIN32 ) + Sys_Error( "couldn't determine current directory: %s", SDL_GetError( )); +#endif // !( XASH_POSIX || XASH_WIN32 ) + return false; +#else // generic case + if( getcwd( out, size )) + return true; + + Sys_Error( "couldn't determine current directory: %s", strerror( errno )); + return false; +#endif // generic case +} + +static qboolean FS_DetermineReadOnlyRootDirectory( char *out, size_t size ) +{ + const char *env_rodir = getenv( "XASH3D_RODIR" ); + + if( _Sys_GetParmFromCmdLine( "-rodir", out, size )) + return true; + + if( COM_CheckString( env_rodir )) + { + Q_strncpy( out, env_rodir, size ); + return true; + } + + return false; +} + /* ================ FS_Init ================ */ -void FS_Init( void ) +void FS_Init( const char *basedir ) { - qboolean hasBaseDir = false; - qboolean hasGameDir = false; - qboolean caseinsensitive = true; - int i; string gamedir; + char rodir[MAX_OSPATH], rootdir[MAX_OSPATH]; + rodir[0] = rootdir[0] = 0; + + if( !FS_DetermineRootDirectory( rootdir, sizeof( rootdir )) || !COM_CheckStringEmpty( rootdir )) + { + Sys_Error( "couldn't determine current directory (empty string)" ); + return; + } + COM_FixSlashes( rootdir ); + COM_StripDirectorySlash( rootdir ); + + FS_DetermineReadOnlyRootDirectory( rodir, sizeof( rodir )); + COM_FixSlashes( rodir ); + COM_StripDirectorySlash( rodir ); + + if( !Sys_GetParmFromCmdLine( "-game", gamedir )) + { + char *env = getenv( "XASH3D_GAME" ); + if( env ) + Q_strncpy( gamedir, env, sizeof( gamedir )); + else + Q_strncpy( gamedir, basedir, sizeof( gamedir )); // gamedir == basedir + } + + FS_LoadProgs(); + + // TODO: this function will cause engine to stop in case of fail + // when it will have an option to return string error, restore Sys_Error + // FIXME: why do we call this function before InitStdio? + // because InitStdio immediately scans all available game directories + // and this better be reworked at some point + g_fsapi.SetCurrentDirectory( rootdir ); + + if( !g_fsapi.InitStdio( true, rootdir, basedir, gamedir, rodir )) + { + Sys_Error( "Can't init filesystem_stdio!\n" ); + return; + } Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" ); Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" ); -#if !XASH_WIN32 - if( Sys_CheckParm( "-casesensitive" ) ) - caseinsensitive = false; -#endif + Cvar_RegisterVariable( &fs_mount_hd ); + Cvar_RegisterVariable( &fs_mount_lv ); + Cvar_RegisterVariable( &fs_mount_addon ); + Cvar_RegisterVariable( &fs_mount_l10n ); + Cvar_RegisterVariable( &ui_language ); - if( !Sys_GetParmFromCmdLine( "-game", gamedir )) - Q_strncpy( gamedir, SI.basedirName, sizeof( gamedir )); // gamedir == basedir + if( !Sys_GetParmFromCmdLine( "-dll", host.gamedll )) + host.gamedll[0] = 0; - if( !FS_InitStdio( caseinsensitive, host.rootdir, SI.basedirName, gamedir, host.rodir )) - { - Host_Error( "Can't init filesystem_stdio!\n" ); - return; - } + if( !Sys_GetParmFromCmdLine( "-clientlib", host.clientlib )) + host.clientlib[0] = 0; - if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll )) - SI.gamedll[0] = 0; - - if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib )) - SI.clientlib[0] = 0; + if( !Sys_GetParmFromCmdLine( "-menulib", host.menulib )) + host.menulib[0] = 0; } /* @@ -139,15 +402,8 @@ FS_Shutdown */ void FS_Shutdown( void ) { - int i; - - FS_ShutdownStdio(); - - memset( &SI, 0, sizeof( sysinfo_t )); + if( g_fsapi.ShutdownStdio ) + g_fsapi.ShutdownStdio(); FS_UnloadProgs(); } - - - - diff --git a/engine/common/gamma.c b/engine/common/gamma.c deleted file mode 100644 index 87fd772c..00000000 --- a/engine/common/gamma.c +++ /dev/null @@ -1,94 +0,0 @@ -/* -gamma.c - gamma routines -Copyright (C) 2011 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "common.h" -#include "xash3d_mathlib.h" -#include "enginefeatures.h" - -//----------------------------------------------------------------------------- -// Gamma conversion support -//----------------------------------------------------------------------------- -static byte texgammatable[256]; // palette is sent through this to convert to screen gamma -static byte lightgammatable[256]; -static int lineargammatable[1024]; -static int screengammatable[1024]; - -void BuildGammaTable( float lightgamma, float brightness ) -{ - int i, inf; - float f, g, g1, g3; - - lightgamma = bound( 1.8f, lightgamma, 3.0f ); - brightness = bound( 0.0f, brightness, 10.0f ); - - if( brightness <= 0.0f ) - g3 = 0.125f; - else if( brightness > 1.0f ) - g3 = 0.05f; - else g3 = 0.125f - (brightness * brightness) * 0.075f; - - g = 1.0f / lightgamma; - g1 = GAMMA * g; - - for( i = 0; i < 256; i++ ) - { - f = pow( i / 255.f, GAMMA ); - - // scale up - if( brightness > 1.0f ) - f = f * brightness; - - // shift up - if( f <= g3 ) f = (f / g3) * 0.125f; - else f = 0.125f + ((f - g3) / (1.0f - g3)) * 0.875f; - - // convert linear space to desired gamma space - inf = (int)( 255.0f * pow( f, g )); - - lightgammatable[i] = bound( 0, inf, 255 ); - } - - for( i = 0; i < 256; i++ ) - { - f = 255.0f * pow(( float )i / 255.0f, TEXGAMMA ); - inf = (int)(f + 0.5f); - texgammatable[i] = bound( 0, inf, 255 ); - } - - for( i = 0; i < 1024; i++ ) - { - // convert from screen gamma space to linear space - lineargammatable[i] = 1023 * pow( i / 1023.0f, g1 ); - - // convert from linear gamma space to screen space - screengammatable[i] = 1023 * pow( i / 1023.0f, 1.0f / g1 ); - } -} - -byte LightToTexGamma( byte b ) -{ - if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) - return b; - else - return lightgammatable[b]; -} - -byte TextureToGamma( byte b ) -{ - if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) - return b; - else - return texgammatable[b]; -} diff --git a/engine/common/host.c b/engine/common/host.c index 97fdb923..b5ee1fe4 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -14,12 +14,7 @@ GNU General Public License for more details. */ #include "build.h" -#ifdef XASH_SDL -#include -#endif // XASH_SDL #include // va_args -#include // errno -#include // strerror #if !XASH_WIN32 #include // fork #include @@ -29,10 +24,10 @@ GNU General Public License for more details. #if XASH_EMSCRIPTEN #include #endif -#include #include "common.h" #include "base_cmd.h" #include "client.h" +#include "server.h" #include "netchan.h" #include "protocol.h" #include "mod_local.h" @@ -42,143 +37,224 @@ GNU General Public License for more details. #include "render_api.h" // decallist_t #include "tests.h" -pfnChangeGame pChangeGame = NULL; +static pfnChangeGame pChangeGame = NULL; host_parm_t host; // host parms -sysinfo_t SI; + +#if XASH_ANDROID +static jmp_buf return_from_main_buf; + +/* +=============== +Host_ExitInMain + +On some platforms (e.g. Android) we can't exit with exit(3) as calling it would +kill wrapper process (e.g. app_process) too early, before all resources would +be freed, contexts released, files closed, etc, etc... + +To fix this, we create jmp_buf in Host_Main function, when jumping into with +non-zero value will immediately return from it with `error_on_exit`. +=============== +*/ +void Host_ExitInMain( void ) +{ + longjmp( return_from_main_buf, 1 ); +} +#endif // XASH_ANDROID #ifdef XASH_ENGINE_TESTS struct tests_stats_s tests_stats; #endif CVAR_DEFINE( host_developer, "developer", "0", FCVAR_FILTERABLE, "engine is in development-mode" ); -CVAR_DEFINE_AUTO( sys_timescale, "1.0", FCVAR_CHEAT|FCVAR_FILTERABLE, "scale frame time" ); -CVAR_DEFINE_AUTO( sys_ticrate, "100", 0, "framerate in dedicated mode" ); +CVAR_DEFINE_AUTO( sys_timescale, "1.0", FCVAR_FILTERABLE, "scale frame time" ); -convar_t *host_serverstate; -convar_t *host_gameloaded; -convar_t *host_clientloaded; -convar_t *host_limitlocal; -convar_t *host_maxfps; -convar_t *host_framerate; -convar_t *host_sleeptime; -convar_t *con_gamemaps; -convar_t *build, *ver; +static CVAR_DEFINE_AUTO( sys_ticrate, "100", FCVAR_SERVER, "framerate in dedicated mode" ); +static CVAR_DEFINE_AUTO( host_serverstate, "0", FCVAR_READ_ONLY, "displays current server state" ); +static CVAR_DEFINE_AUTO( host_gameloaded, "0", FCVAR_READ_ONLY, "inidcates a loaded game.dll" ); +static CVAR_DEFINE_AUTO( host_clientloaded, "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" ); +CVAR_DEFINE_AUTO( host_limitlocal, "0", 0, "apply cl_cmdrate and rate to loopback connection" ); +CVAR_DEFINE( host_maxfps, "fps_max", "72", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "host fps upper limit" ); +CVAR_DEFINE_AUTO( fps_override, "0", FCVAR_FILTERABLE, "unlock higher framerate values, not supported" ); +static CVAR_DEFINE_AUTO( host_framerate, "0", FCVAR_FILTERABLE, "locks frame timing to this value in seconds" ); +static CVAR_DEFINE( host_sleeptime, "sleeptime", "1", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "milliseconds to sleep for each frame. higher values reduce fps accuracy" ); +static CVAR_DEFINE_AUTO( host_sleeptime_debug, "0", 0, "print sleeps between frames" ); +CVAR_DEFINE_AUTO( host_allow_materials, "0", FCVAR_LATCH|FCVAR_ARCHIVE, "allow texture replacements from materials/ folder" ); +CVAR_DEFINE( con_gamemaps, "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" ); -void Sys_PrintUsage( void ) +typedef struct feature_message_s { + uint32_t mask; + const char *msg; + const char *arg; +} feature_message_t; + +static const feature_message_t bugcomp_features[] = +{ +{ BUGCOMP_PENTITYOFENTINDEX_FLAG, "pfnPEntityOfEntIndex bugfix revert", "peoei" }, +{ BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG, "GoldSrc Message Rewrite Facility", "gsmrf" }, +{ BUGCOMP_SPATIALIZE_SOUND_WITH_ATTN_NONE, "spatialize sounds with zero attenuation", "sp_attn_none" }, +{ BUGCOMP_GET_GAME_DIR_FULL_PATH, "Return full path in GET_GAME_DIR()", "get_game_dir_full" } +}; + +static const feature_message_t engine_features[] = +{ +{ ENGINE_WRITE_LARGE_COORD, "Big World Support" }, +{ ENGINE_QUAKE_COMPATIBLE, "Quake Compatibility" }, +{ ENGINE_LOAD_DELUXEDATA, "Deluxemap Support" }, +{ ENGINE_PHYSICS_PUSHER_EXT, "Improved MOVETYPE_PUSH" }, +{ ENGINE_LARGE_LIGHTMAPS, "Large Lightmaps" }, +{ ENGINE_COMPENSATE_QUAKE_BUG, "Stupid Quake Bug Compensation" }, +{ ENGINE_IMPROVED_LINETRACE, "Improved Trace Line" }, +{ ENGINE_COMPUTE_STUDIO_LERP, "Studio MOVETYPE_STEP Lerping" }, +{ ENGINE_LINEAR_GAMMA_SPACE, "Linear Gamma Space" }, +{ ENGINE_STEP_POSHISTORY_LERP, "MOVETYPE_STEP Position History Based Lerping" }, +}; + +static void Sys_MakeVersionString( char *out, size_t len ) +{ + Q_snprintf( out, len, XASH_ENGINE_NAME " %i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); +} + +static void Sys_PrintUsage( const char *exename ) +{ + string version_str; const char *usage_str; -#define O(x,y) " "x" "y"\n" + Sys_MakeVersionString( version_str, sizeof( version_str )); - usage_str = "" -#if XASH_MESSAGEBOX == MSGBOX_STDERR - "\n" // dirty hack to not have Xash Error: Usage: on same line -#endif // XASH_MESSAGEBOX == MSGBOX_STDERR - "Usage:\n" -#if !XASH_MOBILE_PLATFORM +#if XASH_MESSAGEBOX != MSGBOX_STDERR #if XASH_WIN32 - O(".exe [options] [+command1] [+command2 arg]","") - #else // XASH_WIN32 - O(" [options] [+command1] [+command2 arg]","") - #endif // !XASH_WIN32 -#endif // !XASH_MOBILE_PLATFORM - "Options:\n" - O("-dev [level] ","set log verbosity 0-2") - O("-log ","write log to \"engine.log\"") - O("-nowriteconfig ","disable config save") - -#if !XASH_WIN32 - O("-casesensitive ","disable case-insensitive FS emulation") -#endif // !XASH_WIN32 - -#if !XASH_MOBILE_PLATFORM - O("-daemonize ","run engine in background, dedicated only") -#endif // !XASH_MOBILE_PLATFORM - -#if !XASH_DEDICATED - O("-toconsole ","run engine witn console open") - O("-width ","set window width") - O("-height ","set window height") - O("-oldfont ","enable unused Quake font in Half-Life") - -#if !XASH_MOBILE_PLATFORM - O("-fullscreen ","run engine in fullscreen mode") - O("-windowed ","run engine in windowed mode") - O("-dedicated ","run engine in dedicated server mode") -#endif // XASH_MOBILE_PLATFORM - -#if XASH_ANDROID - O("-nativeegl ","use native egl implementation. Use if screen does not update or black") -#endif // XASH_ANDROID - -#if XASH_WIN32 - O("-noavi ","disable AVI support") - O("-nointro ","disable intro video") -#endif // XASH_WIN32 - -#if XASH_DOS - O("-novesa ","disable vesa") -#endif // XASH_DOS - -#if XASH_VIDEO == VIDEO_FBDEV - O("-fbdev ","open selected framebuffer") - O("-ttygfx ","set graphics mode in tty") - O("-doublebuffer ","enable doublebuffering") -#endif // XASH_VIDEO == VIDEO_FBDEV - -#if XASH_SOUND == SOUND_ALSA - O("-alsadev ","open selected ALSA device") -#endif // XASH_SOUND == SOUND_ALSA - - O("-nojoy ","disable joystick support") - -#ifdef XASH_SDL - O("-sdl_joy_old_api ","use SDL legacy joystick API") - O("-sdl_renderer ","use alternative SDL_Renderer for software") -#endif // XASH_SDL - O("-nosound ","disable sound") - O("-noenginemouse ","disable mouse completely") - - O("-ref ","use selected renderer dll") - O("-gldebug ","enable OpenGL debug log") -#endif // XASH_DEDICATED - - O("-noip ","disable TCP/IP") - O("-noch ","disable crashhandler") - O("-disablehelp ","disable this message") - O("-dll ","override server DLL path") -#if !XASH_DEDICATED - O("-clientlib ","override client DLL path") + #define XASH_EXE "(xash).exe" + #else + #define XASH_EXE "(xash)" + #endif +#else + #define XASH_EXE "%s" #endif - O("-rodir ","set read-only base directory, experimental") - O("-bugcomp ","enable precise bug compatibility. Will break games that don't require it") - O(" ","Refer to engine documentation for more info") +#define O( x, y ) " "x" "y"\n" - O("-ip ","set custom ip") - O("-port ","set custom host port") - O("-clockwindow ","adjust clockwindow") + usage_str = S_USAGE XASH_EXE " [options] [+command] [+command2 arg] ...\n" + +"\nCommon options:\n" + O("-dev [level] ", "set log verbosity 0-2") + O("-log [file name] ", "write log to \"engine.log\" or [file name] if specified") + O("-logtime ", "enable writing timestamps to the log file") + O("-nowriteconfig ", "disable config save") + O("-noch ", "disable crashhandler") +#if XASH_WIN32 // !!!! + O("-minidumps ", "enable writing minidumps when game is crashed") +#endif + O("-rodir ", "set read-only base directory") + O("-bugcomp [opts] ", "enable precise bug compatibility") + O(" ", "will break games that don't require it") + O(" ", "refer to engine documentation for more info") + O("-language ", "mount localization game directory") + O("-disablehelp ", "disable this message") +#if !XASH_DEDICATED + O("-dedicated ", "run engine in dedicated mode") +#endif + +"\nNetworking options:\n" + O("-noip ", "disable IPv4") + O("-ip ", "set IPv4 address") + O("-port ", "set IPv4 port") +#if !XASH_DEDICATED + O("-clientport ", "set IPv4 client port") +#endif + O("-noip6 ", "disable IPv6") + O("-ip6 ", "set IPv6 address") + O("-port6 ", "set IPv6 port") +#if !XASH_DEDICATED + O("-clientport6 ", "set IPv6 client port") +#endif + O("-clockwindow ", "adjust clockwindow used to ignore client commands") + O(" ", "to prevent speed hacks") + +"\nGame options:\n" + O("-game ", "set game directory to start engine with") + O("-dll ", "override server DLL path") +#if !XASH_DEDICATED + O("-clientlib ", "override client DLL path") + O("-menulib ", "override menu DLL path") + O("-console ", "run engine with console enabled") + O("-toconsole ", "run engine witn console open") + O("-oldfont ", "enable unused Quake font in Half-Life") + O("-width ", "set window width") + O("-height ", "set window height") + O("-borderless ", "run engine in fullscreen borderless mode") + O("-fullscreen ", "run engine in fullscreen mode") + O("-windowed ", "run engine in windowed mode") + O("-ref ", "use selected renderer dll") + O("-gldebug ", "enable OpenGL debug log") +#if XASH_WIN32 + O("-noavi ", "disable AVI support") + O("-nointro ", "disable intro video") +#endif + O("-noenginejoy ", "disable engine builtin joystick support") + O("-noenginemouse ", "disable engine builtin mouse support") + O("-nosound ", "disable sound output") + O("-timedemo ", "run timedemo and exit") +#endif + +"\nPlatform-specific options:\n" +#if !XASH_MOBILE_PLATFORM + O("-daemonize ", "run engine as a daemon") +#endif +#if XASH_SDL == 2 + O("-sdl_renderer ","use alternative SDL_Renderer for software") +#endif // XASH_SDL +#if XASH_ANDROID && !XASH_SDL + O("-nativeegl ","use native egl implementation. Use if screen does not update or black") +#endif // XASH_ANDROID +#if XASH_DOS + O("-novesa ","disable vesa") +#endif // XASH_DOS +#if XASH_VIDEO == VIDEO_FBDEV + O("-fbdev ","open selected framebuffer") + O("-ttygfx ","set graphics mode in tty") + O("-doublebuffer ","enable doublebuffering") +#endif // XASH_VIDEO == VIDEO_FBDEV +#if XASH_SOUND == SOUND_ALSA + O("-alsadev ","open selected ALSA device") +#endif // XASH_SOUND == SOUND_ALSA ; -#undef O +#undef O +#undef XASH_EXE - Sys_Error( "%s", usage_str ); + // HACKHACK: pretty output in dedicated +#if XASH_MESSAGEBOX != MSGBOX_STDERR + Platform_MessageBox( version_str, usage_str, false ); +#else + fprintf( stderr, "%s\n", version_str ); + fprintf( stderr, usage_str, exename ); +#endif + + Sys_Quit( NULL ); } -int Host_CompareFileTime( int ft1, int ft2 ) +static void Sys_PrintBugcompUsage( const char *exename ) { - if( ft1 < ft2 ) - { - return -1; - } - else if( ft1 > ft2 ) - { - return 1; - } - return 0; -} + string version_str; + char usage_str[4096]; + char *p = usage_str; + int i; -void Host_ShutdownServer( void ) -{ - SV_Shutdown( "Server was killed\n" ); + Sys_MakeVersionString( version_str, sizeof( version_str )); + + p += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), "Known bugcomp flags are:\n" ); + for( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ ) + p += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), " %s: %s\n", bugcomp_features[i].arg, bugcomp_features[i].msg ); + p += Q_snprintf( p, sizeof( usage_str ) - ( usage_str - p ), "\nIt is possible to combine multiple flags with '+' characters.\nExample: -bugcomp flag1+flag2+flag3...\n" ); + + // HACKHACK: pretty output in dedicated +#if XASH_MESSAGEBOX != MSGBOX_STDERR + Platform_MessageBox( version_str, usage_str, false ); +#else + fprintf( stderr, "%s\n", version_str ); + fprintf( stderr, usage_str, exename ); +#endif + + Sys_Quit( NULL ); } /* @@ -186,22 +262,42 @@ void Host_ShutdownServer( void ) Host_PrintEngineFeatures ================ */ -void Host_PrintEngineFeatures( void ) +static void Host_PrintFeatures( uint32_t flags, const char *s, const feature_message_t *features, size_t size ) { - if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) - Con_Reportf( "^3EXT:^7 big world support enabled\n" ); + size_t i; - if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) - Con_Reportf( "^3EXT:^7 deluxemap support enabled\n" ); + for( i = 0; i < size; i++ ) + { + if( FBitSet( flags, features[i].mask )) + Con_Printf( "^3%s:^7 %s is enabled\n", s, features[i].msg ); + } +} - if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) - Con_Reportf( "^3EXT:^7 Improved MOVETYPE_PUSH is used\n" ); +/* +============== +Host_ValidateEngineFeatures - if( FBitSet( host.features, ENGINE_LARGE_LIGHTMAPS )) - Con_Reportf( "^3EXT:^7 Large lightmaps enabled\n" ); +validate features bits and set host.features +============== +*/ +void Host_ValidateEngineFeatures( uint32_t mask, uint32_t features ) +{ + // don't allow unsupported bits + features &= mask; - if( FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) - Con_Reportf( "^3EXT:^7 Compensate quake bug enabled\n" ); + // force bits for some games + if( !Q_stricmp( GI->gamefolder, "cstrike" ) || !Q_stricmp( GI->gamefolder, "czero" )) + SetBits( features, ENGINE_STEP_POSHISTORY_LERP ); + + // print requested first + Host_PrintFeatures( features, "EXT", engine_features, ARRAYSIZE( engine_features )); + + // now warn about incompatible bits + if( FBitSet( features, ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP ) == ( ENGINE_STEP_POSHISTORY_LERP|ENGINE_COMPUTE_STUDIO_LERP )) + Con_Printf( S_WARN "%s: incompatible ENGINE_STEP_POSHISTORY_LERP and ENGINE_COMPUTE_STUDIO_LERP are enabled!\n", __func__ ); + + // finally set global variable + host.features = features; } /* @@ -255,60 +351,39 @@ void Host_EndGame( qboolean abort, const char *message, ... ) if( abort ) Host_AbortCurrentFrame (); } -/* -================ -Host_AbortCurrentFrame - -aborts the current host frame and goes on with the next one -================ -*/ -void Host_AbortCurrentFrame( void ) -{ - longjmp( host.abortframe, 1 ); -} - /* ================== -Host_CheckSleep +Host_CalcSleep ================== */ -void Host_CheckSleep( void ) +static int Host_CalcSleep( void ) { - int sleeptime = host_sleeptime->value; - - if( Host_IsDedicated() ) + if( Host_IsDedicated( )) { // let the dedicated server some sleep - Sys_Sleep( sleeptime ); + return host_sleeptime.value; } - else + + switch( host.status ) { - if( host.status == HOST_NOFOCUS ) - { - if( SV_Active() && CL_IsInGame( )) - Sys_Sleep( sleeptime ); // listenserver - else Sys_Sleep( 20 ); // sleep 20 ms otherwise - } - else if( host.status == HOST_SLEEP ) - { - // completely sleep in minimized state - Sys_Sleep( 20 ); - } - else - { - Sys_Sleep( sleeptime ); - } + case HOST_NOFOCUS: + if( SV_Active() && CL_IsInGame()) + return host_sleeptime.value; + // fallthrough + case HOST_SLEEP: + return 20; } + + return host_sleeptime.value; } -void Host_NewInstance( const char *name, const char *finalmsg ) +static void Host_NewInstance( const char *name, const char *finalmsg ) { if( !pChangeGame ) return; host.change_game = true; - Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); - if( !Sys_NewInstance( name )) + if( !Sys_NewInstance( name, finalmsg )) pChangeGame( name ); // call from hl.exe } @@ -319,7 +394,7 @@ Host_ChangeGame_f Change game modification ================= */ -void Host_ChangeGame_f( void ) +static void Host_ChangeGame_f( void ) { int i; @@ -346,10 +421,10 @@ void Host_ChangeGame_f( void ) } else { - const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); + char finalmsg[MAX_VA_STRING]; - Host_NewInstance( arg1, arg2 ); + Q_snprintf( finalmsg, sizeof( finalmsg ), "change game to '%s'", FI->games[i]->title ); + Host_NewInstance( Cmd_Argv( 1 ), finalmsg ); } } @@ -358,13 +433,11 @@ void Host_ChangeGame_f( void ) Host_Exec_f =============== */ -void Host_Exec_f( void ) +static void Host_Exec_f( void ) { string cfgpath; byte *f; - char *txt; - fs_offset_t len; - const char *arg; + fs_offset_t len; if( Cmd_Argc() != 2 ) { @@ -372,7 +445,8 @@ void Host_Exec_f( void ) return; } - arg = Cmd_Argv( 1 ); + Q_strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath )); + COM_DefaultExtension( cfgpath, ".cfg", sizeof( cfgpath )); // append as default #ifndef XASH_DEDICATED if( !Cmd_CurrentCommandIsPrivileged() ) @@ -384,13 +458,15 @@ void Host_Exec_f( void ) "pyro.cfg", "spy.cfg", "engineer.cfg", "civilian.cfg" }; int i; + char temp[MAX_VA_STRING]; qboolean allow = false; - unprivilegedWhitelist[0] = va( "%s.cfg", clgame.mapname ); + Q_snprintf( temp, sizeof( temp ), "%s.cfg", clgame.mapname ); + unprivilegedWhitelist[0] = temp; for( i = 0; i < ARRAYSIZE( unprivilegedWhitelist ); i++ ) { - if( !Q_strcmp( arg, unprivilegedWhitelist[i] )) + if( !Q_strcmp( cfgpath, unprivilegedWhitelist[i] )) { allow = true; break; @@ -399,21 +475,15 @@ void Host_Exec_f( void ) if( !allow ) { - Con_Printf( "exec %s: not privileged or in whitelist\n", arg ); + Con_Printf( "exec %s: not privileged or in whitelist\n", cfgpath ); return; } } #endif // XASH_DEDICATED - if( !Q_stricmp( "game.cfg", arg )) - { - // don't execute game.cfg in singleplayer - if( SV_GetMaxClients() == 1 ) - return; - } - - Q_strncpy( cfgpath, arg, sizeof( cfgpath )); - COM_DefaultExtension( cfgpath, ".cfg" ); // append as default + // don't execute game.cfg in singleplayer + if( SV_GetMaxClients() == 1 && !Q_stricmp( "game.cfg", cfgpath )) + return; f = FS_LoadFile( cfgpath, &len, false ); if( !f ) @@ -422,19 +492,29 @@ void Host_Exec_f( void ) return; } - if( !Q_stricmp( "config.cfg", arg )) + // len is fs_offset_t, which can be larger than size_t + if( len >= SIZE_MAX ) + { + Con_Reportf( "%s: %s is too long\n", __func__, Cmd_Argv( 1 )); + return; + } + + if( !Q_stricmp( "config.cfg", cfgpath )) host.config_executed = true; - // adds \n\0 at end of the file - txt = Z_Calloc( len + 2 ); - memcpy( txt, f, len ); - Q_strncat( txt, "\n", len + 2 ); - Mem_Free( f ); - if( !host.apply_game_config ) - Con_Printf( "execing %s\n", arg ); - Cbuf_InsertText( txt ); - Mem_Free( txt ); + Con_Printf( "execing %s\n", Cmd_Argv( 1 )); + + // adds \n at end of the file + // FS_LoadFile always null terminates + if( f[len - 1] != '\n' ) + { + Cbuf_InsertTextLen( f, len, len + 1 ); + Cbuf_InsertTextLen( "\n", 1, 1 ); + } + else Cbuf_InsertTextLen( f, len, len ); + + Mem_Free( f ); } /* @@ -442,7 +522,7 @@ void Host_Exec_f( void ) Host_MemStats_f =============== */ -void Host_MemStats_f( void ) +static void Host_MemStats_f( void ) { switch( Cmd_Argc( )) { @@ -460,46 +540,12 @@ void Host_MemStats_f( void ) } } -void Host_Minimize_f( void ) -{ -#ifdef XASH_SDL - if( host.hWnd ) SDL_MinimizeWindow( host.hWnd ); -#endif -} - -/* -================= -Host_IsLocalGame - -singleplayer game detect -================= -*/ -qboolean Host_IsLocalGame( void ) -{ - if( SV_Active( )) - { - return ( SV_GetMaxClients() == 1 ) ? true : false; - } - else - { - return ( CL_GetMaxClients() == 1 ) ? true : false; - } -} - -qboolean Host_IsLocalClient( void ) -{ - // only the local client have the active server - if( CL_Initialized( ) && SV_Initialized( )) - return true; - return false; -} - /* ================= Host_RegisterDecal ================= */ -qboolean Host_RegisterDecal( const char *name, int *count ) +static qboolean Host_RegisterDecal( const char *name, int *count ) { char shortname[MAX_QPATH]; int i; @@ -507,7 +553,7 @@ qboolean Host_RegisterDecal( const char *name, int *count ) if( !COM_CheckString( name )) return 0; - COM_FileBase( name, shortname ); + COM_FileBase( name, shortname, sizeof( shortname )); for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) { @@ -533,15 +579,11 @@ qboolean Host_RegisterDecal( const char *name, int *count ) Host_InitDecals ================= */ -void Host_InitDecals( void ) +static void Host_InitDecals( void ) { int i, num_decals = 0; search_t *t; - // NOTE: only once resource without which engine can't continue work - if( !FS_FileExists( "gfx/conchars", false )) - Sys_Error( "W_LoadWadFile: couldn't load gfx.wad\n" ); - memset( host.draw_decals, 0, sizeof( host.draw_decals )); // lookup all the decals in decals.wad (basedir, gamedir, falldir) @@ -554,7 +596,7 @@ void Host_InitDecals( void ) } if( t ) Mem_Free( t ); - Con_Reportf( "InitDecals: %i decals\n", num_decals ); + Con_Reportf( "%s: %i decals\n", __func__, num_decals ); } /* @@ -564,11 +606,11 @@ Host_GetCommands Add them exactly as if they had been typed at the console =================== */ -void Host_GetCommands( void ) +static void Host_GetCommands( void ) { char *cmd; - while( ( cmd = Sys_Input() ) ) + while( ( cmd = Platform_Input() ) ) { Cbuf_AddText( cmd ); Cbuf_Execute(); @@ -582,11 +624,11 @@ Host_CalcFPS compute actual FPS for various modes =================== */ -double Host_CalcFPS( void ) +static double Host_CalcFPS( void ) { double fps = 0.0; - if( Host_IsDedicated() ) + if( Host_IsDedicated( )) { fps = sys_ticrate.value; } @@ -597,24 +639,22 @@ double Host_CalcFPS( void ) } else if( Host_IsLocalGame( )) { - fps = host_maxfps->value; + if( !gl_vsync.value ) + fps = host_maxfps.value; + } + else if( !SV_Active() && CL_Protocol() == PROTO_GOLDSRC && cls.state != ca_disconnected && cls.state < ca_validate ) + { + return 31.0; } else { - fps = host_maxfps->value; - if( fps == 0.0 ) fps = MAX_FPS; - fps = bound( MIN_FPS, fps, MAX_FPS ); - } - - // probably left part of this condition is redundant :-) - if( host.type != HOST_DEDICATED && Host_IsLocalGame( ) && !CL_IsTimeDemo( )) - { - // ajdust fps for vertical synchronization - if( CVAR_TO_BOOL( gl_vsync )) + if( !gl_vsync.value ) { - if( vid_displayfrequency->value != 0.0f ) - fps = vid_displayfrequency->value; - else fps = 60.0; // default + double max_fps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT; + + fps = host_maxfps.value; + if( fps == 0.0 ) fps = max_fps; + fps = bound( MIN_FPS, fps, max_fps ); } } #endif @@ -622,6 +662,84 @@ double Host_CalcFPS( void ) return fps; } +static qboolean Host_Autosleep( double dt, double scale ) +{ + double targetframetime, fps; + int sleep; + + fps = Host_CalcFPS(); + + if( fps <= 0 ) + return true; + + // limit fps to withing tolerable range + fps = bound( MIN_FPS, fps, MAX_FPS_HARD ); + + if( Host_IsDedicated( )) + targetframetime = ( 1.0 / ( fps + 1.0 )); + else targetframetime = ( 1.0 / fps ); + + sleep = Host_CalcSleep(); + if( sleep == 0 ) // no sleeps between frames, much simpler code + { + if( dt < targetframetime * scale ) + return false; + } + else + { + static double timewindow; // allocate a time window for sleeps + static int counter; // for debug + static double realsleeptime; + const double sleeptime = sleep * 0.000001; + + if( dt < targetframetime * scale ) + { + // if we have allocated time window, try to sleep + if( timewindow > realsleeptime ) + { + // Platform_Sleep isn't guaranteed to sleep an exact amount of microseconds + // so we measure the real sleep time and use it to decrease the window + double t1 = Sys_DoubleTime(), t2; + Platform_NanoSleep( sleep * 1000 ); // in usec! + t2 = Sys_DoubleTime(); + realsleeptime = t2 - t1; + + timewindow -= realsleeptime; + + if( host_sleeptime_debug.value ) + { + counter++; + + Con_NPrintf( counter, "%d: %.4f %.4f", counter, timewindow, realsleeptime ); + } + } + + return false; + } + + // if we exhausted this time window, allocate a new one after new frame + if( timewindow <= realsleeptime ) + { + double targetsleeptime = targetframetime - host.pureframetime * 2; + + if( targetsleeptime > 0 ) + timewindow = targetsleeptime; + else timewindow = 0; + + realsleeptime = sleeptime; // reset in case CPU was too busy + + if( host_sleeptime_debug.value ) + { + counter = 0; + + Con_NPrintf( 0, "tgt = %.4f, pft = %.4f, wnd = %.4f", targetframetime, host.pureframetime, timewindow ); + } + } + } + + return true; +} + /* =================== Host_FilterTime @@ -629,40 +747,26 @@ Host_FilterTime Returns false if the time is too short to run a frame =================== */ -qboolean Host_FilterTime( float time ) +static qboolean Host_FilterTime( double time ) { static double oldtime; - double fps; - double scale = sys_timescale.value; + double dt; + double scale = sys_timescale.value; host.realtime += time * scale; - fps = Host_CalcFPS( ); + dt = host.realtime - oldtime; // clamp the fps in multiplayer games - if( fps != 0.0 ) - { - // limit fps to withing tolerable range - fps = bound( MIN_FPS, fps, MAX_FPS ); - - if( Host_IsDedicated() ) - { - if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 )) * scale) - return false; - } - else - { - if(( host.realtime - oldtime ) < ( 1.0 / fps ) * scale ) - return false; - } - } + if( !Host_Autosleep( dt, scale )) + return false; host.frametime = host.realtime - oldtime; host.realframetime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); oldtime = host.realtime; // NOTE: allow only in singleplayer while demos are not active - if( host_framerate->value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( )) - host.frametime = bound( MIN_FRAMETIME, host_framerate->value * scale, MAX_FRAMETIME ); + if( host_framerate.value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( )) + host.frametime = bound( MIN_FRAMETIME, host_framerate.value * scale, MAX_FRAMETIME ); else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); return true; @@ -673,14 +777,19 @@ qboolean Host_FilterTime( float time ) Host_Frame ================= */ -void Host_Frame( float time ) +void Host_Frame( double time ) { - Host_CheckSleep(); + double t1; // decide the simulation time if( !Host_FilterTime( time )) return; + t1 = Sys_DoubleTime(); + + if( host.framecount == 0 ) + Con_DPrintf( "Time to first frame: %.3f seconds\n", t1 - host.starttime ); + Host_InputFrame (); // input frame Host_ClientBegin (); // begin client Host_GetCommands (); // dedicated in @@ -689,6 +798,7 @@ void Host_Frame( float time ) HTTP_Run(); // both server and client host.framecount++; + host.pureframetime = Sys_DoubleTime() - t1; } /* @@ -703,38 +813,29 @@ void GAME_EXPORT Host_Error( const char *error, ... ) static qboolean recursive = false; va_list argptr; - if( host.mouse_visible && !CL_IsInMenu( )) - { - // hide VGUI mouse -#ifdef XASH_SDL - SDL_ShowCursor( 0 ); -#endif - host.mouse_visible = false; - } - va_start( argptr, error ); - Q_vsprintf( hosterror1, error, argptr ); + Q_vsnprintf( hosterror1, sizeof( hosterror1 ), error, argptr ); va_end( argptr ); CL_WriteMessageHistory (); // before Q_error call if( host.framecount < 3 ) { - Sys_Error( "Host_InitError: %s", hosterror1 ); + Sys_Error( "%sInit: %s", __func__, hosterror1 ); } else if( host.framecount == host.errorframe ) { - Sys_Error( "Host_MultiError: %s", hosterror2 ); + Sys_Error( "%sMulti: %s", __func__, hosterror2 ); } else { + Con_Printf( "%s: %s", __func__, hosterror1 ); if( host.allow_console ) { UI_SetActiveMenu( false ); Key_SetKeyDest( key_console ); - Con_Printf( "Host_Error: %s", hosterror1 ); } - else MSGBOX2( hosterror1 ); + else Platform_MessageBox( "Host Error", hosterror1, true ); } // host is shutting down. don't invoke infinite loop @@ -742,20 +843,19 @@ void GAME_EXPORT Host_Error( const char *error, ... ) if( recursive ) { - Con_Printf( "Host_RecursiveError: %s", hosterror2 ); + Con_Printf( "%sRecursive: %s", __func__, hosterror2 ); Sys_Error( "%s", hosterror1 ); } recursive = true; - Q_strncpy( hosterror2, hosterror1, MAX_SYSPATH ); + Q_strncpy( hosterror2, hosterror1, sizeof( hosterror2 )); host.errorframe = host.framecount; // to avoid multply calls per frame - Q_sprintf( host.finalmsg, "Server crashed: %s", hosterror1 ); // clearing cmd buffer to prevent execute any commands COM_InitHostState(); Cbuf_Clear(); - Host_ShutdownServer(); + SV_Shutdown( "Server was killed due to an error\n" ); CL_Drop(); // drop clients // recreate world if needs @@ -768,7 +868,7 @@ void GAME_EXPORT Host_Error( const char *error, ... ) Host_AbortCurrentFrame(); } -void Host_Error_f( void ) +static void Host_Error_f( void ) { const char *error = Cmd_Argv( 1 ); @@ -776,7 +876,7 @@ void Host_Error_f( void ) Host_Error( "%s\n", error ); } -void Sys_Error_f( void ) +static void Sys_Error_f( void ) { const char *error = Cmd_Argv( 1 ); @@ -799,7 +899,7 @@ static void Host_Crash_f( void ) Host_Userconfigd_f ================= */ -void Host_Userconfigd_f( void ) +static void Host_Userconfigd_f( void ) { search_t *t; int i; @@ -809,7 +909,7 @@ void Host_Userconfigd_f( void ) for( i = 0; i < t->numfilenames; i++ ) { - Cbuf_AddText( va("exec %s\n", t->filenames[i] ) ); + Cbuf_AddTextf( "exec %s\n", t->filenames[i] ); } Mem_Free( t ); @@ -822,68 +922,140 @@ static void Host_RunTests( int stage ) { case 0: // early engine load memset( &tests_stats, 0, sizeof( tests_stats )); - Test_RunLibCommon(); - Test_RunCommon(); - Test_RunCmd(); - Test_RunCvar(); + TEST_LIST_0; #if !XASH_DEDICATED - Test_RunCon(); + TEST_LIST_0_CLIENT; #endif /* XASH_DEDICATED */ break; case 1: // after FS load - Test_RunImagelib(); + TEST_LIST_1; #if !XASH_DEDICATED - Test_RunVOX(); + TEST_LIST_1_CLIENT; #endif Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed ); - Sys_Quit(); + error_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS; + Sys_Quit( NULL ); } } #endif +static int Host_CheckBugcomp_splitstr_handler( char *prev, char *next, void *userdata ) +{ + size_t i; + uint32_t *flags = userdata; + + *next = '\0'; + + if( !COM_CheckStringEmpty( prev )) + return 0; + + for( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ ) + { + if( !Q_stricmp( bugcomp_features[i].arg, prev )) + { + SetBits( *flags, bugcomp_features[i].mask ); + break; + } + } + + if( i == ARRAYSIZE( bugcomp_features )) + { + Con_Printf( S_ERROR "Unknown bugcomp flag %s\n", prev ); + Con_Printf( "Valid flags are:\n" ); + for( i = 0; i < ARRAYSIZE( bugcomp_features ); i++ ) + Con_Printf( "\t%s: %s\n", bugcomp_features[i].arg, bugcomp_features[i].msg ); + } + + return 0; +} + +static uint32_t Host_CheckBugcomp( void ) +{ + uint32_t flags = 0; + string args; + + if( !Sys_CheckParm( "-bugcomp" )) + return 0; + + if( Sys_GetParmFromCmdLine( "-bugcomp", args ) && isalpha( args[0] )) + { + Q_splitstr( args, '+', &flags, Host_CheckBugcomp_splitstr_handler ); + } + else + { + // no argument specified -bugcomp just enables everything + flags = -1; + } + + Host_PrintFeatures( flags, "BUGCOMP", bugcomp_features, ARRAYSIZE( bugcomp_features )); + + return flags; +} + +static void Host_DetermineExecutableName( char *out, size_t size ) +{ +#if XASH_WIN32 + char temp[MAX_SYSPATH]; + + if( GetModuleFileName( NULL, temp, sizeof( temp ))) + COM_FileBase( temp, out, size ); +#else + if( host.argc > 0 ) + COM_FileBase( host.argv[0], out, size ); + else + Q_strncpy( out, "xash", size ); +#endif +} + /* ================= Host_InitCommon ================= */ -void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame ) +static void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bChangeGame, char *exename, size_t exename_size ) { - char dev_level[4]; - int developer = DEFAULT_DEV; - const char *baseDir; - char ticrate[16]; - int len; + const char *basedir = progname[0] == '#' ? progname + 1 : progname; + char dev_level[4], ticrate[16]; + int developer = DEFAULT_DEV; // some commands may turn engine into infinite loop, // e.g. xash.exe +game xash -game xash // so we clear all cmd_args, but leave dbg states as well Sys_ParseCommandLine( argc, argv ); + Host_DetermineExecutableName( exename, exename_size ); - if( !Sys_CheckParm( "-disablehelp" ) ) + if( !Sys_CheckParm( "-disablehelp" )) { - if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" ) ) - { - Sys_PrintUsage(); - } + string arg; + + if( Sys_CheckParm( "-help" ) || Sys_CheckParm( "-h" ) || Sys_CheckParm( "--help" )) + Sys_PrintUsage( exename ); + + if( Sys_GetParmFromCmdLine( "-bugcomp", arg ) && !Q_stricmp( arg, "help" )) + Sys_PrintBugcompUsage( exename ); } - if( !Sys_CheckParm( "-noch" ) ) - Sys_SetupCrashHandler(); + if( !Sys_CheckParm( "-noch" )) + Sys_SetupCrashHandler( argv[0] ); +#if XASH_DLL_LOADER host.enabledll = !Sys_CheckParm( "-nodll" ); +#endif host.change_game = bChangeGame || Sys_CheckParm( "-changegame" ); host.config_executed = false; host.status = HOST_INIT; // initialzation started + host.type = HOST_DEDICATED; // predict state +#ifndef XASH_DEDICATED + if( !Sys_CheckParm( "-dedicated" )) + host.type = HOST_NORMAL; +#endif Memory_Init(); // init memory subsystem host.mempool = Mem_AllocPool( "Zone Engine" ); - // HACKHACK: Quake console is always allowed - // TODO: determine if we are running QWrap more reliable - if( Sys_CheckParm( "-console" ) || !Q_stricmp( SI.exeName, "quake" )) - host.allow_console = true; + host.allow_console = DEFAULT_ALLOWCONSOLE || DEFAULT_DEV > 0; if( Sys_CheckParm( "-dev" )) { @@ -905,51 +1077,13 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha } #endif - host.con_showalways = true; - -#if XASH_DEDICATED - host.type = HOST_DEDICATED; // predict state -#else - if( Sys_CheckParm("-dedicated") || progname[0] == '#' ) - { - host.type = HOST_DEDICATED; - } - else - { - host.type = HOST_NORMAL; - } -#endif - - // set default gamedir - if( progname[0] == '#' ) - progname++; - - Q_strncpy( SI.exeName, progname, sizeof( SI.exeName )); - Q_strncpy( SI.basedirName, progname, sizeof( SI.exeName )); - - if( Host_IsDedicated() ) - { - Sys_MergeCommandLine( ); - + // always enable console for Quake and dedicated + if( !host.allow_console && ( Host_IsDedicated() || Sys_CheckParm( "-console" ) || !Q_strnicmp( exename, "quake", 5 ))) host.allow_console = true; - } - else - { - // don't show console as default - if( developer <= DEV_NORMAL ) - host.con_showalways = false; - } // member console allowing host.allow_console_init = host.allow_console; - if( Sys_CheckParm( "-bugcomp" )) - { - // add argument check here when we add other levels - // of bugcompatibility - host.bugcomp = BUGCOMP_GOLDSRC; - } - // timeBeginPeriod( 1 ); // a1ba: Do we need this? // NOTE: this message couldn't be passed into game console but it doesn't matter @@ -975,7 +1109,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( Sys_GetParmFromCmdLine( "-sys_ticrate", ticrate )) { - double fps = bound( MIN_FPS, atof( ticrate ), MAX_FPS ); + double fps = bound( MIN_FPS, atof( ticrate ), MAX_FPS_HARD ); Cvar_SetValue( "sys_ticrate", fps ); } @@ -986,83 +1120,37 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha Host_RunTests( 0 ); #endif - Platform_Init(); - - baseDir = getenv( "XASH3D_BASEDIR" ); - - if( COM_CheckString( baseDir ) ) - { - Q_strncpy( host.rootdir, baseDir, sizeof(host.rootdir) ); - } - else - { -#if TARGET_OS_IOS - const char *IOS_GetDocsDir(); - Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof(host.rootdir) ); -#elif XASH_SDL == 2 - char *szBasePath; - - if( !( szBasePath = SDL_GetBasePath() ) ) - Sys_Error( "couldn't determine current directory: %s", SDL_GetError() ); - Q_strncpy( host.rootdir, szBasePath, sizeof( host.rootdir ) ); - SDL_free( szBasePath ); -#else - if( !getcwd( host.rootdir, sizeof(host.rootdir) ) ) - { - Sys_Error( "couldn't determine current directory: %s", strerror( errno ) ); - host.rootdir[0] = 0; - } +#if XASH_DEDICATED + Platform_SetupSigtermHandling(); #endif - } - - len = Q_strlen( host.rootdir ); - - if( len && host.rootdir[len - 1] == '/' ) - host.rootdir[len - 1] = 0; - - // get readonly root. The order is: check for arg, then env. - // if still not got it, rodir is disabled. - host.rodir[0] = '\0'; - if( !Sys_GetParmFromCmdLine( "-rodir", host.rodir )) - { - char *roDir = getenv( "XASH3D_RODIR" ); - - if( COM_CheckString( roDir )) - Q_strncpy( host.rodir, roDir, sizeof( host.rodir )); - } - - len = Q_strlen( host.rodir ); - - if( len && host.rodir[len - 1] == '/' ) - host.rodir[len - 1] = 0; - - if( !COM_CheckStringEmpty( host.rootdir )) - { - Sys_Error( "Changing working directory failed (empty working directory)\n" ); - return; - } - - FS_LoadProgs(); - - if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) - Con_Reportf( "%s is working directory now\n", host.rootdir ); - else - Sys_Error( "Changing working directory to %s failed.\n", host.rootdir ); - - FS_Init(); + Platform_Init( Host_IsDedicated( ) || developer >= DEV_EXTENDED, basedir ); + FS_Init( basedir ); Sys_InitLog(); - // print bugcompatibility level here, after log was initialized - if( host.bugcomp == BUGCOMP_GOLDSRC ) + // print current developer level to simplify processing users feedback + if( developer > 0 ) { - Con_Printf( "^3BUGCOMP^7: GoldSrc bug-compatibility enabled\n" ); + int i; + + Con_Printf( "Program args: " S_YELLOW ); + for( i = 0; i < host.argc; i++ ) + Con_Printf( "%s ", host.argv[i] ); + Con_Printf( S_DEFAULT "\n" ); + + Con_Printf( "Developer level: " S_YELLOW "%i" S_DEFAULT "\n", developer ); } + host.bugcomp = Host_CheckBugcomp(); + Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" ); +#if !XASH_DEDICATED + Cmd_AddRestrictedCommand( "host_writeconfig", Host_WriteConfig, "save current configuration" ); +#endif + Image_Init(); Sound_Init(); @@ -1071,35 +1159,24 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha Host_RunTests( 1 ); #endif - FS_LoadGameInfo( NULL ); - - if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) - Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc - else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc - - Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + FS_LoadGameInfo(); + Cvar_PostFSInit(); Image_CheckPaletteQ1 (); + + // NOTE: only once resource without which engine can't continue work + if( !FS_FileExists( "gfx/conchars", false )) + Sys_Error( "%s: couldn't load gfx.wad\n", __func__ ); + Host_InitDecals (); // reload decals - // DEPRECATED: by FWGS fork -#if 0 - if( GI->secure ) - { - // clear all developer levels when game is protected - Cvar_DirectSet( &host_developer, "0" ); - host.allow_console_init = false; - host.con_showalways = false; - host.allow_console = false; - } -#endif HPAK_Init(); IN_Init(); Key_Init(); } -void Host_FreeCommon( void ) +static void Host_FreeCommon( void ) { Image_Shutdown(); Sound_Shutdown(); @@ -1108,6 +1185,19 @@ void Host_FreeCommon( void ) FS_Shutdown(); } +static void Sys_Quit_f( void ) +{ + Sys_Quit( "command" ); +} + +static void Host_MainLoop( void *userdata ) +{ + double *poldtime = (double *)userdata; + double newtime = Sys_DoubleTime(); + COM_Frame( newtime - *poldtime ); + *poldtime = newtime; +} + /* ================= Host_Main @@ -1115,11 +1205,14 @@ Host_Main */ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ) { - static double oldtime, newtime; + static double oldtime; + string demoname, exename; + + host.starttime = Sys_DoubleTime(); pChangeGame = func; // may be NULL - Host_InitCommon( argc, argv, progname, bChangeGame ); + Host_InitCommon( argc, argv, progname, bChangeGame, exename, sizeof( exename )); // init commands and vars if( host_developer.value >= DEV_EXTENDED ) @@ -1129,20 +1222,31 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Cmd_AddRestrictedCommand ( "crash", Host_Crash_f, "a way to force a bus error for development reasons"); } - host_serverstate = Cvar_Get( "host_serverstate", "0", FCVAR_READ_ONLY, "displays current server state" ); - host_maxfps = Cvar_Get( "fps_max", "72", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "host fps upper limit" ); - host_framerate = Cvar_Get( "host_framerate", "0", FCVAR_FILTERABLE, "locks frame timing to this value in seconds" ); - host_sleeptime = Cvar_Get( "sleeptime", "1", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "milliseconds to sleep for each frame. higher values reduce fps accuracy" ); - host_gameloaded = Cvar_Get( "host_gameloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded game.dll" ); - host_clientloaded = Cvar_Get( "host_clientloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" ); - host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" ); - con_gamemaps = Cvar_Get( "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" ); + Cvar_RegisterVariable( &host_allow_materials ); + Cvar_RegisterVariable( &host_serverstate ); + Cvar_RegisterVariable( &host_maxfps ); + Cvar_RegisterVariable( &fps_override ); + Cvar_RegisterVariable( &host_framerate ); + Cvar_RegisterVariable( &host_sleeptime ); + Cvar_RegisterVariable( &host_sleeptime_debug ); + Cvar_RegisterVariable( &host_gameloaded ); + Cvar_RegisterVariable( &host_clientloaded ); + Cvar_RegisterVariable( &host_limitlocal ); + Cvar_RegisterVariable( &con_gamemaps ); Cvar_RegisterVariable( &sys_timescale ); - build = Cvar_Get( "buildnum", va( "%i", Q_buildnum_compat()), FCVAR_READ_ONLY, "returns a current build number" ); - ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()), FCVAR_READ_ONLY, "shows an engine version" ); - Cvar_Get( "host_ver", va( "%i %s %s %s %s", Q_buildnum(), XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildcommit() ), FCVAR_READ_ONLY, "detailed info about this build" ); - Cvar_Get( "host_lowmemorymode", va( "%i", XASH_LOW_MEMORY ), FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)" ); + Cvar_Getf( "buildnum", FCVAR_READ_ONLY, "returns a current build number", "%i", Q_buildnum_compat()); + Cvar_Getf( "ver", FCVAR_READ_ONLY, "shows an engine version", "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()); + Cvar_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); + Cvar_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 ); + + Cvar_Get( "host_hl25_extended_structs", +#if SUPPORT_HL25_EXTENDED_STRUCTS + "1", +#else + "0", +#endif + FCVAR_READ_ONLY, "indicates if engine was compiled with extended msurface_t struct" ); Mod_Init(); NET_Init(); @@ -1164,21 +1268,27 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa CL_Init(); HTTP_Init(); - ID_Init(); + SoundList_Init(); - if( Host_IsDedicated() ) + if( Host_IsDedicated( )) { #ifdef _WIN32 Wcon_InitConsoleCommands (); #endif - Cmd_AddRestrictedCommand( "quit", Sys_Quit, "quit the game" ); - Cmd_AddRestrictedCommand( "exit", Sys_Quit, "quit the game" ); + // disable texture replacements for dedicated + Cvar_FullSet( "host_allow_materials", "0", FCVAR_READ_ONLY ); + + Cmd_AddRestrictedCommand( "quit", Sys_Quit_f, "quit the game" ); + Cmd_AddRestrictedCommand( "exit", Sys_Quit_f, "quit the game" ); } - else Cmd_AddRestrictedCommand( "minimize", Host_Minimize_f, "minimize main window to tray" ); + else Cmd_AddRestrictedCommand( "minimize", Platform_Minimize_f, "minimize main window to tray" ); host.errorframe = 0; + if( progname[0] == '#' ) + progname++; + // post initializations switch( host.type ) { @@ -1187,8 +1297,14 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Wcon_ShowConsole( false ); // hide console #endif // execute startup config and cmdline - Cbuf_AddText( va( "exec %s.rc\n", SI.rcName )); + if( FS_FileExists( va( "%s.rc", progname ), false )) // e.g. valve.rc + Cbuf_AddTextf( "exec %s.rc", progname ); + else if( FS_FileExists( va( "%s.rc", exename ), false )) // e.g. quake.rc + Cbuf_AddTextf( "exec %s.rc", exename ); + else if( FS_FileExists( va( "%s.rc", GI->gamefolder ), false )) // e.g. game.rc (ran from default launcher) + Cbuf_AddTextf( "exec %s.rc", GI->gamefolder ); Cbuf_Execute(); + if( !host.config_executed ) { Cbuf_AddText( "exec config.cfg\n" ); @@ -1208,61 +1324,100 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline) SCR_CheckStartupVids(); // must be last + if( Sys_GetParmFromCmdLine( "-timedemo", demoname )) + Cbuf_AddTextf( "timedemo %s\n", demoname ); + oldtime = Sys_DoubleTime() - 0.1; - if( Host_IsDedicated() && GameState->nextstate == STATE_RUNFRAME ) + if( Host_IsDedicated( )) { + // in dedicated server input system can't set HOST_FRAME status + // so set it here as we're finished initializing + host.status = HOST_FRAME; + + if( GameState->nextstate == STATE_RUNFRAME ) +#if XASH_WIN32 // FIXME: implement autocomplete on *nix + Con_Printf( "Type 'map ' to start game... (TAB-autocomplete is working too)\n" ); +#else // !XASH_WIN32 + Con_Printf( "Type 'map ' to start game...\n" ); +#endif // !XASH_WIN32 + // execute server.cfg after commandline // so we have a chance to set servercfgfile - Con_Printf( "Type 'map ' to start game... (TAB-autocomplete is working too)\n" ); - Cbuf_AddText( va( "exec %s\n", Cvar_VariableString( "servercfgfile" ))); + Cbuf_AddTextf( "exec %s\n", Cvar_VariableString( "servercfgfile" )); Cbuf_Execute(); } - // main window message loop - while( !host.crashed ) - { - newtime = Sys_DoubleTime (); - COM_Frame( newtime - oldtime ); - oldtime = newtime; - } + // check after all configs were executed + HPAK_CheckIntegrity( hpk_custom_file.string ); + +#if XASH_ANDROID + if( setjmp( return_from_main_buf )) + return error_on_exit; +#endif // XASH_ANDROID + +#if !XASH_EMSCRIPTEN + // main window message loop + while( host.status != HOST_CRASHED ) + Host_MainLoop( &oldtime ); +#else // XASH_EMSCRIPTEN + emscripten_set_main_loop_arg( Host_MainLoop, &oldtime, 0, false ); +#endif // XASH_EMSCRIPTEN - // never reached return 0; } +void EXPORT Host_Shutdown( void ); +void EXPORT Host_Shutdown( void ) +{ + Host_ShutdownWithReason( "launcher shutdown" ); +} + /* ================= Host_Shutdown ================= */ -void EXPORT Host_Shutdown( void ) +void Host_ShutdownWithReason( const char *reason ) { - if( host.shutdown_issued ) return; + qboolean error = host.status == HOST_ERR_FATAL; + + if( host.shutdown_issued ) + return; + host.shutdown_issued = true; - if( host.status != HOST_ERR_FATAL ) host.status = HOST_SHUTDOWN; // prepare host to normal shutdown - if( !host.change_game ) Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg )); + if( reason != NULL ) + Con_Printf( S_NOTE "Issuing host shutdown due to reason \"%s\"\n", reason ); + + if( host.status != HOST_ERR_FATAL ) + host.status = HOST_SHUTDOWN; // prepare host to normal shutdown #if !XASH_DEDICATED - if( host.type == HOST_NORMAL ) + if( host.type == HOST_NORMAL && !error ) Host_WriteConfig(); #endif SV_Shutdown( "Server shutdown\n" ); + SV_UnloadProgs(); SV_ShutdownFilter(); CL_Shutdown(); + SoundList_Shutdown(); Mod_Shutdown(); NET_Shutdown(); HTTP_Shutdown(); Host_FreeCommon(); Platform_Shutdown(); + BaseCmd_Shutdown(); + Cmd_Shutdown(); + Cvar_Shutdown(); + // must be last, console uses this Mem_FreePool( &host.mempool ); // restore filter Sys_RestoreCrashHandler(); - Sys_CloseLog(); + Sys_CloseLog( reason ); } diff --git a/engine/common/host_state.c b/engine/common/host_state.c index a1ffeda9..df28a719 100644 --- a/engine/common/host_state.c +++ b/engine/common/host_state.c @@ -16,6 +16,8 @@ GNU General Public License for more details. #include "common.h" #include "platform/platform.h" +static jmp_buf g_abortframe; + void COM_InitHostState( void ) { memset( GameState, 0, sizeof( game_status_t )); @@ -57,6 +59,11 @@ void COM_NewGame( char const *pMapName ) GameState->landmarkName[0] = 0; GameState->loadGame = false; GameState->newGame = true; + + if( !SV_Active( )) + CL_Disconnect( ); // disconnect from current online game + + SV_ShutdownGame(); // exit from current game } void COM_LoadLevel( char const *pMapName, qboolean background ) @@ -74,6 +81,11 @@ void COM_LoadLevel( char const *pMapName, qboolean background ) GameState->landmarkName[0] = 0; GameState->loadGame = false; GameState->newGame = false; + + if( !SV_Active( )) + CL_Disconnect( ); // disconnect from current online game + + SV_ShutdownGame(); // exit from current game } void COM_LoadGame( char const *pMapName ) @@ -89,6 +101,9 @@ void COM_LoadGame( char const *pMapName ) GameState->backgroundMap = false; GameState->newGame = false; GameState->loadGame = true; + + if( !SV_Active( )) + CL_Disconnect( ); // disconnect from current online game } void COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background ) @@ -117,7 +132,7 @@ void COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean GameState->newGame = false; } -void Host_ShutdownGame( void ) +static void Host_ShutdownGame( void ) { SV_ShutdownGame(); @@ -133,7 +148,7 @@ void Host_ShutdownGame( void ) } } -void Host_RunFrame( float time ) +static void Host_RunFrame( double time ) { // at this time, we don't need to get events from OS on dedicated #if !XASH_DEDICATED @@ -164,11 +179,23 @@ void Host_RunFrame( float time ) } } -void COM_Frame( float time ) +/* +================ +Host_AbortCurrentFrame + +aborts the current host frame and goes on with the next one +================ +*/ +void Host_AbortCurrentFrame( void ) +{ + longjmp( g_abortframe, 1 ); +} + +void COM_Frame( double time ) { int loopCount = 0; - if( setjmp( host.abortframe )) + if( setjmp( g_abortframe )) return; while( 1 ) diff --git a/engine/common/hpak.c b/engine/common/hpak.c index 9a179439..5f7c9dcd 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -17,8 +17,6 @@ GNU General Public License for more details. #include "hpak.h" #define HPAK_MAX_ENTRIES 0x8000 -#define HPAK_MIN_SIZE (1 * 1024) -#define HPAK_MAX_SIZE (128 * 1024) typedef struct hash_pack_queue_s { @@ -29,12 +27,18 @@ typedef struct hash_pack_queue_s struct hash_pack_queue_s *next; } hash_pack_queue_t; -convar_t *hpk_maxsize; -hash_pack_queue_t *gp_hpak_queue = NULL; -hpak_header_t hash_pack_header; -hpak_info_t hash_pack_info; +static CVAR_DEFINE( hpk_maxsize, "hpk_max_size", "64", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "set limit by size for all HPK-files in megabytes ( 0 - unlimited )" ); +CVAR_DEFINE_AUTO( hpk_custom_file, "custom.hpk", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "set custom path for players customizations cache file" ); +static hash_pack_queue_t *gp_hpak_queue = NULL; +static hpak_header_t hash_pack_header; +static hpak_info_t hash_pack_info; -const char *HPAK_TypeFromIndex( int type ) +static void HPAK_MaxSize_f( void ) +{ + Con_Printf( S_ERROR "hpk_maxsize is deprecated, use hpk_max_size\n" ); +} + +const char *COM_ResourceTypeFromIndex( int type ) { switch( type ) { @@ -73,7 +77,7 @@ static void HPAK_AddToQueue( const char *name, resource_t *pResource, void *data if( data != NULL ) memcpy( p->data, data, p->size ); else if( f != NULL ) FS_Read( f, p->data, p->size ); - else Host_Error( "HPAK_AddToQueue: data == NULL.\n" ); + else Host_Error( "%s: data == NULL.\n", __func__ ); p->next = gp_hpak_queue; gp_hpak_queue = p; @@ -94,14 +98,13 @@ void HPAK_FlushHostQueue( void ) gp_hpak_queue = NULL; } -void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, file_t *fin ) +static void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, file_t *fin ) { int filelocation; string pakname; byte md5[16]; file_t *fout; - MD5Context_t ctx; - dresource_t dresource; + MD5Context_t ctx = { 0 }; if( !COM_CheckString( filename )) return; @@ -110,19 +113,18 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "creating HPAK %s.\n", pakname ); - fout = FS_Open( pakname, "wb", false ); + fout = FS_Open( pakname, "wb", true ); if( !fout ) { - Con_DPrintf( S_ERROR "HPAK_CreatePak: can't write %s.\n", pakname ); + Con_DPrintf( S_ERROR "%s: can't write %s.\n", __func__, pakname ); return; } // let's hash it. - memset( &ctx, 0, sizeof( MD5Context_t )); MD5Init( &ctx ); if( pData == NULL ) @@ -146,7 +148,7 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f if( memcmp( md5, pResource->rgucMD5_hash, 16 )) { - Con_DPrintf( S_ERROR "HPAK_CreatePak: bad checksum for %s. Ignored\n", pakname ); + Con_DPrintf( S_ERROR "%s: bad checksum for %s. Ignored\n", __func__, pakname ); return; } @@ -211,22 +213,21 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, file_t *file_src; file_t *file_dst; byte md5[16]; - MD5Context_t ctx; + MD5Context_t ctx = { 0 }; if( pData == NULL && pFile == NULL ) return; - if( pResource->nDownloadSize < HPAK_MIN_SIZE || pResource->nDownloadSize > HPAK_MAX_SIZE ) + if( pResource->nDownloadSize < HPAK_ENTRY_MIN_SIZE || pResource->nDownloadSize > HPAK_ENTRY_MAX_SIZE ) { - Con_Printf( S_ERROR "%s: invalid size %s\n", name, Q_pretifymem( pResource->nDownloadSize, 2 )); + Con_Printf( S_ERROR "%s: invalid size %s\n", name, Q_memprint( pResource->nDownloadSize )); return; } // hash it - memset( &ctx, 0, sizeof( MD5Context_t )); MD5Init( &ctx ); - if( pData == NULL ) + if( !pData ) { byte *temp; @@ -247,7 +248,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, if( memcmp( md5, pResource->rgucMD5_hash, 16 )) { - Con_DPrintf( S_ERROR "HPAK_AddLump: bad checksum for %s. Ignored\n", pResource->szFileName ); + Con_DPrintf( S_ERROR "%s: bad checksum for %s. Ignored\n", __func__, pResource->szFileName ); return; } @@ -258,9 +259,9 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, } Q_strncpy( srcname, name, sizeof( srcname )); - COM_ReplaceExtension( srcname, ".hpk" ); + COM_ReplaceExtension( srcname, ".hpk", sizeof( srcname )); - file_src = FS_Open( srcname, "rb", false ); + file_src = FS_Open( srcname, "rb", true ); if( !file_src ) { @@ -270,13 +271,13 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, } Q_strncpy( dstname, srcname, sizeof( dstname )); - COM_ReplaceExtension( dstname, ".hp2" ); + COM_ReplaceExtension( dstname, ".hp2", sizeof( dstname )); - file_dst = FS_Open( dstname, "wb", false ); + file_dst = FS_Open( dstname, "wb", true ); if( !file_dst ) { - Con_DPrintf( S_ERROR "HPAK_AddLump: couldn't open %s.\n", srcname ); + Con_DPrintf( S_ERROR "%s: couldn't open %s.\n", __func__, srcname ); FS_Close( file_src ); return; } @@ -287,7 +288,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, if( hash_pack_header.version != IDHPAK_VERSION ) { // we don't check the HPAK bit for some reason. - Con_DPrintf( S_ERROR "HPAK_AddLump: %s does not have a valid header.\n", srcname ); + Con_DPrintf( S_ERROR "%s: %s does not have a valid header.\n", __func__, srcname ); FS_Close( file_src ); FS_Close( file_dst ); return; @@ -302,7 +303,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, if( srcpak.count < 1 || srcpak.count > HPAK_MAX_ENTRIES ) { - Con_DPrintf( S_ERROR "HPAK_AddLump: %s contain too many lumps.\n", srcname ); + Con_DPrintf( S_ERROR "%s: %s contain too many lumps.\n", __func__, srcname ); FS_Close( file_src ); FS_Close( file_dst ); return; @@ -325,11 +326,12 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, // make a new container dstpak.count = srcpak.count + 1; dstpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * dstpak.count ); - memcpy( dstpak.entries, srcpak.entries, srcpak.count ); + memcpy( dstpak.entries, srcpak.entries, sizeof( hpak_lump_t ) * srcpak.count ); + // check is there are entry with same hash for( i = 0; i < srcpak.count; i++ ) { - if( memcmp( md5, srcpak.entries[i].resource.rgucMD5_hash, 16 )) + if( memcmp( md5, srcpak.entries[i].resource.rgucMD5_hash, 16 ) == 0 ) { pCurrentEntry = &dstpak.entries[i]; @@ -347,8 +349,10 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, pCurrentEntry->filepos = FS_Tell( file_dst ); pCurrentEntry->disksize = pResource->nDownloadSize; - if( !pData ) FS_FileCopy( file_dst, file_src, pCurrentEntry->disksize ); - else FS_Write( file_dst, pData, pCurrentEntry->disksize ); + if( !pData ) + FS_FileCopy( file_dst, pFile, pCurrentEntry->disksize ); + else + FS_Write( file_dst, pData, pCurrentEntry->disksize ); hash_pack_header.infotableofs = FS_Tell( file_dst ); FS_Write( file_dst, &dstpak.count, sizeof( dstpak.count )); @@ -374,7 +378,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, FS_Rename( dstname, srcname ); } -static qboolean HPAK_Validate( const char *filename, qboolean quiet ) +static qboolean HPAK_Validate( const char *filename, qboolean quiet, qboolean delete ) { file_t *f; hpak_lump_t *dataDir; @@ -393,12 +397,13 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) return true; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) { - Con_DPrintf( S_ERROR "Couldn't find %s.\n", pakname ); + if( !quiet ) + Con_DPrintf( S_ERROR "Couldn't find %s.\n", pakname ); return true; } @@ -407,8 +412,9 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) FS_Read( f, &hdr, sizeof( hdr )); if( hdr.ident != IDHPAKHEADER || hdr.version != IDHPAK_VERSION ) { - Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s does not have a valid HPAK header.\n", pakname ); + Con_DPrintf( S_ERROR "%s: %s does not have a valid HPAK header.\n", __func__, pakname ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -417,8 +423,9 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) if( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES ) { - Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); + Con_DPrintf( S_ERROR "%s: %s has too many lumps %u.\n", __func__, pakname, num_lumps ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -431,12 +438,13 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) for( i = 0; i < num_lumps; i++ ) { - if( dataDir[i].disksize < 1 || dataDir[i].disksize > 131071 ) + if( dataDir[i].disksize < HPAK_ENTRY_MIN_SIZE || dataDir[i].disksize > HPAK_ENTRY_MAX_SIZE ) { // odd max size - Con_DPrintf( S_ERROR "HPAK_ValidatePak: lump %i has invalid size %s\n", i, Q_pretifymem( dataDir[i].disksize, 2 )); + Con_DPrintf( S_ERROR "%s: lump %i has invalid size %s\n", __func__, i, Q_memprint( dataDir[i].disksize )); Mem_Free( dataDir ); - FS_Close(f); + FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -451,17 +459,21 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) pRes = &dataDir[i].resource; - Con_Printf( "%i: %s %s %s: ", i, HPAK_TypeFromIndex( pRes->type ), - Q_pretifymem( pRes->nDownloadSize, 2 ), pRes->szFileName ); + if( !quiet ) + { + Con_Printf( "%i: %s %s %s: ", i, COM_ResourceTypeFromIndex( pRes->type ), + Q_memprint( pRes->nDownloadSize ), pRes->szFileName ); + } if( memcmp( md5, pRes->rgucMD5_hash, 0x10 )) { if( quiet ) { - Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has invalid checksum.\n", pakname ); + Con_DPrintf( S_ERROR "%s: %s has invalid checksum.\n", __func__, pakname ); Mem_Free( dataPak ); Mem_Free( dataDir ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } else Con_DPrintf( S_ERROR "failed\n" ); @@ -480,11 +492,6 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) return true; } -void HPAK_ValidatePak( const char *filename ) -{ - HPAK_Validate( filename, true ); -} - void HPAK_CheckIntegrity( const char *filename ) { string pakname; @@ -493,9 +500,9 @@ void HPAK_CheckIntegrity( const char *filename ) return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - HPAK_ValidatePak( pakname ); + HPAK_Validate( pakname, true, true ); } void HPAK_CheckSize( const char *filename ) @@ -503,19 +510,19 @@ void HPAK_CheckSize( const char *filename ) string pakname; int maxsize; - maxsize = hpk_maxsize->value; + maxsize = hpk_maxsize.value; if( maxsize <= 0 ) return; if( !COM_CheckString( filename ) ) return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - if( FS_FileSize( pakname, false ) > ( maxsize * 1000000 )) + if( FS_FileSize( pakname, false ) > ( maxsize * 1024 * 1024 )) { - Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); - Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); + Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize.value ); + Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize.value ); FS_Delete( filename ); } } @@ -543,9 +550,9 @@ qboolean HPAK_ResourceForHash( const char *filename, byte *hash, resource_t *pRe } Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) return false; FS_Read( f, &header, sizeof( header )); @@ -591,9 +598,9 @@ static qboolean HPAK_ResourceForIndex( const char *filename, int index, resource return false; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); @@ -677,9 +684,9 @@ qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte } Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) return false; FS_Read( f, &header, sizeof( header )); @@ -703,7 +710,7 @@ qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) { - Con_DPrintf( S_ERROR "HPAK_GetDataPointer: %s has too many lumps %u.\n", filename, directory.count ); + Con_DPrintf( S_ERROR "%s: %s has too many lumps %u.\n", __func__, filename, directory.count ); FS_Close( f ); return false; } @@ -760,9 +767,9 @@ void HPAK_RemoveLump( const char *name, resource_t *pResource ) HPAK_FlushHostQueue(); Q_strncpy( read_path, name, sizeof( read_path )); - COM_ReplaceExtension( read_path, ".hpk" ); + COM_ReplaceExtension( read_path, ".hpk", sizeof( read_path )); - file_src = FS_Open( read_path, "rb", false ); + file_src = FS_Open( read_path, "rb", true ); if( !file_src ) { Con_DPrintf( S_ERROR "%s couldn't open.\n", read_path ); @@ -770,8 +777,8 @@ void HPAK_RemoveLump( const char *name, resource_t *pResource ) } Q_strncpy( save_path, read_path, sizeof( save_path )); - COM_ReplaceExtension( save_path, ".hp2" ); - file_dst = FS_Open( save_path, "wb", false ); + COM_ReplaceExtension( save_path, ".hp2", sizeof( save_path )); + file_dst = FS_Open( save_path, "wb", true ); if( !file_dst ) { @@ -868,7 +875,7 @@ void HPAK_RemoveLump( const char *name, resource_t *pResource ) FS_Rename( save_path, read_path ); } -void HPAK_List_f( void ) +static void HPAK_List_f( void ) { int nCurrent; hpak_header_t header; @@ -889,10 +896,10 @@ void HPAK_List_f( void ) HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "Contents for %s.\n", pakname ); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); @@ -934,8 +941,8 @@ void HPAK_List_f( void ) for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) { entry = &directory.entries[nCurrent]; - COM_FileBase( entry->resource.szFileName, lumpname ); - type = HPAK_TypeFromIndex( entry->resource.type ); + COM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname )); + type = COM_ResourceTypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); Con_Printf( "%i: %10s %s %s\n : %s\n", nCurrent + 1, type, size, lumpname, MD5_Print( entry->resource.rgucMD5_hash )); @@ -946,7 +953,7 @@ void HPAK_List_f( void ) FS_Close( f ); } -void HPAK_Extract_f( void ) +static void HPAK_Extract_f( void ) { int nCurrent; hpak_header_t header; @@ -980,10 +987,10 @@ void HPAK_Extract_f( void ) HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "Contents for %s.\n", pakname ); - f = FS_Open( pakname, "rb", false ); + f = FS_Open( pakname, "rb", true ); if( !f ) { Con_DPrintf( S_ERROR "couldn't open %s.\n", pakname ); @@ -1029,13 +1036,13 @@ void HPAK_Extract_f( void ) if( nIndex != -1 && nIndex != nCurrent ) continue; - COM_FileBase( entry->resource.szFileName, lumpname ); - type = HPAK_TypeFromIndex( entry->resource.type ); + COM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname ) ); + type = COM_ResourceTypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); Con_Printf( "Extracting %i: %10s %s %s\n", nCurrent + 1, type, size, lumpname ); - if( entry->disksize <= 0 || entry->disksize >= HPAK_MAX_SIZE ) + if( entry->disksize < HPAK_ENTRY_MIN_SIZE || entry->disksize > HPAK_ENTRY_MAX_SIZE ) { Con_DPrintf( S_WARN "Unable to extract data, size invalid: %s\n", Q_memprint( entry->disksize )); continue; @@ -1046,7 +1053,7 @@ void HPAK_Extract_f( void ) FS_Seek( f, entry->filepos, SEEK_SET ); FS_Read( f, pData, nDataSize ); - Q_snprintf( szFileOut, sizeof( szFileOut ), "hpklmps\\lmp%04i.bmp", nCurrent ); + Q_snprintf( szFileOut, sizeof( szFileOut ), "hpklmps/lmp%04i.bmp", nCurrent ); FS_WriteFile( szFileOut, pData, nDataSize ); if( pData ) Mem_Free( pData ); } @@ -1057,7 +1064,7 @@ void HPAK_Extract_f( void ) FS_Close( f ); } -void HPAK_Remove_f( void ) +static void HPAK_Remove_f( void ) { resource_t resource; @@ -1079,7 +1086,7 @@ void HPAK_Remove_f( void ) } } -void HPAK_Validate_f( void ) +static void HPAK_Validate_f( void ) { if( Cmd_Argc() != 2 ) { @@ -1087,7 +1094,7 @@ void HPAK_Validate_f( void ) return; } - HPAK_Validate( Cmd_Argv( 1 ), false ); + HPAK_Validate( Cmd_Argv( 1 ), false, false ); } void HPAK_Init( void ) @@ -1096,7 +1103,9 @@ void HPAK_Init( void ) Cmd_AddRestrictedCommand( "hpkremove", HPAK_Remove_f, "remove specified file from HPK-file" ); Cmd_AddRestrictedCommand( "hpkval", HPAK_Validate_f, "validate specified HPK-file" ); Cmd_AddRestrictedCommand( "hpkextract", HPAK_Extract_f, "extract all lumps from specified HPK-file" ); - hpk_maxsize = Cvar_Get( "hpk_maxsize", "0", FCVAR_ARCHIVE, "set limit by size for all HPK-files ( 0 - unlimited )" ); + Cmd_AddRestrictedCommand( "hpk_maxsize", HPAK_MaxSize_f, "deprecation notice for hpk_maxsize" ); + Cvar_RegisterVariable( &hpk_maxsize ); + Cvar_RegisterVariable( &hpk_custom_file ); gp_hpak_queue = NULL; } diff --git a/engine/common/hpak.h b/engine/common/hpak.h index 5e71eec8..9b22ecb6 100644 --- a/engine/common/hpak.h +++ b/engine/common/hpak.h @@ -17,6 +17,9 @@ GNU General Public License for more details. #include "custom.h" +#define HPAK_ENTRY_MIN_SIZE (512) +#define HPAK_ENTRY_MAX_SIZE (256 * 1024) + /* ======================================================================== .HPK archive format (Hash PAK - HPK) @@ -64,7 +67,7 @@ typedef struct dresource_s } dresource_t; #pragma pack( pop ) -STATIC_ASSERT( sizeof( dresource_t ) == 136, "invalid dresource_t size, HPAKs won't be compatible (no custom logo in multiplayer!)" ); +STATIC_CHECK_SIZEOF( dresource_t, 136, 136 ); typedef struct { @@ -73,6 +76,8 @@ typedef struct int infotableofs; } hpak_header_t; +STATIC_CHECK_SIZEOF( hpak_header_t, 12, 12 ); + typedef struct { dresource_t resource; @@ -80,6 +85,8 @@ typedef struct int disksize; } hpak_lump_t; +STATIC_CHECK_SIZEOF( hpak_lump_t, 144, 144 ); + typedef struct { int count; diff --git a/engine/common/imagelib/imagelib.h b/engine/common/imagelib/imagelib.h index 376ac04d..3429c34f 100644 --- a/engine/common/imagelib/imagelib.h +++ b/engine/common/imagelib/imagelib.h @@ -45,7 +45,6 @@ typedef enum typedef struct loadformat_s { - const char *formatstring; const char *ext; qboolean (*loadfunc)( const char *name, const byte *buffer, fs_offset_t filesize ); image_hint_t hint; @@ -53,7 +52,6 @@ typedef struct loadformat_s typedef struct saveformat_s { - const char *formatstring; const char *ext; qboolean (*savefunc)( const char *name, rgbdata_t *pix ); } savepixformat_t; @@ -91,7 +89,7 @@ typedef struct imglib_s // global parms rgba_t fogParams; // some water textures has info about underwater fog - image_hint_t hint; // hint for some loaders + int hint; // hint for some loaders byte *tempbuffer; // for convert operations int cmd_flags; // global imglib flags int force_flags; // override cmd_flags @@ -103,15 +101,19 @@ typedef struct imglib_s #define IMAGE_MAXHEIGHT 8192 #define LUMP_MAXWIDTH 1024 // WorldCraft limits #define LUMP_MAXHEIGHT 1024 +#define PLDECAL_MAXWIDTH 768 // total of ~2mb uncompressed rgba data +#define PLDECAL_MAXHEIGHT 768 +#define IMAGE_GRADIENT_DECAL (1<<10) // TYP_PALETTE lump in WAD enum { - LUMP_NORMAL = 0, // no alpha - LUMP_MASKED, // 1-bit alpha channel masked texture - LUMP_GRADIENT, // gradient image (decals) - LUMP_EXTENDED, // bmp images have extened palette with alpha-channel - LUMP_HALFLIFE, // get predefined half-life palette - LUMP_QUAKE1 // get predefined quake palette + LUMP_NORMAL = 0, // no alpha + LUMP_MASKED, // 1-bit alpha channel masked texture + LUMP_GRADIENT, // gradient image (decals) + LUMP_EXTENDED, // bmp images have extened palette with alpha-channel + LUMP_HALFLIFE, // get predefined half-life palette + LUMP_QUAKE1, // get predefined quake palette + LUMP_TEXGAMMA, // apply texgamma on top of palette, for half-life mips }; enum @@ -140,6 +142,8 @@ void Image_CopyPalette32bit( void ); void Image_SetPixelFormat( void ); void Image_GetPaletteQ1( void ); void Image_GetPaletteHL( void ); +size_t Image_ComputeSize( int type, int width, int height, int depth ); +void Image_GenerateMipmaps( const byte *source, int width, int height, byte *mip1, byte *mip2, byte *mip3 ); // // formats load @@ -154,6 +158,8 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi qboolean Image_LoadFNT( const char *name, const byte *buffer, fs_offset_t filesize ); qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesize ); qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize ); +qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize ); +qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize ); // // formats save @@ -161,6 +167,7 @@ qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesi qboolean Image_SaveTGA( const char *name, rgbdata_t *pix ); qboolean Image_SaveBMP( const char *name, rgbdata_t *pix ); qboolean Image_SavePNG( const char *name, rgbdata_t *pix ); +qboolean Image_SaveWAD( const char *name, rgbdata_t *pix ); // // img_quant.c @@ -171,7 +178,6 @@ rgbdata_t *Image_Quantize( rgbdata_t *pic ); // img_utils.c // void Image_Reset( void ); -rgbdata_t *ImagePack( void ); byte *Image_Copy( size_t size ); void Image_CopyParms( rgbdata_t *src ); qboolean Image_ValidSize( const char *name ); diff --git a/engine/common/imagelib/img_bmp.c b/engine/common/imagelib/img_bmp.c index c81c3701..ab13c85c 100644 --- a/engine/common/imagelib/img_bmp.c +++ b/engine/common/imagelib/img_bmp.c @@ -25,17 +25,17 @@ Image_LoadBMP qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesize ) { byte *buf_p, *pixbuf; - rgba_t palette[256]; + rgba_t palette[256] = { 0 }; int i, columns, column, rows, row, bpp = 1; int cbPalBytes = 0, padSize = 0, bps = 0; - int reflectivity[3] = { 0, 0, 0 }; + uint reflectivity[3] = { 0, 0, 0 }; qboolean load_qfont = false; bmp_t bhdr; fs_offset_t estimatedSize; if( filesize < sizeof( bhdr )) { - Con_Reportf( S_ERROR "Image_LoadBMP: %s have incorrect file size %li should be greater than %li (header)\n", name, filesize, sizeof( bhdr ) ); + Con_Reportf( S_ERROR "%s: %s have incorrect file size %li should be greater than %zu (header)\n", __func__, name, (long)filesize, sizeof( bhdr )); return false; } @@ -49,13 +49,13 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi if( memcmp( bhdr.id, "BM", 2 )) { - Con_DPrintf( S_ERROR "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: only Windows-style BMP files supported (%s)\n", __func__, name ); return false; } if(!( bhdr.bitmapHeaderSize == 40 || bhdr.bitmapHeaderSize == 108 || bhdr.bitmapHeaderSize == 124 )) { - Con_DPrintf( S_ERROR "Image_LoadBMP: %s have non-standard header size %i\n", name, bhdr.bitmapHeaderSize ); + Con_DPrintf( S_ERROR "%s: %s have non-standard header size %i\n", __func__, name, bhdr.bitmapHeaderSize ); return false; } @@ -63,14 +63,17 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi if( bhdr.fileSize != filesize ) { // Sweet Half-Life issues. splash.bmp have bogus filesize - Con_Reportf( S_WARN "Image_LoadBMP: %s have incorrect file size %li should be %i\n", name, filesize, bhdr.fileSize ); + Con_Reportf( S_WARN "%s: %s have incorrect file size %li should be %i\n", __func__, name, (long)filesize, bhdr.fileSize ); } // bogus compression? Only non-compressed supported. if( bhdr.compression != BI_RGB ) { - Con_DPrintf( S_ERROR "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); - return false; + if( bhdr.bitsPerPixel != 32 || bhdr.compression != BI_BITFIELDS ) + { + Con_DPrintf( S_ERROR "%s: only uncompressed BMP files supported (%s)\n", __func__, name ); + return false; + } } image.width = columns = bhdr.width; @@ -105,7 +108,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi estimatedSize = ( buf_p - buffer ) + cbPalBytes; if( filesize < estimatedSize ) { - Con_Reportf( S_ERROR "Image_LoadBMP: %s have incorrect file size %li should be greater than %li (palette)\n", name, filesize, estimatedSize ); + Con_Reportf( S_ERROR "%s: %s have incorrect file size %li should be greater than %li (palette)\n", __func__, name, (long)filesize, (long)estimatedSize ); return false; } @@ -174,7 +177,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi break; } - estimatedSize = ( buf_p - buffer ) + ( image.width + padSize ) * image.height * ( bhdr.bitsPerPixel >> 3 ); + estimatedSize = ( buf_p - buffer ) + image.width * image.height * ( bhdr.bitsPerPixel >> 3 ); if( filesize < estimatedSize ) { if( image.palette ) @@ -183,7 +186,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi image.palette = NULL; } - Con_Reportf( S_ERROR "Image_LoadBMP: %s have incorrect file size %li should be greater than %li (pixels)\n", name, filesize, estimatedSize ); + Con_Reportf( S_ERROR "%s: %s have incorrect file size %li should be greater than %li (pixels)\n", __func__, name, (long)filesize, (long)estimatedSize ); return false; } diff --git a/engine/common/imagelib/img_bmp.h b/engine/common/imagelib/img_bmp.h index a5728eeb..baed6380 100644 --- a/engine/common/imagelib/img_bmp.h +++ b/engine/common/imagelib/img_bmp.h @@ -25,7 +25,12 @@ GNU General Public License for more details. #define BI_FILE_HEADER_SIZE 14 #define BI_SIZE 40 // size of bitmap info header. #if !defined(BI_RGB) -#define BI_RGB 0 // uncompressed RGB bitmap(defined in wingdi.h) +#define BI_RGB 0 // uncompressed RGB bitmap (defined in wingdi.h) +#define BI_RLE8 1 +#define BI_RLE4 2 +#define BI_BITFIELDS 3 +#define BI_JPEG 4 +#define BI_PNG 5 #endif #pragma pack( push, 1 ) diff --git a/engine/common/imagelib/img_dds.c b/engine/common/imagelib/img_dds.c index 361ddae2..fa840345 100644 --- a/engine/common/imagelib/img_dds.c +++ b/engine/common/imagelib/img_dds.c @@ -17,7 +17,7 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "img_dds.h" -qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin ) +static qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin ) { word sAlpha; byte *alpha; @@ -50,7 +50,7 @@ qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin ) return false; } -qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) +static qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) { uint bits, bitmask; byte *alphamask; @@ -91,7 +91,7 @@ qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) return false; } -void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) +static void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) { uint bits = hdr->dsPixelFormat.dwRGBBitCount; @@ -104,6 +104,13 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) { switch( headerExt->dxgiFormat ) { + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + image.type = PF_BC4_UNSIGNED; + break; + case DXGI_FORMAT_BC4_SNORM: + image.type = PF_BC4_SIGNED; + break; case DXGI_FORMAT_BC6H_SF16: image.type = PF_BC6H_SIGNED; break; @@ -112,9 +119,20 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) image.type = PF_BC6H_UNSIGNED; break; case DXGI_FORMAT_BC7_UNORM: - case DXGI_FORMAT_BC7_UNORM_SRGB: case DXGI_FORMAT_BC7_TYPELESS: - image.type = PF_BC7; + image.type = PF_BC7_UNORM; + break; + case DXGI_FORMAT_BC7_UNORM_SRGB: + image.type = PF_BC7_SRGB; + break; + case DXGI_FORMAT_BC5_TYPELESS: + image.type = PF_ATI2; + break; + case DXGI_FORMAT_BC5_UNORM: + image.type = PF_BC5_UNSIGNED; + break; + case DXGI_FORMAT_BC5_SNORM: + image.type = PF_BC5_SIGNED; break; default: image.type = PF_UNKNOWN; @@ -122,7 +140,7 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) } } else - { + { switch( hdr->dsPixelFormat.dwFourCC ) { case TYPE_DXT1: @@ -143,6 +161,15 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) case TYPE_ATI2: image.type = PF_ATI2; break; + case TYPE_BC5S: + image.type = PF_BC5_SIGNED; + break; + case TYPE_BC4S: + image.type = PF_BC4_SIGNED; + break; + case TYPE_BC4U: + image.type = PF_BC4_UNSIGNED; + break; default: image.type = PF_UNKNOWN; // assume error break; @@ -188,28 +215,7 @@ void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) image.num_mips = hdr->dwMipMapCount; // get actual mip count } -size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ) -{ - switch( type ) - { - case PF_DXT1: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 ); - case PF_DXT3: - case PF_DXT5: - case PF_BC6H_SIGNED: - case PF_BC6H_UNSIGNED: - case PF_BC7: - case PF_ATI2: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 ); - case PF_LUMINANCE: return (width * height * depth); - case PF_BGR_24: - case PF_RGB_24: return (width * height * depth * 3); - case PF_BGRA_32: - case PF_RGBA_32: return (width * height * depth * 4); - } - - return 0; -} - -size_t Image_DXTCalcMipmapSize( dds_t *hdr ) +static size_t Image_DXTCalcMipmapSize( dds_t *hdr ) { size_t buffsize = 0; int i, width, height; @@ -219,13 +225,13 @@ size_t Image_DXTCalcMipmapSize( dds_t *hdr ) { width = Q_max( 1, ( hdr->dwWidth >> i )); height = Q_max( 1, ( hdr->dwHeight >> i )); - buffsize += Image_DXTGetLinearSize( image.type, width, height, image.depth ); + buffsize += Image_ComputeSize( image.type, width, height, image.depth ); } return buffsize; } -uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) +static uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) { size_t buffsize = 0; int w = image.width; @@ -255,7 +261,7 @@ uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) if( filesize != buffsize ) // main check { - Con_DPrintf( S_WARN "Image_LoadDDS: (%s) probably corrupted (%i should be %lu)\n", name, buffsize, filesize ); + Con_DPrintf( S_WARN "%s: (%s) probably corrupted (%zu should be %zu)\n", __func__, name, buffsize, filesize ); if( buffsize > filesize ) return false; } @@ -263,12 +269,12 @@ uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) return buffsize; } -void Image_DXTAdjustVolume( dds_t *hdr ) +static void Image_DXTAdjustVolume( dds_t *hdr ) { if( hdr->dwDepth <= 1 ) return; - hdr->dwLinearSize = Image_DXTGetLinearSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth ); + hdr->dwLinearSize = Image_ComputeSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth ); hdr->dwFlags |= DDS_LINEARSIZE; } @@ -294,13 +300,13 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi if( header.dwSize != sizeof( header ) - sizeof( uint )) // size of the structure (minus MagicNum) { - Con_DPrintf( S_ERROR "Image_LoadDDS: (%s) have corrupted header\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) have corrupted header\n", __func__, name ); return false; } if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure { - Con_DPrintf( S_ERROR "Image_LoadDDS: (%s) have corrupt pixelformat header\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) have corrupt pixelformat header\n", __func__, name ); return false; } @@ -323,12 +329,12 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi Image_DXTGetPixelFormat( &header, &header2 ); // and image type too :) Image_DXTAdjustVolume( &header ); - if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageDXT( image.type )) + if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type )) return false; // silently rejected if( image.type == PF_UNKNOWN ) { - Con_DPrintf( S_ERROR "Image_LoadDDS: (%s) has unrecognized type\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) has unrecognized type\n", __func__, name ); return false; } @@ -356,8 +362,10 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi SetBits( image.flags, IMAGE_HAS_ALPHA ); else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) SetBits( image.flags, IMAGE_HAS_ALPHA ); - else if ( image.type == PF_BC7 ) - SetBits(image.flags, IMAGE_HAS_ALPHA); + else if( image.type == PF_BC5_SIGNED || image.type == PF_BC5_UNSIGNED ) + SetBits( image.flags, IMAGE_HAS_ALPHA ); + else if( image.type == PF_BC7_UNORM || image.type == PF_BC7_SRGB ) + SetBits( image.flags, IMAGE_HAS_ALPHA ); if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) SetBits( image.flags, IMAGE_HAS_COLOR ); break; diff --git a/engine/common/imagelib/img_dds.h b/engine/common/imagelib/img_dds.h index b13c342c..dbf957b3 100644 --- a/engine/common/imagelib/img_dds.h +++ b/engine/common/imagelib/img_dds.h @@ -32,6 +32,9 @@ GNU General Public License for more details. #define TYPE_DX10 (('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian "DX10" #define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1" #define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" +#define TYPE_BC5S (('S'<<24)+('5'<<16)+('C'<<8)+'B') // little-endian "BC5S" +#define TYPE_BC4S (('S'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian "BC4S" +#define TYPE_BC4U (('U'<<24)+('4'<<16)+('C'<<8)+'B') // little-endian "BC4U" #define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps #define TYPE_$ (('\0'<<24)+('\0'<<16)+('\0'<<8)+'$') // little-endian "$" #define TYPE_o (('\0'<<24)+('\0'<<16)+('\0'<<8)+'o') // little-endian "o" @@ -202,7 +205,7 @@ typedef enum DXGI_FORMAT_FORCE_UINT = 0xffffffff } dxgi_format_t; -typedef enum +typedef enum { D3D10_RESOURCE_DIMENSION_UNKNOWN = 0, D3D10_RESOURCE_DIMENSION_BUFFER = 1, diff --git a/engine/common/imagelib/img_ktx2.c b/engine/common/imagelib/img_ktx2.c new file mode 100644 index 00000000..998d82b2 --- /dev/null +++ b/engine/common/imagelib/img_ktx2.c @@ -0,0 +1,211 @@ +/* +img_ktx2.c - ktx2 format load +Copyright (C) 2023 Provod + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "imagelib.h" +#include "xash3d_mathlib.h" +#include "img_ktx2.h" + +static void Image_KTX2Format( uint32_t ktx2_format ) +{ + switch( ktx2_format ) + { + case KTX2_FORMAT_BC4_UNORM_BLOCK: + image.type = PF_BC4_UNSIGNED; + // 1 component for ref_gl + break; + case KTX2_FORMAT_BC4_SNORM_BLOCK: + image.type = PF_BC4_SIGNED; + // 1 component for ref_gl + break; + case KTX2_FORMAT_BC5_UNORM_BLOCK: + image.type = PF_BC5_UNSIGNED; + // 2 components for ref_gl + SetBits( image.flags, IMAGE_HAS_ALPHA ); + break; + case KTX2_FORMAT_BC5_SNORM_BLOCK: + image.type = PF_BC5_SIGNED; + // 2 components for ref_gl + SetBits( image.flags, IMAGE_HAS_ALPHA ); + break; + case KTX2_FORMAT_BC6H_UFLOAT_BLOCK: + image.type = PF_BC6H_UNSIGNED; + // 3 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR ); + break; + case KTX2_FORMAT_BC6H_SFLOAT_BLOCK: + image.type = PF_BC6H_SIGNED; + // 3 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR ); + break; + case KTX2_FORMAT_BC7_UNORM_BLOCK: + image.type = PF_BC7_UNORM; + // 4 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA ); + break; + case KTX2_FORMAT_BC7_SRGB_BLOCK: + image.type = PF_BC7_SRGB; + // 4 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA ); + break; + default: + image.type = PF_UNKNOWN; + break; + } +} + +static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer, fs_offset_t filesize ) +{ + ktx2_index_t index; + size_t total_size = 0; + size_t max_offset = 0; + int mip, cursor; + const byte *const levels_begin = buffer + KTX2_LEVELS_OFFSET; + + // Sets image.type and image.flags + Image_KTX2Format( header->vkFormat ); + + if( image.type == PF_UNKNOWN ) + { + Con_DPrintf( S_ERROR "%s: unsupported KTX2 format %d\n", __func__, header->vkFormat ); + return false; + } + + if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageCompressed( image.type )) + { + Con_DPrintf( S_WARN "%s: has compressed format, but support is not advertized\n", __func__ ); + return false; + } + + if( header->levelCount == 0 ) + { + Con_DPrintf( S_ERROR "%s: file has no mip levels\n", __func__ ); + return false; + } + + if( header->pixelDepth > 1 ) + { + Con_DPrintf( S_ERROR "%s: unsupported KTX2 pixelDepth %d\n", __func__, header->pixelDepth ); + return false; + } + + if( header->faceCount > 1 ) + { + Con_DPrintf( S_ERROR "%s: unsupported KTX2 faceCount %d\n", __func__, header->faceCount ); + return false; + } + + if( header->layerCount > 1 ) + { + Con_DPrintf( S_ERROR "%s: unsupported KTX2 layerCount %d\n", __func__, header->layerCount ); + return false; + } + + if( header->supercompressionScheme != 0 ) + { + Con_DPrintf( S_ERROR "%s: unsupported KTX2 supercompressionScheme %d\n", __func__, header->supercompressionScheme ); + return false; + } + + if( header->levelCount * sizeof( ktx2_level_t ) + KTX2_LEVELS_OFFSET > filesize ) + { + Con_DPrintf( S_ERROR "%s: file abruptly ends\n", __func__ ); + return false; + } + + memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index )); + + for( mip = 0; mip < header->levelCount; ++mip ) + { + const uint32_t width = Q_max( 1, ( header->pixelWidth >> mip )); + const uint32_t height = Q_max( 1, ( header->pixelHeight >> mip )); + const uint32_t mip_size = Image_ComputeSize( image.type, width, height, image.depth ); + + ktx2_level_t level; + memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level )); + + if( mip_size != level.byteLength ) + { + Con_DPrintf( S_ERROR "%s: mip=%d size mismatch read=%d, but computed=%d\n", + __func__, mip, (int)level.byteLength, mip_size ); + return false; + } + + total_size += level.byteLength; + max_offset = Q_max( max_offset, level.byteLength + level.byteOffset ); + } + + if( max_offset > filesize ) + return false; + + image.size = total_size; + image.num_mips = header->levelCount; + + image.rgba = Mem_Malloc( host.imagepool, image.size ); + memcpy( image.rgba, buffer, image.size ); + + for( mip = 0, cursor = 0; mip < header->levelCount; ++mip ) + { + ktx2_level_t level; + memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level )); + memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength ); + cursor += level.byteLength; + } + + return true; +} + +qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + ktx2_header_t header; + + if( filesize < KTX2_MINIMAL_HEADER_SIZE ) + return false; + + if( memcmp( buffer, KTX2_IDENTIFIER, KTX2_IDENTIFIER_SIZE ) != 0 ) + { + Con_DPrintf( S_ERROR "%s: (%s) has invalid identifier\n", __func__, name ); + return false; + } + + memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header )); + + image.width = header.pixelWidth; + image.height = header.pixelHeight; + image.depth = Q_max( 1, header.pixelDepth ); + image.num_mips = 1; + + ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); + + if( !Image_KTX2Parse( &header, buffer, filesize )) + { + if( !Image_CheckFlag( IL_KTX2_RAW )) + return false; + + // If KTX2 to imagelib conversion failed, try passing the file as raw data. + // This is useful for ref_vk which can directly support hundreds of formats which we don't convert to pixformat_t here + + Con_DPrintf( S_WARN "%s: (%s) could not be converted to supported imagelib format, passing as raw KTX2 data\n", __func__, name ); + // This is a catch-all for ref_vk, which can do this format directly and natively + image.type = PF_KTX2_RAW; + + image.size = filesize; + //image.encode = TODO custom encode type? + + image.rgba = Mem_Malloc( host.imagepool, image.size ); + memcpy( image.rgba, buffer, image.size ); + } + + return true; +} diff --git a/engine/common/imagelib/img_ktx2.h b/engine/common/imagelib/img_ktx2.h new file mode 100644 index 00000000..e1183315 --- /dev/null +++ b/engine/common/imagelib/img_ktx2.h @@ -0,0 +1,79 @@ +/* +img_ktx2.h - ktx2 format reference +Copyright (C) 2023 Provod + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef IMG_KTX2_H +#define IMG_KTX2_H + +#include + +#define KTX2_IDENTIFIER_SIZE 12 +#define KTX2_IDENTIFIER "\xABKTX 20\xBB\r\n\x1A\n" + +/* +static const char k_ktx2_identifier[KTX2_IDENTIFIER_SIZE] = +{ + '\xAB', 'K', 'T', 'X', ' ', '2', '0', '\xBB', '\r', '\n', '\x1A', '\n' +}; +*/ + +typedef struct +{ + uint32_t vkFormat; + uint32_t typeSize; + uint32_t pixelWidth; + uint32_t pixelHeight; + uint32_t pixelDepth; + uint32_t layerCount; + uint32_t faceCount; + uint32_t levelCount; + uint32_t supercompressionScheme; +} ktx2_header_t; + +typedef struct +{ + uint32_t dfdByteOffset; + uint32_t dfdByteLength; + uint32_t kvdByteOffset; + uint32_t kvdByteLength; + uint64_t sgdByteOffset; + uint64_t sgdByteLength; +} ktx2_index_t; + +typedef struct +{ + uint64_t byteOffset; + uint64_t byteLength; + uint64_t uncompressedByteLength; +} ktx2_level_t; + +#define KTX2_LEVELS_OFFSET ( KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ) + sizeof( ktx2_index_t )) + +#define KTX2_MINIMAL_HEADER_SIZE ( KTX2_LEVELS_OFFSET + sizeof( ktx2_level_t )) + +// These have the same values as enum VkFormat in vulkan_core.h +// There are hundreds of formats which can be contained in KTX2. +// Below are listed the ones which are supported here. This list can be extended. +typedef enum +{ + KTX2_FORMAT_BC4_UNORM_BLOCK = 139, + KTX2_FORMAT_BC4_SNORM_BLOCK = 140, + KTX2_FORMAT_BC5_UNORM_BLOCK = 141, + KTX2_FORMAT_BC5_SNORM_BLOCK = 142, + KTX2_FORMAT_BC6H_UFLOAT_BLOCK = 143, + KTX2_FORMAT_BC6H_SFLOAT_BLOCK = 144, + KTX2_FORMAT_BC7_UNORM_BLOCK = 145, + KTX2_FORMAT_BC7_SRGB_BLOCK = 146, +} ktx2_format_t; + +#endif // IMG_KTX2_H diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c index 1b99e0c0..49e74ff2 100644 --- a/engine/common/imagelib/img_main.c +++ b/engine/common/imagelib/img_main.c @@ -15,6 +15,10 @@ GNU General Public License for more details. #include #include "imagelib.h" +#include "eiface.h" // ARRAYSIZE + +#define DEBUG_LOOKUPS_COUNT 0 +#define USE_FS_SEARCH_FOR_LOOKUPS 1 // global image variables imglib_t image; @@ -67,7 +71,6 @@ static const cubepack_t load_cubemap[] = { "3Ds Sky1", skybox_qv1 }, { "3Ds Sky2", skybox_qv2 }, { "3Ds Cube", cubemap_v1 }, -{ NULL, NULL }, }; // soul of ImageLib - table of image format constants @@ -87,6 +90,45 @@ const bpc_desc_t PFDesc[] = { PF_ATI2, "ATI 2", 0x8837, 4 }, }; +#if DEBUG_LOOKUPS_COUNT +static int g_lookups = 0; +static int g_lookups_total = 0; +static double g_lookup_start = 0.0f; +static double g_lookup_time = 0.0f; +static double g_lookup_time_total = 0.0f; + +static void Image_ReportLookupsCount( const char *name ) +{ + if( name[0] != '#' ) + { + Con_Reportf( "Performed %i lookups in %fs (of total %i for %fs) while loading %s\n", + g_lookups, g_lookup_time, g_lookups_total, g_lookup_time_total, name ); + } +} + +static void Image_IncrementLookupTime( void ) +{ + double t = Sys_DoubleTime(); + double dt = t - g_lookup_start; + + g_lookup_time += dt; + g_lookup_time_total += dt; + g_lookups++; + g_lookups_total++; + + g_lookup_start = Sys_DoubleTime(); +} +#else +static void Image_ReportLookupsCount( const char *name ) +{ + (void)name; +} +static void Image_IncrementLookupTime( void ) +{ + +} +#endif + void Image_Reset( void ) { // reset global variables @@ -108,22 +150,31 @@ void Image_Reset( void ) image.rgba = NULL; image.ptr = 0; image.size = 0; + +#if DEBUG_LOOKUPS_COUNT + g_lookups = 0; + g_lookup_time = 0.0f; + g_lookup_start = Sys_DoubleTime(); +#endif // DEBUG_LOOKUPS_COUNT } -rgbdata_t *ImagePack( void ) +static MALLOC_LIKE( FS_FreeImage, 1 ) rgbdata_t *ImagePack( const char *name ) { - rgbdata_t *pack = Mem_Calloc( host.imagepool, sizeof( rgbdata_t )); + rgbdata_t *pack; + + Image_ReportLookupsCount( name ); // clear any force flags image.force_flags = 0; if( image.cubemap && image.num_sides != 6 ) { - // this never be happens, just in case - FS_FreeImage( pack ); + // this never can happen, just in case return NULL; } + pack = Mem_Calloc( host.imagepool, sizeof( *pack )); + if( image.cubemap ) { image.flags |= IMAGE_CUBEMAP; @@ -163,7 +214,7 @@ FS_AddSideToPack ================ */ -qboolean FS_AddSideToPack( const char *name, int adjust_flags ) +static qboolean FS_AddSideToPack( int adjust_flags ) { byte *out, *flipped; qboolean resampled = false; @@ -203,6 +254,146 @@ qboolean FS_AddSideToPack( const char *name, int adjust_flags ) return true; } +static const loadpixformat_t *Image_GetLoadFormatForExtension( const char *ext ) +{ + const loadpixformat_t *format; + + if( !COM_CheckStringEmpty( ext )) + return NULL; + + for( format = image.loadformats; format->ext; format++ ) + { + if( !Q_stricmp( ext, format->ext )) + return format; + } + + return NULL; +} + +static qboolean Image_ProbeLoadBuffer_( const loadpixformat_t *fmt, const char *name, const byte *buf, size_t size, int override_hint ) +{ + if( override_hint > 0 ) + image.hint = override_hint; + else image.hint = fmt->hint; + + return fmt->loadfunc( name, buf, size ); +} + +static qboolean Image_ProbeLoadBuffer( const loadpixformat_t *fmt, const char *name, const byte *buf, size_t size, int override_hint ) +{ + if( unlikely( size <= 0 )) + return false; + + // bruteforce all loaders + if( !fmt ) + { + for( fmt = image.loadformats; fmt->ext; fmt++ ) + { + if( Image_ProbeLoadBuffer_( fmt, name, buf, size, override_hint )) + return true; + } + + return false; + } + + return Image_ProbeLoadBuffer_( fmt, name, buf, size, override_hint ); +} + +static qboolean Image_ProbeLoad_( const loadpixformat_t *fmt, const char *name, const char *suffix, int override_hint ) +{ + qboolean success = false; + fs_offset_t filesize; + string path; + byte *f; + + Q_snprintf( path, sizeof( path ), "%s%s.%s", name, suffix, fmt->ext ); + f = FS_LoadFile( path, &filesize, false ); + + Image_IncrementLookupTime(); + + if( f && filesize >= 0 ) + { + success = Image_ProbeLoadBuffer_( fmt, path, f, filesize, override_hint ); + + Mem_Free( f ); + } + + return success; +} + +static qboolean Image_ProbeLoad2( const char *name, const char *suffix, int override_hint ) +{ + const loadpixformat_t *fmt; + search_t *t; + string pattern; + int i; + + Q_snprintf( pattern, sizeof( pattern ), "%s%s.*", name, suffix ); + + t = FS_Search( pattern, true, false ); + + if( !t ) + return false; + + // we now have to check every extension + // to keep the loading order + for( fmt = image.loadformats; fmt->ext; fmt++ ) + { + fs_offset_t filesize; + byte *data; + + for( i = 0; i < t->numfilenames; i++ ) + { + const char *ext = COM_FileExtension( t->filenames[i] ); + + if( !Q_stricmp( ext, fmt->ext )) + break; + } + + // try next... + if( i == t->numfilenames ) + continue; + + data = FS_LoadFile( t->filenames[i], &filesize, false ); + Image_IncrementLookupTime(); + + // can't load file, ignore + if( unlikely( !data || filesize <= 0 )) + continue; + + if( Image_ProbeLoadBuffer_( fmt, t->filenames[i], data, filesize, override_hint )) + { + Mem_Free( data ); + Mem_Free( t ); + return true; + } + } + + Mem_Free( t ); + return false; +} + +static qboolean Image_ProbeLoad( const loadpixformat_t *fmt, const char *name, const char *suffix, int override_hint ) +{ + if( !fmt ) + { +#if USE_FS_SEARCH_FOR_LOOKUPS + return Image_ProbeLoad2( name, suffix, override_hint ); +#else + // bruteforce all formats to allow implicit extension + for( fmt = image.loadformats; fmt->ext; fmt++ ) + { + if( Image_ProbeLoad_( fmt, name, suffix, override_hint )) + return true; + } + + return false; +#endif + } + + return Image_ProbeLoad_( fmt, name, suffix, override_hint ); +} + /* ================ FS_LoadImage @@ -213,87 +404,36 @@ loading and unpack to rgba any known image rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) { const char *ext = COM_FileExtension( filename ); - string path, loadname, sidename; - qboolean anyformat = true; - int i; - fs_offset_t filesize = 0; - const loadpixformat_t *format; - const cubepack_t *cmap; - byte *f; + string loadname; + int i, j; + const loadpixformat_t *extfmt; Q_strncpy( loadname, filename, sizeof( loadname )); - Image_Reset(); // clear old image - if( Q_stricmp( ext, "" )) - { - // we needs to compare file extension with list of supported formats - // and be sure what is real extension, not a filename with dot - for( format = image.loadformats; format && format->formatstring; format++ ) - { - if( !Q_stricmp( format->ext, ext )) - { - COM_StripExtension( loadname ); - anyformat = false; - break; - } - } - } + // we needs to compare file extension with list of supported formats + // and be sure what is real extension, not a filename with dot + if(( extfmt = Image_GetLoadFormatForExtension( ext ))) + COM_StripExtension( loadname ); + + Image_Reset(); // clear old image // special mode: skip any checks, load file from buffer if( filename[0] == '#' && buffer && size ) goto load_internal; - // now try all the formats in the selected list - for( format = image.loadformats; format && format->formatstring; format++) - { - if( anyformat || !Q_stricmp( ext, format->ext )) - { - Q_sprintf( path, format->formatstring, loadname, "", format->ext ); - image.hint = format->hint; - f = FS_LoadFile( path, &filesize, false ); - - if( f && filesize > 0 ) - { - if( format->loadfunc( path, f, filesize )) - { - Mem_Free( f ); // release buffer - return ImagePack(); // loaded - } - else Mem_Free( f ); // release buffer - } - } - } + if( Image_ProbeLoad( extfmt, loadname, "", -1 )) + return ImagePack( filename ); // check all cubemap sides with package suffix - for( cmap = load_cubemap; cmap && cmap->type; cmap++ ) + for( j = 0; j < ARRAYSIZE( load_cubemap ); j++ ) { + const cubepack_t *cmap = &load_cubemap[j]; + for( i = 0; i < 6; i++ ) { - // for support mixed cubemaps e.g. sky_ft.bmp, sky_rt.tga - // NOTE: all loaders must keep sides in one format for all - for( format = image.loadformats; format && format->formatstring; format++ ) + if( Image_ProbeLoad( extfmt, loadname, cmap->type[i].suf, cmap->type[i].hint )) { - if( anyformat || !Q_stricmp( ext, format->ext )) - { - Q_sprintf( path, format->formatstring, loadname, cmap->type[i].suf, format->ext ); - image.hint = (image_hint_t)cmap->type[i].hint; // side hint - - f = FS_LoadFile( path, &filesize, false ); - if( f && filesize > 0 ) - { - // this name will be used only for tell user about problems - if( format->loadfunc( path, f, filesize )) - { - Q_snprintf( sidename, sizeof( sidename ), "%s%s.%s", loadname, cmap->type[i].suf, format->ext ); - if( FS_AddSideToPack( sidename, cmap->type[i].flags )) // process flags to flip some sides - { - Mem_Free( f ); - break; // loaded - } - } - Mem_Free( f ); - } - } + FS_AddSideToPack( cmap->type[i].flags ); } if( image.num_sides != i + 1 ) // check side @@ -308,7 +448,7 @@ rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) } } - // make sure what all sides is loaded + // make sure that all sides is loaded if( image.num_sides != 6 ) { // unexpected errors ? @@ -320,24 +460,20 @@ rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) } if( image.cubemap ) - return ImagePack(); // all done + return ImagePack( filename ); // all done load_internal: - for( format = image.loadformats; format && format->formatstring; format++ ) + if( buffer && size ) { - if( anyformat || !Q_stricmp( ext, format->ext )) - { - image.hint = format->hint; - if( buffer && size > 0 ) - { - if( format->loadfunc( loadname, buffer, size )) - return ImagePack(); // loaded - } - } + if( Image_ProbeLoadBuffer( extfmt, loadname, buffer, size, -1 )) + return ImagePack( filename ); } - if( filename[0] != '#' ) - Con_Reportf( S_WARN "FS_LoadImage: couldn't load \"%s\"\n", loadname ); + if( loadname[0] != '#' ) + { + Con_Reportf( S_WARN "%s: couldn't load \"%s\"\n", __func__, loadname ); + Image_ReportLookupsCount( filename ); + } // clear any force flags image.force_flags = 0; @@ -345,6 +481,8 @@ load_internal: return NULL; } + + /* ================ Image_Save @@ -355,7 +493,7 @@ writes image as any known format qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) { const char *ext = COM_FileExtension( filename ); - qboolean anyformat = !Q_stricmp( ext, "" ) ? true : false; + qboolean anyformat = !COM_CheckStringEmpty( ext ); string path, savename; const savepixformat_t *format; @@ -391,13 +529,13 @@ qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) picBuffer = pix->buffer; // save all sides seperately - for( format = image.saveformats; format && format->formatstring; format++ ) + for( format = image.saveformats; format && format->ext; format++ ) { if( !Q_stricmp( ext, format->ext )) { for( i = 0; i < 6; i++ ) { - Q_sprintf( path, format->formatstring, savename, box[i].suf, format->ext ); + Q_snprintf( path, sizeof( path ), "%s%s.%s", savename, box[i].suf, format->ext ); if( !format->savefunc( path, pix )) break; // there were errors pix->buffer += pix->size; // move pointer } @@ -415,11 +553,11 @@ qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) } else { - for( format = image.saveformats; format && format->formatstring; format++ ) + for( format = image.saveformats; format && format->ext; format++ ) { if( !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, savename, "", format->ext ); + Q_snprintf( path, sizeof( path ), "%s.%s", savename, format->ext ); if( format->savefunc( path, pix )) { // clear any force flags @@ -520,7 +658,7 @@ static void Test_CheckImage( const char *name, rgbdata_t *rgb ) TASSERT( load->size == rgb->size ) TASSERT( memcmp(load->buffer, rgb->buffer, rgb->size ) == 0 ) - Mem_Free( load ); + FS_FreeImage( load ); } void Test_RunImagelib( void ) @@ -551,10 +689,13 @@ void Test_RunImagelib( void ) for( i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++ ) { - const char *name = va( "test_gen.%s", extensions[i] ); + qboolean ret; + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), "test_gen.%s", extensions[i] ); // test saving - qboolean ret = FS_SaveImage( name, &rgb ); + ret = FS_SaveImage( name, &rgb ); Con_Printf( "Checking if we can save images in '%s' format...\n", extensions[i] ); ASSERT(ret == true); @@ -567,6 +708,7 @@ void Test_RunImagelib( void ) } #define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \ +int export( const uint8_t *Data, size_t Size ); \ int EXPORT export( const uint8_t *Data, size_t Size ) \ { \ rgbdata_t *rgb; \ @@ -575,7 +717,7 @@ int EXPORT export( const uint8_t *Data, size_t Size ) \ Image_Init(); \ if( target( "#internal", Data, Size )) \ { \ - rgb = ImagePack(); \ + rgb = ImagePack( "#internal" ); \ FS_FreeImage( rgb ); \ } \ Image_Shutdown(); \ diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 25b32544..a089ea37 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -20,6 +20,9 @@ GNU General Public License for more details. #if defined(XASH_NO_NETWORK) #include "platform/stub/net_stub.h" +#elif XASH_NSWITCH + // our ntohl is here + #include #elif !XASH_WIN32 #include #endif @@ -41,10 +44,10 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi { int ret; short p, a, b, c, pa, pb, pc; - byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL, *rowend; + byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL; byte *pallete = NULL, *trns = NULL; - uint chunk_len, trns_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; - uint uncompressed_size, pixel_size, i, y, filter_type, chunk_sign, r_alpha, g_alpha, b_alpha; + uint chunk_len, trns_len = 0, plte_len = 0, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; + uint uncompressed_size, pixel_size, pixel_count, i, y, filter_type, chunk_sign, r_alpha, g_alpha, b_alpha; qboolean has_iend_chunk = false; z_stream stream = {0}; png_t png_hdr; @@ -60,7 +63,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // check png signature if( memcmp( png_hdr.sign, png_sign, sizeof( png_sign ) ) ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid PNG signature (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: Invalid PNG signature (%s)\n", __func__, name ); return false; } @@ -70,30 +73,33 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // check IHDR chunk length (valid value - 13) if( png_hdr.ihdr_len != sizeof( png_ihdr_t ) ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid IHDR chunk size %u (%s)\n", png_hdr.ihdr_len, name ); + Con_DPrintf( S_ERROR "%s: Invalid IHDR chunk size %u (%s)\n", __func__, png_hdr.ihdr_len, name ); return false; } // check IHDR chunk signature if( memcmp( png_hdr.ihdr_sign, ihdr_sign, sizeof( ihdr_sign ) ) ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IHDR chunk corrupted (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IHDR chunk corrupted (%s)\n", __func__, name ); return false; } // convert image width and height to little endian - png_hdr.ihdr_chunk.height = ntohl( png_hdr.ihdr_chunk.height ); - png_hdr.ihdr_chunk.width = ntohl( png_hdr.ihdr_chunk.width ); + image.height = png_hdr.ihdr_chunk.height = ntohl( png_hdr.ihdr_chunk.height ); + image.width = png_hdr.ihdr_chunk.width = ntohl( png_hdr.ihdr_chunk.width ); if( png_hdr.ihdr_chunk.height == 0 || png_hdr.ihdr_chunk.width == 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid image size %ux%u (%s)\n", png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); + Con_DPrintf( S_ERROR "%s: Invalid image size %ux%u (%s)\n", __func__, png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); return false; } + if( !Image_ValidSize( name )) + return false; + if( png_hdr.ihdr_chunk.bitdepth != 8 ) { - Con_DPrintf( S_WARN "Image_LoadPNG: Only 8-bit images is supported (%s)\n", name ); + Con_DPrintf( S_WARN "%s: Only 8-bit images is supported (%s)\n", __func__, name ); return false; } @@ -103,31 +109,31 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi || png_hdr.ihdr_chunk.colortype == PNG_CT_ALPHA || png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE ) ) { - Con_DPrintf( S_WARN "Image_LoadPNG: Unknown color type %u (%s)\n", png_hdr.ihdr_chunk.colortype, name ); + Con_DPrintf( S_WARN "%s: Unknown color type %u (%s)\n", __func__, png_hdr.ihdr_chunk.colortype, name ); return false; } if( png_hdr.ihdr_chunk.compression > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown compression method %u (%s)\n", png_hdr.ihdr_chunk.compression, name ); + Con_DPrintf( S_ERROR "%s: Unknown compression method %u (%s)\n", __func__, png_hdr.ihdr_chunk.compression, name ); return false; } if( png_hdr.ihdr_chunk.filter > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown filter type %u (%s)\n", png_hdr.ihdr_chunk.filter, name ); + Con_DPrintf( S_ERROR "%s: Unknown filter type %u (%s)\n", __func__, png_hdr.ihdr_chunk.filter, name ); return false; } if( png_hdr.ihdr_chunk.interlace == 1 ) { - Con_DPrintf( S_WARN "Image_LoadPNG: Adam7 Interlacing not supported (%s)\n", name ); + Con_DPrintf( S_WARN "%s: Adam7 Interlacing not supported (%s)\n", __func__, name ); return false; } if( png_hdr.ihdr_chunk.interlace > 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown interlacing type %u (%s)\n", png_hdr.ihdr_chunk.interlace, name ); + Con_DPrintf( S_ERROR "%s: Unknown interlacing type %u (%s)\n", __func__, png_hdr.ihdr_chunk.interlace, name ); return false; } @@ -139,7 +145,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // check IHDR chunk CRC if( ntohl( png_hdr.ihdr_crc32 ) != crc32_check ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IHDR chunk has wrong CRC32 sum (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IHDR chunk has wrong CRC32 sum (%s)\n", __func__, name ); return false; } @@ -157,13 +163,20 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi if( chunk_len > INT_MAX ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Found chunk with wrong size (%s)\n", name ); - Mem_Free( idat_buf ); + Con_DPrintf( S_ERROR "%s: Found chunk with wrong size (%s)\n", __func__, name ); + if( idat_buf ) Mem_Free( idat_buf ); + return false; + } + + if( chunk_len > filesize - ( buf_p - buffer )) + { + Con_DPrintf( S_ERROR "%s: Found chunk with size past file size (%s)\n", __func__, name ); + if( idat_buf ) Mem_Free( idat_buf ); return false; } // move pointer - buf_p += sizeof( chunk_sign ); + buf_p += sizeof( chunk_len ); // find transparency if( !memcmp( buf_p, trns_sign, sizeof( trns_sign ) ) ) @@ -175,6 +188,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi else if( !memcmp( buf_p, plte_sign, sizeof( plte_sign ) ) ) { pallete = buf_p + sizeof( plte_sign ); + plte_len = chunk_len / 3; } // get all IDAT chunks data else if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) @@ -202,8 +216,8 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // check chunk CRC if( ntohl( crc32 ) != crc32_check ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: Found chunk with wrong CRC32 sum (%s)\n", name ); - Mem_Free( idat_buf ); + Con_DPrintf( S_ERROR "%s: Found chunk with wrong CRC32 sum (%s)\n", __func__, name ); + if( idat_buf ) Mem_Free( idat_buf ); return false; } @@ -211,33 +225,33 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi buf_p += sizeof( crc32 ); } + if( oldsize == 0 ) + { + Con_DPrintf( S_ERROR "%s: Couldn't find IDAT chunks (%s)\n", __func__, name ); + return false; + } + if( png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE && !pallete ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: PLTE chunk not found (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: PLTE chunk not found (%s)\n", __func__, name ); Mem_Free( idat_buf ); return false; } if( !has_iend_chunk ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk not found (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IEND chunk not found (%s)\n", __func__, name ); Mem_Free( idat_buf ); return false; } if( chunk_len != 0 ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk has wrong size %u (%s)\n", chunk_len, name ); + Con_DPrintf( S_ERROR "%s: IEND chunk has wrong size %u (%s)\n", __func__, chunk_len, name ); Mem_Free( idat_buf ); return false; } - if( oldsize == 0 ) - { - Con_DPrintf( S_ERROR "Image_LoadPNG: Couldn't find IDAT chunks (%s)\n", name ); - return false; - } - switch( png_hdr.ihdr_chunk.colortype ) { case PNG_CT_GREY: @@ -260,9 +274,8 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi } image.type = PF_RGBA_32; // always exctracted to 32-bit buffer - image.width = png_hdr.ihdr_chunk.width; - image.height = png_hdr.ihdr_chunk.height; - image.size = image.height * image.width * 4; + pixel_count = image.height * image.width; + image.size = pixel_count * 4; if( png_hdr.ihdr_chunk.colortype & PNG_CT_RGB ) image.flags |= IMAGE_HAS_COLOR; @@ -285,7 +298,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // uncompress image if( inflateInit2( &stream, MAX_WBITS ) != Z_OK ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IDAT chunk decompression failed (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IDAT chunk decompression failed (%s)\n", __func__, name ); Mem_Free( uncompressed_buffer ); Mem_Free( idat_buf ); return false; @@ -298,7 +311,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi if( ret != Z_OK && ret != Z_STREAM_END ) { - Con_DPrintf( S_ERROR "Image_LoadPNG: IDAT chunk decompression failed (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IDAT chunk decompression failed (%s)\n", __func__, name ); Mem_Free( uncompressed_buffer ); return false; } @@ -338,7 +351,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi pixbuf[i] = raw[i] + ( pixbuf[i - pixel_size] >> 1 ); break; default: - Con_DPrintf( S_ERROR "Image_LoadPNG: Found unknown filter type (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: Found unknown filter type (%s)\n", __func__, name ); Mem_Free( uncompressed_buffer ); Mem_Free( image.rgba ); return false; @@ -402,7 +415,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi } break; default: - Con_DPrintf( S_ERROR "Image_LoadPNG: Found unknown filter type (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: Found unknown filter type (%s)\n", __func__, name ); Mem_Free( uncompressed_buffer ); Mem_Free( image.rgba ); return false; @@ -424,72 +437,66 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi b_alpha = trns[4] << 8 | trns[5]; } - for( y = 0; y < image.height; y++ ) + for( y = 0; y < pixel_count; y++, raw += pixel_size ) { - rowend = raw + rowsize; - for( ; raw < rowend; raw += pixel_size ) - { - *pixbuf++ = raw[0]; - *pixbuf++ = raw[1]; - *pixbuf++ = raw[2]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[1]; + *pixbuf++ = raw[2]; - if( trns && r_alpha == raw[0] - && g_alpha == raw[1] - && b_alpha == raw[2] ) - *pixbuf++ = 0; - else - *pixbuf++ = 0xFF; - } + if( trns && r_alpha == raw[0] + && g_alpha == raw[1] + && b_alpha == raw[2] ) + *pixbuf++ = 0; + else + *pixbuf++ = 0xFF; } break; case PNG_CT_GREY: if( trns ) r_alpha = trns[0] << 8 | trns[1]; - for( y = 0; y < image.height; y++ ) + for( y = 0; y < pixel_count; y++, raw += pixel_size ) { - rowend = raw + rowsize; - for( ; raw < rowend; raw += pixel_size ) - { - *pixbuf++ = raw[0]; - *pixbuf++ = raw[0]; - *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; - if( trns && r_alpha == raw[0] ) - *pixbuf++ = 0; - else - *pixbuf++ = 0xFF; - } + if( trns && r_alpha == raw[0] ) + *pixbuf++ = 0; + else + *pixbuf++ = 0xFF; } break; case PNG_CT_ALPHA: - for( y = 0; y < image.height; y++ ) + for( y = 0; y < pixel_count; y++, raw += pixel_size ) { - rowend = raw + rowsize; - for( ; raw < rowend; raw += pixel_size ) - { - *pixbuf++ = raw[0]; - *pixbuf++ = raw[0]; - *pixbuf++ = raw[0]; - *pixbuf++ = raw[1]; - } + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[0]; + *pixbuf++ = raw[1]; } break; case PNG_CT_PALLETE: - for( y = 0; y < image.height; y++ ) + for( y = 0; y < pixel_count; y++, raw += pixel_size ) { - rowend = raw + rowsize; - for( ; raw < rowend; raw += pixel_size ) + if( raw[0] < plte_len ) { - *pixbuf++ = pallete[raw[0] + 2]; - *pixbuf++ = pallete[raw[0] + 1]; - *pixbuf++ = pallete[raw[0] + 0]; + *pixbuf++ = pallete[3 * raw[0] + 0]; + *pixbuf++ = pallete[3 * raw[0] + 1]; + *pixbuf++ = pallete[3 * raw[0] + 2]; if( trns && raw[0] < trns_len ) *pixbuf++ = trns[raw[0]]; else *pixbuf++ = 0xFF; } + else + { + *pixbuf++ = 0; + *pixbuf++ = 0; + *pixbuf++ = 0; + *pixbuf++ = 0xFF; + } } break; default: @@ -643,7 +650,7 @@ qboolean Image_SavePNG( const char *name, rgbdata_t *pix ) // compress image if( deflateInit( &stream, Z_BEST_COMPRESSION ) != Z_OK ) { - Con_DPrintf( S_ERROR "Image_SavePNG: deflateInit failed (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: deflateInit failed (%s)\n", __func__, name ); Mem_Free( filtered_buffer ); Mem_Free( buffer ); return false; @@ -656,7 +663,7 @@ qboolean Image_SavePNG( const char *name, rgbdata_t *pix ) if( ret != Z_OK && ret != Z_STREAM_END ) { - Con_DPrintf( S_ERROR "Image_SavePNG: IDAT chunk compression failed (%s)\n", name ); + Con_DPrintf( S_ERROR "%s: IDAT chunk compression failed (%s)\n", __func__, name ); Mem_Free( buffer ); return false; } diff --git a/engine/common/imagelib/img_quant.c b/engine/common/imagelib/img_quant.c index eb5e2e50..536ae977 100644 --- a/engine/common/imagelib/img_quant.c +++ b/engine/common/imagelib/img_quant.c @@ -15,12 +15,13 @@ GNU General Public License for more details. #include "imagelib.h" +#define palettesize 256 +#define netsize 255 // number of colours used -#define netsize 256 // number of colours used -#define prime1 499 -#define prime2 491 -#define prime3 487 -#define prime4 503 +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 #define minpicturebytes (3*prime4) // minimum size for input image @@ -65,7 +66,7 @@ static int bias[netsize]; // bias and freq arrays for learning static int freq[netsize]; static int radpower[initrad]; // radpower for precomputation -void initnet( byte *thepic, int len, int sample ) +static void initnet( byte *thepic, int len, int sample ) { register int i, *p; @@ -83,7 +84,7 @@ void initnet( byte *thepic, int len, int sample ) } // Unbias network to give byte values 0..255 and record position i to prepare for sort -void unbiasnet( void ) +static void unbiasnet( void ) { int i, j, temp; @@ -103,7 +104,7 @@ void unbiasnet( void ) } // Insertion sort of network and building of netindex[0..255] (to do after unbias) -void inxbuild( void ) +static void inxbuild( void ) { register int *p, *q; register int i, j, smallpos, smallval; @@ -162,7 +163,7 @@ void inxbuild( void ) // Search for BGR values 0..255 (after net is unbiased) and return colour index -int inxsearch( int r, int g, int b ) +static int inxsearch( int r, int g, int b ) { register int i, j, dist, a, bestd; register int *p; @@ -243,7 +244,7 @@ int inxsearch( int r, int g, int b ) } // Search for biased BGR values -int contest( int r, int g, int b ) +static int contest( int r, int g, int b ) { register int *p, *f, *n; register int i, dist, a, biasdist, betafreq; @@ -298,7 +299,7 @@ int contest( int r, int g, int b ) } // Move neuron i towards biased (b,g,r) by factor alpha -void altersingle( int alpha, int i, int r, int g, int b ) +static void altersingle( int alpha, int i, int r, int g, int b ) { register int *n; @@ -311,7 +312,7 @@ void altersingle( int alpha, int i, int r, int g, int b ) } // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] -void alterneigh( int rad, int i, int r, int g, int b ) +static void alterneigh( int rad, int i, int r, int g, int b ) { register int j, k, lo, hi, a; register int *p, *q; @@ -354,7 +355,7 @@ void alterneigh( int rad, int i, int r, int g, int b ) } // Main Learning Loop -void learn( void ) +static void learn( void ) { register byte *p; register int i, j, r, g, b; @@ -408,7 +409,7 @@ void learn( void ) if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours p += step; - if( p >= lim ) p -= lengthcount; + while( p >= lim ) p -= lengthcount; i++; @@ -446,7 +447,7 @@ rgbdata_t *Image_Quantize( rgbdata_t *pic ) learn(); unbiasnet(); - pic->palette = Mem_Malloc( host.imagepool, netsize * 3 ); + pic->palette = Mem_Malloc( host.imagepool, palettesize * 3 ); for( i = 0; i < netsize; i++ ) { @@ -455,6 +456,13 @@ rgbdata_t *Image_Quantize( rgbdata_t *pic ) pic->palette[i*3+2] = network[i][2]; // blue } + for( ; i < palettesize; i++ ) + { + pic->palette[i*3+0] = 0; + pic->palette[i*3+1] = 0; + pic->palette[i*3+2] = 0; + } + inxbuild(); for( i = 0; i < image.width * image.height; i++ ) diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c index 59228bdf..fb622e95 100644 --- a/engine/common/imagelib/img_tga.c +++ b/engine/common/imagelib/img_tga.c @@ -29,7 +29,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi rgba_t palette[256]; byte red = 0, green = 0, blue = 0, alpha = 0; int readpixelcount, pixelcount; - int reflectivity[3] = { 0, 0, 0 }; + uint reflectivity[3] = { 0, 0, 0 }; qboolean compressed; tga_t targa_header; @@ -62,17 +62,17 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi // uncompressed colormapped image if( targa_header.pixel_size != 8 ) { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) Only 8 bit images supported for type 1 and 9\n", __func__, name ); return false; } if( targa_header.colormap_length != 256 ) { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", __func__, name ); return false; } if( targa_header.colormap_index ) { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) colormap_index is not supported for type 1 and 9\n", __func__, name ); return false; } if( targa_header.colormap_size == 24 ) @@ -97,7 +97,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi } else { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", __func__, name ); return false; } } @@ -106,7 +106,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi // uncompressed or RLE compressed RGB if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", __func__, name ); return false; } } @@ -115,7 +115,7 @@ qboolean Image_LoadTGA( const char *name, const byte *buffer, fs_offset_t filesi // uncompressed greyscale if( targa_header.pixel_size != 8 && targa_header.pixel_size != 16 ) { - Con_DPrintf( S_ERROR "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + Con_DPrintf( S_ERROR "%s: (%s) Only 8 bit images supported for type 3 and 11\n", __func__, name ); return false; } } diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index ce2e57bd..9f63879a 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -90,22 +90,24 @@ static byte palette_hl[768] = // stub static const loadpixformat_t load_null[] = { -{ NULL, NULL, NULL, IL_HINT_NO } +{ NULL, NULL, IL_HINT_NO } }; static const loadpixformat_t load_game[] = { -{ "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models -{ "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images -{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus -{ "%s%s.%s", "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus -{ "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer -{ "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins -{ "%s%s.%s", "spr", Image_LoadSPR, IL_HINT_HL }, // hl sprite frames -{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc) -{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc) -{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette -{ NULL, NULL, NULL, IL_HINT_NO } +{ "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models +{ "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus +{ "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus +{ "wad", Image_LoadWAD, IL_HINT_NO }, // hl wad files +{ "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer +{ "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins +{ "spr", Image_LoadSPR, IL_HINT_HL }, // hl sprite frames +{ "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc) +{ "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc) +{ "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette +{ "ktx2", Image_LoadKTX2, IL_HINT_NO }, // ktx2 for world and studio models +{ NULL, NULL, IL_HINT_NO } }; /* @@ -118,16 +120,17 @@ static const loadpixformat_t load_game[] = // stub static const savepixformat_t save_null[] = { -{ NULL, NULL, NULL } +{ NULL, NULL } }; // Xash3D normal instance static const savepixformat_t save_game[] = { -{ "%s%s.%s", "tga", Image_SaveTGA }, // tga screenshots -{ "%s%s.%s", "bmp", Image_SaveBMP }, // bmp levelshots or screenshots -{ "%s%s.%s", "png", Image_SavePNG }, // png screenshots -{ NULL, NULL, NULL } +{ "tga", Image_SaveTGA }, // tga screenshots +{ "bmp", Image_SaveBMP }, // bmp levelshots or screenshots +{ "png", Image_SavePNG }, // png screenshots +{ "wad", Image_SaveWAD }, // player logo in tempdecal.wad +{ NULL, NULL } }; void Image_Setup( void ) @@ -173,8 +176,8 @@ byte *Image_Copy( size_t size ) { byte *out; - out = Mem_Malloc( host.imagepool, size ); - memcpy( out, image.tempbuffer, size ); + out = Mem_Realloc( host.imagepool, image.tempbuffer, size ); + image.tempbuffer = NULL; return out; } @@ -237,7 +240,16 @@ void Image_AddCmdFlags( uint flags ) qboolean Image_ValidSize( const char *name ) { - if( image.width > IMAGE_MAXWIDTH || image.height > IMAGE_MAXHEIGHT || image.width <= 0 || image.height <= 0 ) + int max_width = IMAGE_MAXWIDTH; + int max_height = IMAGE_MAXHEIGHT; + + if( Image_CheckFlag( IL_LOAD_PLAYER_DECAL )) + { + max_width = PLDECAL_MAXWIDTH; + max_height = PLDECAL_MAXHEIGHT; + } + + if( image.width > max_width || image.height > max_height || image.width <= 0 || image.height <= 0 ) { Con_DPrintf( S_ERROR "Image: (%s) dims out of range [%dx%d]\n", name, image.width, image.height ); return false; @@ -271,9 +283,10 @@ int Image_ComparePalette( const byte *pal ) return PAL_CUSTOM; } -void Image_SetPalette( const byte *pal, uint *d_table ) +static void Image_SetPalette( const byte *pal, uint *d_table ) { byte rgba[4]; + uint uirgba; // TODO: palette looks byte-swapped on big-endian int i; // setup palette @@ -282,11 +295,21 @@ void Image_SetPalette( const byte *pal, uint *d_table ) case LUMP_NORMAL: for( i = 0; i < 256; i++ ) { - rgba[0] = pal[i*3+0]; - rgba[1] = pal[i*3+1]; - rgba[2] = pal[i*3+2]; + memcpy( rgba, &pal[i * 3], 3 ); rgba[3] = 0xFF; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; + } + break; + case LUMP_TEXGAMMA: + for( i = 0; i < 256; i++ ) + { + rgba[0] = TextureToGamma( pal[i * 3 + 0] ); + rgba[1] = TextureToGamma( pal[i * 3 + 1] ); + rgba[2] = TextureToGamma( pal[i * 3 + 2] ); + rgba[3] = 0xFF; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; case LUMP_GRADIENT: @@ -296,7 +319,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[766]; rgba[2] = pal[767]; rgba[3] = i; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; case LUMP_MASKED: @@ -306,7 +330,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[i*3+1]; rgba[2] = pal[i*3+2]; rgba[3] = 0xFF; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } d_table[255] = 0; break; @@ -317,7 +342,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[i*4+1]; rgba[2] = pal[i*4+2]; rgba[3] = pal[i*4+3]; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; } @@ -515,7 +541,7 @@ void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int } } -void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size ) +static void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size ) { byte dst[256], src[256]; int i; @@ -711,7 +737,7 @@ static void Image_Resample24LerpLine( const byte *in, byte *out, int inwidth, in } } -void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +static void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) { const byte *inrow; int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); @@ -819,7 +845,7 @@ void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void * Mem_Free( resamplerow1 ); } -void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +static void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) { int i, j; uint frac, fracstep; @@ -858,7 +884,7 @@ void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void } } -void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +static void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) { const byte *inrow; int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight - 1); @@ -959,7 +985,7 @@ void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void * Mem_Free( resamplerow1 ); } -void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +static void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) { uint frac, fracstep; int i, j, f, inwidth3 = inwidth * 3; @@ -1025,7 +1051,7 @@ void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void } } -void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +static void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) { int i, j; byte *in, *inrow; @@ -1164,7 +1190,7 @@ byte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int t return image.tempbuffer; } -byte *Image_CreateLumaInternal( byte *fin, int width, int height, int type, int flags ) +static byte *Image_MakeLuma( byte *fin, int width, int height, int type, int flags ) { byte *out; int i; @@ -1182,7 +1208,7 @@ byte *Image_CreateLumaInternal( byte *fin, int width, int height, int type, int break; default: // another formats does ugly result :( - Con_Printf( S_ERROR "Image_MakeLuma: unsupported format %s\n", PFDesc[type].name ); + Con_Printf( S_ERROR "%s: unsupported format %s\n", __func__, PFDesc[type].name ); return (byte *)fin; } @@ -1220,7 +1246,7 @@ Image_Decompress force to unpack any image to 32-bit buffer ============= */ -qboolean Image_Decompress( const byte *data ) +static qboolean Image_Decompress( const byte *data ) { byte *fin, *fout; int i, size; @@ -1288,7 +1314,7 @@ qboolean Image_Decompress( const byte *data ) return true; } -rgbdata_t *Image_DecompressInternal( rgbdata_t *pic ) +static rgbdata_t *Image_DecompressInternal( rgbdata_t *pic ) { // quick case to reject unneeded conversions if( pic->type == PF_RGBA_32 ) @@ -1311,7 +1337,7 @@ rgbdata_t *Image_DecompressInternal( rgbdata_t *pic ) return pic; } -rgbdata_t *Image_LightGamma( rgbdata_t *pic ) +static rgbdata_t *Image_LightGamma( rgbdata_t *pic ) { byte *in = (byte *)pic->buffer; int i; @@ -1329,7 +1355,7 @@ rgbdata_t *Image_LightGamma( rgbdata_t *pic ) return pic; } -qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor ) +static qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor ) { if( !pic->palette ) return false; @@ -1359,20 +1385,20 @@ qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor ) return true; } -qboolean Image_Process(rgbdata_t **pix, int width, int height, uint flags, float reserved ) +qboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, float reserved ) { rgbdata_t *pic = *pix; qboolean result = true; byte *out; // check for buffers - if( !pic || !pic->buffer ) + if( unlikely( !pic || !pic->buffer )) { image.force_flags = 0; return false; } - if( !flags ) + if( unlikely( !flags )) { // clear any force flags image.force_flags = 0; @@ -1381,7 +1407,7 @@ qboolean Image_Process(rgbdata_t **pix, int width, int height, uint flags, float if( FBitSet( flags, IMAGE_MAKE_LUMA )) { - out = Image_CreateLumaInternal( pic->buffer, pic->width, pic->height, pic->type, pic->flags ); + out = Image_MakeLuma( pic->buffer, pic->width, pic->height, pic->type, pic->flags ); if( pic->buffer != out ) memcpy( pic->buffer, image.tempbuffer, pic->size ); ClearBits( pic->flags, IMAGE_HAS_LUMA ); } @@ -1417,7 +1443,7 @@ qboolean Image_Process(rgbdata_t **pix, int width, int height, uint flags, float pic->width = w, pic->height = h; pic->size = w * h * PFDesc[pic->type].bpp; Mem_Free( pic->buffer ); // free original image buffer - pic->buffer = Image_Copy( pic->size ); // unzone buffer (don't touch image.tempbuffer) + pic->buffer = Image_Copy( pic->size ); // unzone buffer } else { @@ -1437,3 +1463,72 @@ qboolean Image_Process(rgbdata_t **pix, int width, int height, uint flags, float return result; } + +// This codebase has too many copies of this function: +// - ref_gl has one +// - ref_vk has one +// - ref_soft has one +// - many more places probably have one too +// TODO figure out how to make it available for ref_* +size_t Image_ComputeSize( int type, int width, int height, int depth ) +{ + switch( type ) + { + case PF_DXT1: + case PF_BC4_SIGNED: + case PF_BC4_UNSIGNED: + return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 ); + case PF_DXT3: + case PF_DXT5: + case PF_ATI2: + case PF_BC5_UNSIGNED: + case PF_BC5_SIGNED: + case PF_BC6H_SIGNED: + case PF_BC6H_UNSIGNED: + case PF_BC7_UNORM: + case PF_BC7_SRGB: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 ); + case PF_LUMINANCE: return ( width * height * depth ); + case PF_BGR_24: + case PF_RGB_24: return ( width * height * depth * 3 ); + case PF_BGRA_32: + case PF_RGBA_32: return ( width * height * depth * 4 ); + } + + return 0; +} + +/* +============ +Image_GenerateMipmaps +============ +*/ +void Image_GenerateMipmaps( const byte *source, int width, int height, byte *mip1, byte *mip2, byte *mip3 ) +{ + const int sizes[3][2] = { + { width / 2, height / 2 }, + { width / 4, height / 4 }, + { width / 8, height / 8 } + }; + byte *mipmaps[3] = { mip1, mip2, mip3 }; + int m; + + for( m = 0; m < 3; ++m ) + { + int mw, mh, step, y; + + if( !mipmaps[m] ) + continue; + mw = sizes[m][0]; + mh = sizes[m][1]; + step = 1 << ( m + 1 ); + for( y = 0; y < mh; ++y ) + { + int x; + + for( x = 0; x < mw; ++x ) + { + mipmaps[m][y * mw + x] = source[( y * step ) * width + ( x * step )]; + } + } + } +} diff --git a/engine/common/imagelib/img_wad.c b/engine/common/imagelib/img_wad.c index fcb22a35..3f0d5740 100644 --- a/engine/common/imagelib/img_wad.c +++ b/engine/common/imagelib/img_wad.c @@ -28,12 +28,20 @@ Image_LoadPAL qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesize ) { int rendermode = LUMP_NORMAL; + byte pal[768]; - if( filesize != 768 ) + if( filesize > sizeof( pal )) { - Con_DPrintf( S_ERROR "Image_LoadPAL: (%s) have invalid size (%ld should be %d)\n", name, filesize, 768 ); + Con_DPrintf( S_ERROR "%s: (%s) have invalid size (%li should be less or equal than %zu)\n", __func__, name, (long)filesize, sizeof( pal )); return false; } + else if( filesize < sizeof( pal ) && buffer != NULL ) + { + // palette might be truncated, fill it with zeros + memset( pal, 0, sizeof( pal )); + memcpy( pal, buffer, filesize ); + buffer = pal; + } if( name[0] == '#' ) { @@ -44,6 +52,8 @@ qboolean Image_LoadPAL( const char *name, const byte *buffer, fs_offset_t filesi rendermode = LUMP_MASKED; else if( Q_stristr( name, "gradient" )) rendermode = LUMP_GRADIENT; + else if( Q_stristr( name, "texgamma" )) + rendermode = LUMP_TEXGAMMA; else if( Q_stristr( name, "valve" )) { rendermode = LUMP_HALFLIFE; @@ -179,7 +189,7 @@ qboolean Image_LoadMDL( const char *name, const byte *buffer, fs_offset_t filesi Image_GetPaletteLMP( pal, LUMP_MASKED ); image.flags |= IMAGE_HAS_ALPHA|IMAGE_ONEBIT_ALPHA; } - else Image_GetPaletteLMP( fin + pixels, LUMP_NORMAL ); + else Image_GetPaletteLMP( fin + pixels, LUMP_TEXGAMMA ); } else { @@ -218,7 +228,7 @@ qboolean Image_LoadSPR( const char *name, const byte *buffer, fs_offset_t filesi return false; } - memcpy( &pin, buffer, sizeof(dspriteframe_t) ); + memcpy( &pin, buffer, sizeof( dspriteframe_t )); image.width = pin.width; image.height = pin.height; @@ -311,13 +321,17 @@ qboolean Image_LoadLMP( const char *name, const byte *buffer, fs_offset_t filesi { int numcolors; - for( i = 0; i < pixels; i++ ) + // HACKHACK: console background image shouldn't be transparent + if( !Q_stristr( name, "conback" )) { - if( fin[i] == 255 ) + for( i = 0; i < pixels; i++ ) { - image.flags |= IMAGE_HAS_ALPHA; - rendermode = LUMP_MASKED; - break; + if( fin[i] == 255 ) + { + image.flags |= IMAGE_HAS_ALPHA; + rendermode = LUMP_MASKED; + break; + } } } pal = fin + pixels; @@ -356,7 +370,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi byte *fin, *pal; int ofs[4], rendermode; int i, pixels, numcolors; - int reflectivity[3] = { 0, 0, 0 }; + uint reflectivity[3] = { 0, 0, 0 }; if( filesize < sizeof( mip )) return false; @@ -386,7 +400,7 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi if( Q_strrchr( name, '{' )) { // NOTE: decals with 'blue base' can be interpret as colored decals - if( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal[765] == 0 && pal[766] == 0 && pal[767] == 255 )) + if( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal && pal[765] == 0 && pal[766] == 0 && pal[767] == 255 )) { SetBits( image.flags, IMAGE_ONEBIT_ALPHA ); rendermode = LUMP_MASKED; @@ -423,8 +437,18 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi } if( pal_type == PAL_QUAKE1 ) + { SetBits( image.flags, IMAGE_QUAKEPAL ); - rendermode = LUMP_NORMAL; + + // if texture was converted from quake to half-life with no palette changes + // then applying texgamma might make it too dark or even outright broken + rendermode = LUMP_NORMAL; + } + else + { + // half-life mips need texgamma applied + rendermode = LUMP_TEXGAMMA; + } } Image_GetPaletteLMP( pal, rendermode ); @@ -484,37 +508,40 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi } // check for half-life water texture - if( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, "water", 5 ))) + if( pal != NULL ) { - // grab the fog color - image.fogParams[0] = pal[3*3+0]; - image.fogParams[1] = pal[3*3+1]; - image.fogParams[2] = pal[3*3+2]; - - // grab the fog density - image.fogParams[3] = pal[4*3+0]; - } - else if( hl_texture && ( rendermode == LUMP_GRADIENT )) - { - // grab the decal color - image.fogParams[0] = pal[255*3+0]; - image.fogParams[1] = pal[255*3+1]; - image.fogParams[2] = pal[255*3+2]; - - // calc the decal reflectivity - image.fogParams[3] = VectorAvg( image.fogParams ); - } - else if( pal != NULL ) - { - // calc texture reflectivity - for( i = 0; i < 256; i++ ) + if( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, "water", 5 ))) { - reflectivity[0] += pal[i*3+0]; - reflectivity[1] += pal[i*3+1]; - reflectivity[2] += pal[i*3+2]; - } + // grab the fog color + image.fogParams[0] = pal[3*3+0]; + image.fogParams[1] = pal[3*3+1]; + image.fogParams[2] = pal[3*3+2]; - VectorDivide( reflectivity, 256, image.fogParams ); + // grab the fog density + image.fogParams[3] = pal[4*3+0]; + } + else if( hl_texture && ( rendermode == LUMP_GRADIENT )) + { + // grab the decal color + image.fogParams[0] = pal[255*3+0]; + image.fogParams[1] = pal[255*3+1]; + image.fogParams[2] = pal[255*3+2]; + + // calc the decal reflectivity + image.fogParams[3] = VectorAvg( image.fogParams ); + } + else + { + // calc texture reflectivity + for( i = 0; i < 256; i++ ) + { + reflectivity[0] += pal[i*3+0]; + reflectivity[1] += pal[i*3+1]; + reflectivity[2] += pal[i*3+2]; + } + + VectorDivide( reflectivity, 256, image.fogParams ); + } } image.type = PF_INDEXED_32; // 32-bit palete @@ -522,3 +549,226 @@ qboolean Image_LoadMIP( const char *name, const byte *buffer, fs_offset_t filesi return Image_AddIndexedImageToPack( fin, image.width, image.height ); } + +/* +============ +Image_LoadWAD +============ +*/ +qboolean Image_LoadWAD( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + const dwadinfo_t *header; + const dlumpinfo_t *lumps; + const unsigned char *mipdata; + int i, j; + + if( !buffer || filesize < sizeof( dwadinfo_t )) + return false; + + header = (const dwadinfo_t *)buffer; + if( header->numlumps <= 0 || header->infotableofs <= 0 || header->infotableofs >= (int)filesize ) + return false; + + lumps = (const dlumpinfo_t *)((const unsigned char *)buffer + header->infotableofs ); + + for( i = 0; i < header->numlumps; ++i ) + { + const unsigned char *pixels, *palette, *use_palette; + unsigned char grad_palette[256 * 3]; + int mip_size; + mip_t mip; + uint32_t width, height, offset0; + uint32_t m0size, m1size, m2size, m3size; + qboolean alpha_mode = false; + unsigned char frontR = 0, frontG = 0, frontB = 0; + float t; + byte idx; + + if( lumps[i].type != TYP_MIPTEX && lumps[i].type != TYP_PALETTE ) + continue; + + // get lump data and validate + mipdata = (const unsigned char *)buffer + lumps[i].filepos; + mip_size = lumps[i].disksize; + if( lumps[i].filepos < 0 || lumps[i].filepos + mip_size > (int)filesize ) + continue; + + memcpy( &mip, mipdata, sizeof( mip )); + width = mip.width; + height = mip.height; + + if( width <= 0 || height <= 0 || width > 256 || height > 256 ) + continue; + + offset0 = mip.offsets[0]; + if( offset0 == 0 || offset0 + width * height > (uint32_t)mip_size ) + continue; + + pixels = mipdata + offset0; + m0size = width * height; + m1size = m0size / 4; + m2size = m0size / 16; + m3size = m0size / 64; + palette = mipdata + 0x28 + m0size + m1size + m2size + m3size + 2; + use_palette = palette; + + // handle gradient palette + if( lumps[i].type == TYP_PALETTE ) + { + // gradient palette + const unsigned char *frontColorPtr = palette + 255 * 3; + frontR = frontColorPtr[0]; + frontG = frontColorPtr[1]; + frontB = frontColorPtr[2]; + for( j = 0; j < 256; ++j ) + { + t = j / 255.0f; + grad_palette[j * 3 + 0] = (unsigned char)( frontR * t ); + grad_palette[j * 3 + 1] = (unsigned char)( frontG * t ); + grad_palette[j * 3 + 2] = (unsigned char)( frontB * t ); + } + use_palette = grad_palette; + alpha_mode = true; // gradient + } + + // prepare image structure + Image_Reset(); + image.width = width; + image.height = height; + image.type = PF_RGBA_32; + image.flags = IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA; + image.size = width * height * 4; + image.rgba = Mem_Malloc( host.imagepool, image.size ); + image.palette = NULL; + + // convert indexed pixels to RGBA + for( j = 0; j < (int)( width * height ); ++j ) + { + idx = pixels[j]; + image.rgba[j * 4 + 0] = use_palette[idx * 3 + 0]; + image.rgba[j * 4 + 1] = use_palette[idx * 3 + 1]; + image.rgba[j * 4 + 2] = use_palette[idx * 3 + 2]; + if( alpha_mode ) + image.rgba[j * 4 + 3] = idx; // soft transparency + else + image.rgba[j * 4 + 3] = ( idx == 255 ) ? 0 : 255; // classic + } + return true; + } + return false; +} + +/* +============ +Image_SaveWAD +============ +*/ +qboolean Image_SaveWAD( const char *name, rgbdata_t *pix ) +{ + int m0size, m1size, m2size, m3size; + byte *mip1_data = NULL, *mip2_data = NULL, *mip3_data = NULL; + const byte *palette; + byte grad_palette[256 * 3]; + file_t *f; + dwadinfo_t header; + mip_t miptex; + long infotableofs; + dlumpinfo_t lump; + fs_offset_t pad; + int i; + qboolean result = false; + int lump_type = ( pix->flags & IMAGE_GRADIENT_DECAL ) ? TYP_PALETTE : TYP_MIPTEX; + short palette_size = 256; + int infotableofs32 = 0; + + if( !pix || !pix->buffer ) + return false; + + palette = pix->palette ? pix->palette : (const byte *)image.palette; + + m0size = pix->width * pix->height; + m1size = m0size / 4; + m2size = m0size / 16; + m3size = m0size / 64; + + mip1_data = (byte *)Mem_Malloc( host.imagepool, m1size ); + mip2_data = (byte *)Mem_Malloc( host.imagepool, m2size ); + mip3_data = (byte *)Mem_Malloc( host.imagepool, m3size ); + if( !mip1_data || !mip2_data || !mip3_data ) + goto cleanup; + + Image_GenerateMipmaps( pix->buffer, pix->width, pix->height, mip1_data, mip2_data, mip3_data ); + + memset( &miptex, 0, sizeof( mip_t )); + Q_strncpy( miptex.name, "{LOGO", sizeof( miptex.name )); + miptex.width = pix->width; + miptex.height = pix->height; + miptex.offsets[0] = sizeof( mip_t ); + miptex.offsets[1] = miptex.offsets[0] + m0size; + miptex.offsets[2] = miptex.offsets[1] + m1size; + miptex.offsets[3] = miptex.offsets[2] + m2size; + + f = FS_Open( name, "wb", false ); + if( !f ) + goto cleanup; + + memset( &header, 0, sizeof( header )); + header.ident = IDWAD3HEADER; + header.numlumps = 1; + + FS_Write( f, &header, sizeof( header )); + FS_Write( f, &miptex, sizeof( mip_t )); + FS_Write( f, pix->buffer, m0size ); + FS_Write( f, mip1_data, m1size ); + FS_Write( f, mip2_data, m2size ); + FS_Write( f, mip3_data, m3size ); + FS_Write( f, &palette_size, sizeof( short )); + + if( lump_type == TYP_PALETTE ) + { + const byte *frontColorPtr = palette + 255 * 3; + for( i = 0; i < 256; ++i ) + { + float t = i / 255.0f; + grad_palette[i * 3 + 0] = (byte)( frontColorPtr[0] * t ); + grad_palette[i * 3 + 1] = (byte)( frontColorPtr[1] * t ); + grad_palette[i * 3 + 2] = (byte)( frontColorPtr[2] * t ); + } + FS_Write( f, grad_palette, 256 * 3 ); + } + else + { + FS_Write( f, palette, 256 * 3 ); + } + + // padding up to a multiple of 4 + pad = (( FS_Tell( f ) + 3 ) & ~3 ) - FS_Tell( f ); + for( i = 0; i < pad; ++i ) + FS_Write( f, (const void *)&(char){0}, 1 ); + + infotableofs = FS_Tell( f ); + memset( &lump, 0, sizeof( lump )); + lump.filepos = sizeof( dwadinfo_t ); + lump.disksize = (int)( miptex.offsets[3] + m3size + sizeof( short ) + 256 * 3 ); + lump.size = lump.disksize; + lump.type = (char)lump_type; + lump.attribs = 0; + Q_strncpy( lump.name, "tempdecal", sizeof( lump.name )); + FS_Write( f, &lump, sizeof( lump )); + + FS_Seek( f, offsetof( dwadinfo_t, infotableofs ), SEEK_SET ); + infotableofs32 = (int)infotableofs; + FS_Write( f, &infotableofs32, sizeof( int )); + + FS_Close( f ); + result = true; + +cleanup: + if( mip1_data ) + Mem_Free( mip1_data ); + if( mip2_data ) + Mem_Free( mip2_data ); + if( mip3_data ) + Mem_Free( mip3_data ); + return result; +} diff --git a/engine/common/infostring.c b/engine/common/infostring.c index 220cc9e8..25acb6a0 100644 --- a/engine/common/infostring.c +++ b/engine/common/infostring.c @@ -189,7 +189,7 @@ Searches the string for the given key and returns the associated value, or an empty string. =============== */ -const char *Info_ValueForKey( const char *s, const char *key ) +const char *GAME_EXPORT Info_ValueForKey( const char *s, const char *key ) { char pkey[MAX_KV_SIZE]; static char value[4][MAX_KV_SIZE]; // use two buffers so compares work without stomping on each other @@ -277,7 +277,9 @@ qboolean GAME_EXPORT Info_RemoveKey( char *s, const char *key ) if( !Q_strncmp( key, pkey, cmpsize )) { - Q_strcpy( start, s ); // remove this part + size_t size = Q_strlen( s ) + 1; + + memmove( start, s, size ); // remove this part return true; } @@ -331,7 +333,7 @@ void Info_RemovePrefixedKeys( char *start, char prefix ) } } -qboolean Info_IsKeyImportant( const char *key ) +static qboolean Info_IsKeyImportant( const char *key ) { if( key[0] == '*' ) return true; @@ -356,7 +358,7 @@ qboolean Info_IsKeyImportant( const char *key ) return false; } -char *Info_FindLargestKey( char *s ) +static char *Info_FindLargestKey( char *s ) { char key[MAX_KV_SIZE]; char value[MAX_KV_SIZE]; @@ -496,3 +498,16 @@ qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int m return Info_SetValueForStarKey( s, key, value, maxsize ); } + +qboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... ) +{ + char value[MAX_VA_STRING]; + va_list args; + + va_start( args, format ); + Q_vsnprintf( value, sizeof( value ), format, args ); + va_end( args ); + + return Info_SetValueForKey( s, key, value, maxsize ); +} + diff --git a/engine/common/ipv6text.c b/engine/common/ipv6text.c new file mode 100644 index 00000000..edcd0937 --- /dev/null +++ b/engine/common/ipv6text.c @@ -0,0 +1,448 @@ +#include +#include +#include "ipv6text.h" +#include "xash3d_types.h" + +#ifdef _WIN32 +#ifndef snprintf +#define snprintf _snprintf +#endif +#endif + +void IPv6IPToString( char *pszOutText, const unsigned char *ip ) +{ + // Find the longest run of consecutive zero quads. + // If there's a tie, we want the leftmost one. + int idxLongestRunStart = -1; + int nLongestRun = 1; // It must be at least 2 quads in a row, a single 0 must not be compressed + int nCurrentRun = 0, idxQuad; + char *p; + qboolean bNeedColon; + + for ( idxQuad = 0 ; idxQuad < 8 ; ++idxQuad ) + { + // Zero + if ( ip[idxQuad*2] || ip[idxQuad*2 + 1] ) + { + // Terminate run + nCurrentRun = 0; + } + else + { + // Extend (or begin) run + ++nCurrentRun; + + // Longer than previously found run? + if ( nCurrentRun > nLongestRun ) + { + nLongestRun = nCurrentRun; + idxLongestRunStart = idxQuad - nCurrentRun + 1; + } + } + } + + // Print the quads + p = pszOutText; + idxQuad = 0; + bNeedColon = false; + while ( idxQuad < 8 ) + { + // Run of compressed zeros? + if ( idxQuad == idxLongestRunStart ) + { + *(p++) = ':'; + *(p++) = ':'; + bNeedColon = false; + idxQuad += nLongestRun; + } + else + { + // Lowercase hex digits, with leading zeros omitted + static const char hexdigits[] = "0123456789abcdef"; + unsigned quad; + + // Colon to separate from previous, unless + // we are first or immediately follow compressed zero "::" + if ( bNeedColon ) + *(p++) = ':'; + + // Next quad should should print a separator + bNeedColon = true; + + // Assemble 16-bit quad value from the two bytes + quad = ( (unsigned)ip[idxQuad*2] << 8U ) | ip[idxQuad*2 + 1]; + + // Manually do the hex number formatting. + if ( quad >= 0x0010 ) + { + if ( quad >= 0x0100 ) + { + if ( quad >= 0x1000 ) + *(p++) = hexdigits[ quad >> 12U ]; + *(p++) = hexdigits[ ( quad >> 8U ) & 0xf ]; + } + *(p++) = hexdigits[ ( quad >> 4U ) & 0xf ]; + } + + // Least significant digit, which is always printed + *(p++) = hexdigits[ quad & 0xf ]; + + // On to the next one + ++idxQuad; + } + } + + // String terminator + *p = '\0'; +} + +void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope ) +{ + char *p = pszOutText; + + // Open bracket + *(p++) = '['; + + // Print in the IP + IPv6IPToString( p, ip ); + + // Find the end of the string + while (*p) + ++p; + + if ( scope ) + { + // And now the scope. Max 32-digit scope number is 10 digits + snprintf( p, 12, "%%%d", scope ); + + // Find the end of the string + while (*p) + ++p; + } + + // And now the rest. Max 16-digit port number is 6 digits + snprintf( p, 8, "]:%u", (unsigned int)port ); +} + +static inline int ParseIPv6Addr_HexDigitVal( char c ) +{ + if ( c >= '0' && c <= '9' ) return c - '0'; + if ( c >= 'a' && c <= 'f' ) return c - ('a' - 0xa); + if ( c >= 'A' && c <= 'F' ) return c - ('A' - 0xa); + return -1; +} +static inline int ParseIPv6Addr_DecimalDigitVal( char c ) +{ + if ( c >= '0' && c <= '9' ) return c - '0'; + return -1; +} +static bool ParseIPv6Addr_IsSpace( char c ) +{ + // Newlines don't count, intentionally + return c == ' ' || c == '\t'; +} +bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope ) +{ + unsigned char *d, *pZeroFill, *pEndIP; + const char *s; + qboolean bQuadMustFollow; + int nPort; + + while ( ParseIPv6Addr_IsSpace( *pszText ) ) + ++pszText; + s = pszText; + + // Skip opening bracket, if present + if ( *s == '[' ) + { + ++s; + while ( ParseIPv6Addr_IsSpace( *s ) ) + ++s; + } + + // Special case for leading "::" + bQuadMustFollow = true; + d = pOutIP; + pZeroFill = NULL; + pEndIP = pOutIP + 16; + if ( s[0] == ':' && s[1] == ':' ) + { + pZeroFill = d; + s += 2; + bQuadMustFollow = false; + } + + // Parse quads until we get to the end + for (;;) + { + // Next thing must be a quad, or end of input. Is it a quad? + int quadDigit = ParseIPv6Addr_HexDigitVal( *s ); + const char *pszStartQuad; + int quad; + + if ( quadDigit < 0 ) + { + if ( bQuadMustFollow ) + return false; + break; + } + + // No room for more quads? + if ( d >= pEndIP ) + return false; + + pszStartQuad = s; + ++s; + quad = quadDigit; + + // Now parse up to three additional characters + quadDigit = ParseIPv6Addr_HexDigitVal( *s ); + if ( quadDigit >= 0 ) + { + quad = ( quad << 4 ) | quadDigit; + ++s; + + quadDigit = ParseIPv6Addr_HexDigitVal( *s ); + if ( quadDigit >= 0 ) + { + quad = ( quad << 4 ) | quadDigit; + ++s; + + quadDigit = ParseIPv6Addr_HexDigitVal( *s ); + if ( quadDigit >= 0 ) + { + quad = ( quad << 4 ) | quadDigit; + ++s; + } + } + } + + // Check if we hit a period, which would happen if we + // have an IPv4 dotted decimal. For example, "::ffff:192.168.1.210" + if ( *s == '.' ) + { + // Make sure we would have room to store four more bytes. + unsigned char *pEndDottedDecimal = d+4; + if ( pEndDottedDecimal > pEndIP ) + return false; + + // Parse 4 octets + s = pszStartQuad; + for (;;) + { + + // Parse 1-3 decimal digits + int octet = ParseIPv6Addr_DecimalDigitVal( *s ); + int dig; + + if ( octet < 0 ) + return false; + ++s; + dig = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( dig >= 0 ) + { + ++s; + octet = octet*10 + dig; + dig = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( dig >= 0 ) + { + ++s; + octet = octet*10 + dig; + + // Make sure value is in range. + if ( octet > 255 ) + return false; + } + } + *(d++) = (unsigned char)octet; + + // All done? + if ( d >= pEndDottedDecimal ) + break; + + // Next thing must be dot dot separator + if ( *s != '.' ) + return false; + + // Eat dot + ++s; + } + + break; + } + + // Stash it in the next slot, ignoring for now the issue + // of compressed zeros + *(d++) = (unsigned char)( quad >> 8 ); + *(d++) = (unsigned char)quad; + + // Only valid character for the IP portion is a colon. + // Anything else ends the IP portion + if ( *s != ':' ) + break; + + // Compressed zeros? + if ( s[1] == ':' ) + { + + // Eat '::' + s += 2; + + // Can only have one range of compressed zeros + if ( pZeroFill ) + return false; + + // Remember where to insert the compressed zeros + pZeroFill = d; + + // An IP can end with '::' + bQuadMustFollow = false; + } + else + { + // If they have filed the entire IP with no compressed zeros, + // then this is unambiguously a port number. That's not + // necessarily the best style, but it *is* unambiguous + // what it should mean, so let's allow it. If there + // are compressed zeros, then this is ambiguous, and we will + // always interpret it as a quad. + if ( !pZeroFill && d >= pEndIP ) + break; // leave ':' as next character, for below + + // Eat ':' + ++s; + + // A single colon must be followed by another quad + bQuadMustFollow = true; + } + } + + // End of the IP. Do we have compressed zeros? + if ( pZeroFill ) + { + // How many zeros do we need to fill? + intptr_t nZeros = pEndIP - d; + if ( nZeros <= 0 ) + return false; + + // Shift the quads after the bytes to the end + memmove( pZeroFill+nZeros, pZeroFill, d-pZeroFill ); + + // And now fill the zeros + memset( pZeroFill, 0, nZeros ); + } + else + { + // No compressed zeros. Just make sure we filled the IP exactly + if ( d != pEndIP ) + return false; + } + + if ( *s == '%' ) + { + // Parse scope number + uint32_t unScope = 0; + int nScopeDigit; + + ++s; + + nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( nScopeDigit < 0 ) + return false; + unScope = (uint32_t)nScopeDigit; + for (;;) + { + ++s; + if ( *s == '\0' || *s == ']' || ParseIPv6Addr_IsSpace( *s ) ) + break; + nScopeDigit = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( nScopeDigit < 0 ) + return false; + unScope = unScope * 10 + nScopeDigit; + } + + if ( pOutScope ) + *pOutScope = unScope; + } + else + { + if ( pOutScope ) + *pOutScope = 0; + } + + // If we started with a bracket, then the next character MUST be a bracket. + // (And this is the only circumstance in which a closing bracket would be legal) + if ( *pszText == '[' ) + { + while ( ParseIPv6Addr_IsSpace( *s ) ) + ++s; + if ( *s != ']' ) + return false; + ++s; + } + + // Now we are definitely at the end of the IP. Do we have a port? + // We support all of the syntaxes mentioned in RFC5952 section 6 other + // than the ambiguous case + if ( *s == ':' || *s == '#' || *s == '.' || *s == 'p' || *s == 'P' ) + { + ++s; + } + else + { + while ( ParseIPv6Addr_IsSpace( *s ) ) + ++s; + if ( *s == '\0' ) + { + // Parsed IP without port OK + if ( pOutPort ) + *pOutPort = -1; + return true; + } + + if ( strncmp( s, "port", 4 ) == 0 ) + { + s += 4; + while ( ParseIPv6Addr_IsSpace( *s ) ) + ++s; + } + else + { + // Extra stuff after the IP which isn't whitespace or a port + return false; + } + } + + // We have a port. If they didn't ask for it, that's considered a parse failure. + if ( !pOutPort ) + return false; + + // Parse port number + nPort = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( nPort < 0 ) + return false; + for (;;) + { + int portDigit; + + ++s; + if ( *s == '\0' || ParseIPv6Addr_IsSpace( *s ) ) + break; + portDigit = ParseIPv6Addr_DecimalDigitVal( *s ); + if ( portDigit < 0 ) + return false; + nPort = nPort * 10 + portDigit; + if ( nPort > 0xffff ) + return false; + } + + // Consume trailing whitespace; confirm nothing else in the input + while ( ParseIPv6Addr_IsSpace( *s ) ) + ++s; + if ( *s != '\0' ) + return false; + + *pOutPort = nPort; + return true; +} + diff --git a/engine/common/ipv6text.h b/engine/common/ipv6text.h new file mode 100644 index 00000000..6ed0f57f --- /dev/null +++ b/engine/common/ipv6text.h @@ -0,0 +1,56 @@ +/// Standalone plain C utilities for parsing and printing IPv6 addresses +#pragma once + +#include +#include + +/// Max length of an IPv6 string, with scope, WITHOUT port number, including \0': +/// 0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295 +#define k_ncchMaxIPV6AddrStringWithoutPort 51 + +/// Max number of bytes output by IPv6AddrToString, including '\0': +/// [0123:4567:89ab:cdef:0123:4567:89ab:cdef%4294967295]:12345 +/// There are other strings that are acceptable to ParseIPv6Addr +/// that are longer than this, but this is the longest canonical +/// string. +#define k_ncchMaxIPV6AddrStringWithPort 59 + +#ifdef __cplusplus +extern "C" { +#endif + +/// Format an IPv6 address to the canonical form according to RFC5952. +/// The address should be 16 bytes (e.g. same as in6_addr::s6_addr). +/// Your buffer MUST be at least k_ncchMaxIPV6AddrStringWithoutPort bytes. +extern void IPv6IPToString( char *pszOutText, const unsigned char *ip ); + +/// Format IPv6 IP and port to string. This uses the recommended +/// bracket notation, eg [1234::1]:12345. Your buffer MUST be +/// at least k_ncchMaxIPV6AddrStringWithPort bytes. +/// +/// If the scope is zero, it is not printed. +extern void IPv6AddrToString( char *pszOutText, const unsigned char *ip, uint16_t port, uint32_t scope ); + +/// Parse IPv6 address string. Returns true if parsed OK. Returns false +/// if input cannot be parsed, or if input specifies a port but pOutPort is NULL. +/// If input does not specify a port, and pOutPort is non-NULL, then *pOutPort is +/// set to -1. +/// +/// Parsing is tolerant of any unambiguous IPv6 representation, the input +/// need not be the canonical RFC5952 representation. +/// +/// IPv6 zones are not supported. +/// +/// Leading and trailing whitespace is OK around the entire string, +/// but not internal whitespace. The different methods for separating the +/// port in RFC5952 section 6 are supported, except the ambiguous case +/// of a colon to separate the port, when the IP contains a double-colon. +/// Brackets around an IP are OK, even if there is no port. +/// +/// Address must point to a 16-byte buffer (e.g. same as in6_addr::s6_addr) +/// Port is returned in host byte order. +extern bool ParseIPv6Addr( const char *pszText, unsigned char *pOutIP, int *pOutPort, uint32_t *pOutScope ); + +#ifdef __cplusplus +} +#endif diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 598c1c17..0ab717e2 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -13,30 +13,24 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#ifdef SINGLE_BINARY - +#if XASH_ENABLE_MAIN #include "build.h" #include "common.h" -#ifdef XASH_SDLMAIN -#include "SDL.h" + +#if XASH_SDLMAIN +#include #endif #if XASH_EMSCRIPTEN #include -#elif XASH_WIN32 -extern "C" -{ -// Enable NVIDIA High Performance Graphics while using Integrated Graphics. -__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - -// Enable AMD High Performance Graphics while using Integrated Graphics. -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -} #endif -#define E_GAME "XASH3D_GAME" // default env dir to start from #ifndef XASH_GAMEDIR -#define XASH_GAMEDIR "valve" +#define XASH_GAMEDIR "valve" // !!! Replace with your default (base) game directory !!! +#endif + +#if XASH_WIN32 +#error "Single-binary or dedicated builds aren't supported for Windows!" #endif static char szGameDir[128]; // safe place to keep gamedir @@ -45,87 +39,43 @@ static char **szArgv; static void Sys_ChangeGame( const char *progname ) { - // a1ba: may never be called within engine - // if platform supports execv() function - Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); - Host_Shutdown( ); - exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) ); + // stub } -_inline int Sys_Start( void ) +static int Sys_Start( void ) { - int ret; - const char *game = getenv( E_GAME ); + Q_strncpy( szGameDir, XASH_GAMEDIR, sizeof( szGameDir )); - if( !game ) - game = XASH_GAMEDIR; - - Q_strncpy( szGameDir, game, sizeof( szGameDir )); #if XASH_EMSCRIPTEN -#ifdef EMSCRIPTEN_LIB_FS - // For some unknown reason emscripten refusing to load libraries later - COM_LoadLibrary("menu", 0 ); - COM_LoadLibrary("server", 0 ); - COM_LoadLibrary("client", 0 ); -#endif -#if XASH_DEDICATED - // NodeJS support for debug - EM_ASM(try{ - FS.mkdir('/xash'); - FS.mount(NODEFS, { root: '.'}, '/xash' ); - FS.chdir('/xash'); - }catch(e){};); -#endif -#elif XASH_IOS - { - void IOS_LaunchDialog( void ); - IOS_LaunchDialog(); - } -#endif +#if !XASH_DEDICATED + EM_ASM( try { + FS.mkdir( '/rwdir' ); + FS.mount( IDBFS, { root: '.' }, '/rwdir' ); + } catch( e ) { };); +#else // XASH_DEDICATED + EM_ASM(try { + FS.mkdir( '/xash' ); + FS.mount( NODEFS, { root: '.'}, '/xash' ); + } catch( e ) { };); +#endif // XASH_DEDICATED +#endif // XASH_EMSCRIPTEN - ret = Host_Main( szArgc, szArgv, game, 0, Sys_ChangeGame ); +#if XASH_IOS + IOS_LaunchDialog(); +#endif // XASH_IOS - return ret; + return Host_Main( szArgc, szArgv, szGameDir, 0, Sys_ChangeGame ); } -#if !XASH_WIN32 int main( int argc, char **argv ) { +#if XASH_PSVITA + // inject -dev -console into args if required + szArgc = PSVita_GetArgv( argc, argv, &szArgv ); +#else szArgc = argc; szArgv = argv; - +#endif // XASH_PSVITA return Sys_Start(); } -#else -int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) -{ - LPWSTR* lpArgv; - int ret, i; - - lpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc ); - szArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* )); - - for( i = 0; i < szArgc; ++i ) - { - size_t size = wcslen(lpArgv[i]) + 1; - - // just in case, allocate some more memory - szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); - wcstombs( szArgv[i], lpArgv[i], size ); - } - szArgv[szArgc] = 0; - - LocalFree( lpArgv ); - - ret = Sys_Start(); - - for( ; i < szArgc; ++i ) - free( szArgv[i] ); - free( szArgv ); - - return ret; -} -#endif // XASH_WIN32 - - -#endif +#endif // XASH_ENABLE_MAIN diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index cdd3f8b6..7d20b5c4 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -65,7 +65,7 @@ void *COM_FunctionFromName_SR( void *hInstance, const char *pName ) if( f ) return f; } -#elif XASH_MSVC +#elif _MSC_VER // TODO: COM_ConvertToLocalPlatform doesn't support MSVC yet // also custom loader strips always MSVC mangling, so Win32 // platforms already use platform-neutral names @@ -81,8 +81,8 @@ void *COM_FunctionFromName_SR( void *hInstance, const char *pName ) const char *COM_OffsetNameForFunction( void *function ) { static string sname; - Q_snprintf( sname, MAX_STRING, "ofs:%lu", (size_t)((byte*)function - (byte*)svgame.dllFuncs.pfnGameInit) ); - Con_Reportf( "COM_OffsetNameForFunction %s\n", sname ); + Q_snprintf( sname, MAX_STRING, "ofs:%zu", ((byte*)function - (byte*)svgame.dllFuncs.pfnGameInit) ); + Con_Reportf( "%s: %s\n", __func__, sname ); return sname; } @@ -128,9 +128,9 @@ dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) static void COM_GenerateCommonLibraryName( const char *name, const char *ext, char *out, size_t size ) { -#if ( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) && XASH_X86 +#if ( XASH_WIN32 || ( XASH_LINUX && !XASH_ANDROID ) || XASH_APPLE ) && XASH_X86 Q_snprintf( out, size, "%s.%s", name, ext ); -#elif ( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) +#elif XASH_WIN32 || ( XASH_LINUX && !XASH_ANDROID ) || XASH_APPLE Q_snprintf( out, size, "%s_%s.%s", name, Q_buildarch(), ext ); #else Q_snprintf( out, size, "%s_%s_%s.%s", name, Q_buildos(), Q_buildarch(), ext ); @@ -151,13 +151,32 @@ static void COM_GenerateClientLibraryPath( const char *name, char *out, size_t s #else string dllpath; - // we don't have any library prefixes, so we can safely append dll_path here +#if XASH_ANDROID + Q_snprintf( dllpath, sizeof( dllpath ), "%s/lib%s", GI->dll_path, name ); +#else Q_snprintf( dllpath, sizeof( dllpath ), "%s/%s", GI->dll_path, name ); +#endif COM_GenerateCommonLibraryName( dllpath, OS_LIB_EXT, out, size ); #endif } +/* +============== +COM_StripIntelSuffix + +Some modders use _i?86 suffix in game library name +So strip it to follow library naming for non-Intel CPUs +============== +*/ +static inline void COM_StripIntelSuffix( char *out ) +{ + char *suffix = Q_strrchr( out, '_' ); + + if( suffix && Q_stricmpext( "_i?86", suffix )) + *suffix = 0; +} + /* ============== COM_GenerateServerLibraryPath @@ -165,35 +184,60 @@ COM_GenerateServerLibraryPath Generates platform-unique and compatible name for server library ============== */ -static void COM_GenerateServerLibraryPath( char *out, size_t size ) +static void COM_GenerateServerLibraryPath( const char *alt_dllname, char *out, size_t size ) { #ifdef XASH_INTERNAL_GAMELIBS // assuming library loader knows where to get libraries Q_strncpy( out, "server", size ); -#elif ( XASH_WIN32 || XASH_LINUX || XASH_APPLE ) && XASH_X86 - -#if XASH_WIN32 +#elif XASH_X86 && XASH_WIN32 Q_strncpy( out, GI->game_dll, size ); -#elif XASH_APPLE +#elif XASH_X86 && XASH_APPLE Q_strncpy( out, GI->game_dll_osx, size ); -#else // XASH_LINUX +#elif XASH_X86 && XASH_LINUX && !XASH_ANDROID Q_strncpy( out, GI->game_dll_linux, size ); -#endif + COM_StripExtension( out ); + // GoldSrc actually strips everything after '_', causing issues for mods that have '_' in the DLL name on Linux + // e.g. delta_particles.so becomes delta.so. We're gonna be smarter and just drop the _i?86 if it matches... + // ... until somebody complains :) + COM_StripIntelSuffix( out ); + COM_DefaultExtension( out, "." OS_LIB_EXT, size ); #else - string dllpath; - const char *ext; + string temp, dir, dllpath, ext; + const char *dllname; #if XASH_WIN32 - Q_strncpy( dllpath, GI->game_dll, sizeof( dllpath ) ); + Q_strncpy( temp, GI->game_dll, sizeof( temp )); #elif XASH_APPLE - Q_strncpy( dllpath, GI->game_dll_osx, sizeof( dllpath ) ); -#else // XASH_APPLE - Q_strncpy( dllpath, GI->game_dll_linux, sizeof( dllpath ) ); + Q_strncpy( temp, GI->game_dll_osx, sizeof( temp )); +#else + Q_strncpy( temp, GI->game_dll_linux, sizeof( temp )); #endif - ext = COM_FileExtension( dllpath ); - COM_StripExtension( dllpath ); + // path to the dll directory + COM_ExtractFilePath( temp, dir ); + if( alt_dllname ) + { + dllname = alt_dllname; + Q_strncpy( ext, OS_LIB_EXT, sizeof( ext )); + } + else + { + // cleaned up dll name + Q_strncpy( ext, COM_FileExtension( temp ), sizeof( ext )); + COM_StripExtension( temp ); + COM_StripIntelSuffix( temp ); + dllname = COM_FileWithoutPath( temp ); + } + + // add `lib` prefix if required by platform +#if XASH_ANDROID + Q_snprintf( dllpath, sizeof( dllpath ), "%s/lib%s", dir, dllname ); +#else + Q_snprintf( dllpath, sizeof( dllpath ), "%s/%s", dir, dllname ); +#endif + + // and finally add platform suffix COM_GenerateCommonLibraryName( dllpath, ext, out, size ); #endif } @@ -211,30 +255,34 @@ void COM_GetCommonLibraryPath( ECommonLibraryType eLibType, char *out, size_t si switch( eLibType ) { case LIBRARY_GAMEUI: - COM_GenerateClientLibraryPath( "menu", out, size ); + if( COM_CheckStringEmpty( host.menulib )) + { + if( host.menulib[0] == '@' ) + COM_GenerateClientLibraryPath( host.menulib + 1, out, size ); + else Q_strncpy( out, host.menulib, size ); + } + else COM_GenerateClientLibraryPath( "menu", out, size ); break; case LIBRARY_CLIENT: - if( SI.clientlib[0] ) + if( COM_CheckStringEmpty( host.clientlib )) { - Q_strncpy( out, SI.clientlib, size ); - } - else - { - COM_GenerateClientLibraryPath( "client", out, size ); + if( host.clientlib[0] == '@' ) + COM_GenerateClientLibraryPath( host.clientlib + 1, out, size ); + else Q_strncpy( out, host.clientlib, size ); } + else COM_GenerateClientLibraryPath( "client", out, size ); break; case LIBRARY_SERVER: - if( SI.gamedll[0] ) + if( COM_CheckStringEmpty( host.gamedll )) { - Q_strncpy( out, SI.gamedll, size ); - } - else - { - COM_GenerateServerLibraryPath( out, size ); + if( host.gamedll[0] == '@' ) + COM_GenerateServerLibraryPath( host.gamedll + 1, out, size ); + else Q_strncpy( out, host.gamedll, size ); } + else COM_GenerateServerLibraryPath( NULL, out, size ); break; default: - ASSERT( true ); + ASSERT( 0 ); out[0] = 0; break; } @@ -342,7 +390,7 @@ static char *COM_GetItaniumName( const char * const in_name ) if( i == MAX_NESTED_NAMESPACES ) { - Con_DPrintf( "%s: too much nested namespaces: %s\n", __FUNCTION__, in_name ); + Con_DPrintf( "%s: too much nested namespaces: %s\n", __func__, in_name ); return NULL; } @@ -356,7 +404,7 @@ static char *COM_GetItaniumName( const char * const in_name ) return out_name; invalid_format: - Con_DPrintf( "%s: invalid format: %s\n", __FUNCTION__, in_name ); + Con_DPrintf( "%s: invalid format: %s\n", __func__, in_name ); return NULL; } @@ -393,16 +441,18 @@ char **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, siz if( at ) len = (uint)( at - prev ); else len = (uint)Q_strlen( prev ); + Q_strncpy( symbols[i], prev, Q_min( len + 1, sizeof( symbols[i] ))); - prev = at + 1; if( !at ) break; + + prev = at + 1; } if( i == MAX_NESTED_NAMESPACES ) { - Con_DPrintf( "%s: too much nested namespaces: %s\n", __FUNCTION__, from ); + Con_DPrintf( "%s: too much nested namespaces: %s\n", __func__, from ); return NULL; } diff --git a/engine/common/library.h b/engine/common/library.h index 10524249..65572ad0 100644 --- a/engine/common/library.h +++ b/engine/common/library.h @@ -24,7 +24,8 @@ typedef struct dll_user_s qboolean custom_loader; // a bit who indicated loader type qboolean encrypted; // dll is crypted (some client.dll in HL, CS etc) char dllName[32]; // for debug messages - string fullPath, shortPath; // actual dll paths + char fullPath[2048]; + string shortPath; // actual dll paths // ordinals stuff, valid only on Win32 word *ordinals; diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index bb2edcc8..905bf60d 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -14,22 +14,50 @@ GNU General Public License for more details. */ #include "common.h" #include "netchan.h" +#include "server.h" typedef struct master_s { struct master_s *next; - qboolean sent; + qboolean sent; // TODO: get rid of this internal state qboolean save; + qboolean v6only; string address; netadr_t adr; // temporary, rewritten after each send + + uint heartbeat_challenge; + double last_heartbeat; } master_t; -struct masterlist_s +static struct masterlist_s { master_t *list; qboolean modified; } ml; +static CVAR_DEFINE_AUTO( sv_verbose_heartbeats, "0", 0, "print every heartbeat to console" ); + +#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes + +/* +======================== +NET_GetMasterHostByName +======================== +*/ +static net_gai_state_t NET_GetMasterHostByName( master_t *m ) +{ + net_gai_state_t res = NET_StringToAdrNB( m->address, &m->adr, m->v6only ); + + if( res == NET_EAI_OK ) + return res; + + m->adr.type = 0; + if( res == NET_EAI_NONAME ) + Con_Reportf( "Can't resolve adr: %s\n", m->address ); + + return res; +} + /* ======================== NET_SendToMasters @@ -45,48 +73,167 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ) for( list = ml.list; list; list = list->next ) { - int res; - if( list->sent ) continue; - res = NET_StringToAdrNB( list->address, &list->adr ); - - if( !res ) - { - Con_Reportf( "Can't resolve adr: %s\n", list->address ); - list->sent = true; - list->adr.type = NA_UNUSED; - continue; - } - - if( res == 2 ) + switch( NET_GetMasterHostByName( list )) { + case NET_EAI_AGAIN: list->sent = false; - list->adr.type = NA_UNUSED; wait = true; - continue; + break; + case NET_EAI_NONAME: + list->sent = true; + break; + case NET_EAI_OK: + list->sent = true; + NET_SendPacket( sock, len, data, list->adr ); + break; } - - list->sent = true; - - NET_SendPacket( sock, len, data, list->adr ); } if( !wait ) { - list = ml.list; - - while( list ) - { + // reset sent state + for( list = ml.list; list; list = list->next ) list->sent = false; - list = list->next; - } } return wait; } +/* +======================== +NET_AnnounceToMaster + +======================== +*/ +static void NET_AnnounceToMaster( master_t *m ) +{ + sizebuf_t msg; + char buf[16]; + + m->heartbeat_challenge = COM_RandomLong( 0, INT_MAX ); + + MSG_Init( &msg, "Master Join", buf, sizeof( buf )); + MSG_WriteBytes( &msg, "q\xFF", 2 ); + MSG_WriteDword( &msg, m->heartbeat_challenge ); + + NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ), m->adr ); + + if( sv_verbose_heartbeats.value ) + { + Con_Printf( S_NOTE "sent heartbeat to %s (%s, 0x%x)\n", + m->address, NET_AdrToString( m->adr ), m->heartbeat_challenge ); + } +} + +/* +======================== +NET_AnnounceToMaster + +======================== +*/ +void NET_MasterClear( void ) +{ + master_t *m; + + for( m = ml.list; m; m = m->next ) + m->last_heartbeat = MAX_HEARTBEAT; +} + +/* +======================== +NET_MasterHeartbeat + +======================== +*/ +void NET_MasterHeartbeat( void ) +{ + master_t *m; + + if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 ) + return; // only public servers send heartbeats + + for( m = ml.list; m; m = m->next ) + { + if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS ) + continue; + + switch( NET_GetMasterHostByName( m )) + { + case NET_EAI_AGAIN: + m->last_heartbeat = MAX_HEARTBEAT; // retry on next frame + if( sv_verbose_heartbeats.value ) + Con_Printf( S_NOTE "delay heartbeat to next frame until %s resolves\n", m->address ); + + break; + case NET_EAI_NONAME: + m->last_heartbeat = host.realtime; // try to resolve again on next heartbeat + break; + case NET_EAI_OK: + m->last_heartbeat = host.realtime; + NET_AnnounceToMaster( m ); + break; + } + } +} + +/* +================= +NET_MasterShutdown + +Informs all masters that this server is going down +(ignored by master servers in current implementation) +================= +*/ +void NET_MasterShutdown( void ) +{ + NET_Config( true, false ); // allow remote + while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); +} + + +/* +======================== +NET_GetMasterFromAdr + +======================== +*/ +static master_t *NET_GetMasterFromAdr( netadr_t adr ) +{ + master_t *master; + + for( master = ml.list; master; master = master->next ) + { + if( NET_CompareAdr( adr, master->adr )) + return master; + } + + return NULL; +} + + +/* +======================== +NET_GetMaster +======================== +*/ +qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat ) +{ + master_t *m; + + m = NET_GetMasterFromAdr( from ); + + if( m ) + { + *challenge = m->heartbeat_challenge; + *last_heartbeat = m->last_heartbeat; + } + + return m != NULL; +} + /* ======================== NET_IsMasterAdr @@ -95,15 +242,7 @@ NET_IsMasterAdr */ qboolean NET_IsMasterAdr( netadr_t adr ) { - master_t *master; - - for( master = ml.list; master; master = master->next ) - { - if( NET_CompareAdr( adr, master->adr )) - return true; - } - - return false; + return NET_GetMasterFromAdr( adr ) != NULL; } /* @@ -113,7 +252,7 @@ NET_AddMaster Add master to the list ======================== */ -static void NET_AddMaster( const char *addr, qboolean save ) +static void NET_AddMaster( const char *addr, qboolean save, qboolean v6only ) { master_t *master, *last; @@ -123,12 +262,13 @@ static void NET_AddMaster( const char *addr, qboolean save ) return; } - master = Mem_Malloc( host.mempool, sizeof( master_t ) ); - Q_strncpy( master->address, addr, MAX_STRING ); + master = Mem_Malloc( host.mempool, sizeof( *master ) ); + Q_strncpy( master->address, addr, sizeof( master->address )); master->sent = false; master->save = save; + master->v6only = v6only; master->next = NULL; - master->adr.type = NA_UNUSED; + master->adr.type = 0; // link in if( last ) @@ -145,7 +285,7 @@ static void NET_AddMaster_f( void ) return; } - NET_AddMaster( Cmd_Argv( 1 ), true ); // save them into config + NET_AddMaster( Cmd_Argv( 1 ), true, false ); // save them into config ml.modified = true; // save config } @@ -178,15 +318,14 @@ static void NET_ListMasters_f( void ) master_t *list; int i; - Msg( "Master servers\n=============\n" ); - + Con_Printf( "Master servers:\n" ); for( i = 1, list = ml.list; list; i++, list = list->next ) { - Msg( "%d\t%s", i, list->address ); - if( list->adr.type != NA_UNUSED ) - Msg( "\t%s\n", NET_AdrToString( list->adr )); - else Msg( "\n" ); + Con_Printf( "%d\t%s", i, list->address ); + if( list->adr.type != 0 ) + Con_Printf( "\t%s\n", NET_AdrToString( list->adr )); + else Con_Printf( "\n" ); } } @@ -216,11 +355,15 @@ static void NET_LoadMasters( void ) // format: master \n while( ( pfile = COM_ParseFile( pfile, token, sizeof( token ) ) ) ) { - if( !Q_strcmp( token, "master" ) ) // load addr + if( !Q_strcmp( token, "master" )) // load addr { pfile = COM_ParseFile( pfile, token, sizeof( token ) ); - - NET_AddMaster( token, true ); + NET_AddMaster( token, true, false ); + } + else if( !Q_strcmp( token, "master6" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token ) ); + NET_AddMaster( token, true, true ); } } @@ -242,23 +385,20 @@ void NET_SaveMasters( void ) master_t *m; if( !ml.modified ) - { - Con_Reportf( "Master server list not changed\n" ); return; - } f = FS_Open( "xashcomm.lst", "w", true ); if( !f ) { - Con_Reportf( S_ERROR "Couldn't write xashcomm.lst\n" ); + Con_Reportf( S_ERROR "Couldn't write xashcomm.lst\n" ); return; } for( m = ml.list; m; m = m->next ) { if( m->save ) - FS_Printf( f, "master %s\n", m->address ); + FS_Printf( f, "%s %s\n", m->v6only ? "master6" : "master", m->address ); } FS_Close( f ); @@ -277,7 +417,23 @@ void NET_InitMasters( void ) Cmd_AddRestrictedCommand( "clearmasters", NET_ClearMasters_f, "clear masterserver list" ); Cmd_AddCommand( "listmasters", NET_ListMasters_f, "list masterservers" ); - // keep main master always there - NET_AddMaster( MASTERSERVER_ADR, false ); + Cvar_RegisterVariable( &sv_verbose_heartbeats ); + + { // IPv4-only + NET_AddMaster( "mentality.rip:27010", false, false ); + NET_AddMaster( "ms2.mentality.rip:27010", false, false ); + NET_AddMaster( "ms3.mentality.rip:27010", false, false ); + } + + { // IPv6-only + NET_AddMaster( "aaaa.mentality.rip:27010", false, true ); + NET_AddMaster( "aaaa.ms2.mentality.rip:27010", false, true ); + } + + { // testing servers, might be offline + NET_AddMaster( "mentality.rip:27011", false, false ); + NET_AddMaster( "aaaa.mentality.rip:27011", false, true ); + } + NET_LoadMasters( ); } diff --git a/engine/common/mod_alias.c b/engine/common/mod_alias.c new file mode 100644 index 00000000..8dceff63 --- /dev/null +++ b/engine/common/mod_alias.c @@ -0,0 +1,241 @@ +/* +mod_alias.c - alias model loading +Copyright (C) 2010 Uncle Mike +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "alias.h" +#if !XASH_DEDICATED +#include "ref_common.h" +#endif // XASH_DEDICATED +#include "mod_local.h" +#include "xash3d_mathlib.h" + +static int g_posenum = 0; +static const trivertex_t *g_poseverts[MAXALIASFRAMES]; + +static const void *Mod_LoadAliasFrame( const daliasframe_t *pdaliasframe, maliasframedesc_t *frame, const aliashdr_t *aliashdr ) +{ + const trivertex_t *pinframe; + int i; + + Q_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name )); + frame->firstpose = g_posenum; + frame->numposes = 1; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; + frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; + } + + pinframe = (const trivertex_t *)(pdaliasframe + 1); + + g_poseverts[g_posenum] = pinframe; + g_posenum++; + + pinframe += aliashdr->numverts; + + return (void *)pinframe; +} + +static const void *Mod_LoadAliasGroup( const daliasgroup_t *pingroup, maliasframedesc_t *frame, const aliashdr_t *aliashdr ) +{ + const daliasinterval_t *pin_intervals; + const void *ptemp; + int i, numframes; + + frame->firstpose = g_posenum; + frame->numposes = numframes = pingroup->numframes; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; + frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; + } + + pin_intervals = (const daliasinterval_t *)(pingroup + 1); + + // all the intervals are always equal 0.1 so we don't care about them + frame->interval = pin_intervals->interval; + pin_intervals += numframes; + ptemp = (void *)pin_intervals; + + for( i = 0; i < numframes; i++ ) + { + g_poseverts[g_posenum] = (const trivertex_t *)((const daliasframe_t *)ptemp + 1); + ptemp = g_poseverts[g_posenum] + aliashdr->numverts; + g_posenum++; + } + + return ptemp; +} + +static void Mod_CalcAliasBounds( model_t *mod, const aliashdr_t *aliashdr ) +{ + int i, j, k; + float radius; + float dist; + vec3_t v; + + ClearBounds( mod->mins, mod->maxs ); + radius = 0.0f; + + // process verts + for( i = 0; i < aliashdr->numposes; i++ ) + { + for( j = 0; j < aliashdr->numverts; j++ ) + { + for( k = 0; k < 3; k++ ) + v[k] = g_poseverts[i][j].v[k] * aliashdr->scale[k] + aliashdr->scale_origin[k]; + + AddPointToBounds( v, mod->mins, mod->maxs ); + dist = DotProduct( v, v ); + + if( radius < dist ) + radius = dist; + } + } + + mod->radius = sqrt( radius ); +} + +static const void *Mod_LoadAllSkins( model_t *mod, int numskins, const daliasskintype_t *pskintype, const aliashdr_t *aliashdr ) +{ + int i, size; + + if(( numskins < 1 ) || ( numskins > MAX_SKINS )) + Host_Error( "%s: Invalid # of skins: %d\n", __func__, numskins ); + + size = aliashdr->skinwidth * aliashdr->skinheight; + + // just skipping textures, renderer will take care of them later + for( i = 0; i < numskins; i++ ) + { + if( pskintype->type == ALIAS_SKIN_SINGLE ) + { + const byte *ptexture = (const byte *)&pskintype[1]; + + pskintype = (const daliasskintype_t *)( ptexture + size ); + } + else + { + const daliasskingroup_t *pinskingroup = (const daliasskingroup_t *)&pskintype[1]; + const daliasskininterval_t *pinskinintervals = (const daliasskininterval_t *)&pinskingroup[1]; + const byte *ptexture = (const byte *)&pinskinintervals[pinskingroup->numskins]; + + pskintype = (const daliasskintype_t *)( ptexture + size ); + } + } + + return pskintype; +} + + +/* +==================== +Mod_LoadAliasModel + +load alias model +==================== +*/ +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) +{ + const daliasframetype_t *pframetype; + const daliasskintype_t *pskintype; + const dtriangle_t *pintriangles; + const daliashdr_t *pinmodel; + const stvert_t *pinstverts; + aliashdr_t *m_pAliasHeader; + size_t size; + char poolname[MAX_VA_STRING]; + int i; + + if( loaded ) *loaded = false; + pinmodel = (const daliashdr_t *)buffer; + i = pinmodel->version; + + if( i != ALIAS_VERSION ) + { + Con_DPrintf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, ALIAS_VERSION ); + return; + } + + if( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 ) + return; // how is it possible to make that? + + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); + + size = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t ); + mod->cache.data = m_pAliasHeader = Mem_Calloc( mod->mempool, size ); + + // endian-adjust and copy the data, starting with the alias model header + m_pAliasHeader->numverts = pinmodel->numverts; + + if( m_pAliasHeader->numverts > MAXALIASVERTS ) + { + Con_DPrintf( S_ERROR "model %s has too many vertices\n", mod->name ); + return; + } + + mod->flags = pinmodel->flags; // share effects flags + m_pAliasHeader->boundingradius = pinmodel->boundingradius; + m_pAliasHeader->numskins = pinmodel->numskins; + m_pAliasHeader->skinwidth = pinmodel->skinwidth; + m_pAliasHeader->skinheight = pinmodel->skinheight; + m_pAliasHeader->numtris = pinmodel->numtris; + mod->numframes = m_pAliasHeader->numframes = pinmodel->numframes; + m_pAliasHeader->size = pinmodel->size; + + for( i = 0; i < 3; i++ ) + { + m_pAliasHeader->scale[i] = pinmodel->scale[i]; + m_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i]; + m_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i]; + } + + // load the skins + pskintype = (const daliasskintype_t *)&pinmodel[1]; + pskintype = Mod_LoadAllSkins( mod, m_pAliasHeader->numskins, pskintype, m_pAliasHeader ); + // will be done at renderer side... + + // load base s and t vertices + pinstverts = (const stvert_t *)pskintype; + // will be done at renderer side... + + // load triangle lists + pintriangles = (const dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; + // will be done at renderer side + + // load the frames + pframetype = (const daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris]; + m_pAliasHeader->pposeverts = g_poseverts; // store the pointer to be accessed by renderer + g_posenum = 0; + + for( i = 0; i < m_pAliasHeader->numframes; i++ ) + { + aliasframetype_t frametype = pframetype->type; + + if( frametype == ALIAS_SINGLE ) + pframetype = (const daliasframetype_t *)Mod_LoadAliasFrame((const daliasframe_t *)&pframetype[1], &m_pAliasHeader->frames[i], m_pAliasHeader ); + else pframetype = (const daliasframetype_t *)Mod_LoadAliasGroup(( const daliasgroup_t *)&pframetype[1], &m_pAliasHeader->frames[i], m_pAliasHeader ); + } + + m_pAliasHeader->numposes = g_posenum; + Mod_CalcAliasBounds( mod, m_pAliasHeader ); + mod->type = mod_alias; + + if( loaded ) *loaded = true; // done +} diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 294d0dd8..c232c9b7 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -24,12 +24,11 @@ GNU General Public License for more details. #include "client.h" #include "server.h" // LUMP_ error codes #include "ref_common.h" -typedef struct wadlist_s -{ - char wadnames[MAX_MAP_WADS][32]; - int wadusage[MAX_MAP_WADS]; - int count; -} wadlist_t; +#if defined( HAVE_OPENMP ) +#include +#endif // HAVE_OPENMP + +#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 ) typedef struct leaflist_s { @@ -130,10 +129,10 @@ typedef struct dclipnode32_t *clipnodes_out; // temporary 32-bit array to hold clipnodes // misc stuff - wadlist_t wadlist; - int lightmap_samples; // samples per lightmap (1 or 3) - int version; // model version - qboolean isworld; + int lightmap_samples; // samples per lightmap (1 or 3) + int version; // model version + qboolean isworld; + qboolean isbsp30ext; } dbspmodel_t; typedef struct @@ -159,18 +158,19 @@ typedef struct #define LUMP_SAVESTATS BIT( 0 ) #define LUMP_TESTONLY BIT( 1 ) #define LUMP_SILENT BIT( 2 ) +#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump typedef struct { - int lumpnumber; - const size_t mincount; - const size_t maxcount; - const int entrysize; - const int entrysize32; // alternative (-1 by default) - const char *loadname; - int flags; - const void **dataptr; - size_t *count; + int lumpnumber; + int flags; + const size_t mincount; + const size_t maxcount; + const int entrysize; + const int entrysize32; // alternative (-1 by default) + const char *loadname; + const void **dataptr; + size_t *count; } mlumpinfo_t; world_static_t world; @@ -178,33 +178,392 @@ static dbspmodel_t srcmodel; static loadstat_t loadstat; static model_t *worldmodel; static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer -static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS]; -static mlumpinfo_t srclumps[HEADER_LUMPS] = +static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS]; +static mlumpinfo_t srclumps[HEADER_LUMPS] = { -{ LUMP_ENTITIES, 32, MAX_MAP_ENTSTRING, sizeof( byte ), -1, "entities", 0, (const void **)&srcmodel.entdata, &srcmodel.entdatasize }, -{ LUMP_PLANES, 1, MAX_MAP_PLANES, sizeof( dplane_t ), -1, "planes", 0, (const void **)&srcmodel.planes, &srcmodel.numplanes }, -{ LUMP_TEXTURES, 1, MAX_MAP_MIPTEX, sizeof( byte ), -1, "textures", 0, (const void **)&srcmodel.textures, &srcmodel.texdatasize }, -{ LUMP_VERTEXES, 0, MAX_MAP_VERTS, sizeof( dvertex_t ), -1, "vertexes", 0, (const void **)&srcmodel.vertexes, &srcmodel.numvertexes }, -{ LUMP_VISIBILITY, 0, MAX_MAP_VISIBILITY, sizeof( byte ), -1, "visibility", 0, (const void **)&srcmodel.visdata, &srcmodel.visdatasize }, -{ LUMP_NODES, 1, MAX_MAP_NODES, sizeof( dnode_t ), sizeof( dnode32_t ), "nodes", CHECK_OVERFLOW, (const void **)&srcmodel.nodes, &srcmodel.numnodes }, -{ LUMP_TEXINFO, 0, MAX_MAP_TEXINFO, sizeof( dtexinfo_t ), -1, "texinfo", CHECK_OVERFLOW, (const void **)&srcmodel.texinfo, &srcmodel.numtexinfo }, -{ LUMP_FACES, 0, MAX_MAP_FACES, sizeof( dface_t ), sizeof( dface32_t ), "faces", CHECK_OVERFLOW, (const void **)&srcmodel.surfaces, &srcmodel.numsurfaces }, -{ LUMP_LIGHTING, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "lightmaps", 0, (const void **)&srcmodel.lightdata, &srcmodel.lightdatasize }, -{ LUMP_CLIPNODES, 0, MAX_MAP_CLIPNODES, sizeof( dclipnode_t ), sizeof( dclipnode32_t ), "clipnodes", 0, (const void **)&srcmodel.clipnodes, &srcmodel.numclipnodes }, -{ LUMP_LEAFS, 1, MAX_MAP_LEAFS, sizeof( dleaf_t ), sizeof( dleaf32_t ), "leafs", CHECK_OVERFLOW, (const void **)&srcmodel.leafs, &srcmodel.numleafs }, -{ LUMP_MARKSURFACES, 0, MAX_MAP_MARKSURFACES, sizeof( dmarkface_t ), sizeof( dmarkface32_t ), "markfaces", 0, (const void **)&srcmodel.markfaces, &srcmodel.nummarkfaces }, -{ LUMP_EDGES, 0, MAX_MAP_EDGES, sizeof( dedge_t ), sizeof( dedge32_t ), "edges", 0, (const void **)&srcmodel.edges, &srcmodel.numedges }, -{ LUMP_SURFEDGES, 0, MAX_MAP_SURFEDGES, sizeof( dsurfedge_t ), -1, "surfedges", 0, (const void **)&srcmodel.surfedges, &srcmodel.numsurfedges }, -{ LUMP_MODELS, 1, MAX_MAP_MODELS, sizeof( dmodel_t ), -1, "models", CHECK_OVERFLOW, (const void **)&srcmodel.submodels, &srcmodel.numsubmodels }, + { + .lumpnumber = LUMP_ENTITIES, + .mincount = 32, + .maxcount = MAX_MAP_ENTSTRING, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "entities", + .flags = 0, + .dataptr = (const void **)&srcmodel.entdata, + .count = &srcmodel.entdatasize, + }, + { + .lumpnumber = LUMP_PLANES, + .mincount = 1, + .maxcount = MAX_MAP_PLANES, + .entrysize = sizeof( dplane_t ), + .entrysize32 = -1, + .loadname = "planes", + .flags = 0, + .dataptr = (const void **)&srcmodel.planes, + .count = &srcmodel.numplanes, + }, + { + .lumpnumber = LUMP_TEXTURES, + .mincount = 1, + .maxcount = MAX_MAP_MIPTEX, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "textures", + .flags = 0, + .dataptr = (const void **)&srcmodel.textures, + .count = &srcmodel.texdatasize, + }, + { + .lumpnumber = LUMP_VERTEXES, + .mincount = 0, + .maxcount = MAX_MAP_VERTS, + .entrysize = sizeof( dvertex_t ), + .entrysize32 = -1, + .loadname = "vertexes", + .flags = 0, + .dataptr = (const void **)&srcmodel.vertexes, + .count = &srcmodel.numvertexes, + }, + { + .lumpnumber = LUMP_VISIBILITY, + .mincount = 0, + .maxcount = MAX_MAP_VISIBILITY, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "visibility", + .flags = 0, + .dataptr = (const void **)&srcmodel.visdata, + .count = &srcmodel.visdatasize, + }, + { + .lumpnumber = LUMP_NODES, + .mincount = 1, + .maxcount = MAX_MAP_NODES, + .entrysize = sizeof( dnode_t ), + .entrysize32 = sizeof( dnode32_t ), + .loadname = "nodes", + .flags = CHECK_OVERFLOW, + .dataptr = (const void **)&srcmodel.nodes, + .count = &srcmodel.numnodes, + }, + { + .lumpnumber = LUMP_TEXINFO, + .mincount = 0, + .maxcount = MAX_MAP_TEXINFO, + .entrysize = sizeof( dtexinfo_t ), + .entrysize32 = -1, + .loadname = "texinfo", + .flags = CHECK_OVERFLOW, + .dataptr = (const void **)&srcmodel.texinfo, + .count = &srcmodel.numtexinfo, + }, + { + .lumpnumber = LUMP_FACES, + .mincount = 0, + .maxcount = MAX_MAP_FACES, + .entrysize = sizeof( dface_t ), + .entrysize32 = sizeof( dface32_t ), + .loadname = "faces", + .flags = CHECK_OVERFLOW, + .dataptr = (const void **)&srcmodel.surfaces, + .count = &srcmodel.numsurfaces, + }, + { + .lumpnumber = LUMP_LIGHTING, + .mincount = 0, + .maxcount = MAX_MAP_LIGHTING, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "lightmaps", + .flags = 0, + .dataptr = (const void **)&srcmodel.lightdata, + .count = &srcmodel.lightdatasize, + }, + { + .lumpnumber = LUMP_CLIPNODES, + .mincount = 0, + .maxcount = MAX_MAP_CLIPNODES, + .entrysize = sizeof( dclipnode_t ), + .entrysize32 = sizeof( dclipnode32_t ), + .loadname = "clipnodes", + .flags = 0, + .dataptr = (const void **)&srcmodel.clipnodes, + .count = &srcmodel.numclipnodes, + }, + { + .lumpnumber = LUMP_LEAFS, + .mincount = 1, + .maxcount = MAX_MAP_LEAFS, + .entrysize = sizeof( dleaf_t ), + .entrysize32 = sizeof( dleaf32_t ), + .loadname = "leafs", + .flags = CHECK_OVERFLOW, + .dataptr = (const void **)&srcmodel.leafs, + .count = &srcmodel.numleafs, + }, + { + .lumpnumber = LUMP_MARKSURFACES, + .mincount = 0, + .maxcount = MAX_MAP_MARKSURFACES, + .entrysize = sizeof( dmarkface_t ), + .entrysize32 = sizeof( dmarkface32_t ), + .loadname = "markfaces", + .flags = 0, + .dataptr = (const void **)&srcmodel.markfaces, + .count = &srcmodel.nummarkfaces, + }, + { + .lumpnumber = LUMP_EDGES, + .mincount = 0, + .maxcount = MAX_MAP_EDGES, + .entrysize = sizeof( dedge_t ), + .entrysize32 = sizeof( dedge32_t ), + .loadname = "edges", + .flags = 0, + .dataptr = (const void **)&srcmodel.edges, + .count = &srcmodel.numedges, + }, + { + .lumpnumber = LUMP_SURFEDGES, + .mincount = 0, + .maxcount = MAX_MAP_SURFEDGES, + .entrysize = sizeof( dsurfedge_t ), + .entrysize32 = -1, + .loadname = "surfedges", + .flags = 0, + .dataptr = (const void **)&srcmodel.surfedges, + .count = &srcmodel.numsurfedges, + }, + { + .lumpnumber = LUMP_MODELS, + .mincount = 1, + .maxcount = MAX_MAP_MODELS, + .entrysize = sizeof( dmodel_t ), + .entrysize32 = -1, + .loadname = "models", + .flags = CHECK_OVERFLOW, + .dataptr = (const void **)&srcmodel.submodels, + .count = &srcmodel.numsubmodels, + }, }; -static mlumpinfo_t extlumps[EXTRA_LUMPS] = +static const mlumpinfo_t extlumps[EXTRA_LUMPS] = { -{ LUMP_LIGHTVECS, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "deluxmaps", USE_EXTRAHEADER, (const void **)&srcmodel.deluxdata, &srcmodel.deluxdatasize }, -{ LUMP_FACEINFO, 0, MAX_MAP_FACEINFO, sizeof( dfaceinfo_t ), -1, "faceinfos", CHECK_OVERFLOW|USE_EXTRAHEADER, (const void **)&srcmodel.faceinfo, &srcmodel.numfaceinfo }, -{ LUMP_SHADOWMAP, 0, MAX_MAP_LIGHTING / 3, sizeof( byte ), -1, "shadowmap", USE_EXTRAHEADER, (const void **)&srcmodel.shadowdata, &srcmodel.shadowdatasize }, + { + .lumpnumber = LUMP_LIGHTVECS, + .mincount = 0, + .maxcount = MAX_MAP_LIGHTING, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "deluxmaps", + .flags = USE_EXTRAHEADER, + .dataptr = (const void **)&srcmodel.deluxdata, + .count = &srcmodel.deluxdatasize, + }, + { + .lumpnumber = LUMP_FACEINFO, + .mincount = 0, + .maxcount = MAX_MAP_FACEINFO, + .entrysize = sizeof( dfaceinfo_t ), + .entrysize32 = -1, + .loadname = "faceinfos", + .flags = CHECK_OVERFLOW|USE_EXTRAHEADER, + .dataptr = (const void **)&srcmodel.faceinfo, + .count = &srcmodel.numfaceinfo, + }, + { + .lumpnumber = LUMP_SHADOWMAP, + .mincount = 0, + .maxcount = MAX_MAP_LIGHTING / 3, + .entrysize = sizeof( byte ), + .entrysize32 = -1, + .loadname = "shadowmap", + .flags = USE_EXTRAHEADER, + .dataptr = (const void **)&srcmodel.shadowdata, + .count = &srcmodel.shadowdatasize, + }, }; +#define BOX_CLIPNODES_INITIALIZER \ + { \ + .planenum = 0, \ + .children = { CONTENTS_EMPTY, 1 }, \ + }, \ + { \ + .planenum = 1, \ + .children = { 2, CONTENTS_EMPTY }, \ + }, \ + { \ + .planenum = 2, \ + .children = { CONTENTS_EMPTY, 3 }, \ + }, \ + { \ + .planenum = 3, \ + .children = { 4, CONTENTS_EMPTY }, \ + }, \ + { \ + .planenum = 4, \ + .children = { CONTENTS_EMPTY, 5 }, \ + }, \ + { \ + .planenum = 5, \ + .children = { CONTENTS_SOLID, CONTENTS_EMPTY }, \ + }, \ + +const mclipnode16_t box_clipnodes16[6] = { BOX_CLIPNODES_INITIALIZER }; +const mclipnode32_t box_clipnodes32[6] = { BOX_CLIPNODES_INITIALIZER }; + +/* +=============================================================================== + + Static helper functions + +=============================================================================== +*/ + +static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i ) +{ + if( i < 0 || i >= bmod->textures->nummiptex ) + return NULL; + + if( bmod->textures->dataofs[i] == -1 ) + return NULL; + + return (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] ); +} + +// Returns index of WAD that texture was found in, or -1 if not found. +static int Mod_LoadTextureFromWadList( wadlist_t *list, const char *name, rgbdata_t **pic, char *texpath, size_t texpathlen ) +{ + int i; + + if( !list || !COM_CheckString( name )) + return -1; + + // check wads in reverse order + for( i = list->count - 1; i >= 0; i-- ) + { + searchpath_t *sp = NULL; + + while(( sp = g_fsapi.GetArchiveByName( list->wadnames[i], sp ))) + { + fs_offset_t len; + byte *buf; + char file[MAX_VA_STRING]; + int pack_ind; + + Q_snprintf( file, sizeof( file ), "%s.mip", name ); + pack_ind = g_fsapi.FindFileInArchive( sp, file, NULL, 0 ); + + if( pack_ind < 0 ) + continue; + + if( texpath != NULL ) + Q_snprintf( texpath, texpathlen, "%s/%s.mip", list->wadnames[i], name ); + + if( pic == NULL ) + return i; // dedicated server don't want to load the textures (why?) + + if( !( buf = g_fsapi.LoadFileFromArchive( sp, file, pack_ind, &len, false ))) + { + *pic = NULL; + return i; // corrupted file, don't ignore it + } + + // tell imagelib to directly load this texture to save time + Q_snprintf( file, sizeof( file ), "#%s/%s.mip", list->wadnames[i], name ); + *pic = FS_LoadImage( file, buf, len ); + Mem_Free( buf ); + return i; // if file is corrupted, it's fine, we want to tell the user about it + } + } + + return -1; +} + +static fs_offset_t Mod_CalculateMipTexSize( const mip_t *mt, qboolean palette ) +{ + if( !mt ) + return 0; + + return sizeof( *mt ) + (( mt->width * mt->height * 85 ) >> 6 ) + + ( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 ); +} + +static qboolean Mod_CalcMipTexUsesCustomPalette( model_t *mod, dbspmodel_t *bmod, int textureIndex ) +{ + int nextTextureIndex = 0; + mip_t *mipTex; + fs_offset_t size, remainingBytes; + + mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + + if( !mipTex || mipTex->offsets[0] <= 0 ) + return false; + + // Calculate the size assuming we are not using a custom palette. + size = Mod_CalculateMipTexSize( mipTex, false ); + + // Compute next data offset to determine allocated miptex space + for( nextTextureIndex = textureIndex + 1; nextTextureIndex < mod->numtextures; nextTextureIndex++ ) + { + int nextOffset = bmod->textures->dataofs[nextTextureIndex]; + + if( nextOffset != -1 ) + { + remainingBytes = nextOffset - ( bmod->textures->dataofs[textureIndex] + size ); + return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES; + } + } + + // There was no other miptex after this one. + // See if there is enough space between the end and our offset. + remainingBytes = bmod->texdatasize - ( bmod->textures->dataofs[textureIndex] + size ); + return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES; +} + +static qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex ) +{ + if( !tex ) + return false; + + // Not an animated texture name + if( tex->name[0] != '-' && tex->name[0] != '+' ) + return false; + + // Name implies texture is animated - check second character is valid. + if( !( tex->name[1] >= '0' && tex->name[1] <= '9' ) && + !( tex->name[1] >= 'a' && tex->name[1] <= 'j' )) + { + Con_Printf( S_ERROR "%s: animating texture \"%s\" has invalid name\n", __func__, tex->name ); + return false; + } + + return true; +} + +static void Mod_CreateDefaultTexture( model_t *mod, texture_t **texture ) +{ + texture_t *tex; + + // Pointer must be valid, and value pointed to must be null. + if( !texture || *texture != NULL ) + return; + + *texture = tex = Mem_Calloc( mod->mempool, sizeof( *tex )); + Q_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name )); + +#if !XASH_DEDICATED + if( !Host_IsDedicated( )) + { + tex->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); + tex->width = 16; + tex->height = 16; + } +#endif // XASH_DEDICATED +} + /* =============================================================================== @@ -219,7 +578,7 @@ Mod_LoadLump generic loader ================= */ -static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, int flags ) +static void Mod_LoadLump( const byte *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags ) { int version = ((dheader_t *)in)->version; size_t numelems, real_entrysize; @@ -251,13 +610,12 @@ static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, // always use alternate entrysize for BSP2 real_entrysize = info->entrysize32; } - else if( info->lumpnumber == LUMP_CLIPNODES && version != Q1BSP_VERSION ) + else if( version == HLBSP_VERSION && FBitSet( flags, LUMP_BSP30EXT ) && info->lumpnumber == LUMP_CLIPNODES ) { - // never run this check for BSP29 because Arguire QBSP 'broken' clipnodes! - if(( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize ) >= MAX_MAP_CLIPNODES ) + // if this map is bsp30ext, try to guess extended clipnodes + if((( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize32 ) >= MAX_MAP_CLIPNODES_HLBSP )) { real_entrysize = info->entrysize32; - SetBits( flags, LUMP_SILENT ); // shut up warning } } @@ -308,7 +666,7 @@ static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, if( l->filelen % real_entrysize ) { if( !FBitSet( flags, LUMP_SILENT )) - Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %lu bytes\n", msg2, l->filelen, real_entrysize ); + Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %zu bytes\n", msg2, l->filelen, real_entrysize ); loadstat.numerrors++; return; } @@ -434,9 +792,9 @@ void Mod_PrintWorldStats_f( void ) Con_Printf( "Lighting: %s\n", FBitSet( w->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" ); Con_Printf( "World total leafs: %d\n", worldmodel->numleafs + 1 ); Con_Printf( "original name: ^1%s\n", worldmodel->name ); - Con_Printf( "internal name: %s\n", (world.message[0]) ? va( "^2%s", world.message ) : "none" ); - Con_Printf( "map compiler: %s\n", (world.compiler[0]) ? va( "^3%s", world.compiler ) : "unknown" ); - Con_Printf( "map editor: %s\n", (world.generator[0]) ? va( "^2%s", world.generator ) : "unknown" ); + Con_Printf( "internal name: ^2%s\n", world.message[0] ? world.message : "none" ); + Con_Printf( "map compiler: ^3%s\n", world.compiler[0] ? world.compiler : "unknown" ); + Con_Printf( "map editor: ^2%s\n", world.generator[0] ? world.generator : "unknown" ); } /* @@ -449,54 +807,85 @@ void Mod_PrintWorldStats_f( void ) /* =================== Mod_DecompressPVS + +TODO: replace all Mod_DecompressPVS calls by this =================== */ -byte *Mod_DecompressPVS( const byte *in, int visbytes ) +static void Mod_DecompressPVSTo( byte *const out, const byte *in, size_t visbytes ) { - byte *out; - int c; + byte *dst = out; - out = g_visdata; - - if( !in ) + if( !in ) // no visinfo, make all visible { - // no vis info, so make all visible - while( visbytes ) - { - *out++ = 0xff; - visbytes--; - } - return g_visdata; + memset( out, 0xFF, visbytes ); + return; } - do + while( dst < out + visbytes ) { - if( *in ) + if( *in ) // uncompressed { - *out++ = *in++; - continue; + *dst++ = *in++; } - - c = in[1]; - in += 2; - - while( c ) + else // zero repeated `c` times { - *out++ = 0; - c--; - } - } while( out - g_visdata < visbytes ); + size_t c = in[1]; + if( c > out + visbytes - dst ) + c = out + visbytes - dst; + memset( dst, 0, c ); + in += 2; + dst += c; + } + } +} + +/* +=================== +Mod_DecompressPVS +=================== +*/ +static byte *Mod_DecompressPVS( const byte *in, int visbytes ) +{ + Mod_DecompressPVSTo( g_visdata, in, visbytes ); return g_visdata; } +static size_t Mod_CompressPVS( byte *const out, const byte *in, size_t inbytes ) +{ + size_t i; + byte *dst = out; + + for( i = 0; i < inbytes; i++ ) + { + size_t j = i + 1, rep = 1; + + *dst++ = in[i]; + + // only compress zeros + if( in[i] ) + continue; + + for( ; j < inbytes && rep != 255; j++, rep++ ) + { + if( in[j] ) + break; + } + + *dst++ = rep; + i = j - 1; + } + + return dst - out; +} + /* ================== Mod_PointInLeaf ================== */ -mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ) +mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod ) { Assert( node != NULL ); @@ -504,7 +893,7 @@ mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ) { if( node->contents < 0 ) return (mleaf_t *)node; - node = node->children[PlaneDiff( p, node->plane ) <= 0]; + node = node_child( node, PlaneDiff( p, node->plane ) <= 0, mod ); } // never reached @@ -521,22 +910,11 @@ NOTE: can return NULL */ byte *Mod_GetPVSForPoint( const vec3_t p ) { - mnode_t *node; - mleaf_t *leaf = NULL; + mleaf_t *leaf; ASSERT( worldmodel != NULL ); - node = worldmodel->nodes; - - while( 1 ) - { - if( node->contents < 0 ) - { - leaf = (mleaf_t *)node; - break; // we found a leaf - } - node = node->children[PlaneDiff( p, node->plane ) <= 0]; - } + leaf = Mod_PointInLeaf( p, worldmodel->nodes, worldmodel ); if( leaf && leaf->cluster >= 0 ) return Mod_DecompressPVS( leaf->compressed_vis, world.visbytes ); @@ -549,33 +927,40 @@ Mod_FatPVS_RecursiveBSPNode ================== */ -static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node ) +static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node, qboolean phs ) { - int i; - while( node->contents >= 0 ) { float d = PlaneDiff( org, node->plane ); if( d > radius ) - node = node->children[0]; + node = node_child( node, 0, worldmodel ); else if( d < -radius ) - node = node->children[1]; + node = node_child( node, 1, worldmodel ); else { // go down both sides - Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0] ); - node = node->children[1]; + Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node_child( node, 0, worldmodel ), phs ); + node = node_child( node, 1, worldmodel ); } } // if this leaf is in a cluster, accumulate the vis bits if(((mleaf_t *)node)->cluster >= 0 ) { - byte *vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes ); + byte *vis; - for( i = 0; i < visbytes; i++ ) - visbuffer[i] |= vis[i]; + if( phs ) + { + int i = ((mleaf_t *)node)->cluster + 1; + vis = Mod_DecompressPVS( &world.compressed_phs[world.phsofs[i]], world.visbytes ); + } + else + { + vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes ); + } + + Q_memor( visbuffer, vis, visbytes ); } } @@ -587,14 +972,14 @@ Calculates a PVS that is the inclusive or of all leafs within radius pixels of the given point. ================== */ -int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis ) +int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean phs ) { int bytes = world.visbytes; mleaf_t *leaf = NULL; ASSERT( worldmodel != NULL ); - leaf = Mod_PointInLeaf( org, worldmodel->nodes ); + leaf = Mod_PointInLeaf( org, worldmodel->nodes, worldmodel ); bytes = Q_min( bytes, visbytes ); // enable full visibility for some reasons @@ -604,9 +989,17 @@ int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, q return bytes; } + // requested PHS but we don't have PHS for some reason + // enable full visibility + if( phs && !( world.compressed_phs && world.phsofs )) + { + memset( visbuffer, 0xFF, bytes ); + return bytes; + } + if( !merge ) memset( visbuffer, 0x00, bytes ); - Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes ); + Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes, phs ); return bytes; } @@ -645,20 +1038,16 @@ static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node ) sides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane ); if( sides == 1 ) - { - node = node->children[0]; - } + node = node_child( node, 0, worldmodel ); else if( sides == 2 ) - { - node = node->children[1]; - } + node = node_child( node, 1, worldmodel ); else { // go down both if( ll->topnode == -1 ) ll->topnode = node - worldmodel->nodes; - Mod_BoxLeafnums_r( ll, node->children[0] ); - node = node->children[1]; + Mod_BoxLeafnums_r( ll, node_child( node, 0, worldmodel )); + node = node_child( node, 1, worldmodel ); } } } @@ -715,53 +1104,6 @@ qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbi return false; } -/* -============= -Mod_HeadnodeVisible -============= -*/ -qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf ) -{ - if( !node || node->contents == CONTENTS_SOLID ) - return false; - - if( node->contents < 0 ) - { - if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster )) - return false; - - if( lastleaf ) - *lastleaf = ((mleaf_t *)node)->cluster; - return true; - } - - if( Mod_HeadnodeVisible( node->children[0], visbits, lastleaf )) - return true; - - if( Mod_HeadnodeVisible( node->children[1], visbits, lastleaf )) - return true; - - return false; -} - -/* -================== -Mod_AmbientLevels - -grab the ambient sound levels for current point -================== -*/ -void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ) -{ - mleaf_t *leaf; - - if( !worldmodel || !p || !pvolumes ) - return; - - leaf = Mod_PointInLeaf( p, worldmodel->nodes ); - *(int *)pvolumes = *(int *)leaf->ambient_sound_level; -} - /* ================= Mod_FindModelOrigin @@ -777,7 +1119,7 @@ static void Mod_FindModelOrigin( const char *entities, const char *modelname, ve qboolean model_found; qboolean origin_found; - if( !entities || !modelname || !*modelname ) + if( !entities || !COM_CheckString( modelname )) return; if( !origin || !VectorIsNull( origin )) @@ -788,7 +1130,7 @@ static void Mod_FindModelOrigin( const char *entities, const char *modelname, ve while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( token[0] != '{' ) - Host_Error( "Mod_FindModelOrigin: found %s when expecting {\n", token ); + Host_Error( "%s: found %s when expecting {\n", __func__, token ); model_found = origin_found = false; VectorClear( origin ); @@ -797,17 +1139,17 @@ static void Mod_FindModelOrigin( const char *entities, const char *modelname, ve { // parse key if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL ) - Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" ); + Host_Error( "%s: EOF without closing brace\n", __func__ ); if( token[0] == '}' ) break; // end of desc Q_strncpy( keyname, token, sizeof( keyname )); // parse value if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL ) - Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" ); + Host_Error( "%s: EOF without closing brace\n", __func__ ); if( token[0] == '}' ) - Host_Error( "Mod_FindModelOrigin: closing brace without data\n" ); + Host_Error( "%s: closing brace without data\n", __func__ ); if( !Q_stricmp( keyname, "model" ) && !Q_stricmp( modelname, token )) model_found = true; @@ -831,7 +1173,7 @@ converted maps potential may don't support water transparency ================== */ -static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod ) +static qboolean Mod_CheckWaterAlphaSupport( model_t *mod, dbspmodel_t *bmod ) { mleaf_t *leaf; int i, j; @@ -842,15 +1184,15 @@ static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod ) // check all liquid leafs to see if they can see into empty leafs, if any // can we can assume this map supports r_wateralpha - for( i = 0, leaf = loadmodel->leafs; i < loadmodel->numleafs; i++, leaf++ ) + for( i = 0, leaf = mod->leafs; i < mod->numleafs; i++, leaf++ ) { if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 ) { pvs = Mod_DecompressPVS( leaf->compressed_vis, world.visbytes ); - for( j = 0; j < loadmodel->numleafs; j++ ) + for( j = 0; j < mod->numleafs; j++ ) { - if( CHECKVISBIT( pvs, loadmodel->leafs[j].cluster ) && loadmodel->leafs[j].contents == CONTENTS_EMPTY ) + if( CHECKVISBIT( pvs, mod->leafs[j].cluster ) && mod->leafs[j].contents == CONTENTS_EMPTY ) return true; } } @@ -866,7 +1208,7 @@ Mod_SampleSizeForFace return the current lightmap resolution per face ================== */ -int Mod_SampleSizeForFace( msurface_t *surf ) +int Mod_SampleSizeForFace( const msurface_t *surf ) { if( !surf || !surf->texinfo ) return LM_SAMPLE_SIZE; @@ -918,22 +1260,35 @@ Mod_GetFaceContents determine face contents by name ================== */ -static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge ) +static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge, const dbspmodel_t *bmod ) { - int lindex; - medge_t *edge; + int lindex = mod->surfedges[surfedge]; - lindex = mod->surfedges[surfedge]; - - if( lindex > 0 ) + if( bmod->version == QBSP2_VERSION ) { - edge = &mod->edges[lindex]; - return &mod->vertexes[edge->v[0]]; + if( lindex > 0 ) + { + medge32_t *edge = &mod->edges32[lindex]; + return &mod->vertexes[edge->v[0]]; + } + else + { + medge32_t *edge = &mod->edges32[-lindex]; + return &mod->vertexes[edge->v[1]]; + } } else { - edge = &mod->edges[-lindex]; - return &mod->vertexes[edge->v[1]]; + if( lindex > 0 ) + { + medge16_t *edge = &mod->edges16[lindex]; + return &mod->vertexes[edge->v[0]]; + } + else + { + medge16_t *edge = &mod->edges16[-lindex]; + return &mod->vertexes[edge->v[1]]; + } } } @@ -1018,7 +1373,7 @@ Mod_CalcSurfaceExtents Fills in surf->texturemins[] and surf->extents[] ================= */ -static void Mod_CalcSurfaceExtents( msurface_t *surf ) +static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod ) { // this place is VERY critical to precision // keep it as float, don't use double, because it causes issues with lightmap @@ -1027,7 +1382,6 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) int bmins[2], bmaxs[2]; int i, j, e, sample_size; mextrasurf_t *info = surf->info; - int facenum = surf - loadmodel->surfaces; mtexinfo_t *tex; mvertex_t *v; @@ -1041,13 +1395,21 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) for( i = 0; i < surf->numedges; i++ ) { - e = loadmodel->surfedges[surf->firstedge + i]; + e = mod->surfedges[surf->firstedge + i]; - if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) - Host_Error( "Mod_CalcSurfaceExtents: bad edge\n" ); + if( e >= mod->numedges || e <= -mod->numedges ) + Host_Error( "%s: bad edge\n", __func__ ); - if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; - else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + if( bmod->version == QBSP2_VERSION ) + { + if( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]]; + else v = &mod->vertexes[mod->edges32[-e].v[1]]; + } + else + { + if( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]]; + else v = &mod->vertexes[mod->edges16[-e].v[1]]; + } for( j = 0; j < 2; j++ ) { @@ -1101,7 +1463,7 @@ Mod_CalcSurfaceBounds fills in surf->mins and surf->maxs ================= */ -static void Mod_CalcSurfaceBounds( msurface_t *surf ) +static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod ) { int i, e; mvertex_t *v; @@ -1110,13 +1472,21 @@ static void Mod_CalcSurfaceBounds( msurface_t *surf ) for( i = 0; i < surf->numedges; i++ ) { - e = loadmodel->surfedges[surf->firstedge + i]; + e = mod->surfedges[surf->firstedge + i]; - if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) - Host_Error( "Mod_CalcSurfaceBounds: bad edge\n" ); + if( e >= mod->numedges || e <= -mod->numedges ) + Host_Error( "%s: bad edge\n", __func__ ); - if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; - else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + if( bmod->version == QBSP2_VERSION ) + { + if( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]]; + else v = &mod->vertexes[mod->edges32[-e].v[1]]; + } + else + { + if( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]]; + else v = &mod->vertexes[mod->edges16[-e].v[1]]; + } AddPointToBounds( v->position, surf->info->mins, surf->info->maxs ); } @@ -1128,7 +1498,7 @@ static void Mod_CalcSurfaceBounds( msurface_t *surf ) Mod_CreateFaceBevels ================= */ -static void Mod_CreateFaceBevels( msurface_t *surf ) +static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod ) { vec3_t delta, edgevec; byte *facebevel; @@ -1144,7 +1514,7 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) else contents = CONTENTS_SOLID; size = sizeof( mfacebevel_t ) + surf->numedges * sizeof( mplane_t ); - facebevel = (byte *)Mem_Calloc( loadmodel->mempool, size ); + facebevel = (byte *)Mem_Calloc( mod->mempool, size ); fb = (mfacebevel_t *)facebevel; facebevel += sizeof( mfacebevel_t ); fb->edges = (mplane_t *)facebevel; @@ -1161,8 +1531,8 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) { mplane_t *dest = &fb->edges[i]; - v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i ); - v1 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + (i + 1) % surf->numedges ); + v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod ); + v1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges, bmod ); VectorSubtract( v1->position, v0->position, edgevec ); CrossProduct( faceNormal, edgevec, dest->normal ); VectorNormalize( dest->normal ); @@ -1176,7 +1546,7 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) // compute face radius for( i = 0; i < surf->numedges; i++ ) { - v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i ); + v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod ); VectorSubtract( v0->position, fb->origin, delta ); radius = DotProduct( delta, delta ); fb->radius = Q_max( radius, fb->radius ); @@ -1188,13 +1558,15 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) Mod_SetParent ================= */ -static void Mod_SetParent( mnode_t *node, mnode_t *parent ) +static void Mod_SetParent( model_t *mod, mnode_t *node, mnode_t *parent ) { node->parent = parent; - if( node->contents < 0 ) return; // it's leaf - Mod_SetParent( node->children[0], node ); - Mod_SetParent( node->children[1], node ); + if( node->contents < 0 ) + return; // it's leaf + + Mod_SetParent( mod, node_child( node, 0, mod ), node ); + Mod_SetParent( mod, node_child( node, 1, mod ), node ); } /* @@ -1202,46 +1574,53 @@ static void Mod_SetParent( mnode_t *node, mnode_t *parent ) CountClipNodes_r ================== */ -static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum ) +static void CountClipNodes16_r( mclipnode16_t *src, hull_t *hull, int nodenum ) { // leaf? if( nodenum < 0 ) return; - if( hull->lastclipnode == MAX_MAP_CLIPNODES ) - Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" ); + if( hull->lastclipnode == MAX_MAP_CLIPNODES_HLBSP ) + Host_Error( "%s: MAX_MAP_CLIPNODES_HLBSP limit exceeded\n", __func__ ); hull->lastclipnode++; - CountClipNodes_r( src, hull, src[nodenum].children[0] ); - CountClipNodes_r( src, hull, src[nodenum].children[1] ); + CountClipNodes16_r( src, hull, src[nodenum].children[0] ); + CountClipNodes16_r( src, hull, src[nodenum].children[1] ); } -/* -================== -CountClipNodes32_r -================== -*/ -static void CountClipNodes32_r( dclipnode32_t *src, hull_t *hull, int nodenum ) +static void CountClipNodes32_r( mclipnode32_t *src, hull_t *hull, int nodenum ) { // leaf? if( nodenum < 0 ) return; - if( hull->lastclipnode == MAX_MAP_CLIPNODES ) - Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" ); + if( hull->lastclipnode == MAX_MAP_CLIPNODES_BSP2 ) + Host_Error( "%s: MAX_MAP_CLIPNODES_BSP2 limit exceeded\n", __func__ ); hull->lastclipnode++; CountClipNodes32_r( src, hull, src[nodenum].children[0] ); CountClipNodes32_r( src, hull, src[nodenum].children[1] ); } +static void CountDClipNodes_r( dclipnode32_t *src, hull_t *hull, int nodenum, const int max_clipnodes ) +{ + // leaf? + if( nodenum < 0 ) return; + + if( hull->lastclipnode == max_clipnodes ) + Host_Error( "%s: MAX_MAP_CLIPNODES (%d) limit exceeded\n", __func__, max_clipnodes ); + hull->lastclipnode++; + + CountDClipNodes_r( src, hull, src[nodenum].children[0], max_clipnodes ); + CountDClipNodes_r( src, hull, src[nodenum].children[1], max_clipnodes ); +} + /* ================== RemapClipNodes_r ================== */ -static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum ) +static int RemapClipNodes_r( dbspmodel_t *bmod, dclipnode32_t *srcnodes, hull_t *hull, int nodenum ) { - dclipnode32_t *src; - mclipnode_t *out; + dclipnode32_t *src; int i, c; // leaf? @@ -1249,18 +1628,36 @@ static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum return nodenum; // emit a clipnode - if( hull->lastclipnode == MAX_MAP_CLIPNODES ) - Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" ); + if( bmod->version == QBSP2_VERSION ) + { + if( hull->lastclipnode == MAX_MAP_CLIPNODES_BSP2 ) + Host_Error( "%s: MAX_MAP_CLIPNODES_BSP2 limit exceeded\n", __func__ ); + } + else + { + if( hull->lastclipnode == MAX_MAP_CLIPNODES_HLBSP ) + Host_Error( "%s: MAX_MAP_CLIPNODES_HLBSP limit exceeded\n", __func__ ); + } + src = srcnodes + nodenum; c = hull->lastclipnode; - out = &hull->clipnodes[c]; hull->lastclipnode++; - out->planenum = src->planenum; - - for( i = 0; i < 2; i++ ) - out->children[i] = RemapClipNodes_r( srcnodes, hull, src->children[i] ); + if( bmod->version == QBSP2_VERSION ) + { + mclipnode32_t *out = &hull->clipnodes32[c]; + out->planenum = src->planenum; + for( i = 0; i < 2; i++ ) + out->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] ); + } + else + { + mclipnode16_t *out = &hull->clipnodes16[c]; + out->planenum = src->planenum; + for( i = 0; i < 2; i++ ) + out->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] ); + } return c; } @@ -1272,34 +1669,64 @@ Mod_MakeHull0 Duplicate the drawing hull structure as a clipping hull ================= */ -static void Mod_MakeHull0( void ) +static void Mod_MakeHull0( model_t *mod, const dbspmodel_t *bmod ) { - mnode_t *in, *child; - mclipnode_t *out; - hull_t *hull; - int i, j; - - hull = &loadmodel->hulls[0]; - hull->clipnodes = out = Mem_Malloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out )); - in = loadmodel->nodes; + hull_t *hull = &mod->hulls[0]; + int i; hull->firstclipnode = 0; - hull->lastclipnode = loadmodel->numnodes - 1; - hull->planes = loadmodel->planes; + hull->lastclipnode = mod->numnodes - 1; + hull->planes = mod->planes; - for( i = 0; i < loadmodel->numnodes; i++, out++, in++ ) + if( bmod->version == QBSP2_VERSION ) { - out->planenum = in->plane - loadmodel->planes; + mclipnode32_t *out; + mnode_t *in = mod->nodes; - for( j = 0; j < 2; j++ ) + hull->clipnodes32 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes32 )); + + for( i = 0; i < mod->numnodes; i++, out++, in++ ) { - child = in->children[j]; + int j; - if( child->contents < 0 ) - out->children[j] = child->contents; - else out->children[j] = child - loadmodel->nodes; + out->planenum = in->plane - mod->planes; + + for( j = 0; j < 2; j++ ) + { + mnode_t *child = node_child( in, j, mod ); + + if( child->contents < 0 ) + out->children[j] = child->contents; + else + out->children[j] = child - mod->nodes; + } } } + else + { + mclipnode16_t *out; + mnode_t *in = mod->nodes; + + hull->clipnodes16 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes16 )); + + for( i = 0; i < mod->numnodes; i++, out++, in++ ) + { + int j; + + out->planenum = in->plane - mod->planes; + + for( j = 0; j < 2; j++ ) + { + mnode_t *child = node_child( in, j, mod ); + + if( child->contents < 0 ) + out->children[j] = child->contents; + else + out->children[j] = child - mod->nodes; + } + } + } + } /* @@ -1307,20 +1734,9 @@ static void Mod_MakeHull0( void ) Mod_SetupHull ================= */ -static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool, int headnode, int hullnum ) +static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, int headnode, int hullnum, model_t *world ) { - hull_t *hull = &mod->hulls[hullnum]; - int count; - - // assume no hull - hull->firstclipnode = hull->lastclipnode = 0; - hull->planes = NULL; // hull is missed - - if(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 )) - return; // hull missed - - if( headnode >= mod->numclipnodes ) - return; // ZHLT weird empty hulls + hull_t *hull = &mod->hulls[hullnum]; switch( hullnum ) { @@ -1337,127 +1753,157 @@ static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool VectorCopy( host.player_maxs[1], hull->clip_maxs ); break; default: - Host_Error( "Mod_SetupHull: bad hull number %i\n", hullnum ); + Host_Error( "%s: bad hull number %i\n", __func__, hullnum ); break; } if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs )) return; // no hull specified - CountClipNodes32_r( bmod->clipnodes_out, hull, headnode ); - count = hull->lastclipnode; + // assume no hull + hull->firstclipnode = hull->lastclipnode = 0; + hull->planes = NULL; // hull is missed + + if( headnode >= mod->numclipnodes ) + return; // ZHLT weird empty hulls + + // bsp30ext allows for extended total amount of clipnodes, but the limit is still 16-bit per submodel + // therefore we need to remap them + // take a simpler route if we don't need clipnodes remapping + if( !bmod->isbsp30ext ) + { + hull->planes = mod->planes; + + // some map "optimizers" (you know who you are!) put -1 here + // ... and it's purposefully? encode CONTENTS_EMPTY sometimes + // but might cause out of bounds reads + hull->firstclipnode = headnode; + hull->lastclipnode = mod->numclipnodes - 1; + + // only allocate clipnodes array for the base model, only for first hull + if( mod == world && hullnum == 1 ) + { + int i; + + if( bmod->version == QBSP2_VERSION ) + { + hull->clipnodes32 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes32 ) * mod->numclipnodes ); + + for( i = 0; i < mod->numclipnodes; i++ ) + { + hull->clipnodes32[i].planenum = bmod->clipnodes_out[i].planenum; + hull->clipnodes32[i].children[0] = bmod->clipnodes_out[i].children[0]; + hull->clipnodes32[i].children[1] = bmod->clipnodes_out[i].children[1]; + } + } + else + { + hull->clipnodes16 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes16 ) * mod->numclipnodes ); + + for( i = 0; i < mod->numclipnodes; i++ ) + { + hull->clipnodes16[i].planenum = bmod->clipnodes_out[i].planenum; + hull->clipnodes16[i].children[0] = bmod->clipnodes_out[i].children[0]; + hull->clipnodes16[i].children[1] = bmod->clipnodes_out[i].children[1]; + } + } + } + else + { + if( bmod->version == QBSP2_VERSION ) + hull->clipnodes32 = world->hulls[1].clipnodes32; + else + hull->clipnodes16 = world->hulls[1].clipnodes16; + } + + return; + } + + if(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 )) + return; // hull missed // fit array to real count - hull->clipnodes = (mclipnode_t *)Mem_Malloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode ); + if( bmod->version == QBSP2_VERSION ) + { + CountDClipNodes_r( bmod->clipnodes_out, hull, headnode, MAX_MAP_CLIPNODES_BSP2 ); + hull->clipnodes32 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes32 ) * hull->lastclipnode ); + } + else + { + CountDClipNodes_r( bmod->clipnodes_out, hull, headnode, MAX_MAP_CLIPNODES_HLBSP ); + hull->clipnodes16 = Mem_Malloc( world->mempool, sizeof( *hull->clipnodes16 ) * hull->lastclipnode ); + } + hull->planes = mod->planes; // share planes hull->lastclipnode = 0; // restart counting - // remap clipnodes to 16-bit indexes - RemapClipNodes_r( bmod->clipnodes_out, hull, headnode ); + RemapClipNodes_r( bmod, bmod->clipnodes_out, hull, headnode ); // remap clipnodes to 16-bit indexes } -/* -================= -Mod_LoadColoredLighting -================= -*/ -static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) +static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_size, color24 **out, size_t *outsize ) { - char modelname[64]; - char path[64]; - int iCompare; - fs_offset_t litdatasize; - byte *in; + char modelname[64], path[64]; + int iCompare; + fs_offset_t datasize; + file_t *f; + uint hdr[2]; - COM_FileBase( loadmodel->name, modelname ); - Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname ); + COM_FileBase( mod->name, modelname, sizeof( modelname )); + Q_snprintf( path, sizeof( path ), "maps/%s.%s", modelname, ext ); - // make sure what deluxemap is actual - if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) + if( !pfnCompareFileTime( path, mod->name, &iCompare )) return false; if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg Con_Printf( S_WARN "%s probably is out of date\n", path ); - in = FS_LoadFile( path, &litdatasize, false ); + f = FS_Open( path, "rb", false ); - Assert( in != NULL ); - - if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION ) + if( !f ) { - Mem_Free( in ); + Con_Printf( S_ERROR "couldn't load %s\n", path ); return false; } + datasize = FS_FileLength( f ); + // skip header bytes - litdatasize -= 8; + datasize -= 8; - if( litdatasize != ( bmod->lightdatasize * 3 )) + if( datasize != expected_size ) { - Con_Printf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, litdatasize, bmod->lightdatasize * 3 ); - Mem_Free( in ); - return false; + Con_Printf( S_ERROR "%s has mismatched size (%li should be %zu)\n", path, (long)datasize, expected_size ); + goto cleanup_and_error; } - loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, litdatasize ); - memcpy( loadmodel->lightdata, in + 8, litdatasize ); - SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); - bmod->lightdatasize = litdatasize; - Mem_Free( in ); + if( FS_Read( f, hdr, sizeof( hdr )) != sizeof( hdr )) + { + Con_Printf( S_ERROR "failed reading header from %s\n", path ); + goto cleanup_and_error; + } + if( LittleLong( hdr[0] ) != IDDELUXEMAPHEADER ) + { + Con_Printf( S_ERROR "%s is corrupted\n", path ); + goto cleanup_and_error; + } + + if( LittleLong( hdr[1] ) != DELUXEMAP_VERSION ) + { + Con_Printf( S_ERROR "has %s mismatched version (%u should be %u)\n", path, LittleLong( hdr[1] ), DELUXEMAP_VERSION ); + goto cleanup_and_error; + } + + *out = Mem_Malloc( mod->mempool, datasize ); + *outsize = datasize; + + FS_Read( f, *out, datasize ); + FS_Close( f ); return true; -} -/* -================= -Mod_LoadDeluxemap -================= -*/ -static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) -{ - char modelname[64]; - fs_offset_t deluxdatasize; - char path[64]; - int iCompare; - byte *in; - - if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) - return; - - COM_FileBase( loadmodel->name, modelname ); - Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname ); - - // make sure what deluxemap is actual - if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) - return; - - if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg - Con_Printf( S_WARN "%s probably is out of date\n", path ); - - in = FS_LoadFile( path, &deluxdatasize, false ); - - Assert( in != NULL ); - - if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION ) - { - Mem_Free( in ); - return; - } - - // skip header bytes - deluxdatasize -= 8; - - if( deluxdatasize != bmod->lightdatasize ) - { - Con_Reportf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, deluxdatasize, bmod->lightdatasize ); - Mem_Free( in ); - return; - } - - bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, deluxdatasize ); - memcpy( bmod->deluxedata_out, in + 8, deluxdatasize ); - bmod->deluxdatasize = deluxdatasize; - Mem_Free( in ); +cleanup_and_error: + FS_Close( f ); + return false; } /* @@ -1468,38 +1914,35 @@ duplicate the basic information for embedded submodels ================= */ -static void Mod_SetupSubmodels( dbspmodel_t *bmod ) +static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod ) { - qboolean colored = false; - poolhandle_t mempool; - char *ents; - model_t *mod; - dmodel_t *bm; - int i, j; + const qboolean colored = FBitSet( mod->flags, MODEL_COLORED_LIGHTING ) ? true : false; + const qboolean qbsp2 = FBitSet( mod->flags, MODEL_QBSP2 ) ? true : false; + const char *name = mod->name; + model_t *world = mod; // submodels might want to share hulls + int i; - ents = loadmodel->entities; - mempool = loadmodel->mempool; - if( FBitSet( loadmodel->flags, MODEL_COLORED_LIGHTING )) - colored = true; - mod = loadmodel; - - loadmodel->numframes = 2; // regular and alternate animation + mod->numframes = 2; // regular and alternate animation // set up the submodels for( i = 0; i < mod->numsubmodels; i++ ) { - bm = &mod->submodels[i]; + dmodel_t *bm = &mod->submodels[i]; + int j; // hull 0 is just shared across all bmodels mod->hulls[0].firstclipnode = bm->headnode[0]; mod->hulls[0].lastclipnode = bm->headnode[0]; // need to be real count // counting a real number of clipnodes per each submodel - CountClipNodes_r( mod->hulls[0].clipnodes, &mod->hulls[0], bm->headnode[0] ); + if( bmod->version == QBSP2_VERSION ) + CountClipNodes32_r( mod->hulls[0].clipnodes32, &mod->hulls[0], bm->headnode[0] ); + else + CountClipNodes16_r( mod->hulls[0].clipnodes16, &mod->hulls[0], bm->headnode[0] ); // but hulls1-3 is build individually for a each given submodel for( j = 1; j < MAX_MAP_HULLS; j++ ) - Mod_SetupHull( bmod, mod, mempool, bm->headnode[j], j ); + Mod_SetupHull( bmod, mod, bm->headnode[j], j, world ); mod->firstmodelsurface = bm->firstface; mod->nummodelsurfaces = bm->numfaces; @@ -1513,17 +1956,21 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) // this bit will be shared between all the submodels include worldmodel if( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING ); + if( qbsp2 ) SetBits( mod->flags, MODEL_QBSP2 ); if( i != 0 ) { - Mod_FindModelOrigin( ents, va( "*%i", i ), bm->origin ); + char temp[MAX_VA_STRING]; + + Q_snprintf( temp, sizeof( temp ), "*%i", i ); + Mod_FindModelOrigin( world->entities, temp, bm->origin ); // mark models that have origin brushes if( !VectorIsNull( bm->origin )) SetBits( mod->flags, MODEL_HAS_ORIGIN ); #ifdef HACKS_RELATED_HLMODS // c2a1 doesn't have origin brush it's just placed at center of the level - if( !Q_stricmp( loadmodel->name, "maps/c2a1.bsp" ) && ( i == 11 )) + if( !Q_stricmp( name, "maps/c2a1.bsp" ) && ( i == 11 )) SetBits( mod->flags, MODEL_HAS_ORIGIN ); #endif } @@ -1546,14 +1993,15 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) if( i < mod->numsubmodels - 1 ) { char name[8]; + model_t *submod; // duplicate the basic information Q_snprintf( name, sizeof( name ), "*%i", i + 1 ); - loadmodel = Mod_FindName( name, true ); - *loadmodel = *mod; - Q_strncpy( loadmodel->name, name, sizeof( loadmodel->name )); - loadmodel->mempool = 0; - mod = loadmodel; + submod = Mod_FindName( name, true ); + *submod = *mod; + Q_strncpy( submod->name, name, sizeof( submod->name )); + submod->mempool = 0; + mod = submod; } } @@ -1573,17 +2021,17 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) Mod_LoadSubmodels ================= */ -static void Mod_LoadSubmodels( dbspmodel_t *bmod ) +static void Mod_LoadSubmodels( model_t *mod, dbspmodel_t *bmod ) { dmodel_t *in, *out; int oldmaxfaces; int i, j; // allocate extradata for each dmodel_t - out = Mem_Malloc( loadmodel->mempool, bmod->numsubmodels * sizeof( *out )); + out = Mem_Malloc( mod->mempool, bmod->numsubmodels * sizeof( *out )); - loadmodel->numsubmodels = bmod->numsubmodels; - loadmodel->submodels = out; + mod->numsubmodels = bmod->numsubmodels; + mod->submodels = out; in = bmod->submodels; if( bmod->isworld ) @@ -1626,31 +2074,60 @@ static void Mod_LoadSubmodels( dbspmodel_t *bmod ) } } +static int Mod_LoadEntities_splitstr_handler( char *prev, char *next, void *userdata ) +{ + const char *wad; + wadlist_t *wadlist = userdata; + + *next = '\0'; + + if( !COM_CheckStringEmpty( prev )) + return 0; + + COM_FixSlashes( prev ); + wad = COM_FileWithoutPath( prev ); + + if( Q_stricmp( COM_FileExtension( wad ), "wad" )) + return 0; + + // make sure that wad is really exist + if( FS_FileExists( wad, false )) + { + int num = wadlist->count++; + Q_strncpy( wadlist->wadnames[num], wad, sizeof( wadlist->wadnames[0] )); + wadlist->wadusage[num] = 0; + } + + if( wadlist->count >= ARRAYSIZE( wadlist->wadnames )) + return 1; + + return 0; +} + /* ================= Mod_LoadEntities ================= */ -static void Mod_LoadEntities( dbspmodel_t *bmod ) +static void Mod_LoadEntities( model_t *mod, dbspmodel_t *bmod ) { - byte *entpatch = NULL; - char token[MAX_TOKEN]; - char wadstring[MAX_TOKEN]; - string keyname; - char *pfile; + byte *entpatch = NULL; + char token[MAX_TOKEN]; + string keyname; + char *pfile; if( bmod->isworld ) { - char entfilename[MAX_QPATH]; + char entfilename[MAX_QPATH]; fs_offset_t entpatchsize; - size_t ft1, ft2; + int ft1, ft2; - // world is check for entfile too - Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename )); - COM_ReplaceExtension( entfilename, ".ent" ); + // if world check for entfile too + Q_strncpy( entfilename, mod->name, sizeof( entfilename )); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); - // make sure what entity patch is never than bsp - ft1 = FS_FileTime( loadmodel->name, false ); + // make sure that entity patch is never than bsp + ft1 = FS_FileTime( mod->name, false ); ft2 = FS_FileTime( entfilename, true ); if( ft2 != -1 ) @@ -1668,68 +2145,52 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) } } - // make sure what we really has terminator - loadmodel->entities = Mem_Calloc( loadmodel->mempool, bmod->entdatasize + 1 ); - memcpy( loadmodel->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool - if( entpatch ) Mem_Free( entpatch ); // release entpatch if present - if( !bmod->isworld ) return; + // make sure that we really have null terminator + mod->entities = Mem_Malloc( mod->mempool, bmod->entdatasize + 1 ); + memcpy( mod->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool + mod->entities[bmod->entdatasize] = 0; - pfile = (char *)loadmodel->entities; + if( entpatch ) + { + Mem_Free( entpatch ); // release entpatch if present + entpatch = NULL; + } + + if( !bmod->isworld ) + return; + + pfile = (char *)mod->entities; world.generator[0] = '\0'; world.compiler[0] = '\0'; world.message[0] = '\0'; - bmod->wadlist.count = 0; + world.wadlist.count = 0; // parse all the wads for loading textures in right ordering while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( token[0] != '{' ) - Host_Error( "Mod_LoadEntities: found %s when expecting {\n", token ); + Host_Error( "%s: found %s when expecting {\n", __func__, token ); while( 1 ) { // parse key if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL ) - Host_Error( "Mod_LoadEntities: EOF without closing brace\n" ); - if( token[0] == '}' ) break; // end of desc + Host_Error( "%s: EOF without closing brace\n", __func__ ); + + if( token[0] == '}' ) + break; // end of desc Q_strncpy( keyname, token, sizeof( keyname )); // parse value if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL ) - Host_Error( "Mod_LoadEntities: EOF without closing brace\n" ); + Host_Error( "%s: EOF without closing brace\n", __func__ ); if( token[0] == '}' ) - Host_Error( "Mod_LoadEntities: closing brace without data\n" ); + Host_Error( "%s: closing brace without data\n", __func__ ); if( !Q_stricmp( keyname, "wad" )) - { - char *pszWadFile; - - Q_strncpy( wadstring, token, MAX_TOKEN - 2 ); - wadstring[MAX_TOKEN - 2] = 0; - - if( !Q_strchr( wadstring, ';' )) - Q_strcat( wadstring, ";" ); - - // parse wad pathes - for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" )) - { - COM_FixSlashes( pszWadFile ); - COM_FileBase( pszWadFile, token ); - - // make sure what wad is really exist - if( FS_FileExists( va( "%s.wad", token ), false )) - { - int num = bmod->wadlist.count++; - Q_strncpy( bmod->wadlist.wadnames[num], token, sizeof( bmod->wadlist.wadnames[0] )); - bmod->wadlist.wadusage[num] = 0; - } - - if( bmod->wadlist.count >= MAX_MAP_WADS ) - break; // too many wads... - } - } + Q_splitstr( token, ';', &world.wadlist, Mod_LoadEntities_splitstr_handler ); else if( !Q_stricmp( keyname, "message" )) Q_strncpy( world.message, token, sizeof( world.message )); else if( !Q_stricmp( keyname, "compiler" ) || !Q_stricmp( keyname, "_compiler" )) @@ -1746,15 +2207,15 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) Mod_LoadPlanes ================= */ -static void Mod_LoadPlanes( dbspmodel_t *bmod ) +static void Mod_LoadPlanes( model_t *mod, dbspmodel_t *bmod ) { dplane_t *in; mplane_t *out; int i, j; in = bmod->planes; - loadmodel->planes = out = Mem_Malloc( loadmodel->mempool, bmod->numplanes * sizeof( *out )); - loadmodel->numplanes = bmod->numplanes; + mod->planes = out = Mem_Malloc( mod->mempool, bmod->numplanes * sizeof( *out )); + mod->numplanes = bmod->numplanes; for( i = 0; i < bmod->numplanes; i++, in++, out++ ) { @@ -1780,15 +2241,15 @@ static void Mod_LoadPlanes( dbspmodel_t *bmod ) Mod_LoadVertexes ================= */ -static void Mod_LoadVertexes( dbspmodel_t *bmod ) +static void Mod_LoadVertexes( model_t *mod, dbspmodel_t *bmod ) { dvertex_t *in; mvertex_t *out; int i; in = bmod->vertexes; - out = loadmodel->vertexes = Mem_Malloc( loadmodel->mempool, bmod->numvertexes * sizeof( mvertex_t )); - loadmodel->numvertexes = bmod->numvertexes; + out = mod->vertexes = Mem_Malloc( mod->mempool, bmod->numvertexes * sizeof( mvertex_t )); + mod->numvertexes = bmod->numvertexes; if( bmod->isworld ) ClearBounds( world.mins, world.maxs ); @@ -1816,17 +2277,17 @@ static void Mod_LoadVertexes( dbspmodel_t *bmod ) Mod_LoadEdges ================= */ -static void Mod_LoadEdges( dbspmodel_t *bmod ) +static void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod ) { - medge_t *out; int i; - loadmodel->edges = out = Mem_Malloc( loadmodel->mempool, bmod->numedges * sizeof( medge_t )); - loadmodel->numedges = bmod->numedges; + mod->numedges = bmod->numedges; if( bmod->version == QBSP2_VERSION ) { - dedge32_t *in = (dedge32_t *)bmod->edges32; + dedge32_t *in = bmod->edges32; + medge32_t *out; + mod->edges32 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out )); for( i = 0; i < bmod->numedges; i++, in++, out++ ) { @@ -1836,7 +2297,9 @@ static void Mod_LoadEdges( dbspmodel_t *bmod ) } else { - dedge_t *in = (dedge_t *)bmod->edges; + dedge_t *in = bmod->edges; + medge16_t *out; + mod->edges16 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out )); for( i = 0; i < bmod->numedges; i++, in++, out++ ) { @@ -1851,11 +2314,11 @@ static void Mod_LoadEdges( dbspmodel_t *bmod ) Mod_LoadSurfEdges ================= */ -static void Mod_LoadSurfEdges( dbspmodel_t *bmod ) +static void Mod_LoadSurfEdges( model_t *mod, dbspmodel_t *bmod ) { - loadmodel->surfedges = Mem_Malloc( loadmodel->mempool, bmod->numsurfedges * sizeof( dsurfedge_t )); - memcpy( loadmodel->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t )); - loadmodel->numsurfedges = bmod->numsurfedges; + mod->surfedges = Mem_Malloc( mod->mempool, bmod->numsurfedges * sizeof( dsurfedge_t )); + memcpy( mod->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t )); + mod->numsurfedges = bmod->numsurfedges; } /* @@ -1863,339 +2326,600 @@ static void Mod_LoadSurfEdges( dbspmodel_t *bmod ) Mod_LoadMarkSurfaces ================= */ -static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) +static void Mod_LoadMarkSurfaces( model_t *mod, dbspmodel_t *bmod ) { msurface_t **out; int i; - loadmodel->marksurfaces = out = Mem_Malloc( loadmodel->mempool, bmod->nummarkfaces * sizeof( *out )); - loadmodel->nummarksurfaces = bmod->nummarkfaces; + mod->marksurfaces = out = Mem_Malloc( mod->mempool, bmod->nummarkfaces * sizeof( *out )); + mod->nummarksurfaces = bmod->nummarkfaces; if( bmod->version == QBSP2_VERSION ) { - dmarkface32_t *in = bmod->markfaces32; + const dmarkface32_t *in = bmod->markfaces32; - for( i = 0; i < bmod->nummarkfaces; i++, in++ ) + for( i = 0; i < bmod->nummarkfaces; i++ ) { - if( *in < 0 || *in >= loadmodel->numsurfaces ) - Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); - out[i] = loadmodel->surfaces + *in; + if( in[i] < 0 || in[i] >= mod->numsurfaces ) + Host_Error( "%s: bad surface number %i at %i (max %i) in '%s'\n", __func__, in[i], i, mod->numsurfaces, mod->name ); + out[i] = mod->surfaces + in[i]; } } else { - dmarkface_t *in = bmod->markfaces; + const dmarkface_t *in = bmod->markfaces; - for( i = 0; i < bmod->nummarkfaces; i++, in++ ) + for( i = 0; i < bmod->nummarkfaces; i++ ) { - if( *in < 0 || *in >= loadmodel->numsurfaces ) - Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); - out[i] = loadmodel->surfaces + *in; + // NOTE: some of the buggy compilers have written a broken BSP file + // with marksurface pointing at negative surface, for example darkf6.bsp + // and darkf26.bsp in darkfuture mod. GoldSrc straight up writes + // invalid pointer to a surface. Try to fix up these cases... + if( mod->numsurfaces <= INT16_MAX && (int16_t)in[i] < 0 ) + { + Con_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 ); + out[i] = mod->surfaces; + continue; + } + + if( in[i] < 0 || in[i] >= mod->numsurfaces ) + Host_Error( "%s: bad surface number %i at %i (max %i) in '%s'\n", __func__, in[i], i, mod->numsurfaces, mod->name ); + out[i] = mod->surfaces + in[i]; } } } +static qboolean Mod_LooksLikeWaterTexture( const char *name ) +{ + if(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' ) + return true; + + if( !Host_IsQuakeCompatible( )) + { + if( !Q_strncmp( name, "water", 5 ) || !Q_strnicmp( name, "laser", 5 )) + return true; + } + + return false; +} + +static void Mod_TextureReplacementReport( const char *modelname, const char *texname, const char *type, int gl_texturenum, const char *foundpath ) +{ + if( host_allow_materials.value != 2.0f ) + return; + + if( gl_texturenum > 0 ) // found and loaded successfully + Con_Printf( "Looking for %s:%s%s tex replacement..." S_GREEN "OK (%s)\n", modelname, texname, type, foundpath ); + else if( gl_texturenum < 0 ) // not found + Con_Printf( "Looking for %s:%s%s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, texname, type, foundpath ); + else // found but not loaded + Con_Printf( "Looking for %s:%s%s tex replacement..." S_RED "FAIL (%s)\n", modelname, texname, type, foundpath ); +} + +static qboolean Mod_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *texname, const char *type ) +{ + const char *subdirs[] = { modelname, "common" }; + int i; + + for( i = 0; i < ARRAYSIZE( subdirs ); i++ ) + { + if( Q_snprintf( out, size, "materials/%s/%s%s.tga", subdirs[i], texname, type ) < 0 ) + continue; // truncated name + + if( g_fsapi.FileExists( out, false )) + return true; // found, load it + } + + Mod_TextureReplacementReport( modelname, texname, type, -1, "not found" ); + + return false; +} + +static void Mod_InitSkyClouds( model_t *mod, const mip_t *mt, texture_t *tx, qboolean custom_palette ) +{ +#if !XASH_DEDICATED + rgbdata_t r_temp, *r_sky; + uint *trans, *rgba; + uint transpix; + int r, g, b; + int i, j, p; + string texname; + int solidskyTexture = 0, alphaskyTexture = 0; + + if( !ref.initialized ) + return; + + if( Mod_AllowMaterials( )) + { + rgbdata_t *pic; + + if( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, "_solid" )) + { + pic = FS_LoadImage( texname, NULL, 0 ); + if( pic ) + { + // need to do rename texture to properly cleanup these textures on reload + solidskyTexture = GL_LoadTextureInternal( "solid_sky", pic, TF_NOMIPMAP ); + Mod_TextureReplacementReport( mod->name, mt->name, "_solid", solidskyTexture, texname ); + FS_FreeImage( pic ); + } + } + + if( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, "_alpha" )) + { + pic = FS_LoadImage( texname, NULL, 0 ); + if( pic ) + { + alphaskyTexture = GL_LoadTextureInternal( "alpha_sky", pic, TF_NOMIPMAP ); + Mod_TextureReplacementReport( mod->name, mt->name, "_alpha", alphaskyTexture, texname ); + FS_FreeImage( pic ); + } + } + + if( !solidskyTexture || !alphaskyTexture ) + { + ref.dllFuncs.GL_FreeTexture( solidskyTexture ); + ref.dllFuncs.GL_FreeTexture( alphaskyTexture ); + } + else goto done; // replacements found, notify the renderer and exit + } + + Q_snprintf( texname, sizeof( texname ), "%s%s.mip", ( mt->offsets[0] > 0 ) ? "#" : "", tx->name ); + + if( mt->offsets[0] > 0 ) + { + size_t size = sizeof( mip_t ) + (( mt->width * mt->height * 85 ) >> 6 ); + + if( custom_palette ) + size += sizeof( short ) + 768; + + r_sky = FS_LoadImage( texname, (byte *)mt, size ); + } + else + { + // okay loading it from wad + r_sky = FS_LoadImage( texname, NULL, 0 ); + } + + if( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 ) + { + Con_Printf( S_ERROR "%s: unable to load sky texture %s\n", __func__, tx->name ); + + if( r_sky ) + FS_FreeImage( r_sky ); + + return; + } + + // make an average value for the back to avoid + // a fringe on the top level + trans = Mem_Malloc( host.mempool, r_sky->height * r_sky->height * sizeof( *trans )); + r = g = b = 0; + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j + r_sky->height]; + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + r += ((byte *)rgba)[0]; + g += ((byte *)rgba)[1]; + b += ((byte *)rgba)[2]; + } + } + + ((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[3] = 0; + + // build a temporary image + r_temp = *r_sky; + r_temp.width = r_sky->width >> 1; + r_temp.height = r_sky->height; + r_temp.type = PF_RGBA_32; + r_temp.flags = IMAGE_HAS_COLOR; + r_temp.size = r_temp.width * r_temp.height * 4; + r_temp.buffer = (byte *)trans; + r_temp.palette = NULL; + + // load it in + solidskyTexture = GL_LoadTextureInternal( "solid_sky", &r_temp, TF_NOMIPMAP ); + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j]; + + if( p == 0 ) + { + trans[(i * r_sky->height) + j] = transpix; + } + else + { + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + } + } + } + + r_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + + // load it in + alphaskyTexture = GL_LoadTextureInternal( "alpha_sky", &r_temp, TF_NOMIPMAP ); + + // clean up + FS_FreeImage( r_sky ); + Mem_Free( trans ); + + if( !solidskyTexture || !alphaskyTexture ) + { + ref.dllFuncs.GL_FreeTexture( solidskyTexture ); + ref.dllFuncs.GL_FreeTexture( alphaskyTexture ); + return; + } + +done: + // notify the renderer + ref.dllFuncs.R_SetSkyCloudsTextures( solidskyTexture, alphaskyTexture ); + + if( solidskyTexture && alphaskyTexture ) + SetBits( world.flags, FWORLD_SKYSPHERE ); +#endif // !XASH_DEDICATED +} + +static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureIndex ) +{ + uint32_t txFlags = 0; + char texpath[MAX_VA_STRING]; + char safemtname[16]; // only for external textures + qboolean load_external = false; + + // don't load texture data on dedicated server, as there is no renderer. + // but count the wadusage for automatic precache + texture_t *texture = mod->textures[textureIndex]; + const mip_t *mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + const qboolean usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( mod, bmod, textureIndex ); + const qboolean iswater = Mod_LooksLikeWaterTexture( mipTex->name ); + + // check for multi-layered sky texture (quake1 specific) + if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 ) + { + Mod_InitSkyClouds( mod, mipTex, texture, usesCustomPalette ); // load quake sky + return; + } + + // FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too + // but there is no facility for this yet + if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' ) + SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing + + // check if this is water to keep the source texture and expand it to RGBA (so ripple effect works) + if( iswater ) + SetBits( txFlags, TF_KEEP_SOURCE | TF_EXPAND_SOURCE ); + + // Texture loading order: + // 1. HQ from disk + // 2. From WAD + // 3. Internal from map + + texture->gl_texturenum = 0; + Q_strncpy( safemtname, mipTex->name, sizeof( safemtname )); + if( safemtname[0] == '*' ) + safemtname[0] = '!'; // replace unexpected symbol + + if( Mod_AllowMaterials( )) + { +#if !XASH_DEDICATED + if( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, "" )) + { + texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags ); + load_external = texture->gl_texturenum != 0; + Mod_TextureReplacementReport( mod->name, safemtname, "", texture->gl_texturenum, texpath ); + } +#endif // !XASH_DEDICATED + } + + // Try WAD texture (force while r_wadtextures is 1) + if( !texture->gl_texturenum && (( r_wadtextures.value && world.wadlist.count > 0 ) || mipTex->offsets[0] <= 0 )) + { + rgbdata_t *pic = NULL; + int wadIndex = Mod_LoadTextureFromWadList( &world.wadlist, mipTex->name, Host_IsDedicated() ? NULL : &pic, texpath, sizeof( texpath )); + + if( wadIndex >= 0 ) + { +#if !XASH_DEDICATED + if( !Host_IsDedicated( ) && pic != NULL ) + { + texture->gl_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texpath, pic, txFlags, false ); + FS_FreeImage( pic ); + } +#endif // !XASH_DEDICATED + world.wadlist.wadusage[wadIndex]++; + } + } + +#if !XASH_DEDICATED + if( Host_IsDedicated( )) + return; + + // WAD failed, so use internal texture (if present) + if( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 ) + { + char texName[64]; + const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette ); + + Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name ); + texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags ); + } + + // If texture is completely missed: + if( texture->gl_texturenum == 0 ) + { + Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex->name ); + texture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); + } + + texture->fb_texturenum = 0; + + // Check for luma texture + // a1ba: ignore for water because fb_texturenum will be used to store ripple texture + if( iswater ) + return; + + if( load_external ) // external textures will not have TF_HAS_LUMA flag because it set only from WAD images loader + { + if( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, "_luma" )) + { + texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, TF_MAKELUMA ); + Mod_TextureReplacementReport( mod->name, safemtname, "_luma", texture->fb_texturenum, texpath ); + } + } + + if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, texture->gl_texturenum ), TF_HAS_LUMA ) && !texture->fb_texturenum ) + { + char texName[64]; + + Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name ); + + if( mipTex->offsets[0] > 0 ) + { + const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette ); + texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA ); + } + else + { + int wadIndex; + rgbdata_t *pic = NULL; + + // NOTE: We can't load the _luma texture from the WAD as normal because it + // doesn't exist there. The original texture is already loaded, but cannot be modified. + // Instead, load the original texture again and convert it to luma. + wadIndex = Mod_LoadTextureFromWadList( &world.wadlist, texture->name, &pic, NULL, 0 ); + + if( wadIndex >= 0 && pic != NULL ) + { + // OK, loading it from wad or hi-res(??) version + texture->fb_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texName, pic, TF_MAKELUMA, false ); + FS_FreeImage( pic ); + world.wadlist.wadusage[wadIndex]++; + } + } + } +#endif // !XASH_DEDICATED +} + +static void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex ) +{ + texture_t *texture; + mip_t *mipTex; + + if( textureIndex < 0 || textureIndex >= mod->numtextures ) + return; + + mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + + if( !mipTex ) + { + // No data for this texture. + // Create default texture (some mods require this). + Mod_CreateDefaultTexture( mod, &mod->textures[textureIndex] ); + return; + } + + if( mipTex->name[0] == '\0' ) + Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex ); + + texture = (texture_t *)Mem_Calloc( mod->mempool, sizeof( *texture )); + mod->textures[textureIndex] = texture; + + // Ensure texture name is lowercase. + Q_strnlwr( mipTex->name, texture->name, sizeof( texture->name )); + + texture->width = mipTex->width; + texture->height = mipTex->height; + + Mod_LoadTextureData( mod, bmod, textureIndex ); +} + +static void Mod_LoadAllTextures( model_t *mod, dbspmodel_t *bmod ) +{ + int i; + + for( i = 0; i < mod->numtextures; i++ ) + Mod_LoadTexture( mod, bmod, i ); +} + +static void Mod_SequenceAnimatedTexture( model_t *mod, int baseTextureIndex ) +{ + texture_t *anims[10]; + texture_t *altanims[10]; + texture_t *baseTexture; + int max = 0; + int altmax = 0; + int candidateIndex; + + if( baseTextureIndex < 0 || baseTextureIndex >= mod->numtextures ) + return; + + baseTexture = mod->textures[baseTextureIndex]; + + if( !Mod_NameImpliesTextureIsAnimated( baseTexture )) + return; + + // Already sequenced + if( baseTexture->anim_next ) + return; + + // find the number of frames in the animation + memset( anims, 0, sizeof( anims )); + memset( altanims, 0, sizeof( altanims )); + + if( baseTexture->name[1] >= '0' && baseTexture->name[1] <= '9' ) + { + // This texture is a standard animation frame. + int frameIndex = (int)baseTexture->name[1] - (int)'0'; + + anims[frameIndex] = baseTexture; + max = frameIndex + 1; + } + else + { + // This texture is an alternate animation frame. + int frameIndex = (int)baseTexture->name[1] - (int)'a'; + + altanims[frameIndex] = baseTexture; + altmax = frameIndex + 1; + } + + // Now search the rest of the textures to find all other frames. + for( candidateIndex = baseTextureIndex + 1; candidateIndex < mod->numtextures; candidateIndex++ ) + { + texture_t *altTexture = mod->textures[candidateIndex]; + + if( !Mod_NameImpliesTextureIsAnimated( altTexture )) + continue; + + // This texture is animated, but is it part of the same group as + // the original texture we encountered? Check that the rest of + // the name matches the original (both will be valid for at least + // string index 2). + if( Q_strcmp( altTexture->name + 2, baseTexture->name + 2 ) != 0 ) + continue; + + if( altTexture->name[1] >= '0' && altTexture->name[1] <= '9' ) + { + // This texture is a standard frame. + int frameIndex = (int)altTexture->name[1] - (int)'0'; + anims[frameIndex] = altTexture; + + if( frameIndex >= max ) + max = frameIndex + 1; + } + else + { + // This texture is an alternate frame. + int frameIndex = (int)altTexture->name[1] - (int)'a'; + altanims[frameIndex] = altTexture; + + if( frameIndex >= altmax ) + altmax = frameIndex + 1; + } + } + + // Link all standard animated frames together. + for( candidateIndex = 0; candidateIndex < max; candidateIndex++ ) + { + texture_t *tex = anims[candidateIndex]; + + if( !tex ) + { + Con_Printf( S_ERROR "%s: missing frame %i of animated texture \"%s\"\n", + __func__, + candidateIndex, + baseTexture->name ); + + baseTexture->anim_total = 0; + break; + } + + tex->anim_total = max * ANIM_CYCLE; + tex->anim_min = candidateIndex * ANIM_CYCLE; + tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE; + tex->anim_next = anims[( candidateIndex + 1 ) % max]; + + if( altmax > 0 ) + tex->alternate_anims = altanims[0]; + } + + // Link all alternate animated frames together. + for( candidateIndex = 0; candidateIndex < altmax; candidateIndex++ ) + { + texture_t *tex = altanims[candidateIndex]; + + if( !tex ) + { + Con_Printf( S_ERROR "%s: missing alternate frame %i of animated texture \"%s\"\n", + __func__, + candidateIndex, + baseTexture->name ); + + baseTexture->anim_total = 0; + break; + } + + tex->anim_total = altmax * ANIM_CYCLE; + tex->anim_min = candidateIndex * ANIM_CYCLE; + tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE; + tex->anim_next = altanims[( candidateIndex + 1 ) % altmax]; + + if( max > 0 ) + tex->alternate_anims = anims[0]; + } +} + +static void Mod_SequenceAllAnimatedTextures( model_t *mod ) +{ + int i; + + for( i = 0; i < mod->numtextures; i++ ) + Mod_SequenceAnimatedTexture( mod, i ); +} + /* ================= Mod_LoadTextures ================= */ -static void Mod_LoadTextures( dbspmodel_t *bmod ) +static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod ) { - dmiptexlump_t *in; - texture_t *tx, *tx2; - texture_t *anims[10]; - texture_t *altanims[10]; - int num, max, altmax; - qboolean custom_palette; - char texname[64]; - mip_t *mt; - int i, j; + dmiptexlump_t *lump; - if( bmod->isworld ) - { #if !XASH_DEDICATED - // release old sky layers first - if( !Host_IsDedicated() ) - { - ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE )); - ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE )); - } -#endif + // release old sky layers first + if( !Host_IsDedicated() && bmod->isworld ) + { + ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( "alpha_sky" )); + ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( "solid_sky" )); } +#endif - if( !bmod->texdatasize ) + lump = bmod->textures; + + if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 ) { // no textures - loadmodel->textures = NULL; + mod->textures = NULL; return; } - in = bmod->textures; - loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, in->nummiptex * sizeof( texture_t* )); - loadmodel->numtextures = in->nummiptex; + mod->textures = (texture_t **)Mem_Calloc( mod->mempool, lump->nummiptex * sizeof( texture_t * )); + mod->numtextures = lump->nummiptex; - for( i = 0; i < loadmodel->numtextures; i++ ) - { - int txFlags = 0; - - if( in->dataofs[i] == -1 ) - { - // create default texture (some mods requires this) - tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx )); - loadmodel->textures[i] = tx; - - Q_strncpy( tx->name, "*default", sizeof( tx->name )); -#if !XASH_DEDICATED - if( !Host_IsDedicated() ) - { - tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); - tx->width = tx->height = 16; - } -#endif - continue; // missed - } - - mt = (mip_t *)((byte *)in + in->dataofs[i] ); - - if( !mt->name[0] ) - Q_snprintf( mt->name, sizeof( mt->name ), "miptex_%i", i ); - tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx )); - loadmodel->textures[i] = tx; - - // convert to lowercase - Q_strncpy( tx->name, mt->name, sizeof( tx->name )); - Q_strnlwr( tx->name, tx->name, sizeof( tx->name )); - custom_palette = false; - - tx->width = mt->width; - tx->height = mt->height; - - if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mt->name[0] == '{' ) - SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing - - if( mt->offsets[0] > 0 ) - { - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - int next_dataofs = 0, remaining; - - // compute next dataofset to determine allocated miptex space - for( j = i + 1; j < loadmodel->numtextures; j++ ) - { - next_dataofs = in->dataofs[j]; - if( next_dataofs != -1 ) break; - } - - if( j == loadmodel->numtextures ) - next_dataofs = bmod->texdatasize; - - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - remaining = next_dataofs - (in->dataofs[i] + size); - if( remaining >= 770 ) custom_palette = true; - } - -#if !XASH_DEDICATED - if( !Host_IsDedicated() ) - { - // check for multi-layered sky texture (quake1 specific) - if( bmod->isworld && !Q_strncmp( mt->name, "sky", 3 ) && (( mt->width / mt->height ) == 2 ) ) - { - ref.dllFuncs.R_InitSkyClouds( mt, tx, custom_palette ); // load quake sky - - if( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ) && - R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ) ) - SetBits( world.flags, FWORLD_SKYSPHERE ); - continue; - } - - // texture loading order: - // 1. from wad - // 2. internal from map - - // trying wad texture (force while r_wadtextures is 1) - if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || ( mt->offsets[0] <= 0 )) - { - Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name ); - - // check wads in reverse order - for( j = bmod->wadlist.count - 1; j >= 0; j-- ) - { - char *texpath = va( "%s.wad/%s", bmod->wadlist.wadnames[j], texname ); - - if( FS_FileExists( texpath, false )) - { - tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags ); - bmod->wadlist.wadusage[j]++; // this wad are really used - break; - } - } - } - - // wad failed, so use internal texture (if present) - if( mt->offsets[0] > 0 && !tx->gl_texturenum ) - { - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - - if( custom_palette ) size += sizeof( short ) + 768; - Q_snprintf( texname, sizeof( texname ), "#%s:%s.mip", loadstat.name, mt->name ); - tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, txFlags ); - } - - // if texture is completely missed - if( !tx->gl_texturenum ) - { - Con_DPrintf( S_ERROR "unable to find %s.mip\n", mt->name ); - tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); - } - - // check for luma texture - if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, tx->gl_texturenum ), TF_HAS_LUMA )) - { - Q_snprintf( texname, sizeof( texname ), "#%s:%s_luma.mip", loadstat.name, mt->name ); - - if( mt->offsets[0] > 0 ) - { - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - - if( custom_palette ) size += sizeof( short ) + 768; - tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, TF_MAKELUMA ); - } - else - { - fs_offset_t srcSize = 0; - byte *src = NULL; - - // NOTE: we can't loading it from wad as normal because _luma texture doesn't exist - // and not be loaded. But original texture is already loaded and can't be modified - // So load original texture manually and convert it to luma - - // check wads in reverse order - for( j = bmod->wadlist.count - 1; j >= 0; j-- ) - { - char *texpath = va( "%s.wad/%s.mip", bmod->wadlist.wadnames[j], tx->name ); - - if( FS_FileExists( texpath, false )) - { - src = FS_LoadFile( texpath, &srcSize, false ); - bmod->wadlist.wadusage[j]++; // this wad are really used - break; - } - } - - // okay, loading it from wad or hi-res version - tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, src, srcSize, TF_MAKELUMA ); - if( src ) Mem_Free( src ); - } - } - } -#endif - } - - // sequence the animations and detail textures - for( i = 0; i < loadmodel->numtextures; i++ ) - { - tx = loadmodel->textures[i]; - - if( !tx || ( tx->name[0] != '-' && tx->name[0] != '+' )) - continue; - - if( tx->anim_next ) - continue; // already sequenced - - // find the number of frames in the animation - memset( anims, 0, sizeof( anims )); - memset( altanims, 0, sizeof( altanims )); - - max = tx->name[1]; - altmax = 0; - - if( max >= '0' && max <= '9' ) - { - max -= '0'; - altmax = 0; - anims[max] = tx; - max++; - } - else if( max >= 'a' && max <= 'j' ) - { - altmax = max - 'a'; - max = 0; - altanims[altmax] = tx; - altmax++; - } - else Con_Printf( S_ERROR "Mod_LoadTextures: bad animating texture %s\n", tx->name ); - - for( j = i + 1; j < loadmodel->numtextures; j++ ) - { - tx2 = loadmodel->textures[j]; - - if( !tx2 || ( tx2->name[0] != '-' && tx2->name[0] != '+' )) - continue; - - if( Q_strcmp( tx2->name + 2, tx->name + 2 )) - continue; - - num = tx2->name[1]; - - if( num >= '0' && num <= '9' ) - { - num -= '0'; - anims[num] = tx2; - if( num + 1 > max ) - max = num + 1; - } - else if( num >= 'a' && num <= 'j' ) - { - num = num - 'a'; - altanims[num] = tx2; - if( num + 1 > altmax ) - altmax = num + 1; - } - else Con_Printf( S_ERROR "Mod_LoadTextures: bad animating texture %s\n", tx->name ); - } - - // link them all together - for( j = 0; j < max; j++ ) - { - tx2 = anims[j]; - - if( !tx2 ) - { - Con_Printf( S_ERROR "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); - tx->anim_total = 0; - break; - } - - tx2->anim_total = max * ANIM_CYCLE; - tx2->anim_min = j * ANIM_CYCLE; - tx2->anim_max = (j + 1) * ANIM_CYCLE; - tx2->anim_next = anims[(j + 1) % max]; - if( altmax ) tx2->alternate_anims = altanims[0]; - } - - for( j = 0; j < altmax; j++ ) - { - tx2 = altanims[j]; - - if( !tx2 ) - { - Con_Printf( S_ERROR "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); - tx->anim_total = 0; - break; - } - - tx2->anim_total = altmax * ANIM_CYCLE; - tx2->anim_min = j * ANIM_CYCLE; - tx2->anim_max = (j+1) * ANIM_CYCLE; - tx2->anim_next = altanims[(j + 1) % altmax]; - if( max ) tx2->alternate_anims = anims[0]; - } - } + Mod_LoadAllTextures( mod, bmod ); + Mod_SequenceAllAnimatedTextures( mod ); } /* @@ -2203,7 +2927,7 @@ static void Mod_LoadTextures( dbspmodel_t *bmod ) Mod_LoadTexInfo ================= */ -static void Mod_LoadTexInfo( dbspmodel_t *bmod ) +static void Mod_LoadTexInfo( model_t *mod, dbspmodel_t *bmod ) { mfaceinfo_t *fout, *faceinfo; int i, j, k, miptex; @@ -2212,7 +2936,7 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) dtexinfo_t *in; // trying to load faceinfo - faceinfo = fout = Mem_Calloc( loadmodel->mempool, bmod->numfaceinfo * sizeof( *fout )); + faceinfo = fout = Mem_Calloc( mod->mempool, bmod->numfaceinfo * sizeof( *fout )); fin = bmod->faceinfo; for( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ ) @@ -2223,8 +2947,8 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) fout->groupid = fin->groupid; } - loadmodel->texinfo = out = Mem_Calloc( loadmodel->mempool, bmod->numtexinfo * sizeof( *out )); - loadmodel->numtexinfo = bmod->numtexinfo; + mod->texinfo = out = Mem_Calloc( mod->mempool, bmod->numtexinfo * sizeof( *out )); + mod->numtexinfo = bmod->numtexinfo; in = bmod->texinfo; for( i = 0; i < bmod->numtexinfo; i++, in++, out++ ) @@ -2234,9 +2958,9 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) out->vecs[j][k] = in->vecs[j][k]; miptex = in->miptex; - if( miptex < 0 || miptex > loadmodel->numtextures ) + if( miptex < 0 || miptex >= mod->numtextures ) miptex = 0; // this is possible? - out->texture = loadmodel->textures[miptex]; + out->texture = mod->textures[miptex]; out->flags = in->flags; // make sure what faceinfo is really exist @@ -2250,7 +2974,7 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) Mod_LoadSurfaces ================= */ -static void Mod_LoadSurfaces( dbspmodel_t *bmod ) +static void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod ) { int test_lightsize = -1; int next_lightofs = -1; @@ -2259,9 +2983,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) mextrasurf_t *info; msurface_t *out; - loadmodel->surfaces = out = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( msurface_t )); - info = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( mextrasurf_t )); - loadmodel->numsurfaces = bmod->numsurfaces; + mod->surfaces = out = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( msurface_t )); + info = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( mextrasurf_t )); + mod->numsurfaces = bmod->numsurfaces; // predict samplecount based on bspversion if( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION ) @@ -2280,13 +3004,13 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) { dface32_t *in = &bmod->surfaces32[i]; - if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + if(( in->firstedge + in->numedges ) > mod->numsurfedges ) continue; // corrupted level? out->firstedge = in->firstedge; out->numedges = in->numedges; if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); - out->plane = loadmodel->planes + in->planenum; - out->texinfo = loadmodel->texinfo + in->texinfo; + out->plane = mod->planes + in->planenum; + out->texinfo = mod->texinfo + in->texinfo; for( j = 0; j < MAXLIGHTMAPS; j++ ) out->styles[j] = in->styles[j]; @@ -2296,17 +3020,17 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) { dface_t *in = &bmod->surfaces[i]; - if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + if(( in->firstedge + in->numedges ) > mod->numsurfedges ) { - Con_Reportf( S_ERROR "bad surface %i from %lu\n", i, bmod->numsurfaces ); + Con_Reportf( S_ERROR "bad surface %i from %zu\n", i, bmod->numsurfaces ); continue; } out->firstedge = in->firstedge; out->numedges = in->numedges; if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); - out->plane = loadmodel->planes + in->planenum; - out->texinfo = loadmodel->texinfo + in->texinfo; + out->plane = mod->planes + in->planenum; + out->texinfo = mod->texinfo + in->texinfo; for( j = 0; j < MAXLIGHTMAPS; j++ ) out->styles[j] = in->styles[j]; @@ -2318,15 +3042,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( !Q_strncmp( tex->name, "sky", 3 )) SetBits( out->flags, SURF_DRAWSKY ); - if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) + if( Mod_LooksLikeWaterTexture( tex->name )) SetBits( out->flags, SURF_DRAWTURB ); - if( !Host_IsQuakeCompatible( )) - { - if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 )) - SetBits( out->flags, SURF_DRAWTURB ); - } - if( !Q_strncmp( tex->name, "scroll", 6 )) SetBits( out->flags, SURF_CONVEYOR ); @@ -2343,9 +3061,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( FBitSet( out->texinfo->flags, TEX_SPECIAL )) SetBits( out->flags, SURF_DRAWTILED ); - Mod_CalcSurfaceBounds( out ); - Mod_CalcSurfaceExtents( out ); - Mod_CreateFaceBevels( out ); + Mod_CalcSurfaceBounds( mod, out, bmod ); + Mod_CalcSurfaceExtents( mod, out, bmod ); + Mod_CreateFaceBevels( mod, out, bmod ); // grab the second sample to detect colored lighting if( test_lightsize > 0 && lightofs != -1 ) @@ -2374,7 +3092,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) #if !XASH_DEDICATED // TODO: Do we need subdivide on server? if( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() ) - ref.dllFuncs.GL_SubdivideSurface( out ); // cut up polygon for warps + ref.dllFuncs.GL_SubdivideSurface( mod, out ); // cut up polygon for warps #endif } @@ -2392,7 +3110,6 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( samples == 1 || samples == 3 ) { bmod->lightmap_samples = (int)samples; - Con_Reportf( "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" ); bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero } else Con_DPrintf( S_WARN "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples ); @@ -2404,15 +3121,15 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) Mod_LoadNodes ================= */ -static void Mod_LoadNodes( dbspmodel_t *bmod ) +static void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod ) { mnode_t *out; int i, j, p; - loadmodel->nodes = out = (mnode_t *)Mem_Calloc( loadmodel->mempool, bmod->numnodes * sizeof( *out )); - loadmodel->numnodes = bmod->numnodes; + mod->nodes = out = (mnode_t *)Mem_Calloc( mod->mempool, bmod->numnodes * sizeof( *out )); + mod->numnodes = bmod->numnodes; - for( i = 0; i < loadmodel->numnodes; i++, out++ ) + for( i = 0; i < mod->numnodes; i++, out++ ) { if( bmod->version == QBSP2_VERSION ) { @@ -2424,16 +3141,62 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) out->minmaxs[j+3] = in->maxs[j]; } +#if !XASH_64BIT + if( in->firstface >= BIT( 24 )) + { + Host_Error( "%s: face index limit exceeded on node %i\n", __func__, i ); + return; + } + + if( in->numfaces >= BIT( 24 )) + { + Host_Error( "%s: face count limit exceeded on node %i\n", __func__, i ); + return; + } +#endif + p = in->planenum; - out->plane = loadmodel->planes + p; - out->firstsurface = in->firstface; - out->numsurfaces = in->numfaces; + out->plane = mod->planes + p; + out->firstsurface_0 = in->firstface & 0xFFFF; + out->numsurfaces_0 = in->numfaces & 0xFFFF; + + out->firstsurface_1 = in->firstface >> 16; + out->numsurfaces_1 = in->numfaces >> 16; for( j = 0; j < 2; j++ ) { p = in->children[j]; - if( p >= 0 ) out->children[j] = loadmodel->nodes + p; - else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); +#if XASH_64BIT + if( p >= 0 ) out->children_[j] = mod->nodes + p; + else out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p )); +#else + if( j == 0 ) + { + if( p >= 0 ) + { + out->child_0_leaf = 0; + out->child_0_off = p; + } + else + { + out->child_0_leaf = 1; + out->child_0_off = -1 - p; + } + } + else + { + if( p >= 0 ) + { + out->child_1_leaf = 0; + out->child_1_off = p; + } + else + { + out->child_1_leaf = 1; + out->child_1_off = -1 - p; + } + } +#endif } } else @@ -2447,21 +3210,21 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) } p = in->planenum; - out->plane = loadmodel->planes + p; - out->firstsurface = in->firstface; - out->numsurfaces = in->numfaces; + out->plane = mod->planes + p; + out->firstsurface_0 = in->firstface; + out->numsurfaces_0 = in->numfaces; for( j = 0; j < 2; j++ ) { p = in->children[j]; - if( p >= 0 ) out->children[j] = loadmodel->nodes + p; - else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); + if( p >= 0 ) out->children_[j] = mod->nodes + p; + else out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p )); } } } // sets nodes and leafs - Mod_SetParent( loadmodel->nodes, NULL ); + Mod_SetParent( mod, mod->nodes, NULL ); } /* @@ -2469,18 +3232,18 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) Mod_LoadLeafs ================= */ -static void Mod_LoadLeafs( dbspmodel_t *bmod ) +static void Mod_LoadLeafs( model_t *mod, dbspmodel_t *bmod ) { mleaf_t *out; int i, j, p; int visclusters = 0; - loadmodel->leafs = out = (mleaf_t *)Mem_Calloc( loadmodel->mempool, bmod->numleafs * sizeof( *out )); - loadmodel->numleafs = bmod->numleafs; + mod->leafs = out = (mleaf_t *)Mem_Calloc( mod->mempool, bmod->numleafs * sizeof( *out )); + mod->numleafs = bmod->numleafs; if( bmod->isworld ) { - visclusters = loadmodel->submodels[0].visleafs; + visclusters = mod->submodels[0].visleafs; world.visbytes = (visclusters + 7) >> 3; world.fatbytes = (visclusters + 31) >> 3; refState.visbytes = world.visbytes; @@ -2504,7 +3267,7 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) for( j = 0; j < 4; j++ ) out->ambient_sound_level[j] = in->ambient_level[j]; - out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->firstmarksurface = mod->marksurfaces + in->firstmarksurface; out->nummarksurfaces = in->nummarksurfaces; } else @@ -2523,7 +3286,7 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) for( j = 0; j < 4; j++ ) out->ambient_sound_level[j] = in->ambient_level[j]; - out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->firstmarksurface = mod->marksurfaces + in->firstmarksurface; out->nummarksurfaces = in->nummarksurfaces; } @@ -2535,17 +3298,17 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) out->cluster = -1; // ignore visofs errors on leaf 0 (solid) - if( p >= 0 && out->cluster >= 0 && loadmodel->visdata ) + if( p >= 0 && out->cluster >= 0 && mod->visdata ) { if( p < bmod->visdatasize ) - out->compressed_vis = loadmodel->visdata + p; + out->compressed_vis = mod->visdata + p; else Con_Reportf( S_WARN "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i ); } } else out->cluster = -1; // no visclusters on bmodels if( p == -1 ) out->compressed_vis = NULL; - else out->compressed_vis = loadmodel->visdata + p; + else out->compressed_vis = mod->visdata + p; // gl underwater warp if( out->contents != CONTENTS_EMPTY ) @@ -2558,29 +3321,176 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) } } - if( bmod->isworld && loadmodel->leafs[0].contents != CONTENTS_SOLID ) - Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", loadmodel->name ); + if( bmod->isworld && mod->leafs[0].contents != CONTENTS_SOLID ) + Host_Error( "%s: Map %s has leaf 0 is not CONTENTS_SOLID\n", __func__, mod->name ); // do some final things for world - if( bmod->isworld && Mod_CheckWaterAlphaSupport( bmod )) + if( bmod->isworld && Mod_CheckWaterAlphaSupport( mod, bmod )) SetBits( world.flags, FWORLD_WATERALPHA ); } +/* +=========== +Mod_CalcPHS + +To be called while loading world for multiplayer game server +=========== +*/ +static void Mod_CalcPHS( model_t *mod ) +{ + const qboolean vis_stats = host_developer.value >= DEV_EXTENDED; + const size_t rowbytes = ALIGN( world.visbytes, 4 ); // force align rows by 32-bit boundary + const size_t count = mod->numleafs + 1; // same as mod->submodels[0].visleafs + 1 + double t1; + double t2; + size_t total_compressed_size = 0; + size_t hcount = 0; + size_t vcount = 0; + int i; + byte *uncompressed_pvs; + byte *uncompressed_phs; + + if( !mod->visdata ) + return; + +#if defined( HAVE_OPENMP ) + Con_Reportf( "Building PHS in %d threads...\n", omp_get_max_threads( )); +#else + Con_Reportf( "Building PHS...\n" ); +#endif + + uncompressed_pvs = Mem_Calloc( mod->mempool, rowbytes * count * 2 ); + uncompressed_phs = &uncompressed_pvs[rowbytes * count]; + + world.phsofs = Mem_Calloc( mod->mempool, sizeof( size_t ) * count ); + world.compressed_phs = NULL; + + t1 = Platform_DoubleTime(); + +#pragma omp parallel + { + // uncompress pvs first +#pragma omp for schedule( static, 256 ) // there might be thousands of leafs, split by 256 + for( i = 0; i < count; i++ ) + Mod_DecompressPVSTo( &uncompressed_pvs[rowbytes * i], mod->leafs[i].compressed_vis, world.visbytes ); + + // now create phs +#pragma omp for schedule( static, 256 ) reduction( + : vcount, hcount ) + for( i = 0; i < count; i++ ) + { + const byte *scan = &uncompressed_pvs[rowbytes * i]; + byte *dst = &uncompressed_phs[rowbytes * i]; // rowbytes, not rowwords! + size_t j; + + memcpy( dst, scan, rowbytes ); + + for( j = 0; j < rowbytes; j++ ) + { + size_t k; + uint bitbyte = scan[j]; + + if( bitbyte == 0 ) + continue; + + for( k = 0; k < 8; k++ ) + { + size_t index; + + if( !FBitSet( bitbyte, BIT( k ))) + continue; + + // OR this pvs row into the phs + // +1 because pvs is 1 based + index = (( j * 8 ) + k + 1 ); + if( index >= count ) + continue; + + Q_memor( dst, &uncompressed_pvs[rowbytes * index], rowbytes ); + } + } + + if( vis_stats && i != 0 ) + { + size_t j; + + for( j = 0; j < count; j++ ) + { + if( CHECKVISBIT( scan, j )) + vcount++; + + if( CHECKVISBIT( dst, j )) + hcount++; + } + } + } + } + + // since I can't predict at which spot compressed array + // should be put, this loop is single threaded + for( i = 0; i < count; i++ ) + { + const byte *src = &uncompressed_phs[rowbytes * i]; + byte temp_compressed_row[(MAX_MAP_LEAFS+1)/4]; // compression for this row might be ineffective + size_t compressed_size; + + compressed_size = Mod_CompressPVS( temp_compressed_row, src, rowbytes ); + + world.compressed_phs = Mem_Realloc( mod->mempool, world.compressed_phs, total_compressed_size + compressed_size ); + memcpy( &world.compressed_phs[total_compressed_size], temp_compressed_row, compressed_size ); + world.phsofs[i] = total_compressed_size; + + total_compressed_size += compressed_size; + } + + t2 = Platform_DoubleTime(); + + if( vis_stats ) + Con_Reportf( "Average leaves visible / audible / total: %zu / %zu / %zu\n", vcount / count, hcount / count, count ); + Con_Reportf( "Uncompressed PHS size: %s\n", Q_memprint( rowbytes * count )); + Con_Reportf( "Compressed PHS size: %s\n", Q_memprint( total_compressed_size + sizeof( *world.phsofs ) * count )); + Con_Reportf( "PHS building time: %.2f ms\n", ( t2 - t1 ) * 1000.0f ); + + // TODO: rewrite this into a unit test + // NOTE: how to get GoldSrc fat PHS and PVS data + // start a multiplayer server with some op4_bootcamp (for example) + // attach to process with GDB: + // (gdb) p gPAS[0] + // $0 = (byte *) ... + // (gdb) p gPAS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)] + // $1 = (byte *) ... + // (gdb) dump binary memory op4_bootcamp_gs.phs $0 $1 + // (gdb) p gPVS[0] + // $2 = (byte *) ... + // (gdb) p gPVS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)] + // $3 = (byte *) ... + // (gdb) dump binary memory op4_bootcamp_gs.pvs $0 $1 + // + // NOTE: as of writing, uncompressed PVS and PHS data do match! hooray! + // + // FS_WriteFile( "op4_bootcamp.pvs", uncompressed_pvs, rowbytes * count ); + // FS_WriteFile( "op4_bootcamp.phs", uncompressed_phs, rowbytes * count ); + + // release uncompressed data + Mem_Free( uncompressed_pvs ); + + // TODO: cache the PHS somewhere, it might take a long time on giant maps +} + /* ================= Mod_LoadClipnodes ================= */ -static void Mod_LoadClipnodes( dbspmodel_t *bmod ) +static void Mod_LoadClipnodes( model_t *mod, dbspmodel_t *bmod ) { dclipnode32_t *out; int i; - bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( loadmodel->mempool, bmod->numclipnodes * sizeof( *out )); + bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( mod->mempool, bmod->numclipnodes * sizeof( *out )); - if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->numclipnodes >= MAX_MAP_CLIPNODES )) + if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->isbsp30ext && bmod->numclipnodes >= MAX_MAP_CLIPNODES_HLBSP )) { - dclipnode32_t *in = bmod->clipnodes32; + dclipnode32_t *in = bmod->clipnodes32; for( i = 0; i < bmod->numclipnodes; i++, out++, in++ ) { @@ -2600,7 +3510,7 @@ static void Mod_LoadClipnodes( dbspmodel_t *bmod ) out->children[0] = (unsigned short)in->children[0]; out->children[1] = (unsigned short)in->children[1]; - // Arguire QBSP 'broken' clipnodes + // aguirRe QBSP 'broken' clipnodes if( out->children[0] >= bmod->numclipnodes ) out->children[0] -= 65536; if( out->children[1] >= bmod->numclipnodes ) @@ -2608,8 +3518,8 @@ static void Mod_LoadClipnodes( dbspmodel_t *bmod ) } } - // FIXME: fill loadmodel->clipnodes? - loadmodel->numclipnodes = bmod->numclipnodes; + // FIXME: fill mod->clipnodes? + mod->numclipnodes = bmod->numclipnodes; } /* @@ -2617,10 +3527,10 @@ static void Mod_LoadClipnodes( dbspmodel_t *bmod ) Mod_LoadVisibility ================= */ -static void Mod_LoadVisibility( dbspmodel_t *bmod ) +static void Mod_LoadVisibility( model_t *mod, dbspmodel_t *bmod ) { - loadmodel->visdata = Mem_Malloc( loadmodel->mempool, bmod->visdatasize ); - memcpy( loadmodel->visdata, bmod->visdata, bmod->visdatasize ); + mod->visdata = Mem_Malloc( mod->mempool, bmod->visdatasize ); + memcpy( mod->visdata, bmod->visdata, bmod->visdatasize ); } /* @@ -2628,17 +3538,18 @@ static void Mod_LoadVisibility( dbspmodel_t *bmod ) Mod_LoadLightVecs ================= */ -static void Mod_LoadLightVecs( dbspmodel_t *bmod ) +static void Mod_LoadLightVecs( model_t *mod, dbspmodel_t *bmod ) { if( bmod->deluxdatasize != bmod->lightdatasize ) { if( bmod->deluxdatasize > 0 ) - Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%lu should be %i)\n", bmod->deluxdatasize, bmod->lightdatasize ); - else Mod_LoadDeluxemap( bmod ); // old method + Con_Printf( S_ERROR "%s: has mismatched size (%zu should be %zu)\n", __func__, bmod->deluxdatasize, bmod->lightdatasize ); + else + Mod_LoadLitfile( mod, "dlit", bmod->lightdatasize, &bmod->deluxedata_out, &bmod->deluxdatasize ); // old method return; } - bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, bmod->deluxdatasize ); + bmod->deluxedata_out = Mem_Malloc( mod->mempool, bmod->deluxdatasize ); memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize ); } @@ -2647,16 +3558,16 @@ static void Mod_LoadLightVecs( dbspmodel_t *bmod ) Mod_LoadShadowmap ================= */ -static void Mod_LoadShadowmap( dbspmodel_t *bmod ) +static void Mod_LoadShadowmap( model_t *mod, dbspmodel_t *bmod ) { if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 )) { if( bmod->shadowdatasize > 0 ) - Con_Printf( S_ERROR "Mod_LoadShadowmap: has mismatched size (%i should be %lu)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 ); + Con_Printf( S_ERROR "%s: has mismatched size (%zu should be %zu)\n", __func__, bmod->shadowdatasize, bmod->lightdatasize / 3 ); return; } - bmod->shadowdata_out = Mem_Malloc( loadmodel->mempool, bmod->shadowdatasize ); + bmod->shadowdata_out = Mem_Malloc( mod->mempool, bmod->shadowdatasize ); memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize ); } @@ -2665,12 +3576,9 @@ static void Mod_LoadShadowmap( dbspmodel_t *bmod ) Mod_LoadLighting ================= */ -static void Mod_LoadLighting( dbspmodel_t *bmod ) +static void Mod_LoadLighting( model_t *mod, dbspmodel_t *bmod ) { - int i, lightofs; - msurface_t *surf; - color24 *out; - byte *in; + int i; if( !bmod->lightdatasize ) return; @@ -2678,93 +3586,80 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) switch( bmod->lightmap_samples ) { case 1: - if( !Mod_LoadColoredLighting( bmod )) + if( !Mod_LoadLitfile( mod, "lit", bmod->lightdatasize * 3, &mod->lightdata, &bmod->lightdatasize )) { - loadmodel->lightdata = out = (color24 *)Mem_Malloc( loadmodel->mempool, bmod->lightdatasize * sizeof( color24 )); - in = bmod->lightdata; + mod->lightdata = (color24 *)Mem_Malloc( mod->mempool, bmod->lightdatasize * sizeof( color24 )); // expand the white lighting data - for( i = 0; i < bmod->lightdatasize; i++, out++ ) - out->r = out->g = out->b = *in++; + for( i = 0; i < bmod->lightdatasize; i++ ) + mod->lightdata[i].r = mod->lightdata[i].g = mod->lightdata[i].b = bmod->lightdata[i]; } + else SetBits( mod->flags, MODEL_COLORED_LIGHTING ); break; case 3: // load colored lighting - loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, bmod->lightdatasize ); - memcpy( loadmodel->lightdata, bmod->lightdata, bmod->lightdatasize ); - SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); + mod->lightdata = Mem_Malloc( mod->mempool, bmod->lightdatasize ); + memcpy( mod->lightdata, bmod->lightdata, bmod->lightdatasize ); + SetBits( mod->flags, MODEL_COLORED_LIGHTING ); break; default: - Host_Error( "Mod_LoadLighting: bad lightmap sample count %i\n", bmod->lightmap_samples ); + Host_Error( "%s: bad lightmap sample count %i\n", __func__, bmod->lightmap_samples ); break; } + Con_Reportf( "lighting: %s\n", FBitSet( mod->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" ); + // not supposed to be load ? if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) { - Mod_LoadLightVecs( bmod ); - Mod_LoadShadowmap( bmod ); + Mod_LoadLightVecs( mod, bmod ); + Mod_LoadShadowmap( mod, bmod ); if( bmod->isworld && bmod->deluxdatasize ) SetBits( world.flags, FWORLD_HAS_DELUXEMAP ); } - surf = loadmodel->surfaces; - // setup lightdata pointers - for( i = 0; i < loadmodel->numsurfaces; i++, surf++ ) + if( !mod->lightdata ) + return; + + for( i = 0; i < mod->numsurfaces; i++ ) { + int lightofs; + if( bmod->version == QBSP2_VERSION ) lightofs = bmod->surfaces32[i].lightofs; - else lightofs = bmod->surfaces[i].lightofs; + else + lightofs = bmod->surfaces[i].lightofs; - if( loadmodel->lightdata && lightofs != -1 ) + if( lightofs != -1 ) { - int offset = (lightofs / bmod->lightmap_samples); + int offset = lightofs / bmod->lightmap_samples; // NOTE: we divide offset by three because lighting and deluxemap keep their pointers // into three-bytes structs and shadowmap just monochrome - surf->samples = loadmodel->lightdata + offset; + mod->surfaces[i].samples = mod->lightdata + offset; // if deluxemap is present setup it too if( bmod->deluxedata_out ) - surf->info->deluxemap = bmod->deluxedata_out + offset; + mod->surfaces[i].info->deluxemap = bmod->deluxedata_out + offset; // will be used by mods if( bmod->shadowdata_out ) - surf->info->shadowmap = bmod->shadowdata_out + offset; + mod->surfaces[i].info->shadowmap = bmod->shadowdata_out + offset; } } } /* ================= -Mod_LumpLooksLikePlanes +Mod_LumpLooksLikeEntities ================= */ -static qboolean Mod_LumpLooksLikePlanes( const byte *in, dlump_t *lump, qboolean fast ) +static int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen ) { - int numplanes, i; - const dplane_t *planes; - - if( lump->filelen < sizeof( dplane_t ) && - lump->filelen % sizeof( dplane_t ) != 0 ) - return false; - - if( fast ) - return true; - - numplanes = lump->filelen / sizeof( dplane_t ); - planes = (const dplane_t*)(in + lump->fileofs); - - for( i = 0; i < numplanes; i++ ) - { - // planes can only be from 0 to 5: PLANE_X, Y, Z and PLANE_ANYX, Y and Z - if( planes[i].type < 0 || planes[i].type > 5 ) - return false; - } - - return true; + // look for "classname" string + return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0; } /* @@ -2774,76 +3669,71 @@ Mod_LoadBmodelLumps loading and processing bmodel ================= */ -qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) +static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolean isworld ) { - dheader_t *header = (dheader_t *)mod_base; - dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)mod_base + sizeof( dheader_t )); + const dheader_t *header = (const dheader_t *)mod_base; + const dextrahdr_t *extrahdr = (const dextrahdr_t *)(mod_base + sizeof( dheader_t )); dbspmodel_t *bmod = &srcmodel; - model_t *mod = loadmodel; char wadvalue[2048]; - int i; + size_t len = 0; + int i, ret, flags = 0; + qboolean wadlist_warn = false; // always reset the intermediate struct memset( bmod, 0, sizeof( dbspmodel_t )); memset( &loadstat, 0, sizeof( loadstat_t )); - Q_strncpy( loadstat.name, loadmodel->name, sizeof( loadstat.name )); + Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name )); wadvalue[0] = '\0'; -#ifndef SUPPORT_BSP2_FORMAT - if( header->version == QBSP2_VERSION ) - { - Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, loadmodel->name ); - return false; - } -#endif switch( header->version ) { - case Q1BSP_VERSION: case HLBSP_VERSION: + if( extrahdr->id == IDEXTRAHEADER ) + { + SetBits( flags, LUMP_BSP30EXT ); + } + // only relevant for half-life maps + else if( !Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_ENTITIES].fileofs, header->lumps[LUMP_ENTITIES].filelen ) && + Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen )) + { + // blue-shift swapped lumps + srclumps[0].lumpnumber = LUMP_PLANES; + srclumps[1].lumpnumber = LUMP_ENTITIES; + break; + } + // intended fallthrough + case Q1BSP_VERSION: case QBSP2_VERSION: + // everything else + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; + + if( header->version == QBSP2_VERSION ) + SetBits( mod->flags, MODEL_QBSP2 ); break; default: - Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", loadmodel->name, header->version, HLBSP_VERSION ); + Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, header->version, HLBSP_VERSION ); loadstat.numerrors++; return false; } bmod->version = header->version; // share up global - if( isworld ) world.flags = 0; // clear world settings + if( isworld ) + { + world.flags = 0; // clear world settings + SetBits( flags, LUMP_SAVESTATS|LUMP_SILENT ); + } bmod->isworld = isworld; - - if( header->version == HLBSP_VERSION ) - { - // only relevant for half-life maps - if( !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], false ) && - Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], false )) - { - // blue-shift swapped lumps - srclumps[0].lumpnumber = LUMP_PLANES; - srclumps[1].lumpnumber = LUMP_ENTITIES; - } - else - { - // everything else - srclumps[0].lumpnumber = LUMP_ENTITIES; - srclumps[1].lumpnumber = LUMP_PLANES; - } - } - else - { - // everything else - srclumps[0].lumpnumber = LUMP_ENTITIES; - srclumps[1].lumpnumber = LUMP_PLANES; - } + bmod->isbsp30ext = FBitSet( flags, LUMP_BSP30EXT ); // loading base lumps for( i = 0; i < ARRAYSIZE( srclumps ); i++ ) - Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 ); + Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags ); // loading extralumps for( i = 0; i < ARRAYSIZE( extlumps ); i++ ) - Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 ); + Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags ); if( !bmod->isworld && loadstat.numerrors ) { @@ -2854,41 +3744,53 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings ); // load into heap - Mod_LoadEntities( bmod ); - Mod_LoadPlanes( bmod ); - Mod_LoadSubmodels( bmod ); - Mod_LoadVertexes( bmod ); - Mod_LoadEdges( bmod ); - Mod_LoadSurfEdges( bmod ); - Mod_LoadTextures( bmod ); - Mod_LoadVisibility( bmod ); - Mod_LoadTexInfo( bmod ); - Mod_LoadSurfaces( bmod ); - Mod_LoadLighting( bmod ); - Mod_LoadMarkSurfaces( bmod ); - Mod_LoadLeafs( bmod ); - Mod_LoadNodes( bmod ); - Mod_LoadClipnodes( bmod ); + Mod_LoadEntities( mod, bmod ); + Mod_LoadPlanes( mod, bmod ); + Mod_LoadSubmodels( mod, bmod ); + Mod_LoadVertexes( mod, bmod ); + Mod_LoadEdges( mod, bmod ); + Mod_LoadSurfEdges( mod, bmod ); + Mod_LoadTextures( mod, bmod ); + Mod_LoadVisibility( mod, bmod ); + Mod_LoadTexInfo( mod, bmod ); + Mod_LoadSurfaces( mod, bmod ); + Mod_LoadLighting( mod, bmod ); + Mod_LoadMarkSurfaces( mod, bmod ); + Mod_LoadLeafs( mod, bmod ); + Mod_LoadNodes( mod, bmod ); + Mod_LoadClipnodes( mod, bmod ); // preform some post-initalization - Mod_MakeHull0 (); - Mod_SetupSubmodels( bmod ); + Mod_MakeHull0( mod, bmod ); + Mod_SetupSubmodels( mod, bmod ); if( isworld ) { - loadmodel = mod; // restore pointer to world + world.version = bmod->version; #if !XASH_DEDICATED - Mod_InitDebugHulls(); // FIXME: build hulls for separate bmodels (shells, medkits etc) world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer world.shadowdata = bmod->shadowdata_out; // occlusion data pointer #endif // XASH_DEDICATED + + if( SV_Active() && svs.maxclients > 1 ) + Mod_CalcPHS( mod ); } - for( i = 0; i < bmod->wadlist.count; i++ ) + for( i = 0; i < world.wadlist.count; i++ ) { - if( !bmod->wadlist.wadusage[i] ) + if( !world.wadlist.wadusage[i] ) continue; - Q_strncat( wadvalue, va( "%s.wad; ", bmod->wadlist.wadnames[i] ), sizeof( wadvalue )); + + if( !wadlist_warn ) + { + ret = Q_snprintf( &wadvalue[len], sizeof( wadvalue ), "%s; ", world.wadlist.wadnames[i] ); + if( ret == -1 ) + { + Con_DPrintf( S_WARN "Too many wad files for output!\n" ); + wadlist_warn = true; + } + len += ret; + } } if( COM_CheckString( wadvalue )) @@ -2900,16 +3802,45 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) return true; } +static int Mod_LumpLooksLikeEntitiesFile( file_t *f, const dlump_t *l, int flags, const char *msg ) +{ + char *buf; + int ret; + + if( FS_Seek( f, l->fileofs, SEEK_SET ) < 0 ) + { + if( !FBitSet( flags, LUMP_SILENT )) + Con_DPrintf( S_ERROR "map ^2%s^7 %s lump past end of file\n", loadstat.name, msg ); + return -1; + } + + buf = Z_Malloc( l->filelen + 1 ); + if( FS_Read( f, buf, l->filelen ) != l->filelen ) + { + if( !FBitSet( flags, LUMP_SILENT )) + Con_DPrintf( S_ERROR "can't read %s lump of map ^2%s^7", msg, loadstat.name ); + Z_Free( buf ); + return -1; + } + + ret = Mod_LumpLooksLikeEntities( buf, l->filelen ); + + Z_Free( buf ); + return ret; +} + /* ================= Mod_TestBmodelLumps check for possible errors +return real entities lump (for bshift swapped lumps) ================= */ -qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean silent ) +qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities ) { - dheader_t *header = (dheader_t *)mod_base; + const dheader_t *header = (const dheader_t *)mod_base; + const dextrahdr_t *extrahdr = (const dextrahdr_t *)( mod_base + sizeof( dheader_t )); int i, flags = LUMP_TESTONLY; // always reset the intermediate struct @@ -2917,21 +3848,44 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s // store the name to correct show errors and warnings Q_strncpy( loadstat.name, name, sizeof( loadstat.name )); - if( silent ) SetBits( flags, LUMP_SILENT ); + if( silent ) + SetBits( flags, LUMP_SILENT ); -#ifndef SUPPORT_BSP2_FORMAT - if( header->version == QBSP2_VERSION ) - { - if( !FBitSet( flags, LUMP_SILENT )) - Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, name ); - return false; - } -#endif switch( header->version ) { - case Q1BSP_VERSION: case HLBSP_VERSION: + if( extrahdr->id == IDEXTRAHEADER ) + { + SetBits( flags, LUMP_BSP30EXT ); + } + else + { + // only relevant for half-life maps + int ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_ENTITIES], flags, "entities" ); + if( ret < 0 ) return false; + if( !ret ) + { + ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_PLANES], flags, "planes" ); + if( ret < 0 ) return false; + if( ret ) + { + // blue-shift swapped lumps + *entities = header->lumps[LUMP_PLANES]; + + srclumps[0].lumpnumber = LUMP_PLANES; + srclumps[1].lumpnumber = LUMP_ENTITIES; + break; + } + } + } + // intended fallthrough + case Q1BSP_VERSION: case QBSP2_VERSION: + // everything else + *entities = header->lumps[LUMP_ENTITIES]; + + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; break; default: // don't early out: let me analyze errors @@ -2941,30 +3895,6 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s break; } - if( header->version == HLBSP_VERSION ) - { - // only relevant for half-life maps - if( Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], true ) && - !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], true )) - { - // blue-shift swapped lumps - srclumps[0].lumpnumber = LUMP_PLANES; - srclumps[1].lumpnumber = LUMP_ENTITIES; - } - else - { - // everything else - srclumps[0].lumpnumber = LUMP_ENTITIES; - srclumps[1].lumpnumber = LUMP_PLANES; - } - } - else - { - // everything else - srclumps[0].lumpnumber = LUMP_ENTITIES; - srclumps[1].lumpnumber = LUMP_PLANES; - } - // loading base lumps for( i = 0; i < ARRAYSIZE( srclumps ); i++ ) Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags ); @@ -2976,13 +3906,13 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s if( loadstat.numerrors ) { if( !FBitSet( flags, LUMP_SILENT )) - Con_Printf( "Mod_LoadWorld: %i error(s), %i warning(s)\n", loadstat.numerrors, loadstat.numwarnings ); + Con_Printf( "%s: %i error(s), %i warning(s)\n", __func__, loadstat.numerrors, loadstat.numwarnings ); return false; // there were errors, we can't load this map } else if( loadstat.numwarnings ) { if( !FBitSet( flags, LUMP_SILENT )) - Con_Printf( "Mod_LoadWorld: %i warning(s)\n", loadstat.numwarnings ); + Con_Printf( "%s: %i warning(s)\n", __func__, loadstat.numwarnings ); } return true; @@ -2995,13 +3925,17 @@ Mod_LoadBrushModel */ void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ) { + char poolname[MAX_VA_STRING]; + + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); - loadmodel->type = mod_brush; + mod->mempool = Mem_AllocPool( poolname ); + mod->type = mod_brush; // loading all the lumps into heap - if( !Mod_LoadBmodelLumps( buffer, world.loading )) + if( !Mod_LoadBmodelLumps( mod, buffer, world.loading )) return; // there were errors if( world.loading ) worldmodel = mod; @@ -3016,7 +3950,7 @@ Mod_CheckLump check lump for existing ================== */ -int Mod_CheckLump( const char *filename, const int lump, int *lumpsize ) +int GAME_EXPORT Mod_CheckLump( const char *filename, const int lump, int *lumpsize ) { file_t *f = FS_Open( filename, "rb", false ); byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; @@ -3075,7 +4009,7 @@ Mod_ReadLump reading random lump by user request ================== */ -int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ) +int GAME_EXPORT Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ) { file_t *f = FS_Open( filename, "rb", false ); byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; @@ -3157,7 +4091,7 @@ writing lump by user request only empty lumps is allows ================== */ -int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ) +int GAME_EXPORT Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ) { byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; size_t prefetch_size = sizeof( buffer ); diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 5c7e579d..1d3ec47d 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -33,7 +33,7 @@ GNU General Public License for more details. #define REFPVS_RADIUS 2.0f // radius for rendering #define FATPVS_RADIUS 8.0f // FatPVS use radius smaller than the FatPHS -#define FATPHS_RADIUS 16.0f +#define FATPHS_RADIUS 8.0f // see SV_AddToFatPAS in GoldSrc #define WORLD_INDEX (1) // world index is always 1 @@ -74,7 +74,7 @@ typedef struct winding_s struct winding_s *pair; hullnode_t chain; int numpoints; - vec3_t p[4]; // variable sized + vec3_t p[]; // variable sized } winding_t; typedef struct @@ -83,6 +83,12 @@ typedef struct uint num_polys; } hull_model_t; +typedef struct wadlist_s +{ + char wadnames[MAX_MAP_WADS][36]; // including .wad extension + int wadusage[MAX_MAP_WADS]; + int count; +} wadlist_t; typedef struct world_static_s { @@ -113,15 +119,24 @@ typedef struct world_static_s // tree visualization stuff int recursion_level; int max_recursion; + + uint32_t version; // BSP version + + // Potentially Hearable Set + byte *compressed_phs; + size_t *phsofs; + + wadlist_t wadlist; } world_static_t; #ifndef REF_DLL extern world_static_t world; extern poolhandle_t com_studiocache; -extern model_t *loadmodel; -extern convar_t *mod_studiocache; -extern convar_t *r_wadtextures; -extern convar_t *r_showhull; +extern convar_t mod_studiocache; +extern convar_t r_wadtextures; +extern convar_t r_showhull; +extern const mclipnode16_t box_clipnodes16[6]; +extern const mclipnode32_t box_clipnodes32[6]; // // model.c @@ -144,20 +159,23 @@ qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ); void Mod_NeedCRC( const char *name, qboolean needCRC ); void Mod_FreeUnused( void ); +// +// mod_alias.c +// +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ); + // // mod_bmodel.c // void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ); -qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean silent ); -qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf ); -int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis ); +qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities ); +int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean phs ); qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits ); int Mod_CheckLump( const char *filename, const int lump, int *lumpsize ); int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ); int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ); -mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ); -void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ); -int Mod_SampleSizeForFace( msurface_t *surf ); +mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod ); +int Mod_SampleSizeForFace( const msurface_t *surf ); byte *Mod_GetPVSForPoint( const vec3_t p ); void Mod_UnloadBrushModel( model_t *mod ); void Mod_PrintWorldStats_f( void ); @@ -165,8 +183,8 @@ void Mod_PrintWorldStats_f( void ); // // mod_dbghulls.c // -void Mod_InitDebugHulls( void ); -void Mod_CreatePolygonsForHull( int hullnum ); +void R_DrawWorldHull( void ); +void R_DrawModelHull( model_t *mod ); void Mod_ReleaseHullPolygons( void ); // @@ -182,9 +200,6 @@ qboolean Mod_GetStudioBounds( const char *name, vec3_t mins, vec3_t maxs ); void Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *org, float *ang ); void Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang ); hull_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 ); -void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); -void R_StudioCalcBoneQuaternion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec4_t q ); -void R_StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, vec3_t adj, vec3_t pos ); void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean ignore_sequences ); int Mod_HitgroupForStudioHull( int index ); @@ -193,7 +208,7 @@ void Mod_ClearStudioCache( void ); // // mod_sprite.c // -void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ); +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded ); #endif #endif//MOD_LOCAL_H diff --git a/engine/common/mod_sprite.c b/engine/common/mod_sprite.c index 5cc31827..880802d3 100644 --- a/engine/common/mod_sprite.c +++ b/engine/common/mod_sprite.c @@ -20,6 +20,7 @@ GNU General Public License for more details. #if !XASH_DEDICATED #include "ref_common.h" #endif // XASH_DEDICATED +#include "mod_local.h" /* @@ -29,12 +30,13 @@ Mod_LoadSpriteModel load sprite model ==================== */ -void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ) +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded ) { dsprite_q1_t *pinq1; dsprite_hl_t *pinhl; dsprite_t *pin; msprite_t *psprite; + char poolname[MAX_VA_STRING]; int i, size; if( loaded ) *loaded = false; @@ -54,7 +56,8 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui return; } - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); if( i == SPRITE_VERSION_Q1 || i == SPRITE_VERSION_32 ) { diff --git a/engine/common/mod_studio.c b/engine/common/mod_studio.c index 45a39c54..edfcdbba 100644 --- a/engine/common/mod_studio.c +++ b/engine/common/mod_studio.c @@ -24,17 +24,17 @@ typedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*, typedef struct mstudiocache_s { - float frame; - int sequence; - vec3_t angles; - vec3_t origin; - vec3_t size; - byte controller[4]; - byte blending[2]; - model_t *model; - uint current_hull; - uint current_plane; - uint numhitboxes; + model_t *model; + float frame; + int sequence; + vec3_t angles; + vec3_t origin; + vec3_t size; + byte controller[4]; + byte blending[2]; + uint current_hull; + uint current_plane; + uint numhitboxes; } mstudiocache_t; #define STUDIO_CACHESIZE 16 @@ -50,9 +50,8 @@ static matrix3x4 studio_bones[MAXSTUDIOBONES]; static uint studio_hull_hitgroup[MAXSTUDIOBONES]; static uint cache_hull_hitgroup[MAXSTUDIOBONES]; static mstudiocache_t cache_studio[STUDIO_CACHESIZE]; -static mclipnode_t studio_clipnodes[6]; -static mplane_t studio_planes[768]; -static mplane_t cache_planes[768]; +static mplane_t studio_planes[MAXSTUDIOBONES * 6]; +static mplane_t cache_planes[MAXSTUDIOBONES * 6]; // current cache state static int cache_current; @@ -66,25 +65,14 @@ Mod_InitStudioHull */ void Mod_InitStudioHull( void ) { - int i, side; + int i; if( studio_hull[0].planes != NULL ) return; // already initailized - for( i = 0; i < 6; i++ ) - { - studio_clipnodes[i].planenum = i; - - side = i & 1; - - studio_clipnodes[i].children[side] = CONTENTS_EMPTY; - if( i != 5 ) studio_clipnodes[i].children[side^1] = i + 1; - else studio_clipnodes[i].children[side^1] = CONTENTS_SOLID; - } - for( i = 0; i < MAXSTUDIOBONES; i++ ) { - studio_hull[i].clipnodes = studio_clipnodes; + studio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16; studio_hull[i].planes = &studio_planes[i*6]; studio_hull[i].firstclipnode = 0; studio_hull[i].lastclipnode = 5; @@ -116,7 +104,7 @@ void Mod_ClearStudioCache( void ) AddToStudioCache ==================== */ -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 ) +static 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 ) { mstudiocache_t *pCache; @@ -153,7 +141,7 @@ void Mod_AddToStudioCache( float frame, int sequence, vec3_t angles, vec3_t orig CheckStudioCache ==================== */ -mstudiocache_t *Mod_CheckStudioCache( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *controller, byte *blending ) +static mstudiocache_t *Mod_CheckStudioCache( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *controller, byte *blending ) { mstudiocache_t *pCached; int i; @@ -204,7 +192,7 @@ mstudiocache_t *Mod_CheckStudioCache( model_t *model, float frame, int sequence, SetStudioHullPlane ==================== */ -void Mod_SetStudioHullPlane( int planenum, int bone, int axis, float offset, const vec3_t size ) +static void Mod_SetStudioHullPlane( int planenum, int bone, int axis, float offset, const vec3_t size ) { mplane_t *pl = &studio_planes[planenum]; @@ -239,7 +227,7 @@ hull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t ang bSkipShield = false; *numhitboxes = 0; // assume error - if( mod_studiocache->value ) + if( mod_studiocache.value ) { bonecache = Mod_CheckStudioCache( model, frame, sequence, angles, origin, size, pcontroller, pblending ); @@ -270,6 +258,11 @@ hull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t ang for( i = j = 0; i < mod_studiohdr->numhitboxes; i++, j += 6 ) { + if( world.version == QBSP2_VERSION ) + studio_hull[i].clipnodes32 = (mclipnode32_t *)box_clipnodes32; + else + studio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16; + if( bSkipShield && i == 21 ) continue; // CS stuff @@ -286,7 +279,7 @@ hull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t ang // tell trace code about hitbox count *numhitboxes = (bSkipShield) ? (mod_studiohdr->numhitboxes - 1) : (mod_studiohdr->numhitboxes); - if( mod_studiocache->value ) + if( mod_studiocache.value ) Mod_AddToStudioCache( frame, sequence, angles, origin, size, pcontroller, pblending, model, studio_hull, *numhitboxes ); return studio_hull; @@ -383,13 +376,13 @@ static void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *p // add in programtic controllers pbone = (mstudiobone_t *)((byte *)mod_studiohdr + mod_studiohdr->boneindex); + memset( adj, 0, sizeof( adj )); Mod_StudioCalcBoneAdj( adj, pcontroller ); for( j = numbones - 1; j >= 0; j-- ) { i = boneused[j]; - R_StudioCalcBoneQuaternion( frame, s, &pbone[i], &panim[i], adj, q[i] ); - R_StudioCalcBonePosition( frame, s, &pbone[i], &panim[i], adj, pos[i] ); + R_StudioCalcBones( frame, s, &pbone[i], &panim[i], adj, pos[i], q[i] ); } if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; @@ -397,196 +390,6 @@ static void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *p if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f; } -/* -==================== -StudioCalcBoneQuaternion - -==================== -*/ -void R_StudioCalcBoneQuaternion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec4_t q ) -{ - vec3_t angles1; - vec3_t angles2; - int j, k; - - for( j = 0; j < 3; j++ ) - { - if( !panim || panim->offset[j+3] == 0 ) - { - angles2[j] = angles1[j] = pbone->value[j+3]; // default; - } - else - { - mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); - - k = frame; - - // debug - if( panimvalue->num.total < panimvalue->num.valid ) - k = 0; - - // find span of values that includes the frame we want - while( panimvalue->num.total <= k ) - { - k -= panimvalue->num.total; - panimvalue += panimvalue->num.valid + 1; - - // debug - if( panimvalue->num.total < panimvalue->num.valid ) - k = 0; - } - - // bah, missing blend! - if( panimvalue->num.valid > k ) - { - angles1[j] = panimvalue[k+1].value; - - if( panimvalue->num.valid > k + 1 ) - { - angles2[j] = panimvalue[k+2].value; - } - else - { - if( panimvalue->num.total > k + 1 ) - angles2[j] = angles1[j]; - else angles2[j] = panimvalue[panimvalue->num.valid+2].value; - } - } - else - { - angles1[j] = panimvalue[panimvalue->num.valid].value; - if( panimvalue->num.total > k + 1 ) - angles2[j] = angles1[j]; - else angles2[j] = panimvalue[panimvalue->num.valid+2].value; - } - - angles1[j] = pbone->value[j+3] + angles1[j] * pbone->scale[j+3]; - angles2[j] = pbone->value[j+3] + angles2[j] * pbone->scale[j+3]; - } - - if( pbone->bonecontroller[j+3] != -1 && adj != NULL ) - { - angles1[j] += adj[pbone->bonecontroller[j+3]]; - angles2[j] += adj[pbone->bonecontroller[j+3]]; - } - } - - if( !VectorCompare( angles1, angles2 )) - { - vec4_t q1, q2; - - AngleQuaternion( angles1, q1, true ); - AngleQuaternion( angles2, q2, true ); - QuaternionSlerp( q1, q2, s, q ); - } - else - { - AngleQuaternion( angles1, q, true ); - } -} - -/* -==================== -StudioCalcBonePosition - -==================== -*/ -void R_StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec3_t pos ) -{ - vec3_t origin1; - vec3_t origin2; - int j, k; - - for( j = 0; j < 3; j++ ) - { - if( !panim || panim->offset[j] == 0 ) - { - origin2[j] = origin1[j] = pbone->value[j]; // default; - } - else - { - mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); - - k = frame; - - // debug - if( panimvalue->num.total < panimvalue->num.valid ) - k = 0; - - // find span of values that includes the frame we want - while( panimvalue->num.total <= k ) - { - k -= panimvalue->num.total; - panimvalue += panimvalue->num.valid + 1; - - // debug - if( panimvalue->num.total < panimvalue->num.valid ) - k = 0; - } - - // bah, missing blend! - if( panimvalue->num.valid > k ) - { - origin1[j] = panimvalue[k+1].value; - - if( panimvalue->num.valid > k + 1 ) - { - origin2[j] = panimvalue[k+2].value; - } - else - { - if( panimvalue->num.total > k + 1 ) - origin2[j] = origin1[j]; - else origin2[j] = panimvalue[panimvalue->num.valid+2].value; - } - } - else - { - origin1[j] = panimvalue[panimvalue->num.valid].value; - if( panimvalue->num.total > k + 1 ) - origin2[j] = origin1[j]; - else origin2[j] = panimvalue[panimvalue->num.valid+2].value; - } - - origin1[j] = pbone->value[j] + origin1[j] * pbone->scale[j]; - origin2[j] = pbone->value[j] + origin2[j] * pbone->scale[j]; - } - - if( pbone->bonecontroller[j] != -1 && adj != NULL ) - { - origin1[j] += adj[pbone->bonecontroller[j]]; - origin2[j] += adj[pbone->bonecontroller[j]]; - } - } - - if( !VectorCompare( origin1, origin2 )) - { - VectorLerp( origin1, s, origin2, pos ); - } - else - { - VectorCopy( origin1, pos ); - } -} - -/* -==================== -StudioSlerpBones - -==================== -*/ -void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) -{ - int i; - - s = bound( 0.0f, s, 1.0f ); - - for( i = 0; i < numbones; i++ ) - { - QuaternionSlerp( q1[i], q2[i], s, q1[i] ); - VectorLerp( pos1[i], s, pos2[i], pos1[i] ); - } -} /* ==================== @@ -618,15 +421,15 @@ void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstud { string filepath, modelname, modelpath; - COM_FileBase( m_pSubModel->name, modelname ); + COM_FileBase( m_pSubModel->name, modelname, sizeof( modelname )); COM_ExtractFilePath( m_pSubModel->name, modelpath ); // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); buf = FS_LoadFile( filepath, &filesize, false ); - if( !buf || !filesize ) Host_Error( "StudioGetAnim: can't load %s\n", filepath ); - if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "StudioGetAnim: %s is corrupted\n", filepath ); + if( !buf || !filesize ) Host_Error( "%s: can't load %s\n", __func__, filepath ); + if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "%s: %s is corrupted\n", __func__, filepath ); Con_Printf( "loading: %s\n", filepath ); @@ -671,7 +474,7 @@ static void SV_StudioSetupBones( model_t *pModel, float frame, int sequence, con { // only show warn if sequence that out of range was specified intentionally if( sequence > mod_studiohdr->numseq ) - Con_Reportf( S_WARN "SV_StudioSetupBones: sequence %i/%i out of range for model %s\n", sequence, mod_studiohdr->numseq, pModel->name ); + Con_Reportf( S_WARN "%s: sequence %i/%i out of range for model %s\n", __func__, sequence, mod_studiohdr->numseq, pModel->name ); sequence = 0; } @@ -823,7 +626,7 @@ int Mod_HitgroupForStudioHull( int index ) StudioBoundVertex ==================== */ -void Mod_StudioBoundVertex( vec3_t mins, vec3_t maxs, int *numverts, const vec3_t vertex ) +static void Mod_StudioBoundVertex( vec3_t mins, vec3_t maxs, int *numverts, const vec3_t vertex ) { if((*numverts) == 0 ) ClearBounds( mins, maxs ); @@ -837,7 +640,7 @@ void Mod_StudioBoundVertex( vec3_t mins, vec3_t maxs, int *numverts, const vec3_ StudioAccumulateBoneVerts ==================== */ -void Mod_StudioAccumulateBoneVerts( vec3_t mins, vec3_t maxs, int *numverts, vec3_t bone_mins, vec3_t bone_maxs, int *numbones ) +static void Mod_StudioAccumulateBoneVerts( vec3_t mins, vec3_t maxs, int *numverts, vec3_t bone_mins, vec3_t bone_maxs, int *numbones ) { vec3_t delta; vec3_t point; @@ -920,7 +723,7 @@ void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean i { for( k = 0; k < pseqdesc->numframes; k++ ) { - R_StudioCalcBonePosition( k, 0, &pbones[j], panim, NULL, pos ); + R_StudioCalcBones( k, 0, &pbones[j], panim, NULL, pos, NULL ); Mod_StudioBoundVertex( vert_mins, vert_maxs, &bone_count, pos ); } } @@ -969,7 +772,7 @@ extract texture filename from modelname */ const char *Mod_StudioTexName( const char *modname ) { - static char texname[MAX_QPATH]; + static char texname[MAX_QPATH+1]; Q_strncpy( texname, modname, sizeof( texname )); COM_StripExtension( texname ); @@ -1009,7 +812,7 @@ static int Mod_StudioBodyVariations( model_t *mod ) R_StudioLoadHeader ================= */ -studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer ) +static studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer ) { byte *pin; studiohdr_t *phdr; @@ -1030,6 +833,22 @@ studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer ) return (studiohdr_t *)buffer; } +static studiohdr_t *Mod_MaybeTruncateStudioTextureData( model_t *mod ) +{ +#if XASH_LOW_MEMORY + studiohdr_t *phdr = (studiohdr_t *)mod->cache.data; + + mod->cache.data = Mem_Realloc( mod->mempool, mod->cache.data, phdr->texturedataindex ); + phdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr + phdr->length = phdr->texturedataindex; // update model size + + return phdr; +#else + // NOTE: some mods potentially might require full studio model data + return (studiohdr_t *)mod->cache.data; +#endif +} + /* ================= Mod_LoadStudioModel @@ -1037,106 +856,102 @@ Mod_LoadStudioModel */ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) { + char poolname[MAX_VA_STRING]; studiohdr_t *phdr; + qboolean textures_loaded = false; + + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); - loadmodel->type = mod_studio; + mod->mempool = Mem_AllocPool( poolname ); + mod->type = mod_studio; phdr = R_StudioLoadHeader( mod, buffer ); - if( !phdr ) return; // bad model + if( !phdr || phdr->length < sizeof( studiohdr_t )) // garbage value in length + return; // bad model - if( !Host_IsDedicated() ) - { - if( phdr->numtextures == 0 ) - { - studiohdr_t *thdr; - byte *in, *out; - void *buffer2 = NULL; - size_t size1, size2; - - buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false ); - thdr = R_StudioLoadHeader( mod, buffer2 ); - - if( !thdr ) - { - Con_Printf( S_WARN "Mod_LoadStudioModel: %s missing textures file\n", mod->name ); - if( buffer2 ) Mem_Free( buffer2 ); - } - else - { #if !XASH_DEDICATED - ref.dllFuncs.Mod_StudioLoadTextures( mod, thdr ); + if( !Host_IsDedicated( ) && phdr->numtextures == 0 ) + { + studiohdr_t *thdr; + void *buffer2; + + buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false ); + thdr = R_StudioLoadHeader( mod, buffer2 ); + + if( thdr != NULL ) + { + byte *in, *out; + size_t size1, size2; + + // TODO: Mod_StudioLoadTextures will crash if passed a merged studio model! + ref.dllFuncs.Mod_StudioLoadTextures( mod, thdr ); + textures_loaded = true; + + // give space for textures and skinrefs + size1 = thdr->numtextures * sizeof( mstudiotexture_t ); + size2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short ); + mod->cache.data = Mem_Calloc( mod->mempool, phdr->length + size1 + size2 ); + memcpy( mod->cache.data, buffer, phdr->length ); // copy main mdl buffer + phdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr + phdr->numskinfamilies = thdr->numskinfamilies; + phdr->numtextures = thdr->numtextures; + phdr->numskinref = thdr->numskinref; + phdr->textureindex = phdr->length; + phdr->skinindex = phdr->textureindex + size1; + + in = (byte *)thdr + thdr->textureindex; + out = (byte *)phdr + phdr->textureindex; + memcpy( out, in, size1 + size2 ); // copy textures + skinrefs + phdr->length += size1 + size2; + } + else Con_Printf( S_WARN "%s: %s missing textures file\n", __func__, mod->name ); + + if( buffer2 ) + Mem_Free( buffer2 ); // release T.mdl + } #endif - // give space for textures and skinrefs - size1 = thdr->numtextures * sizeof( mstudiotexture_t ); - size2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short ); - mod->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length + size1 + size2 ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); // copy main mdl buffer - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr - phdr->numskinfamilies = thdr->numskinfamilies; - phdr->numtextures = thdr->numtextures; - phdr->numskinref = thdr->numskinref; - phdr->textureindex = phdr->length; - phdr->skinindex = phdr->textureindex + size1; + if( !textures_loaded ) + { + // NOTE: don't modify source buffer because it's used for CRC computing + mod->cache.data = Mem_Calloc( mod->mempool, phdr->length ); + memcpy( mod->cache.data, buffer, phdr->length ); + phdr = mod->cache.data; - in = (byte *)thdr + thdr->textureindex; - out = (byte *)phdr + phdr->textureindex; - memcpy( out, in, size1 + size2 ); // copy textures + skinrefs - phdr->length += size1 + size2; - Mem_Free( buffer2 ); // release T.mdl - } - } - else - { - // NOTE: don't modify source buffer because it's used for CRC computing - loadmodel->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr #if !XASH_DEDICATED + if( !Host_IsDedicated( )) ref.dllFuncs.Mod_StudioLoadTextures( mod, phdr ); #endif - - // NOTE: we wan't keep raw textures in memory. just cutoff model pointer above texture base - loadmodel->cache.data = Mem_Realloc( loadmodel->mempool, loadmodel->cache.data, phdr->texturedataindex ); - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr - phdr->length = phdr->texturedataindex; // update model size - } } - else - { - // just copy model into memory - loadmodel->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); - phdr = loadmodel->cache.data; - } + // NOTE: we may not want to keep raw textures in memory. just cutoff model pointer above texture base + phdr = Mod_MaybeTruncateStudioTextureData( mod ); // setup bounding box if( !VectorCompare( vec3_origin, phdr->bbmin )) { // clipping bounding box - VectorCopy( phdr->bbmin, loadmodel->mins ); - VectorCopy( phdr->bbmax, loadmodel->maxs ); + VectorCopy( phdr->bbmin, mod->mins ); + VectorCopy( phdr->bbmax, mod->maxs ); } else if( !VectorCompare( vec3_origin, phdr->min )) { // movement bounding box - VectorCopy( phdr->min, loadmodel->mins ); - VectorCopy( phdr->max, loadmodel->maxs ); + VectorCopy( phdr->min, mod->mins ); + VectorCopy( phdr->max, mod->maxs ); } else { // well compute bounds from vertices and round to nearest even values - Mod_StudioComputeBounds( phdr, loadmodel->mins, loadmodel->maxs, true ); - RoundUpHullSize( loadmodel->mins ); - RoundUpHullSize( loadmodel->maxs ); + Mod_StudioComputeBounds( phdr, mod->mins, mod->maxs, true ); + RoundUpHullSize( mod->mins ); + RoundUpHullSize( mod->maxs ); } - loadmodel->numframes = Mod_StudioBodyVariations( loadmodel ); - loadmodel->radius = RadiusFromBounds( loadmodel->mins, loadmodel->maxs ); - loadmodel->flags = phdr->flags; // copy header flags + mod->numframes = Mod_StudioBodyVariations( mod ); + mod->radius = RadiusFromBounds( mod->mins, mod->maxs ); + mod->flags = phdr->flags; // copy header flags if( loaded ) *loaded = true; } @@ -1171,7 +986,7 @@ void Mod_InitStudioAPI( void ) pBlendIface = (STUDIOAPI)COM_GetProcAddress( svgame.hInstance, "Server_GetBlendingInterface" ); if( pBlendIface && pBlendIface( SV_BLENDING_INTERFACE_VERSION, &pBlendAPI, &gStudioAPI, &studio_transform, &studio_bones )) { - Con_Reportf( "SV_LoadProgs: ^2initailized Server Blending interface ^7ver. %i\n", SV_BLENDING_INTERFACE_VERSION ); + Con_Reportf( "%s: ^2initailized Server Blending interface ^7ver. %i\n", __func__, SV_BLENDING_INTERFACE_VERSION ); return; } diff --git a/engine/common/model.c b/engine/common/model.c index ad8e80ca..9f5e3c1d 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -28,10 +28,9 @@ static model_info_t mod_crcinfo[MAX_MODELS]; static model_t mod_known[MAX_MODELS]; static int mod_numknown = 0; poolhandle_t com_studiocache; // cache for submodels -convar_t *mod_studiocache; -convar_t *r_wadtextures; -convar_t *r_showhull; -model_t *loadmodel; +CVAR_DEFINE( mod_studiocache, "r_studiocache", "1", FCVAR_ARCHIVE, "enables studio cache for speedup tracing hitboxes" ); +CVAR_DEFINE_AUTO( r_wadtextures, "0", 0, "completely ignore textures in the bsp-file if enabled" ); +CVAR_DEFINE_AUTO( r_showhull, "0", 0, "draw collision hulls 1-3" ); /* =============================================================================== @@ -112,8 +111,14 @@ void Mod_FreeModel( model_t *mod ) if( mod->type == mod_brush && FBitSet( mod->flags, MODEL_WORLD ) ) { + world.version = 0; world.shadowdata = NULL; world.deluxedata = NULL; + + // data already freed by Mem_FreePool above + world.hull_models = NULL; + world.compressed_phs = NULL; + world.phsofs = NULL; } memset( mod, 0, sizeof( *mod )); @@ -134,9 +139,9 @@ Mod_Init void Mod_Init( void ) { com_studiocache = Mem_AllocPool( "Studio Cache" ); - mod_studiocache = Cvar_Get( "r_studiocache", "1", FCVAR_ARCHIVE, "enables studio cache for speedup tracing hitboxes" ); - r_wadtextures = Cvar_Get( "r_wadtextures", "0", 0, "completely ignore textures in the bsp-file if enabled" ); - r_showhull = Cvar_Get( "r_showhull", "0", 0, "draw collision hulls 1-3" ); + Cvar_RegisterVariable( &mod_studiocache ); + Cvar_RegisterVariable( &r_wadtextures ); + Cvar_RegisterVariable( &r_showhull ); Cmd_AddCommand( "mapstats", Mod_PrintWorldStats_f, "show stats for currently loaded map" ); Cmd_AddCommand( "modellist", Mod_Modellist_f, "display loaded models list" ); @@ -253,7 +258,7 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) { char tempname[MAX_QPATH]; fs_offset_t length = 0; - qboolean loaded; + qboolean loaded, loaded2 = false; byte *buf; model_info_t *p; @@ -274,7 +279,7 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) buf = FS_LoadFile( tempname, &length, false ); - if( !buf ) + if( !buf || length < sizeof( uint )) { memset( mod, 0, sizeof( model_t )); @@ -287,7 +292,6 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) Con_Reportf( "loading %s\n", mod->name ); mod->needload = NL_PRESENT; mod->type = mod_bad; - loadmodel = mod; // call the apropriate loader switch( *(uint *)buf ) @@ -296,17 +300,15 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) Mod_LoadStudioModel( mod, buf, &loaded ); break; case IDSPRITEHEADER: - Mod_LoadSpriteModel( mod, buf, &loaded, 0 ); + Mod_LoadSpriteModel( mod, buf, &loaded ); break; case IDALIASHEADER: - // REFTODO: move server-related code here - loaded = true; + Mod_LoadAliasModel( mod, buf, &loaded ); break; case Q1BSP_VERSION: case HLBSP_VERSION: case QBSP2_VERSION: Mod_LoadBrushModel( mod, buf, &loaded ); - // ref.dllFuncs.Mod_LoadModel( mod_brush, mod, buf, &loaded, 0 ); break; default: Mem_Free( buf ); @@ -314,6 +316,7 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) else Con_Printf( S_ERROR "%s has unknown format\n", tempname ); return NULL; } + if( loaded ) { if( world.loading ) @@ -326,16 +329,24 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) // let the server.dll load custom data svgame.physFuncs.Mod_ProcessUserData( mod, true, buf ); } + loaded2 = true; } #if !XASH_DEDICATED else { - loaded = ref.dllFuncs.Mod_ProcessRenderData( mod, true, buf ); + loaded2 = ref.dllFuncs.Mod_ProcessRenderData( mod, true, buf ); } #endif } - if( !loaded ) + if( mod->type == mod_alias ) + { + aliashdr_t *hdr = mod->cache.data; + if( hdr ) // clean up temporary pointer after passing the alias model to the renderer + hdr->pposeverts = NULL; + } + + if( !loaded || !loaded2 ) { Mod_FreeModel( mod ); Mem_Free( buf ); @@ -403,7 +414,7 @@ static void Mod_PurgeStudioCache( void ) int i; // refresh hull data - SetBits( r_showhull->flags, FCVAR_CHANGED ); + SetBits( r_showhull.flags, FCVAR_CHANGED ); #if !XASH_DEDICATED Mod_ReleaseHullPolygons(); #endif @@ -600,3 +611,41 @@ void Mod_NeedCRC( const char *name, qboolean needCRC ) if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); } + +#if XASH_ENGINE_TESTS + +static const uint8_t *fuzz_data; +static size_t fuzz_size; + +static byte *Fuzz_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) +{ + byte *buf = Mem_Malloc( host.mempool, fuzz_size ); + memcpy( buf, fuzz_data, fuzz_size ); + *filesizeptr = fuzz_size; + return buf; +} + +int EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size ); +int EXPORT Fuzz_Mod_LoadModel( const uint8_t *Data, size_t Size ) +{ + model_t mod = { .name = "test", .needload = NL_NEEDS_LOADED }; + + Memory_Init(); + + host.type = HOST_DEDICATED; + host.mempool = Mem_AllocPool( "fuzzing pool" ); + fuzz_data = Data; + fuzz_size = Size; + refState.draw_surfaces = NULL; + + g_fsapi.LoadFile = Fuzz_LoadFile; + + if( Mod_LoadModel( &mod, false ) && mod.mempool ) + Mem_FreePool( &mod.mempool ); + + Mem_FreePool( &host.mempool ); + + return 0; +} + +#endif // XASH_ENGINE_TESTS diff --git a/engine/common/munge.c b/engine/common/munge.c new file mode 100644 index 00000000..4f16ef13 --- /dev/null +++ b/engine/common/munge.c @@ -0,0 +1,161 @@ +/* +munge.c - protocol mangling for GoldSrc +Copyright (C) ReHLDS developers +Copyright (C) 2023 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "common.h" + +static const byte mungify_table[] = +{ + 0x7A, 0x64, 0x05, 0xF1, 0x1B, 0x9B, 0xA0, 0xB5, + 0xCA, 0xED, 0x61, 0x0D, 0x4A, 0xDF, 0x8E, 0xC7, +}; + +static const byte mungify_table2[] = +{ + 0x05, 0x61, 0x7A, 0xED, 0x1B, 0xCA, 0x0D, 0x9B, + 0x4A, 0xF1, 0x64, 0xC7, 0xB5, 0x8E, 0xDF, 0xA0, +}; + +static const byte mungify_table3[] = +{ + 0x20, 0x07, 0x13, 0x61, 0x03, 0x45, 0x17, 0x72, + 0x0A, 0x2D, 0x48, 0x0C, 0x4A, 0x12, 0xA9, 0xB5, +}; + +static uint COM_SwapLong( uint c ) +{ + return ((( c >> 0 ) & 0xFF) << 24 ) + + ((( c >> 8 ) & 0xFF) << 16 ) + + ((( c >> 16 ) & 0xFF) << 8 ) + + ((( c >> 24 ) & 0xFF) << 0 ); +} + +static void COM_GenericMunge( byte *data, const size_t len, const int seq, const byte *table, const qboolean reverse ) +{ + const size_t mungelen = len / 4; + int i; + + for( i = 0; i < mungelen; i++ ) + { + uint32_t c; + void *pc = &data[i * 4]; + byte *p; + int j; + + memcpy( &c, pc, sizeof( c )); + c ^= seq; + if( !reverse ) + c = COM_SwapLong( c ); + + p = (byte *)&c; + for( j = 0; j < 4; j++ ) + *p++ ^= (0xa5 | (j << j) | j | table[(i + j) & 0x0f]); + + if( reverse ) + c = COM_SwapLong( c ); + c ^= ~seq; + memcpy( pc, &c, sizeof( c )); + } +} + +// Anti-proxy/aimbot obfuscation code +// COM_UnMunge should reversably fixup the data +void COM_Munge( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table, false ); +} + +void COM_UnMunge( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table, true ); +} + +void COM_Munge2( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table2, false ); +} + +void COM_UnMunge2( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table2, true ); +} + +void COM_Munge3( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table3, false ); +} + +void COM_UnMunge3( byte *data, size_t len, int seq ) +{ + COM_GenericMunge( data, len, seq, mungify_table3, true ); +} + +#if XASH_ENGINE_TESTS +#include "tests.h" + +void Test_RunMunge( void ) +{ + const char *msg = "0123456789qwertyuiopasdfghjklzxcvbnmaa"; + const char *expected[][3] = + { + { + "\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", + "\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", + "\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", + }, { + "\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", + "\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", + "\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", + } + }; + string buf; + size_t msglen = Q_strlen( msg ) + 1; + int i; + + Q_strncpy( buf, msg, msglen ); + + for( i = 0; i < 0xFF; i++ ) + { + COM_Munge( buf, msglen, i ); + if( i < sizeof( expected ) / sizeof( expected[0] )) + { + //for( int j = 0; j < msglen; j++ ) + // printf( "\\x%02x", buf[j] ); + //printf( "\n" ); + TASSERT( !memcmp( buf, expected[i][0], msglen )); + } + COM_UnMunge( buf, msglen, i ); + + TASSERT( !Q_strcmp( buf, msg )); + + COM_Munge2( buf, msglen, i ); + if( i < sizeof( expected ) / sizeof( expected[0] )) + { + TASSERT( !memcmp( buf, expected[i][1], msglen )); + } + COM_UnMunge2( buf, msglen, i ); + + TASSERT( !Q_strcmp( buf, msg )); + + COM_Munge3( buf, msglen, i ); + if( i < sizeof( expected ) / sizeof( expected[0] )) + { + TASSERT( !memcmp( buf, expected[i][2], msglen )); + } + COM_UnMunge3( buf, msglen, i ); + + TASSERT( !Q_strcmp( buf, msg )); + } +} +#endif diff --git a/engine/common/net_buffer.c b/engine/common/net_buffer.c index fbcd7a36..672f33b1 100644 --- a/engine/common/net_buffer.c +++ b/engine/common/net_buffer.c @@ -18,174 +18,337 @@ GNU General Public License for more details. #include "net_buffer.h" #include "xash3d_mathlib.h" -//#define DEBUG_NET_MESSAGES_SEND -//#define DEBUG_NET_MESSAGES_READ +static const uint32_t BitWriteMasks[32][32] = +{ { + 0xfffffffe, 0xfffffffc, 0xfffffff8, 0xfffffff0, 0xffffffe0, 0xffffffc0, 0xffffff80, 0xffffff00, + 0xfffffe00, 0xfffffc00, 0xfffff800, 0xfffff000, 0xffffe000, 0xffffc000, 0xffff8000, 0xffff0000, + 0xfffe0000, 0xfffc0000, 0xfff80000, 0xfff00000, 0xffe00000, 0xffc00000, 0xff800000, 0xff000000, + 0xfe000000, 0xfc000000, 0xf8000000, 0xf0000000, 0xe0000000, 0xc0000000, 0x80000000, 0x00000000, +}, { + 0xfffffffd, 0xfffffff9, 0xfffffff1, 0xffffffe1, 0xffffffc1, 0xffffff81, 0xffffff01, 0xfffffe01, + 0xfffffc01, 0xfffff801, 0xfffff001, 0xffffe001, 0xffffc001, 0xffff8001, 0xffff0001, 0xfffe0001, + 0xfffc0001, 0xfff80001, 0xfff00001, 0xffe00001, 0xffc00001, 0xff800001, 0xff000001, 0xfe000001, + 0xfc000001, 0xf8000001, 0xf0000001, 0xe0000001, 0xc0000001, 0x80000001, 0x00000001, 0x00000001, +}, { + 0xfffffffb, 0xfffffff3, 0xffffffe3, 0xffffffc3, 0xffffff83, 0xffffff03, 0xfffffe03, 0xfffffc03, + 0xfffff803, 0xfffff003, 0xffffe003, 0xffffc003, 0xffff8003, 0xffff0003, 0xfffe0003, 0xfffc0003, + 0xfff80003, 0xfff00003, 0xffe00003, 0xffc00003, 0xff800003, 0xff000003, 0xfe000003, 0xfc000003, + 0xf8000003, 0xf0000003, 0xe0000003, 0xc0000003, 0x80000003, 0x00000003, 0x00000003, 0x00000003, +}, { + 0xfffffff7, 0xffffffe7, 0xffffffc7, 0xffffff87, 0xffffff07, 0xfffffe07, 0xfffffc07, 0xfffff807, + 0xfffff007, 0xffffe007, 0xffffc007, 0xffff8007, 0xffff0007, 0xfffe0007, 0xfffc0007, 0xfff80007, + 0xfff00007, 0xffe00007, 0xffc00007, 0xff800007, 0xff000007, 0xfe000007, 0xfc000007, 0xf8000007, + 0xf0000007, 0xe0000007, 0xc0000007, 0x80000007, 0x00000007, 0x00000007, 0x00000007, 0x00000007, +}, { + 0xffffffef, 0xffffffcf, 0xffffff8f, 0xffffff0f, 0xfffffe0f, 0xfffffc0f, 0xfffff80f, 0xfffff00f, + 0xffffe00f, 0xffffc00f, 0xffff800f, 0xffff000f, 0xfffe000f, 0xfffc000f, 0xfff8000f, 0xfff0000f, + 0xffe0000f, 0xffc0000f, 0xff80000f, 0xff00000f, 0xfe00000f, 0xfc00000f, 0xf800000f, 0xf000000f, + 0xe000000f, 0xc000000f, 0x8000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, 0x0000000f, +}, { + 0xffffffdf, 0xffffff9f, 0xffffff1f, 0xfffffe1f, 0xfffffc1f, 0xfffff81f, 0xfffff01f, 0xffffe01f, + 0xffffc01f, 0xffff801f, 0xffff001f, 0xfffe001f, 0xfffc001f, 0xfff8001f, 0xfff0001f, 0xffe0001f, + 0xffc0001f, 0xff80001f, 0xff00001f, 0xfe00001f, 0xfc00001f, 0xf800001f, 0xf000001f, 0xe000001f, + 0xc000001f, 0x8000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, 0x0000001f, +}, { + 0xffffffbf, 0xffffff3f, 0xfffffe3f, 0xfffffc3f, 0xfffff83f, 0xfffff03f, 0xffffe03f, 0xffffc03f, + 0xffff803f, 0xffff003f, 0xfffe003f, 0xfffc003f, 0xfff8003f, 0xfff0003f, 0xffe0003f, 0xffc0003f, + 0xff80003f, 0xff00003f, 0xfe00003f, 0xfc00003f, 0xf800003f, 0xf000003f, 0xe000003f, 0xc000003f, + 0x8000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, 0x0000003f, +}, { + 0xffffff7f, 0xfffffe7f, 0xfffffc7f, 0xfffff87f, 0xfffff07f, 0xffffe07f, 0xffffc07f, 0xffff807f, + 0xffff007f, 0xfffe007f, 0xfffc007f, 0xfff8007f, 0xfff0007f, 0xffe0007f, 0xffc0007f, 0xff80007f, + 0xff00007f, 0xfe00007f, 0xfc00007f, 0xf800007f, 0xf000007f, 0xe000007f, 0xc000007f, 0x8000007f, + 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, 0x0000007f, +}, { + 0xfffffeff, 0xfffffcff, 0xfffff8ff, 0xfffff0ff, 0xffffe0ff, 0xffffc0ff, 0xffff80ff, 0xffff00ff, + 0xfffe00ff, 0xfffc00ff, 0xfff800ff, 0xfff000ff, 0xffe000ff, 0xffc000ff, 0xff8000ff, 0xff0000ff, + 0xfe0000ff, 0xfc0000ff, 0xf80000ff, 0xf00000ff, 0xe00000ff, 0xc00000ff, 0x800000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, +}, { + 0xfffffdff, 0xfffff9ff, 0xfffff1ff, 0xffffe1ff, 0xffffc1ff, 0xffff81ff, 0xffff01ff, 0xfffe01ff, + 0xfffc01ff, 0xfff801ff, 0xfff001ff, 0xffe001ff, 0xffc001ff, 0xff8001ff, 0xff0001ff, 0xfe0001ff, + 0xfc0001ff, 0xf80001ff, 0xf00001ff, 0xe00001ff, 0xc00001ff, 0x800001ff, 0x000001ff, 0x000001ff, + 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, 0x000001ff, +}, { + 0xfffffbff, 0xfffff3ff, 0xffffe3ff, 0xffffc3ff, 0xffff83ff, 0xffff03ff, 0xfffe03ff, 0xfffc03ff, + 0xfff803ff, 0xfff003ff, 0xffe003ff, 0xffc003ff, 0xff8003ff, 0xff0003ff, 0xfe0003ff, 0xfc0003ff, + 0xf80003ff, 0xf00003ff, 0xe00003ff, 0xc00003ff, 0x800003ff, 0x000003ff, 0x000003ff, 0x000003ff, + 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, 0x000003ff, +}, { + 0xfffff7ff, 0xffffe7ff, 0xffffc7ff, 0xffff87ff, 0xffff07ff, 0xfffe07ff, 0xfffc07ff, 0xfff807ff, + 0xfff007ff, 0xffe007ff, 0xffc007ff, 0xff8007ff, 0xff0007ff, 0xfe0007ff, 0xfc0007ff, 0xf80007ff, + 0xf00007ff, 0xe00007ff, 0xc00007ff, 0x800007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, + 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, 0x000007ff, +}, { + 0xffffefff, 0xffffcfff, 0xffff8fff, 0xffff0fff, 0xfffe0fff, 0xfffc0fff, 0xfff80fff, 0xfff00fff, + 0xffe00fff, 0xffc00fff, 0xff800fff, 0xff000fff, 0xfe000fff, 0xfc000fff, 0xf8000fff, 0xf0000fff, + 0xe0000fff, 0xc0000fff, 0x80000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, + 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, 0x00000fff, +}, { + 0xffffdfff, 0xffff9fff, 0xffff1fff, 0xfffe1fff, 0xfffc1fff, 0xfff81fff, 0xfff01fff, 0xffe01fff, + 0xffc01fff, 0xff801fff, 0xff001fff, 0xfe001fff, 0xfc001fff, 0xf8001fff, 0xf0001fff, 0xe0001fff, + 0xc0001fff, 0x80001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, + 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, 0x00001fff, +}, { + 0xffffbfff, 0xffff3fff, 0xfffe3fff, 0xfffc3fff, 0xfff83fff, 0xfff03fff, 0xffe03fff, 0xffc03fff, + 0xff803fff, 0xff003fff, 0xfe003fff, 0xfc003fff, 0xf8003fff, 0xf0003fff, 0xe0003fff, 0xc0003fff, + 0x80003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, + 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, 0x00003fff, +}, { + 0xffff7fff, 0xfffe7fff, 0xfffc7fff, 0xfff87fff, 0xfff07fff, 0xffe07fff, 0xffc07fff, 0xff807fff, + 0xff007fff, 0xfe007fff, 0xfc007fff, 0xf8007fff, 0xf0007fff, 0xe0007fff, 0xc0007fff, 0x80007fff, + 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, + 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, 0x00007fff, +}, { + 0xfffeffff, 0xfffcffff, 0xfff8ffff, 0xfff0ffff, 0xffe0ffff, 0xffc0ffff, 0xff80ffff, 0xff00ffff, + 0xfe00ffff, 0xfc00ffff, 0xf800ffff, 0xf000ffff, 0xe000ffff, 0xc000ffff, 0x8000ffff, 0x0000ffff, + 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, +}, { + 0xfffdffff, 0xfff9ffff, 0xfff1ffff, 0xffe1ffff, 0xffc1ffff, 0xff81ffff, 0xff01ffff, 0xfe01ffff, + 0xfc01ffff, 0xf801ffff, 0xf001ffff, 0xe001ffff, 0xc001ffff, 0x8001ffff, 0x0001ffff, 0x0001ffff, + 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, + 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, 0x0001ffff, +}, { + 0xfffbffff, 0xfff3ffff, 0xffe3ffff, 0xffc3ffff, 0xff83ffff, 0xff03ffff, 0xfe03ffff, 0xfc03ffff, + 0xf803ffff, 0xf003ffff, 0xe003ffff, 0xc003ffff, 0x8003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, + 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, + 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, 0x0003ffff, +}, { + 0xfff7ffff, 0xffe7ffff, 0xffc7ffff, 0xff87ffff, 0xff07ffff, 0xfe07ffff, 0xfc07ffff, 0xf807ffff, + 0xf007ffff, 0xe007ffff, 0xc007ffff, 0x8007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, + 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, + 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, 0x0007ffff, +}, { + 0xffefffff, 0xffcfffff, 0xff8fffff, 0xff0fffff, 0xfe0fffff, 0xfc0fffff, 0xf80fffff, 0xf00fffff, + 0xe00fffff, 0xc00fffff, 0x800fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, + 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, + 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, 0x000fffff, +}, { + 0xffdfffff, 0xff9fffff, 0xff1fffff, 0xfe1fffff, 0xfc1fffff, 0xf81fffff, 0xf01fffff, 0xe01fffff, + 0xc01fffff, 0x801fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, + 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, + 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, 0x001fffff, +}, { + 0xffbfffff, 0xff3fffff, 0xfe3fffff, 0xfc3fffff, 0xf83fffff, 0xf03fffff, 0xe03fffff, 0xc03fffff, + 0x803fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, + 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, + 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, 0x003fffff, +}, { + 0xff7fffff, 0xfe7fffff, 0xfc7fffff, 0xf87fffff, 0xf07fffff, 0xe07fffff, 0xc07fffff, 0x807fffff, + 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, + 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, + 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, 0x007fffff, +}, { + 0xfeffffff, 0xfcffffff, 0xf8ffffff, 0xf0ffffff, 0xe0ffffff, 0xc0ffffff, 0x80ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, + 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, +}, { + 0xfdffffff, 0xf9ffffff, 0xf1ffffff, 0xe1ffffff, 0xc1ffffff, 0x81ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, +}, { + 0xfbffffff, 0xf3ffffff, 0xe3ffffff, 0xc3ffffff, 0x83ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, + 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, + 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, + 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, +}, { + 0xf7ffffff, 0xe7ffffff, 0xc7ffffff, 0x87ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, + 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, + 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, + 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, 0x07ffffff, +}, { + 0xefffffff, 0xcfffffff, 0x8fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, + 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, + 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, + 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, 0x0fffffff, +}, { + 0xdfffffff, 0x9fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff, +}, { + 0xbfffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, + 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, + 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, + 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, 0x3fffffff, +}, { + 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, + 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, + 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, + 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff, +} }; -// precalculated bit masks for WriteUBitLong. -// Using these tables instead of doing the calculations -// gives a 33% speedup in WriteUBitLong. -static dword BitWriteMasks[32][33]; -static dword ExtraMasks[32]; - -unsigned short MSG_BigShort( unsigned short swap ) +static const uint32_t ExtraMasks[32] = { - return (swap >> 8)|(swap << 8); -} + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f, + 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff, + 0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, + 0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, +}; -void MSG_InitMasks( void ) +static const char *const clc_strings[clc_lastmsg+1] = { - uint startbit, endbit; - uint maskBit, nBitsLeft; + "clc_bad", + "clc_nop", + "clc_move", + "clc_stringcmd", + "clc_delta", + "clc_resourcelist", + "clc_legacy_userinfo", + "clc_fileconsistency", + "clc_voicedata", + "clc_cvarvalue/clc_goldsrc_hltv", + "clc_cvarvalue2/clc_goldsrc_requestcvarvalue", + "clc_goldsrc_requestcvarvalue2", +}; - for( startbit = 0; startbit < 32; startbit++ ) - { - for( nBitsLeft = 0; nBitsLeft < 33; nBitsLeft++ ) - { - endbit = startbit + nBitsLeft; - - BitWriteMasks[startbit][nBitsLeft] = (uint)BIT( startbit ) - 1; - if( endbit < 32 ) BitWriteMasks[startbit][nBitsLeft] |= ~((uint)BIT( endbit ) - 1 ); - } - } - - for( maskBit = 0; maskBit < 32; maskBit++ ) - ExtraMasks[maskBit] = (uint)BIT( maskBit ) - 1; -} - -void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nMaxBits ) +const char *const svc_strings[svc_lastmsg+1] = { - MSG_StartWriting( sb, pData, nBytes, 0, nMaxBits ); + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_event", + "svc_changing", + "svc_setview", + "svc_sound", + "svc_time", + "svc_print", + "svc_stufftext", + "svc_setangle", + "svc_serverdata", + "svc_lightstyle", + "svc_updateuserinfo", + "svc_deltatable", + "svc_clientdata", + "svc_resource", + "svc_pings", + "svc_particle", + "svc_restoresound", + "svc_spawnstatic", + "svc_event_reliable", + "svc_spawnbaseline", + "svc_temp_entity", + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_unused27", + "svc_unused28", + "svc_unused29", + "svc_intermission", + "svc_finale", + "svc_cdtrack", + "svc_restore", + "svc_cutscene", + "svc_weaponanim", + "svc_bspdecal", + "svc_roomtype", + "svc_addangle", + "svc_usermessage", + "svc_packetentities", + "svc_deltapacketentities", + "svc_choke", + "svc_resourcelist", + "svc_deltamovevars", + "svc_resourcerequest", + "svc_customization", + "svc_crosshairangle", + "svc_soundfade", + "svc_filetxferfailed", + "svc_hltv", + "svc_director", + "svc_voiceinit", + "svc_voicedata", + "svc_unused54", + "svc_unused55", + "svc_resourcelocation", + "svc_querycvarvalue", + "svc_querycvarvalue2", + "svc_exec", +}; - sb->pDebugName = pDebugName; -} - -void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits ) +const char *const svc_legacy_strings[svc_lastmsg+1] = { - // make sure it's dword aligned and padded. - Assert(((dword)pData & 3 ) == 0 ); + [svc_legacy_changing] = "svc_legacy_changing", + [svc_legacy_ambientsound] = "svc_legacy_ambientsound", + [svc_legacy_soundindex] = "svc_legacy_soundindex", + [svc_legacy_ambientsound] = "svc_legacy_ambientsound", + [svc_legacy_modelindex] = "svc_legacy_modelindex", + [svc_legacy_eventindex] = "svc_legacy_eventindex", + [svc_legacy_chokecount] = "svc_legacy_chokecount", +}; - sb->pDebugName = "Unnamed"; - sb->pData = (byte *)pData; - - if( nBits == -1 ) - { - sb->nDataBits = nBytes << 3; - } - else - { - Assert( nBits <= nBytes * 8 ); - sb->nDataBits = nBits; - } - - sb->iCurBit = iStartBit; - sb->bOverflow = false; -} - -/* -======================= -MSG_Clear - -for clearing overflowed buffer -======================= -*/ -void MSG_Clear( sizebuf_t *sb ) +const char *const svc_goldsrc_strings[svc_lastmsg+1] = { - sb->iCurBit = 0; - sb->bOverflow = false; -} + [svc_goldsrc_version] = "svc_goldsrc_version", + [svc_goldsrc_stopsound] = "svc_goldsrc_stopsound", + [svc_goldsrc_damage] = "svc_goldsrc_damage", + [svc_goldsrc_killedmonster] = "svc_goldsrc_killedmonster", + [svc_goldsrc_foundsecret] = "svc_goldsrc_foundsecret", + [svc_goldsrc_spawnstaticsound] = "svc_goldsrc_spawnstaticsound", + [svc_goldsrc_decalname] = "svc_goldsrc_decalname", + [svc_goldsrc_sendextrainfo] = "svc_goldsrc_sendextrainfo", + [svc_goldsrc_timescale] = "svc_goldsrc_timescale", +}; -static qboolean MSG_Overflow( sizebuf_t *sb, int nBits ) +const char *const svc_quake_strings[svc_lastmsg+1] = { - if( sb->iCurBit + nBits > sb->nDataBits ) - sb->bOverflow = true; - return sb->bOverflow; -} - -qboolean MSG_CheckOverflow( sizebuf_t *sb ) -{ - return MSG_Overflow( sb, 0 ); -} - -int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ) -{ - // compute the file offset - switch( whence ) - { - case SEEK_CUR: - bitPos += sb->iCurBit; - break; - case SEEK_SET: - break; - case SEEK_END: - bitPos += sb->nDataBits; - break; - default: - return -1; - } - - if( bitPos < 0 || bitPos > sb->nDataBits ) - return -1; - - sb->iCurBit = bitPos; - - return 0; -} - -void MSG_SeekToByte( sizebuf_t *sb, int bytePos ) -{ - sb->iCurBit = bytePos << 3; -} - -void MSG_WriteOneBit( sizebuf_t *sb, int nValue ) -{ - if( !MSG_Overflow( sb, 1 )) - { - if( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 ); - else sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 ); - - sb->iCurBit++; - } -} + [svc_updatestat] = "svc_quake_updatestat", + [svc_version] = "svc_quake_version", + [svc_updatename] = "svc_quake_updatename", + [svc_updatefrags] = "svc_quake_updatefrags", + [svc_stopsound] = "svc_quake_stopsound", + [svc_updatecolors] = "svc_quake_updatecolors", + [svc_damage] = "svc_quake_damage", + [svc_spawnbinary] = "svc_quake_spawnbinary", + [svc_killedmonster] = "svc_quake_killedmonster", + [svc_foundsecret] = "svc_quake_foundsecret", + [svc_spawnstaticsound] = "svc_quake_spawnstaticsound", + [svc_sellscreen] = "svc_quake_sellscreen", + [svc_showlmp] = "svc_quake_showlmp", + [svc_hidelmp] = "svc_quake_hidelmp", + [svc_skybox] = "svc_quake_skybox", + [svc_skyboxsize] = "svc_quake_skyboxsize", + [svc_fog] = "svc_quake_fog", +}; void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ) { - Assert( numbits >= 0 && numbits <= 32 ); + int nBitsLeft = numbits; + int iCurBit = sb->iCurBit; + uint iDWord = iCurBit >> 5; // Mask in a dword. + uint32_t iCurBitMasked; + int nBitsWritten; + + Assert( numbits >= 1 && numbits <= 32 ); // bounds checking.. - if(( sb->iCurBit + numbits ) > sb->nDataBits ) + if( MSG_Overflow( sb, numbits )) { - sb->bOverflow = true; sb->iCurBit = sb->nDataBits; + return; } - else - { - int nBitsLeft = numbits; - int iCurBit = sb->iCurBit; - uint iDWord = iCurBit >> 5; // Mask in a dword. - dword iCurBitMasked; - int nBitsWritten; - Assert(( iDWord * 4 + sizeof( int )) <= (uint)MSG_GetMaxBytes( sb )); + iCurBitMasked = iCurBit & 31; + ((uint32_t *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1]; + ((uint32_t *)sb->pData)[iDWord] |= curData << iCurBitMasked; + + // did it span a dword? + nBitsWritten = 32 - iCurBitMasked; + + if( nBitsWritten < nBitsLeft ) + { + nBitsLeft -= nBitsWritten; + iCurBit += nBitsWritten; + curData >>= nBitsWritten; iCurBitMasked = iCurBit & 31; - ((dword *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft]; - ((dword *)sb->pData)[iDWord] |= curData << iCurBitMasked; - - // did it span a dword? - nBitsWritten = 32 - iCurBitMasked; - - if( nBitsWritten < nBitsLeft ) - { - nBitsLeft -= nBitsWritten; - iCurBit += nBitsWritten; - curData >>= nBitsWritten; - - iCurBitMasked = iCurBit & 31; - ((dword *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft]; - ((dword *)sb->pData)[iDWord+1] |= curData << iCurBitMasked; - } - sb->iCurBit += numbits; + ((uint32_t *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft-1]; + ((uint32_t *)sb->pData)[iDWord+1] |= curData << iCurBitMasked; } + sb->iCurBit += numbits; } /* @@ -200,17 +363,23 @@ void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ) // do we have a valid # of bits to encode with? Assert( numbits >= 1 && numbits <= 32 ); - // NOTE: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. - // (Some old code writes direct integers right into the buffer). - if( data < 0 ) + if( sb->iAlternateSign ) { - MSG_WriteUBitLong( sb, (uint)( 0x80000000 + data ), numbits - 1 ); - MSG_WriteOneBit( sb, 1 ); + MSG_WriteOneBit( sb, data < 0 ? 1 : 0 ); + MSG_WriteUBitLong( sb, (uint)abs( data ), numbits - 1 ); } else { - MSG_WriteUBitLong( sb, (uint)data, numbits - 1 ); - MSG_WriteOneBit( sb, 0 ); + if( data < 0 ) + { + MSG_WriteUBitLong( sb, (uint)( 0x80000000 + data ), numbits - 1 ); + MSG_WriteOneBit( sb, 1 ); + } + else + { + MSG_WriteUBitLong( sb, (uint)data, numbits - 1 ); + MSG_WriteOneBit( sb, 0 ); + } } } @@ -227,7 +396,7 @@ qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ) int nBitsLeft = nBits; // get output dword-aligned. - while((( dword )pOut & 3 ) != 0 && nBitsLeft >= 8 ) + while((( uintptr_t )pOut & 3 ) != 0 && nBitsLeft >= 8 ) { MSG_WriteUBitLong( sb, *pOut, 8 ); @@ -238,9 +407,9 @@ qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ) // read dwords. while( nBitsLeft >= 32 ) { - MSG_WriteUBitLong( sb, *(( dword *)pOut ), 32 ); + MSG_WriteUBitLong( sb, *(( uint32_t *)pOut ), 32 ); - pOut += sizeof( dword ); + pOut += sizeof( uint32_t ); nBitsLeft -= 32; } @@ -264,16 +433,14 @@ qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ) void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits ) { - uint mask, shift; + const uint shift = ( 1 << numbits ); + const uint mask = shift - 1; int d; // clamp the angle before receiving fAngle = fmod( fAngle, 360.0f ); if( fAngle < 0 ) fAngle += 360.0f; - shift = ( 1 << numbits ); - mask = shift - 1; - d = (int)(( fAngle * shift ) / 360.0f ); d &= mask; @@ -302,72 +469,63 @@ void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ) MSG_WriteBitAngle( sb, fa[2], 16 ); } -void MSG_WriteBitFloat( sizebuf_t *sb, float val ) -{ - int intVal; - - Assert( sizeof( int ) == sizeof( float )); - Assert( sizeof( float ) == 4 ); - - intVal = *((int *)&val ); - MSG_WriteUBitLong( sb, intVal, 32 ); -} - void MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name ) { -#ifdef DEBUG_NET_MESSAGES_SEND - if( name != NULL ) + if( unlikely( net_send_debug.value )) { - // get custom name - Con_Printf( "^1sv^7 write: %s\n", name ); - } - else if( type == NS_SERVER ) - { - if( cmd >= 0 && cmd <= svc_lastmsg ) + if( name != NULL ) { - // get engine message name - Con_Printf( "^1sv^7 write: %s\n", svc_strings[cmd] ); + // get custom name + Con_Printf( "^1sv^7 (%d) write: %s\n", sb->iCurBit, name ); + } + else if( type == NS_SERVER ) + { + if( cmd >= 0 && cmd <= svc_lastmsg ) + { + // get engine message name + Con_Printf( "^1sv^7 (%d) write: %s\n", sb->iCurBit, svc_strings[cmd] ); + } + } + else if( type == NS_CLIENT ) + { + if( cmd >= 0 && cmd <= clc_lastmsg && cmd != clc_nop ) + { + Con_Printf( "^1cl^7 (%d) write: %s\n", sb->iCurBit, clc_strings[cmd] ); + } } } - else if( type == NS_CLIENT ) - { - if( cmd >= 0 && cmd <= clc_lastmsg ) - { - Con_Printf( "^1cl^7 write: %s\n", clc_strings[cmd] ); - } - } -#endif - MSG_WriteUBitLong( sb, cmd, sizeof( byte ) << 3 ); + + MSG_WriteUBitLong( sb, cmd, sizeof( uint8_t ) << 3 ); } void MSG_WriteChar( sizebuf_t *sb, int val ) { - MSG_WriteSBitLong( sb, val, sizeof( char ) << 3 ); + MSG_WriteSBitLong( sb, val, sizeof( int8_t ) << 3 ); } void MSG_WriteByte( sizebuf_t *sb, int val ) { - MSG_WriteUBitLong( sb, val, sizeof( byte ) << 3 ); + MSG_WriteUBitLong( sb, val, sizeof( uint8_t ) << 3 ); } void MSG_WriteShort( sizebuf_t *sb, int val ) { - MSG_WriteSBitLong( sb, val, sizeof(short ) << 3 ); + MSG_WriteSBitLong( sb, val, sizeof( int16_t ) << 3 ); } void MSG_WriteWord( sizebuf_t *sb, int val ) { - MSG_WriteUBitLong( sb, val, sizeof( word ) << 3 ); + MSG_WriteUBitLong( sb, val, sizeof( uint16_t ) << 3 ); } void MSG_WriteLong( sizebuf_t *sb, int val ) { - MSG_WriteSBitLong( sb, val, sizeof( int ) << 3 ); + MSG_WriteSBitLong( sb, val, sizeof( int32_t ) << 3 ); } -void MSG_WriteDword( sizebuf_t *sb, dword val ) +void MSG_WriteDword( sizebuf_t *sb, uint val ) { - MSG_WriteUBitLong( sb, val, sizeof( dword ) << 3 ); + MSG_WriteUBitLong( sb, val, sizeof( uint32_t ) << 3 ); } void MSG_WriteFloat( sizebuf_t *sb, float val ) @@ -382,15 +540,32 @@ qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ) qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ) { - if( pStr ) + int len = Q_strlen( pStr ); + + if( len ) + MSG_WriteBytes( sb, pStr, len + 1 ); + else MSG_WriteByte( sb, 0 ); + + return !sb->bOverflow; +} + +qboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... ) +{ + va_list va; + int len; + char buf[MAX_VA_STRING]; + + va_start( va, format ); + len = Q_vsnprintf( buf, sizeof( buf ), format, va ); + va_end( va ); + + if( len < 0 ) { - do - { - MSG_WriteChar( sb, (signed char)*pStr ); - pStr++; - } while( *( pStr - 1 )); + Host_Error( "%s: snprintf overflow!\n", __func__ ); + return false; } - else MSG_WriteChar( sb, 0 ); + + MSG_WriteBytes( sb, buf, len + 1 ); return !sb->bOverflow; } @@ -419,9 +594,8 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits ) return 0; // end of message } - if(( sb->iCurBit + numbits ) > sb->nDataBits ) + if( MSG_Overflow( sb, numbits )) { - sb->bOverflow = true; sb->iCurBit = sb->nDataBits; return 0; } @@ -454,39 +628,13 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits ) return ret; } -float MSG_ReadBitFloat( sizebuf_t *sb ) -{ - int val; - int bit, byte; - - Assert( sizeof( float ) == sizeof( int )); - Assert( sizeof( float ) == 4 ); - - if( MSG_Overflow( sb, 32 )) - return 0.0f; - - bit = sb->iCurBit & 0x7; - byte = sb->iCurBit >> 3; - - val = sb->pData[byte] >> bit; - val |= ((int)sb->pData[byte + 1]) << ( 8 - bit ); - val |= ((int)sb->pData[byte + 2]) << ( 16 - bit ); - val |= ((int)sb->pData[byte + 3]) << ( 24 - bit ); - - if( bit != 0 ) - val |= ((int)sb->pData[byte + 4]) << ( 32 - bit ); - sb->iCurBit += 32; - - return *((float *)&val); -} - qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ) { byte *pOut = (byte *)pOutData; int nBitsLeft = nBits; // get output dword-aligned. - while((( dword )pOut & 3) != 0 && nBitsLeft >= 8 ) + while((( uintptr_t )pOut & 3) != 0 && nBitsLeft >= 8 ) { *pOut = (byte)MSG_ReadUBitLong( sb, 8 ); ++pOut; @@ -496,8 +644,8 @@ qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ) // read dwords. while( nBitsLeft >= 32 ) { - *((dword *)pOut) = MSG_ReadUBitLong( sb, 32 ); - pOut += sizeof( dword ); + *((uint32_t *)pOut) = MSG_ReadUBitLong( sb, 32 ); + pOut += sizeof( uint32_t ); nBitsLeft -= 32; } @@ -520,13 +668,9 @@ qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ) float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ) { - float fReturn, shift; - int i; - - shift = (float)( 1 << numbits ); - - i = MSG_ReadUBitLong( sb, numbits ); - fReturn = (float)i * ( 360.0f / shift ); + float shift = (float)( 1 << numbits ); + int i = MSG_ReadUBitLong( sb, numbits ); + float fReturn = (float)i * ( 360.0f / shift ); // clamp the finale angle if( fReturn < -180.0f ) fReturn += 360.0f; @@ -538,14 +682,22 @@ float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ) // Append numbits least significant bits from data to the current bit stream int MSG_ReadSBitLong( sizebuf_t *sb, int numbits ) { - int r, sign; + int r; - r = MSG_ReadUBitLong( sb, numbits - 1 ); + if( sb->iAlternateSign ) + { + int sign = MSG_ReadOneBit( sb ); + r = MSG_ReadUBitLong( sb, numbits - 1 ); - // NOTE: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. - // (Some old code writes direct integers right into the buffer). - sign = MSG_ReadOneBit( sb ); - if( sign ) r = -( BIT( numbits - 1 ) - r ); + if( sign ) + r = -r; + } + else + { + r = MSG_ReadUBitLong( sb, numbits - 1 ); + if( MSG_ReadOneBit( sb )) + r = -( BIT( numbits - 1 ) - r ); + } return r; } @@ -559,39 +711,54 @@ uint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned ) int MSG_ReadCmd( sizebuf_t *sb, netsrc_t type ) { - int cmd = MSG_ReadUBitLong( sb, sizeof( byte ) << 3 ); + int cmd = MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 ); -#ifdef DEBUG_NET_MESSAGES_READ - if( type == NS_SERVER ) + if( unlikely( net_recv_debug.value )) { - Con_Printf( "^1cl^7 read: %s\n", CL_MsgInfo( cmd )); + if( type == NS_SERVER ) + { + if( cmd != svc_nop ) + Con_Printf( "^1cl^7 read: %s\n", CL_MsgInfo( cmd )); + } + else if( cmd >= 0 && cmd <= clc_lastmsg ) + { + Con_Printf( "^1sv^7 read: %s\n", clc_strings[cmd] ); + } } - else if( cmd >= 0 && cmd <= clc_lastmsg ) - { - Con_Printf( "^1sv^7 read: %s\n", clc_strings[cmd] ); - } -#endif + return cmd; } int MSG_ReadChar( sizebuf_t *sb ) { - return MSG_ReadSBitLong( sb, sizeof( char ) << 3 ); + int alt = sb->iAlternateSign, ret; + + sb->iAlternateSign = 0; + ret = MSG_ReadSBitLong( sb, sizeof( int8_t ) << 3 ); + sb->iAlternateSign = alt; + + return ret; } int MSG_ReadByte( sizebuf_t *sb ) { - return MSG_ReadUBitLong( sb, sizeof( byte ) << 3 ); + return MSG_ReadUBitLong( sb, sizeof( uint8_t ) << 3 ); } int MSG_ReadShort( sizebuf_t *sb ) { - return MSG_ReadSBitLong( sb, sizeof( short ) << 3 ); + int alt = sb->iAlternateSign, ret; + + sb->iAlternateSign = 0; + ret = MSG_ReadSBitLong( sb, sizeof( int16_t ) << 3 ); + sb->iAlternateSign = alt; + + return ret; } int MSG_ReadWord( sizebuf_t *sb ) { - return MSG_ReadUBitLong( sb, sizeof( word ) << 3 ); + return MSG_ReadUBitLong( sb, sizeof( uint16_t ) << 3 ); } float MSG_ReadCoord( sizebuf_t *sb ) @@ -618,21 +785,25 @@ void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa ) int MSG_ReadLong( sizebuf_t *sb ) { - return MSG_ReadSBitLong( sb, sizeof( int ) << 3 ); + int alt = sb->iAlternateSign, ret; + + sb->iAlternateSign = 0; + ret = MSG_ReadSBitLong( sb, sizeof( int32_t ) << 3 ); + sb->iAlternateSign = alt; + + return ret; } -dword MSG_ReadDword( sizebuf_t *sb ) +uint MSG_ReadDword( sizebuf_t *sb ) { - return MSG_ReadUBitLong( sb, sizeof( dword ) << 3 ); + return MSG_ReadUBitLong( sb, sizeof( uint32_t ) << 3 ); } float MSG_ReadFloat( sizebuf_t *sb ) { float ret; - Assert( sizeof( ret ) == 4 ); - - MSG_ReadBits( sb, &ret, 32 ); + MSG_ReadBits( sb, &ret, sizeof( ret ) << 3 ); return ret; } @@ -642,15 +813,15 @@ qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes ) return MSG_ReadBits( sb, pOut, nBytes << 3 ); } -char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ) +static char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ) { static char string[4096]; - int l = 0, c; + int l = 0; do { // use MSG_ReadByte so -1 is out of bounds - c = MSG_ReadByte( sb ); + int c = MSG_ReadByte( sb ); if( c == 0 ) break; else if( bLine && c == '\n' ) @@ -668,6 +839,16 @@ char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ) return string; } +char *MSG_ReadString( sizebuf_t *sb ) +{ + return MSG_ReadStringExt( sb, false ); +} + +char *MSG_ReadStringLine( sizebuf_t *sb ) +{ + return MSG_ReadStringExt( sb, true ); +} + void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ) { int i, endbit = startbit + bitstoremove; @@ -685,3 +866,158 @@ void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ) MSG_SeekToBit( sb, startbit, SEEK_SET ); sb->nDataBits -= bitstoremove; } + +#ifdef XASH_ENGINE_TESTS +#include "tests.h" + +static const void *g_testbuf = "asdf\xba\xa1\xba\xa1\xed\xc8\x15\x7a"; +static const size_t g_testbuf_bits = (( 4 + 4 + 2 + 1 ) << 3 ) + 4; + +static void Test_Buffer_BitByte( void ) +{ + TASSERT_EQi( BitByte( 0 ), 0 ); + TASSERT_EQi( BitByte( 1 ), 1 ); + TASSERT_EQi( BitByte( 8 ), 1 ); + TASSERT_EQi( BitByte( 9 ), 2 ); +} + +static void Test_Buffer_Write( void ) +{ + sizebuf_t sb; + char testdata[0x100] = { 0 }; + + MSG_Init( &sb, __func__, testdata, sizeof( testdata )); + TASSERT_EQi( sb.iCurBit, 0 ); + TASSERT_EQi( sb.nDataBits, sizeof( testdata ) << 3 ); + TASSERT_EQp( sb.pData, (void *)testdata ); + TASSERT_EQi( sb.bOverflow, false ); + + MSG_WriteBytes( &sb, "asdf", 4 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 32 ); + + MSG_WriteDword( &sb, 0xa1baa1ba ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 64 ); + + MSG_WriteShort( &sb, -0x3713 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 80 ); + + MSG_WriteOneBit( &sb, 1 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 81 ); + + MSG_WriteOneBit( &sb, 0 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 82 ); + + MSG_WriteOneBit( &sb, 1 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 83 ); + + MSG_WriteOneBit( &sb, 0 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 84 ); + + MSG_WriteByte( &sb, 0xa1 ); + TASSERT_EQi( sb.bOverflow, false ); + TASSERT_EQi( sb.iCurBit, 92 ); + + TASSERT_EQi( MSG_GetNumBitsWritten( &sb ), g_testbuf_bits ); + TASSERT_EQi( MSG_GetNumBytesWritten( &sb ), BitByte( g_testbuf_bits )); + TASSERT_EQi( MSG_GetRealBytesWritten( &sb ), g_testbuf_bits >> 3 ); + + // if tests fails here on big endian, it's possible due to endian issues + TASSERT( !memcmp( sb.pData, g_testbuf, g_testbuf_bits >> 3 )); + + // must check last 4 bits separately because we never care about uninitialized bits + MSG_SeekToBit( &sb, g_testbuf_bits & ~7, SEEK_SET ); + TASSERT_EQi( sb.iCurBit, 88 ); + TASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa ); +} + +static void Test_Buffer_Read( void ) +{ + sizebuf_t sb; + char buf[4]; + + MSG_StartReading( &sb, (void *)g_testbuf, -1, 0, g_testbuf_bits ); + TASSERT_EQi( sb.iCurBit, 0 ); + TASSERT_EQi( sb.nDataBits, g_testbuf_bits ); + TASSERT_EQp( sb.pData, (void *)g_testbuf ); + TASSERT_EQi( sb.bOverflow, false ); + + MSG_ReadBytes( &sb, buf, 4 ); + TASSERT( !memcmp( buf, "asdf", 4 )); + TASSERT_EQi( sb.iCurBit, 32 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadWord( &sb ), 0xa1ba ); + TASSERT_EQi( sb.iCurBit, 48 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadDword( &sb ), 0xc8eda1baU ); + TASSERT_EQi( sb.iCurBit, 80 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadOneBit( &sb ), 1 ); + TASSERT_EQi( sb.iCurBit, 81 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadOneBit( &sb ), 0 ); + TASSERT_EQi( sb.iCurBit, 82 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadOneBit( &sb ), 1 ); + TASSERT_EQi( sb.iCurBit, 83 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadOneBit( &sb ), 0 ); + TASSERT_EQi( sb.iCurBit, 84 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_ReadByte( &sb ), 0xa1 ); + TASSERT_EQi( sb.iCurBit, 92 ); + TASSERT_EQi( sb.bOverflow, false ); + + TASSERT_EQi( MSG_Overflow( &sb, 1 ), true ); + TASSERT_EQi( sb.bOverflow, true ); +} + +static void Test_Buffer_ExciseBits( void ) +{ + sizebuf_t sb; + char testdata[0x100]; + + memcpy( testdata, g_testbuf, BitByte( g_testbuf_bits )); + + MSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits ); + MSG_ExciseBits( &sb, 8, 28 ); + + TASSERT_EQi( MSG_CheckOverflow( &sb ), false ); + TASSERT_EQi( MSG_GetMaxBits( &sb ), 64 ); + TASSERT( !memcmp( MSG_GetData( &sb ), "a\x1b\xaa\x1b\xda\x8e\x5c\xa1", 8 )); + + memcpy( testdata, g_testbuf, BitByte( g_testbuf_bits )); + + MSG_StartWriting( &sb, testdata, 0, 0, g_testbuf_bits ); + MSG_ExciseBits( &sb, 16, 32 ); + + TASSERT_EQi( MSG_CheckOverflow( &sb ), false ); + TASSERT_EQi( MSG_GetMaxBits( &sb ), g_testbuf_bits - 32 ); + TASSERT( !memcmp( MSG_GetData( &sb ), "as\xba\xa1\xed\xc8\x15", 7 )); + + MSG_SeekToBit( &sb, 7 << 3, SEEK_SET ); + TASSERT_EQi( MSG_ReadUBitLong( &sb, 4 ), 0xa ); +} + +void Test_RunBuffer( void ) +{ + TRUN( Test_Buffer_BitByte( )); + TRUN( Test_Buffer_Write( )); + TRUN( Test_Buffer_Read( )); + TRUN( Test_Buffer_ExciseBits( )); +} + +#endif // XASH_ENGINE_TESTS diff --git a/engine/common/net_buffer.h b/engine/common/net_buffer.h index 50cb7361..e6373936 100644 --- a/engine/common/net_buffer.h +++ b/engine/common/net_buffer.h @@ -30,53 +30,188 @@ GNU General Public License for more details. // So PAD_NUMBER(0,4) is 0 and PAD_NUMBER(1,4) is 4 #define PAD_NUMBER( num, boundary ) ((( num ) + (( boundary ) - 1 )) / ( boundary )) * ( boundary ) -_inline int BitByte( int bits ) +static inline int BitByte( int bits ) { return PAD_NUMBER( bits, 8 ) >> 3; } struct sizebuf_s { - qboolean bOverflow; // overflow reading or writing - const char *pDebugName; // buffer name (pointer to const name) + byte *pData; + qboolean bOverflow; // overflow reading or writing + int iCurBit; + int nDataBits; + const char *pDebugName; // buffer name (pointer to const name) - byte *pData; - int iCurBit; - int nDataBits; + // to support GoldSrc broken signed integers + int iAlternateSign; }; -#define MSG_StartReading MSG_StartWriting -#define MSG_GetNumBytesRead MSG_GetNumBytesWritten -#define MSG_GetRealBytesRead MSG_GetRealBytesWritten -#define MSG_GetNumBitsRead MSG_GetNumBitsWritten -#define MSG_ReadBitAngles MSG_ReadBitVec3Coord -#define MSG_ReadString( sb ) MSG_ReadStringExt( sb, false ) -#define MSG_ReadStringLine( sb ) MSG_ReadStringExt( sb, true ) -#define MSG_ReadAngle( sb ) (float)(MSG_ReadChar( sb ) * ( 360.0f / 256.0f )) -#define MSG_Init( sb, name, data, bytes ) MSG_InitExt( sb, name, data, bytes, -1 ) +#define MSG_StartReading MSG_StartWriting +#define MSG_GetNumBytesRead MSG_GetNumBytesWritten +#define MSG_GetRealBytesRead MSG_GetRealBytesWritten +#define MSG_GetNumBitsRead MSG_GetNumBitsWritten +#define MSG_ReadBitAngles MSG_ReadBitVec3Coord +#define MSG_ReadAngle( sb ) (float)( MSG_ReadChar( sb ) * ( 360.0f / 256.0f )) +#define MSG_Init( sb, name, data, bytes ) MSG_InitExt( sb, name, data, bytes, -1 ) +#define MSG_CheckOverflow( sb ) MSG_Overflow( sb, 0 ) // common functions -void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nMaxBits ); -void MSG_InitMasks( void ); // called once at startup engine -int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ); -void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ); -_inline int MSG_TellBit( sizebuf_t *sb ) { return sb->iCurBit; } -_inline const char *MSG_GetName( sizebuf_t *sb ) { return sb->pDebugName; } -qboolean MSG_CheckOverflow( sizebuf_t *sb ); -unsigned short MSG_BigShort( unsigned short swap ); +static inline void MSG_Clear( sizebuf_t *sb ) +{ + sb->bOverflow = false; + sb->iCurBit = 0; +} -// init writing -void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits ); -void MSG_Clear( sizebuf_t *sb ); +static inline void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nBits ) +{ + sb->pData = pData; + MSG_Clear( sb ); + + if( nBits < 0 ) + sb->nDataBits = nBytes << 3; + else + sb->nDataBits = nBits; + + sb->pDebugName = pDebugName; + sb->iAlternateSign = 0; +} + +static inline void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits ) +{ + MSG_InitExt( sb, "Unnamed", pData, nBytes, nBits ); + sb->iCurBit = iStartBit; +} + +static inline int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ) +{ + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + bitPos += sb->iCurBit; + break; + case SEEK_SET: + break; + case SEEK_END: + bitPos += sb->nDataBits; + break; + default: + return -1; + } + + if( unlikely( bitPos < 0 || bitPos > sb->nDataBits )) + return -1; + + sb->iCurBit = bitPos; + + return 0; +} + +static inline int MSG_TellBit( sizebuf_t *sb ) +{ + return sb->iCurBit; +} + +static inline const char *MSG_GetName( sizebuf_t *sb ) +{ + return sb->pDebugName; +} + +static inline int MSG_GetNumBytesWritten( sizebuf_t *sb ) +{ + return BitByte( sb->iCurBit ); +} + +static inline int MSG_GetRealBytesWritten( sizebuf_t *sb ) +{ + return sb->iCurBit >> 3; // unpadded +} +static inline int MSG_GetNumBitsWritten( sizebuf_t *sb ) +{ + return sb->iCurBit; +} + +static inline int MSG_GetMaxBits( sizebuf_t *sb ) +{ + return sb->nDataBits; +} + +static inline int MSG_GetMaxBytes( sizebuf_t *sb ) +{ + return sb->nDataBits >> 3; +} + +static inline int MSG_GetNumBitsLeft( sizebuf_t *sb ) +{ + return sb->nDataBits - sb->iCurBit; +} + +static inline int MSG_GetNumBytesLeft( sizebuf_t *sb ) +{ + return MSG_GetNumBitsLeft( sb ) >> 3; +} + +static inline byte *MSG_GetData( sizebuf_t *sb ) +{ + return sb->pData; +} + +#if XASH_BIG_ENDIAN +#define MSG_BigShort( x ) ( x ) +#else +static inline uint16_t MSG_BigShort( const uint16_t x ) +{ + return (x >> 8) | (x << 8); +} +#endif + +static inline qboolean MSG_Overflow( sizebuf_t *sb, int nBits ) +{ + if( sb->iCurBit + nBits > sb->nDataBits ) + sb->bOverflow = true; + return sb->bOverflow; +} + +static inline void MSG_EndBitWriting( sizebuf_t *sb ) +{ + sb->iAlternateSign--; + + if( sb->iAlternateSign < 0 ) + { + Con_Printf( "%s: non-even MSG_Start/EndBitWriting\n", __func__ ); + sb->iAlternateSign = 0; + } + + // we have native bit ops here, just pad to closest byte + if(( sb->iCurBit & 7 ) != 0 ) + MSG_SeekToBit( sb, 8 - ( sb->iCurBit & 7 ), SEEK_CUR ); +} + +static inline void MSG_StartBitWriting( sizebuf_t *sb ) +{ + sb->iAlternateSign++; +} + +void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ); // Bit-write functions -void MSG_WriteOneBit( sizebuf_t *sb, int nValue ); -void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ); +static inline void MSG_WriteOneBit( sizebuf_t *sb, int nValue ) +{ + if( !MSG_Overflow( sb, 1 )) + { + if( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 ); + else sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 ); + + sb->iCurBit++; + } +} + +NO_ASAN void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ); void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ); void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned ); qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ); void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits ); -void MSG_WriteBitFloat( sizebuf_t *sb, float val ); // Byte-write functions #define MSG_BeginServerCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_SERVER, NULL ) @@ -87,28 +222,19 @@ void MSG_WriteByte( sizebuf_t *sb, int val ); void MSG_WriteShort( sizebuf_t *sb, int val ); void MSG_WriteWord( sizebuf_t *sb, int val ); void MSG_WriteLong( sizebuf_t *sb, int val ); -void MSG_WriteDword( sizebuf_t *sb, dword val ); +void MSG_WriteDword( sizebuf_t *sb, uint val ); void MSG_WriteCoord( sizebuf_t *sb, float val ); void MSG_WriteFloat( sizebuf_t *sb, float val ); void MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa ); void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ); -qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ); // same as MSG_WriteData qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ); // returns false if it overflows the buffer. +qboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... ) FORMAT_CHECK( 2 ); +qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ); // helper functions -_inline int MSG_GetNumBytesWritten( sizebuf_t *sb ) { return BitByte( sb->iCurBit ); } -_inline int MSG_GetRealBytesWritten( sizebuf_t *sb ) { return sb->iCurBit >> 3; } // unpadded -_inline int MSG_GetNumBitsWritten( sizebuf_t *sb ) { return sb->iCurBit; } -_inline int MSG_GetMaxBits( sizebuf_t *sb ) { return sb->nDataBits; } -_inline int MSG_GetMaxBytes( sizebuf_t *sb ) { return sb->nDataBits >> 3; } -_inline int MSG_GetNumBitsLeft( sizebuf_t *sb ) { return sb->nDataBits - sb->iCurBit; } -_inline int MSG_GetNumBytesLeft( sizebuf_t *sb ) { return MSG_GetNumBitsLeft( sb ) >> 3; } -_inline byte *MSG_GetData( sizebuf_t *sb ) { return sb->pData; } -_inline byte *MSG_GetBuf( sizebuf_t *sb ) { return sb->pData; } // just an alias // Bit-read functions int MSG_ReadOneBit( sizebuf_t *sb ); -float MSG_ReadBitFloat( sizebuf_t *sb ); qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ); float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ); int MSG_ReadSBitLong( sizebuf_t *sb, int numbits ); @@ -124,12 +250,13 @@ int MSG_ReadByte( sizebuf_t *sb ); int MSG_ReadShort( sizebuf_t *sb ); int MSG_ReadWord( sizebuf_t *sb ); int MSG_ReadLong( sizebuf_t *sb ); -dword MSG_ReadDword( sizebuf_t *sb ); +uint MSG_ReadDword( sizebuf_t *sb ); float MSG_ReadCoord( sizebuf_t *sb ); float MSG_ReadFloat( sizebuf_t *sb ); void MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa ); void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa ); +char *MSG_ReadString( sizebuf_t *sb ) RETURNS_NONNULL; +char *MSG_ReadStringLine( sizebuf_t *sb ) RETURNS_NONNULL; qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes ); -char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ); #endif//NET_BUFFER_H diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 93177df1..c26ad99e 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -18,6 +18,9 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "net_encode.h" #include "protocol.h" +#if !XASH_DEDICATED +#include +#endif // !XASH_DEDICATED #define MAKE_FRAGID( id, count ) ((( id & 0xffff ) << 16 ) | ( count & 0xffff )) #define FRAG_GETID( fragid ) (( fragid >> 16 ) & 0xffff ) @@ -84,10 +87,12 @@ such as during the connection stage while waiting for the client to load, then a packet only needs to be delivered if there is something in the unacknowledged reliable */ -convar_t *net_showpackets; -convar_t *net_chokeloopback; -convar_t *net_showdrop; -convar_t *net_qport; +CVAR_DEFINE_AUTO( net_showpackets, "0", FCVAR_PRIVILEGED, "show network packets" ); +static CVAR_DEFINE_AUTO( net_chokeloop, "0", 0, "apply bandwidth choke to loopback packets" ); +static CVAR_DEFINE_AUTO( net_showdrop, "0", 0, "show packets that are dropped" ); +static CVAR_DEFINE_AUTO( net_qport, "0", FCVAR_READ_ONLY, "current quake netport" ); +CVAR_DEFINE_AUTO( net_send_debug, "0", FCVAR_PRIVILEGED, "enable debugging output for outgoing messages" ); +CVAR_DEFINE_AUTO( net_recv_debug, "0", FCVAR_PRIVILEGED, "enable debugging output for incoming messages" ); int net_drop; netadr_t net_from; @@ -95,12 +100,58 @@ sizebuf_t net_message; static poolhandle_t net_mempool; byte net_message_buffer[NET_MAX_MESSAGE]; -const char *ns_strings[NS_COUNT] = +static const char *const ns_strings[NS_COUNT] = { "Client", "Server", }; +#if !XASH_DEDICATED +void bz_internal_error( int errcode ); +void bz_internal_error( int errcode ) +{ + Con_Printf( S_ERROR "bzip2/libbzip2: internal error number %d.\n" + "This is a bug in bzip2/libbzip2, %s.\n" + "Please report it at: https://gitlab.com/bzip2/bzip2/-/issues\n" + "If this happened when you were using some program which uses\n" + "libbzip2 as a component, you should also report this bug to\n" + "the author(s) of that program.\n" + "Please make an effort to report this bug;\n" + "timely and accurate bug reports eventually lead to higher\n" + "quality software. Thanks.\n\n", + errcode, BZ2_bzlibVersion( )); + + if (errcode == 1007) { + Con_Printf( + "\n*** A special note about internal error number 1007 ***\n" + "\n" + "Experience suggests that a common cause of i.e. 1007\n" + "is unreliable memory or other hardware. The 1007 assertion\n" + "just happens to cross-check the results of huge numbers of\n" + "memory reads/writes, and so acts (unintendedly) as a stress\n" + "test of your memory system.\n" + "\n" + "I suggest the following: try compressing the file again,\n" + "possibly monitoring progress in detail with the -vv flag.\n" + "\n" + "* If the error cannot be reproduced, and/or happens at different\n" + " points in compression, you may have a flaky memory system.\n" + " Try a memory-test program. I have used Memtest86\n" + " (www.memtest86.com). At the time of writing it is free (GPLd).\n" + " Memtest86 tests memory much more thorougly than your BIOSs\n" + " power-on test, and may find failures that the BIOS doesn't.\n" + "\n" + "* If the error can be repeatably reproduced, this is a bug in\n" + " bzip2, and I would very much like to hear about it. Please\n" + " let me know, and, ideally, save a copy of the file causing the\n" + " problem -- without which I will be unable to investigate it.\n" + "\n" + ); + } + + Sys_Error( "bzip2/libbzip2: internal error number %d\n", errcode ); +} +#endif // XASH_DEDICATED /* ================================= @@ -131,7 +182,7 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l LittleLongSW(packet->part); p = &ns->packets[packet->id & NETSPLIT_BACKUP_MASK]; - // Con_Reportf( S_NOTE "NetSplit_GetLong: packet from %s, id %d, index %d length %d\n", NET_AdrToString( *from ), (int)packet->id, (int)packet->index, (int)*length ); + // 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 ); // no packets with this id received if( packet->id != p->id ) @@ -140,7 +191,7 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l if( p->received < p->count ) { UI_ShowConnectionWarning(); - Con_Reportf( S_WARN "NetSplit_GetLong: lost packet %d\n", p->id ); + Con_Reportf( S_WARN "%s: lost packet %d\n", __func__, p->id ); } p->id = packet->id; @@ -152,7 +203,7 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l // use bool vector to detect dup packets if( p->recieved_v[packet->index >> 5 ] & ( 1 << ( packet->index & 31 ) ) ) { - Con_Reportf( S_WARN "NetSplit_GetLong: dup packet from %s\n", NET_AdrToString( *from ) ); + Con_Reportf( S_WARN "%s: dup packet from %s\n", __func__, NET_AdrToString( *from ) ); return false; } @@ -164,13 +215,13 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l // prevent overflow if( packet->part * packet->index > NET_MAX_PAYLOAD ) { - Con_Reportf( S_WARN "NetSplit_GetLong: packet out fo bounds from %s (part %d index %d)\n", NET_AdrToString( *from ), packet->part, packet->index ); + Con_Reportf( S_WARN "%s: packet out fo bounds from %s (part %d index %d)\n", __func__, NET_AdrToString( *from ), packet->part, packet->index ); return false; } if( packet->length > NET_MAX_PAYLOAD ) { - Con_Reportf( S_WARN "NetSplit_GetLong: packet out fo bounds from %s (length %d)\n", NET_AdrToString( *from ), packet->length ); + Con_Reportf( S_WARN "%s: packet out fo bounds from %s (length %d)\n", __func__, NET_AdrToString( *from ), packet->length ); return false; } @@ -187,7 +238,7 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l ns->total_received_uncompressed += len; *length = len; - // Con_Reportf( S_NOTE "NetSplit_GetLong: packet from %s, id %d received %d length %d\n", NET_AdrToString( *from ), (int)packet->id, (int)p->received, (int)packet->length ); + // 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 ); memcpy( data, p->data, len ); return true; } @@ -198,44 +249,6 @@ qboolean NetSplit_GetLong( netsplit_t *ns, netadr_t *from, byte *data, size_t *l return false; } -/* -====================== -NetSplit_SendLong - -Send parts that are less or equal maxpacket -====================== -*/ -void NetSplit_SendLong( netsrc_t sock, size_t length, void *data, netadr_t to, unsigned int maxpacket, unsigned int id) -{ - netsplit_packet_t packet = {0}; - unsigned int part = maxpacket - NETSPLIT_HEADER_SIZE; - - packet.signature = LittleLong(0xFFFFFFFE); - packet.id = LittleLong(id); - packet.length = LittleLong(length); - packet.part = LittleLong(part); - packet.count = ( length - 1 ) / part + 1; - - //Con_Reportf( S_NOTE "NetSplit_SendLong: packet to %s, count %d, length %d\n", NET_AdrToString( to ), (int)packet.count, (int)packet.length ); - - while( packet.index < packet.count ) - { - unsigned int size = part; - - if( size > length ) - size = length; - - length -= size; - - memcpy( packet.data, (const byte*)data + packet.index * part, size ); - //Con_Reportf( S_NOTE "NetSplit_SendLong: packet to %s, id %d, index %d\n", NET_AdrToString( to ), (int)packet.id, (int)packet.index ); - - NET_SendPacket( sock, size + NETSPLIT_HEADER_SIZE, &packet, to ); - packet.index++; - } - -} - /* =============== Netchan_Init @@ -243,19 +256,22 @@ Netchan_Init */ void Netchan_Init( void ) { + char buf[32]; int port; // pick a port value that should be nice and random port = COM_RandomLong( 1, 65535 ); + Q_snprintf( buf, sizeof( buf ), "%i", port ); - net_showpackets = Cvar_Get ("net_showpackets", "0", 0, "show network packets" ); - net_chokeloopback = Cvar_Get( "net_chokeloop", "0", 0, "apply bandwidth choke to loopback packets" ); - net_showdrop = Cvar_Get( "net_showdrop", "0", 0, "show packets that are dropped" ); - net_qport = Cvar_Get( "net_qport", va( "%i", port ), FCVAR_READ_ONLY, "current quake netport" ); + Cvar_RegisterVariable( &net_showpackets ); + Cvar_RegisterVariable( &net_chokeloop ); + Cvar_RegisterVariable( &net_showdrop ); + Cvar_RegisterVariable( &net_qport ); + Cvar_RegisterVariable( &net_send_debug ); + Cvar_RegisterVariable( &net_recv_debug ); + Cvar_FullSet( net_qport.name, buf, net_qport.flags ); net_mempool = Mem_AllocPool( "Network Pool" ); - - MSG_InitMasks(); // initialize bit-masks } void Netchan_Shutdown( void ) @@ -273,8 +289,8 @@ void Netchan_ReportFlow( netchan_t *chan ) Assert( chan != NULL ); - Q_strcpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 )); - Q_strcpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 )); + Q_strncpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 ), sizeof( incoming )); + Q_strncpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 ), sizeof( outgoing )); Con_DPrintf( "Signon network traffic: %s from server, %s to server\n", incoming, outgoing ); } @@ -300,12 +316,18 @@ Netchan_Setup called to open a channel to a remote system ============== */ -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode )) +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags ) { Netchan_Clear( chan ); memset( chan, 0, sizeof( *chan )); + if( pfnBlockSize == NULL ) + { + Host_Error( "%s: pfnBlockSize == NULL", __func__ ); + return; + } + chan->sock = sock; chan->remote_address = adr; chan->last_received = host.realtime; @@ -316,6 +338,11 @@ void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, voi chan->qport = qport; chan->client = client; chan->pfnBlockSize = pfnBlockSize; + chan->split = FBitSet( flags, NETCHAN_USE_LEGACY_SPLIT ) ? true : false; + chan->use_munge = FBitSet( flags, NETCHAN_USE_MUNGE ) ? true : false; + chan->use_bz2 = FBitSet( flags, NETCHAN_USE_BZIP2 ) ? true : false; + chan->use_lzss = FBitSet( flags, NETCHAN_USE_LZSS ) ? true : false; + chan->gs_netchan = FBitSet( flags, NETCHAN_GOLDSRC ) ? true : false; MSG_Init( &chan->message, "NetData", chan->message_buf, sizeof( chan->message_buf )); } @@ -349,7 +376,7 @@ Returns true if the bandwidth choke isn't active qboolean Netchan_CanPacket( netchan_t *chan, qboolean choke ) { // never choke loopback packets. - if( !choke || ( !net_chokeloopback->value && NET_IsLocalAddress( chan->remote_address ) )) + if( !choke || ( !net_chokeloop.value && NET_IsLocalAddress( chan->remote_address ) )) { chan->cleartime = host.realtime; return true; @@ -364,7 +391,7 @@ Netchan_UnlinkFragment ============================== */ -void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list ) +static void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list ) { fragbuf_t *search; @@ -402,7 +429,7 @@ Netchan_ClearFragbufs ============================== */ -void Netchan_ClearFragbufs( fragbuf_t **ppbuf ) +static void Netchan_ClearFragbufs( fragbuf_t **ppbuf ) { fragbuf_t *buf, *n; @@ -427,7 +454,7 @@ Netchan_ClearFragments ============================== */ -void Netchan_ClearFragments( netchan_t *chan ) +static void Netchan_ClearFragments( netchan_t *chan ) { fragbufwaiting_t *wait, *next; int i; @@ -492,22 +519,21 @@ Netchan_OutOfBand Sends an out-of-band datagram ================ */ -void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ) +void Netchan_OutOfBand( int net_socket, netadr_t adr, int len, const byte *data ) { - byte send_buf[MAX_PRINT_MSG]; - sizebuf_t send; + byte buf[MAX_PRINT_MSG + 4] = { 0xff, 0xff, 0xff, 0xff }; - // write the packet header - MSG_Init( &send, "SequencePacket", send_buf, sizeof( send_buf )); + if( CL_IsPlaybackDemo( )) + return; - MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET ); // -1 sequence means out of band - MSG_WriteBytes( &send, data, length ); - - if( !CL_IsPlaybackDemo( )) + if( len > sizeof( buf ) - 4 ) { - // send the datagram - NET_SendPacket( net_socket, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr ); + Host_Error( "%s: overflow!\n", __func__ ); + return; } + + memcpy( &buf[4], data, len ); + NET_SendPacket( net_socket, len + 4, buf, adr ); } /* @@ -517,16 +543,26 @@ Netchan_OutOfBandPrint Sends a text message in an out-of-band datagram ================ */ -void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) +void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *fmt, ... ) { - char string[MAX_PRINT_MSG]; - va_list argptr; + va_list va; + byte buf[MAX_PRINT_MSG + 4] = { 0xff, 0xff, 0xff, 0xff }; + int len; - va_start( argptr, format ); - Q_vsnprintf( string, sizeof( string ) - 1, format, argptr ); - va_end( argptr ); + if( CL_IsPlaybackDemo( )) + return; - Netchan_OutOfBand( net_socket, adr, Q_strlen( string ), (byte *)string ); + va_start( va, fmt ); + len = Q_vsnprintf( &buf[4], sizeof( buf ) - 4, fmt, va ); + va_end( va ); + + if( len < 0 ) + { + Host_Error( "%s: snprintf overflow!\n", __func__ ); + return; + } + + NET_SendPacket( net_socket, len + 4, buf, adr ); } /* @@ -535,12 +571,12 @@ Netchan_AllocFragbuf ============================== */ -fragbuf_t *Netchan_AllocFragbuf( void ) +static fragbuf_t *Netchan_AllocFragbuf( int fragment_size ) { fragbuf_t *buf; - buf = (fragbuf_t *)Mem_Calloc( net_mempool, sizeof( fragbuf_t )); - MSG_Init( &buf->frag_message, "Frag Message", buf->frag_message_buf, sizeof( buf->frag_message_buf )); + buf = (fragbuf_t *)Mem_Calloc( net_mempool, sizeof( fragbuf_t ) + fragment_size ); + MSG_Init( &buf->frag_message, "Frag Message", buf->frag_message_buf, fragment_size ); return buf; } @@ -551,9 +587,9 @@ Netchan_AddFragbufToTail ============================== */ -void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf ) +static void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf ) { - fragbuf_t *p; + fragbuf_t *p; buf->next = NULL; wait->fragbufcount++; @@ -574,7 +610,7 @@ Netchan_UpdateFlow ============================== */ -void Netchan_UpdateFlow( netchan_t *chan ) +static void Netchan_UpdateFlow( netchan_t *chan ) { float faccumulatedtime = 0.0; int i, bytes = 0; @@ -707,21 +743,39 @@ static void Netchan_CreateFragments_( netchan_t *chan, sizebuf_t *msg ) if( MSG_GetNumBytesWritten( msg ) == 0 ) return; - if( chan->pfnBlockSize != NULL ) - chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); - else chunksize = FRAGMENT_MAX_SIZE; // fallback + chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t )); - if( !LZSS_IsCompressed( MSG_GetData( msg ))) + if( chan->use_bz2 && memcmp( MSG_GetData( msg ), "BZ2", 4 )) { - uint uCompressedSize = 0; - uint uSourceSize = MSG_GetNumBytesWritten( msg ); - byte *pbOut = LZSS_Compress( msg->pData, uSourceSize, &uCompressedSize ); +#if !XASH_DEDICATED + byte pbOut[0x10000]; + uint uSourceSize = MSG_GetNumBytesWritten( msg ); + uint uCompressedSize = MSG_GetNumBytesWritten( msg ) - 4; + if( BZ2_bzBuffToBuffCompress( pbOut, &uCompressedSize, MSG_GetData( msg ), uSourceSize, 9, 0, 30 ) == BZ_OK ) + { + if( uCompressedSize < uSourceSize ) + { + Con_Reportf( "Compressing split packet with BZip2 (%d -> %d bytes)\n", uSourceSize, uCompressedSize ); + memcpy( msg->pData, "BZ2", 4 ); + memcpy( msg->pData + 4, pbOut, uCompressedSize ); + MSG_SeekToBit( msg, ( uCompressedSize + 4 ) << 3, SEEK_SET ); + } + } +#else + Host_Error( "%s: BZ2 compression is not supported for server", __func__ ); +#endif + } + else if( chan->use_lzss && !LZSS_IsCompressed( MSG_GetData( msg ), MSG_GetMaxBytes( msg ))) + { + uint uCompressedSize = 0; + uint uSourceSize = MSG_GetNumBytesWritten( msg ); + byte *pbOut = LZSS_Compress( msg->pData, uSourceSize, &uCompressedSize ); if( pbOut && uCompressedSize > 0 && uCompressedSize < uSourceSize ) { - Con_Reportf( "Compressing split packet (%d -> %d bytes)\n", uSourceSize, uCompressedSize ); + Con_Reportf( "Compressing split packet with LZSS (%d -> %d bytes)\n", uSourceSize, uCompressedSize ); memcpy( msg->pData, pbOut, uCompressedSize ); MSG_SeekToBit( msg, uCompressedSize << 3, SEEK_SET ); } @@ -736,7 +790,7 @@ static void Netchan_CreateFragments_( netchan_t *chan, sizebuf_t *msg ) bytes = Q_min( remaining, chunksize ); remaining -= bytes; - buf = Netchan_AllocFragbuf(); + buf = Netchan_AllocFragbuf( bytes ); buf->bufferid = bufferid++; // Copy in data @@ -786,7 +840,7 @@ Netchan_FindBufferById ============================== */ -fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate ) +static fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate ) { fragbuf_t *list = *pplist; fragbuf_t *pnewbuf; @@ -803,7 +857,7 @@ fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate return NULL; // create new entry - pnewbuf = Netchan_AllocFragbuf(); + pnewbuf = Netchan_AllocFragbuf( NET_MAX_FRAGMENT ); pnewbuf->bufferid = id; Netchan_AddBufferToList( pplist, pnewbuf ); @@ -816,7 +870,7 @@ Netchan_CheckForCompletion ============================== */ -void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers ) +static void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers ) { int c, id; int size; @@ -858,32 +912,51 @@ Netchan_CreateFileFragmentsFromBuffer */ void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size ) { - int chunksize; - int send, pos; - int remaining; - int bufferid = 1; - qboolean firstfragment = true; - fragbufwaiting_t *wait, *p; - fragbuf_t *buf; + int chunksize; + int send, pos; + int remaining; + int bufferid = 1; + qboolean firstfragment = true; + fragbufwaiting_t *wait, *p; + fragbuf_t *buf; + uint originalSize = size; + const char *compressor = ""; - if( !size ) return; + if( !size ) + return; - if( chan->pfnBlockSize != NULL ) - chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); - else chunksize = FRAGMENT_MAX_SIZE; // fallback + chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); - if( !LZSS_IsCompressed( pbuf )) + if( chan->gs_netchan ) { - uint uCompressedSize = 0; - byte *pbOut = LZSS_Compress( pbuf, size, &uCompressedSize ); - +#if !XASH_DEDICATED + uint uCompressedSize = size + 600; + byte *pbOut = Mem_Malloc( net_mempool, uCompressedSize ); + if( BZ2_bzBuffToBuffCompress( pbOut, &uCompressedSize, pbuf, size, 9, 0, 30 ) == BZ_OK && uCompressedSize < size ) + { + Con_DPrintf( "Compressing filebuffer (%s -> %s)\n", Q_memprint( size ), Q_memprint( uCompressedSize )); + memcpy( pbuf, pbOut, uCompressedSize ); + size = uCompressedSize; + compressor = "bz2"; + } + Mem_Free( pbOut ); +#else + Host_Error( "%s: BZ2 compression is not supported for server\n", __func__ ); +#endif + } + else + { + uint uCompressedSize = 0; + byte *pbOut = LZSS_Compress( pbuf, size, &uCompressedSize ); if( pbOut && uCompressedSize > 0 && uCompressedSize < size ) { Con_DPrintf( "Compressing filebuffer (%s -> %s)\n", Q_memprint( size ), Q_memprint( uCompressedSize )); memcpy( pbuf, pbOut, uCompressedSize ); size = uCompressedSize; + compressor = "lzss"; } - if( pbOut ) free( pbOut ); + if( pbOut ) + free( pbOut ); } wait = (fragbufwaiting_t *)Mem_Calloc( net_mempool, sizeof( fragbufwaiting_t )); @@ -894,7 +967,7 @@ void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filenam { send = Q_min( remaining, chunksize ); - buf = Netchan_AllocFragbuf(); + buf = Netchan_AllocFragbuf( send ); buf->bufferid = bufferid++; // copy in data @@ -904,6 +977,13 @@ void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filenam { // write filename MSG_WriteString( &buf->frag_message, filename ); + + // write compressor name and uncompressed size + if( chan->gs_netchan ) + { + MSG_WriteString( &buf->frag_message, compressor ); + MSG_WriteLong( &buf->frag_message, originalSize ); + } // send a bit less on first package send -= MSG_GetNumBytesWritten( &buf->frag_message ); @@ -947,18 +1027,30 @@ Netchan_CreateFileFragments */ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) { - int chunksize; - int send, pos; - int remaining; - int bufferid = 1; - fs_offset_t filesize = 0; - char compressedfilename[MAX_OSPATH]; - int compressedFileTime; - int fileTime; - qboolean firstfragment = true; - qboolean bCompressed = false; - fragbufwaiting_t *wait, *p; - fragbuf_t *buf; + int chunksize; + int send, pos; + int remaining; + int bufferid = 1; + fs_offset_t filesize = 0; + fs_offset_t originalSize = 0; + int compressedFileTime; + int fileTime; + qboolean firstfragment = true; + qboolean bCompressed = false; + fragbufwaiting_t *wait, *p; + fragbuf_t *buf; + char compressedfilename[sizeof( buf->filename ) + 5]; + const char *compressor = ""; + uint uCompressedSize = 0; + byte *compressed = NULL; + byte *uncompressed = NULL; + + // shouldn't be critical, but just in case + if( Q_strlen( filename ) > sizeof( buf->filename ) - 1 ) + { + Con_Printf( S_WARN "Unable to transfer %s due to path length overflow\n", filename ); + return 0; + } if(( filesize = FS_FileSize( filename, false )) <= 0 ) { @@ -966,37 +1058,56 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) return 0; } - if( chan->pfnBlockSize != NULL ) - chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); - else chunksize = FRAGMENT_MAX_SIZE; // fallback + originalSize = filesize; + chunksize = chan->pfnBlockSize( chan->client, FRAGSIZE_FRAG ); - Q_strncpy( compressedfilename, filename, sizeof( compressedfilename )); - COM_ReplaceExtension( compressedfilename, ".ztmp" ); + Q_snprintf( compressedfilename, sizeof( compressedfilename ), "%s.ztmp", filename ); compressedFileTime = FS_FileTime( compressedfilename, false ); fileTime = FS_FileTime( filename, false ); if( compressedFileTime >= fileTime ) { // if compressed file already created and newer than source - if( FS_FileSize( compressedfilename, false ) != -1 ) + fs_offset_t compressedSize = FS_FileSize( compressedfilename, false ); + if( compressedSize != -1 ) + { bCompressed = true; + filesize = compressedSize; + } } else { - uint uCompressedSize; - byte *uncompressed; - byte *compressed; - uncompressed = FS_LoadFile( filename, &filesize, false ); - compressed = LZSS_Compress( uncompressed, filesize, &uCompressedSize ); - - if( compressed ) + if( chan->gs_netchan ) { - Con_DPrintf( "compressed file %s (%s -> %s)\n", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize )); - FS_WriteFile( compressedfilename, compressed, uCompressedSize ); - filesize = uCompressedSize; - bCompressed = true; - free( compressed ); +#if !XASH_DEDICATED + compressed = Mem_Malloc( net_mempool, filesize + 600 ); + uCompressedSize = filesize + 600; + if( BZ2_bzBuffToBuffCompress( compressed, &uCompressedSize, uncompressed, filesize, 9, 0, 30 ) == BZ_OK && uCompressedSize < filesize ) + { + Con_DPrintf( "compressed file %s (%s -> %s)\n", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize )); + FS_WriteFile( compressedfilename, compressed, uCompressedSize ); + filesize = uCompressedSize; + bCompressed = true; + compressor = "bz2"; + } + Mem_Free( compressed ); +#else + Host_Error( "%s: BZ2 compression is not supported for server\n", __func__ ); +#endif + } + else + { + compressed = LZSS_Compress( uncompressed, filesize, &uCompressedSize ); + if( compressed && uCompressedSize > 0 && uCompressedSize < filesize ) + { + Con_DPrintf( "compressed file %s (%s -> %s)\n", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize )); + FS_WriteFile( compressedfilename, compressed, uCompressedSize ); + filesize = uCompressedSize; + bCompressed = true; + compressor = "lzss"; + free( compressed ); + } } Mem_Free( uncompressed ); } @@ -1009,7 +1120,7 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) { send = Q_min( remaining, chunksize ); - buf = Netchan_AllocFragbuf(); + buf = Netchan_AllocFragbuf( send ); buf->bufferid = bufferid++; // copy in data @@ -1020,6 +1131,13 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) // Write filename MSG_WriteString( &buf->frag_message, filename ); + // write compressor name and uncompressed size + if( chan->gs_netchan ) + { + MSG_WriteString( &buf->frag_message, compressor ); + MSG_WriteLong( &buf->frag_message, (uint)originalSize ); + } + // Send a bit less on first package send -= MSG_GetNumBytesWritten( &buf->frag_message ); @@ -1114,14 +1232,35 @@ qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *l p = n; } - if( LZSS_IsCompressed( MSG_GetData( msg ))) + if( chan->use_bz2 && !memcmp( MSG_GetData( msg ), "BZ2", 4 )) { - uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg )); +#if !XASH_DEDICATED + byte buf[0x10000]; + uint uDecompressedLen = sizeof( buf ); + int bz2_err = BZ2_bzBuffToBuffDecompress( buf, &uDecompressedLen, MSG_GetData( msg ) + 4, MSG_GetNumBytesWritten( msg ) - 4, 1, 0 ); + + if( bz2_err == BZ_OK ) + { + size = uDecompressedLen; + memcpy( msg->pData, buf, size ); + } + else + { + Con_Printf( S_ERROR "%s: BZ2 decompression failed (%d)\n", __func__, bz2_err ); + return false; + } +#else + Host_Error( "%s: BZ2 compression is not supported for server\n", __func__ ); +#endif + } + else if( chan->use_lzss && LZSS_IsCompressed( MSG_GetData( msg ), size )) + { + uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg ), size ); byte buf[NET_MAX_MESSAGE]; if( uDecompressedLen <= sizeof( buf )) { - size = LZSS_Decompress( MSG_GetData( msg ), buf ); + size = LZSS_Decompress( MSG_GetData( msg ), buf, size, sizeof( buf )); memcpy( msg->pData, buf, size ); } else @@ -1151,7 +1290,8 @@ Netchan_CopyFileFragments */ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) { - char filename[MAX_OSPATH]; + char filename[MAX_OSPATH], compressor[32]; + uint uncompressedSize; int nsize, pos; byte *buffer; fragbuf_t *p, *n; @@ -1174,6 +1314,12 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) MSG_Clear( msg ); Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename )); + compressor[0] = 0; + if( chan->gs_netchan ) + { + Q_strncpy( compressor, MSG_ReadString( msg ), sizeof( compressor )); + uncompressedSize = MSG_ReadLong( msg ); + } if( !COM_CheckString( filename )) { @@ -1188,6 +1334,13 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) return false; } + if( filename[0] != '!' ) + { + string temp_filename; + Q_snprintf( temp_filename, sizeof( temp_filename ), DEFAULT_DOWNLOADED_DIRECTORY "%s", filename ); + Q_strncpy( filename, temp_filename, sizeof( filename )); + } + Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); if( filename[0] != '!' && FS_FileExists( filename, false )) @@ -1237,12 +1390,35 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) p = n; } - if( LZSS_IsCompressed( buffer )) + if( chan->gs_netchan && chan->use_bz2 && !Q_stricmp( compressor, "bz2" )) { - uint uncompressedSize = LZSS_GetActualSize( buffer ) + 1; - byte *uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize ); +#if !XASH_DEDICATED + byte *uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize ); - nsize = LZSS_Decompress( buffer, uncompressedBuffer ); + Con_DPrintf( "Decompressing file %s (%d -> %d bytes)\n", filename, nsize, uncompressedSize ); + if( BZ2_bzBuffToBuffDecompress( uncompressedBuffer, &uncompressedSize, buffer, nsize, 1, 0 ) != BZ_OK ) + { + Con_DPrintf( S_ERROR "BZ2 decompression failed for %s\n", filename ); + Mem_Free( buffer ); + Mem_Free( uncompressedBuffer ); + Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); + return false; + } + Mem_Free( buffer ); + nsize = uncompressedSize; + buffer = uncompressedBuffer; +#else + Host_Error( "%s: BZ2 compression is not supported for server", __func__ ); +#endif + } + else if( chan->use_lzss && LZSS_IsCompressed( buffer, nsize + 1 )) + { + byte *uncompressedBuffer; + + uncompressedSize = LZSS_GetActualSize( buffer, nsize + 1 ) + 1; + uncompressedBuffer = Mem_Calloc( net_mempool, uncompressedSize ); + + nsize = LZSS_Decompress( buffer, uncompressedBuffer, nsize + 1, uncompressedSize ); Mem_Free( buffer ); buffer = uncompressedBuffer; } @@ -1271,7 +1447,7 @@ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) return true; } -qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length ) +static qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length ) { int i, buffer, offset; int count, length; @@ -1318,7 +1494,7 @@ void Netchan_UpdateProgress( netchan_t *chan ) if( host.downloadcount == 0 ) { - scr_download->value = -1.0f; + scr_download.value = -1.0f; host.downloadfile[0] = '\0'; } @@ -1386,7 +1562,7 @@ void Netchan_UpdateProgress( netchan_t *chan ) } - scr_download->value = bestpercent; + scr_download.value = bestpercent; #endif // XASH_DEDICATED } @@ -1400,7 +1576,7 @@ transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ -void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) +void Netchan_TransmitBits( netchan_t *chan, int length, const byte *data ) { byte send_buf[NET_MAX_MESSAGE]; qboolean send_reliable_fragment; @@ -1417,6 +1593,9 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) return; } + if( chan->pfnBlockSize == NULL ) // not initialized + return; + // if the remote side dropped the last reliable message, resend it send_reliable = false; @@ -1455,12 +1634,9 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) // stall reliable payloads if sending from frag buffer if( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] )) { - int maxsize = MAX_RELIABLE_PAYLOAD; + int maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); send_from_regular = false; - - if( chan->pfnBlockSize ) - maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); if( maxsize == 0 ) maxsize = MAX_RELIABLE_PAYLOAD; @@ -1543,10 +1719,9 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) if( pbuf->iscompressed ) { - char compressedfilename[MAX_OSPATH]; + char compressedfilename[sizeof( pbuf->filename ) + 5]; - Q_strncpy( compressedfilename, pbuf->filename, sizeof( compressedfilename )); - COM_ReplaceExtension( compressedfilename, ".ztmp" ); + Q_snprintf( compressedfilename, sizeof( compressedfilename ), "%s.ztmp", pbuf->filename ); file = FS_Open( compressedfilename, "rb", false ); } else file = FS_Open( pbuf->filename, "rb", false ); @@ -1576,6 +1751,7 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) } } + memset( send_buf, 0, sizeof( send_buf )); MSG_Init( &send, "NetSend", send_buf, sizeof( send_buf )); // prepare the packet header @@ -1602,9 +1778,9 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) MSG_WriteLong( &send, w2 ); // send the qport if we are a client - if( chan->sock == NS_CLIENT ) + if( chan->sock == NS_CLIENT && !chan->gs_netchan ) { - MSG_WriteWord( &send, Cvar_VariableInteger( "net_qport" )); + MSG_WriteWord( &send, (int)net_qport.value ); } if( send_reliable && send_reliable_fragment ) @@ -1615,8 +1791,16 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) { MSG_WriteByte( &send, 1 ); MSG_WriteLong( &send, chan->reliable_fragid[i] ); - MSG_WriteLong( &send, chan->frag_startpos[i] ); - MSG_WriteLong( &send, chan->frag_length[i] ); + if( chan->gs_netchan ) + { + MSG_WriteShort( &send, chan->frag_startpos[i] >> 3 ); + MSG_WriteShort( &send, chan->frag_length[i] >> 3 ); + } + else + { + MSG_WriteLong( &send, chan->frag_startpos[i] ); + MSG_WriteLong( &send, chan->frag_length[i] ); + } } else { @@ -1634,13 +1818,11 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) if( length ) { - int maxsize = NET_MAX_MESSAGE; - if( chan->pfnBlockSize ) - maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_UNRELIABLE ); + int maxsize = chan->pfnBlockSize( chan->client, FRAGSIZE_UNRELIABLE ); if( (( MSG_GetNumBytesWritten( &send ) + length ) >> 3) <= maxsize ) MSG_WriteBits( &send, data, length ); - else Con_Printf( S_WARN "Netchan_Transmit: unreliable message overflow: %d\n", MSG_GetNumBytesWritten( &send ) ); + else Con_Printf( S_WARN "%s: unreliable message overflow: %d\n", __func__, MSG_GetNumBytesWritten( &send ) ); } // deal with packets that are too small for some networks @@ -1670,9 +1852,11 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) // send the datagram if( !CL_IsPlaybackDemo( )) { - int splitsize = 0; - if( chan->pfnBlockSize ) - splitsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); + int splitsize = chan->pfnBlockSize( chan->client, FRAGSIZE_SPLIT ); + + if( chan->use_munge ) + COM_Munge2( send.pData + 8, MSG_GetNumBytesWritten( &send ) - 8, (byte)( chan->outgoing_sequence - 1 )); + NET_SendPacketEx( chan->sock, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), chan->remote_address, splitsize ); } @@ -1685,7 +1869,7 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) chan->cleartime += ( MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ) * fRate; - if( net_showpackets->value && net_showpackets->value != 2.0f ) + if( net_showpackets.value && net_showpackets.value != 2.0f ) { Con_Printf( " %s --> sz=%i seq=%i ack=%i rel=%i tm=%f\n" , ns_strings[chan->sock] @@ -1697,21 +1881,6 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) } } -/* -=============== -Netchan_Transmit - -tries to send an unreliable message to a connection, and handles the -transmition / retransmition of the reliable messages. - -A 0 length will still generate a packet and deal with the reliable messages. -================ -*/ -void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ) -{ - Netchan_TransmitBits( chan, lengthInBytes << 3, data ); -} - /* ================= Netchan_Process @@ -1731,14 +1900,14 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) qboolean message_contains_fragments; int i, qport, statId; - if( !CL_IsPlaybackDemo() && !NET_CompareAdr( net_from, chan->remote_address )) - return false; - // get sequence numbers MSG_Clear( msg ); sequence = MSG_ReadLong( msg ); sequence_ack = MSG_ReadLong( msg ); + if( chan->use_munge ) + COM_UnMunge2( msg->pData + 8, ( msg->nDataBits >> 3 ) - 8, sequence & 0xFF ); + // read the qport if we are a server if( chan->sock == NS_SERVER ) qport = MSG_ReadShort( msg ); @@ -1756,8 +1925,16 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) { frag_message[i] = true; fragid[i] = MSG_ReadLong( msg ); - frag_offset[i] = MSG_ReadLong( msg ); - frag_length[i] = MSG_ReadLong( msg ); + if( chan->gs_netchan ) + { + frag_offset[i] = MSG_ReadShort( msg ) << 3; + frag_length[i] = MSG_ReadShort( msg ) << 3; + } + else + { + frag_offset[i] = MSG_ReadLong( msg ); + frag_length[i] = MSG_ReadLong( msg ); + } } } @@ -1770,7 +1947,7 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) sequence_ack &= ~BIT( 30 ); sequence_ack &= ~BIT( 31 ); - if( net_showpackets->value && net_showpackets->value != 3.0f ) + if( net_showpackets.value && net_showpackets.value != 3.0f ) { Con_Printf( " %s <-- sz=%i seq=%i ack=%i rel=%i tm=%f\n" , ns_strings[chan->sock] @@ -1784,7 +1961,7 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) // discard stale or duplicated packets if( sequence <= (uint)chan->incoming_sequence ) { - if( net_showdrop->value ) + if( net_showdrop.value ) { const char *adr = NET_AdrToString( chan->remote_address ); @@ -1797,7 +1974,7 @@ qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) // dropped packets don't keep the message from being used net_drop = sequence - ( chan->incoming_sequence + 1 ); - if( net_drop > 0 && net_showdrop->value ) + if( net_drop > 0 && net_showdrop.value ) Con_Printf( "%s:dropped %i packets at %i\n", NET_AdrToString( chan->remote_address ), net_drop, sequence ); // if the current outgoing reliable message has been acknowledged diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index dc1c0f6c..d913aec5 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -28,6 +28,26 @@ GNU General Public License for more details. #define DELTA_PATH "delta.lst" +#define DT_BYTE BIT( 0 ) // A byte +#define DT_SHORT BIT( 1 ) // 2 byte field +#define DT_FLOAT BIT( 2 ) // A floating point field +#define DT_INTEGER BIT( 3 ) // 4 byte integer +#define DT_ANGLE BIT( 4 ) // A floating point angle ( will get masked correctly ) +#define DT_TIMEWINDOW_8 BIT( 5 ) // A floating point timestamp, relative to sv.time +#define DT_TIMEWINDOW_BIG BIT( 6 ) // and re-encoded on the client relative to the client's clock +#define DT_STRING BIT( 7 ) // A null terminated string, sent as 8 byte chars +#define DT_SIGNED BIT( 8 ) // sign modificator +#define DT_SIGNED_GS BIT( 31 ) // GoldSrc-specific sign modificator + +// helper macroses +#define ENTS_DEF( x ) #x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x ) +#define UCMD_DEF( x ) #x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x ) +#define EVNT_DEF( x ) #x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x ) +#define PHYS_DEF( x ) #x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x ) +#define CLDT_DEF( x ) #x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x ) +#define WPDT_DEF( x ) #x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x ) +#define DESC_DEF( x ) #x, offsetof( goldsrc_delta_t, x ), sizeof( ((goldsrc_delta_t *)0)->x ) + static qboolean delta_init = false; // list of all the struct names @@ -49,7 +69,6 @@ static const delta_field_t cmd_fields[] = { UCMD_DEF( impact_position[0] ) }, { UCMD_DEF( impact_position[1] ) }, { UCMD_DEF( impact_position[2] ) }, -{ NULL }, }; static const delta_field_t pm_fields[] = @@ -85,7 +104,6 @@ static const delta_field_t pm_fields[] = { PHYS_DEF( skydir_y ) }, { PHYS_DEF( skydir_z ) }, { PHYS_DEF( skyangle ) }, -{ NULL }, }; static const delta_field_t ev_fields[] = @@ -108,7 +126,6 @@ static const delta_field_t ev_fields[] = { EVNT_DEF( iparam2 ) }, { EVNT_DEF( bparam1 ) }, { EVNT_DEF( bparam2 ) }, -{ NULL }, }; static const delta_field_t wd_fields[] = @@ -135,7 +152,6 @@ static const delta_field_t wd_fields[] = { WPDT_DEF( fuser2 ) }, { WPDT_DEF( fuser3 ) }, { WPDT_DEF( fuser4 ) }, -{ NULL }, }; static const delta_field_t cd_fields[] = @@ -196,7 +212,6 @@ static const delta_field_t cd_fields[] = { CLDT_DEF( vuser4[0] ) }, { CLDT_DEF( vuser4[1] ) }, { CLDT_DEF( vuser4[2] ) }, -{ NULL }, }; static const delta_field_t ent_fields[] = @@ -292,30 +307,140 @@ static const delta_field_t ent_fields[] = { ENTS_DEF( vuser4[0] ) }, { ENTS_DEF( vuser4[1] ) }, { ENTS_DEF( vuser4[2] ) }, -{ NULL }, }; +static const delta_field_t meta_fields[] = +{ +{ DESC_DEF( fieldType ), }, +{ DESC_DEF( fieldName ), }, +{ DESC_DEF( fieldOffset ), }, +{ DESC_DEF( fieldSize ), }, +{ DESC_DEF( significant_bits ), }, +{ DESC_DEF( premultiply ), }, +{ DESC_DEF( postmultiply ), }, +}; + +#if XASH_ENGINE_TESTS +typedef struct delta_test_struct_t +{ + char dt_string[128]; // always signed + float dt_timewindow_big; // always signed + float dt_timewindow_8; // always signed + float dt_angle; // always_signed + float dt_float_signed; + float dt_float_unsigned; + int32_t dt_integer_signed; + uint32_t dt_integer_unsigned; + int16_t dt_short_signed; + uint16_t dt_short_unsigned; + int8_t dt_byte_signed; + uint8_t dt_byte_unsigned; +} delta_test_struct_t; + +#define TEST_DEF( x ) #x, offsetof( delta_test_struct_t, x ), sizeof( ((delta_test_struct_t *)0)->x ) + +static const delta_field_t test_fields[] = +{ +{ TEST_DEF( dt_string ) }, +{ TEST_DEF( dt_timewindow_big )}, +{ TEST_DEF( dt_timewindow_8 )}, +{ TEST_DEF( dt_angle ) }, +{ TEST_DEF( dt_float_signed ) }, +{ TEST_DEF( dt_float_unsigned ) }, +{ TEST_DEF( dt_integer_signed ) }, +{ TEST_DEF( dt_integer_unsigned ) }, +{ TEST_DEF( dt_short_signed ) }, +{ TEST_DEF( dt_short_unsigned ) }, +{ TEST_DEF( dt_byte_signed ) }, +{ TEST_DEF( dt_byte_unsigned ) }, +}; +#endif + static delta_info_t dt_info[] = { -{ "event_t", ev_fields, NUM_FIELDS( ev_fields ) }, -{ "movevars_t", pm_fields, NUM_FIELDS( pm_fields ) }, -{ "usercmd_t", cmd_fields, NUM_FIELDS( cmd_fields ) }, -{ "clientdata_t", cd_fields, NUM_FIELDS( cd_fields ) }, -{ "weapon_data_t", wd_fields, NUM_FIELDS( wd_fields ) }, -{ "entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, -{ "entity_state_player_t", ent_fields, NUM_FIELDS( ent_fields ) }, -{ "custom_entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, -{ NULL }, +[DT_EVENT_T] = { "event_t", ev_fields, ARRAYSIZE( ev_fields ) }, +[DT_MOVEVARS_T] = { "movevars_t", pm_fields, ARRAYSIZE( pm_fields ) }, +[DT_USERCMD_T] = { "usercmd_t", cmd_fields, ARRAYSIZE( cmd_fields ) }, +[DT_CLIENTDATA_T] = { "clientdata_t", cd_fields, ARRAYSIZE( cd_fields ) }, +[DT_WEAPONDATA_T] = { "weapon_data_t", wd_fields, ARRAYSIZE( wd_fields ) }, +[DT_ENTITY_STATE_T] = { "entity_state_t", ent_fields, ARRAYSIZE( ent_fields ) }, +[DT_ENTITY_STATE_PLAYER_T] = { "entity_state_player_t", ent_fields, ARRAYSIZE( ent_fields ) }, +[DT_CUSTOM_ENTITY_STATE_T] = { "custom_entity_state_t", ent_fields, ARRAYSIZE( ent_fields ) }, +#if XASH_ENGINE_TESTS +[DT_DELTA_TEST_STRUCT_T] = { "delta_test_struct_t", test_fields, ARRAYSIZE( test_fields ) }, +#endif }; -delta_info_t *Delta_FindStruct( const char *name ) +// meta description is special, it cannot be overriden +static const delta_info_t dt_goldsrc_meta = +{ + .pName = "goldsrc_delta_t", + .pInfo = meta_fields, + .maxFields = ARRAYSIZE( meta_fields ), + .numFields = ARRAYSIZE( meta_fields ), + .pFields = (delta_t[ARRAYSIZE( meta_fields )]) + { + { + DESC_DEF( fieldType ), + .flags = DT_INTEGER, + .multiplier = 1.0f, + .post_multiplier = 1.0f, + .bits = 32, + }, + { + DESC_DEF( fieldName ), + .flags = DT_STRING, + .multiplier = 1.0f, + .post_multiplier = 1.0f, + .bits = 1, + }, + { + DESC_DEF( fieldOffset ), + .flags = DT_INTEGER, + .multiplier = 1.0f, + .post_multiplier = 1.0f, + .bits = 16, + }, + { + DESC_DEF( fieldSize ), + .flags = DT_INTEGER, + .multiplier = 1.0f, + .post_multiplier = 1.0f, + .bits = 8, + }, + { + DESC_DEF( significant_bits ), + .flags = DT_INTEGER, + .multiplier = 1.0f, + .post_multiplier = 1.0f, + .bits = 8, + }, + { + DESC_DEF( premultiply ), + .flags = DT_FLOAT, + .multiplier = 4000.0f, + .post_multiplier = 1.0f, + .bits = 32, + }, + { + DESC_DEF( postmultiply ), + .flags = DT_FLOAT, + .multiplier = 4000.0f, + .post_multiplier = 1.0f, + .bits = 32, + }, + }, + .bInitialized = true +}; + +static delta_info_t *Delta_FindStruct( const char *name ) { int i; if( !COM_CheckString( name )) return NULL; - for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + for( i = 0; i < ARRAYSIZE( dt_info ); i++ ) { if( !Q_stricmp( dt_info[i].pName, name )) return &dt_info[i]; @@ -327,27 +452,24 @@ delta_info_t *Delta_FindStruct( const char *name ) return NULL; } -int Delta_NumTables( void ) +static int Delta_NumTables( void ) { - return NUM_FIELDS( dt_info ); + return ARRAYSIZE( dt_info ); } -delta_info_t *Delta_FindStructByIndex( int index ) +static delta_info_t *Delta_FindStructByIndex( int index ) { - if( index < 0 || index >= NUM_FIELDS( dt_info )) - return NULL; - return &dt_info[index]; } -delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) +static delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) { int i; if( !COM_CheckString( encoderName ) ) return NULL; - for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + for( i = 0; i < ARRAYSIZE( dt_info ); i++ ) { if( !Q_stricmp( dt_info[i].funcName, encoderName )) return &dt_info[i]; @@ -356,13 +478,13 @@ delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) return NULL; } -delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) +static delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) { int i; if( !pFields ) return NULL; - for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + for( i = 0; i < ARRAYSIZE( dt_info ); i++ ) { if( dt_info[i].pFields == pFields ) return &dt_info[i]; @@ -371,7 +493,7 @@ delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) return NULL; } -void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) +static void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) { int i; @@ -382,52 +504,48 @@ void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) dt->pFields[i].bInactive = false; if( dt->userCallback ) - { dt->userCallback( dt->pFields, from, to ); - } } -delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +static const delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName, int maxFields ) { + int i; + if( !fieldName || !*fieldName ) return NULL; - for( ; pInfo->name; pInfo++ ) + for( i = 0; i < maxFields; i++ ) { - if( !Q_strcmp( pInfo->name, fieldName )) - return (delta_field_t *)pInfo; + if( !Q_strcmp( pInfo[i].name, fieldName )) + return &pInfo[i]; } + return NULL; } -int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +static int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName, int maxFields ) { int i; if( !fieldName || !*fieldName ) return -1; - for( i = 0; pInfo->name; i++, pInfo++ ) + for( i = 0; i < maxFields; i++ ) { - if( !Q_strcmp( pInfo->name, fieldName )) + if( !Q_strcmp( pInfo[i].name, fieldName )) return i; } return -1; } -qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, int bits, float mul, float post_mul ) +static qboolean Delta_AddField( delta_info_t *dt, const char *pName, int flags, int bits, float mul, float post_mul ) { - delta_info_t *dt; - delta_field_t *pFieldInfo; + const delta_field_t *pFieldInfo; delta_t *pField; int i; - // get the delta struct - dt = Delta_FindStruct( pStructName ); - Assert( dt != NULL ); - // check for coexisting field - for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + for( i = 0, pField = dt->pFields; i < dt->numFields && pField; i++, pField++ ) { if( !Q_strcmp( pField->name, pName )) { @@ -441,16 +559,16 @@ qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, } // find field description - pFieldInfo = Delta_FindFieldInfo( dt->pInfo, pName ); + pFieldInfo = Delta_FindFieldInfo( dt->pInfo, pName, dt->maxFields ); if( !pFieldInfo ) { - Con_DPrintf( S_ERROR "Delta_Add: couldn't find description for %s->%s\n", pStructName, pName ); + Con_DPrintf( S_ERROR "%s: couldn't find description for %s->%s\n", __func__, dt->pName, pName ); return false; } if( dt->numFields + 1 > dt->maxFields ) { - Con_DPrintf( S_WARN "Delta_Add: can't add %s->%s encoder list is full\n", pStructName, pName ); + Con_DPrintf( S_WARN "%s: can't add %s->%s encoder list is full\n", __func__, dt->pName, pName ); return false; // too many fields specified (duplicated ?) } @@ -471,7 +589,7 @@ qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, return true; } -void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ) +static void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ) { int nameIndex; delta_info_t *dt; @@ -484,7 +602,7 @@ void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pFiel dt = Delta_FindStructByIndex( tableIndex ); Assert( dt && dt->bInitialized ); - nameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name ); + nameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name, dt->maxFields ); Assert( nameIndex >= 0 && nameIndex < dt->maxFields ); MSG_BeginServerCmd( msg, svc_deltatable ); @@ -494,14 +612,14 @@ void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pFiel MSG_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit) // multipliers is null-compressed - if( !Q_equal(pField->multiplier, 1.0f) ) + if( !Q_equal( pField->multiplier, 1.0f )) { MSG_WriteOneBit( msg, 1 ); MSG_WriteFloat( msg, pField->multiplier ); } else MSG_WriteOneBit( msg, 0 ); - if( !Q_equal(pField->post_multiplier, 1.0f) ) + if( !Q_equal( pField->post_multiplier, 1.0f )) { MSG_WriteOneBit( msg, 1 ); MSG_WriteFloat( msg, pField->post_multiplier ); @@ -521,7 +639,7 @@ void Delta_ParseTableField( sizebuf_t *msg ) tableIndex = MSG_ReadUBitLong( msg, 4 ); dt = Delta_FindStructByIndex( tableIndex ); if( !dt ) - Host_Error( "Delta_ParseTableField: not initialized" ); + Host_Error( "%s: not initialized", __func__ ); nameIndex = MSG_ReadUBitLong( msg, 8 ); // read field name index if( ( nameIndex >= 0 && nameIndex < dt->maxFields ) ) @@ -531,7 +649,7 @@ void Delta_ParseTableField( sizebuf_t *msg ) else { ignore = true; - Con_Reportf( "Delta_ParseTableField: wrong nameIndex %d for table %s, ignoring\n", nameIndex, dt->pName ); + Con_Reportf( "%s: wrong nameIndex %d for table %s, ignoring\n", __func__, nameIndex, dt->pName ); } flags = MSG_ReadUBitLong( msg, 10 ); @@ -552,40 +670,40 @@ void Delta_ParseTableField( sizebuf_t *msg ) Delta_Shutdown(); // add field to table - Delta_AddField( dt->pName, pName, flags, bits, mul, post_mul ); + Delta_AddField( dt, pName, flags, bits, mul, post_mul ); } -qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delta_t *pField, qboolean bPost ) +static qboolean Delta_ParseField( char **delta_script, const delta_info_t *dt, delta_t *pField, qboolean bPost ) { + const delta_field_t *pFieldInfo; string token; - delta_field_t *pFieldInfo; char *oldpos; *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "(" )) { - Con_DPrintf( S_ERROR "Delta_ParseField: expected '(', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected '(', found '%s' instead\n", __func__, token ); return false; } // read the variable name if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ParseField: missing field name\n" ); + Con_DPrintf( S_ERROR "%s: missing field name\n", __func__ ); return false; } - pFieldInfo = Delta_FindFieldInfo( pInfo, token ); + pFieldInfo = Delta_FindFieldInfo( dt->pInfo, token, dt->maxFields ); if( !pFieldInfo ) { - Con_DPrintf( S_ERROR "Delta_ParseField: unable to find field %s\n", token ); + Con_DPrintf( S_ERROR "%s: unable to find field %s\n", __func__, token ); return false; } *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ParseField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected ',', found '%s' instead\n", __func__, token ); return false; } @@ -626,14 +744,14 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ParseField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected ',', found '%s' instead\n", __func__, token ); return false; } // read delta-bits if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s field bits argument is missing\n", pField->name ); + Con_DPrintf( S_ERROR "%s: %s field bits argument is missing\n", __func__, pField->name ); return false; } @@ -642,14 +760,14 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ReadField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected ',', found '%s' instead\n", __func__, token ); return false; } // read delta-multiplier if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s missing 'multiplier' argument\n", pField->name ); + Con_DPrintf( S_ERROR "%s: %s missing 'multiplier' argument\n", __func__, pField->name ); return false; } @@ -660,14 +778,14 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ReadField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected ',', found '%s' instead\n", __func__, token ); return false; } // read delta-postmultiplier if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s missing 'post_multiply' argument\n", pField->name ); + Con_DPrintf( S_ERROR "%s: %s missing 'post_multiply' argument\n", __func__, pField->name ); return false; } @@ -683,7 +801,7 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, ")" )) { - Con_DPrintf( S_ERROR "Delta_ParseField: expected ')', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "%s: expected ')', found '%s' instead\n", __func__, token ); return false; } @@ -695,17 +813,15 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt return true; } -void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) +static void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) { string token; delta_t *pField; - const delta_field_t *pInfo; // allocate the delta-structures if( !dt->pFields ) dt->pFields = (delta_t *)Z_Calloc( dt->maxFields * sizeof( delta_t )); pField = dt->pFields; - pInfo = dt->pInfo; dt->numFields = 0; // assume we have handled '{' @@ -715,12 +831,12 @@ void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encode if( !Q_strcmp( token, "DEFINE_DELTA" )) { - if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], false )) + if( Delta_ParseField( delta_script, dt, &pField[dt->numFields], false )) dt->numFields++; } else if( !Q_strcmp( token, "DEFINE_DELTA_POST" )) { - if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], true )) + if( Delta_ParseField( delta_script, dt, &pField[dt->numFields], true )) dt->numFields++; } else if( token[0] == '}' ) @@ -749,7 +865,7 @@ void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encode dt->bInitialized = true; // table is ok } -void Delta_InitFields( void ) +static void Delta_InitFields( void ) { byte *afile; char *pfile; @@ -757,7 +873,7 @@ void Delta_InitFields( void ) delta_info_t *dt; afile = FS_LoadFile( DELTA_PATH, NULL, false ); - if( !afile ) Sys_Error( "DELTA_Load: couldn't load file %s\n", DELTA_PATH ); + if( !afile ) Sys_Error( "%s: couldn't load file %s\n", __func__, DELTA_PATH ); pfile = (char *)afile; @@ -773,7 +889,7 @@ void Delta_InitFields( void ) pfile = COM_ParseFile( pfile, encodeDll, sizeof( encodeDll )); if( !Q_stricmp( encodeDll, "none" )) - Q_strcpy( encodeFunc, "null" ); + Q_strncpy( encodeFunc, "null", sizeof( encodeFunc )); else pfile = COM_ParseFile( pfile, encodeFunc, sizeof( encodeFunc )); // jump to '{' @@ -800,44 +916,44 @@ void Delta_Init( void ) Delta_InitFields (); // initialize fields delta_init = true; - dt = Delta_FindStruct( "movevars_t" ); + dt = Delta_FindStructByIndex( DT_MOVEVARS_T ); Assert( dt != NULL ); if( dt->bInitialized ) return; // "movevars_t" already specified by user // create movevars_t delta internal - Delta_AddField( "movevars_t", "gravity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "stopspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "maxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "spectatormaxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "accelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "airaccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "wateraccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "friction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "edgefriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "waterfriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "bounce", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "stepsize", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); - Delta_AddField( "movevars_t", "maxvelocity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "gravity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "stopspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "maxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "spectatormaxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "accelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "airaccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "wateraccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "friction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "edgefriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "waterfriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "bounce", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "stepsize", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); + Delta_AddField( dt, "maxvelocity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) - Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 18, 1.0f, 1.0f ); - else Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( dt, "zmax", DT_FLOAT|DT_SIGNED, 18, 1.0f, 1.0f ); + else Delta_AddField( dt, "zmax", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); - Delta_AddField( "movevars_t", "waveHeight", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); - Delta_AddField( "movevars_t", "skyName", DT_STRING, 1, 1.0f, 1.0f ); - Delta_AddField( "movevars_t", "footsteps", DT_INTEGER, 1, 1.0f, 1.0f ); - Delta_AddField( "movevars_t", "rollangle", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); - Delta_AddField( "movevars_t", "rollspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); - Delta_AddField( "movevars_t", "skycolor_r", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); // 0 - 264 - Delta_AddField( "movevars_t", "skycolor_g", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); - Delta_AddField( "movevars_t", "skycolor_b", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); - Delta_AddField( "movevars_t", "skyvec_x", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // 0 - 1 - Delta_AddField( "movevars_t", "skyvec_y", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); - Delta_AddField( "movevars_t", "skyvec_z", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); - Delta_AddField( "movevars_t", "wateralpha", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); - Delta_AddField( "movevars_t", "fog_settings", DT_INTEGER, 32, 1.0f, 1.0f ); - dt->numFields = NUM_FIELDS( pm_fields ) - 4; + Delta_AddField( dt, "waveHeight", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); + Delta_AddField( dt, "skyName", DT_STRING, 1, 1.0f, 1.0f ); + Delta_AddField( dt, "footsteps", DT_INTEGER, 1, 1.0f, 1.0f ); + Delta_AddField( dt, "rollangle", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( dt, "rollspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( dt, "skycolor_r", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); // 0 - 264 + Delta_AddField( dt, "skycolor_g", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( dt, "skycolor_b", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( dt, "skyvec_x", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // 0 - 1 + Delta_AddField( dt, "skyvec_y", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( dt, "skyvec_z", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( dt, "wateralpha", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( dt, "fog_settings", DT_INTEGER, 32, 1.0f, 1.0f ); + dt->numFields = ARRAYSIZE( pm_fields ) - 4; // now done dt->bInitialized = true; @@ -850,7 +966,7 @@ void Delta_InitClient( void ) // already initalized if( delta_init ) return; - for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + for( i = 0; i < ARRAYSIZE( dt_info ); i++ ) { if( dt_info[i].numFields > 0 ) { @@ -868,7 +984,7 @@ void Delta_Shutdown( void ) if( !delta_init ) return; - for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + for( i = 0; i < ARRAYSIZE( dt_info ); i++ ) { dt_info[i].numFields = 0; dt_info[i].customEncode = CUSTOM_NONE; @@ -894,7 +1010,7 @@ Delta_ClampIntegerField prevent data to out of range ===================== */ -int Delta_ClampIntegerField( delta_t *pField, int iValue, qboolean bSigned, int numbits ) +static int Delta_ClampIntegerField( delta_t *pField, int iValue, int signbit, int numbits ) { #ifdef _DEBUG if( numbits < 32 && abs( iValue ) >= (uint)BIT( numbits )) @@ -902,10 +1018,13 @@ int Delta_ClampIntegerField( delta_t *pField, int iValue, qboolean bSigned, int #endif if( numbits < 32 ) { - int signbits = bSigned ? (numbits - 1) : numbits; + int signbits = numbits - signbit; int maxnum = BIT( signbits ) - 1; - int minnum = bSigned ? ( -maxnum - 1 ) : 0; - iValue = bound( minnum, iValue, maxnum ); + + if( iValue > maxnum ) + iValue = maxnum; + else if( signbit && iValue < -maxnum - 1 ) + iValue = -maxnum - 1; } return iValue; // clamped; @@ -919,9 +1038,9 @@ compare fields by offsets assume from and to is valid ===================== */ -qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timebase ) +static qboolean Delta_CompareField( delta_t *pField, const void *from, const void *to ) { - qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; + int signbit = ( pField->flags & DT_SIGNED ) ? 1 : 0; float val_a, val_b; int fromF, toF; @@ -936,66 +1055,69 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb if( pField->flags & DT_BYTE ) { - if( pField->flags & DT_SIGNED ) + if( signbit ) { - fromF = *(signed char *)((byte *)from + pField->offset ); - toF = *(signed char *)((byte *)to + pField->offset ); + fromF = *(int8_t *)((int8_t *)from + pField->offset ); + toF = *(int8_t *)((int8_t *)to + pField->offset ); } else { - fromF = *(byte *)((byte *)from + pField->offset ); - toF = *(byte *)((byte *)to + pField->offset ); + fromF = *(uint8_t *)((int8_t *)from + pField->offset ); + toF = *(uint8_t *)((int8_t *)to + pField->offset ); } - fromF = Delta_ClampIntegerField( pField, fromF, bSigned, pField->bits ); - toF = Delta_ClampIntegerField( pField, toF, bSigned, pField->bits ); - - if( !Q_equal(pField->multiplier, 1.0) ) + if( !Q_equal(pField->multiplier, 1.0f )) + { fromF *= pField->multiplier; - - if( !Q_equal( pField->multiplier, 1.0 ) ) toF *= pField->multiplier; + } + + fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits ); + toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits ); } else if( pField->flags & DT_SHORT ) { - if( pField->flags & DT_SIGNED ) + if( signbit ) { - fromF = *(short *)((byte *)from + pField->offset ); - toF = *(short *)((byte *)to + pField->offset ); + fromF = *(int16_t *)((int8_t *)from + pField->offset ); + toF = *(int16_t *)((int8_t *)to + pField->offset ); } else { - fromF = *(word *)((byte *)from + pField->offset ); - toF = *(word *)((byte *)to + pField->offset ); + fromF = *(uint16_t *)((int8_t *)from + pField->offset ); + toF = *(uint16_t *)((int8_t *)to + pField->offset ); } - fromF = Delta_ClampIntegerField( pField, fromF, bSigned, pField->bits ); - toF = Delta_ClampIntegerField( pField, toF, bSigned, pField->bits ); - - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal(pField->multiplier, 1.0f )) + { fromF *= pField->multiplier; - - if( !Q_equal( pField->multiplier, 1.0 ) ) toF *= pField->multiplier; + } + + fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits ); + toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits ); } else if( pField->flags & DT_INTEGER ) { - if( pField->flags & DT_SIGNED ) + if( signbit ) { - fromF = *(int *)((byte *)from + pField->offset ); - toF = *(int *)((byte *)to + pField->offset ); + fromF = *(int32_t *)((int8_t *)from + pField->offset ); + toF = *(int32_t *)((int8_t *)to + pField->offset ); } else { - fromF = *(uint *)((byte *)from + pField->offset ); - toF = *(uint *)((byte *)to + pField->offset ); + fromF = *(uint32_t *)((int8_t *)from + pField->offset ); + toF = *(uint32_t *)((int8_t *)to + pField->offset ); } - fromF = Delta_ClampIntegerField( pField, fromF, bSigned, pField->bits ); - toF = Delta_ClampIntegerField( pField, toF, bSigned, pField->bits ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + + if( !Q_equal(pField->multiplier, 1.0f )) + { fromF *= pField->multiplier; - if( !Q_equal( pField->multiplier, 1.0 ) ) toF *= pField->multiplier; + } + + fromF = Delta_ClampIntegerField( pField, fromF, signbit, pField->bits ); + toF = Delta_ClampIntegerField( pField, toF, signbit, pField->bits ); } else if( pField->flags & ( DT_ANGLE|DT_FLOAT )) { @@ -1005,33 +1127,17 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb } else if( pField->flags & DT_TIMEWINDOW_8 ) { - val_a = Q_rint((*(float *)((byte *)from + pField->offset )) * 100.0 ); - val_b = Q_rint((*(float *)((byte *)to + pField->offset )) * 100.0 ); - val_a -= Q_rint(timebase * 100.0); - val_b -= Q_rint(timebase * 100.0); - fromF = *((int *)&val_a); - toF = *((int *)&val_b); + val_a = *(float *)((byte *)from + pField->offset ); + val_b = *(float *)((byte *)to + pField->offset ); + fromF = Q_rint( val_a * 100.0 ); + toF = Q_rint( val_b * 100.0 ); } else if( pField->flags & DT_TIMEWINDOW_BIG ) { - val_a = (*(float *)((byte *)from + pField->offset )); - val_b = (*(float *)((byte *)to + pField->offset )); - - if( !Q_equal( pField->multiplier, 1.0 ) ) - { - val_a *= pField->multiplier; - val_b *= pField->multiplier; - val_a = (timebase * pField->multiplier) - val_a; - val_b = (timebase * pField->multiplier) - val_b; - } - else - { - val_a = timebase - val_a; - val_b = timebase - val_b; - } - - fromF = *((int *)&val_a); - toF = *((int *)&val_b); + val_a = *(float *)((byte *)from + pField->offset ); + val_b = *(float *)((byte *)to + pField->offset ); + fromF = Q_rint( val_a * pField->multiplier ); + toF = Q_rint( val_b * pField->multiplier ); } else if( pField->flags & DT_STRING ) { @@ -1043,7 +1149,7 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb toF = Q_strcmp( s1, s2 ); } - return ( fromF == toF ) ? true : false; + return fromF == toF; } /* @@ -1053,12 +1159,11 @@ Delta_TestBaseline compare baselines to find optimal ===================== */ -int Delta_TestBaseline( entity_state_t *from, entity_state_t *to, qboolean player, double timebase ) +int Delta_TestBaseline( const entity_state_t *from, const entity_state_t *to, qboolean player, double timebase ) { delta_info_t *dt = NULL; delta_t *pField; int i, countBits; - int numChanges = 0; countBits = MAX_ENTITY_BITS + 2; @@ -1069,10 +1174,10 @@ int Delta_TestBaseline( entity_state_t *from, entity_state_t *to, qboolean playe } if( FBitSet( to->entityType, ENTITY_BEAM )) - dt = Delta_FindStruct( "custom_entity_state_t" ); + dt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T ); else if( player ) - dt = Delta_FindStruct( "entity_state_player_t" ); - else dt = Delta_FindStruct( "entity_state_t" ); + dt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T ); + else dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T ); Assert( dt && dt->bInitialized ); @@ -1090,9 +1195,9 @@ int Delta_TestBaseline( entity_state_t *from, entity_state_t *to, qboolean playe // flag about field change (sets always) countBits++; - if( !Delta_CompareField( pField, from, to, timebase )) + if( !Delta_CompareField( pField, from, to )) { - // strings are handled difference + // strings are handled differently if( FBitSet( pField->flags, DT_STRING )) countBits += Q_strlen((char *)((byte *)to + pField->offset )) * 8; else countBits += pField->bits; @@ -1111,66 +1216,59 @@ write fields by offsets assume from and to is valid ===================== */ -qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) +static void Delta_WriteField_( sizebuf_t *msg, delta_t *pField, const void *from, const void *to, double timebase ) { - qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; + int signbit = FBitSet( pField->flags, DT_SIGNED ) ? 1 : 0; float flValue, flAngle; uint iValue; + int dt; const char *pStr; - if( Delta_CompareField( pField, from, to, timebase )) - { - MSG_WriteOneBit( msg, 0 ); // unchanged - return false; - } - - MSG_WriteOneBit( msg, 1 ); // changed - if( pField->flags & DT_BYTE ) { - if( bSigned ) + if( signbit ) iValue = *(int8_t *)((int8_t *)to + pField->offset ); else iValue = *(uint8_t *)((int8_t *)to + pField->offset ); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue *= pField->multiplier; - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits ); + MSG_WriteBitLong( msg, iValue, pField->bits, signbit ); } else if( pField->flags & DT_SHORT ) { - if( bSigned ) + if( signbit ) iValue = *(int16_t *)((int8_t *)to + pField->offset ); else iValue = *(uint16_t *)((int8_t *)to + pField->offset ); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue *= pField->multiplier; - - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + + iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits ); + MSG_WriteBitLong( msg, iValue, pField->bits, signbit ); } else if( pField->flags & DT_INTEGER ) { - if( bSigned ) + if( signbit ) iValue = *(int32_t *)((int8_t *)to + pField->offset ); else iValue = *(uint32_t *)((int8_t *)to + pField->offset ); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue *= pField->multiplier; - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits ); + MSG_WriteBitLong( msg, iValue, pField->bits, signbit ); } else if( pField->flags & DT_FLOAT ) { flValue = *(float *)((byte *)to + pField->offset ); iValue = (int)((double)flValue * pField->multiplier); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + iValue = Delta_ClampIntegerField( pField, iValue, signbit, pField->bits ); + MSG_WriteBitLong( msg, iValue, pField->bits, signbit ); } else if( pField->flags & DT_ANGLE ) { @@ -1182,25 +1280,37 @@ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to } else if( pField->flags & DT_TIMEWINDOW_8 ) { - bSigned = true; // timewindow is always signed flValue = *(float *)((byte *)to + pField->offset ); - iValue = (int)Q_rint( timebase * 100.0 ) - (int)Q_rint( flValue * 100.0 ); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + dt = Q_rint(( timebase - flValue ) * 100.0 ); + dt = Delta_ClampIntegerField( pField, dt, 1, pField->bits ); + MSG_WriteSBitLong( msg, dt, pField->bits ); } else if( pField->flags & DT_TIMEWINDOW_BIG ) { - bSigned = true; // timewindow is always signed flValue = *(float *)((byte *)to + pField->offset ); - iValue = (int)Q_rint( timebase * pField->multiplier ) - (int)Q_rint( flValue * pField->multiplier ); - iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); - MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + dt = Q_rint(( timebase - flValue ) * pField->multiplier ); + dt = Delta_ClampIntegerField( pField, dt, 1, pField->bits ); + MSG_WriteSBitLong( msg, dt, pField->bits ); } else if( pField->flags & DT_STRING ) { pStr = (char *)((byte *)to + pField->offset ); MSG_WriteString( msg, pStr ); } +} + +static qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, const void *from, const void *to, double timebase ) +{ + if( Delta_CompareField( pField, from, to )) + { + MSG_WriteOneBit( msg, 0 ); // unchanged + return false; + } + + MSG_WriteOneBit( msg, 1 ); // changed + + Delta_WriteField_( msg, pField, from, to, timebase ); + return true; } @@ -1210,7 +1320,7 @@ Delta_CopyField ==================== */ -static void Delta_CopyField( delta_t *pField, void *from, void *to, double timebase ) +static void Delta_CopyField( delta_t *pField, const void *from, void *to, double timebase ) { qboolean bSigned = FBitSet( pField->flags, DT_SIGNED ); uint8_t *to_field = (uint8_t *)to + pField->offset; @@ -1259,7 +1369,7 @@ read fields by offsets assume 'from' and 'to' is valid ===================== */ -qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) +static void Delta_ReadField_( sizebuf_t *msg, delta_t *pField, void *to, double timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; @@ -1267,20 +1377,17 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, const char *pStr; char *pOut; - if( !MSG_ReadOneBit( msg ) ) - { - Delta_CopyField( pField, from, to, timebase ); - return false; - } - Assert( pField->multiplier != 0.0f ); if( pField->flags & DT_BYTE ) { iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue /= pField->multiplier; + if( !Q_equal( pField->post_multiplier, 1.0 )) + iValue *= pField->post_multiplier; + if( bSigned ) *(int8_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1289,9 +1396,12 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, else if( pField->flags & DT_SHORT ) { iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue /= pField->multiplier; + if( !Q_equal( pField->post_multiplier, 1.0 )) + iValue *= pField->post_multiplier; + if( bSigned ) *(int16_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1300,9 +1410,12 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, else if( pField->flags & DT_INTEGER ) { iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) + if( !Q_equal( pField->multiplier, 1.0 )) iValue /= pField->multiplier; + if( !Q_equal( pField->post_multiplier, 1.0 )) + iValue *= pField->post_multiplier; + if( bSigned ) *(int32_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1316,11 +1429,11 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, else flValue = iValue; - if( !Q_equal( pField->multiplier, 1.0 ) ) - flValue = flValue / pField->multiplier; + if( !Q_equal( pField->multiplier, 1.0 )) + flValue /= pField->multiplier; - if( !Q_equal( pField->post_multiplier, 1.0 ) ) - flValue = flValue * pField->post_multiplier; + if( !Q_equal( pField->post_multiplier, 1.0 )) + flValue *= pField->post_multiplier; *(float *)((byte *)to + pField->offset ) = flValue; } @@ -1331,22 +1444,14 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_TIMEWINDOW_8 ) { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - flTime = (timebase * 100.0 - (int)iValue) / 100.0; - + iValue = MSG_ReadSBitLong( msg, pField->bits ); + flTime = ( timebase * 100.0 - (int)iValue ) / 100.0; *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_TIMEWINDOW_BIG ) { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - - if( !Q_equal( pField->multiplier, 1.0 ) ) - flTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier; - else - flTime = timebase - (int)iValue; - + iValue = MSG_ReadSBitLong( msg, pField->bits ); + flTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier; *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_STRING ) @@ -1355,9 +1460,85 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, pOut = (char *)((byte *)to + pField->offset ); Q_strncpy( pOut, pStr, pField->size ); } +} + +static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, const void *from, void *to, double timebase ) +{ + if( !MSG_ReadOneBit( msg )) + { + Delta_CopyField( pField, from, to, timebase ); + return false; + } + + Delta_ReadField_( msg, pField, to, timebase ); return true; } +static void Delta_ParseGSFields( sizebuf_t *msg, const delta_info_t *dt, const void *from, void *to, double timebase ) +{ + uint8_t bits[8] = { 0 }; + delta_t *pField; + byte c; + int i; + + c = MSG_ReadUBitLong( msg, 3 ); + + for( i = 0; i < c; i++ ) + bits[i] = MSG_ReadByte( msg ); + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + int b = i >> 3; + int n = 1 << ( i & 7 ); + + if( FBitSet( bits[b], n )) + Delta_ReadField_( msg, pField, to, timebase ); + else Delta_CopyField( pField, from, to, timebase ); + } +} + +void Delta_ReadGSFields( sizebuf_t *msg, int index, const void *from, void *to, double timebase ) +{ + const delta_info_t *dt = Delta_FindStructByIndex( index ); + Delta_ParseGSFields( msg, dt, from, to, timebase ); +} + +void Delta_WriteGSFields( sizebuf_t *msg, int index, const void *from, const void *to, double timebase ) +{ + delta_info_t *dt = Delta_FindStructByIndex( index ); + delta_t *pField; + uint8_t bits[8] = { 0 }; + uint c = 0; + int i; + + Delta_CustomEncode( dt, from, to ); + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + if( !Delta_CompareField( pField, from, to )) + { + int b = i >> 3; + int n = 1 << ( i & 7 ); + + SetBits( bits[b], n ); + c = b + 1; + } + } + + MSG_WriteUBitLong( msg, c, 3 ); + for( i = 0; i < c; i++ ) + MSG_WriteByte( msg, bits[i] ); + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + int b = i >> 3; + int n = 1 << ( i & 7 ); + + if( FBitSet( bits[b], n )) + Delta_WriteField_( msg, pField, from, to, timebase ); + } +} + /* ============================================================================= @@ -1370,13 +1551,13 @@ usercmd_t communication MSG_WriteDeltaUsercmd ===================== */ -void MSG_WriteDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) +void MSG_WriteDeltaUsercmd( sizebuf_t *msg, const usercmd_t *from, const usercmd_t *to ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "usercmd_t" ); + dt = Delta_FindStructByIndex( DT_USERCMD_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1397,13 +1578,13 @@ void MSG_WriteDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) MSG_ReadDeltaUsercmd ===================== */ -void MSG_ReadDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) +void MSG_ReadDeltaUsercmd( sizebuf_t *msg, const usercmd_t *from, usercmd_t *to ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "usercmd_t" ); + dt = Delta_FindStructByIndex( DT_USERCMD_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1432,13 +1613,13 @@ event_args_t communication MSG_WriteDeltaEvent ===================== */ -void MSG_WriteDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) +void MSG_WriteDeltaEvent( sizebuf_t *msg, const event_args_t *from, const event_args_t *to ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "event_t" ); + dt = Delta_FindStructByIndex( DT_EVENT_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1459,13 +1640,13 @@ void MSG_WriteDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) MSG_ReadDeltaEvent ===================== */ -void MSG_ReadDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) +void MSG_ReadDeltaEvent( sizebuf_t *msg, const event_args_t *from, event_args_t *to ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "event_t" ); + dt = Delta_FindStructByIndex( DT_EVENT_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1487,14 +1668,14 @@ movevars_t communication ============================================================================= */ -qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) +qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, const movevars_t *from, const movevars_t *to ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; - dt = Delta_FindStruct( "movevars_t" ); + dt = Delta_FindStructByIndex( DT_MOVEVARS_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1523,13 +1704,13 @@ qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *t return true; } -void MSG_ReadDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) +void MSG_ReadDeltaMovevars( sizebuf_t *msg, const movevars_t *from, movevars_t *to ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "movevars_t" ); + dt = Delta_FindStructByIndex( DT_MOVEVARS_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1559,14 +1740,14 @@ Writes current client data only for local client Other clients can grab the client state from entity_state_t ================== */ -void MSG_WriteClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, double timebase ) +void MSG_WriteClientData( sizebuf_t *msg, const clientdata_t *from, const clientdata_t *to, double timebase ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; - dt = Delta_FindStruct( "clientdata_t" ); + dt = Delta_FindStructByIndex( DT_CLIENTDATA_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1599,7 +1780,7 @@ MSG_ReadClientData Read the clientdata ================== */ -void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, double timebase ) +void MSG_ReadClientData( sizebuf_t *msg, const clientdata_t *from, clientdata_t *to, double timebase ) { #if !XASH_DEDICATED delta_t *pField; @@ -1607,7 +1788,7 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, d int i; qboolean noChanges; - dt = Delta_FindStruct( "clientdata_t" ); + dt = Delta_FindStructByIndex( DT_CLIENTDATA_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1640,14 +1821,14 @@ Writes current client data only for local client Other clients can grab the client state from entity_state_t ================== */ -void MSG_WriteWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, double timebase, int index ) +void MSG_WriteWeaponData( sizebuf_t *msg, const weapon_data_t *from, const weapon_data_t *to, double timebase, int index ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; - dt = Delta_FindStruct( "weapon_data_t" ); + dt = Delta_FindStructByIndex( DT_WEAPONDATA_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1679,13 +1860,13 @@ MSG_ReadWeaponData Read the clientdata ================== */ -void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, double timebase ) +void MSG_ReadWeaponData( sizebuf_t *msg, const weapon_data_t *from, weapon_data_t *to, double timebase ) { delta_t *pField; delta_info_t *dt; int i; - dt = Delta_FindStruct( "weapon_data_t" ); + dt = Delta_FindStructByIndex( DT_WEAPONDATA_T ); Assert( dt && dt->bInitialized ); pField = dt->pFields; @@ -1716,7 +1897,7 @@ If force is not set, then nothing at all will be generated if the entity is identical, under the assumption that the in-order delta code will catch it. ================== */ -void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, int delta_type, double timebase, int baseline ) +void MSG_WriteDeltaEntity( const entity_state_t *from, const entity_state_t *to, sizebuf_t *msg, qboolean force, int delta_type, double timebase, int baseline ) { delta_info_t *dt = NULL; delta_t *pField; @@ -1746,7 +1927,7 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * startBit = msg->iCurBit; if( to->number < 0 || to->number >= GI->max_edicts ) - Host_Error( "MSG_WriteDeltaEntity: Bad entity number: %i\n", to->number ); + Host_Error( "%s: Bad entity number: %i\n", __func__, to->number ); MSG_WriteUBitLong( msg, to->number, MAX_ENTITY_BITS ); MSG_WriteUBitLong( msg, 0, 2 ); // alive @@ -1768,15 +1949,15 @@ void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t * if( FBitSet( to->entityType, ENTITY_BEAM )) { - dt = Delta_FindStruct( "custom_entity_state_t" ); + dt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T ); } else if( delta_type == DELTA_PLAYER ) { - dt = Delta_FindStruct( "entity_state_player_t" ); + dt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T ); } else { - dt = Delta_FindStruct( "entity_state_t" ); + dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T ); } Assert( dt && dt->bInitialized ); @@ -1818,7 +1999,7 @@ If the delta removes the entity, entity_state_t->number will be set to MAX_EDICT Can go from either a baseline or a previous packet_entity ================== */ -qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int number, int delta_type, double timebase ) +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, const entity_state_t *from, entity_state_t *to, int number, int delta_type, double timebase ) { #if !XASH_DEDICATED delta_info_t *dt = NULL; @@ -1827,7 +2008,10 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state int baseline_offset = 0; if( number < 0 || number >= clgame.maxEntities ) - Host_Error( "MSG_ReadDeltaEntity: bad delta entity number: %i\n", number ); + { + Con_Printf( S_ERROR "%s: bad delta entity number: %i\n", __func__, number ); + return false; + } fRemoveType = MSG_ReadUBitLong( msg, 2 ); @@ -1849,7 +2033,8 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state return false; } - Host_Error( "MSG_ReadDeltaEntity: unknown update type %i\n", fRemoveType ); + Con_Printf( S_ERROR "%s: unknown update type %i\n", __func__, fRemoveType ); + return false; } if( !cls.legacymode ) @@ -1886,18 +2071,22 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state if( cls.legacymode ? ( to->entityType == ENTITY_BEAM ) : FBitSet( to->entityType, ENTITY_BEAM )) { - dt = Delta_FindStruct( "custom_entity_state_t" ); + dt = Delta_FindStructByIndex( DT_CUSTOM_ENTITY_STATE_T ); } else if( delta_type == DELTA_PLAYER ) { - dt = Delta_FindStruct( "entity_state_player_t" ); + dt = Delta_FindStructByIndex( DT_ENTITY_STATE_PLAYER_T ); } else { - dt = Delta_FindStruct( "entity_state_t" ); + dt = Delta_FindStructByIndex( DT_ENTITY_STATE_T ); } - Assert( dt && dt->bInitialized ); + if( !dt || !dt->bInitialized ) + { + Con_Printf( S_ERROR "%s: broken delta\n", __func__ ); + return true; + } pField = dt->pFields; Assert( pField != NULL ); @@ -1912,6 +2101,65 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state return true; } +void Delta_ParseTableField_GS( sizebuf_t *msg ) +{ + const char *s = MSG_ReadString( msg ); + delta_info_t *dt = Delta_FindStruct( s ); + goldsrc_delta_t null = { 0 }; + int i, num_fields; + + // delta encoders it's already initialized on this machine (local game) + if( delta_init ) + Delta_Shutdown(); + + if( !dt ) + Host_Error( "%s: not initialized", __func__ ); + + num_fields = MSG_ReadShort( msg ); + if( num_fields > dt->maxFields ) + Host_Error( "%s: numFields > maxFields", __func__ ); + + MSG_StartBitWriting( msg ); + + for( i = 0; i < num_fields; i++ ) + { + goldsrc_delta_t to; + + Delta_ParseGSFields( msg, &dt_goldsrc_meta, &null, &to, 0.0f ); + + // patch our DT_SIGNED flag + if( FBitSet( to.fieldType, DT_SIGNED_GS )) + { + ClearBits( to.fieldType, DT_SIGNED_GS ); + SetBits( to.fieldType, DT_SIGNED ); + } + Delta_AddField( dt, to.fieldName, to.fieldType, to.significant_bits, to.premultiply, to.postmultiply ); + } + + MSG_EndBitWriting( msg ); +} + +/* +================== +Delta_WriteDescriptionToClient + +send delta communication encoding +================== +*/ +void Delta_WriteDescriptionToClient( sizebuf_t *msg ) +{ + int tableIndex; + int fieldIndex; + + for( tableIndex = 0; tableIndex < Delta_NumTables(); tableIndex++ ) + { + delta_info_t *dt = Delta_FindStructByIndex( tableIndex ); + + for( fieldIndex = 0; fieldIndex < dt->numFields; fieldIndex++ ) + Delta_WriteTableField( msg, tableIndex, &dt->pFields[fieldIndex] ); + } +} + /* ============================================================================= @@ -1927,13 +2175,13 @@ void GAME_EXPORT Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ) if( !dt || !dt->bInitialized ) { - Con_DPrintf( S_ERROR "Delta_AddEncoder: couldn't find delta with specified custom encode %s\n", name ); + Con_DPrintf( S_ERROR "%s: couldn't find delta with specified custom encode %s\n", __func__, name ); return; } if( dt->customEncode == CUSTOM_NONE ) { - Con_DPrintf( S_ERROR "Delta_AddEncoder: %s not supposed for custom encoding\n", dt->pName ); + Con_DPrintf( S_ERROR "%s: %s not supposed for custom encoding\n", __func__, dt->pName ); return; } @@ -2020,3 +2268,99 @@ void GAME_EXPORT Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber ) dt->pFields[fieldNumber].bInactive = true; } + +#if XASH_ENGINE_TESTS +#include "tests.h" + +void Test_RunDelta( void ) +{ + delta_info_t *dt = &dt_info[DT_DELTA_TEST_STRUCT_T]; + delta_test_struct_t from, to = { 0 }; + delta_test_struct_t null = { 0 }; + sizebuf_t msg; + int i; + char buffer[4096] = { 0 }; + const double timebase = 123.123; + + Delta_AddField( dt, "dt_string", DT_STRING, 1, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_timewindow_big", DT_TIMEWINDOW_BIG, 24, 1000.f, 1.0f ); + Delta_AddField( dt, "dt_timewindow_8", DT_TIMEWINDOW_8, 8, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_angle", DT_ANGLE, 16, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_float_signed", DT_FLOAT | DT_SIGNED, 22, 100.0f, 1.0f ); + Delta_AddField( dt, "dt_float_unsigned", DT_FLOAT, 24, 10000.0f, 0.1f ); + Delta_AddField( dt, "dt_integer_signed", DT_INTEGER | DT_SIGNED, 24, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_integer_unsigned", DT_INTEGER, 24, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_short_signed", DT_SHORT | DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_short_unsigned", DT_SHORT, 15, 0.125f, 1.0f ); + Delta_AddField( dt, "dt_byte_signed", DT_BYTE | DT_SIGNED, 6, 1.0f, 1.0f ); + Delta_AddField( dt, "dt_byte_unsigned", DT_BYTE, 8, 1.0f, 1.0f ); + + Q_strncpy( from.dt_string, "test data check it's the same", sizeof( from.dt_string )); + from.dt_timewindow_big = timebase + 2.3456; + from.dt_timewindow_8 = timebase + 0.0234; + from.dt_angle = 160.245f; + from.dt_float_signed = -15.123f; + from.dt_float_unsigned = 1235.321f; + from.dt_integer_signed = -412784; + from.dt_integer_unsigned = 123453; + from.dt_short_signed = -12343; + from.dt_short_unsigned = 32131; + from.dt_byte_signed = 16; + from.dt_byte_unsigned = 218; + + MSG_Init( &msg, "test message", buffer, sizeof( buffer )); + + for( i = 0; i < dt->numFields; i++ ) + Delta_WriteField( &msg, &dt->pFields[i], &null, &from, timebase ); + + MSG_SeekToBit( &msg, 0, SEEK_SET ); + + for( i = 0; i < dt->numFields; i++ ) + Delta_ReadField( &msg, &dt->pFields[i], &null, &to, timebase ); + + Con_Printf( "struct as encoded to delta:\n" ); + TASSERT_STR( from.dt_string, to.dt_string ); + + // the epsilon value is derived from multiplier value + TASSERT( Q_equal_e( from.dt_timewindow_big, to.dt_timewindow_big, 0.001f )); + + // dt_timewindow_8 type has multiplier locked at 100.0f + TASSERT( Q_equal_e( from.dt_timewindow_8, to.dt_timewindow_8, 0.01f )); + TASSERT( Q_equal_e( from.dt_angle, to.dt_angle, 0.1f )); + TASSERT( Q_equal_e( from.dt_float_signed, to.dt_float_signed, 0.01f )); + + // dt_float_unsigned has post-multiplier that doesn't affect network data + // and therefore should be reverted back when comparing + TASSERT( Q_equal_e( from.dt_float_unsigned, to.dt_float_unsigned * 10.f , 0.01f )); + + TASSERT_EQi( from.dt_integer_signed, to.dt_integer_signed ); + TASSERT_EQi( from.dt_integer_unsigned, to.dt_integer_unsigned ); + TASSERT_EQi( from.dt_short_signed, to.dt_short_signed ); + TASSERT(( from.dt_short_unsigned & ( 0xffff << 3 )) == to.dt_short_unsigned ); + TASSERT_EQi( from.dt_byte_signed, to.dt_byte_signed ); + TASSERT_EQi( from.dt_byte_unsigned, to.dt_byte_unsigned ); + + Con_Printf( "from.dt_timewindow_big = %f\n", from.dt_timewindow_big ); + Con_Printf( "to.dt_timewindow_big = %f\n", to.dt_timewindow_big ); + Con_Printf( "from.dt_timewindow_8 = %f\n", from.dt_timewindow_8 ); + Con_Printf( "to.dt_timewindow_8 = %f\n", to.dt_timewindow_8 ); + Con_Printf( "from.dt_angle = %f\n", from.dt_angle ); + Con_Printf( "to.dt_angle = %f\n", to.dt_angle ); + Con_Printf( "from.dt_float_signed = %f\n", from.dt_float_signed ); + Con_Printf( "to.dt_float_signed = %f\n", to.dt_float_signed ); + Con_Printf( "from.dt_float_unsigned = %f\n", from.dt_float_unsigned ); + Con_Printf( "to.dt_float_unsigned = %f\n", to.dt_float_unsigned ); + Con_Printf( "from.dt_integer_signed = %i\n", from.dt_integer_signed ); + Con_Printf( "to.dt_integer_signed = %i\n", to.dt_integer_signed ); + Con_Printf( "from.dt_integer_unsigned = %i\n", from.dt_integer_unsigned ); + Con_Printf( "to.dt_integer_unsigned = %i\n", to.dt_integer_unsigned ); + Con_Printf( "from.dt_short_signed = %i\n", from.dt_short_signed ); + Con_Printf( "to.dt_short_signed = %i\n", to.dt_short_signed ); + Con_Printf( "from.dt_short_unsigned = %i\n", from.dt_short_unsigned ); + Con_Printf( "to.dt_short_unsigned = %i\n", to.dt_short_unsigned ); + Con_Printf( "from.dt_byte_signed = %i\n", from.dt_byte_signed ); + Con_Printf( "to.dt_byte_signed = %i\n", to.dt_byte_signed ); + Con_Printf( "from.dt_byte_unsigned = %i\n", from.dt_byte_unsigned ); + Con_Printf( "to.dt_byte_unsigned = %i\n", to.dt_byte_unsigned ); +} +#endif // XASH_ENGINE_TESTS diff --git a/engine/common/net_encode.h b/engine/common/net_encode.h index 85d533c9..9a3ac92e 100644 --- a/engine/common/net_encode.h +++ b/engine/common/net_encode.h @@ -18,26 +18,6 @@ GNU General Public License for more details. #include "eiface.h" -#define DT_BYTE BIT( 0 ) // A byte -#define DT_SHORT BIT( 1 ) // 2 byte field -#define DT_FLOAT BIT( 2 ) // A floating point field -#define DT_INTEGER BIT( 3 ) // 4 byte integer -#define DT_ANGLE BIT( 4 ) // A floating point angle ( will get masked correctly ) -#define DT_TIMEWINDOW_8 BIT( 5 ) // A floating point timestamp, relative to sv.time -#define DT_TIMEWINDOW_BIG BIT( 6 ) // and re-encoded on the client relative to the client's clock -#define DT_STRING BIT( 7 ) // A null terminated string, sent as 8 byte chars -#define DT_SIGNED BIT( 8 ) // sign modificator - -#define NUM_FIELDS( x ) ((sizeof( x ) / sizeof( x[0] )) - 1) - -// helper macroses -#define ENTS_DEF( x ) #x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x ) -#define UCMD_DEF( x ) #x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x ) -#define EVNT_DEF( x ) #x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x ) -#define PHYS_DEF( x ) #x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x ) -#define CLDT_DEF( x ) #x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x ) -#define WPDT_DEF( x ) #x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x ) - enum { CUSTOM_NONE = 0, @@ -53,6 +33,21 @@ enum DELTA_STATIC, }; +enum +{ + DT_EVENT_T = 0, + DT_MOVEVARS_T, + DT_USERCMD_T, + DT_CLIENTDATA_T, + DT_WEAPONDATA_T, + DT_ENTITY_STATE_T, + DT_ENTITY_STATE_PLAYER_T, + DT_CUSTOM_ENTITY_STATE_T, +#if XASH_ENGINE_TESTS + DT_DELTA_TEST_STRUCT_T, +#endif +}; + // struct info (filled by engine) typedef struct { @@ -74,6 +69,17 @@ struct delta_s qboolean bInactive; // unsetted by user request }; +typedef struct goldsrc_delta_s +{ + int fieldType; + char fieldName[32]; + int fieldOffset; + short fieldSize; + int significant_bits; + float premultiply; + float postmultiply; +} goldsrc_delta_t; + typedef void (*pfnDeltaEncode)( struct delta_s *pFields, const byte *from, const byte *to ); typedef struct @@ -97,9 +103,6 @@ typedef struct void Delta_Init( void ); void Delta_InitClient( void ); void Delta_Shutdown( void ); -void Delta_InitFields( void ); -int Delta_NumTables( void ); -delta_info_t *Delta_FindStructByIndex( int index ); void Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ); int Delta_FindField( delta_t *pFields, const char *fieldname ); void Delta_SetField( delta_t *pFields, const char *fieldname ); @@ -108,8 +111,9 @@ void Delta_SetFieldByIndex( delta_t *pFields, int fieldNumber ); void Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber ); // send table over network -void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ); +void Delta_WriteDescriptionToClient( sizebuf_t *msg ); void Delta_ParseTableField( sizebuf_t *msg ); +void Delta_ParseTableField_GS( sizebuf_t *msg ); // encode routines @@ -119,18 +123,20 @@ struct event_args_s; struct movevars_s; struct clientdata_s; struct weapon_data_s; -void MSG_WriteDeltaUsercmd( sizebuf_t *msg, struct usercmd_s *from, struct usercmd_s *to ); -void MSG_ReadDeltaUsercmd( sizebuf_t *msg, struct usercmd_s *from, struct usercmd_s *to ); -void MSG_WriteDeltaEvent( sizebuf_t *msg, struct event_args_s *from, struct event_args_s *to ); -void MSG_ReadDeltaEvent( sizebuf_t *msg, struct event_args_s *from, struct event_args_s *to ); -qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct movevars_s *to ); -void MSG_ReadDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct movevars_s *to ); -void MSG_WriteClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, double timebase ); -void MSG_ReadClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, double timebase ); -void MSG_WriteWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, double timebase, int index ); -void MSG_ReadWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, double timebase ); -void MSG_WriteDeltaEntity( struct entity_state_s *from, struct entity_state_s *to, sizebuf_t *msg, qboolean force, int type, double timebase, int ofs ); -qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, struct entity_state_s *from, struct entity_state_s *to, int num, int type, double timebase ); -int Delta_TestBaseline( struct entity_state_s *from, struct entity_state_s *to, qboolean player, double timebase ); +void MSG_WriteDeltaUsercmd( sizebuf_t *msg, const struct usercmd_s *from, const struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( sizebuf_t *msg, const struct usercmd_s *from, struct usercmd_s *to ); +void MSG_WriteDeltaEvent( sizebuf_t *msg, const struct event_args_s *from, const struct event_args_s *to ); +void MSG_ReadDeltaEvent( sizebuf_t *msg, const struct event_args_s *from, struct event_args_s *to ); +qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, const struct movevars_s *from, const struct movevars_s *to ); +void MSG_ReadDeltaMovevars( sizebuf_t *msg, const struct movevars_s *from, struct movevars_s *to ); +void MSG_WriteClientData( sizebuf_t *msg, const struct clientdata_s *from, const struct clientdata_s *to, double timebase ); +void MSG_ReadClientData( sizebuf_t *msg, const struct clientdata_s *from, struct clientdata_s *to, double timebase ); +void MSG_WriteWeaponData( sizebuf_t *msg, const struct weapon_data_s *from, const struct weapon_data_s *to, double timebase, int index ); +void MSG_ReadWeaponData( sizebuf_t *msg, const struct weapon_data_s *from, struct weapon_data_s *to, double timebase ); +void 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 ); +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, const struct entity_state_s *from, struct entity_state_s *to, int num, int type, double timebase ); +int Delta_TestBaseline( const struct entity_state_s *from, const struct entity_state_s *to, qboolean player, double timebase ); +void Delta_ReadGSFields( sizebuf_t *msg, int index, const void *from, void *to, double timebase ); +void Delta_WriteGSFields( sizebuf_t *msg, int index, const void *from, const void *to, double timebase ); #endif//NET_ENCODE_H diff --git a/engine/common/net_http.c b/engine/common/net_http.c new file mode 100644 index 00000000..ff6fd41b --- /dev/null +++ b/engine/common/net_http.c @@ -0,0 +1,1224 @@ +/* +net_http.c - HTTP client implementation +Copyright (C) 2024 mittorn +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "client.h" // ConnectionProgress +#include "netchan.h" +#include "xash3d_mathlib.h" +#include "ipv6text.h" +#include "net_ws_private.h" +#include "miniz.h" + +/* +================================================= + +HTTP downloader + +================================================= +*/ + +#define MAX_HTTP_BUFFER_SIZE (BIT( 16 )) + +typedef struct httpserver_s +{ + char host[256]; + int port; + char path[MAX_SYSPATH]; + struct httpserver_s *next; +} httpserver_t; + +typedef struct httpfile_s httpfile_t; +typedef int (*http_process_fn_t)( httpfile_t *file ); + +typedef struct httpfile_s +{ + struct httpfile_s *next; + httpserver_t *server; + char path[MAX_SYSPATH]; + file_t *file; + int socket; + int size; + int reported_size; + int downloaded; + int lastchecksize; + float checktime; + float blocktime; + const char *blockreason; + qboolean process; + qboolean got_response; + qboolean success; + qboolean compressed; + qboolean chunked; + int chunksize; + resource_t *resource; + http_process_fn_t pfn_process; + struct sockaddr_storage addr; + + char query_backup[1024]; + + // query or response + char buf[MAX_HTTP_BUFFER_SIZE+1]; + int header_size, query_length, bytes_sent; +} httpfile_t; + +static struct http_static_s +{ + // file and server lists + httpfile_t *first_file; + httpserver_t *first_server; + + int active_count, progress_count; + float progress; + qboolean resolving; +} http; + + +static CVAR_DEFINE_AUTO( http_useragent, "", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "User-Agent string" ); +static CVAR_DEFINE_AUTO( http_autoremove, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "remove broken files" ); +static CVAR_DEFINE_AUTO( http_timeout, "45", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "timeout for http downloader" ); +static CVAR_DEFINE_AUTO( http_maxconnections, "2", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum http connection number" ); +static CVAR_DEFINE_AUTO( http_show_headers, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "show HTTP headers (request and response)" ); + +static int HTTP_FileFree( httpfile_t *file ); +static int HTTP_FileConnect( httpfile_t *file ); +static int HTTP_FileCreateSocket( httpfile_t *file ); +static int HTTP_FileProcessStream( httpfile_t *file ); +static int HTTP_FileQueue( httpfile_t *file ); +static int HTTP_FileResolveNS( httpfile_t *file ); +static int HTTP_FileSendRequest( httpfile_t *file ); +static int HTTP_FileDecompress( httpfile_t *file ); + +/* +============== +HTTP_FreeFile + +Skip to next server/file +============== +*/ +static void HTTP_FreeFile( httpfile_t *file, qboolean error ) +{ + char incname[MAX_SYSPATH + 64]; // plus downloaded/ plus .incomplete + qboolean was_open = false; + + file->blocktime = 0; + + // Allways close file and socket + if( file->file ) + { + FS_Close( file->file ); + was_open = true; + } + + file->file = NULL; + + if( file->socket != -1 ) + { + closesocket( file->socket ); + http.active_count--; + } + + file->socket = -1; + + Q_snprintf( incname, sizeof( incname ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path ); + + if( error ) + { + // switch to next fastdl server if present + if( file->server && was_open ) + { + file->server = file->server->next; + + file->pfn_process = HTTP_FileQueue; // Reset download state, HTTP_Run() will open file again + return; + } + + // Called because there was no servers to download, free file now + if( http_autoremove.value == 1 ) // remove broken file + { + Con_Printf( S_ERROR "no servers to download %s\n", file->path ); + FS_Delete( incname ); + } + else // autoremove disabled, keep file + { + // warn about trash file + Con_Printf( S_ERROR "no servers to download %s. You may remove %s now\n", file->path, incname ); + } + } + else + { + if( file->compressed ) + { + FS_Delete( incname ); + } + else + { + // Success, rename and process file + char name[MAX_SYSPATH]; + Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path ); + FS_Rename( incname, name ); + } + } + + file->pfn_process = HTTP_FileFree; + file->success = !error; +} + +static int HTTP_FileFree( httpfile_t *file ) +{ + return 0; // do nothing, wait for memory clean up +} + +static int HTTP_FileQueue( httpfile_t *file ) +{ + char name[MAX_SYSPATH]; + + if( http.active_count > http_maxconnections.value ) + return 0; + + if( !file->server ) + { + HTTP_FreeFile( file, true ); + return 0; + } + + Con_Reportf( "HTTP: Starting download %s from %s:%d\n", file->path, file->server->host, file->server->port ); + Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s.incomplete", file->path ); + + if( !( file->file = FS_Open( name, "wb+", true ))) + { + Con_Printf( S_ERROR "HTTP: cannot open %s!\n", name ); + HTTP_FreeFile( file, true ); + return 0; + } + + file->pfn_process = HTTP_FileResolveNS; + file->blocktime = file->downloaded = file->lastchecksize = file->checktime = 0; + return 1; +} + +static int HTTP_FileResolveNS( httpfile_t *file ) +{ + net_gai_state_t res; + + if( http.resolving ) + return 0; + + memset( &file->addr, 0, sizeof( file->addr )); + + res = NET_StringToSockaddr( file->server->host, &file->addr, true, AF_UNSPEC ); + + switch( file->addr.ss_family ) + { + case AF_INET: + ((struct sockaddr_in *)&file->addr)->sin_port = MSG_BigShort( file->server->port ); + break; + case AF_INET6: + ((struct sockaddr_in6 *)&file->addr)->sin6_port = MSG_BigShort( file->server->port ); + break; + } + + if( res == NET_EAI_AGAIN ) + { + http.resolving = true; + return 0; + } + + if( res == NET_EAI_NONAME ) + { + Con_Printf( S_ERROR "failed to resolve server address for %s!\n", file->server->host ); + HTTP_FreeFile( file, true ); + return 0; + } + + file->pfn_process = HTTP_FileCreateSocket; + return 1; +} + +static int HTTP_FileCreateSocket( httpfile_t *file ) +{ + uint mode = 1; + int res; + + file->socket = socket( file->addr.ss_family, SOCK_STREAM, IPPROTO_TCP ); + + if( file->socket < 0 ) + { + Con_Printf( S_ERROR "%s: socket() returned %s\n", __func__, NET_ErrorString()); + HTTP_FreeFile( file, true ); + return 0; + } + + if( ioctlsocket( file->socket, FIONBIO, (void *)&mode ) < 0 ) + { + Con_Printf( S_ERROR "%s: ioctl() returned %s\n", __func__, NET_ErrorString()); + HTTP_FreeFile( file, true ); + return 0; + } + +#if XASH_LINUX + + res = fcntl( file->socket, F_GETFL, 0 ); + + if( res < 0 ) + { + Con_Printf( S_ERROR "%s: fcntl( F_GETFL ) returned %s\n", __func__, NET_ErrorString()); + HTTP_FreeFile( file, true ); + return 0; + } + + // SOCK_NONBLOCK is not portable, so use fcntl + if( fcntl( file->socket, F_SETFL, res | O_NONBLOCK ) < 0 ) + { + Con_Printf( S_ERROR "%s: fcntl( F_SETFL ) returned %s\n", __func__, NET_ErrorString()); + HTTP_FreeFile( file, true ); + return 0; + } +#endif + + http.active_count++; + file->pfn_process = HTTP_FileConnect; + return 1; +} + +static int HTTP_FileConnect( httpfile_t *file ) +{ + string useragent; + int res = connect( file->socket, (struct sockaddr *)&file->addr, NET_SockAddrLen( &file->addr )); + + if( res < 0 ) + { + int err = WSAGetLastError(); + + switch( err ) + { + case WSAEISCONN: + // we're connected, proceed + break; + case WSAEWOULDBLOCK: + case WSAEINPROGRESS: + case WSAEALREADY: + // add to the timeout + file->blocktime += host.frametime; + file->blockreason = "request send"; + return 0; + default: + // error, exit + Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( )); + HTTP_FreeFile( file, true ); + return 0; + } + } + + file->blocktime = 0; + + if( !COM_CheckStringEmpty( http_useragent.string ) || !Q_strcmp( http_useragent.string, "xash3d" )) + { + Q_snprintf( useragent, sizeof( useragent ), "%s/%s (%s-%s; build %d; %s)", + XASH_ENGINE_NAME, XASH_VERSION, Q_buildos( ), Q_buildarch( ), Q_buildnum( ), g_buildcommit ); + } + else Q_strncpy( useragent, http_useragent.string, sizeof( useragent )); + + file->query_length = Q_snprintf( file->buf, sizeof( file->buf ), + "GET %s%s HTTP/1.1\r\n" + "Host: %s:%d\r\n" + "User-Agent: %s\r\n" + "Accept-Encoding: gzip, deflate\r\n" + "Accept: */*\r\n\r\n", + file->server->path, file->path, + file->server->host, file->server->port, + useragent ); + Q_strncpy( file->query_backup, file->buf, sizeof( file->query_backup )); + file->bytes_sent = 0; + file->header_size = 0; + file->pfn_process = HTTP_FileSendRequest; + return 1; +} + +static int HTTP_FileSendRequest( httpfile_t *file ) +{ + int res = -1; + + res = send( file->socket, file->buf + file->bytes_sent, file->query_length - file->bytes_sent, 0 ); + + if( res >= 0 ) + { + file->bytes_sent += res; + file->blocktime = 0; + + if( file->bytes_sent >= file->query_length ) + { + if( http_show_headers.value ) + Con_Reportf( "HTTP: Request sent! (size %d data %s)\n", file->bytes_sent, file->buf ); + else + Con_Reportf( "HTTP: Request sent!\n" ); + memset( file->buf, 0, sizeof( file->buf )); + file->pfn_process = HTTP_FileProcessStream; + return 1; + } + } + else + { + int err = WSAGetLastError(); + if( err != WSAEWOULDBLOCK && err != WSAENOTCONN ) + { + Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString( )); + HTTP_FreeFile( file, true ); + return 0; + } + + file->blocktime += host.frametime; + file->blockreason = "request send"; + } + + return 0; +} + +static int HTTP_FileDecompress( httpfile_t *file ) +{ + fs_offset_t len; +#pragma pack( push, 1 ) + struct + { + byte magic[2]; + byte method; + byte flags; + uint32_t mtime; // ignored + byte xfl; + byte os; // can be ignored + } hdr; +#pragma pack( pop ) + + enum + { + GZFLG_FTEXT = BIT( 0 ), // can be ignored + GZFLG_FHCRC = BIT( 1 ), + GZFLG_FEXTRA = BIT( 2 ), + GZFLG_FNAME = BIT( 3 ), + GZFLG_FCOMMENT = BIT( 4 ) + }; + + z_stream decompress_stream; + char name[MAX_SYSPATH]; + fs_offset_t deflate_pos; + size_t compressed_len, decompressed_len; + byte *data_in, *data_out; + int zlib_result; + + g_fsapi.Seek( file->file, 0, SEEK_END ); + len = g_fsapi.Tell( file->file ); + + g_fsapi.Seek( file->file, 0, SEEK_SET ); + if( g_fsapi.Read( file->file, &hdr, sizeof( hdr )) != sizeof( hdr )) + { + HTTP_FreeFile( file, true ); + return 0; + } + + if( hdr.magic[0] != 0x1f && hdr.magic[1] != 0x8b && hdr.method != 0x08 ) + { + HTTP_FreeFile( file, true ); + return 0; + } + + if( FBitSet( hdr.flags, GZFLG_FEXTRA )) + { + byte res[2]; + uint16_t xlen; + + g_fsapi.Read( file->file, res, sizeof( res )); + xlen = res[0] | res[1] << 16; + g_fsapi.Seek( file->file, xlen, SEEK_CUR ); + } + + if( FBitSet( hdr.flags, GZFLG_FNAME )) + { + byte ch; + do + { + g_fsapi.Read( file->file, &ch, sizeof( ch )); + } while( ch != 0 ); + } + + if( FBitSet( hdr.flags, GZFLG_FCOMMENT )) + { + byte ch; + do + { + g_fsapi.Read( file->file, &ch, sizeof( ch )); + } while( ch != 0 ); + } + + if( FBitSet( hdr.flags, GZFLG_FHCRC )) + g_fsapi.Seek( file->file, 2, SEEK_CUR ); + + deflate_pos = g_fsapi.Tell( file->file ); + compressed_len = len - deflate_pos; + + { + byte data[4]; + + g_fsapi.Seek( file->file, -4, SEEK_END ); + g_fsapi.Read( file->file, data, sizeof( data )); + + // FIXME: this isn't correct, as this size might be modulo 2^32 + // but we probably won't decompress files this big + decompressed_len = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24; + } + + data_in = Mem_Malloc( host.mempool, compressed_len + 1 ); + data_out = Mem_Malloc( host.mempool, decompressed_len + 1 ); + + Q_snprintf( name, sizeof( name ), DEFAULT_DOWNLOADED_DIRECTORY "%s", file->path ); + + memset( &decompress_stream, 0, sizeof( decompress_stream )); + decompress_stream.total_in = decompress_stream.avail_in = compressed_len; + decompress_stream.next_in = data_in; + decompress_stream.total_out = decompress_stream.avail_out = decompressed_len; + decompress_stream.next_out = data_out; + + g_fsapi.Seek( file->file, deflate_pos, SEEK_SET ); + g_fsapi.Read( file->file, data_in, compressed_len ); + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "%s: inflateInit2 failed\n", __func__ ); + Mem_Free( data_in ); + Mem_Free( data_out ); + HTTP_FreeFile( file, true ); + return 0; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + g_fsapi.WriteFile( name, data_out, decompressed_len ); + HTTP_FreeFile( file, false ); + } + else HTTP_FreeFile( file, true ); + + Mem_Free( data_in ); + Mem_Free( data_out ); + + return 1; +} + +/* +======================== +HTTP_ClearCustomServers +======================== +*/ +void HTTP_ClearCustomServers( void ) +{ + if( http.first_file ) + return; // may be referenced + + while( http.first_server ) + { + httpserver_t *tmp = http.first_server; + + http.first_server = http.first_server->next; + Mem_Free( tmp ); + } +} + + +/* +=================== +HTTP_AutoClean + +remove files with HTTP_FREE state from list +=================== +*/ +static void HTTP_AutoClean( void ) +{ + char buf[1024]; + httpfile_t *cur, **prev = &http.first_file; + sizebuf_t msg; + + MSG_Init( &msg, "DlFile", buf, sizeof( buf )); + + // clean all files marked to free + while( 1 ) + { + cur = *prev; + + if( !cur ) + break; + + if( cur->pfn_process != HTTP_FileFree ) + { + prev = &cur->next; + continue; + } + +#if !XASH_DEDICATED + if( cur->process ) + { + if( cur->resource && !cur->success ) + { + MSG_BeginClientCmd( &msg, clc_stringcmd ); + MSG_WriteStringf( &msg, "dlfile %s", cur->path ); + } + else CL_ProcessFile( cur->success, cur->path ); + } + else +#endif + { + if( cur->success ) + Con_Printf( "successfully downloaded %s!\n", cur->path ); + } + + *prev = cur->next; + Mem_Free( cur ); + } + +#if !XASH_DEDICATED + if( MSG_GetNumBytesWritten( &msg ) > 0 ) + { + // it's expected to be on fragments channel + Netchan_CreateFragments( &cls.netchan, &msg ); + Netchan_FragSend( &cls.netchan ); + } +#endif +} + +static int HTTP_FileSaveReceivedData( httpfile_t *file, int pos, int length ) +{ + while( length > 0 ) + { + int oldpos = pos; + int ret; + int len_to_write; + + if( file->chunked && file->chunksize <= 0 ) + { + char *begin = &file->buf[pos]; + + if( begin[0] == '\r' && begin[1] == '\r' ) + begin += 2; + + file->chunksize = Q_atoi_hex( 1, begin ); + + if( !file->chunksize && begin[0] == '0' ) // actually an end, not Q_atoi being stupid + { + if( file->compressed ) + { + file->blocktime = 0; + file->pfn_process = HTTP_FileDecompress; + return 1; + } + else + { + fs_offset_t filelen = FS_FileLength( file->file ); + + if( filelen != file->reported_size ) + { + Con_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 ); + HTTP_FreeFile( file, true ); + } + else + { + HTTP_FreeFile( file, false ); // success + } + + return 1; + } + } + + begin = Q_strstr( begin, "\r\n" ); + if( !begin ) + { + Con_Printf( S_ERROR "can't parse chunked transfer encoding header for %s\n", file->path ); + if( http_show_headers.value ) + Con_Reportf( "Request headers: %s", file->query_backup ); + HTTP_FreeFile( file, true ); + return 0; + } + + pos = ( begin + 2 ) - file->buf; + length -= pos - oldpos; + + if( length < 0 ) + { + Con_Printf( S_ERROR "can't parse chunked transfer encoding header 2 for %s\n", file->path ); + if( http_show_headers.value ) + Con_Reportf( "Request headers: %s", file->query_backup ); + HTTP_FreeFile( file, true ); + return 0; + } + } + + if( file->chunked ) + len_to_write = Q_min( length, file->chunksize ); + else len_to_write = length; + + ret = FS_Write( file->file, &file->buf[pos], len_to_write ); + if( ret != len_to_write ) + { + // close it and go to next + Con_Printf( S_ERROR "write failed for %s!\n", file->path ); + HTTP_FreeFile( file, true ); + return 0; + } + + length -= len_to_write; + file->chunksize -= len_to_write; + + pos += ret; + file->downloaded += ret; + file->lastchecksize += ret; + } + + return 1; +} + +/* +=================== +HTTP_ProcessStream + +process incoming data +=================== +*/ +static int HTTP_FileProcessStream( httpfile_t *curfile ) +{ + char buf[sizeof( curfile->buf )]; + char *begin = 0; + int res; + + // if we got there, we are receiving data + while(( res = recv( curfile->socket, buf, sizeof( buf ) - curfile->header_size - 1, 0 )) > 0 ) + { + curfile->blocktime = 0; + + if( !curfile->got_response ) // Response still not received + { + if( curfile->header_size + res + 1 > sizeof( buf )) + { + Con_Reportf( S_ERROR "Header too big, the size is %d\n", curfile->header_size ); + HTTP_FreeFile( curfile, true ); + return 0; + } + + memcpy( curfile->buf + curfile->header_size, buf, res ); + curfile->buf[curfile->header_size + res] = 0; + begin = Q_strstr( curfile->buf, "\r\n\r\n" ); + + if( begin ) // Got full header + { + char *content_length; + char *content_encoding; + char *transfer_encoding; + + *begin = 0; // cut string to print out response + + if( !Q_strstr( curfile->buf, "200 OK" )) + { + char *p; + + int num = -1; + + p = Q_strchr( curfile->buf, '\r' ); + if( !p ) p = Q_strchr( curfile->buf, '\n' ); + if( p ) *p = 0; + + // extract the error code, don't assume the response is valid HTTP + if( !Q_strncmp( curfile->buf, "HTTP/1.", 7 )) + { + char tmp[4]; + + Q_strncpy( tmp, curfile->buf + 9, sizeof( tmp )); + if( Q_isdigit( tmp )) + num = Q_atoi( tmp ); + } + + switch( num ) + { + // TODO: handle redirects + case 404: + Con_Printf( S_ERROR "%s: file not found\n", curfile->path ); + break; + default: + Con_Printf( S_ERROR "%s: bad response: %s\n", curfile->path, curfile->buf ); + if( http_show_headers.value ) + Con_Printf( "Request headers: %s", curfile->query_backup ); + break; + } + + HTTP_FreeFile( curfile, true ); + return 0; + } + + content_encoding = Q_stristr( curfile->buf, "Content-Encoding" ); + if( content_encoding ) // fetch compressed status + { + content_encoding += sizeof( "Content-Encoding: " ) - 1; + + if( !Q_strnicmp( content_encoding, "gzip", 4 ) && ( content_encoding[4] == '\0' || content_encoding[4] == '\n' || content_encoding[4] == '\r' )) + curfile->compressed = true; + else + { + Con_Printf( S_ERROR "%s: bad Content-Encoding: %s\n", curfile->path, content_encoding ); + if( http_show_headers.value ) + Con_Printf( "Request headers: %s", curfile->query_backup ); + HTTP_FreeFile( curfile, true ); + return 0; + } + } + + if(( transfer_encoding = Q_stristr( curfile->buf, "Transfer-Encoding: chunked" ))) + { + curfile->size = -1; + curfile->chunked = true; + + Con_Reportf( "HTTP: Got 200 OK! Chunked transfer encoding%s\n", curfile->compressed ? ", compressed" : "" ); + } + else if(( content_length = Q_stristr( curfile->buf, "Content-Length: " ) )) + { + int size; + + content_length += sizeof( "Content-Length: " ) - 1; + size = Q_atoi( content_length ); + + Con_Reportf( "HTTP: Got 200 OK! File size is %d%s\n", curfile->size, curfile->compressed ? ", compressed" : "" ); + + if( !curfile->compressed ) + { + if( ( curfile->size != -1 ) && ( curfile->size != size )) // check size if specified, not used + Con_Reportf( S_WARN "Server reports wrong file size for %s!\n", curfile->path ); + } + + curfile->size = size; + curfile->header_size = 0; + } + + if( curfile->size == -1 && !curfile->chunked ) + { + // Usually fastdl's reports file size if link is correct + Con_Printf( S_ERROR "file size is unknown, refusing download!\n" ); + HTTP_FreeFile( curfile, true ); + return 0; + } + + if( http_show_headers.value ) + Con_Reportf( "Response headers: %s\n", curfile->buf ); + + curfile->got_response = true; // got response, let's start download + begin += 4; + + if( res - ( begin - curfile->buf ) > 0 ) + { + if( !HTTP_FileSaveReceivedData( curfile, begin - curfile->buf, res - ( begin - curfile->buf ))) + return 0; + } + } + else + curfile->header_size += res; + } + else if( res > 0 ) + { + memcpy( curfile->buf, buf, res ); + + // data download + if( !HTTP_FileSaveReceivedData( curfile, 0, res )) + return 0; + + // as after it will run in same frame + if( curfile->checktime > 5 ) + { + float speed = (float)curfile->lastchecksize / ( 5.0f * 1024 ); + + curfile->checktime = 0; + Con_Reportf( "download speed %f KB/s\n", speed ); + curfile->lastchecksize = 0; + } + } + } + + if( curfile->size > 0 ) + { + http.progress += (float)curfile->downloaded / curfile->size; + http.progress_count++; + + if( curfile->downloaded >= curfile->size ) + { + // chunked files are finalized in FileSaveReceivedData + if( curfile->compressed && !curfile->chunked ) + { + curfile->pfn_process = HTTP_FileDecompress; + curfile->success = true; + } + else + { + HTTP_FreeFile( curfile, false ); // success + } + return 0; + } + } + + if( res == 0 ) + { + curfile->blocktime += host.frametime; + curfile->blockreason = "waiting for data"; + } + + if( res < 0 ) + { + int err = WSAGetLastError(); + + if( err != WSAEWOULDBLOCK && err != WSAEINPROGRESS ) + { + Con_Reportf( "problem downloading %s: %s\n", curfile->path, NET_ErrorString( )); + HTTP_FreeFile( curfile, true ); + return 0; + } + + curfile->blocktime += host.frametime; + + if( !curfile->got_response ) + curfile->blockreason = "receiving header"; + else curfile->blockreason = "receiving data"; + return 0; + } + + curfile->checktime += host.frametime; + return 0; // don't block +} + +/* +============== +HTTP_Run + +Download next file block of each active file +Call every frame +============== +*/ +void HTTP_Run( void ) +{ + httpfile_t *curfile; + + http.resolving = false; + http.progress_count = 0; + http.progress = 0; + + for( curfile = http.first_file; curfile; curfile = curfile->next ) + { + int move_next = 1; + + while( move_next > 0 ) + move_next = curfile->pfn_process( curfile ); + + if( curfile->blocktime > http_timeout.value ) + { + Con_Printf( S_ERROR "timeout on %s (file: %s)\n", curfile->blockreason, curfile->path ); + HTTP_FreeFile( curfile, true ); + } + } + + // update progress + if( !Host_IsDedicated() && http.progress_count != 0 ) + Cvar_SetValue( "scr_download", http.progress/http.progress_count * 100 ); + + HTTP_AutoClean(); +} + +/* +=================== +HTTP_AddDownload + +Add new download to end of queue +=================== +*/ +void HTTP_AddDownload( const char *path, int size, qboolean process, resource_t *res ) +{ + httpfile_t *httpfile; + + if( !http.first_server ) + { + Con_Printf( S_ERROR "no servers to download %s\n", path ); + return; + } + + httpfile = Z_Calloc( sizeof( *httpfile )); + + Con_Reportf( "File %s queued to download\n", path ); + + httpfile->resource = res; + httpfile->size = size; + httpfile->reported_size = size; + httpfile->socket = -1; + Q_strncpy( httpfile->path, path, sizeof( httpfile->path )); + + httpfile->pfn_process = HTTP_FileQueue; + httpfile->server = http.first_server; + httpfile->process = process; + + httpfile->next = http.first_file; + http.first_file = httpfile; +} + +/* +=============== +HTTP_Download_f + +Console wrapper +=============== +*/ +static void HTTP_Download_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "download \n"); + return; + } + + HTTP_AddDownload( Cmd_Argv( 1 ), -1, false, NULL ); +} + +/* +============== +HTTP_ParseURL +============== +*/ +static httpserver_t *HTTP_ParseURL( const char *url ) +{ + httpserver_t *server; + int i; + + url = Q_strstr( url, "http://" ); + + if( !url ) + return NULL; + + url += 7; + server = Z_Calloc( sizeof( httpserver_t )); + i = 0; + + while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' )) + { + if( i > sizeof( server->host )) + return NULL; + + server->host[i++] = *url++; + } + + server->host[i] = 0; + + if( *url == ':' ) + { + server->port = Q_atoi( ++url ); + + while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' )) + url++; + } + else + server->port = 80; + + i = 0; + + while( *url && ( *url != '\r' ) && ( *url != '\n' )) + { + if( i > sizeof( server->path ) - 1 ) + return NULL; + + server->path[i++] = *url++; + } + + if( i == 0 || server->path[i-1] != '/' ) + server->path[i++] = '/'; + server->path[i] = 0; + server->next = NULL; + + return server; +} + +/* +======================= +HTTP_AddCustomServer +======================= +*/ +void HTTP_AddCustomServer( const char *url ) +{ + httpserver_t *server = HTTP_ParseURL( url ); + + if( !server ) + { + Con_Printf( S_ERROR "\"%s\" is not valid url!\n", url ); + return; + } + + server->next = http.first_server; + http.first_server = server; +} + +/* +======================= +HTTP_AddCustomServer_f +======================= +*/ +static void HTTP_AddCustomServer_f( void ) +{ + if( Cmd_Argc() == 2 ) + { + HTTP_AddCustomServer( Cmd_Argv( 1 )); + } + else + { + Con_Printf( S_USAGE "http_addcustomserver \n" ); + } +} + +/* +============ +HTTP_Clear_f + +Clear all queue +============ +*/ +static void HTTP_Clear_f( void ) +{ + while( http.first_file ) + { + httpfile_t *file = http.first_file; + + http.first_file = http.first_file->next; + + if( file->file ) + FS_Close( file->file ); + + if( file->socket != -1 ) + closesocket( file->socket ); + + Mem_Free( file ); + } +} + +/* +============== +HTTP_Cancel_f + +Stop current download, skip to next file +============== +*/ +static void HTTP_Cancel_f( void ) +{ + if( !http.first_file ) + return; + + http.first_file->server = NULL; + HTTP_FreeFile( http.first_file, true ); +} + +/* +============= +HTTP_Skip_f + +Stop current download, skip to next server +============= +*/ +static void HTTP_Skip_f( void ) +{ + if( http.first_file ) + HTTP_FreeFile( http.first_file, true ); +} + +/* +============= +HTTP_List_f + +Print all pending downloads to console +============= +*/ +static void HTTP_List_f( void ) +{ + int i = 0; + httpfile_t *file; + + if( !http.first_file ) + Con_Printf( "no downloads queued\n" ); + + for( file = http.first_file; file; file = file->next ) + { + Con_Printf( "%d. %s (%d of %d)\n", i++, file->path, file->downloaded, file->size ); + + if( file->server ) + { + httpserver_t *server; + for( server = file->server; server; server = server->next ) + { + Con_Printf( "\thttp://%s:%d/%s%s\n", file->server->host, file->server->port, + file->server->path, file->path ); + } + } + } +} + +/* +================ +HTTP_ResetProcessState + +When connected to new server, all old files should not increase counter +================ +*/ +void HTTP_ResetProcessState( void ) +{ + httpfile_t *file; + + for( file = http.first_file; file; file = file->next ) + file->process = false; +} + +/* +============= +HTTP_Init +============= +*/ +void HTTP_Init( void ) +{ + http.first_file = NULL; + + Cmd_AddRestrictedCommand( "http_download", HTTP_Download_f, "add file to download queue" ); + Cmd_AddRestrictedCommand( "http_skip", HTTP_Skip_f, "skip current download server" ); + Cmd_AddRestrictedCommand( "http_cancel", HTTP_Cancel_f, "cancel current download" ); + Cmd_AddRestrictedCommand( "http_clear", HTTP_Clear_f, "cancel all downloads" ); + Cmd_AddRestrictedCommand( "http_list", HTTP_List_f, "list all queued downloads" ); + Cmd_AddCommand( "http_addcustomserver", HTTP_AddCustomServer_f, "add custom fastdl server"); + + Cvar_RegisterVariable( &http_useragent ); + Cvar_RegisterVariable( &http_autoremove ); + Cvar_RegisterVariable( &http_timeout ); + Cvar_RegisterVariable( &http_maxconnections ); + Cvar_RegisterVariable( &http_show_headers ); +} + +/* +==================== +HTTP_Shutdown +==================== +*/ +void HTTP_Shutdown( void ) +{ + HTTP_Clear_f(); + + while( http.first_server ) + { + httpserver_t *tmp = http.first_server; + + http.first_server = http.first_server->next; + Mem_Free( tmp ); + } +} diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index 53f3418f..7ee4cba0 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -17,95 +17,27 @@ GNU General Public License for more details. #include "client.h" // ConnectionProgress #include "netchan.h" #include "xash3d_mathlib.h" -#if XASH_WIN32 -// Winsock -#include -typedef int WSAsize_t; +#include "ipv6text.h" +#include "net_ws_private.h" -#elif !defined XASH_NO_NETWORK -// BSD sockets -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define WSAGetLastError() errno -#define WSAEINTR EINTR -#define WSAEBADF EBADF -#define WSAEACCES EACCES -#define WSAEFAULT EFAULT -#define WSAEINVAL EINVAL -#define WSAEMFILE EMFILE -#define WSAEWOULDBLOCK EWOULDBLOCK -#define WSAEINPROGRESS EINPROGRESS -#define WSAEALREADY EALREADY -#define WSAENOTSOCK ENOTSOCK -#define WSAEDESTADDRREQ EDESTADDRREQ -#define WSAEMSGSIZE EMSGSIZE -#define WSAEPROTOTYPE EPROTOTYPE -#define WSAENOPROTOOPT ENOPROTOOPT -#define WSAEPROTONOSUPPORT EPROTONOSUPPORT -#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT -#define WSAEOPNOTSUPP EOPNOTSUPP -#define WSAEPFNOSUPPORT EPFNOSUPPORT -#define WSAEAFNOSUPPORT EAFNOSUPPORT -#define WSAEADDRINUSE EADDRINUSE -#define WSAEADDRNOTAVAIL EADDRNOTAVAIL -#define WSAENETDOWN ENETDOWN -#define WSAENETUNREACH ENETUNREACH -#define WSAENETRESET ENETRESET -#define WSAECONNABORTED ECONNABORTED -#define WSAECONNRESET ECONNRESET -#define WSAENOBUFS ENOBUFS -#define WSAEISCONN EISCONN -#define WSAENOTCONN ENOTCONN -#define WSAESHUTDOWN ESHUTDOWN -#define WSAETOOMANYREFS ETOOMANYREFS -#define WSAETIMEDOUT ETIMEDOUT -#define WSAECONNREFUSED ECONNREFUSED -#define WSAELOOP ELOOP -#define WSAENAMETOOLONG ENAMETOOLONG -#define WSAEHOSTDOWN EHOSTDOWN - - -#ifndef XASH_DOS4GW -#define HAVE_GETADDRINFO -#define INVALID_SOCKET -1 -#define SOCKET_ERROR -1 - -#if XASH_EMSCRIPTEN -/* All socket operations are non-blocking already */ -static int ioctl_stub( int d, unsigned long r, ... ) -{ - return 0; -} -#define ioctlsocket ioctl_stub -#else // XASH_EMSCRIPTEN -#define ioctlsocket ioctl -#endif // XASH_EMSCRIPTEN -#define closesocket close -#endif -#define SOCKET int -typedef int WSAsize_t; -#else -#include "platform/stub/net_stub.h" +#if XASH_SDL == 2 +#include #endif #define NET_USE_FRAGMENTS -#define PORT_ANY -1 #define MAX_LOOPBACK 4 #define MASK_LOOPBACK (MAX_LOOPBACK - 1) -#define MAX_ROUTEABLE_PACKET 1400 -#define SPLITPACKET_MIN_SIZE 508 // RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header) -#define SPLITPACKET_MAX_SIZE 64000 -#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET )) ) +#define MAX_ROUTEABLE_PACKET 1400 +#define SPLITPACKET_MIN_SIZE 508 // RFC 791: 576(min ip packet) - 60 (ip header) - 8 (udp header) +#define SPLITPACKET_MAX_SIZE 64000 +#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / (SPLITPACKET_MIN_SIZE - sizeof( SPLITPACKET ))) +#define NET_MAX_GOLDSRC_FRAGMENTS 5 // magic number + +// ff02:1 +static const uint8_t k_ipv6Bytes_LinkLocalAllNodes[16] = +{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; typedef struct { @@ -146,6 +78,13 @@ typedef struct int sequence_number; short packet_id; } SPLITPACKET; + +typedef struct +{ + int net_id; + int sequence_number; + unsigned char packet_id; +} SPLITPACKETGS; #pragma pack(pop) typedef struct @@ -158,94 +97,41 @@ typedef struct int split_flags[NET_MAX_FRAGMENTS]; int sequence_number; int ip_sockets[NS_COUNT]; + int ip6_sockets[NS_COUNT]; qboolean initialized; qboolean threads_initialized; qboolean configured; qboolean allow_ip; + qboolean allow_ip6; #if XASH_WIN32 WSADATA winsockdata; #endif } net_state_t; static net_state_t net; -static convar_t *net_ipname; -static convar_t *net_hostport; -static convar_t *net_iphostport; -static convar_t *net_clientport; -static convar_t *net_ipclientport; -static convar_t *net_fakelag; -static convar_t *net_fakeloss; -static convar_t *net_address; -convar_t *net_clockwindow; +static CVAR_DEFINE_AUTO( net_address, "0", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, "contain local address of current client" ); +static CVAR_DEFINE( net_ipname, "ip", "localhost", FCVAR_PRIVILEGED, "network ip address" ); +static CVAR_DEFINE( net_iphostport, "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); +static CVAR_DEFINE( net_hostport, "hostport", "0", FCVAR_READ_ONLY, "network default host port" ); +static CVAR_DEFINE( net_ipclientport, "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); +static CVAR_DEFINE( net_clientport, "clientport", "0", FCVAR_READ_ONLY, "network default client port" ); +static CVAR_DEFINE( net_fakelag, "fakelag", "0", FCVAR_PRIVILEGED, "lag all incoming network data (including loopback) by xxx ms." ); +static CVAR_DEFINE( net_fakeloss, "fakeloss", "0", FCVAR_PRIVILEGED, "act like we dropped the packet this % of the time." ); +static CVAR_DEFINE_AUTO( net_resolve_debug, "0", FCVAR_PRIVILEGED, "print resolve thread debug messages" ); +CVAR_DEFINE( net_clockwindow, "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" ); + netadr_t net_local; +static netadr_t net6_local; -/* -==================== -NET_ErrorString -==================== -*/ -char *NET_ErrorString( void ) -{ -#if XASH_WIN32 - int err = WSANOTINITIALISED; +// cvars equivalents for IPv6 +static CVAR_DEFINE( net_ip6name, "ip6", "localhost", FCVAR_PRIVILEGED, "network ip6 address" ); +static CVAR_DEFINE( net_ip6hostport, "ip6_hostport", "0", FCVAR_READ_ONLY, "network ip6 host port" ); +static CVAR_DEFINE( net_ip6clientport, "ip6_clientport", "0", FCVAR_READ_ONLY, "network ip6 client port" ); +static CVAR_DEFINE_AUTO( net6_address, "0", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, "contain local IPv6 address of current client" ); - if( net.initialized ) - err = WSAGetLastError(); +static void NET_ClearLagData( qboolean bClient, qboolean bServer ); - switch( err ) - { - case WSAEINTR: return "WSAEINTR"; - case WSAEBADF: return "WSAEBADF"; - case WSAEACCES: return "WSAEACCES"; - case WSAEFAULT: return "WSAEFAULT"; - case WSAEINVAL: return "WSAEINVAL"; - case WSAEMFILE: return "WSAEMFILE"; - case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; - case WSAEINPROGRESS: return "WSAEINPROGRESS"; - case WSAEALREADY: return "WSAEALREADY"; - case WSAENOTSOCK: return "WSAENOTSOCK"; - case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; - case WSAEMSGSIZE: return "WSAEMSGSIZE"; - case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; - case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; - case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; - case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; - case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; - case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; - case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; - case WSAEADDRINUSE: return "WSAEADDRINUSE"; - case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; - case WSAENETDOWN: return "WSAENETDOWN"; - case WSAENETUNREACH: return "WSAENETUNREACH"; - case WSAENETRESET: return "WSAENETRESET"; - case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; - case WSAECONNRESET: return "WSAECONNRESET"; - case WSAENOBUFS: return "WSAENOBUFS"; - case WSAEISCONN: return "WSAEISCONN"; - case WSAENOTCONN: return "WSAENOTCONN"; - case WSAESHUTDOWN: return "WSAESHUTDOWN"; - case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; - case WSAETIMEDOUT: return "WSAETIMEDOUT"; - case WSAECONNREFUSED: return "WSAECONNREFUSED"; - case WSAELOOP: return "WSAELOOP"; - case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; - case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; - case WSAEDISCON: return "WSAEDISCON"; - case WSASYSNOTREADY: return "WSASYSNOTREADY"; - case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; - case WSANOTINITIALISED: return "WSANOTINITIALISED"; - case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; - case WSATRY_AGAIN: return "WSATRY_AGAIN"; - case WSANO_RECOVERY: return "WSANO_RECOVERY"; - case WSANO_DATA: return "WSANO_DATA"; - default: return "NO ERROR"; - } -#else - return strerror( errno ); -#endif -} - -_inline qboolean NET_IsSocketError( int retval ) +static inline qboolean NET_IsSocketError( int retval ) { #if XASH_WIN32 || XASH_DOS4GW return retval == SOCKET_ERROR ? true : false; @@ -254,7 +140,7 @@ _inline qboolean NET_IsSocketError( int retval ) #endif } -_inline qboolean NET_IsSocketValid( int socket ) +static inline qboolean NET_IsSocketValid( int socket ) { #if XASH_WIN32 || XASH_DOS4GW return socket != INVALID_SOCKET; @@ -263,26 +149,62 @@ _inline qboolean NET_IsSocketValid( int socket ) #endif } +void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr ) +{ + memcpy( &ip6[0], adr->ip6_0, 2 ); + memcpy( &ip6[2], adr->ip6_1, 14 ); +} + +void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 ) +{ + memcpy( adr->ip6_0, &ip6[0], 2 ); + memcpy( adr->ip6_1, &ip6[2], 14 ); +} + +static int NET_NetadrIP6Compare( const netadr_t *a, const netadr_t *b ) +{ + uint8_t ip6_a[16], ip6_b[16]; + + NET_NetadrToIP6Bytes( ip6_a, a ); + NET_NetadrToIP6Bytes( ip6_b, b ); + + return memcmp( ip6_a, ip6_b, sizeof( ip6_a )); +} + /* ==================== NET_NetadrToSockadr ==================== */ -static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr *s ) +static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s ) { + netadrtype_t type = NET_NetadrType( a ); + memset( s, 0, sizeof( *s )); - if( a->type == NA_BROADCAST ) + if( type == NA_BROADCAST ) { - ((struct sockaddr_in *)s)->sin_family = AF_INET; + s->ss_family = AF_INET; ((struct sockaddr_in *)s)->sin_port = a->port; ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; } - else if( a->type == NA_IP ) + else if( type == NA_IP ) { - ((struct sockaddr_in *)s)->sin_family = AF_INET; - ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + s->ss_family = AF_INET; ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = a->ip4; + } + else if( type == NA_IP6 ) + { + s->ss_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + NET_NetadrToIP6Bytes(((struct sockaddr_in6 *)s)->sin6_addr.s6_addr, a ); + } + else if( type == NA_MULTICAST_IP6 ) + { + s->ss_family = AF_INET6; + ((struct sockaddr_in6 *)s)->sin6_port = a->port; + memcpy(((struct sockaddr_in6 *)s)->sin6_addr.s6_addr, k_ipv6Bytes_LinkLocalAllNodes, sizeof( struct in6_addr )); } } @@ -291,14 +213,20 @@ static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr *s ) NET_SockadrToNetAdr ==================== */ -static void NET_SockadrToNetadr( struct sockaddr *s, netadr_t *a ) +static void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a ) { - if( s->sa_family == AF_INET ) + if( s->ss_family == AF_INET ) { - a->type = NA_IP; - *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + NET_NetadrSetType( a, NA_IP ); + a->ip4 = ((struct sockaddr_in *)s)->sin_addr.s_addr; a->port = ((struct sockaddr_in *)s)->sin_port; } + else if( s->ss_family == AF_INET6 ) + { + NET_NetadrSetType( a, NA_IP6 ); + NET_IP6BytesToNetadr( a, ((struct sockaddr_in6 *)s)->sin6_addr.s6_addr ); + a->port = ((struct sockaddr_in6 *)s)->sin6_port; + } } /* @@ -306,23 +234,29 @@ static void NET_SockadrToNetadr( struct sockaddr *s, netadr_t *a ) NET_GetHostByName ============ */ -int NET_GetHostByName( const char *hostname ) +static qboolean NET_GetHostByName( const char *hostname, int family, struct sockaddr_storage *addr ) { -#ifdef HAVE_GETADDRINFO +#if defined HAVE_GETADDRINFO struct addrinfo *ai = NULL, *cur; struct addrinfo hints; - int ip = 0; + qboolean ret = false; + +#if XASH_NO_IPV6_RESOLVE + if( family == AF_INET6 ) + return false; +#endif memset( &hints, 0, sizeof( hints )); - hints.ai_family = AF_INET; + hints.ai_family = family; if( !getaddrinfo( hostname, NULL, &hints, &ai )) { for( cur = ai; cur; cur = cur->ai_next ) { - if( cur->ai_family == AF_INET ) + if( family == AF_UNSPEC || cur->ai_family == family ) { - ip = *((int*)&((struct sockaddr_in *)cur->ai_addr)->sin_addr); + memcpy( addr, cur->ai_addr, cur->ai_addrlen ); + ret = true; break; } } @@ -331,83 +265,117 @@ int NET_GetHostByName( const char *hostname ) freeaddrinfo( ai ); } - return ip; + return ret; #else struct hostent *h; + +#if XASH_NO_IPV6_RESOLVE + if( family == AF_INET6 ) + return false; +#endif + if(!( h = gethostbyname( hostname ))) - return 0; - return *(int *)h->h_addr_list[0]; + return false; + + ((struct sockaddr_in *)addr)->sin_family = AF_INET; + ((struct sockaddr_in *)addr)->sin_addr = *(struct in_addr *)h->h_addr_list[0]; + + return true; #endif } -#if !defined XASH_NO_ASYNC_NS_RESOLVE && ( XASH_WIN32 || !(XASH_EMSCRIPTEN || XASH_DOS4GW) ) +#if !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE #define CAN_ASYNC_NS_RESOLVE -#endif +#endif // !XASH_EMSCRIPTEN && !XASH_DOS4GW && !defined XASH_NO_ASYNC_NS_RESOLVE #ifdef CAN_ASYNC_NS_RESOLVE static void NET_ResolveThread( void ); -#if !XASH_WIN32 +#if XASH_SDL == 2 +#define mutex_create( x ) (( x ) = SDL_CreateMutex() ) +#define mutex_destroy( x ) SDL_DestroyMutex(( x )) +#define mutex_lock( x ) SDL_LockMutex(( x )) +#define mutex_unlock( x ) SDL_UnlockMutex(( x )) +#define create_thread( thread, pfn ) (( thread ) = SDL_CreateThread(( pfn ), "DNS resolver thread", NULL )) +#define detach_thread( x ) SDL_DetachThread(( x )) +typedef SDL_mutex *mutex_t; +typedef SDL_Thread *thread_t; +static int NET_ThreadStart( void *ununsed ) +{ + NET_ResolveThread(); + return 0; +} +#elif !XASH_WIN32 #include -#define mutex_lock pthread_mutex_lock -#define mutex_unlock pthread_mutex_unlock -#define exit_thread( x ) pthread_exit(x) -#define create_thread( pfn ) !pthread_create( &nsthread.thread, NULL, (pfn), NULL ) -#define detach_thread( x ) pthread_detach(x) -#define mutex_t pthread_mutex_t -#define thread_t pthread_t -void *NET_ThreadStart( void *unused ) +#define mutex_create( x ) pthread_mutex_init( &( x ), NULL ) +#define mutex_destroy( x ) pthread_mutex_destroy( &( x )) +#define mutex_lock( x ) pthread_mutex_lock( &( x )) +#define mutex_unlock( x ) pthread_mutex_unlock( &( x )) +#define create_thread( thread, pfn ) !pthread_create( &( thread ), NULL, ( pfn ), NULL ) +#define detach_thread( x ) pthread_detach( x ) +typedef pthread_mutex_t mutex_t; +typedef pthread_t thread_t; +static void *NET_ThreadStart( void *unused ) { NET_ResolveThread(); return NULL; } #else // WIN32 -#define mutex_lock EnterCriticalSection -#define mutex_unlock LeaveCriticalSection -#define detach_thread( x ) CloseHandle(x) -#define create_thread( pfn ) nsthread.thread = CreateThread( NULL, 0, pfn, NULL, 0, NULL ) -#define mutex_t CRITICAL_SECTION -#define thread_t HANDLE +#define mutex_create( x ) InitializeCriticalSection( &( x )) +#define mutex_destroy( x ) DeleteCriticalSection( &( x )) +#define mutex_lock( x ) EnterCriticalSection( &( x )) +#define mutex_unlock( x ) LeaveCriticalSection( &( x )) +#define create_thread( thread, pfn ) (( thread ) = CreateThread( NULL, 0, ( pfn ), NULL, 0, NULL )) +#define detach_thread( x ) CloseHandle(( x )) +typedef CRITICAL_SECTION mutex_t; +typedef HANDLE thread_t; DWORD WINAPI NET_ThreadStart( LPVOID unused ) { NET_ResolveThread(); - ExitThread(0); + ExitThread( 0 ); return 0; } #endif // !_WIN32 -#ifdef DEBUG_RESOLVE -#define RESOLVE_DBG(x) Sys_PrintLog(x) -#else -#define RESOLVE_DBG(x) -#endif // DEBUG_RESOLVE +#define RESOLVE_DBG( x ) do { if( net_resolve_debug.value ) Sys_PrintLog(( x )); } while( 0 ) static struct nsthread_s { - mutex_t mutexns; - mutex_t mutexres; + mutex_t mutexns; + mutex_t mutexres; thread_t thread; - int result; - string hostname; + int result; + string hostname; + int family; + struct sockaddr_storage addr; qboolean busy; -} nsthread -#if !XASH_WIN32 -= { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER } -#endif -; +} nsthread; -#if XASH_WIN32 static void NET_InitializeCriticalSections( void ) { net.threads_initialized = true; - pInitializeCriticalSection( &nsthread.mutexns ); - pInitializeCriticalSection( &nsthread.mutexres ); -} -#endif -void NET_ResolveThread( void ) + mutex_create( nsthread.mutexns ); + mutex_create( nsthread.mutexres ); +} + +static void NET_DeleteCriticalSections( void ) { - int sin_addr = 0; + if( net.threads_initialized ) + { + mutex_destroy( nsthread.mutexns ); + mutex_destroy( nsthread.mutexres ); + + net.threads_initialized = false; + } + + memset( &nsthread, 0, sizeof( nsthread )); +} + +static void NET_ResolveThread( void ) +{ + struct sockaddr_storage addr; + qboolean res; RESOLVE_DBG( "[resolve thread] starting resolve for " ); RESOLVE_DBG( nsthread.hostname ); @@ -417,17 +385,16 @@ void NET_ResolveThread( void ) RESOLVE_DBG( " with gethostbyname\n" ); #endif - sin_addr = NET_GetHostByName( nsthread.hostname ); - - if( sin_addr ) + if(( res = NET_GetHostByName( nsthread.hostname, nsthread.family, &addr ))) RESOLVE_DBG( "[resolve thread] success\n" ); else RESOLVE_DBG( "[resolve thread] failed\n" ); - mutex_lock( &nsthread.mutexres ); - nsthread.result = sin_addr; + mutex_lock( nsthread.mutexres ); + nsthread.addr = addr; nsthread.busy = false; + nsthread.result = res ? NET_EAI_OK : NET_EAI_NONAME; RESOLVE_DBG( "[resolve thread] returning result\n" ); - mutex_unlock( &nsthread.mutexres ); + mutex_unlock( nsthread.mutexres ); RESOLVE_DBG( "[resolve thread] exiting thread\n" ); } #endif // CAN_ASYNC_NS_RESOLVE @@ -444,23 +411,33 @@ idnewt:28000 192.246.40.70:28000 ============= */ -static int NET_StringToSockaddr( const char *s, struct sockaddr *sadr, qboolean nonblocking ) +net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family ) { - int ip = 0; + int ret = 0, port; char *colon; char copy[128]; + byte ip6[16]; + struct sockaddr_storage temp; if( !net.initialized ) - return false; + return NET_EAI_NONAME; memset( sadr, 0, sizeof( *sadr )); - ((struct sockaddr_in *)sadr)->sin_family = AF_INET; - ((struct sockaddr_in *)sadr)->sin_port = 0; + // try to parse it as IPv6 first + if(( family == AF_UNSPEC || family == AF_INET6 ) && ParseIPv6Addr( s, ip6, &port, NULL )) + { + ((struct sockaddr_in6 *)sadr)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *)sadr)->sin6_port = htons((short)port); + memcpy(((struct sockaddr_in6 *)sadr)->sin6_addr.s6_addr, ip6, sizeof( struct in6_addr )); + + return NET_EAI_OK; + } Q_strncpy( copy, s, sizeof( copy )); // strip off a trailing :port if present + ((struct sockaddr_in *)sadr)->sin_port = 0; for( colon = copy; *colon; colon++ ) { if( *colon == ':' ) @@ -472,63 +449,190 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr *sadr, qboolean if( copy[0] >= '0' && copy[0] <= '9' ) { - *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr( copy ); + ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + ((struct sockaddr_in *)sadr)->sin_addr.s_addr = inet_addr( copy ); } else { qboolean asyncfailed = true; #ifdef CAN_ASYNC_NS_RESOLVE - if( net.threads_initialized && !nonblocking ) + if( net.threads_initialized && nonblocking ) { - mutex_lock( &nsthread.mutexres ); + mutex_lock( nsthread.mutexres ); if( nsthread.busy ) { - mutex_unlock( &nsthread.mutexres ); - return 2; + mutex_unlock( nsthread.mutexres ); + return NET_EAI_AGAIN; } - if( !Q_strcmp( copy, nsthread.hostname ) ) + if( !Q_strcmp( copy, nsthread.hostname )) { - ip = nsthread.result; - nsthread.hostname[0] = 0; + ret = nsthread.result; + + nsthread.hostname[0] = '\0'; + nsthread.family = AF_UNSPEC; + temp = nsthread.addr; + memset( &nsthread.addr, 0, sizeof( nsthread.addr )); + detach_thread( nsthread.thread ); + asyncfailed = false; } else { - Q_strncpy( nsthread.hostname, copy, MAX_STRING ); + Q_strncpy( nsthread.hostname, copy, sizeof( nsthread.hostname )); + nsthread.family = family; nsthread.busy = true; - mutex_unlock( &nsthread.mutexres ); + mutex_unlock( nsthread.mutexres ); - if( create_thread( NET_ThreadStart ) ) + if( create_thread( nsthread.thread, NET_ThreadStart )) { asyncfailed = false; - return 2; - } - else // failed to create thread - { - Con_Reportf( S_ERROR "NET_StringToSockaddr: failed to create thread!\n"); - nsthread.busy = false; + return NET_EAI_AGAIN; } + + Con_Reportf( S_ERROR "%s: failed to create thread!\n", __func__ ); + nsthread.busy = false; } - mutex_unlock( &nsthread.mutexres ); + mutex_unlock( nsthread.mutexres ); } #endif // CAN_ASYNC_NS_RESOLVE if( asyncfailed ) + ret = NET_GetHostByName( copy, family, &temp ); + + if( !ret ) { - ip = NET_GetHostByName( copy ); + if( family == AF_INET6 ) + sadr->ss_family = AF_INET6; + else sadr->ss_family = AF_INET; + + return NET_EAI_NONAME; } - if( !ip ) - return 0; + sadr->ss_family = temp.ss_family; - *(int *)&((struct sockaddr_in *)sadr)->sin_addr = ip; + if( temp.ss_family == AF_INET ) + ((struct sockaddr_in *)sadr)->sin_addr = ((struct sockaddr_in*)&temp)->sin_addr; + else if( temp.ss_family == AF_INET6 ) + ((struct sockaddr_in6 *)sadr)->sin6_addr = ((struct sockaddr_in6*)&temp)->sin6_addr; } - return 1; + return NET_EAI_OK; +} + +/* +==================== +NET_StringToFilterAdr + +==================== +*/ +qboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen ) +{ + char copy[128], *temp; + qboolean hasCIDR = false; + byte ip6[16]; + uint len; + + if( !COM_CheckStringEmpty( s )) + return false; + + memset( adr, 0, sizeof( *adr )); + + // copy the string and remove CIDR prefix + Q_strncpy( copy, s, sizeof( copy )); + temp = Q_strrchr( copy, '/' ); + + if( temp ) + { + *temp = 0; + if( Q_isdigit( temp + 1 )) + { + len = Q_atoi( temp + 1 ); + hasCIDR = len != 0; + } + } + + // try to parse as IPv6 first + if( ParseIPv6Addr( copy, ip6, NULL, NULL )) + { + NET_NetadrSetType( adr, NA_IP6 ); + NET_IP6BytesToNetadr( adr, ip6 ); + + if( !hasCIDR ) + *prefixlen = 128; + else + *prefixlen = len; + } + else + { + int num = 0; + int octet = 0; + + // parse as ipv4 but we don't need to allow all forms here + for( temp = copy; *temp; temp++ ) + { + char c = *temp; + + if( c >= '0' && c <= '9' ) + { + num *= 10; + num += c - '0'; + } + else if( c == '.' ) + { + if( num > 255 ) + return false; + + adr->ip[octet++] = num; + num = 0; + + if( octet > 3 ) + return false; + } + else + { + return false; + } + } + + if( num > 255 ) + return false; + + adr->ip[octet++] = num; + + if( !hasCIDR ) + { + int i; + + *prefixlen = 32; + + for( i = 3; i >= 0; i-- ) + { + if( !adr->ip[i] ) + *prefixlen -= 8; + else + break; + } + } + else + { + uint32_t mask; + + len = bound( 0, len, 32 ); + *prefixlen = len; + + // drop unneeded bits + mask = htonl( adr->ip4 ) & ( 0xFFFFFFFF << ( 32 - len )); + adr->ip4 = ntohl( mask ); + } + + NET_NetadrSetType( adr, NA_IP ); + } + + return true; } /* @@ -538,9 +642,25 @@ NET_AdrToString */ const char *NET_AdrToString( const netadr_t a ) { - if( a.type == NA_LOOPBACK ) + static char s[64]; + netadrtype_t type = NET_NetadrType( &a ); + + if( type == NA_LOOPBACK ) return "loopback"; - return va( "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port )); + if( type == NA_IP6 || type == NA_MULTICAST_IP6 ) + { + uint8_t ip6[16]; + + NET_NetadrToIP6Bytes( ip6, &a ); + IPv6AddrToString( s, ip6, ntohs( a.port ), 0 ); + + return s; + } + + Q_snprintf( s, sizeof( s ), + "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port )); + + return s; } /* @@ -550,9 +670,25 @@ NET_BaseAdrToString */ const char *NET_BaseAdrToString( const netadr_t a ) { - if( a.type == NA_LOOPBACK ) + static char s[64]; + netadrtype_t type = NET_NetadrType( &a ); + + if( type == NA_LOOPBACK ) return "loopback"; - return va( "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] ); + if( type == NA_IP6 || type == NA_MULTICAST_IP6 ) + { + uint8_t ip6[16]; + + NET_NetadrToIP6Bytes( ip6, &a ); + IPv6IPToString( s, ip6 ); + + return s; + } + + Q_snprintf( s, sizeof( s ), + "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] ); + + return s; } /* @@ -564,16 +700,22 @@ Compares without the port */ qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ) { - if( a.type != b.type ) + netadrtype_t type_a = NET_NetadrType( &a ); + netadrtype_t type_b = NET_NetadrType( &b ); + + if( type_a != type_b ) return false; - if( a.type == NA_LOOPBACK ) + if( type_a == NA_LOOPBACK ) return true; - if( a.type == NA_IP ) + if( type_a == NA_IP ) + return a.ip4 == b.ip4; + + if( type_a == NA_IP6 ) { - if( !memcmp( a.ip, b.ip, 4 )) - return true; + if( !NET_NetadrIP6Compare( &a, &b )) + return true; } return false; @@ -581,24 +723,56 @@ qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ) /* ==================== -NET_CompareClassBAdr +NET_CompareAdrByMask -Compare local masks +Checks if adr is a part of subnet ==================== */ -qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b ) +qboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen ) { - if( a.type != b.type ) + netadrtype_t type_a = NET_NetadrType( &a ); + netadrtype_t type_b = NET_NetadrType( &b ); + + if( type_a != type_b || type_a == NA_LOOPBACK ) return false; - if( a.type == NA_LOOPBACK ) - return true; - - if( a.type == NA_IP ) + if( type_a == NA_IP ) { - if( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] ) + uint32_t ipa = htonl( a.ip4 ); + uint32_t ipb = htonl( b.ip4 ); + + if(( ipa & (( 0xFFFFFFFFU ) << ( 32 - prefixlen ))) == ipb ) return true; } + else if( type_a == NA_IP6 ) + { + uint16_t a_[8], b_[8]; + size_t check = prefixlen / 16; + size_t remaining = prefixlen % 16; + + // convert to 16-bit pieces first + NET_NetadrToIP6Bytes( (uint8_t*)a_, &a ); + NET_NetadrToIP6Bytes( (uint8_t*)b_, &b ); + + // check complete hextets first, if not equal, then it's different subnets + if( check && memcmp( a_, b_, check * sizeof( uint16_t ))) + return false; + + // check by bits now, similar to v4 check but with 16-bit type + if( remaining ) + { + uint16_t hexa, hexb, mask = 0xFFFFU << ( 16 - remaining ); + + hexa = htons( a_[check] ); + hexb = htons( b_[check] ); + + if(( hexa & mask ) == ( hexb & mask )) + return true; + } + else + return true; + } + return false; } @@ -611,23 +785,43 @@ Check for reserved ip's */ qboolean NET_IsReservedAdr( netadr_t a ) { - if( a.type == NA_LOOPBACK ) + netadrtype_t type_a = NET_NetadrType( &a ); + + if( type_a == NA_LOOPBACK ) return true; - if( a.type == NA_IP ) + // Following checks was imported from GameNetworkingSockets library + if( type_a == NA_IP ) { - if( a.ip[0] == 10 || a.ip[0] == 127 ) - return true; - - if( a.ip[0] == 172 && a.ip[1] >= 16 ) + if(( a.ip[0] == 10 ) || // 10.x.x.x is reserved + ( a.ip[0] == 127 ) || // 127.x.x.x + ( a.ip[0] == 169 && a.ip[1] == 254 ) || // 169.254.x.x is link-local ipv4 + ( a.ip[0] == 172 && a.ip[1] >= 16 && a.ip[1] <= 31 ) || // 172.16.x.x - 172.31.x.x + ( a.ip[0] == 192 && a.ip[1] >= 168 )) // 192.168.x.x + { + return true; + } + } + + if( type_a == NA_IP6 ) + { + uint8_t ip6[16]; + + NET_NetadrToIP6Bytes( ip6, &a ); + + // Private addresses, fc00::/7 + // Range is fc00:: to fdff:ffff:etc + if( ip6[0] >= 0xFC && ip6[1] <= 0xFD ) { - if( a.ip[1] >= 32 ) - return false; return true; } - if( a.ip[0] == 192 && a.ip[1] == 168 ) + // Link-local fe80::/10 + // Range is fe80:: to febf:: + if( ip6[0] == 0xFE && ( ip6[1] >= 0x80 && ip6[1] <= 0xBF )) + { return true; + } } return false; @@ -642,31 +836,87 @@ Compare full address */ qboolean NET_CompareAdr( const netadr_t a, const netadr_t b ) { - if( a.type != b.type ) + netadrtype_t type_a = NET_NetadrType( &a ); + netadrtype_t type_b = NET_NetadrType( &b ); + + if( type_a != type_b ) return false; - if( a.type == NA_LOOPBACK ) + if( type_a == NA_LOOPBACK ) return true; - if( a.type == NA_IP ) + if( type_a == NA_IP ) { - if(!memcmp( a.ip, b.ip, 4 ) && a.port == b.port ) + if( a.ip4 == b.ip4 && a.port == b.port ) return true; return false; } - Con_DPrintf( S_ERROR "NET_CompareAdr: bad address type\n" ); + if( type_a == NA_IP6 ) + { + if( a.port == b.port && !NET_NetadrIP6Compare( &a, &b )) + return true; + return false; + } + + Con_DPrintf( S_ERROR "%s: bad address type\n", __func__ ); return false; } /* ==================== -NET_IsLocalAddress +NET_CompareAdrSort + +Network address sorting comparator +guaranteed to return -1, 0 or 1 ==================== */ -qboolean NET_IsLocalAddress( netadr_t adr ) +int NET_CompareAdrSort( const void *_a, const void *_b ) { - return (adr.type == NA_LOOPBACK) ? true : false; + const netadr_t *a = _a, *b = _b; + int porta, portb, portdiff, addrdiff; + netadrtype_t type_a, type_b; + + type_a = NET_NetadrType( a ); + type_b = NET_NetadrType( b ); + + if( type_a != type_b ) + return bound( -1, (int)type_a - (int)type_b, 1 ); + + porta = ntohs( a->port ); + portb = ntohs( b->port ); + if( porta < portb ) + portdiff = -1; + else if( porta > portb ) + portdiff = 1; + else + portdiff = 0; + + switch( type_a ) + { + case NA_IP6: + if(( addrdiff = NET_NetadrIP6Compare( a, b ))) + return addrdiff; + // fallthrough + case NA_MULTICAST_IP6: + return portdiff; + + case NA_IP: + if(( addrdiff = memcmp( a->ip, b->ip, sizeof( a->ipx )))) + return addrdiff; + // fallthrough + case NA_BROADCAST: + return portdiff; + + case NA_IPX: + if(( addrdiff = memcmp( a->ipx, b->ipx, sizeof( a->ipx )))) + return addrdiff; + // fallthrough + case NA_BROADCAST_IPX: + return portdiff; + } + + return 0; } /* @@ -677,45 +927,49 @@ idnewt 192.246.40.70 ============= */ -qboolean NET_StringToAdr( const char *string, netadr_t *adr ) +static qboolean NET_StringToAdrEx( const char *string, netadr_t *adr, int family ) { - struct sockaddr s; + struct sockaddr_storage s; memset( adr, 0, sizeof( netadr_t )); - if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) ) + if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) { - adr->type = NA_LOOPBACK; + NET_NetadrSetType( adr, NA_LOOPBACK ); return true; } - if( !NET_StringToSockaddr( string, &s, false )) + if( NET_StringToSockaddr( string, &s, false, family ) != NET_EAI_OK ) return false; NET_SockadrToNetadr( &s, adr ); - return true; } -int NET_StringToAdrNB( const char *string, netadr_t *adr ) + +qboolean NET_StringToAdr( const char *string, netadr_t *adr ) { - struct sockaddr s; - int res; + return NET_StringToAdrEx( string, adr, AF_UNSPEC ); +} + +net_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr, qboolean v6only ) +{ + struct sockaddr_storage s; + net_gai_state_t res; memset( adr, 0, sizeof( netadr_t )); - if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" ) ) + + if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) { - adr->type = NA_LOOPBACK; - return true; + NET_NetadrSetType( adr, NA_LOOPBACK ); + return NET_EAI_OK; } - res = NET_StringToSockaddr( string, &s, true ); + res = NET_StringToSockaddr( string, &s, true, v6only ? AF_INET6 : AF_UNSPEC ); - if( res == 0 || res == 2 ) - return res; + if( res == NET_EAI_OK ) + NET_SockadrToNetadr( &s, adr ); - NET_SockadrToNetadr( &s, adr ); - - return true; + return res; } /* @@ -752,7 +1006,7 @@ static qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *from, byte *data, si *length = loop->msgs[i].datalen; memset( from, 0, sizeof( *from )); - from->type = NA_LOOPBACK; + NET_NetadrSetType( from, NA_LOOPBACK ); return true; } @@ -865,7 +1119,7 @@ static void NET_AddToLagged( netsrc_t sock, packetlag_t *list, packetlag_t *pack packet->data = pStart; packet->size = length; packet->receivedtime = timestamp; - memcpy( &packet->from, from, sizeof( netadr_t )); + packet->from = *from; } /* @@ -885,11 +1139,11 @@ static void NET_AdjustLag( void ) dt = bound( 0.0, dt, 0.1 ); lasttime = host.realtime; - if( host_developer.value || !net_fakelag->value ) + if( host_developer.value || !net_fakelag.value ) { - if( net_fakelag->value != net.fakelag ) + if( net_fakelag.value != net.fakelag ) { - diff = net_fakelag->value - net.fakelag; + diff = net_fakelag.value - net.fakelag; converge = dt * 200.0f; if( fabs( diff ) < converge ) converge = fabs( diff ); @@ -930,14 +1184,14 @@ static qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, if( newdata ) { - if( net_fakeloss->value != 0.0f ) + if( net_fakeloss.value != 0.0f ) { if( host_developer.value ) { net.losscount[sock]++; - if( net_fakeloss->value <= 0.0f ) + if( net_fakeloss.value <= 0.0f ) { - ninterval = fabs( net_fakeloss->value ); + ninterval = fabs( net_fakeloss.value ); if( ninterval < 2 ) ninterval = 2; if(( net.losscount[sock] % ninterval ) == 0 ) @@ -945,7 +1199,7 @@ static qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, } else { - if( COM_RandomLong( 0, 100 ) <= net_fakeloss->value ) + if( COM_RandomLong( 0, 100 ) <= net_fakeloss.value ) return false; } } @@ -977,7 +1231,7 @@ static qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, // delivery packet from fake lag queue memcpy( data, pPacket->data, pPacket->size ); - memcpy( &net_from, &pPacket->from, sizeof( netadr_t )); + net_from = pPacket->from; *length = pPacket->size; if( pPacket->data ) @@ -995,30 +1249,49 @@ NET_GetLong receive long packet from network ================== */ -qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize ) +static qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize, connprotocol_t proto ) { int i, sequence_number, offset; - SPLITPACKET *pHeader = (SPLITPACKET *)pData; int packet_number; int packet_count; short packet_id; - int body_size = splitsize - sizeof( SPLITPACKET ); + size_t header_size = proto == PROTO_GOLDSRC ? sizeof( SPLITPACKETGS ) : sizeof( SPLITPACKET ); + int body_size = splitsize - header_size; + int max_splits; if( body_size < 0 ) return false; - if( size < sizeof( SPLITPACKET )) + if( size < header_size ) { Con_Printf( S_ERROR "invalid split packet length %i\n", size ); return false; } - sequence_number = pHeader->sequence_number; - packet_id = pHeader->packet_id; - packet_count = ( packet_id & 0xFF ); - packet_number = ( packet_id >> 8 ); + if( proto == PROTO_GOLDSRC ) + { + SPLITPACKETGS *pHeader = (SPLITPACKETGS *)pData; - if( packet_number >= NET_MAX_FRAGMENTS || packet_count > NET_MAX_FRAGMENTS ) + sequence_number = pHeader->sequence_number; + packet_id = pHeader->packet_id; + packet_count = ( packet_id & 0xF ); + packet_number = ( packet_id >> 4 ); + + max_splits = NET_MAX_GOLDSRC_FRAGMENTS; + } + else + { + SPLITPACKET *pHeader = (SPLITPACKET *)pData; + + sequence_number = pHeader->sequence_number; + packet_id = pHeader->packet_id; + packet_count = ( packet_id & 0xFF ); + packet_number = ( packet_id >> 8 ); + + max_splits = ARRAYSIZE( net.split_flags ); + } + + if( packet_number >= max_splits || packet_count > max_splits ) { Con_Printf( S_ERROR "malformed packet number (%i/%i)\n", packet_number + 1, packet_count ); return false; @@ -1031,14 +1304,14 @@ qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize ) net.split.total_size = 0; // clear part's sequence - for( i = 0; i < NET_MAX_FRAGMENTS; i++ ) + for( i = 0; i < ARRAYSIZE( net.split_flags ); i++ ) net.split_flags[i] = -1; - if( net_showpackets && net_showpackets->value == 4.0f ) + if( net_showpackets.value == 4.0f ) Con_Printf( "<-- Split packet restart %i count %i seq\n", net.split.split_count, sequence_number ); } - size -= sizeof( SPLITPACKET ); + size -= header_size; if( net.split_flags[packet_number] != sequence_number ) { @@ -1048,16 +1321,16 @@ qboolean NET_GetLong( byte *pData, int size, size_t *outSize, int splitsize ) net.split.split_count--; net.split_flags[packet_number] = sequence_number; - if( net_showpackets && net_showpackets->value == 4.0f ) + if( net_showpackets.value == 4.0f ) Con_Printf( "<-- Split packet %i of %i, %i bytes %i seq\n", packet_number + 1, packet_count, size, sequence_number ); } else { - Con_DPrintf( "NET_GetLong: Ignoring duplicated split packet %i of %i ( %i bytes )\n", packet_number + 1, packet_count, size ); + Con_DPrintf( "%s: Ignoring duplicated split packet %i of %i ( %i bytes )\n", __func__, packet_number + 1, packet_count, size ); } offset = (packet_number * body_size); - memcpy( net.split.buffer + offset, pData + sizeof( SPLITPACKET ), size ); + memcpy( net.split.buffer + offset, pData + header_size, size ); // have we received all of the pieces to the packet? if( net.split.split_count <= 0 ) @@ -1086,40 +1359,49 @@ NET_QueuePacket queue normal and lagged packets ================== */ -qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ) +static qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ) { byte buf[NET_MAX_FRAGMENT]; - int ret; + int ret, protocol; int net_socket; WSAsize_t addr_len; - struct sockaddr addr; + struct sockaddr_storage addr = { 0 }; *length = 0; - net_socket = net.ip_sockets[sock]; - - if( NET_IsSocketValid( net_socket ) ) + for( protocol = 0; protocol < 2; protocol++ ) { + switch( protocol ) + { + case 0: net_socket = net.ip_sockets[sock]; break; + case 1: net_socket = net.ip6_sockets[sock]; break; + } + + if( !NET_IsSocketValid( net_socket )) + continue; + addr_len = sizeof( addr ); ret = recvfrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len ); - if( !NET_IsSocketError( ret ) ) - { - NET_SockadrToNetadr( &addr, from ); + NET_SockadrToNetadr( &addr, from ); + if( !NET_IsSocketError( ret )) + { if( ret < NET_MAX_FRAGMENT ) { // Transfer data memcpy( data, buf, ret ); *length = ret; #if !XASH_DEDICATED - if( CL_LegacyMode() ) - return NET_LagPacket( true, sock, from, length, data ); - - // check for split message - if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET ) { - return NET_GetLong( data, ret, length, CL_GetSplitSize() ); + connprotocol_t proto = CL_Protocol(); + + if( proto == PROTO_LEGACY ) + return NET_LagPacket( true, sock, from, length, data ); + + // check for split message + if( sock == NS_CLIENT && *(int *)data == NET_HEADER_SPLITPACKET ) + return NET_GetLong( data, ret, length, CL_GetSplitSize( ), proto ); } #endif // lag the packet, if needed @@ -1127,7 +1409,7 @@ qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *len } else { - Con_Reportf( "NET_QueuePacket: oversize packet from %s\n", NET_AdrToString( *from )); + Con_Reportf( "%s: oversize packet from %s\n", __func__, NET_AdrToString( *from )); } } else @@ -1143,7 +1425,7 @@ qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *len case WSAETIMEDOUT: break; default: // let's continue even after errors - Con_DPrintf( S_ERROR "NET_QueuePacket: %s from %s\n", NET_ErrorString(), NET_AdrToString( *from )); + Con_DPrintf( S_ERROR "%s: %s from %s\n", __func__, NET_ErrorString(), NET_AdrToString( *from )); break; } } @@ -1183,7 +1465,7 @@ NET_SendLong Fragment long packets, send short directly ================== */ -int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, size_t len, int flags, const struct sockaddr *to, size_t tolen, size_t splitsize ) +static 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 ) { #ifdef NET_USE_FRAGMENTS // do we need to break this packet up? @@ -1212,25 +1494,25 @@ int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, size_t len, in pPacket->packet_id = (packet_number << 8) + packet_count; memcpy( packet + sizeof( SPLITPACKET ), buf + ( packet_number * body_size ), size ); - if( net_showpackets && net_showpackets->value == 3.0f ) + if( net_showpackets.value == 3.0f ) { netadr_t adr; memset( &adr, 0, sizeof( adr )); - NET_SockadrToNetadr((struct sockaddr *)to, &adr ); + NET_SockadrToNetadr( to, &adr ); Con_Printf( "Sending split %i of %i with %i bytes and seq %i to %s\n", packet_number + 1, packet_count, size, net.sequence_number, NET_AdrToString( adr )); } - ret = sendto( net_socket, packet, size + sizeof( SPLITPACKET ), flags, to, tolen ); + ret = sendto( net_socket, packet, size + sizeof( SPLITPACKET ), flags, (const struct sockaddr *)to, tolen ); if( ret < 0 ) return ret; // error if( ret >= size ) total_sent += size; len -= size; packet_number++; - Sys_Sleep( 1 ); + Platform_NanoSleep( 100 * 1000 ); } return total_sent; @@ -1239,7 +1521,7 @@ int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, size_t len, in #endif { // no fragmenantion for client connection - return sendto( net_socket, buf, len, flags, to, tolen ); + return sendto( net_socket, buf, len, flags, (const struct sockaddr *)to, tolen ); } } @@ -1251,34 +1533,35 @@ NET_SendPacketEx void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize ) { int ret; - struct sockaddr addr; + struct sockaddr_storage addr = { 0 }; SOCKET net_socket = 0; + netadrtype_t type = NET_NetadrType( &to ); - if( !net.initialized || to.type == NA_LOOPBACK ) + if( !net.initialized || type == NA_LOOPBACK ) { NET_SendLoopPacket( sock, length, data, to ); return; } - else if( to.type == NA_BROADCAST ) + else if( type == NA_BROADCAST || type == NA_IP ) { net_socket = net.ip_sockets[sock]; - if( !NET_IsSocketValid( net_socket ) ) + if( !NET_IsSocketValid( net_socket )) return; } - else if( to.type == NA_IP ) + else if( type == NA_MULTICAST_IP6 || type == NA_IP6 ) { - net_socket = net.ip_sockets[sock]; - if( !NET_IsSocketValid( net_socket ) ) + net_socket = net.ip6_sockets[sock]; + if( !NET_IsSocketValid( net_socket )) return; } else { - Host_Error( "NET_SendPacket: bad address type %i\n", to.type ); + Host_Error( "%s: bad address type %i (%i, %i)\n", __func__, to.type, to.ip6_0[0], to.ip6_0[1] ); } NET_NetadrToSockadr( &to, &addr ); - ret = NET_SendLong( sock, net_socket, data, length, 0, &addr, sizeof( addr ), splitsize ); + ret = NET_SendLong( sock, net_socket, data, length, 0, &addr, NET_SockAddrLen( &addr ), splitsize ); if( NET_IsSocketError( ret )) { @@ -1289,20 +1572,20 @@ void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t return; // some PPP links don't allow broadcasts - if( err == WSAEADDRNOTAVAIL && to.type == NA_BROADCAST ) + if( err == WSAEADDRNOTAVAIL && ( type == NA_BROADCAST || type == NA_MULTICAST_IP6 )) return; - if( Host_IsDedicated() ) + if( Host_IsDedicated( )) { - Con_DPrintf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + Con_DPrintf( S_ERROR "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to )); } else if( err == WSAEADDRNOTAVAIL || err == WSAENOBUFS ) { - Con_DPrintf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + Con_DPrintf( S_ERROR "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to )); } else { - Con_Printf( S_ERROR "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + Con_Printf( S_ERROR "%s: %s to %s\n", __func__, NET_ErrorString(), NET_AdrToString( to )); } } @@ -1318,150 +1601,120 @@ void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to NET_SendPacketEx( sock, length, data, to, 0 ); } -/* -==================== -NET_BufferToBufferCompress - -generic fast compression -==================== -*/ -qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen ) -{ - uint uCompressedLen = 0; - byte *pbOut = NULL; - - memcpy( dest, source, sourceLen ); - pbOut = LZSS_Compress( source, sourceLen, &uCompressedLen ); - - if( pbOut && uCompressedLen > 0 && uCompressedLen <= *destLen ) - { - memcpy( dest, pbOut, uCompressedLen ); - *destLen = uCompressedLen; - free( pbOut ); - return true; - } - else - { - if( pbOut ) free( pbOut ); - memcpy( dest, source, sourceLen ); - *destLen = sourceLen; - return false; - } -} - -/* -==================== -NET_BufferToBufferDecompress - -generic fast decompression -==================== -*/ -qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen ) -{ - if( LZSS_IsCompressed( source )) - { - uint uDecompressedLen = LZSS_GetActualSize( source ); - - if( uDecompressedLen <= *destLen ) - { - *destLen = LZSS_Decompress( source, dest ); - } - else - { - return false; - } - } - else - { - memcpy( dest, source, sourceLen ); - *destLen = sourceLen; - } - - return true; -} - /* ==================== NET_IPSocket ==================== */ -static int NET_IPSocket( const char *net_interface, int port, qboolean multicast ) +static int NET_IPSocket( const char *net_iface, int port, int family ) { - struct sockaddr_in addr; + struct sockaddr_storage addr = { 0 }; int err, net_socket; uint optval = 1; dword _true = 1; + int pfamily = PF_INET; - if( NET_IsSocketError(( net_socket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP )) ) ) + if( family == AF_INET6 ) + pfamily = PF_INET6; + + if( NET_IsSocketError(( net_socket = socket( pfamily, SOCK_DGRAM, IPPROTO_UDP )))) { err = WSAGetLastError(); if( err != WSAEAFNOSUPPORT ) - Con_DPrintf( S_WARN "NET_UDPSocket: port: %d socket: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "%s: port: %d socket: %s\n", __func__, port, NET_ErrorString( )); return INVALID_SOCKET; } - if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true ) ) ) + if( NET_IsSocketError( ioctlsocket( net_socket, FIONBIO, (void*)&_true ))) { struct timeval timeout; - Con_DPrintf( S_WARN "NET_UDPSocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "%s: port: %d ioctl FIONBIO: %s\n", __func__, port, NET_ErrorString( )); // try timeout instead of NBIO timeout.tv_sec = timeout.tv_usec = 0; setsockopt( net_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); } // make it broadcast capable - if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) ) ) + if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true )))) { - Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "%s: port: %d setsockopt SO_BROADCAST: %s\n", __func__, port, NET_ErrorString( )); } - if( Sys_CheckParm( "-reuse" ) || multicast ) + if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )))) { - if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) ) ) - { - Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); - closesocket( net_socket ); - return INVALID_SOCKET; - } - } - - if( Sys_CheckParm( "-tos" )) - { - optval = 16; - Con_Printf( "Enabling LOWDELAY TOS option\n" ); - - if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_TOS, (const char *)&optval, sizeof( optval )) ) ) - { - err = WSAGetLastError(); - if( err != WSAENOPROTOOPT ) - Con_Printf( S_WARN "NET_UDPSocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); - closesocket( net_socket ); - return INVALID_SOCKET; - } - } - - if( !COM_CheckStringEmpty( net_interface ) || !Q_stricmp( net_interface, "localhost" )) - addr.sin_addr.s_addr = INADDR_ANY; - else NET_StringToSockaddr( net_interface, (struct sockaddr *)&addr, false ); - - if( port == PORT_ANY ) addr.sin_port = 0; - else addr.sin_port = htons((short)port); - - addr.sin_family = AF_INET; - - if( NET_IsSocketError( bind( net_socket, (void *)&addr, sizeof( addr )) ) ) - { - Con_DPrintf( S_WARN "NET_UDPSocket: port: %d bind: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "%s: port: %d setsockopt SO_REUSEADDR: %s\n", __func__, port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } - if( Sys_CheckParm( "-loopback" )) + addr.ss_family = family; + + if( family == AF_INET6 ) { - optval = 1; - if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&optval, sizeof( optval )) ) ) - Con_DPrintf( S_WARN "NET_UDPSocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); + if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&_true, sizeof( _true )))) + { + Con_DPrintf( S_WARN "%s: port: %d setsockopt IPV6_V6ONLY: %s\n", __func__, port, NET_ErrorString( )); + closesocket( net_socket ); + return INVALID_SOCKET; + } + + if( Sys_CheckParm( "-loopback" )) + { + if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&_true, sizeof( _true )))) + Con_DPrintf( S_WARN "%s: port %d setsockopt IPV6_MULTICAST_LOOP: %s\n", __func__, port, NET_ErrorString( )); + } + + if( COM_CheckStringEmpty( net_iface ) && Q_stricmp( net_iface, "localhost" )) + NET_StringToSockaddr( net_iface, &addr, false, AF_INET6 ); + else ((struct sockaddr_in6 *)&addr)->sin6_addr = in6addr_any; + + if( port == PORT_ANY ) ((struct sockaddr_in6 *)&addr)->sin6_port = 0; + else ((struct sockaddr_in6 *)&addr)->sin6_port = htons((short)port); + + if( NET_IsSocketError( bind( net_socket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in6 )))) + { + Con_DPrintf( S_WARN "%s: port: %d bind6: %s\n", __func__, port, NET_ErrorString( )); + closesocket( net_socket ); + return INVALID_SOCKET; + } + } + else if( family == AF_INET ) + { + if( Sys_CheckParm( "-tos" )) + { + optval = 0x10; // IPTOS_LOWDELAY + Con_Printf( "Enabling LOWDELAY TOS option\n" ); + + if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_TOS, (const char *)&optval, sizeof( optval )))) + { + err = WSAGetLastError(); + if( err != WSAENOPROTOOPT ) + Con_Printf( S_WARN "%s: port: %d setsockopt IP_TOS: %s\n", __func__, port, NET_ErrorString( )); + closesocket( net_socket ); + return INVALID_SOCKET; + } + } + + if( Sys_CheckParm( "-loopback" )) + { + if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&_true, sizeof( _true )))) + Con_DPrintf( S_WARN "%s: port %d setsockopt IP_MULTICAST_LOOP: %s\n", __func__, port, NET_ErrorString( )); + } + + if( COM_CheckStringEmpty( net_iface ) && Q_stricmp( net_iface, "localhost" )) + NET_StringToSockaddr( net_iface, &addr, false, AF_INET ); + else ((struct sockaddr_in *)&addr)->sin_addr.s_addr = INADDR_ANY; + + if( port == PORT_ANY ) ((struct sockaddr_in *)&addr)->sin_port = 0; + else ((struct sockaddr_in *)&addr)->sin_port = htons((short)port); + + if( NET_IsSocketError( bind( net_socket, (struct sockaddr *)&addr, sizeof( struct sockaddr_in )))) + { + Con_DPrintf( S_WARN "%s: port: %d bind: %s\n", __func__, port, NET_ErrorString( )); + closesocket( net_socket ); + return INVALID_SOCKET; + } } return net_socket; @@ -1472,130 +1725,141 @@ static int NET_IPSocket( const char *net_interface, int port, qboolean multicast NET_OpenIP ==================== */ -static void NET_OpenIP( qboolean change_port ) +static void NET_OpenIP( qboolean change_port, int *sockets, const char *net_iface, int hostport, int clientport, int family ) { - int port; - qboolean sv_nat = Cvar_VariableInteger("sv_nat"); - qboolean cl_nat = Cvar_VariableInteger("cl_nat"); + int port; + qboolean sv_nat = Cvar_VariableInteger( "sv_nat" ); + qboolean cl_nat = Cvar_VariableInteger( "cl_nat" ); - if( change_port && ( FBitSet( net_hostport->flags, FCVAR_CHANGED ) || sv_nat )) + if( change_port && ( FBitSet( net_hostport.flags, FCVAR_CHANGED ) || sv_nat )) { // reopen socket to set random port - if( NET_IsSocketValid( net.ip_sockets[NS_SERVER] )) - closesocket( net.ip_sockets[NS_SERVER] ); - - net.ip_sockets[NS_SERVER] = INVALID_SOCKET; - ClearBits( net_hostport->flags, FCVAR_CHANGED ); + if( NET_IsSocketValid( sockets[NS_SERVER] )) + closesocket( sockets[NS_SERVER] ); + + sockets[NS_SERVER] = INVALID_SOCKET; + ClearBits( net_hostport.flags, FCVAR_CHANGED ); } - if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) ) + if( !NET_IsSocketValid( sockets[NS_SERVER] )) { - port = net_iphostport->value; + port = hostport; if( !port ) { - if( sv_nat ) - port = PORT_ANY; - else - port = net_hostport->value; + port = sv_nat ? PORT_ANY : net_hostport.value; if( !port ) port = PORT_SERVER; // forcing to default } - net.ip_sockets[NS_SERVER] = NET_IPSocket( net_ipname->string, port, false ); + sockets[NS_SERVER] = NET_IPSocket( net_iface, port, family ); - if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) && Host_IsDedicated() ) - Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); + if( !NET_IsSocketValid( sockets[NS_SERVER] ) && Host_IsDedicated( )) + return; } // dedicated servers don't need client ports - if( Host_IsDedicated() ) return; + if( Host_IsDedicated( )) return; - if (change_port && ( FBitSet( net_clientport->flags, FCVAR_CHANGED ) || cl_nat )) + if( change_port && ( FBitSet( net_clientport.flags, FCVAR_CHANGED ) || cl_nat )) { // reopen socket to set random port - if( NET_IsSocketValid(net.ip_sockets[NS_CLIENT] )) - closesocket( net.ip_sockets[NS_CLIENT] ); + if( NET_IsSocketValid( sockets[NS_CLIENT] )) + closesocket( sockets[NS_CLIENT] ); - net.ip_sockets[NS_CLIENT] = INVALID_SOCKET; - ClearBits( net_clientport->flags, FCVAR_CHANGED ); + sockets[NS_CLIENT] = INVALID_SOCKET; + ClearBits( net_clientport.flags, FCVAR_CHANGED ); } - if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) + if( !NET_IsSocketValid( sockets[NS_CLIENT] )) { - port = net_ipclientport->value; + port = clientport; if( !port ) { - if( cl_nat ) - port = PORT_ANY; - else - port = net_clientport->value; + port = cl_nat ? PORT_ANY : net_clientport.value; if( !port ) port = PORT_ANY; // forcing to default } - net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, port, false ); + sockets[NS_CLIENT] = NET_IPSocket( net_iface, port, family ); - if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) - net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, PORT_ANY, false ); + if( !NET_IsSocketValid( sockets[NS_CLIENT] )) + sockets[NS_CLIENT] = NET_IPSocket( net_ipname.string, PORT_ANY, family ); } + + return; } /* ================ -NET_GetLocalAddress +NET_DetermineLocalAddress Returns the servers' ip address as a string. ================ */ -void NET_GetLocalAddress( void ) +static void NET_DetermineLocalAddress( void ) { + char hostname[512]; char buff[512]; - struct sockaddr_in address; + struct sockaddr_storage address; WSAsize_t namelen; + const char *net_addr_string; memset( &net_local, 0, sizeof( netadr_t )); - buff[0] = '\0'; + memset( &net6_local, 0, sizeof( netadr_t )); + + if( !net.allow_ip && !net.allow_ip6 ) + { + Con_Printf( "TCP/IP Disabled.\n" ); + return; + } + + gethostname( hostname, sizeof( hostname )); + hostname[sizeof(hostname) - 1] = 0; if( net.allow_ip ) { // If we have changed the ip var from the command line, use that instead. - if( Q_strcmp( net_ipname->string, "localhost" )) - { - Q_strncpy( buff, net_ipname->string, sizeof( buff ) ); - } - else - { - gethostname( buff, 512 ); + if( Q_stricmp( net_ipname.string, "localhost" )) + Q_strncpy( buff, net_ipname.string, sizeof( buff )); + else Q_strncpy( buff, hostname, sizeof( buff )); - // ensure that it doesn't overrun the buffer - buff[511] = 0; - } - - if( NET_StringToAdr( buff, &net_local )) + if( NET_StringToAdrEx( buff, &net_local, AF_INET )) { - namelen = sizeof( address ); + namelen = sizeof( struct sockaddr_in ); - if( NET_IsSocketError( getsockname( net.ip_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen ) ) ) + if( !NET_IsSocketError( getsockname( net.ip_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen ))) { - // this may happens if multiple clients running on single machine - Con_DPrintf( S_ERROR "Could not get TCP/IP address. Reason: %s\n", NET_ErrorString( )); -// net.allow_ip = false; - } - else - { - net_local.port = address.sin_port; - Con_Printf( "Server IP address %s\n", NET_AdrToString( net_local )); - Cvar_FullSet( "net_address", va( "%s", NET_AdrToString( net_local )), FCVAR_READ_ONLY ); + net_local.port = ((struct sockaddr_in *)&address)->sin_port; + net_addr_string = NET_AdrToString( net_local ); + Con_Printf( "Server IPv4 address %s\n", net_addr_string ); + Cvar_FullSet( "net_address", net_addr_string, net_address.flags ); } + else Con_DPrintf( S_ERROR "Could not get TCP/IPv4 address. Reason: %s\n", NET_ErrorString( )); } - else - { - Con_DPrintf( S_ERROR "Could not get TCP/IP address, Invalid hostname: '%s'\n", buff ); - } + else Con_DPrintf( S_ERROR "Could not get TCP/IPv4 address, Invalid hostname: '%s'\n", buff ); } - else + + if( net.allow_ip6 ) { - Con_Printf( "TCP/IP Disabled.\n" ); + // If we have changed the ip var from the command line, use that instead. + if( Q_stricmp( net_ip6name.string, "localhost" )) + Q_strncpy( buff, net_ip6name.string, sizeof( buff )); + else Q_strncpy( buff, hostname, sizeof( buff )); + + if( NET_StringToAdrEx( buff, &net6_local, AF_INET6 )) + { + namelen = sizeof( struct sockaddr_in6 ); + + if( !NET_IsSocketError( getsockname( net.ip6_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen ))) + { + net6_local.port = ((struct sockaddr_in6 *)&address)->sin6_port; + net_addr_string = NET_AdrToString( net6_local ); + Con_Printf( "Server IPv6 address %s\n", net_addr_string ); + Cvar_FullSet( "net6_address", net_addr_string, net6_address.flags ); + } + else Con_DPrintf( S_ERROR "Could not get TCP/IPv6 address. Reason: %s\n", NET_ErrorString( )); + } + else Con_DPrintf( S_ERROR "Could not get TCP/IPv6 address, Invalid hostname: '%s'\n", buff ); } } @@ -1622,12 +1886,31 @@ void NET_Config( qboolean multiplayer, qboolean changeport ) if( multiplayer ) { // open sockets - if( net.allow_ip ) NET_OpenIP( changeport ); + if( net.allow_ip ) + NET_OpenIP( changeport, net.ip_sockets, net_ipname.string, net_iphostport.value, net_ipclientport.value, AF_INET ); + + if( net.allow_ip6 ) + NET_OpenIP( changeport, net.ip6_sockets, net_ip6name.string, net_ip6hostport.value, net_ip6clientport.value, AF_INET6 ); + + // validate sockets for dedicated + if( Host_IsDedicated( )) + { + qboolean nov4, nov6; + nov4 = net.allow_ip && NET_IsSocketError( net.ip_sockets[NS_SERVER] ); + nov6 = net.allow_ip6 && NET_IsSocketError( net.ip6_sockets[NS_SERVER] ); + + if( nov4 && nov6 ) + Host_Error( "Couldn't allocate IPv4 and IPv6 server ports.\n" ); + else if( nov4 && !nov6 ) + Con_Printf( S_ERROR "Couldn't allocate IPv4 server port\n" ); + else if( !nov4 && nov6 ) + Con_Printf( S_ERROR "Couldn't allocate IPv6 server_port\n" ); + } // get our local address, if possible if( bFirst ) { - NET_GetLocalAddress(); + NET_DetermineLocalAddress(); bFirst = false; } } @@ -1638,11 +1921,17 @@ void NET_Config( qboolean multiplayer, qboolean changeport ) // shut down any existing sockets for( i = 0; i < NS_COUNT; i++ ) { - if( net.ip_sockets[i] != INVALID_SOCKET ) + if( NET_IsSocketValid( net.ip_sockets[i] )) { closesocket( net.ip_sockets[i] ); net.ip_sockets[i] = INVALID_SOCKET; } + + if( NET_IsSocketValid( net.ip6_sockets[i] )) + { + closesocket( net.ip6_sockets[i] ); + net.ip6_sockets[i] = INVALID_SOCKET; + } } } @@ -1711,12 +2000,38 @@ NET_ClearLagData clear fakelag list ==================== */ -void NET_ClearLagData( qboolean bClient, qboolean bServer ) +static void NET_ClearLagData( qboolean bClient, qboolean bServer ) { if( bClient ) NET_ClearLaggedList( &net.lagdata[NS_CLIENT] ); if( bServer ) NET_ClearLaggedList( &net.lagdata[NS_SERVER] ); } +/* +==================== +NET_GetLocalAddress + +get local server addresses +==================== +*/ +void NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 ) +{ + if( ip4 ) + { + if( net.allow_ip ) + *ip4 = net_local; + else + memset( ip4, 0, sizeof( *ip4 )); + } + + if( ip6 ) + { + if( net.allow_ip6 ) + *ip6 = net6_local; + else + memset( ip6, 0, sizeof( *ip6 )); + } +} + /* ==================== NET_Init @@ -1729,50 +2044,77 @@ void NET_Init( void ) if( net.initialized ) return; - net_clockwindow = Cvar_Get( "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" ); - net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" ); - net_ipname = Cvar_Get( "ip", "localhost", 0, "network ip address" ); - net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); - net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" ); - net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); - net_clientport = Cvar_Get( "clientport", va( "%i", PORT_CLIENT ), FCVAR_READ_ONLY, "network default client port" ); - net_fakelag = Cvar_Get( "fakelag", "0", FCVAR_PRIVILEGED, "lag all incoming network data (including loopback) by xxx ms." ); - net_fakeloss = Cvar_Get( "fakeloss", "0", FCVAR_PRIVILEGED, "act like we dropped the packet this % of the time." ); + Cvar_RegisterVariable( &net_address ); + Cvar_RegisterVariable( &net_ipname ); + Cvar_RegisterVariable( &net_iphostport ); + Cvar_RegisterVariable( &net_hostport ); + Cvar_RegisterVariable( &net_ipclientport ); + Cvar_RegisterVariable( &net_clientport ); + Cvar_RegisterVariable( &net_fakelag ); + Cvar_RegisterVariable( &net_fakeloss ); + Cvar_RegisterVariable( &net_resolve_debug ); + Cvar_RegisterVariable( &net_clockwindow ); + + Q_snprintf( cmd, sizeof( cmd ), "%i", PORT_SERVER ); + Cvar_FullSet( "hostport", cmd, FCVAR_READ_ONLY ); + + // cvar equivalents for IPv6 + Cvar_RegisterVariable( &net_ip6name ); + Cvar_RegisterVariable( &net_ip6hostport ); + Cvar_RegisterVariable( &net_ip6clientport ); + Cvar_RegisterVariable( &net6_address ); // prepare some network data for( i = 0; i < NS_COUNT; i++ ) { net.lagdata[i].prev = &net.lagdata[i]; net.lagdata[i].next = &net.lagdata[i]; - net.ip_sockets[i] = INVALID_SOCKET; + net.ip_sockets[i] = INVALID_SOCKET; + net.ip6_sockets[i] = INVALID_SOCKET; } #if XASH_WIN32 - if( WSAStartup( MAKEWORD( 1, 1 ), &net.winsockdata ) ) + if( WSAStartup( MAKEWORD( 2, 0 ), &net.winsockdata )) { Con_DPrintf( S_ERROR "network initialization failed.\n" ); return; } -#else - // we have pthreads by default - net.threads_initialized = true; #endif - if( Sys_CheckParm( "-noip" )) - net.allow_ip = false; - else net.allow_ip = true; +#ifdef CAN_ASYNC_NS_RESOLVE + NET_InitializeCriticalSections(); +#endif + + net.allow_ip = !Sys_CheckParm( "-noip" ); + net.allow_ip6 = !Sys_CheckParm( "-noip6" ); // specify custom host port if( Sys_GetParmFromCmdLine( "-port", cmd ) && Q_isdigit( cmd )) - Cvar_FullSet( "hostport", cmd, FCVAR_READ_ONLY ); + Cvar_FullSet( net_hostport.name, cmd, net_hostport.flags ); + + // specify custom IPv6 host port + if( Sys_GetParmFromCmdLine( "-port6", cmd ) && Q_isdigit( cmd )) + Cvar_FullSet( net_ip6hostport.name, cmd, net_ip6hostport.flags ); + + // specify custom client port + if( Sys_GetParmFromCmdLine( "-clientport", cmd ) && Q_isdigit( cmd )) + Cvar_FullSet( net_clientport.name, cmd, net_clientport.flags ); + + // specify custom IPv6 client port + if( Sys_GetParmFromCmdLine( "-clientport6", cmd ) && Q_isdigit( cmd )) + Cvar_FullSet( net_ip6clientport.name, cmd, net_ip6clientport.flags ); // specify custom ip if( Sys_GetParmFromCmdLine( "-ip", cmd )) - Cvar_FullSet( "ip", cmd, FCVAR_READ_ONLY ); + Cvar_DirectSet( &net_ipname, cmd ); + + // specify custom ip6 + if( Sys_GetParmFromCmdLine( "-ip6", cmd )) + Cvar_DirectSet( &net_ip6name, cmd ); // adjust clockwindow if( Sys_GetParmFromCmdLine( "-clockwindow", cmd )) - Cvar_SetValue( "clockwindow", Q_atof( cmd )); + Cvar_DirectSetValue( &net_clockwindow, Q_atof( cmd )); net.sequence_number = 1; net.initialized = true; @@ -1793,6 +2135,11 @@ void NET_Shutdown( void ) NET_ClearLagData( true, true ); NET_Config( false, false ); + +#ifdef CAN_ASYNC_NS_RESOLVE + NET_DeleteCriticalSections(); +#endif + #if XASH_WIN32 WSACleanup(); #endif @@ -1800,837 +2147,3 @@ void NET_Shutdown( void ) } -/* -================================================= - -HTTP downloader - -================================================= -*/ - -typedef struct httpserver_s -{ - char host[256]; - int port; - char path[MAX_SYSPATH]; - qboolean needfree; - struct httpserver_s *next; - -} httpserver_t; - -enum connectionstate -{ - HTTP_QUEUE = 0, - HTTP_OPENED, - HTTP_SOCKET, - HTTP_NS_RESOLVED, - HTTP_CONNECTED, - HTTP_REQUEST, - HTTP_REQUEST_SENT, - HTTP_RESPONSE_RECEIVED, - HTTP_FREE -}; - -typedef struct httpfile_s -{ - struct httpfile_s *next; - httpserver_t *server; - char path[MAX_SYSPATH]; - file_t *file; - int socket; - int size; - int downloaded; - int lastchecksize; - float checktime; - float blocktime; - int id; - enum connectionstate state; - qboolean process; - - // query or response - char buf[BUFSIZ+1]; - int header_size, query_length, bytes_sent; -} httpfile_t; - -static struct http_static_s -{ - // file and server lists - httpfile_t *first_file, *last_file; - httpserver_t *first_server, *last_server; -} http; - - -static convar_t *http_useragent; -static convar_t *http_autoremove; -static convar_t *http_timeout; -static convar_t *http_maxconnections; - -/* -======================== -HTTP_ClearCustomServers -======================== -*/ -void HTTP_ClearCustomServers( void ) -{ - if( http.first_file ) - return; // may be referenced - - while( http.first_server && http.first_server->needfree ) - { - httpserver_t *tmp = http.first_server; - - http.first_server = http.first_server->next; - Mem_Free( tmp ); - } -} - -/* -============== -HTTP_FreeFile - -Skip to next server/file -============== -*/ -static void HTTP_FreeFile( httpfile_t *file, qboolean error ) -{ - char incname[256]; - - // Allways close file and socket - if( file->file ) - FS_Close( file->file ); - - file->file = NULL; - - if( file->socket != -1 ) - closesocket( file->socket ); - - file->socket = -1; - - Q_snprintf( incname, 256, "downloaded/%s.incomplete", file->path ); - if( error ) - { - // Switch to next fastdl server if present - if( file->server && ( file->state > HTTP_QUEUE ) && (file->state != HTTP_FREE ) ) - { - file->server = file->server->next; - file->state = HTTP_QUEUE; // Reset download state, HTTP_Run() will open file again - return; - } - - // Called because there was no servers to download, free file now - if( http_autoremove->value == 1 ) // remove broken file - FS_Delete( incname ); - else // autoremove disabled, keep file - Con_Printf( "cannot download %s from any server. " - "You may remove %s now\n", file->path, incname ); // Warn about trash file - - if( file->process ) - CL_ProcessFile( false, file->path ); // Process file, increase counter - } - else - { - // Success, rename and process file - char name[256]; - - Q_snprintf( name, 256, "downloaded/%s", file->path ); - FS_Rename( incname, name ); - - if( file->process ) - CL_ProcessFile( true, name ); - else - Con_Printf( "successfully downloaded %s, processing disabled!\n", name ); - } - - file->state = HTTP_FREE; -} - -/* -=================== -HTTP_AutoClean - -remove files with HTTP_FREE state from list -=================== -*/ -static void HTTP_AutoClean( void ) -{ - httpfile_t *curfile, *prevfile = 0; - - // clean all files marked to free - for( curfile = http.first_file; curfile; curfile = curfile->next ) - { - if( curfile->state != HTTP_FREE ) - { - prevfile = curfile; - continue; - } - - if( curfile == http.first_file ) - { - http.first_file = http.first_file->next; - Mem_Free( curfile ); - curfile = http.first_file; - if( !curfile ) - break; - continue; - } - - if( prevfile ) - prevfile->next = curfile->next; - Mem_Free( curfile ); - curfile = prevfile; - if( !curfile ) - break; - } - http.last_file = prevfile; -} - -/* -=================== -HTTP_ProcessStream - -process incoming data -=================== -*/ -static qboolean HTTP_ProcessStream( httpfile_t *curfile ) -{ - char buf[BUFSIZ+1]; - char *begin = 0; - int res; - - if( curfile->header_size >= BUFSIZ ) - { - Con_Reportf( S_ERROR "Header to big\n"); - HTTP_FreeFile( curfile, true ); - return false; - } - - while( ( res = recv( curfile->socket, buf, BUFSIZ - curfile->header_size, 0 ) ) > 0) // if we got there, we are receiving data - { - curfile->blocktime = 0; - - if( curfile->state < HTTP_RESPONSE_RECEIVED ) // Response still not received - { - memcpy( curfile->buf + curfile->header_size, buf, res ); - curfile->buf[curfile->header_size + res] = 0; - begin = Q_strstr( curfile->buf, "\r\n\r\n" ); - - if( begin ) // Got full header - { - int cutheadersize = begin - curfile->buf + 4; // after that begin of data - char *length; - - Con_Reportf( "HTTP: Got response!\n" ); - - if( !Q_strstr( curfile->buf, "200 OK" ) ) - { - *begin = 0; // cut string to print out response - begin = Q_strchr( curfile->buf, '\r' ); - - if( !begin ) begin = Q_strchr( curfile->buf, '\n' ); - if( begin ) - *begin = 0; - - Con_Printf( S_ERROR "%s: bad response: %s\n", curfile->path, curfile->buf ); - HTTP_FreeFile( curfile, true ); - return false; - } - - // print size - length = Q_stristr( curfile->buf, "Content-Length: " ); - if( length ) - { - int size = Q_atoi( length += 16 ); - - Con_Reportf( "HTTP: File size is %d\n", size ); - - if( ( curfile->size != -1 ) && ( curfile->size != size ) ) // check size if specified, not used - Con_Reportf( S_WARN "Server reports wrong file size!\n" ); - - curfile->size = size; - curfile->header_size = 0; - } - - if( curfile->size == -1 ) - { - // Usually fastdl's reports file size if link is correct - Con_Printf( S_ERROR "file size is unknown, refusing download!\n" ); - HTTP_FreeFile( curfile, true ); - return false; - } - - curfile->state = HTTP_RESPONSE_RECEIVED; // got response, let's start download - begin += 4; - - // Write remaining message part - if( res - cutheadersize - curfile->header_size > 0 ) - { - int ret = FS_Write( curfile->file, begin, res - cutheadersize - curfile->header_size ); - - if( ret != res - cutheadersize - curfile->header_size ) // could not write file - { - // close it and go to next - Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); - HTTP_FreeFile( curfile, true ); - return false; - } - curfile->downloaded += ret; - } - } - else - curfile->header_size += res; - } - else if( res > 0 ) - { - // data download - int ret = FS_Write( curfile->file, buf, res ); - - if ( ret != res ) - { - // close it and go to next - Con_Printf( S_ERROR "write failed for %s!\n", curfile->path ); - curfile->state = HTTP_FREE; - HTTP_FreeFile( curfile, true ); - return false; - } - - curfile->downloaded += ret; - curfile->lastchecksize += ret; - - // as after it will run in same frame - if( curfile->checktime > 5 ) - { - float speed = (float)curfile->lastchecksize / ( 5.0f * 1024 ); - - curfile->checktime = 0; - Con_Reportf( "download speed %f KB/s\n", speed ); - curfile->lastchecksize = 0; - } - } - } - curfile->checktime += host.frametime; - - return true; -} - -/* -============== -HTTP_Run - -Download next file block of each active file -Call every frame -============== -*/ -void HTTP_Run( void ) -{ - httpfile_t *curfile; - int iActiveCount = 0; - int iProgressCount = 0; - float flProgress = 0; - qboolean fResolving = false; - - for( curfile = http.first_file; curfile; curfile = curfile->next ) - { - int res; - struct sockaddr addr; - - if( curfile->state == HTTP_FREE ) - continue; - - if( curfile->state == HTTP_QUEUE ) - { - char name[MAX_SYSPATH]; - - if( iActiveCount > http_maxconnections->value ) - continue; - - if( !curfile->server ) - { - Con_Printf( S_ERROR "no servers to download %s!\n", curfile->path ); - HTTP_FreeFile( curfile, true ); - break; - } - - Con_Reportf( "HTTP: Starting download %s from %s\n", curfile->path, curfile->server->host ); - Q_snprintf( name, sizeof( name ), "downloaded/%s.incomplete", curfile->path ); - - curfile->file = FS_Open( name, "wb", true ); - - if( !curfile->file ) - { - Con_Printf( S_ERROR "cannot open %s!\n", name ); - HTTP_FreeFile( curfile, true ); - break; - } - - curfile->state = HTTP_OPENED; - curfile->blocktime = 0; - curfile->downloaded = 0; - curfile->lastchecksize = 0; - curfile->checktime = 0; - } - - iActiveCount++; - - if( curfile->state < HTTP_SOCKET ) // Socket is not created - { - dword mode; - - curfile->socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); - - // Now set non-blocking mode - // You may skip this if not supported by system, - // but download will lock engine, maybe you will need to add manual returns - mode = 1; - ioctlsocket( curfile->socket, FIONBIO, (void*)&mode ); -#if XASH_LINUX - // SOCK_NONBLOCK is not portable, so use fcntl - fcntl( curfile->socket, F_SETFL, fcntl( curfile->socket, F_GETFL, 0 ) | O_NONBLOCK ); -#endif - curfile->state = HTTP_SOCKET; - } - - if( curfile->state < HTTP_NS_RESOLVED ) - { - if( fResolving ) - continue; - - res = NET_StringToSockaddr( va( "%s:%d", curfile->server->host, curfile->server->port ), &addr, true ); - - if( res == 2 ) - { - fResolving = true; - continue; - } - - if( !res ) - { - Con_Printf( S_ERROR "failed to resolve server address for %s!\n", curfile->server->host ); - HTTP_FreeFile( curfile, true ); // Cannot connect - break; - } - curfile->state = HTTP_NS_RESOLVED; - } - - if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished - { - res = connect( curfile->socket, &addr, sizeof( struct sockaddr ) ); - - if( res ) - { - if( WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK ) // Should give EWOOLDBLOCK if try recv too soon - curfile->state = HTTP_CONNECTED; - else - { - Con_Printf( S_ERROR "cannot connect to server: %s\n", NET_ErrorString( ) ); - HTTP_FreeFile( curfile, true ); // Cannot connect - break; - } - continue; // skip to next file - } - curfile->state = HTTP_CONNECTED; - } - - if( curfile->state < HTTP_REQUEST ) // Request not formatted - { - curfile->query_length = Q_snprintf( curfile->buf, sizeof( curfile->buf ), - "GET %s%s HTTP/1.0\r\n" - "Host: %s\r\n" - "User-Agent: %s\r\n\r\n", curfile->server->path, - curfile->path, curfile->server->host, http_useragent->string ); - curfile->header_size = 0; - curfile->bytes_sent = 0; - curfile->state = HTTP_REQUEST; - } - - if( curfile->state < HTTP_REQUEST_SENT ) // Request not sent - { - qboolean wait = false; - - while( curfile->bytes_sent < curfile->query_length ) - { - res = send( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 ); - - - if( res < 0 ) - { - if( WSAGetLastError() != WSAEWOULDBLOCK && WSAGetLastError() != WSAENOTCONN ) - { - Con_Printf( S_ERROR "failed to send request: %s\n", NET_ErrorString() ); - HTTP_FreeFile( curfile, true ); - wait = true; - break; - } - // blocking while waiting connection - // increase counter when blocking - curfile->blocktime += host.frametime; - wait = true; - - if( curfile->blocktime > http_timeout->value ) - { - Con_Printf( S_ERROR "timeout on request send:\n%s\n", curfile->buf ); - HTTP_FreeFile( curfile, true ); - break; - } - break; - } - else - { - curfile->bytes_sent += res; - curfile->blocktime = 0; - } - } - - if( wait ) - continue; - - Con_Reportf( "HTTP: Request sent!\n"); - memset( curfile->buf, 0, sizeof( curfile->buf ) ); - curfile->state = HTTP_REQUEST_SENT; - } - - if( !HTTP_ProcessStream( curfile ) ) - break; - - if( curfile->size > 0 ) - { - flProgress += (float)curfile->downloaded / curfile->size; - iProgressCount++; - } - - if( curfile->size > 0 && curfile->downloaded >= curfile->size ) - { - HTTP_FreeFile( curfile, false ); // success - break; - } - else if( (WSAGetLastError() != WSAEWOULDBLOCK) && (WSAGetLastError() != WSAEINPROGRESS) ) - Con_Reportf( "problem downloading %s:\n%s\n", curfile->path, NET_ErrorString() ); - else - curfile->blocktime += host.frametime; - - if( curfile->blocktime > http_timeout->value ) - { - Con_Printf( S_ERROR "timeout on receiving data!\n"); - HTTP_FreeFile( curfile, true ); - break; - } - } - - // update progress - if( !Host_IsDedicated() && iProgressCount != 0 ) - Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); - - HTTP_AutoClean(); -} - -/* -=================== -HTTP_AddDownload - -Add new download to end of queue -=================== -*/ -void HTTP_AddDownload( const char *path, int size, qboolean process ) -{ - httpfile_t *httpfile = Z_Calloc( sizeof( httpfile_t ) ); - - Con_Reportf( "File %s queued to download\n", path ); - - httpfile->size = size; - httpfile->downloaded = 0; - httpfile->socket = -1; - Q_strncpy ( httpfile->path, path, sizeof( httpfile->path ) ); - - if( http.last_file ) - { - // Add next to last download - httpfile->id = http.last_file->id + 1; - http.last_file->next= httpfile; - http.last_file = httpfile; - } - else - { - // It will be the only download - httpfile->id = 0; - http.last_file = http.first_file = httpfile; - } - - httpfile->file = NULL; - httpfile->next = NULL; - httpfile->state = HTTP_QUEUE; - httpfile->server = http.first_server; - httpfile->process = process; -} - -/* -=============== -HTTP_Download_f - -Console wrapper -=============== -*/ -static void HTTP_Download_f( void ) -{ - if( Cmd_Argc() < 2 ) - { - Con_Printf( S_USAGE "download \n"); - return; - } - - HTTP_AddDownload( Cmd_Argv( 1 ), -1, false ); -} - -/* -============== -HTTP_ParseURL -============== -*/ -static httpserver_t *HTTP_ParseURL( const char *url ) -{ - httpserver_t *server; - int i; - - url = Q_strstr( url, "http://" ); - - if( !url ) - return NULL; - - url += 7; - server = Z_Calloc( sizeof( httpserver_t ) ); - i = 0; - - while( *url && ( *url != ':' ) && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) - { - if( i > sizeof( server->host ) ) - return NULL; - - server->host[i++] = *url++; - } - - server->host[i] = 0; - - if( *url == ':' ) - { - server->port = Q_atoi( ++url ); - - while( *url && ( *url != '/' ) && ( *url != '\r' ) && ( *url != '\n' ) ) - url++; - } - else - server->port = 80; - - i = 0; - - while( *url && ( *url != '\r' ) && ( *url != '\n' ) ) - { - if( i > sizeof( server->path ) ) - return NULL; - - server->path[i++] = *url++; - } - - server->path[i] = 0; - server->next = NULL; - server->needfree = false; - - return server; -} - -/* -======================= -HTTP_AddCustomServer -======================= -*/ -void HTTP_AddCustomServer( const char *url ) -{ - httpserver_t *server = HTTP_ParseURL( url ); - - if( !server ) - { - Con_Printf( S_ERROR "\"%s\" is not valid url!\n", url ); - return; - } - - server->needfree = true; - server->next = http.first_server; - http.first_server = server; -} - -/* -======================= -HTTP_AddCustomServer_f -======================= -*/ -static void HTTP_AddCustomServer_f( void ) -{ - if( Cmd_Argc() == 2 ) - { - HTTP_AddCustomServer( Cmd_Argv( 1 ) ); - } -} - -/* -============ -HTTP_Clear_f - -Clear all queue -============ -*/ -static void HTTP_Clear_f( void ) -{ - http.last_file = NULL; - - while( http.first_file ) - { - httpfile_t *file = http.first_file; - - http.first_file = http.first_file->next; - - if( file->file ) - FS_Close( file->file ); - - if( file->socket != -1 ) - closesocket( file->socket ); - - Mem_Free( file ); - } -} - -/* -============== -HTTP_Cancel_f - -Stop current download, skip to next file -============== -*/ -static void HTTP_Cancel_f( void ) -{ - if( !http.first_file ) - return; - - http.first_file->state = HTTP_FREE; - HTTP_FreeFile( http.first_file, true ); -} - -/* -============= -HTTP_Skip_f - -Stop current download, skip to next server -============= -*/ -static void HTTP_Skip_f( void ) -{ - if( http.first_file ) - HTTP_FreeFile( http.first_file, true ); -} - -/* -============= -HTTP_List_f - -Print all pending downloads to console -============= -*/ -static void HTTP_List_f( void ) -{ - httpfile_t *file = http.first_file; - - while( file ) - { - if ( file->server ) - Con_Printf ( "\t%d %d http://%s:%d/%s%s %d\n", file->id, file->state, - file->server->host, file->server->port, file->server->path, - file->path, file->downloaded ); - else - Con_Printf ( "\t%d %d (no server) %s\n", file->id, file->state, file->path ); - - file = file->next; - } -} - -/* -================ -HTTP_ResetProcessState - -When connected to new server, all old files should not increase counter -================ -*/ -void HTTP_ResetProcessState( void ) -{ - httpfile_t *file = http.first_file; - - while( file ) - { - file->process = false; - file = file->next; - } -} - -/* -============= -HTTP_Init -============= -*/ -void HTTP_Init( void ) -{ - char *serverfile, *line, token[1024]; - - http.last_server = NULL; - - http.first_file = http.last_file = NULL; - - Cmd_AddRestrictedCommand("http_download", &HTTP_Download_f, "add file to download queue"); - Cmd_AddRestrictedCommand("http_skip", &HTTP_Skip_f, "skip current download server"); - Cmd_AddRestrictedCommand("http_cancel", &HTTP_Cancel_f, "cancel current download"); - Cmd_AddRestrictedCommand("http_clear", &HTTP_Clear_f, "cancel all downloads"); - Cmd_AddCommand("http_list", &HTTP_List_f, "list all queued downloads"); - Cmd_AddCommand("http_addcustomserver", &HTTP_AddCustomServer_f, "add custom fastdl server"); - http_useragent = Cvar_Get( "http_useragent", "xash3d", FCVAR_ARCHIVE, "User-Agent string" ); - http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE, "remove broken files" ); - http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE, "timeout for http downloader" ); - http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE, "maximum http connection number" ); - - // Read servers from fastdl.txt - line = serverfile = (char *)FS_LoadFile( "fastdl.txt", 0, false ); - - if( serverfile ) - { - while( ( line = COM_ParseFile( line, token, sizeof( token ) ) ) ) - { - httpserver_t *server = HTTP_ParseURL( token ); - - if( !server ) - continue; - - if( !http.last_server ) - http.last_server = http.first_server = server; - else - { - http.last_server->next = server; - http.last_server = server; - } - } - - Mem_Free( serverfile ); - } -} - -/* -==================== -HTTP_Shutdown -==================== -*/ -void HTTP_Shutdown( void ) -{ - HTTP_Clear_f(); - - while( http.first_server ) - { - httpserver_t *tmp = http.first_server; - - http.first_server = http.first_server->next; - Mem_Free( tmp ); - } - - http.last_server = NULL; -} diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index 5ab40ecd..791bff78 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -23,6 +23,13 @@ typedef enum NS_COUNT } netsrc_t; +typedef enum +{ + NET_EAI_NONAME = 0, + NET_EAI_OK = 1, + NET_EAI_AGAIN = 2 +} net_gai_state_t; + // Max length of unreliable message #define MAX_DATAGRAM 16384 @@ -43,8 +50,10 @@ typedef enum #include "netadr.h" -extern convar_t *net_showpackets; -extern convar_t *net_clockwindow; +extern convar_t net_showpackets; +extern convar_t net_clockwindow; +extern convar_t net_send_debug; +extern convar_t net_recv_debug; void NET_Init( void ); void NET_Shutdown( void ); @@ -52,31 +61,38 @@ void NET_Sleep( int msec ); qboolean NET_IsActive( void ); qboolean NET_IsConfigured( void ); void NET_Config( qboolean net_enable, qboolean changeport ); -qboolean NET_IsLocalAddress( netadr_t adr ); -const char *NET_AdrToString( const netadr_t a ); -const char *NET_BaseAdrToString( const netadr_t a ); +const char *NET_AdrToString( const netadr_t a ) RETURNS_NONNULL; +const char *NET_BaseAdrToString( const netadr_t a ) RETURNS_NONNULL; qboolean NET_IsReservedAdr( netadr_t a ); -qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b ); qboolean NET_StringToAdr( const char *string, netadr_t *adr ); -int NET_StringToAdrNB( const char *string, netadr_t *adr ); +qboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen ); +net_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr, qboolean v6only ); +int NET_CompareAdrSort( const void *_a, const void *_b ); qboolean NET_CompareAdr( const netadr_t a, const netadr_t b ); qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ); +qboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen ); qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ); -qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen ); -qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen ); void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to ); void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize ); -void NET_ClearLagData( qboolean bClient, qboolean bServer ); +void NET_IP6BytesToNetadr( netadr_t *adr, const uint8_t *ip6 ); +void NET_NetadrToIP6Bytes( uint8_t *ip6, const netadr_t *adr ); + +static inline qboolean NET_IsLocalAddress( netadr_t adr ) +{ + return NET_NetadrType( &adr ) == NA_LOOPBACK; +} + +void NET_GetLocalAddress( netadr_t *ip4, netadr_t *ip6 ); #if !XASH_DEDICATED -qboolean CL_LegacyMode( void ); int CL_GetSplitSize( void ); #endif void HTTP_AddCustomServer( const char *url ); -void HTTP_AddDownload( const char *path, int size, qboolean process ); +void HTTP_AddDownload( const char *path, int size, qboolean process, resource_t *res ); void HTTP_ClearCustomServers( void ); void HTTP_Shutdown( void ); +void HTTP_ResetProcessState( void ); void HTTP_Init( void ); void HTTP_Run( void ); diff --git a/engine/common/net_ws_private.h b/engine/common/net_ws_private.h new file mode 100644 index 00000000..16ad622f --- /dev/null +++ b/engine/common/net_ws_private.h @@ -0,0 +1,103 @@ +/* +net_ws_private.h - networking private definitions +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "net_ws.h" +#if XASH_NO_NETWORK +#include "platform/stub/net_stub.h" +#elif XASH_WIN32 +#include "platform/win32/net.h" +#elif XASH_PSVITA +#include "platform/psvita/net_psvita.h" +static const struct in6_addr in6addr_any; +#else +#include "platform/posix/net.h" +#endif + + +// +// net_ws.c +// +static inline const char *NET_ErrorString( void ) +{ +#if XASH_WIN32 + int err = WSAGetLastError(); + switch( err ) + { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSAEDISCON: return "WSAEDISCON"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + } + + return "NO ERROR"; +#else + return strerror( errno ); +#endif +} + +static inline socklen_t NET_SockAddrLen( const struct sockaddr_storage *addr ) +{ + switch ( addr->ss_family ) + { + case AF_INET: + return sizeof( struct sockaddr_in ); + case AF_INET6: + return sizeof( struct sockaddr_in6 ); + default: + return sizeof( *addr ); // what the fuck is this? + } +} + +net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family ); diff --git a/engine/common/netchan.h b/engine/common/netchan.h index a1e7c3a7..cf0325fc 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -69,11 +69,9 @@ GNU General Public License for more details. // bytes will be stripped by the networking channel layer #define NET_MAX_MESSAGE PAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 ) -#define MASTERSERVER_ADR "mentality.rip:27010" #define MS_SCAN_REQUEST "1\xFF" "0.0.0.0:0\0" #define PORT_MASTER 27010 -#define PORT_CLIENT 27005 #define PORT_SERVER 27015 #define MULTIPLAYER_BACKUP 64 // how many data slots to use when in multiplayer (must be power of 2) @@ -185,13 +183,13 @@ typedef struct fragbuf_s struct fragbuf_s *next; // next buffer in chain int bufferid; // id of this buffer sizebuf_t frag_message; // message buffer where raw data is stored - byte frag_message_buf[NET_MAX_FRAGMENT]; // the actual data sits here qboolean isfile; // is this a file buffer? qboolean isbuffer; // is this file buffer from memory ( custom decal, etc. ). qboolean iscompressed; // is compressed file, we should using filename.ztmp char filename[MAX_OSPATH]; // name of the file to save out on remote host int foffset; // offset in file from which to read data int size; // size of data to read at that offset + byte frag_message_buf[]; // the actual data sits here (flexible) } fragbuf_t; // Waiting list of fragbuf chains @@ -209,6 +207,15 @@ typedef enum fragsize_e FRAGSIZE_UNRELIABLE } fragsize_t; +typedef enum netchan_flags_e +{ + NETCHAN_USE_LEGACY_SPLIT = BIT( 0 ), + NETCHAN_USE_MUNGE = BIT( 1 ), + NETCHAN_USE_BZIP2 = BIT( 2 ), + NETCHAN_GOLDSRC = BIT( 3 ), + NETCHAN_USE_LZSS = BIT( 4 ), // mutually exclusive with bzip2 +} netchan_flags_t; + // Network Connection Channel typedef struct netchan_s { @@ -271,14 +278,18 @@ typedef struct netchan_s // added for net_speeds size_t total_sended; size_t total_received; - qboolean split; unsigned int maxpacket; unsigned int splitid; - netsplit_t netsplit; + netsplit_t netsplit; + + qboolean split; + qboolean use_munge; + qboolean use_bz2; + qboolean use_lzss; + qboolean gs_netchan; } netchan_t; extern netadr_t net_from; -extern netadr_t net_local; extern sizebuf_t net_message; extern byte net_message_buffer[NET_MAX_MESSAGE]; extern convar_t sv_lan; @@ -287,16 +298,15 @@ extern int net_drop; void Netchan_Init( void ); void Netchan_Shutdown( void ); -void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ) ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void *, fragsize_t mode ), uint flags ); void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, const char *filename, byte *pbuf, int size ); qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length ); qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ); void Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg ); int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ); -void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ); -void Netchan_TransmitBits( netchan_t *chan, int lengthInBits, byte *data ); -void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ); -void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) _format( 3 ); +void Netchan_TransmitBits( netchan_t *chan, int lengthInBits, const byte *data ); +void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, const byte *data ); +void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) FORMAT_CHECK( 3 ); qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ); void Netchan_UpdateProgress( netchan_t *chan ); qboolean Netchan_IncomingReady( netchan_t *chan ); diff --git a/engine/common/pm_debug.c b/engine/common/pm_debug.c deleted file mode 100644 index 6dab417a..00000000 --- a/engine/common/pm_debug.c +++ /dev/null @@ -1,95 +0,0 @@ -/* -pm_debug.c - player move debugging code -Copyright (C) 2017 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "common.h" -#include "xash3d_mathlib.h" -#include "pm_local.h" -#if !XASH_DEDICATED -#include "client.h" // CL_Particle -#endif - -// expand debugging BBOX particle hulls by this many units. -#define BOX_GAP 0.0f - -/* -=============== -PM_ParticleLine - -draw line from particles -================ -*/ -void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ) -{ -#if !XASH_DEDICATED - float len, curdist; - vec3_t diff, pos; - - // determine distance - VectorSubtract( end, start, diff ); - len = VectorNormalizeLength( diff ); - curdist = 0; - - while( curdist <= len ) - { - VectorMA( start, curdist, diff, pos ); - CL_Particle( pos, pcolor, life, 0, zvel ); - curdist += 2.0f; - } -#endif // XASH_DEDICATED -} - -/* -================ -PM_DrawRectangle - -================ -*/ -static void PM_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life ) -{ - PM_ParticleLine( tl, bl, pcolor, life, 0 ); - PM_ParticleLine( bl, br, pcolor, life, 0 ); - PM_ParticleLine( br, tr, pcolor, life, 0 ); - PM_ParticleLine( tr, tl, pcolor, life, 0 ); -} - -/* -================ -PM_DrawBBox - -================ -*/ -void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ) -{ -#if !XASH_DEDICATED - vec3_t p[8], tmp; - float gap = BOX_GAP; - int i; - - for( i = 0; i < 8; i++ ) - { - tmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap; - tmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ; - tmp[2] = (i & 4) ? mins[2] - gap : maxs[2] + gap ; - - VectorAdd( tmp, origin, tmp ); - VectorCopy( tmp, p[i] ); - } - - for( i = 0; i < 6; i++ ) - { - PM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life ); - } -#endif // XASH_DEDICATED -} diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h index 6ad6d8e2..a079c310 100644 --- a/engine/common/pm_local.h +++ b/engine/common/pm_local.h @@ -20,12 +20,6 @@ GNU General Public License for more details. typedef int (*pfnIgnore)( physent_t *pe ); // custom trace filter -// -// pm_debug.c -// -void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ); -void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ); - // // pm_trace.c // @@ -38,7 +32,36 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p int PM_HullPointContents( hull_t *hull, int num, const vec3_t p ); int PM_TruePointContents( playermove_t *pmove, const vec3_t p ); int PM_PointContents( playermove_t *pmove, const vec3_t p ); -void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ); +float PM_TraceModel( playermove_t *pmove, physent_t *pe, float *start, float *end, trace_t *trace ); +pmtrace_t *PM_TraceLine( playermove_t *pmove, float *start, float *end, int flags, int usehull, int ignore_pe ); +pmtrace_t *PM_TraceLineEx( playermove_t *pmove, float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ); +struct msurface_s *PM_TraceSurfacePmove( playermove_t *pmove, int ground, float *vstart, float *vend ); +const char *PM_TraceTexture( playermove_t *pmove, int ground, float *vstart, float *vend ); +int PM_PointContentsPmove( playermove_t *pmove, const float *p, int *truecontents ); +void PM_StuckTouch( playermove_t *pmove, int hitent, pmtrace_t *tr ); + +static inline void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ) +{ + out->allsolid = in->allsolid; + out->startsolid = in->startsolid; + out->inopen = in->inopen; + out->inwater = in->inwater; + out->fraction = in->fraction; + out->plane.dist = in->plane.dist; + out->hitgroup = in->hitgroup; + out->ent = ent; + + VectorCopy( in->endpos, out->endpos ); + VectorCopy( in->plane.normal, out->plane.normal ); +} + +static inline void PM_ClearPhysEnts( playermove_t *pmove ) +{ + pmove->nummoveent = 0; + pmove->numphysent = 0; + pmove->numvisent = 0; + pmove->numtouch = 0; +} static inline void PM_InitTrace( trace_t *trace, const vec3_t end ) { @@ -59,7 +82,6 @@ static inline void PM_InitPMTrace( pmtrace_t *trace, const vec3_t end ) // // pm_surface.c // -const char *PM_TraceTexture( physent_t *pe, vec3_t vstart, vec3_t vend ); msurface_t *PM_RecursiveSurfCheck( model_t *model, mnode_t *node, vec3_t p1, vec3_t p2 ); msurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end ); int PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags ); diff --git a/engine/common/pm_surface.c b/engine/common/pm_surface.c index 6ba55a55..5ae4b2d1 100644 --- a/engine/common/pm_surface.c +++ b/engine/common/pm_surface.c @@ -48,7 +48,7 @@ SampleMiptex fence texture testing ============= */ -int PM_SampleMiptex( const msurface_t *surf, const vec3_t point ) +static int PM_SampleMiptex( const msurface_t *surf, const vec3_t point ) { mextrasurf_t *info = surf->info; mfacebevel_t *fb = info->bevel; @@ -112,6 +112,9 @@ msurface_t *PM_RecursiveSurfCheck( model_t *mod, mnode_t *node, vec3_t p1, vec3_ int i, side; msurface_t *surf; vec3_t mid; + mnode_t *children[2]; + int numsurfaces, firstsurface; + loc0: if( node->contents < 0 ) return NULL; @@ -119,15 +122,17 @@ loc0: t1 = PlaneDiff( p1, node->plane ); t2 = PlaneDiff( p2, node->plane ); + node_children( children, node, mod ); + if( t1 >= -FRAC_EPSILON && t2 >= -FRAC_EPSILON ) { - node = node->children[0]; + node = children[0]; goto loc0; } if( t1 < FRAC_EPSILON && t2 < FRAC_EPSILON ) { - node = node->children[1]; + node = children[1]; goto loc0; } @@ -137,13 +142,15 @@ loc0: VectorLerp( p1, frac, p2, mid ); - if(( surf = PM_RecursiveSurfCheck( mod, node->children[side], p1, mid )) != NULL ) + if(( surf = PM_RecursiveSurfCheck( mod, children[side], p1, mid )) != NULL ) return surf; // walk through real faces - for( i = 0; i < node->numsurfaces; i++ ) + numsurfaces = node_numsurfaces( node, mod ); + firstsurface = node_firstsurface( node, mod ); + for( i = 0; i < numsurfaces; i++ ) { - msurface_t *surf = &mod->surfaces[node->firstsurface + i]; + msurface_t *surf = &mod->surfaces[firstsurface + i]; mextrasurf_t *info = surf->info; mfacebevel_t *fb = info->bevel; int j, contents; @@ -172,7 +179,7 @@ loc0: return NULL; // through the fence } - return PM_RecursiveSurfCheck( mod, node->children[side^1], mid, p2 ); + return PM_RecursiveSurfCheck( mod, children[side^1], mid, p2 ); } /* @@ -214,24 +221,6 @@ msurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end ) return PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l ); } -/* -================== -PM_TraceTexture - -find the face where the traceline hit -assume physentity is valid -================== -*/ -const char *PM_TraceTexture( physent_t *pe, vec3_t start, vec3_t end ) -{ - msurface_t *surf = PM_TraceSurface( pe, start, end ); - - if( !surf || !surf->texinfo || !surf->texinfo->texture ) - return NULL; - - return surf->texinfo->texture->name; -} - /* ================== PM_TestLine_r @@ -239,12 +228,15 @@ PM_TestLine_r optimized trace for light gathering ================== */ -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 ) +static 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 ) { float front, back; float frac, midf; int i, r, side; vec3_t mid; + mnode_t *children[2]; + int numsurfaces, firstsurface; + loc0: if( node->contents < 0 ) { @@ -260,15 +252,17 @@ loc0: front = PlaneDiff( start, node->plane ); back = PlaneDiff( stop, node->plane ); + node_children( children, node, mod ); + if( front >= -FRAC_EPSILON && back >= -FRAC_EPSILON ) { - node = node->children[0]; + node = children[0]; goto loc0; } if( front < FRAC_EPSILON && back < FRAC_EPSILON ) { - node = node->children[1]; + node = children[1]; goto loc0; } @@ -279,7 +273,7 @@ loc0: VectorLerp( start, frac, stop, mid ); midf = p1f + ( p2f - p1f ) * frac; - r = PM_TestLine_r( mod, node->children[side], p1f, midf, start, mid, trace ); + r = PM_TestLine_r( mod, children[side], p1f, midf, start, mid, trace ); if( r != CONTENTS_EMPTY ) { @@ -290,9 +284,11 @@ loc0: } // walk through real faces - for( i = 0; i < node->numsurfaces; i++ ) + numsurfaces = node_numsurfaces( node, mod ); + firstsurface = node_firstsurface( node, mod ); + for( i = 0; i < numsurfaces; i++ ) { - msurface_t *surf = &mod->surfaces[node->firstsurface + i]; + msurface_t *surf = &mod->surfaces[firstsurface + i]; mextrasurf_t *info = surf->info; mfacebevel_t *fb = info->bevel; int j, contents; @@ -326,7 +322,7 @@ loc0: return contents; } - return PM_TestLine_r( mod, node->children[!side], midf, p2f, mid, stop, trace ); + return PM_TestLine_r( mod, children[!side], midf, p2f, mid, stop, trace ); } int PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags ) diff --git a/engine/common/pm_trace.c b/engine/common/pm_trace.c index 0e1ce7c6..ed365843 100644 --- a/engine/common/pm_trace.c +++ b/engine/common/pm_trace.c @@ -25,8 +25,7 @@ GNU General Public License for more details. #define PM_AllowHitBoxTrace( model, hull ) ( model && model->type == mod_studio && ( FBitSet( model->flags, STUDIO_TRACE_HITBOX ) || hull == 2 )) static mplane_t pm_boxplanes[6]; -static mclipnode_t pm_boxclipnodes[6]; -static hull_t pm_boxhull; +static hull_t pm_boxhull; // default hullmins static const vec3_t pm_hullmins[MAX_MAP_HULLS] = @@ -65,23 +64,15 @@ can just be stored out and get a proper hull_t structure. */ void PM_InitBoxHull( void ) { - int i, side; + int i; - pm_boxhull.clipnodes = pm_boxclipnodes; + pm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16; pm_boxhull.planes = pm_boxplanes; pm_boxhull.firstclipnode = 0; pm_boxhull.lastclipnode = 5; for( i = 0; i < 6; i++ ) { - pm_boxclipnodes[i].planenum = i; - - side = i & 1; - - pm_boxclipnodes[i].children[side] = CONTENTS_EMPTY; - if( i != 5 ) pm_boxclipnodes[i].children[side^1] = i + 1; - else pm_boxclipnodes[i].children[side^1] = CONTENTS_SOLID; - pm_boxplanes[i].type = i>>1; pm_boxplanes[i].normal[i>>1] = 1.0f; pm_boxplanes[i].signbits = 0; @@ -97,7 +88,7 @@ To keep everything totally uniform, bounding boxes are turned into small BSP trees instead of being compared directly. =================== */ -hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs ) +static hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs ) { pm_boxplanes[0].dist = maxs[0]; pm_boxplanes[1].dist = mins[0]; @@ -106,14 +97,12 @@ hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs ) pm_boxplanes[4].dist = maxs[2]; pm_boxplanes[5].dist = mins[2]; - return &pm_boxhull; -} + if( world.version == QBSP2_VERSION ) + pm_boxhull.clipnodes32 = (mclipnode32_t *)box_clipnodes32; + else + pm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16; -void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ) -{ - memcpy( out, in, 48 ); // matched - out->hitgroup = in->hitgroup; - out->ent = ent; + return &pm_boxhull; } /* @@ -122,17 +111,28 @@ PM_HullPointContents ================== */ -int PM_HullPointContents( hull_t *hull, int num, const vec3_t p ) +int GAME_EXPORT PM_HullPointContents( hull_t *hull, int num, const vec3_t p ) { mplane_t *plane; if( !hull || !hull->planes ) // fantom bmodels? return CONTENTS_NONE; - while( num >= 0 ) + if( world.version == QBSP2_VERSION ) { - plane = &hull->planes[hull->clipnodes[num].planenum]; - num = hull->clipnodes[num].children[PlaneDiff( p, plane ) < 0]; + while( num >= 0 ) + { + plane = &hull->planes[hull->clipnodes32[num].planenum]; + num = hull->clipnodes32[num].children[PlaneDiff( p, plane ) < 0]; + } + } + else + { + while( num >= 0 ) + { + plane = &hull->planes[hull->clipnodes16[num].planenum]; + num = hull->clipnodes16[num].children[PlaneDiff( p, plane ) < 0]; + } } return num; } @@ -170,7 +170,7 @@ hull_t *PM_HullForBsp( physent_t *pe, playermove_t *pmove, float *offset ) Assert( hull != NULL ); // calculate an offset value to center the origin - VectorSubtract( hull->clip_mins, pmove->player_mins[pmove->usehull], offset ); + VectorSubtract( hull->clip_mins, host.player_mins[pmove->usehull], offset ); VectorAdd( offset, pe->origin, offset ); return hull; @@ -183,11 +183,11 @@ PM_HullForStudio generate multiple hulls as hitboxes ================== */ -hull_t *PM_HullForStudio( physent_t *pe, playermove_t *pmove, int *numhitboxes ) +static hull_t *PM_HullForStudio( physent_t *pe, playermove_t *pmove, int *numhitboxes ) { vec3_t size; - VectorSubtract( pmove->player_maxs[pmove->usehull], pmove->player_mins[pmove->usehull], size ); + VectorSubtract( host.player_maxs[pmove->usehull], host.player_mins[pmove->usehull], size ); VectorScale( size, 0.5f, size ); return Mod_HullForStudio( pe->studiomodel, pe->frame, pe->sequence, pe->angles, pe->origin, size, pe->controller, pe->blending, numhitboxes, NULL ); @@ -200,7 +200,7 @@ PM_RecursiveHullCheck */ qboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace ) { - mclipnode_t *node; + int children[2]; mplane_t *plane; float t1, t2; float frac, midf; @@ -230,24 +230,34 @@ loc0: } if( num < hull->firstclipnode || num > hull->lastclipnode ) - Host_Error( "PM_RecursiveHullCheck: bad node number %i\n", num ); + Host_Error( "%s: bad node number %i\n", __func__, num ); // find the point distances - node = hull->clipnodes + num; - plane = hull->planes + node->planenum; + if( world.version == QBSP2_VERSION ) + { + children[0] = hull->clipnodes32[num].children[0]; + children[1] = hull->clipnodes32[num].children[1]; + plane = hull->planes + hull->clipnodes32[num].planenum; + } + else + { + children[0] = hull->clipnodes16[num].children[0]; + children[1] = hull->clipnodes16[num].children[1]; + plane = hull->planes + hull->clipnodes16[num].planenum; + } t1 = PlaneDiff( p1, plane ); t2 = PlaneDiff( p2, plane ); if( t1 >= 0.0f && t2 >= 0.0f ) { - num = node->children[0]; + num = children[0]; goto loc0; } if( t1 < 0.0f && t2 < 0.0f ) { - num = node->children[1]; + num = children[1]; goto loc0; } @@ -264,14 +274,14 @@ loc0: VectorLerp( p1, frac, p2, mid ); // move up to the node - if( !PM_RecursiveHullCheck( hull, node->children[side], p1f, midf, p1, mid, trace )) + if( !PM_RecursiveHullCheck( hull, children[side], p1f, midf, p1, mid, trace )) return false; // this recursion can not be optimized because mid would need to be duplicated on a stack - if( PM_HullPointContents( hull, node->children[side^1], mid ) != CONTENTS_SOLID ) + if( PM_HullPointContents( hull, children[side^1], mid ) != CONTENTS_SOLID ) { // go past the node - return PM_RecursiveHullCheck( hull, node->children[side^1], midf, p2f, mid, p2, trace ); + return PM_RecursiveHullCheck( hull, children[side^1], midf, p2f, mid, p2, trace ); } // never got out of the solid area @@ -363,8 +373,8 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int if( pe->solid == SOLID_CUSTOM ) { - VectorCopy( pmove->player_mins[pmove->usehull], mins ); - VectorCopy( pmove->player_maxs[pmove->usehull], maxs ); + VectorCopy( host.player_mins[pmove->usehull], mins ); + VectorCopy( host.player_maxs[pmove->usehull], maxs ); VectorClear( offset ); } else if( pe->model ) @@ -385,8 +395,8 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int } else { - VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); - VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + VectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs ); hull = PM_HullForBox( mins, maxs ); VectorCopy( pe->origin, offset ); @@ -394,8 +404,8 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int } else { - VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); - VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + VectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs ); hull = PM_HullForBox( mins, maxs ); VectorCopy( pe->origin, offset ); @@ -426,7 +436,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int if( transform_bbox ) { - World_TransformAABB( matrix, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], mins, maxs ); + World_TransformAABB( matrix, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], mins, maxs ); VectorSubtract( hull->clip_mins, mins, offset ); // calc new local offset for( j = 0; j < 3; j++ ) @@ -553,8 +563,8 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p if( pe->solid == SOLID_CUSTOM ) { - VectorCopy( pmove->player_mins[pmove->usehull], mins ); - VectorCopy( pmove->player_maxs[pmove->usehull], maxs ); + VectorCopy( host.player_mins[pmove->usehull], mins ); + VectorCopy( host.player_maxs[pmove->usehull], maxs ); VectorClear( offset ); } else if( pe->model ) @@ -568,8 +578,8 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p } else { - VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); - VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + VectorSubtract( pe->mins, host.player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, host.player_mins[pmove->usehull], maxs ); hull = PM_HullForBox( mins, maxs ); VectorCopy( pe->origin, offset ); @@ -595,7 +605,7 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p if( transform_bbox ) { - World_TransformAABB( matrix, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], mins, maxs ); + World_TransformAABB( matrix, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], mins, maxs ); VectorSubtract( hull->clip_mins, mins, offset ); // calc new local offset for( j = 0; j < 3; j++ ) @@ -724,3 +734,157 @@ int PM_PointContents( playermove_t *pmove, const vec3_t p ) return contents; } + +/* +============= +PM_TraceModel + +============= +*/ +float PM_TraceModel( playermove_t *pmove, physent_t *pe, float *start, float *end, trace_t *trace ) +{ + int old_usehull; + vec3_t start_l, end_l; + vec3_t offset, temp; + qboolean rotated; + matrix4x4 matrix; + hull_t *hull; + + PM_InitTrace( trace, end ); + + old_usehull = pmove->usehull; + pmove->usehull = 2; + + hull = PM_HullForBsp( pe, pmove, offset ); + + pmove->usehull = old_usehull; + + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + rotated = true; + else rotated = false; + + if( rotated ) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + else + { + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + } + + PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace ); + trace->ent = NULL; + + if( rotated ) + { + VectorCopy( trace->plane.normal, temp ); + Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); + } + + VectorLerp( start, trace->fraction, end, trace->endpos ); + + return trace->fraction; +} + +pmtrace_t *PM_TraceLine( playermove_t *pmove, float *start, float *end, int flags, int usehull, int ignore_pe ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = pmove->usehull; + pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numphysent, pmove->physents, ignore_pe, NULL ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numvisent, pmove->visents, ignore_pe, NULL ); + break; + } + + pmove->usehull = old_usehull; + + return &tr; +} + +pmtrace_t *PM_TraceLineEx( playermove_t *pmove, float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = pmove->usehull; + pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numphysent, pmove->physents, -1, pmFilter ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( pmove, start, end, 0, pmove->numvisent, pmove->visents, -1, pmFilter ); + break; + } + + pmove->usehull = old_usehull; + + return &tr; +} + +struct msurface_s *PM_TraceSurfacePmove( playermove_t *pmove, int ground, float *vstart, float *vend ) +{ + if( ground < 0 || ground >= pmove->numphysent ) + return NULL; // bad ground + + return PM_TraceSurface( &pmove->physents[ground], vstart, vend ); +} + +const char *PM_TraceTexture( playermove_t *pmove, int ground, float *vstart, float *vend ) +{ + msurface_t *surf; + + if( ground < 0 || ground >= pmove->numphysent ) + return NULL; // bad ground + + surf = PM_TraceSurface( &pmove->physents[ground], vstart, vend ); + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return NULL; + + return surf->texinfo->texture->name; +} + +int PM_PointContentsPmove( playermove_t *pmove, const float *p, int *truecontents ) +{ + int cont, truecont; + + truecont = cont = PM_PointContents( pmove, p ); + if( truecontents ) *truecontents = truecont; + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +void PM_StuckTouch( playermove_t *pmove, int hitent, pmtrace_t *tr ) +{ + int i; + + for( i = 0; i < pmove->numtouch; i++ ) + { + if( pmove->touchindex[i].ent == hitent ) + return; + } + + if( pmove->numtouch >= MAX_PHYSENTS ) + return; + + VectorCopy( pmove->velocity, tr->deltavelocity ); + tr->ent = hitent; + + pmove->touchindex[pmove->numtouch++] = *tr; +} diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 7d00ba0b..55bdc7d7 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -73,7 +73,7 @@ GNU General Public License for more details. #define svc_director 51 // #define svc_voiceinit 52 // #define svc_voicedata 53 // [byte][short][...] -#define svc_deltapacketbones 54 // [short][byte][...] +// reserved // reserved #define svc_resourcelocation 56 // [string] #define svc_querycvarvalue 57 // [string] @@ -93,7 +93,7 @@ GNU General Public License for more details. #define clc_voicedata 8 #define clc_requestcvarvalue 9 #define clc_requestcvarvalue2 10 -#define clc_lastmsg 10 // end client messages +#define clc_lastmsg 11 // end client messages (11 is GoldSrc message) #define MAX_VISIBLE_PACKET_BITS 11 // 2048 visible entities per frame (hl1 has 256) #define MAX_VISIBLE_PACKET (1< -#include "common.h" -#include "eiface.h" -#include "sequence.h" - -sequenceCommandLine_s g_fileScopeDefaults; -sequenceCommandLine_s g_blockScopeDefaults; -sequenceEntry_s *g_sequenceList = NULL; -sentenceGroupEntry_s *g_sentenceGroupList = NULL; -qboolean g_sequenceParseFileIsGlobal; -unsigned int g_nonGlobalSentences = 0; -char g_sequenceParseFileName[MAX_STRING]; -int g_lineNum = 1; -char *g_scan = NULL; -char *g_lineScan = NULL; - -const sequenceCommandMapping_s g_sequenceCommandMappingTable[] = -{ - {SEQUENCE_COMMAND_PAUSE, "pause", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_TEXT, "text", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_SOUND, "sound", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_FIRETARGETS, "firetargets", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_KILLTARGETS, "killtargets", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_GOSUB, "gosub", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_SENTENCE, "sentence", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_REPEAT, "repeat", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_SETDEFAULTS, "setdefaults", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_MODIFIER, "modifier", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_POSTMODIFIER, "postmodifier", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_COMMAND_NOOP, "noop", SEQUENCE_TYPE_COMMAND}, - {SEQUENCE_MODIFIER_EFFECT, "effect", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_POSITION, "position", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_COLOR, "color", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_COLOR2, "color2", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_FADEIN, "fadein", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_FADEOUT, "fadeout", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_HOLDTIME, "holdtime", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_FXTIME, "fxtime", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_SPEAKER, "speaker", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_LISTENER, "listener", SEQUENCE_TYPE_MODIFIER}, - {SEQUENCE_MODIFIER_TEXTCHANNEL, "channel", SEQUENCE_TYPE_MODIFIER} -}; - -/* -============= -Sequence_GetCommandEnumForName - -============= -*/ -sequenceCommandEnum_e Sequence_GetCommandEnumForName( const char *commandName, sequenceCommandType_e type ) -{ - int i; - - for( i = 0; i < ARRAYSIZE( g_sequenceCommandMappingTable ); i++ ) - { - const sequenceCommandMapping_s *mapping = g_sequenceCommandMappingTable + i; - - if( mapping->commandType == type && !Q_stricmp( mapping->commandName, commandName ) ) - return mapping->commandEnum; - } - return SEQUENCE_COMMAND_ERROR; -} - -/* -============= -Sequence_ResetDefaults - -============= -*/ -void Sequence_ResetDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) -{ - if( !source ) - { - static client_textmessage_t defaultClientMessage = - { - 0, // effect - 255, 255, 255, 255, // rgba1 - 255, 255, 255, 255, // rgba2 - 0.5, 0.5, // xy - 0.2, 0.2, // fade-in/out - 1.6, // holdtime - 1.0, // fxtime - NULL, NULL // pName, pMessage - }; - - destination->clientMessage = defaultClientMessage; - destination->textChannel = 0; - destination->delay = 0; - destination->repeatCount = 0; - destination->nextCommandLine = NULL; - destination->soundFileName = NULL; - destination->speakerName = NULL; - destination->listenerName = NULL; - return; - } - - destination->clientMessage = source->clientMessage; - destination->clientMessage.pName = NULL; - destination->clientMessage.pMessage = NULL; - destination->textChannel = source->textChannel; - destination->delay = source->delay; - destination->repeatCount = source->repeatCount; - destination->nextCommandLine = NULL; - destination->soundFileName = NULL; - - Z_Free( destination->speakerName ); - destination->speakerName = copystring( source->speakerName ); - - Z_Free( destination->listenerName ); - destination->listenerName = copystring( source->listenerName ); -} - -/* -============= -Sequence_WriteDefaults - -============= -*/ -void Sequence_WriteDefaults( sequenceCommandLine_s *source, sequenceCommandLine_s *destination ) -{ - if( !destination ) - Con_Reportf( S_ERROR "Attempt to bake defaults into a non-existant command." ); - - if( !source ) - Con_Reportf( S_ERROR "Attempt to bake defaults from a non-existant command." ); - - if( source->modifierBitField & SEQUENCE_MODIFIER_EFFECT_BIT ) - { - destination->clientMessage.effect = source->clientMessage.effect; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_POSITION_BIT ) - { - destination->clientMessage.x = source->clientMessage.x; - destination->clientMessage.y = source->clientMessage.y; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_COLOR_BIT ) - { - destination->clientMessage.r1 = source->clientMessage.r1; - destination->clientMessage.g1 = source->clientMessage.g1; - destination->clientMessage.b1 = source->clientMessage.b1; - destination->clientMessage.a1 = source->clientMessage.a1; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_COLOR2_BIT ) - { - destination->clientMessage.r2 = source->clientMessage.r2; - destination->clientMessage.g2 = source->clientMessage.g2; - destination->clientMessage.b2 = source->clientMessage.b2; - destination->clientMessage.a2 = source->clientMessage.a2; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_FADEIN_BIT ) - { - destination->clientMessage.fadein = source->clientMessage.fadein; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_FADEOUT_BIT ) - { - destination->clientMessage.fadeout = source->clientMessage.fadeout; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_HOLDTIME_BIT ) - { - destination->clientMessage.holdtime = source->clientMessage.holdtime; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_FXTIME_BIT ) - { - destination->clientMessage.fxtime = source->clientMessage.fxtime; - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_SPEAKER_BIT ) - { - Z_Free( destination->speakerName ); - destination->speakerName = copystring( source->speakerName ); - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_LISTENER_BIT ) - { - Z_Free( destination->listenerName ); - destination->listenerName = copystring( source->listenerName ); - } - - if( source->modifierBitField & SEQUENCE_MODIFIER_TEXTCHANNEL_BIT ) - { - destination->textChannel = source->textChannel; - } -} - -/* -============= -Sequence_BakeDefaults - -============= -*/ -void Sequence_BakeDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) -{ - char *saveName, *saveMessage; - - if( !destination ) - Con_Reportf( S_ERROR "Attempt to bake defaults into a non-existant command." ); - - if( !source ) - Con_Reportf( S_ERROR "Attempt to bake defaults from a non-existant command." ); - - saveName= destination->clientMessage.pName; - saveMessage = destination->clientMessage.pMessage; - - destination->clientMessage = source->clientMessage; - - destination->clientMessage.pName = saveName; - destination->clientMessage.pMessage = saveMessage; - - destination->textChannel = source->textChannel; - - Z_Free( destination->speakerName ); - destination->speakerName = copystring( source->speakerName ); - - Z_Free( destination->listenerName ); - destination->listenerName = copystring( source->listenerName ); -} - -/* -============= -Sequence_SkipWhitespace - -============= -*/ -qboolean Sequence_SkipWhitespace( void ) -{ - qboolean newLine = false; - - for( ; isspace( *g_scan ); g_scan++ ) - { - if( *g_scan == '\n' ) - { - g_lineScan = g_scan + 1; - g_lineNum++; - - newLine = true; - } - } - - return newLine; -} - -/* -============= -Sequence_IsNameValueChar - -============= -*/ -qboolean Sequence_IsNameValueChar( char ch ) -{ - if( isalnum( ch ) ) - return true; - - switch( ch ) - { - case '.': - case '-': - case '_': - case '/': - case '\\': - return true; - } - - return false; -} - -/* -============= -Sequence_IsSymbol - -============= -*/ -qboolean Sequence_IsSymbol( char ch ) -{ - switch( ch ) - { - case '"': - case '#': - case '$': - case '%': - case ',': - case '=': - case '@': - case '{': - case '}': - return true; - } - - return false; -} - -/* -============= -Sequence_GetNameValueString - -============= -*/ -size_t Sequence_GetNameValueString( char *token, size_t len ) -{ - char *p; - - Sequence_SkipWhitespace( ); - - if( !Sequence_IsNameValueChar( *g_scan ) ) - { - if( *g_scan == '#' || *g_scan == '$' ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: cannot have more than one '%c' per line; '%c' must be at the beginning of the line ONLY\n", g_lineNum, g_sequenceParseFileName, *g_scan, *g_scan ); - else - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: expected name/value, found illegal character '%c'\n", g_lineNum, g_sequenceParseFileName, *g_scan ); - } - - for( p = token; Sequence_IsNameValueChar( *g_scan ) && len; p++, g_scan++, len-- ) - { - *p = *g_scan; - } - - *p = 0; - - return p - token; -} - -/* -============= -Sequence_GetSymbol - -============= -*/ -char Sequence_GetSymbol( void ) -{ - char ch; - - Sequence_SkipWhitespace( ); - - ch = *g_scan; - - if( ch ) - g_scan++; - - return ch; -} - -/* -============= -Sequence_ValidateNameValueString - -============= -*/ -void Sequence_ValidateNameValueString( char *token ) -{ - char *scan; - - for( scan = token; *scan; scan++ ) - { - if( !Sequence_IsNameValueChar( *scan ) ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: name/value string \"%s\" had illegal character '%c'\n", g_lineNum, g_sequenceParseFileName, token, *scan ); - } -} - -/* -============= -Sequence_GetToken - -============= -*/ -size_t Sequence_GetToken( char *token, size_t size ) -{ - Sequence_SkipWhitespace( ); - - if( Sequence_IsNameValueChar( *g_scan ) ) - { - return Sequence_GetNameValueString( token, size ); - } - - if( !Sequence_IsSymbol( *g_scan ) ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: expected token, found '%c' instead\n", g_lineNum, g_sequenceParseFileName, *g_scan ); - - token[0] = *g_scan++; - token[1] = 0; - g_scan++; - - return 1; // only one symbol has copied to token -} - -/* -============= -Sequence_GetLine - -============= -*/ -size_t Sequence_GetLine( char *line, int lineMaxLen ) -{ - int lineLen; - char *read; - char *write = line; - - Sequence_SkipWhitespace( ); - - read = Q_strchr( g_scan, '\n' ); - - if( !read ) - Con_Reportf( S_ERROR "Syntax Error on line %d of %s.seq: expected sentence definition or '}', found End-Of-File!\n", g_lineNum, g_sequenceParseFileName ); - - lineLen = read - g_scan; - - if( lineLen >= lineMaxLen ) - Con_Reportf( S_ERROR "Syntax Error on line %d of %s.seq: line was too long (was %d chars; max is %d chars)\n", g_lineNum, g_sequenceParseFileName, lineLen, lineMaxLen - 1 ); - - Q_strncpy( write, g_scan, lineLen ); - write[lineLen] = 0; - g_scan = read; - - return lineLen; -} - -/* -============= -Sequence_StripComments - -============= -*/ -void Sequence_StripComments( char *buffer, int *pBufSize ) -{ - char *eof = buffer + *pBufSize; - char *read = buffer; - char *write = buffer; - - for( ; read < eof; ) - { - if( !*read ) - break; - - if( *read == '/' ) - { - // skip one line comments // - if( read[1] == '/' ) - { - read += 2; - - while( *read ) - { - if( *read == '\n' ) - break; - - if( *read == '\r' ) - break; - - read++; - } - - continue; - } - - // skip multiline /* */ - if( read[1] == '*' ) - { - read += 2; - - while( *read && read[1] ) - { - if( *read == '*' && read[1] == '/' ) - { - read += 2; - break; - } - - if( *read == '\n' || *read == '\r' ) - *write++ = *read; - - read++; - } - - continue; - } - } - - *write++ = *read++; - } - - *write = 0; -} - -/* -============= -Sequence_ReadInt - -============= -*/ -int Sequence_ReadInt( void ) -{ - char str[MAX_STRING]; - - Sequence_SkipWhitespace( ); - Sequence_GetNameValueString( str, MAX_STRING ); - - return Q_atoi( str ); -} - -/* -============= -Sequence_ReadFloat - -============= -*/ -float Sequence_ReadFloat( void ) -{ - char str[MAX_STRING]; - - Sequence_SkipWhitespace( ); - Sequence_GetNameValueString( str, MAX_STRING ); - - return Q_atof( str ); -} - -/* -============= -Sequence_ReadFloat - -============= -*/ -void Sequence_ReadString( char **dest, char *string, size_t len ) -{ - Sequence_SkipWhitespace( ); - Sequence_GetNameValueString( string, len ); - - if( dest ) *dest = copystring( string ); -} - -/* -============= -Sequence_ReadQuotedString - -============= -*/ -void Sequence_ReadQuotedString( char **dest, char *str, size_t len ) -{ - char *write, ch; - - Sequence_SkipWhitespace( ); - - ch = Sequence_GetSymbol( ); - if( ch != '\"' ) - Con_Reportf( S_ERROR "Parsing error on or before line %d of %s.seq: expected quote (\"), found '%c' instead\n", g_lineNum, g_sequenceParseFileName, ch ); - - for( write = str; *g_scan && len; write++, g_scan++, len-- ) - { - if( *g_scan == '\"' ) - break; - - if( *g_scan == '\n' ) - g_lineNum++; - - *write = *g_scan; - } - - *write = 0; - g_scan++; - - if( dest ) *dest = copystring( str ); -} - -/* -============= -Sequence_ConfirmCarriageReturnOrSymbol - -============= -*/ -qboolean Sequence_ConfirmCarriageReturnOrSymbol( char symbol ) -{ - if( Sequence_SkipWhitespace( ) ) - return true; - return *g_scan == symbol; -} - - -/* -============= -Sequence_IsCommandAModifier - -============= -*/ -qboolean Sequence_IsCommandAModifier( sequenceCommandEnum_e commandEnum ) -{ - int i; - - for( i = 0; i < ARRAYSIZE( g_sequenceCommandMappingTable ); i++ ) - { - if( g_sequenceCommandMappingTable[i].commandEnum == commandEnum ) - return ( g_sequenceCommandMappingTable[i].commandType == SEQUENCE_TYPE_MODIFIER ); - } - - Con_Reportf( S_ERROR "Internal error caused by line %d of %s.seq: unknown command enum = %d\n", g_lineNum, g_sequenceParseFileName, commandEnum ); - return false; -} - -/* -============= -Sequence_ReadCommandData - -============= -*/ -void Sequence_ReadCommandData( sequenceCommandEnum_e commandEnum, sequenceCommandLine_s *defaults ) -{ - char temp[1024]; - - if( commandEnum >= SEQUENCE_MODIFIER_EFFECT && commandEnum <= SEQUENCE_MODIFIER_TEXTCHANNEL ) - defaults->modifierBitField |= BIT( SEQUENCE_MODIFIER_EFFECT - SEQUENCE_COMMAND_NOOP ); - - switch( commandEnum ) - { - case SEQUENCE_COMMAND_PAUSE: - defaults->delay = Sequence_ReadFloat( ); - break; - - case SEQUENCE_COMMAND_FIRETARGETS: - Sequence_ReadQuotedString( &defaults->fireTargetNames, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_KILLTARGETS: - Sequence_ReadQuotedString( &defaults->killTargetNames, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_TEXT: - Sequence_ReadQuotedString( &defaults->clientMessage.pMessage, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_SOUND: - Sequence_ReadString( &defaults->soundFileName, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_GOSUB: - Sequence_ReadString( &defaults->clientMessage.pName, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_SENTENCE: - Sequence_ReadString( &defaults->sentenceName, temp, sizeof( temp ) ); - break; - - case SEQUENCE_COMMAND_REPEAT: - defaults->repeatCount = Sequence_ReadInt( ); - break; - - case SEQUENCE_MODIFIER_EFFECT: - defaults->clientMessage.effect = Sequence_ReadInt( ); - break; - - case SEQUENCE_MODIFIER_POSITION: - defaults->clientMessage.x = Sequence_ReadFloat( ); - defaults->clientMessage.y = Sequence_ReadFloat( ); - break; - - case SEQUENCE_MODIFIER_COLOR: - defaults->clientMessage.r1 = Sequence_ReadInt( ); - defaults->clientMessage.g1 = Sequence_ReadInt( ); - defaults->clientMessage.b1 = Sequence_ReadInt( ); - defaults->clientMessage.a1 = 255; - break; - - case SEQUENCE_MODIFIER_COLOR2: - defaults->clientMessage.r2 = Sequence_ReadInt( ); - defaults->clientMessage.g2 = Sequence_ReadInt( ); - defaults->clientMessage.b2 = Sequence_ReadInt( ); - defaults->clientMessage.a2 = 255; - break; - - case SEQUENCE_MODIFIER_FADEIN: - defaults->clientMessage.fadein = Sequence_ReadFloat( ); - break; - - case SEQUENCE_MODIFIER_FADEOUT: - defaults->clientMessage.fadeout = Sequence_ReadFloat( ); - break; - - case SEQUENCE_MODIFIER_HOLDTIME: - defaults->clientMessage.holdtime = Sequence_ReadFloat( ); - break; - - case SEQUENCE_MODIFIER_FXTIME: - defaults->clientMessage.fxtime = Sequence_ReadFloat( ); - break; - - case SEQUENCE_MODIFIER_SPEAKER: - Sequence_ReadString( &defaults->speakerName, temp, sizeof( temp ) ); - break; - - case SEQUENCE_MODIFIER_LISTENER: - Sequence_ReadString( &defaults->listenerName, temp, sizeof( temp ) ); - break; - - case SEQUENCE_MODIFIER_TEXTCHANNEL: - defaults->textChannel = Sequence_ReadInt( ); - break; - - default: - Con_Reportf( S_ERROR "Internal error caused by line %d of %s.seq: unknown command enum = %d\n", g_lineNum, g_sequenceParseFileName, commandEnum ); - } -} - -/* -============= -Sequence_ParseModifier - -============= -*/ -char Sequence_ParseModifier( sequenceCommandLine_s *defaults ) -{ - char modifierName[MAX_STRING]; - char delimiter; - sequenceCommandEnum_e modifierEnum; - - Sequence_GetNameValueString( modifierName, MAX_STRING ); - modifierEnum = Sequence_GetCommandEnumForName( modifierName, SEQUENCE_TYPE_MODIFIER ); - - if( modifierEnum == SEQUENCE_COMMAND_ERROR ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: unknown modifier \"%s\"\n", g_lineNum, g_sequenceParseFileName, modifierName ); - - if( !Sequence_IsCommandAModifier( modifierEnum ) ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: \"%s\" is a #command, not a $modifier\n", g_lineNum, g_sequenceParseFileName, modifierName ); - - delimiter = Sequence_GetSymbol( ); - - if( delimiter != '=' ) - Con_Reportf( S_ERROR "Parsing error on or after line %d of %s.seq: after modifier \"%s\", expected '=', found '%c'\n", g_lineNum, g_sequenceParseFileName, modifierName, delimiter ); - - Sequence_ReadCommandData( modifierEnum, defaults ); - - if( !Sequence_ConfirmCarriageReturnOrSymbol( ',' ) ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: after value(s) for modifier \"%s\", expected ',' or End-Of-Line; found '%c'\n", g_lineNum, g_sequenceParseFileName, modifierName, *g_scan ); - - return Sequence_GetSymbol( ); -} - -/* -============= -Sequence_AddCommandLineToEntry - -============= -*/ -void Sequence_AddCommandLineToEntry( sequenceCommandLine_s *commandLine, sequenceEntry_s *entry ) -{ - sequenceCommandLine_s *scan; - - if( entry->firstCommand ) - { - for( scan = entry->firstCommand; scan->nextCommandLine; scan = scan->nextCommandLine ); - scan->nextCommandLine = commandLine; - } - else entry->firstCommand = commandLine; - - commandLine->nextCommandLine = NULL; -} - -/* -============= -Sequence_ParseModifierLine - -============= -*/ -char Sequence_ParseModifierLine( sequenceEntry_s *entry, sequenceCommandType_e modifierType ) -{ - sequenceCommandLine_s *newCommandLine; - char delimiter = ','; - - while( delimiter == ',' ) - { - switch( modifierType ) - { - case SEQUENCE_TYPE_COMMAND: - newCommandLine = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) ); - newCommandLine->commandType = SEQUENCE_COMMAND_MODIFIER; - Sequence_AddCommandLineToEntry( newCommandLine, entry ); - delimiter = Sequence_ParseModifier( newCommandLine ); - break; - - case SEQUENCE_TYPE_MODIFIER: - delimiter = Sequence_ParseModifier( &g_fileScopeDefaults ); - break; - } - } - - return delimiter; -} - -/* -============= -Sequence_ParseCommand - -============= -*/ -char Sequence_ParseCommand( sequenceCommandLine_s *newCommandLine ) -{ - char commandName[MAX_STRING], ch; - sequenceCommandEnum_e commandEnum; - sequenceCommandLine_s *modifierCommandLine; - - Sequence_GetNameValueString( commandName, MAX_STRING ); - commandEnum = Sequence_GetCommandEnumForName( commandName, SEQUENCE_TYPE_COMMAND ); - - if( commandEnum == SEQUENCE_COMMAND_ERROR ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: unknown command \"%s\"\n", g_lineNum, g_sequenceParseFileName, commandName ); - - if( Sequence_IsCommandAModifier( commandEnum ) ) - { - modifierCommandLine = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - memset( modifierCommandLine, 0, sizeof( sequenceCommandLine_s ) ); - modifierCommandLine->commandType = SEQUENCE_COMMAND_POSTMODIFIER; - - for( ; newCommandLine->nextCommandLine; newCommandLine = newCommandLine->nextCommandLine ); - - newCommandLine->nextCommandLine = modifierCommandLine; - newCommandLine = modifierCommandLine; - } - - ch = Sequence_GetSymbol( ); - if( ch != '=' ) - Con_Reportf( S_ERROR "Parsing error on or before line %d of %s.seq: after command \"%s\", expected '=', found '%c'\n", - g_lineNum, g_sequenceParseFileName, commandName, ch ); - - Sequence_ReadCommandData( commandEnum, newCommandLine ); - return Sequence_GetSymbol( ); -} - -/* -============= -Sequence_ParseCommandLine - -============= -*/ -char Sequence_ParseCommandLine( sequenceEntry_s *entry ) -{ - char symbol; - sequenceCommandLine_s *newCommandLine; - - newCommandLine = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) ); - - Sequence_ResetDefaults( newCommandLine, &g_blockScopeDefaults ); - Sequence_AddCommandLineToEntry( newCommandLine, entry ); - - symbol = Sequence_ParseCommand( newCommandLine ); - - while( symbol == ',' ) - { - symbol = Sequence_ParseCommand( newCommandLine ); - } - - return symbol; -} - -/* -============= -Sequence_ParseMacro - -============= -*/ -char Sequence_ParseMacro( sequenceEntry_s *entry ) -{ - char symbol; - sequenceCommandLine_s *newCommandLine; - - newCommandLine = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - memset( newCommandLine, 0, sizeof( sequenceCommandLine_s ) ); - - Sequence_ResetDefaults( newCommandLine, &g_blockScopeDefaults ); - Sequence_AddCommandLineToEntry( newCommandLine, entry ); - Sequence_ReadCommandData( SEQUENCE_COMMAND_GOSUB, newCommandLine ); - - symbol = Sequence_GetSymbol( ); - - while( symbol == ',' ) - { - symbol = Sequence_ParseCommand( newCommandLine ); - } - - return symbol; -} - -/* -============= -Sequence_ParseLine - -============= -*/ -char Sequence_ParseLine( char start, sequenceEntry_s *entry ) -{ - char end = '\0'; - - switch( start ) - { - case '#': - end = Sequence_ParseCommandLine( entry ); - break; - - case '$': - end = Sequence_ParseModifierLine( entry, SEQUENCE_TYPE_MODIFIER ); - break; - - case '@': - end = Sequence_ParseMacro( entry ); - break; - - default: - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: line must begin with either '#' (command) or '$' (modifier); found '%c'\n", g_lineNum, g_sequenceParseFileName, start ); - } - - return end; -} - -/* -============= -Sequence_CalcEntryDuration - -============= -*/ -float Sequence_CalcEntryDuration( sequenceEntry_s *entry ) -{ - float duration; - sequenceCommandLine_s *cmd; - - duration = 0; - - for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine ) - duration += cmd->delay; - - return duration; -} - -/* -============= -Sequence_DoesEntryContainInfiniteLoop - -============= -*/ -qboolean Sequence_DoesEntryContainInfiniteLoop( sequenceEntry_s *entry ) -{ - sequenceCommandLine_s *cmd; - - for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine ) - { - if( cmd->repeatCount < 0 ) - return true; - } - - return false; -} - -/* -============= -Sequence_IsEntrySafe - -============= -*/ -qboolean Sequence_IsEntrySafe( sequenceEntry_s *entry ) -{ - float duration; - sequenceCommandLine_s *cmd; - - duration = 0; - - for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine ) - { - duration += cmd->delay; - - if( cmd->repeatCount < 0 ) - { - if( duration <= 0 ) - return false; - } - } - - return true; -} - -/* -============= -Sequence_CreateDefaultsCommand - -============= -*/ -void Sequence_CreateDefaultsCommand( sequenceEntry_s *entry ) -{ - sequenceCommandLine_s *cmd; - - cmd = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - memset( cmd, 0, sizeof( sequenceCommandLine_s ) ); - - Sequence_ResetDefaults( cmd, &g_fileScopeDefaults ); - cmd->commandType = SEQUENCE_COMMAND_SETDEFAULTS; - cmd->modifierBitField = SEQUENCE_MODIFIER_EFFECT_BIT | - SEQUENCE_MODIFIER_POSITION_BIT | - SEQUENCE_MODIFIER_COLOR_BIT | - SEQUENCE_MODIFIER_COLOR2_BIT | - SEQUENCE_MODIFIER_FADEIN_BIT | - SEQUENCE_MODIFIER_FADEOUT_BIT | - SEQUENCE_MODIFIER_HOLDTIME_BIT | - SEQUENCE_MODIFIER_FXTIME_BIT; - - Sequence_AddCommandLineToEntry( cmd, entry ); -} - - -/* -============= -Sequence_ParseEntry - -============= -*/ -char Sequence_ParseEntry( void ) -{ - char symbol; - char token[MAX_STRING]; - sequenceEntry_s *entry; - - Sequence_GetNameValueString( token, MAX_STRING ); - symbol = Sequence_GetSymbol( ); - - if( symbol != '{' ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: expected '{' to start a\n new entry block; found '%c' instead!", g_lineNum, g_sequenceParseFileName, symbol ); - - entry = Z_Malloc( sizeof( sequenceEntry_s ) ); - Sequence_ResetDefaults( &g_blockScopeDefaults, &g_fileScopeDefaults ); - entry->entryName = copystring( token ); - entry->fileName = copystring( g_sequenceParseFileName ); - entry->isGlobal = g_sequenceParseFileIsGlobal; - entry->firstCommand = NULL; - Sequence_CreateDefaultsCommand( entry ); - - symbol = Sequence_GetSymbol( ); - - while( symbol != '}' ) - { - symbol = Sequence_ParseLine( symbol, entry ); - } - - if( !Sequence_IsEntrySafe( entry ) ) - Con_Reportf( S_ERROR "Logic error in file %s.seq before line %d: execution of entry \"%%%s\" would cause an infinite loop!", g_sequenceParseFileName, g_lineNum, entry->entryName ); - - entry->nextEntry = g_sequenceList; - g_sequenceList = entry; - - return Sequence_GetSymbol( ); -} - -/* -============= -Sequence_FindSentenceGroup - -============= -*/ -sentenceGroupEntry_s *Sequence_FindSentenceGroup( const char *groupName ) -{ - sentenceGroupEntry_s *groupEntry; - - for( groupEntry = g_sentenceGroupList; groupEntry; groupEntry = groupEntry->nextEntry ) - { - if( !Q_stricmp( groupEntry->groupName, groupName ) ) - return groupEntry; - } - - return NULL; -} - -/* -============= -Sequence_GetSentenceByIndex - -============= -*/ -sentenceEntry_s *Sequence_GetSentenceByIndex( unsigned int index ) -{ - sentenceEntry_s *sentenceEntry; - sentenceGroupEntry_s *groupEntry; - unsigned int sentenceCount=0; - - for( groupEntry = g_sentenceGroupList; groupEntry; groupEntry = groupEntry->nextEntry ) - { - sentenceCount += groupEntry->numSentences; - - if( index < sentenceCount ) - { - for( sentenceEntry = groupEntry->firstSentence; sentenceEntry; sentenceEntry = sentenceEntry->nextEntry ) - { - if( sentenceEntry->index == index ) - return sentenceEntry; - } - } - } - - return NULL; -} - - -/* -============= -Sequence_PickSentence - -============= -*/ -sentenceEntry_s *Sequence_PickSentence( const char *groupName, int pickMethod, int *picked ) -{ - sentenceEntry_s *sentenceEntry; - sentenceGroupEntry_s *groupEntry; - unsigned int pickedIdx; - unsigned int entryIdx; - - groupEntry = Sequence_FindSentenceGroup( groupName ); - - if( groupEntry ) - { - pickedIdx = COM_RandomLong( 0, groupEntry->numSentences - 1 ); - sentenceEntry = groupEntry->firstSentence; - - for( entryIdx = pickedIdx; entryIdx; entryIdx-- ) - sentenceEntry = sentenceEntry->nextEntry; - } - else - { - pickedIdx = 0; - sentenceEntry = NULL; - } - - if( picked ) - *picked = pickedIdx; - - return sentenceEntry; -} - -/* -============= -Sequence_AddSentenceGroup - -============= -*/ -sentenceGroupEntry_s *Sequence_AddSentenceGroup( char *groupName ) -{ - sentenceGroupEntry_s *entry, *last; - - entry = Z_Malloc( sizeof( sentenceGroupEntry_s ) ); - entry->numSentences = 0; - entry->firstSentence = NULL; - entry->nextEntry = NULL; - entry->groupName = copystring( groupName ); - - if( g_sentenceGroupList ) - { - for( last = g_sentenceGroupList; last->nextEntry; last = last->nextEntry ); - last->nextEntry = entry; - } - else - { - g_sentenceGroupList = entry; - } - - return entry; -} - -/* -============= -Sequence_AddSentenceToGroup - -============= -*/ -void Sequence_AddSentenceToGroup( char *groupName, char *data ) -{ - sentenceEntry_s *entry, *last; - sentenceGroupEntry_s *group; - - group = Sequence_FindSentenceGroup( groupName ); - - if( !group ) - { - group = Sequence_AddSentenceGroup( groupName ); - - if( !group ) - Con_Reportf( S_ERROR "Unable to allocate sentence group %s at line %d in file %s.seq", groupName, g_lineNum, g_sequenceParseFileName ); - } - - entry = Z_Malloc( sizeof( sentenceEntry_s ) ); - entry->nextEntry = NULL; - entry->data = copystring( data ); - entry->index = g_nonGlobalSentences; - entry->isGlobal = g_sequenceParseFileIsGlobal; - - group->numSentences++; - g_nonGlobalSentences++; - - if( group->firstSentence ) - { - for( last = group->firstSentence; last->nextEntry; last = last->nextEntry ); - - last->nextEntry = entry; - } - else - { - group->firstSentence = entry; - } -} - -/* -============= -Sequence_ParseSentenceLine - -============= -*/ -qboolean Sequence_ParseSentenceLine( void ) -{ - char data[1024]; - char fullgroup[64]; - char groupName[64]; - char *c; - int lastCharacterPos; - size_t len; - - len = Sequence_GetToken( fullgroup, sizeof( fullgroup ) ); - - if( *fullgroup == '}' ) - return true; - - c = fullgroup + len; - - while( !isalpha( *c ) && *c != '_' ) - c--; - - c += 1; - - if( *c ) - *c = 0; - - Q_strncpy( groupName, fullgroup, sizeof( groupName )); - - len = Sequence_GetLine( data, sizeof( data ) ); - lastCharacterPos = len - 1; - - if( data[lastCharacterPos] == '\n' || data[lastCharacterPos] == '\r' ) - data[lastCharacterPos] = 0; - - Sequence_AddSentenceToGroup( groupName, data ); - return false; -} - -/* -============== -Sequence_ParseSentenceBlock - -============== -*/ -char Sequence_ParseSentenceBlock( void ) -{ - qboolean end = false; - char ch = Sequence_GetSymbol( ); - if( ch != '{' ) - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: expected '{' to start a\n new sentence block; found '%c' instead!", g_lineNum, g_sequenceParseFileName, ch ); - - while( !end ) - { - end = Sequence_ParseSentenceLine( ); - } - - return Sequence_GetSymbol( ); -} - -/* -============== -Sequence_ParseGlobalDataBlock - -============== -*/ -char Sequence_ParseGlobalDataBlock( void ) -{ - char token[MAX_STRING]; - - Sequence_GetNameValueString( token, MAX_STRING ); - - if( Q_stricmp( token, "Sentences" ) ) - Con_Reportf( S_ERROR "Syntax error in file %s.seq on line %d: found global data block symbol '!' with unknown data type \"%s\"", g_sequenceParseFileName, g_lineNum, token ); - - return Sequence_ParseSentenceBlock( ); -} - -/* -============== -Sequence_GetEntryForName - -============== -*/ -sequenceEntry_s *Sequence_GetEntryForName( const char *entryName ) -{ - sequenceEntry_s *scan; - - for( scan = g_sequenceList; scan; scan = scan->nextEntry ) - { - if( !Q_stricmp( entryName, scan->entryName ) ) - return scan; - } - - return NULL; -} - -/* -============== -Sequence_CopyCommand - -============== -*/ -sequenceCommandLine_s *Sequence_CopyCommand( sequenceCommandLine_s *commandOrig ) -{ - sequenceCommandLine_s *commandCopy; - - commandCopy = Z_Malloc( sizeof( sequenceCommandLine_s ) ); - - commandCopy->commandType = commandOrig->commandType; - commandCopy->clientMessage = commandOrig->clientMessage; - commandCopy->clientMessage.pMessage = copystring( commandOrig->clientMessage.pMessage ); - commandCopy->clientMessage.pName = copystring( commandOrig->clientMessage.pName ); - commandCopy->speakerName = copystring( commandOrig->speakerName ); - commandCopy->listenerName = copystring( commandOrig->listenerName ); - commandCopy->soundFileName = copystring( commandOrig->soundFileName ); - commandCopy->sentenceName = copystring( commandOrig->sentenceName ); - commandCopy->fireTargetNames = copystring( commandOrig->fireTargetNames ); - commandCopy->killTargetNames = copystring( commandOrig->killTargetNames ); - commandCopy->delay = commandOrig->delay; - commandCopy->repeatCount = commandOrig->repeatCount; - commandCopy->textChannel = commandOrig->textChannel; - commandCopy->modifierBitField = commandOrig->modifierBitField; - commandCopy->nextCommandLine = NULL; - - return commandCopy; -} - -/* -============== -Sequence_CopyCommandList - -============== -*/ -sequenceCommandLine_s *Sequence_CopyCommandList( sequenceCommandLine_s *list ) -{ - sequenceCommandLine_s *scan, *copy, *new, *prev; - - copy = NULL; - prev = NULL; - - for( scan = list; scan; scan = scan->nextCommandLine ) - { - if( scan->commandType != SEQUENCE_COMMAND_SETDEFAULTS ) - { - new = Sequence_CopyCommand( scan ); - - if( prev ) - { - prev->nextCommandLine = new; - prev = new; - } - else - { - prev = new; - copy = new; - } - } - } - - return copy; -} - -/* -============== -Sequence_ExpandGosubsForEntry - -============== -*/ -qboolean Sequence_ExpandGosubsForEntry( sequenceEntry_s *entry ) -{ - sequenceCommandLine_s *cmd, *copyList, *scan; - sequenceEntry_s *gosubEntry; - qboolean foundGosubs = false; - - for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine ) - { - if( !cmd->clientMessage.pName ) - continue; - - if( !Q_stricmp( cmd->clientMessage.pName, entry->entryName ) ) - Con_Reportf( S_ERROR "Error in %s.seq: entry \"%s\" gosubs itself!\n", entry->fileName, entry->entryName ); - - gosubEntry = Sequence_GetEntryForName( cmd->clientMessage.pName ); - - if( !gosubEntry ) - Con_Reportf( S_ERROR "Error in %s.seq: Gosub in entry \"%s\" specified unknown entry \"%s\"\n", entry->fileName, entry->entryName, cmd->clientMessage.pName ); - - foundGosubs = true; - copyList = Sequence_CopyCommandList( gosubEntry->firstCommand ); - - if( copyList ) - { - for( scan = copyList->nextCommandLine; scan; scan = scan->nextCommandLine ); - - scan->nextCommandLine = cmd->nextCommandLine; - - Z_Free( cmd->clientMessage.pName ); - cmd->clientMessage.pName = NULL; - cmd = scan; - } - else - { - Z_Free( cmd->clientMessage.pName ); - cmd->clientMessage.pName = NULL; - } - } - - return !foundGosubs; -} - -/* -============== -Sequence_ExpandAllGosubs - -============== -*/ -void Sequence_ExpandAllGosubs( void ) -{ - sequenceEntry_s *scan; - qboolean isComplete = true; - - while( !isComplete ) - { - for( scan = g_sequenceList; scan; scan = scan->nextEntry ) - { - isComplete = Sequence_ExpandGosubsForEntry( scan ); - } - } -} - -/* -============== -Sequence_FlattenEntry - -============== -*/ -void Sequence_FlattenEntry( sequenceEntry_s *entry ) -{ - sequenceCommandLine_s *cmd, *last = NULL; - - for( cmd = entry->firstCommand; cmd; cmd = cmd->nextCommandLine ) - { - switch( cmd->commandType ) - { - case SEQUENCE_COMMAND_SETDEFAULTS: - Sequence_WriteDefaults( cmd, &g_blockScopeDefaults ); - cmd->commandType = SEQUENCE_COMMAND_NOOP; - break; - - case SEQUENCE_COMMAND_MODIFIER: - Sequence_WriteDefaults( cmd, &g_blockScopeDefaults ); - break; - - case SEQUENCE_COMMAND_POSTMODIFIER: - Sequence_WriteDefaults( cmd, last ); - break; - - default: - Sequence_BakeDefaults( cmd, &g_blockScopeDefaults ); - last = cmd; - } - } -} - -/* -============== -Sequence_FlattenAllEntries - -============== -*/ -void Sequence_FlattenAllEntries( void ) -{ - sequenceEntry_s *entry; - - for( entry = g_sequenceList; entry; entry = entry->nextEntry ) - Sequence_FlattenEntry( entry ); -} - -/* -============== -Sequence_ParseBuffer - -============== -*/ -static void Sequence_ParseBuffer( char *buffer, int bufferSize ) -{ - char symbol; - - g_lineNum = 1; - g_scan = buffer; - g_lineScan = g_scan; - - Sequence_StripComments( buffer, &bufferSize ); - Sequence_ResetDefaults( &g_fileScopeDefaults, NULL ); - - symbol = Sequence_GetSymbol( ); - - while( symbol ) - { - switch( symbol ) - { - case '$': - do - symbol = Sequence_ParseModifier( &g_fileScopeDefaults ); - while( symbol == ',' ); - break; - - case '%': - symbol = Sequence_ParseEntry( ); - break; - - case '!': - symbol = Sequence_ParseGlobalDataBlock( ); - break; - - default: - Con_Reportf( S_ERROR "Parsing error on line %d of %s.seq: At file scope, lines must begin with '$' (modifier) or '%%' (entry block) or '!' (sentence / global data block); found '%c'\n", g_lineNum, g_sequenceParseFileName, symbol ); - } - } - - Sequence_ExpandAllGosubs( ); - Sequence_FlattenAllEntries( ); -} - -/* -============== -Sequence_ParseFile - -============== -*/ -void Sequence_ParseFile( const char *fileName, qboolean isGlobal ) -{ - byte *buffer; - fs_offset_t bufSize = 0; - - Q_strcpy( g_sequenceParseFileName, fileName ); - g_sequenceParseFileIsGlobal = isGlobal; - - buffer = FS_LoadFile( va("sequences/%s.seq", fileName ), &bufSize, true ); - - if( !buffer ) - return; - - Con_Reportf( "reading sequence file: %s\n", fileName ); - - Sequence_ParseBuffer( (char *)buffer, bufSize ); - - Mem_Free( buffer ); -} - -/* -============== -Sequence_Init - -============== -*/ -void Sequence_Init( void ) -{ - Sequence_ParseFile( "global", true ); -} - -/* -============== -SequenceGet - -============== -*/ -sequenceEntry_s *Sequence_Get( const char *fileName, const char *entryName ) -{ - sequenceEntry_s *scan; - - for( scan = g_sequenceList; scan; scan = scan->nextEntry ) - { - if( ( !fileName || !Q_stricmp( fileName, scan->fileName ) ) && // a1ba: add filename check, even if originally it is ignored - !Q_stricmp( entryName, scan->entryName ) ) - return scan; - } - - return NULL; -} - -/* -============== -Sequence_FreeCommand - -============== -*/ -void Sequence_FreeCommand( sequenceCommandLine_s *kill ) -{ - Z_Free( kill->fireTargetNames ); - Z_Free( kill->speakerName ); - Z_Free( kill->listenerName ); - Z_Free( kill->soundFileName ); - Z_Free( kill->sentenceName ); - Z_Free( kill->clientMessage.pName ); - Z_Free( kill->clientMessage.pMessage ); -} - -/* -============== -Sequence_FreeEntry - -============== -*/ -void Sequence_FreeEntry( sequenceEntry_s *kill ) -{ - sequenceCommandLine_s *dead; - - Z_Free( kill->entryName ); - Z_Free( kill->fileName ); - - for( dead = kill->firstCommand; dead; dead = dead->nextCommandLine ) - { - kill->firstCommand = dead->nextCommandLine; - Sequence_FreeCommand( dead ); - } - - Z_Free( kill ); -} - -/* -============== -Sequence_FreeSentence - -============== -*/ -void Sequence_FreeSentence( sentenceEntry_s *sentenceEntry ) -{ - Z_Free( sentenceEntry->data ); - Z_Free( sentenceEntry ); -} - -/* -============== -Sequence_FreeSentenceGroup - -============== -*/ -void Sequence_FreeSentenceGroup( sentenceGroupEntry_s *groupEntry ) -{ - Z_Free( groupEntry->groupName ); - Z_Free( groupEntry ); -} - -/* -============== -Sequence_FreeSentenceGroupEntries - -============== -*/ -void Sequence_FreeSentenceGroupEntries( sentenceGroupEntry_s *groupEntry, qboolean purgeGlobals ) -{ - sentenceEntry_s *sentenceEntry; - sentenceEntry_s *deadSentence; - sentenceEntry_s *prevSentence; - - sentenceEntry = groupEntry->firstSentence; - prevSentence = NULL; - - while( sentenceEntry ) - { - if( !sentenceEntry->isGlobal || purgeGlobals ) - { - if( prevSentence ) - prevSentence->nextEntry = sentenceEntry->nextEntry; - else - groupEntry->firstSentence = sentenceEntry->nextEntry; - - groupEntry->numSentences--; - g_nonGlobalSentences--; - - deadSentence = sentenceEntry; - sentenceEntry = sentenceEntry->nextEntry; - - Sequence_FreeSentence( deadSentence ); - } - else - { - prevSentence = sentenceEntry; - sentenceEntry = sentenceEntry->nextEntry; - } - } -} - -/* -============== -Sequence_PurgeEntries - -============== -*/ -void Sequence_PurgeEntries( qboolean purgeGlobals ) -{ - sequenceEntry_s *scan; - sequenceEntry_s *dead; - sequenceEntry_s *prev; - sentenceGroupEntry_s *groupEntry; - sentenceGroupEntry_s *deadGroup; - sentenceGroupEntry_s *prevGroup; - - dead = NULL; - prev = NULL; - - for( scan = g_sequenceList; scan; ) - { - if( !scan->isGlobal || purgeGlobals ) - { - if( prev ) - prev->nextEntry = scan->nextEntry; - else - g_sequenceList = scan->nextEntry; - - dead = scan; - scan = scan->nextEntry; - Sequence_FreeEntry( dead ); - } - else - { - prev = scan; - scan = scan->nextEntry; - } - } - - groupEntry = g_sentenceGroupList; - prevGroup = NULL; - - while( groupEntry ) - { - Sequence_FreeSentenceGroupEntries( groupEntry, purgeGlobals ); - - if( groupEntry->numSentences ) - { - prevGroup = groupEntry; - groupEntry = groupEntry->nextEntry; - } - else - { - if( prevGroup ) - prevGroup->nextEntry = groupEntry->nextEntry; - else - g_sentenceGroupList = groupEntry->nextEntry; - - deadGroup = groupEntry; - groupEntry = groupEntry->nextEntry; - Sequence_FreeSentenceGroup( deadGroup ); - } - } -} - -/* -============== -Sequence_OnLevelLoad - -============== -*/ -void Sequence_OnLevelLoad( const char *mapName ) -{ - Sequence_PurgeEntries( false ); - Sequence_ParseFile( mapName, false ); -} diff --git a/engine/common/soundlib/snd_mp3.c b/engine/common/soundlib/snd_mp3.c deleted file mode 100644 index 4b66664d..00000000 --- a/engine/common/soundlib/snd_mp3.c +++ /dev/null @@ -1,263 +0,0 @@ -/* -snd_mp3.c - mp3 format loading and streaming -Copyright (C) 2010 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "soundlib.h" -#include "libmpg/libmpg.h" - -/* -================================================================= - - MPEG decompression - -================================================================= -*/ -qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize ) -{ - void *mpeg; - size_t pos = 0; - size_t bytesWrite = 0; - byte out[OUTBUF_SIZE]; - size_t outsize, padsize; - int ret; - wavinfo_t sc; - - // load the file - if( !buffer || filesize < FRAME_SIZE ) - return false; - - // couldn't create decoder - if(( mpeg = create_decoder( &ret )) == NULL ) - return false; - - if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg )); - - // trying to read header - if( !feed_mpeg_header( mpeg, buffer, FRAME_SIZE, filesize, &sc )) - { - Con_DPrintf( S_ERROR "Sound_LoadMPG: failed to load (%s): %s\n", name, get_error( mpeg )); - close_decoder( mpeg ); - return false; - } - - sound.channels = sc.channels; - sound.rate = sc.rate; - sound.width = 2; // always 16-bit PCM - sound.loopstart = -1; - sound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes - padsize = sound.size % FRAME_SIZE; - pos += FRAME_SIZE; // evaluate pos - - if( !sound.size ) - { - // bad mpeg file ? - Con_DPrintf( S_ERROR "Sound_LoadMPG: (%s) is probably corrupted\n", name ); - close_decoder( mpeg ); - return false; - } - - // add sentinel make sure we not overrun - sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size + padsize ); - sound.type = WF_PCMDATA; - - // decompress mpg into pcm wav format - while( bytesWrite < sound.size ) - { - int size; - - if( feed_mpeg_stream( mpeg, NULL, 0, out, &outsize ) != MP3_OK && outsize <= 0 ) - { - const byte *data = buffer + pos; - int bufsize; - - // if there are no bytes remainig so we can decompress the new frame - if( pos + FRAME_SIZE > filesize ) - bufsize = ( filesize - pos ); - else bufsize = FRAME_SIZE; - pos += bufsize; - - if( feed_mpeg_stream( mpeg, data, bufsize, out, &outsize ) != MP3_OK ) - break; // there was end of the stream - } - - if( bytesWrite + outsize > sound.size ) - size = ( sound.size - bytesWrite ); - else size = outsize; - - memcpy( &sound.wav[bytesWrite], out, size ); - bytesWrite += size; - } - - sound.samples = bytesWrite / ( sound.width * sound.channels ); - close_decoder( mpeg ); - - return true; -} - -/* -================= -Stream_OpenMPG -================= -*/ -stream_t *Stream_OpenMPG( const char *filename ) -{ - stream_t *stream; - void *mpeg; - file_t *file; - int ret; - wavinfo_t sc; - - file = FS_Open( filename, "rb", false ); - if( !file ) return NULL; - - // at this point we have valid stream - stream = Mem_Calloc( host.soundpool, sizeof( stream_t )); - stream->file = file; - stream->pos = 0; - - // couldn't create decoder - if(( mpeg = create_decoder( &ret )) == NULL ) - { - Con_DPrintf( S_ERROR "Stream_OpenMPG: couldn't create decoder: %s\n", get_error( mpeg ) ); - Mem_Free( stream ); - FS_Close( file ); - return NULL; - } - - if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg )); - - // trying to open stream and read header - if( !open_mpeg_stream( mpeg, file, (void*)FS_Read, (void*)FS_Seek, &sc )) - { - Con_DPrintf( S_ERROR "Stream_OpenMPG: failed to load (%s): %s\n", filename, get_error( mpeg )); - close_decoder( mpeg ); - Mem_Free( stream ); - FS_Close( file ); - - return NULL; - } - - stream->buffsize = 0; // how many samples left from previous frame - stream->channels = sc.channels; - stream->rate = sc.rate; - stream->width = 2; // always 16 bit - stream->ptr = mpeg; - stream->type = WF_MPGDATA; - - return stream; -} - -/* -================= -Stream_ReadMPG - -assume stream is valid -================= -*/ -int Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer ) -{ - // buffer handling - int bytesWritten = 0; - void *mpg; - - mpg = stream->ptr; - - while( 1 ) - { - byte *data; - int outsize; - - if( !stream->buffsize ) - { - if( read_mpeg_stream( mpg, (byte*)stream->temp, &stream->pos ) != MP3_OK ) - break; // there was end of the stream - } - - // check remaining size - if( bytesWritten + stream->pos > needBytes ) - outsize = ( needBytes - bytesWritten ); - else outsize = stream->pos; - - // copy raw sample to output buffer - data = (byte *)buffer + bytesWritten; - memcpy( data, &stream->temp[stream->buffsize], outsize ); - bytesWritten += outsize; - stream->pos -= outsize; - stream->buffsize += outsize; - - // continue from this sample on a next call - if( bytesWritten >= needBytes ) - return bytesWritten; - - stream->buffsize = 0; // no bytes remaining - } - - return 0; -} - -/* -================= -Stream_SetPosMPG - -assume stream is valid -================= -*/ -int Stream_SetPosMPG( stream_t *stream, int newpos ) -{ - if( set_stream_pos( stream->ptr, newpos ) != -1 ) - { - // flush any previous data - stream->buffsize = 0; - return true; - } - - // failed to seek for some reasons - return false; -} - -/* -================= -Stream_GetPosMPG - -assume stream is valid -================= -*/ -int Stream_GetPosMPG( stream_t *stream ) -{ - return get_stream_pos( stream->ptr ); -} - -/* -================= -Stream_FreeMPG - -assume stream is valid -================= -*/ -void Stream_FreeMPG( stream_t *stream ) -{ - if( stream->ptr ) - { - close_decoder( stream->ptr ); - stream->ptr = NULL; - } - - if( stream->file ) - { - FS_Close( stream->file ); - stream->file = NULL; - } - - Mem_Free( stream ); -} diff --git a/engine/common/soundlib/snd_utils.c b/engine/common/soundlib/snd_utils.c index 98626066..355a7093 100644 --- a/engine/common/soundlib/snd_utils.c +++ b/engine/common/soundlib/snd_utils.c @@ -15,6 +15,9 @@ GNU General Public License for more details. #include "soundlib.h" +// global sound variables +sndlib_t sound; + /* ============================================================================= @@ -22,19 +25,20 @@ GNU General Public License for more details. ============================================================================= */ -// stub -static const loadwavfmt_t load_null[] = -{ -{ NULL, NULL, NULL } -}; - static const loadwavfmt_t load_game[] = { -{ DEFAULT_SOUNDPATH "%s%s.%s", "wav", Sound_LoadWAV }, -{ "%s%s.%s", "wav", Sound_LoadWAV }, -{ DEFAULT_SOUNDPATH "%s%s.%s", "mp3", Sound_LoadMPG }, -{ "%s%s.%s", "mp3", Sound_LoadMPG }, -{ NULL, NULL, NULL } +#ifndef XASH_DEDICATED +{ "wav", Sound_LoadWAV }, +{ "mp3", Sound_LoadMPG }, +{ "ogg", Sound_LoadOggVorbis }, +{ "opus", Sound_LoadOggOpus }, +#else // we only need extensions +{ "wav" }, +{ "mp3" }, +{ "ogg" }, +{ "opus" }, +#endif +{ NULL }, }; /* @@ -44,17 +48,20 @@ static const loadwavfmt_t load_game[] = ============================================================================= */ -// stub -static const streamfmt_t stream_null[] = -{ -{ NULL, NULL, NULL, NULL, NULL, NULL, NULL } -}; - static const streamfmt_t stream_game[] = { -{ "%s%s.%s", "mp3", Stream_OpenMPG, Stream_ReadMPG, Stream_SetPosMPG, Stream_GetPosMPG, Stream_FreeMPG }, -{ "%s%s.%s", "wav", Stream_OpenWAV, Stream_ReadWAV, Stream_SetPosWAV, Stream_GetPosWAV, Stream_FreeWAV }, -{ NULL, NULL, NULL, NULL, NULL, NULL, NULL } +#ifndef XASH_DEDICATED +{ "mp3", Stream_OpenMPG, Stream_ReadMPG, Stream_SetPosMPG, Stream_GetPosMPG, Stream_FreeMPG }, +{ "wav", Stream_OpenWAV, Stream_ReadWAV, Stream_SetPosWAV, Stream_GetPosWAV, Stream_FreeWAV }, +{ "ogg", Stream_OpenOggVorbis, Stream_ReadOggVorbis, Stream_SetPosOggVorbis, Stream_GetPosOggVorbis, Stream_FreeOggVorbis }, +{ "opus", Stream_OpenOggOpus, Stream_ReadOggOpus, Stream_SetPosOggOpus, Stream_GetPosOggOpus, Stream_FreeOggOpus }, +#else // we only need extensions +{ "mp3" }, +{ "wav" }, +{ "ogg" }, +{ "opus" }, +#endif +{ NULL }, }; void Sound_Init( void ) @@ -62,18 +69,10 @@ void Sound_Init( void ) // init pools host.soundpool = Mem_AllocPool( "SoundLib Pool" ); - // install image formats (can be re-install later by Sound_Setup) - switch( host.type ) - { - case HOST_NORMAL: - sound.loadformats = load_game; - sound.streamformat = stream_game; - break; - default: // all other instances not using soundlib or will be reinstalling later - sound.loadformats = load_null; - sound.streamformat = stream_null; - break; - } + // install sound formats + sound.loadformats = load_game; + sound.streamformat = stream_game; + sound.tempbuffer = NULL; } @@ -83,24 +82,18 @@ void Sound_Shutdown( void ) Mem_FreePool( &host.soundpool ); } -byte *Sound_Copy( size_t size ) -{ - byte *out; - - out = Mem_Malloc( host.soundpool, size ); - memcpy( out, sound.tempbuffer, size ); - - return out; -} - uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath ) { + string name; file_t *f; wavehdr_t wav; size_t filesize; uint msecs; - f = FS_Open( filepath, "rb", false ); + Q_strncpy( name, filepath, sizeof( name )); + COM_FixSlashes( name ); + + f = FS_Open( name, "rb", false ); if( !f ) return 0; @@ -126,167 +119,341 @@ uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath ) return msecs; } -#define drint( v ) (int)( v + 0.5 ) +#define SOUND_FORMATCONVERT_BOILERPLATE( resamplemacro ) \ + if( inwidth == 1 ) \ + { \ + const int8_t *data = (const int8_t *)sc->buffer; \ + if( outwidth == 1 ) \ + { \ + int8_t *outdata = (int8_t *)sound.tempbuffer; \ + resamplemacro( 1 ) \ + } \ + else if( outwidth == 2 ) \ + { \ + int16_t *outdata = (int16_t *)sound.tempbuffer; \ + resamplemacro( 256 ) \ + } \ + else \ + return false; \ + } \ + else if( inwidth == 2 ) \ + { \ + const int16_t *data = (const int16_t *)sc->buffer; \ + if( outwidth == 1 ) \ + { \ + int8_t *outdata = (int8_t *)sound.tempbuffer; \ + resamplemacro( 1 / 256.0 ) \ + } \ + else if( outwidth == 2 ) \ + { \ + int16_t *outdata = (int16_t *)sound.tempbuffer; \ + resamplemacro( 1 ) \ + } \ + else \ + return false; \ + } \ + else \ + return false; \ + + +#define SOUND_CONVERTNORESAMPLE_BOILERPLATE( multiplier ) \ + if( inchannels == 1 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount * outchannels; i++ ) \ + outdata[i] = data[i] * ( multiplier ); \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + outdata[i * 2 + 0] = data[i] * ( multiplier ); \ + outdata[i * 2 + 1] = data[i] * ( multiplier ); \ + } \ + } \ + else \ + return false; \ + } \ + else if( inchannels == 2 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + outdata[i] = ( data[i * 2 + 0] + data[i * 2 + 1] ) * ( multiplier ) / 2; \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount * outchannels; i++ ) \ + outdata[i] = data[i] * ( multiplier ); \ + } \ + else \ + return false; \ + } \ + else \ + return false; \ + +#define SOUND_CONVERTDOWNSAMPLE_BOILERPLATE( multiplier ) \ + if( inchannels == 1 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i] = data[(int)j] * ( multiplier ); \ + } \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i;\ + outdata[i * 2 + 0] = data[(int)j] * ( multiplier ); \ + outdata[i * 2 + 1] = data[(int)j] * ( multiplier ); \ + } \ + } \ + else \ + return false; \ + } \ + else if( inchannels == 2 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i] = ( data[((int)j) * 2 + 0] + data[((int)j) * 2 + 1] ) * ( multiplier ) / 2; \ + } \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i * 2 + 0] = data[((int)j) * 2 + 0] * ( multiplier ); \ + outdata[i * 2 + 1] = data[((int)j) * 2 + 1] * ( multiplier ); \ + } \ + } \ + else \ + return false; \ + } \ + else \ + return false; \ + + +#define SOUND_CONVERTUPSAMPLE_BOILERPLATE( multiplier ) \ + if( inchannels == 1 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i] = data[(int)j] * ( multiplier ); \ + if( j != (int)j && j < incount ) \ + { \ + double frac = ( j - (int)j ) * ( multiplier ); \ + outdata[i] += (data[(int)j+1] - data[(int)j]) * frac; \ + } \ + } \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i * 2 + 0] = data[(int)j] * ( multiplier ); \ + if( j != (int)j && j < incount ) \ + { \ + double frac = ( j - (int)j ) * ( multiplier ); \ + outdata[i * 2 + 0] += (data[(int)j+1] - data[(int)j]) * frac; \ + } \ + outdata[i * 2 + 1] = outdata[i * 2 + 0]; \ + } \ + } \ + else \ + return false; \ + } \ + else if( inchannels == 2 ) \ + { \ + if( outchannels == 1 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i] = ( data[((int)j) * 2 + 0] + data[((int)j) * 2 + 1] ) * ( multiplier ) / 2; \ + if( j != (int)j && j < incount ) \ + { \ + double frac = ( j - (int)j ) * ( multiplier ) / 2; \ + outdata[i] += (data[((int)j + 1 ) * 2 + 0] - data[((int)j) * 2 + 0]) * frac; \ + outdata[i] += (data[((int)j + 1 ) * 2 + 1] - data[((int)j) * 2 + 1]) * frac; \ + } \ + } \ + } \ + else if( outchannels == 2 ) \ + { \ + for( i = 0; i < outcount; i++ ) \ + { \ + double j = stepscale * i; \ + outdata[i*2+0] = data[((int)j)*2+0] * ( multiplier ); \ + outdata[i*2+1] = data[((int)j)*2+1] * ( multiplier ); \ + if( j != (int)j && j < incount ) \ + { \ + double frac = ( j - (int)j ) * ( multiplier ); \ + outdata[i*2+0] += (data[((int)j+1)*2+0] - data[((int)j)*2+0]) * frac; \ + outdata[i*2+1] += (data[((int)j+1)*2+1] - data[((int)j)*2+1]) * frac; \ + } \ + } \ + } \ + else \ + return false; \ + } \ + else \ + return false; \ + +static qboolean Sound_ConvertNoResample( wavdata_t *sc, int inwidth, int inchannels, int outwidth, int outchannels, int outcount ) +{ + size_t i; + + // I could write a generic case here but I also want to make it easier for compiler + // to unroll these for-loops + + SOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTNORESAMPLE_BOILERPLATE ) + + return true; +} + +static qboolean Sound_ConvertDownsample( wavdata_t *sc, int inwidth, int inchannels, int outwidth, int outchannels, int outcount, double stepscale ) +{ + size_t i; + + SOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTDOWNSAMPLE_BOILERPLATE ) + + return true; +} + +static qboolean Sound_ConvertUpsample( wavdata_t *sc, int inwidth, int inchannels, int incount, int outwidth, int outchannels, int outcount, double stepscale ) +{ + size_t i; + + incount--; // to not go past last sample while interpolating + + SOUND_FORMATCONVERT_BOILERPLATE( SOUND_CONVERTUPSAMPLE_BOILERPLATE ) + + return true; +} + +#undef SOUND_FORMATCONVERT_BOILERPLATE +#undef SOUND_CONVERTNORESAMPLE_BOILERPLATE +#undef SOUND_CONVERTDOWNSAMPLE_BOILERPLATE +#undef SOUND_CONVERTUPSAMPLE_BOILERPLATE /* ================ Sound_ResampleInternal -We need convert sound to signed even if nothing to resample ================ */ -qboolean Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate, int outwidth ) +static qboolean Sound_ResampleInternal( wavdata_t *sc, int outrate, int outwidth, int outchannels ) { - double stepscale, j; - int outcount; - int i; + const int inrate = sc->rate; + const int inwidth = sc->width; + const int inchannels = sc->channels; + const int incount = sc->samples; + const int insize = sc->size; qboolean handled = false; + double stepscale; + double t1, t2; + int outcount; - if( inrate == outrate && inwidth == outwidth ) + if( inrate == outrate && inwidth == outwidth && inchannels == outchannels ) return false; + t1 = Sys_DoubleTime(); + stepscale = (double)inrate / outrate; // this is usually 0.5, 1, or 2 outcount = sc->samples / stepscale; - sc->size = outcount * outwidth * sc->channels; + sc->size = outcount * outwidth * outchannels; + sc->channels = outchannels; + + sc->samples = outcount; + if( FBitSet( sc->flags, SOUND_LOOPED )) + sc->loopStart = sc->loopStart / stepscale; sound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, sc->size ); - sc->samples = outcount; - if( sc->loopStart != -1 ) - sc->loopStart = sc->loopStart / stepscale; + if( inrate == outrate ) // no resampling, just copy data + handled = Sound_ConvertNoResample( sc, inwidth, inchannels, outwidth, outchannels, outcount ); + else if( inrate > outrate ) // fast case, usually downsample but is also ok for upsampling + handled = Sound_ConvertDownsample( sc, inwidth, inchannels, outwidth, outchannels, outcount, stepscale ); + else // upsample case, w/ interpolation + handled = Sound_ConvertUpsample( sc, inwidth, inchannels, incount, outwidth, outchannels, outcount, stepscale ); - if( inrate == outrate ) + if( !handled ) { - if( inwidth == 1 && outwidth == 2 ) // S8 to S16 - { - for( i = 0; i < outcount * sc->channels; i++ ) - ((int16_t*)sound.tempbuffer)[i] = ((int8_t *)sc->buffer)[i] * 256; - handled = true; - } - else if( inwidth == 2 && outwidth == 1 ) // S16 to S8 - { - for( i = 0; i < outcount * sc->channels; i++ ) - ((int8_t*)sound.tempbuffer)[i] = ((int16_t *)sc->buffer)[i] / 256; - handled = true; - } - } - else // resample case - { - if( inwidth == 1 ) - { - int8_t *data = (int8_t *)sc->buffer; + // restore previously changed data + sc->rate = inrate; + sc->width = inwidth; + sc->channels = inchannels; + sc->samples = incount; + sc->size = insize; - if( outwidth == 1 ) - { - if( sc->channels == 2 ) - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - { - ((int8_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0]; - ((int8_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1]; - } - } - else - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - ((int8_t*)sound.tempbuffer)[i] = data[(int)j]; - } - handled = true; - } - else if( outwidth == 2 ) - { - if( sc->channels == 2 ) - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - { - ((int16_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0] * 256; - ((int16_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1] * 256; - } - } - else - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - ((int16_t*)sound.tempbuffer)[i] = data[(int)j] * 256; - } - handled = true; - } - } - else if( inwidth == 2 ) - { - int16_t *data = (int16_t *)sc->buffer; + Con_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 ); - if( outwidth == 1 ) - { - if( sc->channels == 2 ) - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - { - ((int8_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0] / 256; - ((int8_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1] / 256; - } - } - else - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - ((int8_t*)sound.tempbuffer)[i] = data[(int)j] / 256; - } - handled = true; - } - else if( outwidth == 2 ) - { - if( sc->channels == 2 ) - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - { - ((int16_t*)sound.tempbuffer)[i*2+0] = data[((int)j)*2+0]; - ((int16_t*)sound.tempbuffer)[i*2+1] = data[((int)j)*2+1]; - } - } - else - { - for( i = 0, j = 0; i < outcount; i++, j += stepscale ) - ((int16_t*)sound.tempbuffer)[i] = data[(int)j]; - } - handled = true; - } - } + return false; } - if( handled ) - Con_Reportf( "Sound_Resample: from [%d bit %d Hz] to [%d bit %d Hz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); - else - Con_Reportf( S_ERROR "Sound_Resample: unsupported from [%d bit %d Hz] to [%d bit %d Hz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); - + t2 = Sys_DoubleTime(); sc->rate = outrate; sc->width = outwidth; - return handled; + if( t2 - t1 > 0.01f ) // critical, report to mod developer + Con_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 ); + else + Con_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 ); + + return true; } -qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ) +qboolean Sound_Process( wavdata_t **wav, int rate, int width, int channels, uint flags ) { wavdata_t *snd = *wav; qboolean result = true; // check for buffers - if( !snd || !snd->buffer ) + if( unlikely( !snd || !snd->buffer )) return false; - if(( flags & SOUND_RESAMPLE ) && ( width > 0 || rate > 0 )) + if( likely( FBitSet( flags, SOUND_RESAMPLE ) && ( width > 0 || rate > 0 || channels > 0 ))) { - if( Sound_ResampleInternal( snd, snd->rate, snd->width, rate, width )) + result = Sound_ResampleInternal( snd, rate, width, channels ); + + if( result ) { - Mem_Free( snd->buffer ); // free original image buffer - snd->buffer = Sound_Copy( snd->size ); // unzone buffer (don't touch image.tempbuffer) - } - else - { - // not resampled - result = false; + snd = Mem_Realloc( host.soundpool, snd, sizeof( *snd ) + snd->size ); + memcpy( snd->buffer, sound.tempbuffer, snd->size ); + + Mem_Free( sound.tempbuffer ); + sound.tempbuffer = NULL; } } *wav = snd; + return result; +} + +qboolean Sound_SupportedFileFormat( const char *fileext ) +{ + const loadwavfmt_t *format; + if( COM_CheckStringEmpty( fileext )) + { + for( format = sound.loadformats; format && format->ext; format++ ) + { + if( !Q_stricmp( format->ext, fileext )) + return true; + } + } return false; } diff --git a/engine/common/soundlib/soundlib.h b/engine/common/soundlib/soundlib.h index 9ce2af46..0e20c8c7 100644 --- a/engine/common/soundlib/soundlib.h +++ b/engine/common/soundlib/soundlib.h @@ -23,14 +23,12 @@ GNU General Public License for more details. typedef struct loadwavfmt_s { - const char *formatstring; const char *ext; qboolean (*loadfunc)( const char *name, const byte *buffer, fs_offset_t filesize ); } loadwavfmt_t; typedef struct streamfmt_s { - const char *formatstring; const char *ext; stream_t *(*openfunc)( const char *filename ); @@ -50,7 +48,7 @@ typedef struct sndlib_s int rate; // num samples per second (e.g. 11025 - 11 khz) int width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) int channels; // num channels (1 - mono, 2 - stereo) - int loopstart; // start looping from + uint loopstart; // start looping from uint samples; // total samplecount in sound uint flags; // additional sound flags size_t size; // sound unpacked size (for bounds checking) @@ -119,6 +117,8 @@ extern sndlib_t sound; // qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize ); qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize ); +qboolean Sound_LoadOggVorbis( const char *name, const byte *buffer, fs_offset_t filesize ); +qboolean Sound_LoadOggOpus( const char *name, const byte *buffer, fs_offset_t filesize ); // // stream operate @@ -133,5 +133,15 @@ int Stream_ReadMPG( stream_t *stream, int bytes, void *buffer ); int Stream_SetPosMPG( stream_t *stream, int newpos ); int Stream_GetPosMPG( stream_t *stream ); void Stream_FreeMPG( stream_t *stream ); +stream_t *Stream_OpenOggVorbis( const char *filename ); +int Stream_ReadOggVorbis( stream_t *stream, int bytes, void *buffer ); +int Stream_SetPosOggVorbis( stream_t *stream, int newpos ); +int Stream_GetPosOggVorbis( stream_t *stream ); +void Stream_FreeOggVorbis( stream_t *stream ); +stream_t *Stream_OpenOggOpus( const char *filename ); +int Stream_ReadOggOpus( stream_t *stream, int bytes, void *buffer ); +int Stream_SetPosOggOpus( stream_t *stream, int newpos ); +int Stream_GetPosOggOpus( stream_t *stream ); +void Stream_FreeOggOpus( stream_t *stream ); #endif//SOUNDLIB_H diff --git a/engine/common/sounds.c b/engine/common/sounds.c new file mode 100644 index 00000000..abe5ea8a --- /dev/null +++ b/engine/common/sounds.c @@ -0,0 +1,362 @@ +/* +sounds.c - sounds.lst parser +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "common.h" + +enum soundlst_type_e +{ + SoundList_None, + SoundList_Range, + SoundList_List +}; + +static const char *const soundlst_groups[SoundList_Groups] = +{ + "BouncePlayerShell", + "BounceWeaponShell", + "BounceConcrete", + "BounceGlass", + "BounceMetal", + "BounceFlesh", + "BounceWood", + "Ricochet", + "Explode", + "PlayerWaterEnter", + "PlayerWaterExit", + "EntityWaterEnter", + "EntityWaterExit", +}; + +typedef struct soundlst_s +{ + enum soundlst_type_e type; + char *snd; + int min; // the string length if type is group + int max; // the string count if type is group +} soundlst_t; + +static soundlst_t soundlst[SoundList_Groups]; + +static void SoundList_Print_f( void ); +static void SoundList_Free( soundlst_t *lst ) +{ + if( lst->snd ) + Mem_Free( lst->snd ); + + memset( lst, 0, sizeof( *lst )); +} + +void SoundList_Shutdown( void ) +{ + int i; + + for( i = 0; i < SoundList_Groups; i++ ) + SoundList_Free( &soundlst[i] ); +} + +int SoundList_Count( enum soundlst_group_e group ) +{ + soundlst_t *lst = &soundlst[group]; + + switch( lst->type ) + { + case SoundList_Range: + return lst->max - lst->min + 1; + case SoundList_List: + return lst->max; + } + + return 0; +} + +const char *SoundList_Get( enum soundlst_group_e group, int i ) +{ + static string temp; + soundlst_t *lst = &soundlst[group]; + + if( i < 0 || i >= SoundList_Count( group )) + return NULL; + + switch( lst->type ) + { + case SoundList_Range: + Q_snprintf( temp, sizeof( temp ), lst->snd, lst->min + i ); + return temp; + case SoundList_List: + return &lst->snd[i * lst->min]; + } + + return NULL; +} + +const char *SoundList_GetRandom( enum soundlst_group_e group ) +{ + int count = SoundList_Count( group ); + int idx = COM_RandomLong( 0, count - 1 ); + + // Con_Printf( "%s: %s %d %d\n", __func__, soundlst_groups[group], count, idx ); + + return SoundList_Get( group, idx ); +} + +static qboolean SoundList_ParseGroup( soundlst_t *lst, char **file ) +{ + string token; + int count = 0, slen = 0, i; + char *p; + + p = *file; + + while(( p = COM_ParseFile( p, token, sizeof( token )))) + { + int len; + + if( !Q_strcmp( token, "}" )) + break; + + if( !Q_strcmp( token, "{" )) + { + Con_Printf( "%s: expected '}' but got '{' during group list parse\n", __func__ ); + return false; + } + else if( !COM_CheckStringEmpty( token )) + { + Con_Printf( "%s: expected '}' but got EOF during group list parse\n", __func__ ); + return false; + } + + len = Q_strlen( token ) + 1; + if( slen < len ) + slen = len; + + count++; + } + + if( count == 0 ) // deactivate group + { + lst->type = SoundList_None; + lst->snd = NULL; + lst->min = lst->max = 0; + return true; + } + + lst->type = SoundList_List; + lst->min = slen; + lst->max = count; + lst->snd = Mem_Malloc( host.mempool, count * slen ); // allocate single buffer for the whole group + + for( i = 0; i < count; i++ ) + { + *file = COM_ParseFile( *file, token, sizeof( token )); + + Q_strncpy( &lst->snd[i * slen], token, slen ); + } + + return true; +} + +static qboolean SoundList_ParseRange( soundlst_t *lst, char **file ) +{ + string token, snd; + char *p; + int i = 0; + + lst->type = SoundList_Range; + *file = COM_ParseFile( *file, snd, sizeof( snd )); + + // validate format string, count all % characters + p = snd; + i = 0; + while(( p = Q_strchr( p, '%' ))) + { + // only decimal + if( p[1] != 'd' && p[1] != 'i' && p[1] != 'u' ) + { + Con_Printf( "%s: invalid range string %s, only decimal numbers are allowed", __func__, snd ); + return false; + } + + i++; + p++; + } + + // if more than one %, then it's an invalid format string + if( i != 1 ) + { + Con_Printf( "%s: invalid range string %s, only single specifier is allowed\n", __func__, snd ); + return false; + } + + *file = COM_ParseFile( *file, token, sizeof( token )); + if( !Q_isdigit( token )) + { + Con_Printf( "%s: %s must be a digit\n", __func__, token ); + return false; + } + lst->min = Q_atoi( token ); + + *file = COM_ParseFile( *file, token, sizeof( token )); + if( !Q_isdigit( token )) + { + Con_Printf( "%s: %s must be a digit\n", __func__, token ); + return false; + } + lst->max = Q_atoi( token ); + lst->snd = copystring( snd ); + + return true; +} + +static qboolean SoundList_Parse( char *file ) +{ + string token; + int i; + + while(( file = COM_ParseFile( file, token, sizeof( token )))) + { + soundlst_t *lst = NULL; + char *p; + + for( i = 0; i < SoundList_Groups; i++ ) + { + if( !Q_strcmp( token, soundlst_groups[i] )) + { + lst = &soundlst[i]; + break; + } + } + + if( !lst ) + { + Con_Printf( "%s: unexpected token %s, must be group name\n", __func__, token ); + goto cleanup; + } + + p = COM_ParseFile( file, token, sizeof( token )); + + // group is a range + if( !Q_strcmp( token, "{" )) + { + file = p; // advance pointer + + if( !SoundList_ParseGroup( lst, &file )) + goto cleanup; + + file = COM_ParseFile( file, token, sizeof( token )); + if( Q_strcmp( token, "}" )) + { + Con_Printf( "%s: unexpected token %s, must be }\n", __func__, token ); + goto cleanup; + } + } + else + { + // ranges are more simple, but need to rewind pointer for them + if( !SoundList_ParseRange( lst, &file )) + goto cleanup; + } + } + + if( host_developer.value >= 2 ) + SoundList_Print_f(); + + return true; + +cleanup: + SoundList_Shutdown(); + return false; +} + +static void SoundList_Print_f( void ) +{ + int i; + + for( i = 0; i < SoundList_Groups; i++ ) + { + soundlst_t *lst = &soundlst[i]; + + switch( lst->type ) + { + case SoundList_Range: + Con_Reportf( "%-16s\t" S_CYAN "Range" S_DEFAULT " %s [%d; %d]\n", + soundlst_groups[i], lst->snd, lst->min, lst->max ); + break; + case SoundList_List: + { + int j; + + Con_Reportf( "%-16s\t" S_MAGENTA "List" S_DEFAULT " [", soundlst_groups[i] ); + for( j = 0; j < lst->max; j++ ) + Con_Reportf( "%s%s", &lst->snd[j * lst->min], j + 1 == lst->max ? "" : ", " ); + Con_Reportf( "]\n" ); + break; + } + default: + Con_Reportf( "%-16s\t" S_RED "inactive" S_DEFAULT "\n", soundlst_groups[i] ); + break; + } + } +} + +// I wish we had #embed already +static const char default_sounds_lst[] = +"BouncePlayerShell \"player/pl_shell%d.wav\" 1 3\n" +"BounceWeaponShell \"weapons/sshell%d.wav\" 1 3\n" +"BounceConcrete \"debris/concrete%d.wav\" 1 3\n" +"BounceGlass \"debris/glass%d.wav\" 1 4\n" +"BounceMetal \"debris/metal%d.wav\" 1 6\n" +"BounceFlesh \"debris/flesh%d.wav\" 1 7\n" +"BounceWood \"debris/wood%d.wav\" 1 4\n" +"Ricochet \"weapons/ric%d.wav\" 1 5\n" +"Explode \"weapons/explode%d.wav\" 3 5\n" +"EntityWaterEnter \"player/pl_wade%d.wav\" 1 4\n" +"EntityWaterExit \"player/pl_wade%d.wav\" 1 4\n" +"PlayerWaterEnter\n" +"{\n" +" \"player/pl_wade1.wav\"\n" +"}\n" +"\n" +"PlayerWaterExit\n" +"{\n" +" \"player/pl_wade2.wav\"\n" +"}\n"; + +static void SoundList_Reload_f( void ) +{ + qboolean load_internal = false; + char *pfile = FS_LoadFile( "scripts/sounds.lst", NULL, false ); + + if( pfile ) + { + if( !SoundList_Parse( pfile )) + { + Con_Printf( S_ERROR "can't parse sounds.lst file, loading internal...\n" ); + load_internal = true; + } + + Mem_Free( pfile ); + } + else load_internal = true; + + if( load_internal ) + SoundList_Parse( (char *)default_sounds_lst ); +} + +void SoundList_Init( void ) +{ + Cmd_AddRestrictedCommand( "host_soundlist_print", SoundList_Print_f, "print current sound list" ); + Cmd_AddRestrictedCommand( "host_soundlist_reload", SoundList_Reload_f, "reload sound list" ); + + SoundList_Reload_f (); +} diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 67c5d3bb..d7e09dd5 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -14,78 +14,36 @@ GNU General Public License for more details. */ #include "common.h" -#if XASH_WIN32 -#define STDOUT_FILENO 1 -#include -#elif XASH_ANDROID +#if XASH_ANDROID #include #endif #include #include +#if XASH_IRIX +#include +#endif +#include "xash3d_mathlib.h" // do not waste precious CPU cycles on mobiles or low memory devices -#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY -#define XASH_COLORIZE_CONSOLE true -// use with caution, running engine in Qt Creator may cause a freeze in read() call -// I was never encountered this bug anywhere else, so still enable by default -// #define XASH_USE_SELECT 1 +#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY && !XASH_EMSCRIPTEN +#define XASH_COLORIZE_CONSOLE 1 #else -#define XASH_COLORIZE_CONSOLE false +#define XASH_COLORIZE_CONSOLE 0 #endif -#if XASH_USE_SELECT -// non-blocking console input -#include -#endif - -typedef struct { - char title[64]; - qboolean log_active; - char log_path[MAX_SYSPATH]; - FILE *logfile; - int logfileno; -} LogData; - -static LogData s_ld; - -char *Sys_Input( void ) -{ -#if XASH_USE_SELECT - { - fd_set rfds; - static char line[1024]; - static int len; - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO( &rfds ); - FD_SET( 0, &rfds); // stdin - while( select( 1, &rfds, NULL, NULL, &tv ) > 0 ) - { - if( read( 0, &line[len], 1 ) != 1 ) - break; - if( line[len] == '\n' || len > 1022 ) - { - line[ ++len ] = 0; - len = 0; - return line; - } - len++; - tv.tv_sec = 0; - tv.tv_usec = 0; - } - } -#endif -#if XASH_WIN32 - return Wcon_Input(); -#endif - return NULL; -} +static struct logdata_s { + char title[64]; + qboolean log_active; + qboolean log_time; + char log_path[MAX_SYSPATH]; + FILE *logfile; + int logfileno; +} s_ld; void Sys_DestroyConsole( void ) { // last text message into console or log - Con_Reportf( "Sys_DestroyConsole: Exiting!\n" ); + Con_Reportf( "%s: Exiting!\n", __func__ ); #if XASH_WIN32 Wcon_DestroyConsole(); #endif @@ -119,18 +77,27 @@ static void Sys_FlushLogfile( void ) void Sys_InitLog( void ) { - const char *mode; + const char *mode; - if( Sys_CheckParm( "-log" ) && host.allow_console != 0 ) + if( Sys_CheckParm( "-log" )) { + if( !Sys_GetParmFromCmdLine( "-log", s_ld.log_path ) || !isalnum( s_ld.log_path[0] )) + Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path )); + + COM_DefaultExtension( s_ld.log_path, ".log", sizeof( s_ld.log_path )); s_ld.log_active = true; - Q_strncpy( s_ld.log_path, "engine.log", sizeof( s_ld.log_path )); } + s_ld.log_time = Sys_CheckParm( "-logtime" ); + if( host.change_game && host.type != HOST_DEDICATED ) mode = "a"; else mode = "w"; + if( Host_IsDedicated( )) + Q_strncpy( s_ld.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_ld.title )); + else Q_strncpy( s_ld.title, XASH_ENGINE_NAME " " XASH_VERSION, sizeof( s_ld.title )); + // create log if needed if( s_ld.log_active ) { @@ -138,81 +105,90 @@ void Sys_InitLog( void ) if ( !s_ld.logfile ) { - Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); + Con_Reportf( S_ERROR "%s: can't create log file %s: %s\n", __func__, s_ld.log_path, strerror( errno )); return; } s_ld.logfileno = fileno( s_ld.logfile ); - fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); - fprintf( s_ld.logfile, "=================================================================================\n" ); + // fit to 80 columns for easier read on standard terminal + fputs( "================================================================================\n", s_ld.logfile ); + fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + fprintf( s_ld.logfile, "Game started at %s\n", Q_timestamp( TIME_FULL )); + fputs( "================================================================================\n", s_ld.logfile ); + fflush( s_ld.logfile ); } } -void Sys_CloseLog( void ) +void Sys_CloseLog( const char *finalmsg ) { - char event_name[64]; - - // continue logged - switch( host.status ) - { - case HOST_CRASHED: - Q_strncpy( event_name, "crashed", sizeof( event_name )); - break; - case HOST_ERR_FATAL: - Q_strncpy( event_name, "stopped with error", sizeof( event_name )); - break; - default: - if( !host.change_game ) Q_strncpy( event_name, "stopped", sizeof( event_name )); - else Q_strncpy( event_name, host.finalmsg, sizeof( event_name )); - break; - } - Sys_FlushStdout(); // flush to stdout to ensure all data was written - if( s_ld.logfile ) - { - fprintf( s_ld.logfile, "\n"); - fprintf( s_ld.logfile, "================================================================================="); - if( host.change_game ) fprintf( s_ld.logfile, "\n\t%s (build %i) %s\n", s_ld.title, Q_buildnum(), event_name ); - else fprintf( s_ld.logfile, "\n\t%s (build %i) %s at %s\n", s_ld.title, Q_buildnum(), event_name, Q_timestamp( TIME_FULL )); - fprintf( s_ld.logfile, "=================================================================================\n"); + if( !s_ld.logfile ) + return; - fclose( s_ld.logfile ); - s_ld.logfile = NULL; + // continue logged + if( !finalmsg ) + { + switch( host.status ) + { + case HOST_CRASHED: + finalmsg = "crashed"; + break; + case HOST_ERR_FATAL: + finalmsg = "stopped with error"; + break; + default: + finalmsg = "stopped"; + break; + } } + + fputc( '\n', s_ld.logfile ); + fputs( "================================================================================\n", s_ld.logfile ); + fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + fprintf( s_ld.logfile, "Stopped with reason \"%s\" at %s\n", finalmsg, Q_timestamp( TIME_FULL )); + fputs( "================================================================================\n", s_ld.logfile ); + fclose( s_ld.logfile ); + s_ld.logfile = NULL; } -#if XASH_COLORIZE_CONSOLE == true -static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) +#if XASH_COLORIZE_CONSOLE +static qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c ) { static const char *q3ToAnsi[ 8 ] = { - "\033[30m", // COLOR_BLACK - "\033[31m", // COLOR_RED - "\033[32m", // COLOR_GREEN - "\033[33m", // COLOR_YELLOW - "\033[34m", // COLOR_BLUE - "\033[36m", // COLOR_CYAN - "\033[35m", // COLOR_MAGENTA + "\033[1;30m", // COLOR_BLACK + "\033[1;31m", // COLOR_RED + "\033[1;32m", // COLOR_GREEN + "\033[1;33m", // COLOR_YELLOW + "\033[1;34m", // COLOR_BLUE + "\033[1;36m", // COLOR_CYAN + "\033[1;35m", // COLOR_MAGENTA "\033[0m", // COLOR_WHITE }; const char *esc = q3ToAnsi[c]; - if( c == 7 ) - write( fd, esc, 4 ); - else write( fd, esc, 5 ); + return write( fd, esc, c == 7 ? 4 : 7 ) < 0 ? false : true; } #else -static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {} +static qboolean Sys_WriteEscapeSequenceForColorcode( int fd, int c ) +{ + return true; +} #endif -static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize ) +static void Sys_PrintLogfile( const int fd, const char *logtime, size_t logtime_len, const char *msg, const int colorize ) { const char *p = msg; - write( fd, logtime, Q_strlen( logtime ) ); + if( logtime_len != 0 ) + { + if( write( fd, logtime, logtime_len ) < 0 ) + { + // not critical for us + } + } while( p && *p ) { @@ -220,13 +196,20 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg if( p == NULL ) { - write( fd, msg, Q_strlen( msg )); + if( write( fd, msg, Q_strlen( msg )) < 0 ) + { + // don't call engine Msg, might cause recursion + fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno )); + } break; } else if( IsColorString( p )) { if( p != msg ) - write( fd, msg, p - msg ); + { + if( write( fd, msg, p - msg ) < 0 ) + fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno )); + } msg = p + 2; if( colorize ) @@ -234,7 +217,8 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg } else { - write( fd, msg, p - msg + 1 ); + if( write( fd, msg, p - msg + 1 ) < 0 ) + fprintf( stderr, "%s: write failed: %s\n", __func__, strerror( errno )); msg = p + 1; } } @@ -244,7 +228,7 @@ static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg Sys_WriteEscapeSequenceForColorcode( fd, 7 ); } -static void Sys_PrintStdout( const char *logtime, const char *msg ) +static void Sys_PrintStdout( const char *logtime, size_t logtime_len, const char *msg ) { #if XASH_MOBILE_PLATFORM static char buf[MAX_PRINT_MSG]; @@ -254,15 +238,29 @@ static void Sys_PrintStdout( const char *logtime, const char *msg ) // platform-specific output #if XASH_ANDROID && !XASH_DEDICATED - __android_log_write( ANDROID_LOG_DEBUG, "Xash", buf ); + __android_log_write( ANDROID_LOG_INFO, "Xash", buf ); #endif // XASH_ANDROID && !XASH_DEDICATED #if TARGET_OS_IOS void IOS_Log( const char * ); IOS_Log( buf ); #endif // TARGET_OS_IOS + +#if XASH_NSWITCH && NSWITCH_DEBUG + // just spew it to stderr normally in debug mode + fprintf( stderr, "%s %s", logtime, buf ); +#endif // XASH_NSWITCH && NSWITCH_DEBUG + +#if XASH_PSVITA + // spew to stderr only in developer mode + if( host_developer.value ) + { + fprintf( stderr, "%s %s", logtime, buf ); + } +#endif + #elif !XASH_WIN32 // Wcon does the job - Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); + Sys_PrintLogfile( STDOUT_FILENO, logtime, logtime_len, msg, XASH_COLORIZE_CONSOLE ); Sys_FlushStdout(); #endif } @@ -273,31 +271,49 @@ void Sys_PrintLog( const char *pMsg ) const struct tm *crt_tm; char logtime[32] = ""; static char lastchar; + qboolean print_time = false; + size_t len, logtime_len = 0; - time( &crt_time ); - crt_tm = localtime( &crt_time ); - - if( !lastchar || lastchar == '\n') - strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time - - // spew to stdout - Sys_PrintStdout( logtime, pMsg ); - - if( !s_ld.logfile ) + if( !lastchar || lastchar == '\n' ) { - // save last char to detect when line was not ended - lastchar = pMsg[Q_strlen( pMsg ) - 1]; - return; + if( time( &crt_time ) >= 0 ) + { + crt_tm = localtime( &crt_time ); + print_time = crt_tm != NULL; + } } - if( !lastchar || lastchar == '\n') - strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time - - // save last char to detect when line was not ended - lastchar = pMsg[Q_strlen( pMsg ) - 1]; + if( print_time ) + { + logtime_len = strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); // short time + logtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case + } - Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false ); - Sys_FlushLogfile(); + // spew to stdout + Sys_PrintStdout( logtime, logtime_len, pMsg ); + + len = Q_strlen( pMsg ); + + // save last char to detect when line was not ended + lastchar = len > 0 ? pMsg[len - 1] : 0; + + // spew to engine.log + if( s_ld.logfile ) + { + if( s_ld.log_time && print_time ) + { + logtime_len = strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time + logtime_len = Q_min( logtime_len, sizeof( logtime ) - 1 ); // just in case + } + else + { + logtime[0] = '\0'; + logtime_len = 0; + } + + Sys_PrintLogfile( s_ld.logfileno, logtime, logtime_len, pMsg, false ); + Sys_FlushLogfile(); + } } /* @@ -307,6 +323,21 @@ CONSOLE PRINT ============================================================================= */ +static void Con_Printfv( qboolean debug, const char *szFmt, va_list args ) +{ + static char buffer[MAX_PRINT_MSG]; + qboolean add_newline; + + add_newline = Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ) < 0; + + if( debug && !Q_strcmp( buffer, "0\n" )) + return; // hlrally spam + + Sys_Print( buffer ); + if( add_newline ) + Sys_Print( "\n" ); +} + /* ============= Con_Printf @@ -315,17 +346,14 @@ Con_Printf */ void GAME_EXPORT Con_Printf( const char *szFmt, ... ) { - static char buffer[MAX_PRINT_MSG]; - va_list args; + va_list args; if( !host.allow_console ) return; va_start( args, szFmt ); - Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + Con_Printfv( false, szFmt, args ); va_end( args ); - - Sys_Print( buffer ); } /* @@ -336,20 +364,14 @@ Con_DPrintf */ void GAME_EXPORT Con_DPrintf( const char *szFmt, ... ) { - static char buffer[MAX_PRINT_MSG]; - va_list args; + va_list args; if( host_developer.value < DEV_NORMAL ) return; va_start( args, szFmt ); - Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + Con_Printfv( true, szFmt, args ); va_end( args ); - - if( buffer[0] == '0' && buffer[1] == '\n' && buffer[2] == '\0' ) - return; // hlrally spam - - Sys_Print( buffer ); } /* @@ -360,17 +382,14 @@ Con_Reportf */ void Con_Reportf( const char *szFmt, ... ) { - static char buffer[MAX_PRINT_MSG]; - va_list args; + va_list args; if( host_developer.value < DEV_EXTENDED ) return; va_start( args, szFmt ); - Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + Con_Printfv( false, szFmt, args ); va_end( args ); - - Sys_Print( buffer ); } diff --git a/engine/common/system.c b/engine/common/system.c index 18ba734f..74104db9 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -1,5 +1,5 @@ /* -sys_win.c - platform dependent code +sys_win.c - platform dependent code (which haven't moved to platform dir yet) Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -19,14 +19,19 @@ GNU General Public License for more details. #include #include -#ifdef XASH_SDL +#if _MSC_VER +#include +#endif + +#if XASH_SDL == 2 #include +#elif XASH_SDL == 3 +#include #endif #if XASH_POSIX #include #include -#include #if !XASH_ANDROID #include @@ -37,13 +42,20 @@ GNU General Public License for more details. #include #endif +#if XASH_NSWITCH +#include +#endif + +#if XASH_PSVITA +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro #include "library.h" #include "whereami.h" -qboolean error_on_exit = false; // arg for exit(); -#define DEBUG_BREAK +int error_on_exit = 0; // arg for exit(); /* ================ @@ -54,23 +66,36 @@ double GAME_EXPORT Sys_DoubleTime( void ) { return Platform_DoubleTime(); } -#if XASH_LINUX || ( XASH_WIN32 && !XASH_64BIT ) - #undef DEBUG_BREAK - qboolean Sys_DebuggerPresent( void ); // see sys_linux.c - #if XASH_MSVC - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - _asm{ int 3 } - #elif XASH_X86 - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - asm volatile("int $3;") - #else - #define DEBUG_BREAK \ - if( Sys_DebuggerPresent() ) \ - raise( SIGINT ) - #endif -#endif + +/* +================ +Sys_DebugBreak +================ +*/ +void Sys_DebugBreak( void ) +{ + qboolean was_grabbed = false; + + if( !Sys_DebuggerPresent( )) + return; + + if( host.hWnd ) // so annoying + { + was_grabbed = Platform_GetMouseGrab(); + Platform_SetMouseGrab( false ); + } + +#if _MSC_VER + __debugbreak(); +#else // !_MSC_VER + INLINE_RAISE( SIGINT ); + INLINE_NANOSLEEP1(); // sometimes signal comes with delay, let it interrupt nanosleep +#endif // !_MSC_VER + + if( was_grabbed ) + Platform_SetMouseGrab( true ); +} + #if !XASH_DEDICATED /* ================ @@ -91,22 +116,6 @@ char *Sys_GetClipboardData( void ) } #endif // XASH_DEDICATED -/* -================ -Sys_Sleep - -freeze application for some time -================ -*/ -void Sys_Sleep( int msec ) -{ - if( !msec ) - return; - - msec = Q_min( msec, 1000 ); - Platform_Sleep( msec ); -} - /* ================ Sys_GetCurrentUser @@ -116,18 +125,34 @@ returns username for current profile */ const char *Sys_GetCurrentUser( void ) { + // TODO: move to platform #if XASH_WIN32 - static string s_userName; - unsigned long size = sizeof( s_userName ); + static wchar_t sw_userName[MAX_STRING]; + DWORD size = ARRAYSIZE( sw_userName ); - if( GetUserName( s_userName, &size )) + if( GetUserNameW( sw_userName, &size ) && sw_userName[0] != 0 ) + { + static char s_userName[MAX_STRING * 4]; + + // set length to -1, so it will null terminate + WideCharToMultiByte( CP_UTF8, 0, sw_userName, -1, s_userName, sizeof( s_userName ), NULL, NULL ); return s_userName; -#elif XASH_POSIX && !XASH_ANDROID - uid_t uid = geteuid(); - struct passwd *pw = getpwuid( uid ); + } +#elif XASH_PSVITA + static string username; + sceAppUtilSystemParamGetString( SCE_SYSTEM_PARAM_ID_USERNAME, username, sizeof( username ) - 1 ); + if( COM_CheckStringEmpty( username )) + return username; +#elif XASH_POSIX && !XASH_ANDROID && !XASH_NSWITCH + static string username; + struct passwd *pw = getpwuid( geteuid( )); - if( pw ) - return pw->pw_name; + // POSIX standard says pw _might_ point to static area, so let's make a copy + if( pw && COM_CheckString( pw->pw_name )) + { + Q_strncpy( username, pw->pw_name, sizeof( username )); + return username; + } #endif return "Player"; } @@ -163,27 +188,6 @@ void Sys_ParseCommandLine( int argc, char** argv ) } } -/* -================== -Sys_MergeCommandLine - -================== -*/ -void Sys_MergeCommandLine( void ) -{ - const char *blank = "censored"; - int i; - - if( !host.change_game ) return; - - for( i = 0; i < host.argc; i++ ) - { - // second call - if( Host_IsDedicated() && !Q_strnicmp( "+menu_", host.argv[i], 6 )) - host.argv[i] = (char *)blank; - } -} - /* ================ Sys_CheckParm @@ -240,28 +244,12 @@ qboolean Sys_GetIntFromCmdLine( const char* argName, int *out ) return true; } -void Sys_SendKeyEvents( void ) -{ -#if XASH_WIN32 - MSG msg; - - while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE )) - { - if( !GetMessage( &msg, NULL, 0, 0 )) - Sys_Quit (); - - TranslateMessage( &msg ); - DispatchMessage( &msg ); - } -#endif -} - //======================================================================= // DLL'S MANAGER SYSTEM //======================================================================= qboolean Sys_LoadLibrary( dll_info_t *dll ) { - const dllfunc_t *func; + size_t i; string errorstring; // check errors @@ -271,14 +259,10 @@ qboolean Sys_LoadLibrary( dll_info_t *dll ) if( !dll->name || !*dll->name ) return false; // nothing to load - Con_Reportf( "Sys_LoadLibrary: Loading %s", dll->name ); + Con_Reportf( "%s: Loading %s", __func__, dll->name ); - if( dll->fcts ) - { - // lookup export table - for( func = dll->fcts; func && func->name != NULL; func++ ) - *func->func = NULL; - } + if( dll->fcts ) // lookup export table + ClearExports( dll->fcts, dll->num_fcts ); if( !dll->link ) dll->link = COM_LoadLibrary( dll->name, false, true ); // environment pathes @@ -291,9 +275,10 @@ qboolean Sys_LoadLibrary( dll_info_t *dll ) } // Get the function adresses - for( func = dll->fcts; func && func->name != NULL; func++ ) + for( i = 0; i < dll->num_fcts; i++ ) { - if( !( *func->func = Sys_GetProcAddress( dll, func->name ))) + const dllfunc_t *func = &dll->fcts[i]; + if( !( *func->func = COM_GetProcAddress( dll->link, func->name ))) { Q_snprintf( errorstring, sizeof( errorstring ), "Sys_LoadLibrary: %s missing or invalid function (%s)\n", dll->name, func->name ); goto error; @@ -306,19 +291,11 @@ error: Con_Reportf( " - failed\n" ); Sys_FreeLibrary( dll ); // trying to free if( dll->crash ) Sys_Error( "%s", errorstring ); - else Con_Reportf( S_ERROR "%s", errorstring ); + else Con_Reportf( S_ERROR "%s", errorstring ); return false; } -void* Sys_GetProcAddress( dll_info_t *dll, const char* name ) -{ - if( !dll || !dll->link ) // invalid desc - return NULL; - - return (void *)COM_GetProcAddress( dll->link, name ); -} - qboolean Sys_FreeLibrary( dll_info_t *dll ) { // invalid desc or alredy freed @@ -328,14 +305,16 @@ qboolean Sys_FreeLibrary( dll_info_t *dll ) if( host.status == HOST_CRASHED ) { // we need to hold down all modules, while MSVC can find error - Con_Reportf( "Sys_FreeLibrary: hold %s for debugging\n", dll->name ); + Con_Reportf( "%s: hold %s for debugging\n", __func__, dll->name ); return false; } - else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", dll->name ); + else Con_Reportf( "%s: Unloading %s\n", __func__, dll->name ); COM_FreeLibrary( dll->link ); dll->link = NULL; + ClearExports( dll->fcts, dll->num_fcts ); + return true; } @@ -346,7 +325,7 @@ Sys_WaitForQuit wait for 'Esc' key will be hit ================ */ -void Sys_WaitForQuit( void ) +static void Sys_WaitForQuit( void ) { #if XASH_WIN32 MSG msg; @@ -360,7 +339,7 @@ void Sys_WaitForQuit( void ) TranslateMessage( &msg ); DispatchMessage( &msg ); } - else Sys_Sleep( 20 ); + else Platform_Sleep( 20 ); } #endif } @@ -377,14 +356,16 @@ void Sys_Warn( const char *format, ... ) va_list argptr; char text[MAX_PRINT_MSG]; - DEBUG_BREAK; - va_start( argptr, format ); Q_vsnprintf( text, MAX_PRINT_MSG, format, argptr ); va_end( argptr ); - Msg( "Sys_Warn: %s\n", text ); + + Sys_DebugBreak(); + + Msg( "%s: %s\n", __func__, text ); + if( !Host_IsDedicated() ) // dedicated server should not hang on messagebox - MSGBOX(text); + Platform_MessageBox( "Xash Warning", text, true ); } /* @@ -400,31 +381,36 @@ void Sys_Error( const char *error, ... ) va_list argptr; char text[MAX_PRINT_MSG]; - DEBUG_BREAK; + // enable cursor before debugger call + if( !Host_IsDedicated( )) + Platform_SetCursorType( dc_arrow ); if( host.status == HOST_ERR_FATAL ) return; // don't multiple executes - // make sure what console received last message - if( host.change_game ) Sys_Sleep( 200 ); + // make sure that console received last message + if( host.change_game ) Platform_Sleep( 200 ); - error_on_exit = true; + error_on_exit = 1; host.status = HOST_ERR_FATAL; va_start( argptr, error ); Q_vsnprintf( text, MAX_PRINT_MSG, error, argptr ); va_end( argptr ); + Sys_DebugBreak(); + SV_SysError( text ); if( !Host_IsDedicated() ) { -#if XASH_SDL == 2 +#if XASH_SDL >= 2 if( host.hWnd ) SDL_HideWindow( host.hWnd ); #endif #if XASH_WIN32 Wcon_ShowConsole( false ); #endif - MSGBOX( text ); + Sys_Print( text ); + Platform_MessageBox( "Xash Error", text, true ); } else { @@ -435,22 +421,17 @@ void Sys_Error( const char *error, ... ) Sys_Print( text ); // print error message Sys_WaitForQuit(); } - - Sys_Quit(); + + Sys_Quit( "caught an error" ); } #if XASH_EMSCRIPTEN -/* strange glitchy bug on emscripten -_exit->_Exit->asm._exit->_exit -As we do not need atexit(), just throw hidden exception -*/ #include #define exit my_exit -void my_exit(int ret) +void my_exit( int ret ) { emscripten_cancel_main_loop(); - printf("exit(%d)\n", ret); - EM_ASM(if(showElement)showElement('reload', true);throw 'SimulateInfiniteLoop'); + emscripten_force_exit( ret ); } #endif @@ -459,10 +440,14 @@ void my_exit(int ret) Sys_Quit ================ */ -void Sys_Quit( void ) +void Sys_Quit( const char *reason ) { - Host_Shutdown(); + Host_ShutdownWithReason( reason ); +#if XASH_ANDROID + Host_ExitInMain(); +#else exit( error_on_exit ); +#endif } /* @@ -539,7 +524,7 @@ void Sys_Print( const char *pMsg ) Sys_PrintLog( pMsg ); - Rcon_Print( pMsg ); + Rcon_Print( &host.rd, pMsg ); } /* @@ -553,8 +538,21 @@ but since engine will be unloaded during this call it explicitly doesn't use internal allocation or string copy utils ================== */ -qboolean Sys_NewInstance( const char *gamedir ) +qboolean Sys_NewInstance( const char *gamedir, const char *finalmsg ) { +#if XASH_NSWITCH + char newargs[4096]; + const char *exe = host.argv[0]; // arg 0 is always the full NRO path + + // TODO: carry over the old args (assuming you can even pass any) + Q_snprintf( newargs, sizeof( newargs ), "%s -game %s", exe, gamedir ); + // just restart the entire thing + printf( "envSetNextLoad exe: `%s`\n", exe ); + printf( "envSetNextLoad argv:\n`%s`\n", newargs ); + Host_ShutdownWithReason( finalmsg ); + envSetNextLoad( exe, newargs ); + exit( 0 ); +#else int i = 0; qboolean replacedArg = false; size_t exelen; @@ -586,14 +584,21 @@ qboolean Sys_NewInstance( const char *gamedir ) newargs[i++] = strdup( "-changegame" ); newargs[i] = NULL; +#if XASH_PSVITA + // under normal circumstances it's always going to be the same path + exe = strdup( "app0:/eboot.bin" ); + Host_ShutdownWithReason( finalmsg ); + sceAppMgrLoadExec( exe, newargs, NULL ); +#else exelen = wai_getExecutablePath( NULL, 0, NULL ); exe = malloc( exelen + 1 ); wai_getExecutablePath( exe, exelen, NULL ); exe[exelen] = 0; - Host_Shutdown(); + Host_ShutdownWithReason( finalmsg ); execv( exe, newargs ); +#endif // if execv returned, it's probably an error printf( "execv failed: %s", strerror( errno )); @@ -602,6 +607,35 @@ qboolean Sys_NewInstance( const char *gamedir ) free( newargs[i] ); free( newargs ); free( exe ); +#endif return false; } + + +/* +================== +Sys_GetNativeObject + +Get platform-specific native object +================== +*/ +void *Sys_GetNativeObject( const char *obj ) +{ + void *ptr; + + if( !COM_CheckString( obj )) + return NULL; + + ptr = FS_GetNativeObject( obj ); + + if( ptr ) + return ptr; + + // Backend should consider that obj is case-sensitive +#if XASH_ANDROID + ptr = Android_GetNativeObject( obj ); +#endif // XASH_ANDROID + + return ptr; +} diff --git a/engine/common/system.h b/engine/common/system.h index c6d8956d..0b79ed64 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -30,9 +30,6 @@ extern "C" { #include "crtlib.h" #include "platform/platform.h" -#define MSGBOX( x ) Platform_MessageBox( "Xash Error", (x), false ); -#define MSGBOX2( x ) Platform_MessageBox( "Host Error", (x), true ); -#define MSGBOX3( x ) Platform_MessageBox( "Host Recursive Error", (x), true ); #define ASSERT( exp ) if(!( exp )) Sys_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) /* @@ -44,56 +41,44 @@ NOTE: never change this structure because all dll descriptions in xash code writes into struct by offsets not names ======================================================================== */ +typedef struct dll_info_s +{ + const char *name; // name of library + const dllfunc_t *fcts; // list of dll exports + const size_t num_fcts; + qboolean crash; // crash if dll not found + void *link; // hinstance of loading library +} dll_info_t; -void Sys_Sleep( int msec ); +extern int error_on_exit; double Sys_DoubleTime( void ); char *Sys_GetClipboardData( void ); const char *Sys_GetCurrentUser( void ); int Sys_CheckParm( const char *parm ); -void Sys_Warn( const char *format, ... ) _format( 1 ); -void Sys_Error( const char *error, ... ) _format( 1 ); +void Sys_Warn( const char *format, ... ) FORMAT_CHECK( 1 ); +void Sys_Error( const char *error, ... ) FORMAT_CHECK( 1 ); qboolean Sys_LoadLibrary( dll_info_t *dll ); -void* Sys_GetProcAddress( dll_info_t *dll, const char* name ); qboolean Sys_FreeLibrary( dll_info_t *dll ); void Sys_ParseCommandLine( int argc, char **argv ); -void Sys_MergeCommandLine( void ); -void Sys_SetupCrashHandler( void ); -void Sys_RestoreCrashHandler( void ); +void Sys_DebugBreak( void ); #define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size ); qboolean Sys_GetIntFromCmdLine( const char *parm, int *out ); -void Sys_SendKeyEvents( void ); void Sys_Print( const char *pMsg ); -void Sys_PrintLog( const char *pMsg ); -void Sys_InitLog( void ); -void Sys_CloseLog( void ); -void Sys_Quit( void ) NORETURN; -qboolean Sys_NewInstance( const char *gamedir ); +void Sys_Quit( const char *reason ) NORETURN; +qboolean Sys_NewInstance( const char *gamedir, const char *finalmsg ); +void *Sys_GetNativeObject( const char *obj ); // // sys_con.c // char *Sys_Input( void ); void Sys_DestroyConsole( void ); -void Sys_CloseLog( void ); +void Sys_CloseLog( const char *finalmsg ); void Sys_InitLog( void ); void Sys_PrintLog( const char *pMsg ); int Sys_LogFileNo( void ); -// -// con_win.c -// -#if XASH_WIN32 -void Wcon_InitConsoleCommands( void ); -void Wcon_ShowConsole( qboolean show ); -void Wcon_CreateConsole( void ); -void Wcon_DestroyConsole( void ); -void Wcon_DisableInput( void ); -char *Wcon_Input( void ); -void Wcon_WinPrint( const char *pMsg ); -void Wcon_SetStatus( const char *pStatus ); -#endif - // text messages #define Msg Con_Printf diff --git a/engine/common/tests.h b/engine/common/tests.h index e58f20a5..1b5f1dde 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -26,7 +26,9 @@ extern struct tests_stats_s tests_stats; #define TASSERT( exp ) \ _TASSERT( !(exp), Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ) ) #define TASSERT_EQi( val1, val2 ) \ - _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, #val1, #val2 )) + _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, val1, val2 )) +#define TASSERT_EQp( val1, val2 ) \ + _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%p\" != \"%p\"\n", __FILE__, __LINE__, val1, val2 )) #define TASSERT_STR( str1, str2 ) \ _TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 ))) @@ -37,6 +39,31 @@ void Test_RunCmd( void ); void Test_RunCvar( void ); void Test_RunCon( void ); void Test_RunVOX( void ); +void Test_RunIPFilter( void ); +void Test_RunGamma( void ); +void Test_RunDelta( void ); +void Test_RunBuffer( void ); +void Test_RunMunge( void ); + +#define TEST_LIST_0 \ + Test_RunLibCommon(); \ + Test_RunCommon(); \ + Test_RunCmd(); \ + Test_RunCvar(); \ + Test_RunIPFilter(); \ + Test_RunBuffer(); \ + Test_RunDelta(); \ + Test_RunMunge(); + +#define TEST_LIST_0_CLIENT \ + Test_RunCon(); \ + Test_RunGamma(); + +#define TEST_LIST_1 \ + Test_RunImagelib(); + +#define TEST_LIST_1_CLIENT \ + Test_RunVOX(); #endif diff --git a/engine/common/whereami.c b/engine/common/whereami.c index 4d3401a1..3eb3f086 100644 --- a/engine/common/whereami.c +++ b/engine/common/whereami.c @@ -68,7 +68,13 @@ extern "C" { #if defined(_MSC_VER) #pragma warning(pop) #endif +#if (_MSC_VER >= 1900) #include +#else +#define bool int +#define false 0 +#define true 1 +#endif static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) { @@ -169,7 +175,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) return length; } -#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(__serenity__) || defined(__gnu_hurd__) || defined(WAI_USE_PROC_SELF_EXE) #include #include @@ -185,6 +191,10 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) #include #include +#if defined(__gnu_hurd__) && !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif // defined(__gnu_hurd__) && !defined(PATH_MAX) + #if !defined(WAI_PROC_SELF_EXE) #if defined(__sun) #define WAI_PROC_SELF_EXE "/proc/self/path/a.out" @@ -243,6 +253,11 @@ int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) #endif #endif +#if !defined(WAI_STRINGIZE) +#define WAI_STRINGIZE(s) +#define WAI_STRINGIZE_(s) #s +#endif + #if defined(__ANDROID__) || defined(ANDROID) #include #include @@ -265,20 +280,20 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) for (;;) { - char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; - uint64_t low, high; + char buffer[128 + PATH_MAX]; + uintptr_t low, high; char perms[5]; uint64_t offset; - uint32_t major, minor; - char path[PATH_MAX]; - uint32_t inode; + uint32_t major, minor, inode; + char path[PATH_MAX + 1]; if (!fgets(buffer, sizeof(buffer), maps)) break; - if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + 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) { - uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + void* _addr = WAI_RETURN_ADDRESS(); + uintptr_t addr = (uintptr_t)_addr; if (low <= addr && addr <= high) { char* resolved; @@ -632,12 +647,14 @@ int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) else { const char* PATH = getenv("PATH"); + const char* begin; + size_t argv0_length; if (!PATH) break; - size_t argv0_length = strlen(argv[0]); + argv0_length = strlen(argv[0]); - const char* begin = PATH; + begin = PATH; while (1) { const char* separator = strchr(begin, ':'); @@ -795,6 +812,49 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) return length; } +#elif defined(__sgi) + +/* + * These functions are stubbed for now to get the code compiling. + * In the future it may be possible to get these working in some way. + * Current ideas are checking the working directory for a binary with + * the same executed name and reading links, or worst case just searching + * through the entirety of the filesystem that's readable by the user. + * + * I'm not sure it's actually possible to find the absolute path via a + * direct method on IRIX. Its implementation of /proc is a fairly barebones + * SVR4 implementation. Other UNIXes (e.g. Solaris) have extensions to /proc + * that make finding the absolute path possible but these don't exist on IRIX. + */ + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + return -1; +} + +#elif defined(__SWITCH__) || defined(__vita__) || defined(__EMSCRIPTEN__) + +/* Not possible on this platform */ + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + return -1; +} + #else #error unsupported platform diff --git a/engine/common/whereami.h b/engine/common/whereami.h index 670db54c..8e58c818 100644 --- a/engine/common/whereami.h +++ b/engine/common/whereami.h @@ -31,7 +31,8 @@ extern "C" { * @param out destination buffer, optional * @param capacity destination buffer capacity * @param dirname_length optional recipient for the length of the dirname part - * of the path. + * of the path. Available only when `capacity` is large enough to retrieve the + * path. * * @return the length of the executable path on success (without a terminal NUL * character), otherwise `-1` @@ -52,7 +53,8 @@ int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); * @param out destination buffer, optional * @param capacity destination buffer capacity * @param dirname_length optional recipient for the length of the dirname part - * of the path. + * of the path. Available only when `capacity` is large enough to retrieve the + * path. * * @return the length of the module path on success (without a terminal NUL * character), otherwise `-1` diff --git a/engine/common/world.c b/engine/common/world.c index 592c39db..06275cb4 100644 --- a/engine/common/world.c +++ b/engine/common/world.c @@ -20,104 +20,6 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "studio.h" -// just for debug -const char *et_name[] = -{ - "normal", - "player", - "tempentity", - "beam", - "fragmented", -}; - -/* -=============================================================================== - - ENTITY LINKING - -=============================================================================== -*/ -/* -=============== -ClearLink - -ClearLink is used for new headnodes -=============== -*/ -void ClearLink( link_t *l ) -{ - l->prev = l->next = l; -} - -/* -=============== -RemoveLink - -remove link from chain -=============== -*/ -void RemoveLink( link_t *l ) -{ - l->next->prev = l->prev; - l->prev->next = l->next; -} - -/* -=============== -InsertLinkBefore - -kept trigger and solid entities seperate -=============== -*/ -void InsertLinkBefore( link_t *l, link_t *before ) -{ - l->next = before; - l->prev = before->prev; - l->prev->next = l; - l->next->prev = l; -} - -/* -================== -World_MoveBounds -================== -*/ -void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ) -{ - int i; - - for( i = 0; i < 3; i++ ) - { - if( end[i] > start[i] ) - { - boxmins[i] = start[i] + mins[i] - 1.0f; - boxmaxs[i] = end[i] + maxs[i] + 1.0f; - } - else - { - boxmins[i] = end[i] + mins[i] - 1.0f; - boxmaxs[i] = start[i] + maxs[i] + 1.0f; - } - } -} - -trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch ) -{ - if( trace->allsolid || trace->startsolid || trace->fraction < cliptrace->fraction ) - { - trace->ent = touch; - - if( cliptrace->startsolid ) - { - *cliptrace = *trace; - cliptrace->startsolid = true; - } - else *cliptrace = *trace; - } - - return *cliptrace; -} - /* ================== World_TransformAABB @@ -164,31 +66,3 @@ void World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t m } } } - -/* -================== -RankForContents - -Used for determine contents priority -================== -*/ -int RankForContents( int contents ) -{ - switch( contents ) - { - case CONTENTS_EMPTY: return 0; - case CONTENTS_WATER: return 1; - case CONTENTS_TRANSLUCENT: return 2; - case CONTENTS_CURRENT_0: return 3; - case CONTENTS_CURRENT_90: return 4; - case CONTENTS_CURRENT_180: return 5; - case CONTENTS_CURRENT_270: return 6; - case CONTENTS_CURRENT_UP: return 7; - case CONTENTS_CURRENT_DOWN: return 8; - case CONTENTS_SLIME: return 9; - case CONTENTS_LAVA: return 10; - case CONTENTS_SKY: return 11; - case CONTENTS_SOLID: return 12; - default: return 13; // any user contents has more priority than default - } -} diff --git a/engine/common/world.h b/engine/common/world.h index d3a10ce5..30b30bcf 100644 --- a/engine/common/world.h +++ b/engine/common/world.h @@ -35,18 +35,72 @@ ENTITY AREA CHECKING #include "lightstyle.h" -extern const char *et_name[]; - -// linked list -void InsertLinkBefore( link_t *l, link_t *before ); -void RemoveLink( link_t *l ); -void ClearLink( link_t *l ); - // trace common -void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ); +static inline void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( end[i] > start[i] ) + { + boxmins[i] = start[i] + mins[i] - 1.0f; + boxmaxs[i] = end[i] + maxs[i] + 1.0f; + } + else + { + boxmins[i] = end[i] + mins[i] - 1.0f; + boxmaxs[i] = start[i] + maxs[i] + 1.0f; + } + } +} + +static inline trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch ) +{ + if( trace->allsolid || trace->startsolid || trace->fraction < cliptrace->fraction ) + { + trace->ent = touch; + + if( cliptrace->startsolid ) + { + *cliptrace = *trace; + cliptrace->startsolid = true; + } + else *cliptrace = *trace; + } + + return *cliptrace; +} + +/* +================== +RankForContents + +Used for determine contents priority +================== +*/ +static inline int RankForContents( int contents ) +{ + switch( contents ) + { + case CONTENTS_EMPTY: return 0; + case CONTENTS_WATER: return 1; + case CONTENTS_TRANSLUCENT: return 2; + case CONTENTS_CURRENT_0: return 3; + case CONTENTS_CURRENT_90: return 4; + case CONTENTS_CURRENT_180: return 5; + case CONTENTS_CURRENT_270: return 6; + case CONTENTS_CURRENT_UP: return 7; + case CONTENTS_CURRENT_DOWN: return 8; + case CONTENTS_SLIME: return 9; + case CONTENTS_LAVA: return 10; + case CONTENTS_SKY: return 11; + case CONTENTS_SOLID: return 12; + default: return 13; // any user contents has more priority than default + } +} + void World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t maxs, vec3_t outmins, vec3_t outmaxs ); -trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch ); -int RankForContents( int contents ); #define check_angles( x ) ( (int)x == 90 || (int)x == 180 || (int)x == 270 || (int)x == -90 || (int)x == -180 || (int)x == -270 ) diff --git a/engine/common/zone.c b/engine/common/zone.c index 4ca83f65..f091b1ae 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -1,6 +1,9 @@ /* zone.c - zone memory allocation from DarkPlaces +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,67 +18,156 @@ GNU General Public License for more details. #include "common.h" -#define MEMHEADER_SENTINEL1 0xDEADF00D -#define MEMHEADER_SENTINEL2 0xDF +#define MEMHEADER_SENTINEL1 0xA1BAU +#define MEMHEADER_SENTINEL2 0xDFU #ifdef XASH_CUSTOM_SWAP #include "platform/swap/swap.h" #define Q_malloc SWAP_Malloc #define Q_free SWAP_Free + +static void *Q_realloc( void *mem, size_t size ) +{ + void *newmem; + + if( mem && size == 0 ) + { + Q_free( mem ); + return NULL; + } + + newmem = Q_malloc( size ); + if( mem && newmem ) + { + memcpy( newmem, mem, size ); + Q_free( mem ); + } + + return newmem; +} #else #define Q_malloc malloc #define Q_free free +#define Q_realloc realloc #endif +// keep this structure as compact as possible while keeping it aligned +// on ILP32 it's 24 bytes, which is aligned to 8 byte boundary +// on LP64 it's 40 bytes, which is also aligned to 8 byte boundary typedef struct memheader_s { - struct memheader_s *next; // next and previous memheaders in chain belonging to pool - struct memheader_s *prev; - struct mempool_s *pool; // pool this memheader belongs to - size_t size; // size of the memory after the header (excluding header and sentinel2) - const char *filename; // file name and line where Mem_Alloc was called - uint fileline; - uint sentinel1; // should always be MEMHEADER_SENTINEL1 - + struct memheader_s *next, *prev; // next and previous memheaders in chain belonging to pool + const char *filename; // file name and line where Mem_Alloc was called + size_t size; // size of the memory after the header (excluding header and sentinel2) + poolhandle_t poolptr; // pool this memheader belongs to + uint16_t fileline; + uint16_t sentinel1; // must be equal to MEMHEADER_SENTINEL1 // immediately followed by data, which is followed by a MEMHEADER_SENTINEL2 byte } memheader_t; +STATIC_CHECK_SIZEOF( memheader_t, 24, 40 ); + typedef struct mempool_s { - uint sentinel1; // should always be MEMHEADER_SENTINEL1 - struct memheader_s *chain; // chain of individual memory allocations - size_t totalsize; // total memory allocated in this pool (inside memheaders) - size_t realsize; // total memory allocated in this pool (actual malloc total) - size_t lastchecksize; // updated each time the pool is displayed by memlist - struct mempool_s *next; // linked into global mempool list - const char *filename; // file name and line where Mem_AllocPool was called - int fileline; - poolhandle_t idx; - char name[64]; // name of the pool - uint sentinel2; // should always be MEMHEADER_SENTINEL1 + struct memheader_s *chain; // chain of individual memory allocations + size_t totalsize; // total memory allocated in this pool (inside memheaders) + size_t realsize; // total memory allocated in this pool (actual malloc total) + size_t lastchecksize; // updated each time the pool is displayed by memlist + const char *filename; // file name and line where Mem_AllocPool was called + int fileline; + char name[64]; // name of the pool } mempool_t; static mempool_t *poolchain = NULL; // critical stuff +static size_t poolcount = 0; // a1ba: due to mempool being passed with the model through reused 32-bit field // which makes engine incompatible with 64-bit pointers I changed mempool type // from pointer to 32-bit handle, thankfully mempool structure is private -// But! Mempools are handled through linked list so we can't index them safely -static uint lastidx = 0; - static mempool_t *Mem_FindPool( poolhandle_t poolptr ) { - mempool_t *pool; + if( likely( poolptr > 0 && poolptr <= poolcount )) + return &poolchain[poolptr - 1]; - for( pool = poolchain; pool; pool = pool->next ) + Sys_Error( "%s: not allocated or double freed pool %d", __func__, poolptr ); + return NULL; +} + +static poolhandle_t Mem_PoolIndex( mempool_t *mempool ) +{ + return (poolhandle_t)(mempool - poolchain) + 1; +} + +static inline void Mem_PoolAdd( mempool_t *pool, size_t size ) +{ + pool->totalsize += size; + pool->realsize += sizeof( memheader_t ) + size + sizeof( byte ); +} + +static inline void Mem_PoolSubtract( mempool_t *pool, size_t size ) +{ + pool->totalsize -= size; + pool->realsize -= sizeof( memheader_t ) + size + sizeof( byte ); +} + +static inline void Mem_PoolLinkAlloc( mempool_t *pool, memheader_t *mem ) +{ + mem->next = pool->chain; + if( mem->next ) mem->next->prev = mem; + pool->chain = mem; + mem->prev = NULL; + mem->poolptr = Mem_PoolIndex( pool ); +} + +static inline void Mem_PoolUnlinkAlloc( mempool_t *pool, memheader_t *mem ) +{ + if( mem->next ) mem->next->prev = mem->prev; + if( mem->prev ) mem->prev->next = mem->next; + else pool->chain = mem->next; + mem->poolptr = 0; +} + +static inline void Mem_InitAlloc( memheader_t *mem, size_t size, const char *filename, int fileline ) +{ + mem->size = size; + mem->filename = filename; + mem->fileline = fileline; + mem->sentinel1 = MEMHEADER_SENTINEL1; + *((byte *)mem + sizeof( memheader_t ) + mem->size ) = MEMHEADER_SENTINEL2; +} + +static const char *Mem_CheckFilename( const char *filename ) +{ + static const char *dummy = "\0"; + + if( !COM_CheckString( filename )) + return dummy; + + if( memchr( filename, '\0', MAX_OSPATH ) != NULL ) + return filename; + + return dummy; +} + +static qboolean Mem_CheckAllocHeader( const char *func, const memheader_t *mem, const char *filename, int fileline ) +{ + const char *memfilename; + + if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) { - if( pool->idx == poolptr ) - return pool; + memfilename = Mem_CheckFilename( mem->filename ); + Sys_Error( "%s: trashed header sentinel 1 (alloc at %s:%i, check at %s:%i)\n", func, memfilename, mem->fileline, filename, fileline ); + return false; } - Sys_Error( "%s: not allocated or double freed pool %d", __FUNCTION__, poolptr ); + if( *((byte *)mem + sizeof( memheader_t ) + mem->size ) != MEMHEADER_SENTINEL2 ) + { + memfilename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args + Sys_Error( "%s: trashed header sentinel 2 (alloc at %s:%i, check at %s:%i)\n", func, memfilename, mem->fileline, filename, fileline ); + return false; + } - return NULL; + return true; } void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) @@ -83,178 +175,224 @@ void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char memheader_t *mem; mempool_t *pool; - if( size <= 0 ) return NULL; - if( !poolptr ) Sys_Error( "Mem_Alloc: pool == NULL (alloc at %s:%i)\n", filename, fileline ); + if( size <= 0 ) + return NULL; + + if( unlikely( !poolptr )) + { + Sys_Error( "%s: pool == NULL (alloc at %s:%i)\n", __func__, filename, fileline ); + return NULL; + } pool = Mem_FindPool( poolptr ); + if( !pool ) + return NULL; - pool->totalsize += size; + mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( byte )); + if( mem == NULL ) + { + Sys_Error( "%s: out of memory (alloc size %s at %s:%i)\n", __func__, Q_memprint( size ), filename, fileline ); + return NULL; + } - // big allocations are not clumped - pool->realsize += sizeof( memheader_t ) + size + sizeof( size_t ); - mem = (memheader_t *)Q_malloc( sizeof( memheader_t ) + size + sizeof( size_t )); - if( mem == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); + Mem_InitAlloc( mem, size, filename, fileline ); + + Mem_PoolAdd( pool, size ); + Mem_PoolLinkAlloc( pool, mem ); - mem->filename = filename; - mem->fileline = fileline; - mem->size = size; - mem->pool = pool; - mem->sentinel1 = MEMHEADER_SENTINEL1; - // we have to use only a single byte for this sentinel, because it may not be aligned - // and some platforms can't use unaligned accesses - *((byte *)mem + sizeof( memheader_t ) + mem->size ) = MEMHEADER_SENTINEL2; - // append to head of list - mem->next = pool->chain; - mem->prev = NULL; - pool->chain = mem; - if( mem->next ) mem->next->prev = mem; if( clear ) memset((void *)((byte *)mem + sizeof( memheader_t )), 0, mem->size ); return (void *)((byte *)mem + sizeof( memheader_t )); } -static const char *Mem_CheckFilename( const char *filename ) -{ - static const char *dummy = "\0"; - const char *out = filename; - int i; - - if( !COM_CheckString( out )) - return dummy; - - for( i = 0; i < MAX_OSPATH; i++, out++ ) - { - if( *out == '\0' ) - return filename; // valid name - } - - return dummy; -} - static void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline ) { mempool_t *pool; - if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) - { - mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args - Sys_Error( "Mem_Free: trashed header sentinel 1 (alloc at %s:%i, free at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); - } + if( !Mem_CheckAllocHeader( __func__, mem, filename, fileline )) + return; - if( *((byte *)mem + sizeof( memheader_t ) + mem->size ) != MEMHEADER_SENTINEL2 ) - { - mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args - Sys_Error( "Mem_Free: trashed header sentinel 2 (alloc at %s:%i, free at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); - } + pool = Mem_FindPool( mem->poolptr ); + if( !pool ) + return; - pool = mem->pool; // unlink memheader from doubly linked list if(( mem->prev ? mem->prev->next != mem : pool->chain != mem ) || ( mem->next && mem->next->prev != mem )) - Sys_Error( "Mem_Free: not allocated or double freed (free at %s:%i)\n", filename, fileline ); + { + Sys_Error( "%s: not allocated or double freed (free at %s:%i)\n", __func__, filename, fileline ); + return; + } - if( mem->prev ) mem->prev->next = mem->next; - else pool->chain = mem->next; + Mem_PoolSubtract( pool, mem->size ); + Mem_PoolUnlinkAlloc( pool, mem ); - if( mem->next ) - mem->next->prev = mem->prev; - - // memheader has been unlinked, do the actual free now - pool->totalsize -= mem->size; - - pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( size_t ); Q_free( mem ); } void _Mem_Free( void *data, const char *filename, int fileline ) { - if( data == NULL ) Sys_Error( "Mem_Free: data == NULL (called at %s:%i)\n", filename, fileline ); + if( data == NULL ) + return; + Mem_FreeBlock((memheader_t *)((byte *)data - sizeof( memheader_t )), filename, fileline ); } -void *_Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +static void Mem_MigratePool( poolhandle_t newpoolptr, memheader_t *mem, const char *filename, int fileline ) { - memheader_t *memhdr = NULL; - char *nb; + mempool_t *oldpool = Mem_FindPool( mem->poolptr ); + mempool_t *newpool = Mem_FindPool( newpoolptr ); - if( size <= 0 ) return memptr; // no need to reallocate + // dettach allocation from one pool and reattach it to new pool + // might be made into public function at some point - if( memptr ) + Mem_PoolUnlinkAlloc( oldpool, mem ); + Mem_PoolSubtract( oldpool, mem->size ); + + Mem_PoolLinkAlloc( newpool, mem ); + Mem_PoolAdd( newpool, mem->size ); +} + +void *_Mem_Realloc( poolhandle_t poolptr, void *data, size_t size, qboolean clear, const char *filename, int fileline ) +{ + memheader_t *mem; + uintptr_t oldmem; + mempool_t *pool; + size_t oldsize; + + if( size <= 0 ) + return data; // no need to reallocate + + if( unlikely( !poolptr )) { - memhdr = (memheader_t *)((byte *)memptr - sizeof( memheader_t )); - if( size == memhdr->size ) return memptr; + Sys_Error( "%s: pool == NULL (alloc at %s:%i)\n", __func__, filename, fileline ); + return NULL; } - nb = _Mem_Alloc( poolptr, size, clear, filename, fileline ); + if( !data ) + return _Mem_Alloc( poolptr, size, clear, filename, fileline ); - if( memptr ) // first allocate? + mem = (memheader_t *)((byte *)data - sizeof( memheader_t )); + + if( !Mem_CheckAllocHeader( __func__, mem, filename, fileline )) + return NULL; + + // migrate pool if requested, even if no reallocation needed + if( mem->poolptr != poolptr ) + Mem_MigratePool( poolptr, mem, filename, fileline ); + + oldsize = mem->size; + if( size == oldsize ) + return data; + + pool = Mem_FindPool( poolptr ); + + oldmem = (uintptr_t)mem; + mem = Q_realloc( mem, sizeof( memheader_t ) + size + sizeof( byte )); + + if( mem == NULL ) { - size_t newsize = memhdr->size < size ? memhdr->size : size; // upper data can be trucnated! - memcpy( nb, memptr, newsize ); - _Mem_Free( memptr, filename, fileline ); // free unused old block + Sys_Error( "%s: out of memory (alloc size %s at %s:%i)\n", __func__, Q_memprint( size ), filename, fileline ); + return NULL; } - return (void *)nb; + // Con_Printf( S_NOTE "%s: mem %s oldmem, size before %zu now %zu (alloc at %s:%i)\n", + // __func__, (uintptr_t)mem != oldmem ? "!=" : "==", oldsize, size, filename, fileline ); + + Mem_InitAlloc( mem, size, filename, fileline ); + + if( size > oldsize ) + { + Mem_PoolAdd( pool, size - oldsize ); + + if( clear ) + memset((byte *)mem + sizeof( memheader_t ) + oldsize, 0, size - oldsize ); + } + else Mem_PoolSubtract( pool, oldsize - size ); + + if( oldmem != (uintptr_t)mem ) // just relink pointers + { + if( mem->next ) mem->next->prev = mem; + if( mem->prev ) mem->prev->next = mem; + else pool->chain = mem; + } + + return (void *)((byte *)mem + sizeof( memheader_t )); +} + +static poolhandle_t Mem_InitPool( mempool_t *pool, const char *name, const char *filename, int fileline ) +{ + memset( pool, 0, sizeof( *pool )); + + // fill header + pool->filename = filename; + pool->fileline = fileline; + pool->realsize = sizeof( mempool_t ); + Q_strncpy( pool->name, name, sizeof( pool->name )); + + return Mem_PoolIndex( pool ); } poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) { mempool_t *pool; + size_t i; - pool = (mempool_t *)Q_malloc( sizeof( mempool_t )); + for( i = 0, pool = poolchain; i < poolcount; i++, pool++ ) + { + if( pool->filename == NULL ) + return Mem_InitPool( pool, name, filename, fileline ); + } + + pool = (mempool_t *)Q_realloc( poolchain, sizeof( *poolchain ) * ( poolcount + 1 )); if( pool == NULL ) { - Sys_Error( "Mem_AllocPool: out of memory (allocpool at %s:%i)\n", filename, fileline ); + Sys_Error( "%s: out of memory (allocpool at %s:%i)\n", __func__, filename, fileline ); return 0; } - memset( pool, 0, sizeof( mempool_t )); - // fill header - pool->sentinel1 = MEMHEADER_SENTINEL1; - pool->sentinel2 = MEMHEADER_SENTINEL1; - pool->filename = filename; - pool->fileline = fileline; - pool->chain = NULL; - pool->totalsize = 0; - pool->realsize = sizeof( mempool_t ); - Q_strncpy( pool->name, name, sizeof( pool->name )); - pool->next = poolchain; - pool->idx = ++lastidx; poolchain = pool; - - return pool->idx; + pool = &poolchain[poolcount++]; + return Mem_InitPool( pool, name, filename, fileline ); } void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) { mempool_t *pool; - mempool_t **chainaddress; if( *poolptr && ( pool = Mem_FindPool( *poolptr ))) { - // unlink pool from chain - for( chainaddress = &poolchain; *chainaddress && *chainaddress != pool; chainaddress = &((*chainaddress)->next)); - if( *chainaddress != pool ) Sys_Error( "Mem_FreePool: pool already free (freepool at %s:%i)\n", filename, fileline ); - if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); - if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); - *chainaddress = pool->next; + if( !pool->filename ) + { + Sys_Error( "%s: pool already free (freepool at %s:%i)\n", __func__, filename, fileline ); + *poolptr = 0; + return; + } // free memory owned by the pool - while( pool->chain ) Mem_FreeBlock( pool->chain, filename, fileline ); + while( pool->chain ) + Mem_FreeBlock( pool->chain, filename, fileline ); + // free the pool itself memset( pool, 0xBF, sizeof( mempool_t )); - Q_free( pool ); + pool->chain = NULL; + pool->filename = NULL; // mark as reusable *poolptr = 0; } } void _Mem_EmptyPool( poolhandle_t poolptr, const char *filename, int fileline ) { - mempool_t *pool = Mem_FindPool( poolptr ); - if( !poolptr ) Sys_Error( "Mem_EmptyPool: pool == NULL (emptypool at %s:%i)\n", filename, fileline ); + mempool_t *pool; + if( unlikely( !poolptr )) + { + Sys_Error( "%s: pool == NULL (emptypool at %s:%i)\n", __func__, filename, fileline ); + return; + } - if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); - if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + pool = Mem_FindPool( poolptr ); + if( !pool ) + return; // free memory owned by the pool while( pool->chain ) Mem_FreeBlock( pool->chain, filename, fileline ); @@ -269,14 +407,20 @@ static qboolean Mem_CheckAlloc( mempool_t *pool, void *data ) // search only one pool target = (memheader_t *)((byte *)data - sizeof( memheader_t )); for( header = pool->chain; header; header = header->next ) - if( header == target ) return true; + { + if( header == target ) + return true; + } } else { // search all pools - for( pool = poolchain; pool; pool = pool->next ) + size_t i; + for( i = 0, pool = poolchain; i < poolcount; i++, pool++ ) + { if( Mem_CheckAlloc( pool, data )) return true; + } } return false; } @@ -289,65 +433,41 @@ Check pointer for memory qboolean Mem_IsAllocatedExt( poolhandle_t poolptr, void *data ) { mempool_t *pool = NULL; - if( poolptr ) pool = Mem_FindPool( poolptr ); + + if( poolptr ) + pool = Mem_FindPool( poolptr ); return Mem_CheckAlloc( pool, data ); } -static void Mem_CheckHeaderSentinels( void *data, const char *filename, int fileline ) -{ - memheader_t *mem; - - if( data == NULL ) - Sys_Error( "Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)\n", filename, fileline ); - - mem = (memheader_t *)((byte *) data - sizeof(memheader_t)); - - if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) - { - mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args - Sys_Error( "Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); - } - - if( *((byte *)mem + sizeof(memheader_t) + mem->size) != MEMHEADER_SENTINEL2 ) - { - mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args - Sys_Error( "Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); - } -} - void _Mem_Check( const char *filename, int fileline ) { memheader_t *mem; mempool_t *pool; + size_t i; - for( pool = poolchain; pool; pool = pool->next ) - { - if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) - Sys_Error( "Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); - if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) - Sys_Error( "Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); - } - - for( pool = poolchain; pool; pool = pool->next ) + for( i = 0, pool = poolchain; i < poolcount; i++, pool++ ) for( mem = pool->chain; mem; mem = mem->next ) - Mem_CheckHeaderSentinels((void *)((byte *) mem + sizeof(memheader_t)), filename, fileline ); + Mem_CheckAllocHeader( __func__, mem, filename, fileline ); } void Mem_PrintStats( void ) { - size_t count = 0, size = 0, realsize = 0; + size_t count = 0, size = 0, realsize = 0, i; mempool_t *pool; Mem_Check(); - for( pool = poolchain; pool; pool = pool->next ) + for( i = 0, pool = poolchain; i < poolcount; i++, pool++ ) { + if( !pool->filename ) + continue; + count++; size += pool->totalsize; realsize += pool->realsize; } - Con_Printf( "^3%lu^7 memory pools, totalling: ^1%s\n", count, Q_memprint( size )); + Con_Printf( "^3%zu^7 memory pools, totalling: ^1%s\n", count, Q_memprint( size )); Con_Printf( "total allocated size: ^1%s\n", Q_memprint( realsize )); } @@ -355,31 +475,38 @@ void Mem_PrintList( size_t minallocationsize ) { mempool_t *pool; memheader_t *mem; + size_t i; Mem_Check(); - Con_Printf( "memory pool list:\n"" ^3size name\n"); - for( pool = poolchain; pool; pool = pool->next ) + Con_Printf( "memory pool list:\n" ); + Con_Printf( "\t^3size\t\t\t\tname\n"); + for( i = 0, pool = poolchain; i < poolcount; i++, pool++ ) { long changed_size = (long)pool->totalsize - (long)pool->lastchecksize; + if( !pool->filename ) + continue; + // poolnames can contain color symbols, make sure what color is reset - if( changed_size != 0 ) + if( pool->lastchecksize != 0 && changed_size != 0 ) { char sign = (changed_size < 0) ? '-' : '+'; - Con_Printf( "%10s (%10s actual) %s (^7%c%s change)\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), - pool->name, sign, Q_memprint( abs( changed_size ))); + Con_Printf( "%10s (%10s real)\t%s (^7%c%s change)\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), + pool->name, sign, Q_memprint( abs( changed_size ))); } else { - Con_Printf( "%5s (%5s actual) %s\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), pool->name ); + Con_Printf( "%10s (%10s real)\t%s\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), pool->name ); } pool->lastchecksize = pool->totalsize; for( mem = pool->chain; mem; mem = mem->next ) + { if( mem->size >= minallocationsize ) Con_Printf( "%10s allocated at %s:%i\n", Q_memprint( mem->size ), mem->filename, mem->fileline ); + } } } @@ -391,4 +518,5 @@ Memory_Init void Memory_Init( void ) { poolchain = NULL; // init mem chain + poolcount = 0; } diff --git a/engine/custom.h b/engine/custom.h index da01f745..604ad305 100644 --- a/engine/custom.h +++ b/engine/custom.h @@ -53,6 +53,10 @@ typedef struct resourceinfo_s #define RES_ALWAYS (1<<5) // Download always even if available on client #define RES_CHECKFILE (1<<7) // Check file on client +// this archive was already mounted after rescan +// only makes sense for archives and on client +#define RES_EXTRA_ARCHIVE_CHECKED BIT( 0 ) + typedef struct resource_s { char szFileName[64]; // File name to download/precache. @@ -67,6 +71,7 @@ typedef struct resource_s // if it's a custom resource. unsigned char rguc_reserved[32]; // For future expansion + unsigned short ucExtraFlags; // fwgs extension, doesn't change the size of struct because of compiler padding struct resource_s *pNext; // Next in chain. struct resource_s *pPrev; } resource_t; @@ -90,4 +95,7 @@ typedef struct customization_s #define FCUST_WIPEDATA ( 1<<1 ) #define FCUST_IGNOREINIT ( 1<<2 ) +STATIC_CHECK_SIZEOF( customization_t, 164, 192 ); +STATIC_CHECK_SIZEOF( resource_t, 136, 144 ); + #endif // CUSTOM_H diff --git a/engine/edict.h b/engine/edict.h index d74d08b1..d0e76715 100644 --- a/engine/edict.h +++ b/engine/edict.h @@ -16,11 +16,8 @@ #ifndef EDICT_H #define EDICT_H -#ifdef SUPPORT_BSP2_FORMAT -#define MAX_ENT_LEAFS 24 // Orignally was 16 -#else -#define MAX_ENT_LEAFS 48 -#endif +#define MAX_ENT_LEAFS_32 24 // Orignally was 16 +#define MAX_ENT_LEAFS_16 48 #include "progdefs.h" @@ -33,11 +30,12 @@ struct edict_s int headnode; // -1 to use normal leaf check int num_leafs; -#ifdef SUPPORT_BSP2_FORMAT - int leafnums[MAX_ENT_LEAFS]; -#else - short leafnums[MAX_ENT_LEAFS]; -#endif + union + { + int leafnums32[MAX_ENT_LEAFS_32]; + short leafnums16[MAX_ENT_LEAFS_16]; + }; + float freetime; // sv.time when the object was freed void* pvPrivateData; // Alloced and freed by engine, used by DLLs diff --git a/engine/eiface.h b/engine/eiface.h index e18cdedb..b0817de6 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -64,6 +64,7 @@ typedef enum force_exactfile, // File on client must exactly match server's file force_model_samebounds, // For model files only, the geometry must fit in the same bbox force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox + force_model_specifybounds_if_avail, } FORCE_TYPE; // Returned by TraceLine diff --git a/engine/keydefs.h b/engine/keydefs.h index 6bce443a..dc9dd354 100644 --- a/engine/keydefs.h +++ b/engine/keydefs.h @@ -1,174 +1,164 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ +/* +Copyright (C) 1997-2001 Id Software, Inc. -#ifndef KEYDEFS_H -#define KEYDEFS_H +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// taken from Quake-2 source code and modified for GoldSrc compatibility // // these are the key numbers that should be passed to Key_Event -// -#define K_TAB 9 -#define K_ENTER 13 -#define K_ESCAPE 27 -#define K_SPACE 32 -#define K_SCROLLOCK 70 - // normal keys should be passed as lowercased ascii +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 +#define K_SCROLLLOCK 70 +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 -#define K_BACKSPACE 127 -#define K_UPARROW 128 -#define K_DOWNARROW 129 -#define K_LEFTARROW 130 -#define K_RIGHTARROW 131 - -#define K_ALT 132 -#define K_CTRL 133 -#define K_SHIFT 134 -#define K_F1 135 -#define K_F2 136 -#define K_F3 137 -#define K_F4 138 -#define K_F5 139 -#define K_F6 140 -#define K_F7 141 -#define K_F8 142 -#define K_F9 143 -#define K_F10 144 -#define K_F11 145 -#define K_F12 146 -#define K_INS 147 -#define K_DEL 148 -#define K_PGDN 149 -#define K_PGUP 150 -#define K_HOME 151 -#define K_END 152 - -#define K_KP_HOME 160 -#define K_KP_UPARROW 161 -#define K_KP_PGUP 162 -#define K_KP_LEFTARROW 163 -#define K_KP_5 164 +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 #define K_KP_RIGHTARROW 165 -#define K_KP_END 166 -#define K_KP_DOWNARROW 167 -#define K_KP_PGDN 168 -#define K_KP_ENTER 169 -#define K_KP_INS 170 -#define K_KP_DEL 171 -#define K_KP_SLASH 172 -#define K_KP_MINUS 173 -#define K_KP_PLUS 174 -#define K_CAPSLOCK 175 -#define K_KP_MUL 176 -#define K_WIN 177 -#define K_KP_NUMLOCK 178 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 +#define K_CAPSLOCK 175 +#define K_KP_MUL 176 +#define K_WIN 177 +#define K_KP_NUMLOCK 178 // // joystick buttons // -#define K_JOY1 203 // LTRIGGER (L2) -#define K_JOY2 204 // RTRIGGER (R2) -#define K_JOY3 205 -#define K_JOY4 206 +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 // // aux keys are for multi-buttoned joysticks to generate so they can use // the normal binding process // -#define K_AUX1 207 -#define K_A_BUTTON K_AUX1 +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 -#define K_AUX2 208 -#define K_B_BUTTON K_AUX2 - -#define K_AUX3 209 -#define K_X_BUTTON K_AUX3 - -#define K_AUX4 210 -#define K_Y_BUTTON K_AUX4 - -#define K_AUX5 211 -#define K_L1_BUTTON K_AUX5 - -#define K_AUX6 212 -#define K_R1_BUTTON K_AUX6 - -#define K_AUX7 213 -#define K_BACK_BUTTON K_AUX7 - -#define K_AUX8 214 -#define K_MODE_BUTTON K_AUX8 - -#define K_AUX9 215 -#define K_START_BUTTON K_AUX9 - -#define K_AUX10 216 -#define K_LSTICK K_AUX10 - -#define K_AUX11 217 -#define K_RSTICK K_AUX11 - -#define K_AUX12 218 -#define K_L2_BUTTON K_AUX12 - -#define K_AUX13 219 -#define K_R2_BUTTON K_AUX13 - -#define K_AUX14 220 -#define K_C_BUTTON K_AUX14 - -#define K_AUX15 221 -#define K_Z_BUTTON K_AUX15 - -#define K_AUX16 222 -#define K_DPAD_UP K_AUX16 - -#define K_AUX17 223 -#define K_DPAD_DOWN K_AUX17 - -#define K_AUX18 224 -#define K_DPAD_LEFT K_AUX18 - -#define K_AUX19 225 -#define K_DPAD_RIGHT K_AUX19 - -#define K_AUX20 226 -#define K_AUX21 227 -#define K_AUX22 228 -#define K_AUX23 229 -#define K_AUX24 230 -#define K_AUX25 231 -#define K_AUX26 232 -#define K_AUX27 233 -#define K_AUX28 234 -#define K_AUX29 235 -#define K_AUX30 236 -#define K_AUX31 237 -#define K_AUX32 238 -#define K_MWHEELDOWN 239 -#define K_MWHEELUP 240 - -#define K_PAUSE 255 +// +// button names for game pads +// +#define K_A_BUTTON K_AUX1 +#define K_B_BUTTON K_AUX2 +#define K_X_BUTTON K_AUX3 +#define K_Y_BUTTON K_AUX4 +#define K_L1_BUTTON K_AUX5 +#define K_R1_BUTTON K_AUX6 +#define K_BACK_BUTTON K_AUX7 +#define K_MODE_BUTTON K_AUX8 +#define K_START_BUTTON K_AUX9 +#define K_LSTICK K_AUX10 +#define K_RSTICK K_AUX11 +#define K_L2_BUTTON K_AUX12 +#define K_R2_BUTTON K_AUX13 +#define K_C_BUTTON K_AUX14 +#define K_Z_BUTTON K_AUX15 +#define K_DPAD_UP K_AUX16 +#define K_DPAD_DOWN K_AUX17 +#define K_DPAD_LEFT K_AUX18 +#define K_DPAD_RIGHT K_AUX19 +#define K_MISC_BUTTON K_AUX20 +#define K_PADDLE1_BUTTON K_AUX21 +#define K_PADDLE2_BUTTON K_AUX22 +#define K_PADDLE3_BUTTON K_AUX23 +#define K_PADDLE4_BUTTON K_AUX24 +#define K_TOUCHPAD K_AUX25 // // mouse buttons generate virtual keys // -#define K_MOUSE1 241 -#define K_MOUSE2 242 -#define K_MOUSE3 243 -#define K_MOUSE4 244 -#define K_MOUSE5 245 +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 +#define K_MOUSE1 241 +#define K_MOUSE2 242 +#define K_MOUSE3 243 +#define K_MOUSE4 244 +#define K_MOUSE5 245 -#endif//KEYDEFS_H +#define K_PAUSE 255 diff --git a/engine/menu_int.h b/engine/menu_int.h index d273d8f6..dba23f24 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -19,6 +19,7 @@ GNU General Public License for more details. #include "cvardef.h" #include "gameinfo.h" #include "wrect.h" +#include "net_api.h" // a macro for mainui_cpp, indicating that mainui should be compiled for // Xash3D 1.0 interface @@ -35,6 +36,7 @@ typedef int HIMAGE; // handle to a graphic // flags for COM_ParseFileSafe #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) +#define PFILE_IGNOREHASHCMT (1<<2) typedef struct ui_globalvars_s { @@ -75,7 +77,7 @@ typedef struct ui_enginefuncs_s // cvar handlers cvar_t* (*pfnRegisterVariable)( const char *szName, const char *szValue, int flags ); float (*pfnGetCvarFloat)( const char *szName ); - const char* (*pfnGetCvarString)( const char *szName ); + const char* (*pfnGetCvarString)( const char *szName ) PFN_RETURNS_NONNULL; void (*pfnCvarSetString)( const char *szName, const char *szValue ); void (*pfnCvarSetValue)( const char *szName, float flValue ); @@ -84,14 +86,14 @@ typedef struct ui_enginefuncs_s void (*pfnClientCmd)( int execute_now, const char *szCmdString ); void (*pfnDelCommand)( const char *cmd_name ); int (*pfnCmdArgc)( void ); - const char* (*pfnCmdArgv)( int argc ); - const char* (*pfnCmd_Args)( void ); + const char* (*pfnCmdArgv)( int argc ) PFN_RETURNS_NONNULL; + const char* (*pfnCmd_Args)( void ) PFN_RETURNS_NONNULL; // debug messages (in-menu shows only notify) - void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); - void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); - void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); + void (*Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); + void (*Con_DPrintf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) FORMAT_CHECK( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) FORMAT_CHECK( 2 ); // sound handlers void (*pfnPlayLocalSound)( const char *szSound ); @@ -117,7 +119,7 @@ typedef struct ui_enginefuncs_s int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); // misc handlers - void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ); + void (*pfnHostError)( const char *szFmt, ... ) FORMAT_CHECK( 1 ); int (*pfnFileExists)( const char *filename, int gamedironly ); void (*pfnGetGameDir)( char *szGetGameDir ); @@ -143,7 +145,7 @@ typedef struct ui_enginefuncs_s void *(*pfnKeyGetState)( const char *name ); // for mlook, klook etc // engine memory manager - void* (*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void* (*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 ); void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); // collect info from engine @@ -208,13 +210,23 @@ typedef struct ui_extendedfuncs_s { // new engine extended api start here // returns 1 if there are more in list, otherwise 0 - int (*pfnGetRenderers)( unsigned int num, char *shortName, size_t size1, char *readableName, size_t size2 ); - + int (*pfnGetRenderers)( unsigned int num, char *short_name, size_t size1, char *long_name, size_t size2 ); double (*pfnDoubleTime)( void ); - char *(*pfnParseFile)( char *data, char *buf, const int size, unsigned int flags, int *len ); - const char *(*pfnAdrToString)( const struct netadr_s a ); + // network address funcs + const char *(*pfnAdrToString)( const struct netadr_s a ) PFN_RETURNS_NONNULL; + int (*pfnCompareAdr)( const void *a, const void *b ); // netadr_t + void *(*pfnGetNativeObject)( const char *name ); + struct net_api_s *pNetAPI; + + // new mods info + gameinfo2_t *(*pfnGetGameInfo)( int gi_version ); // might return NULL if gi_version is unsupported + gameinfo2_t *(*pfnGetModInfo)( int gi_version, int mod_index ); // continiously call it until it returns null + + // returns 1 if cvar has read-only flag + // or -1 if cvar not found + int (*pfnIsCvarReadOnly)( const char *name ); } ui_extendedfuncs_t; // deprecated export from old engine diff --git a/engine/mobility_int.h b/engine/mobility_int.h index c6471dff..44d4686b 100644 --- a/engine/mobility_int.h +++ b/engine/mobility_int.h @@ -2,15 +2,30 @@ mobility_int.h - interface between engine and client for mobile platforms Copyright (C) 2015 a1batross -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +This is free and unencumbered software released into the public domain. -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to */ #pragma once @@ -39,6 +54,7 @@ extern "C" { // flags for COM_ParseFileSafe #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) +#define PFILE_IGNOREHASHCMT (1<<2) typedef struct mobile_engfuncs_s { diff --git a/engine/physint.h b/engine/physint.h index 9c246023..d5baeeab 100644 --- a/engine/physint.h +++ b/engine/physint.h @@ -50,6 +50,10 @@ GNU General Public License for more details. #define LUMP_SAVE_NO_DATA 7 #define LUMP_SAVE_CORRUPTED 8 +#ifndef ALLOC_CHECK +#define ALLOC_CHECK( x ) +#endif + typedef struct areanode_s { int axis; // -1 = leaf node @@ -86,7 +90,7 @@ typedef struct server_physics_api_s const byte *(*pfnGetTextureData)( unsigned int texnum ); // static allocations - void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 ); void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); // trace & contents @@ -107,7 +111,10 @@ typedef struct server_physics_api_s int (*pfnSaveFile)( const char *filename, const void *data, int len ); const byte *(*pfnLoadImagePixels)( const char *filename, int *width, int *height ); - const char* (*pfnGetModelName)( int modelindex ); + const char *(*pfnGetModelName)( int modelindex ); + + // FWGS extension + void *(*pfnGetNativeObject)( const char *object ); } server_physics_api_t; // physic callbacks diff --git a/engine/platform/android/android.c b/engine/platform/android/android.c index 5a61bccf..a843c0e5 100644 --- a/engine/platform/android/android.c +++ b/engine/platform/android/android.c @@ -13,603 +13,47 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "platform/platform.h" -#if !defined(XASH_DEDICATED) #include "input.h" #include "client.h" #include "sound.h" -#include "platform/android/android_priv.h" #include "errno.h" #include #include -#ifndef JNICALL -#define JNICALL // a1ba: workaround for my IDE, where Java files are not included -#define JNIEXPORT -#endif +#include +#include +#if XASH_SDL +#include +#endif // XASH_SDL -convar_t *android_sleep; - -static const int s_android_scantokey[] = +struct jnimethods_s { - 0, K_LEFTARROW, K_RIGHTARROW, K_AUX26, K_ESCAPE, // 0 - K_AUX26, K_AUX25, '0', '1', '2', // 5 - '3', '4', '5', '6', '7', // 10 - '8', '9', '*', '#', K_UPARROW, // 15 - K_DOWNARROW, K_LEFTARROW, K_RIGHTARROW, K_ENTER, K_AUX32, // 20 - K_AUX31, K_AUX29, K_AUX28, K_AUX27, 'a', // 25 - 'b', 'c', 'd', 'e', 'f', // 30 - 'g', 'h', 'i', 'j', 'k', // 35 - 'l', 'm', 'n', 'o', 'p', // 40 - 'q', 'r', 's', 't', 'u', // 45 - 'v', 'w', 'x', 'y', 'z', // 50 - ',', '.', K_ALT, K_ALT, K_SHIFT, // 55 - K_SHIFT, K_TAB, K_SPACE, 0, 0, // 60 - 0, K_ENTER, K_BACKSPACE, '`', '-', // 65 - '=', '[', ']', '\\', ';', // 70 - '\'', '/', '@', K_KP_NUMLOCK, 0, // 75 - 0, '+', '`', 0, 0, // 80 - 0, 0, 0, 0, 0, // 85 - 0, 0, K_PGUP, K_PGDN, 0, // 90 - 0, K_AUX1, K_AUX2, K_AUX14, K_AUX3, // 95 - K_AUX4, K_AUX15, K_AUX6, K_AUX7, K_JOY1, // 100 - K_JOY2, K_AUX10, K_AUX11, K_ESCAPE, K_ESCAPE, // 105 - 0, K_ESCAPE, K_DEL, K_CTRL, K_CTRL, // 110 - K_CAPSLOCK, 0, 0, 0, 0, // 115 - 0, K_PAUSE, K_HOME, K_END, K_INS, // 120 - 0, 0, 0, 0, 0, // 125 - 0, K_F1, K_F2, K_F3, K_F4, // 130 - K_F5, K_F6, K_F7, K_F8, K_F9, // 135 - K_F10, K_F11, K_F12, K_KP_NUMLOCK, K_KP_INS, // 140 - K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5, // 145 - K_KP_RIGHTARROW,K_KP_HOME, K_KP_UPARROW, K_KP_PGUP, K_KP_SLASH, // 150 - 0, K_KP_MINUS, K_KP_PLUS, K_KP_DEL, ',', // 155 - K_KP_ENTER, '=', '(', ')' -}; + JNIEnv *env; + jobject activity; + jclass actcls; + jmethodID loadAndroidID; + jmethodID getAndroidID; + jmethodID saveAndroidID; +} jni; -#define ANDROID_MAX_EVENTS 64 -#define MAX_FINGERS 10 - -typedef enum event_type +void Android_Init( void ) { - event_touch_down = 0, - event_touch_up, - event_touch_move, // compatible with touchEventType - event_key_down, - event_key_up, - event_set_pause, - event_resize, - event_joyhat, - event_joyball, - event_joybutton, - event_joyaxis, - event_joyadd, - event_joyremove, - event_onpause, - event_ondestroy, - event_onresume, - event_onfocuschange, -} eventtype_t; - -typedef struct touchevent_s -{ - float x; - float y; - float dx; - float dy; -} touchevent_t; - -typedef struct joyball_s -{ - short xrel; - short yrel; - byte ball; -} joyball_t; - -typedef struct joyhat_s -{ - byte hat; - byte key; -} joyhat_t; - -typedef struct joyaxis_s -{ - short val; - byte axis; -} joyaxis_t; - -typedef struct joybutton_s -{ - int down; - byte button; -} joybutton_t; - -typedef struct keyevent_s -{ - int code; -} keyevent_t; - -typedef struct event_s -{ - eventtype_t type; - int arg; - union - { - touchevent_t touch; - joyhat_t hat; - joyball_t ball; - joyaxis_t axis; - joybutton_t button; - keyevent_t key; - }; -} event_t; - -typedef struct finger_s -{ - float x, y; - qboolean down; -} finger_t; - -static struct { - pthread_mutex_t mutex; // this mutex is locked while not running frame, used for events synchronization - pthread_mutex_t framemutex; // this mutex is locked while engine is running and unlocked while it reading events, used for pause in background. - event_t queue[ANDROID_MAX_EVENTS]; - volatile int count; - finger_t fingers[MAX_FINGERS]; - char inputtext[256]; - float mousex, mousey; -} events = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; - -struct jnimethods_s jni; -struct jnimouse_s jnimouse; - -#define Android_Lock() pthread_mutex_lock(&events.mutex); -#define Android_Unlock() pthread_mutex_unlock(&events.mutex); -#define Android_PushEvent() Android_Unlock() - -typedef void (*pfnChangeGame)( const char *progname ); -int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); - -/* -======================== -Android_AllocEvent - -Lock event queue and return pointer to next event. -Caller must do Android_PushEvent() to unlock queue after setting parameters. -======================== -*/ -event_t *Android_AllocEvent( void ) -{ - Android_Lock(); - if( events.count == ANDROID_MAX_EVENTS ) - { - events.count--; //override last event - __android_log_print( ANDROID_LOG_ERROR, "Xash", "Too many events!!!" ); - } - return &events.queue[ events.count++ ]; -} - - -/* -===================================================== -JNI callbacks - -On application start, setenv and onNativeResize called from -ui thread to set up engine configuration -nativeInit called directly from engine thread and will not return until exit. -These functions may be called from other threads at any time: -nativeKey -nativeTouch -onNativeResize -nativeString -nativeSetPause -===================================================== -*/ -#define VA_ARGS(...) , ##__VA_ARGS__ // GCC extension -#define DECLARE_JNI_INTERFACE( ret, name, ... ) \ - JNIEXPORT ret JNICALL Java_su_xash_engine_XashActivity_##name( JNIEnv *env, jclass clazz VA_ARGS(__VA_ARGS__) ) - -DECLARE_JNI_INTERFACE( int, nativeInit, jobject array ) -{ - int i; - int argc; - int status; - /* Prepare the arguments. */ - - int len = (*env)->GetArrayLength(env, array); - char** argv = calloc( 1 + len + 1, sizeof( char ** )); - argc = 0; - argv[argc++] = strdup("app_process"); - for (i = 0; i < len; ++i) { - const char* utf; - char* arg = NULL; - jstring string = (*env)->GetObjectArrayElement(env, array, i); - if (string) { - utf = (*env)->GetStringUTFChars(env, string, 0); - if (utf) { - arg = strdup(utf); - (*env)->ReleaseStringUTFChars(env, string, utf); - } - (*env)->DeleteLocalRef(env, string); - } - if (!arg) { - arg = strdup(""); - } - argv[argc++] = arg; - } - argv[argc] = NULL; - prctl(PR_SET_DUMPABLE, 1); - - /* Init callbacks. */ - - jni.env = env; - jni.actcls = (*env)->FindClass(env, "su/xash/engine/XashActivity"); - jni.enableTextInput = (*env)->GetStaticMethodID(env, jni.actcls, "showKeyboard", "(I)V"); - jni.vibrate = (*env)->GetStaticMethodID(env, jni.actcls, "vibrate", "(I)V" ); - jni.messageBox = (*env)->GetStaticMethodID(env, jni.actcls, "messageBox", "(Ljava/lang/String;Ljava/lang/String;)V"); - jni.notify = (*env)->GetStaticMethodID(env, jni.actcls, "engineThreadNotify", "()V"); - jni.setTitle = (*env)->GetStaticMethodID(env, jni.actcls, "setTitle", "(Ljava/lang/String;)V"); - jni.setIcon = (*env)->GetStaticMethodID(env, jni.actcls, "setIcon", "(Ljava/lang/String;)V"); - jni.getAndroidId = (*env)->GetStaticMethodID(env, jni.actcls, "getAndroidID", "()Ljava/lang/String;"); - jni.saveID = (*env)->GetStaticMethodID(env, jni.actcls, "saveID", "(Ljava/lang/String;)V"); - jni.loadID = (*env)->GetStaticMethodID(env, jni.actcls, "loadID", "()Ljava/lang/String;"); - jni.showMouse = (*env)->GetStaticMethodID(env, jni.actcls, "showMouse", "(I)V"); - jni.shellExecute = (*env)->GetStaticMethodID(env, jni.actcls, "shellExecute", "(Ljava/lang/String;)V"); - - jni.swapBuffers = (*env)->GetStaticMethodID(env, jni.actcls, "swapBuffers", "()V"); - jni.toggleEGL = (*env)->GetStaticMethodID(env, jni.actcls, "toggleEGL", "(I)V"); - jni.createGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "createGLContext", "([I[I)Z"); - jni.getGLAttribute = (*env)->GetStaticMethodID(env, jni.actcls, "getGLAttribute", "(I)I"); - jni.deleteGLContext = (*env)->GetStaticMethodID(env, jni.actcls, "deleteGLContext", "()Z"); - jni.getSelectedPixelFormat = (*env)->GetStaticMethodID(env, jni.actcls, "getSelectedPixelFormat", "()I"); - jni.getSurface = (*env)->GetStaticMethodID(env, jni.actcls, "getNativeSurface", "()Landroid/view/Surface;"); - - /* Run the application. */ - - status = Host_Main( argc, argv, getenv("XASH3D_GAMEDIR"), false, NULL ); - - /* Release the arguments. */ - - for (i = 0; i < argc; ++i) - free(argv[i]); - free(argv); - - return status; -} - -DECLARE_JNI_INTERFACE( void, onNativeResize, jint width, jint height ) -{ - event_t *event; - - if( !width || !height ) - return; - - jni.width=width, jni.height=height; - - // alloc update event to change screen size - event = Android_AllocEvent(); - event->type = event_resize; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeQuit ) -{ -} - -DECLARE_JNI_INTERFACE( void, nativeSetPause, jint pause ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_set_pause; - event->arg = pause; - Android_PushEvent(); - - // if pause enabled, hold engine by locking frame mutex. - // Engine will stop after event reading and will not continue untill unlock - if( android_sleep && android_sleep->value ) - { - if( pause ) - pthread_mutex_lock( &events.framemutex ); - else - pthread_mutex_unlock( &events.framemutex ); - } -} - -DECLARE_JNI_INTERFACE( void, nativeUnPause ) -{ - // UnPause engine before sending critical events - if( android_sleep && android_sleep->value ) - pthread_mutex_unlock( &events.framemutex ); -} - -DECLARE_JNI_INTERFACE( void, nativeKey, jint down, jint code ) -{ - event_t *event; - - if( code < 0 ) - { - event = Android_AllocEvent(); - event->arg = (-code) & 255; - event->type = down?event_key_down:event_key_up; - Android_PushEvent(); - } - else - { - if( code >= ( sizeof( s_android_scantokey ) / sizeof( s_android_scantokey[0] ) ) ) - { - Con_DPrintf( "nativeKey: unknown Android key %d\n", code ); - return; - } - - if( !s_android_scantokey[code] ) - { - Con_DPrintf( "nativeKey: unmapped Android key %d\n", code ); - return; - } - - event = Android_AllocEvent(); - event->type = down?event_key_down:event_key_up; - event->arg = s_android_scantokey[code]; - Android_PushEvent(); - } -} - -DECLARE_JNI_INTERFACE( void, nativeString, jobject string ) -{ - char* str = (char *) (*env)->GetStringUTFChars(env, string, NULL); - - Android_Lock(); - strncat( events.inputtext, str, 256 ); - Android_Unlock(); - - (*env)->ReleaseStringUTFChars(env, string, str); -} - -#ifdef SOFTFP_LINK -DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) __attribute__((pcs("aapcs"))); -#endif -DECLARE_JNI_INTERFACE( void, nativeTouch, jint finger, jint action, jfloat x, jfloat y ) -{ - float dx, dy; - event_t *event; - - // if something wrong with android event - if( finger > MAX_FINGERS ) - return; - - // not touch action? - if( !( action >=0 && action <= 2 ) ) - return; - - // 0.0f .. 1.0f - x /= jni.width; - y /= jni.height; - - if( action ) - dx = x - events.fingers[finger].x, dy = y - events.fingers[finger].y; - else - dx = dy = 0.0f; - events.fingers[finger].x = x, events.fingers[finger].y = y; - - // check if we should skip some events - if( ( action == 2 ) && ( !dx && !dy ) ) - return; - - if( ( action == 0 ) && events.fingers[finger].down ) - return; - - if( ( action == 1 ) && !events.fingers[finger].down ) - return; - - if( action == 2 && !events.fingers[finger].down ) - action = 0; - - if( action == 0 ) - events.fingers[finger].down = true; - else if( action == 1 ) - events.fingers[finger].down = false; - - event = Android_AllocEvent(); - event->arg = finger; - event->type = action; - event->touch.x = x; - event->touch.y = y; - event->touch.dx = dx; - event->touch.dy = dy; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeBall, jint id, jbyte ball, jshort xrel, jshort yrel ) -{ - event_t *event = Android_AllocEvent(); - - event->type = event_joyball; - event->arg = id; - event->ball.ball = ball; - event->ball.xrel = xrel; - event->ball.yrel = yrel; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeHat, jint id, jbyte hat, jbyte key, jboolean down ) -{ - event_t *event = Android_AllocEvent(); - static byte engineKeys; - - if( !key ) - engineKeys = 0; // centered; - - if( down ) - engineKeys |= key; - else - engineKeys &= ~key; - - event->type = event_joyhat; - event->arg = id; - event->hat.hat = hat; - event->hat.key = engineKeys; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeAxis, jint id, jbyte axis, jshort val ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_joyaxis; - event->arg = id; - event->axis.axis = axis; - event->axis.val = val; - - __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "axis %i %i", axis, val ); - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeJoyButton, jint id, jbyte button, jboolean down ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_joybutton; - event->arg = id; - event->button.button = button; - event->button.down = down; - __android_log_print(ANDROID_LOG_VERBOSE, "Xash", "button %i", button ); - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeJoyAdd, jint id ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_joyadd; - event->arg = id; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeJoyDel, jint id ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_joyremove; - event->arg = id; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeOnResume ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_onresume; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeOnFocusChange ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_onfocuschange; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeOnPause ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_onpause; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( void, nativeOnDestroy ) -{ - event_t *event = Android_AllocEvent(); - event->type = event_ondestroy; - Android_PushEvent(); -} - -DECLARE_JNI_INTERFACE( int, setenv, jstring key, jstring value, jboolean overwrite ) -{ - char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL); - char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL); - int err = setenv(k, v, overwrite); - (*env)->ReleaseStringUTFChars(env, key, k); - (*env)->ReleaseStringUTFChars(env, value, v); - return err; -} - - -DECLARE_JNI_INTERFACE( void, nativeMouseMove, jfloat x, jfloat y ) -{ - Android_Lock(); - events.mousex += x; - events.mousey += y; - Android_Unlock(); -} - -DECLARE_JNI_INTERFACE( int, nativeTestWritePermission, jstring jPath ) -{ - char *path = (char *)(*env)->GetStringUTFChars(env, jPath, NULL); - FILE *fd; - char testFile[PATH_MAX]; - int ret = 0; - - // maybe generate new file everytime? - Q_snprintf( testFile, PATH_MAX, "%s/.testfile", path ); - - __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: file=%s", testFile ); - - fd = fopen( testFile, "w+" ); - - if( fd ) - { - __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: passed" ); - ret = 1; - fclose( fd ); - - remove( testFile ); - } - else - { - __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "nativeTestWritePermission: error=%s", strerror( errno ) ); - } - - (*env)->ReleaseStringUTFChars( env, jPath, path ); - - return ret; -} - -JNIEXPORT jint JNICALL JNI_OnLoad( JavaVM *vm, void *reserved ) -{ - return JNI_VERSION_1_6; -} - -/* -======================== -Platform_Init - -Initialize android-related cvars -======================== -*/ -void Platform_Init( void ) -{ - android_sleep = Cvar_Get( "android_sleep", "1", FCVAR_ARCHIVE, "Enable sleep in background" ); -} - -void Platform_Shutdown( void ) -{ - -} - -/* -======================== -Android_EnableTextInput - -Show virtual keyboard -======================== -*/ -void Platform_EnableTextInput( qboolean enable ) -{ - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.enableTextInput, enable ); -} - -/* -======================== -Android_Vibrate -======================== -*/ -void Platform_Vibrate( float life, char flags ) -{ - if( life ) - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.vibrate, (int)life ); + memset( &jni, 0, sizeof( jni )); + +#if XASH_SDL + jni.env = (JNIEnv *)SDL_AndroidGetJNIEnv(); + jni.activity = (jobject)SDL_AndroidGetActivity(); + jni.actcls = (*jni.env)->GetObjectClass( jni.env, jni.activity ); + jni.loadAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, "loadAndroidID", "()Ljava/lang/String;" ); + jni.getAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, "getAndroidID", "()Ljava/lang/String;" ); + jni.saveAndroidID = (*jni.env)->GetMethodID( jni.env, jni.actcls, "saveAndroidID", "(Ljava/lang/String;)V" ); + + SDL_SetHint( SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight" ); + SDL_SetHint( SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1" ); + SDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0" ); + SDL_SetHint( SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0" ); + SDL_SetHint( SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1" ); +#endif // !XASH_SDL } /* @@ -617,41 +61,21 @@ void Platform_Vibrate( float life, char flags ) Android_GetNativeObject ======================== */ -void *Platform_GetNativeObject( const char *objName ) + +void *Android_GetNativeObject( const char *name ) { - static const char *availObjects[] = { "JNIEnv", "ActivityClass", NULL }; - void *object = NULL; - - if( !objName ) + if( !strcasecmp( name, "JNIEnv" ) ) { - object = (void*)availObjects; + return (void *)jni.env; } - else if( !strcasecmp( objName, "JNIEnv" ) ) + else if( !strcasecmp( name, "ActivityClass" ) ) { - object = (void*)jni.env; - } - else if( !strcasecmp( objName, "ActivityClass" ) ) - { - object = (void*)jni.actcls; + return (void *)jni.actcls; } - return object; + return NULL; } -/* -======================== -Android_MessageBox - -Show messagebox and wait for OK button press -======================== -*/ -#if XASH_MESSAGEBOX == MSGBOX_ANDROID -void Platform_MessageBox( const char *title, const char *text, qboolean parentMainWindow ) -{ - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.messageBox, (*jni.env)->NewStringUTF( jni.env, title ), (*jni.env)->NewStringUTF( jni.env ,text ) ); -} -#endif // XASH_MESSAGEBOX == MSGBOX_ANDROID - /* ======================== Android_GetAndroidID @@ -659,21 +83,17 @@ Android_GetAndroidID */ const char *Android_GetAndroidID( void ) { - static char id[65]; - const char *resultCStr; + static char id[32]; jstring resultJNIStr; + const char *resultCStr; - if( id[0] ) - return id; + if( COM_CheckString( id ) ) return id; - resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.getAndroidId ); + resultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAndroidID ); resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); - Q_strncpy( id, resultCStr, 64 ); + Q_strncpy( id, resultCStr, sizeof( id ) ); (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); - if( !id[0] ) - return NULL; - return id; } @@ -684,11 +104,15 @@ Android_LoadID */ const char *Android_LoadID( void ) { - static char id[65]; - jstring resultJNIStr = (jstring)(*jni.env)->CallStaticObjectMethod( jni.env, jni.actcls, jni.loadID ); - const char *resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); - Q_strncpy( id, resultCStr, 64 ); + static char id[32]; + jstring resultJNIStr; + const char *resultCStr; + + resultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.loadAndroidID ); + resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); + Q_strncpy( id, resultCStr, sizeof( id ) ); (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); + return id; } @@ -699,60 +123,7 @@ Android_SaveID */ void Android_SaveID( const char *id ) { - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.saveID, (*jni.env)->NewStringUTF( jni.env, id ) ); -} - -/* -======================== -Android_MouseMove -======================== -*/ -void Platform_MouseMove( float *x, float *y ) -{ - *x = jnimouse.x; - *y = jnimouse.y; - jnimouse.x = 0; - jnimouse.y = 0; - // Con_Reportf( "Android_MouseMove: %f %f\n", *x, *y ); -} - -/* -======================== -Android_AddMove -======================== -*/ -void Android_AddMove( float x, float y ) -{ - jnimouse.x += x; - jnimouse.y += y; -} - -void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) -{ - // stub -} - -void GAME_EXPORT Platform_SetMousePos( int x, int y ) -{ - // stub -} - -int Platform_JoyInit( int numjoy ) -{ - // stub - return 0; -} - -/* -======================== -Android_ShowMouse -======================== -*/ -void Android_ShowMouse( qboolean show ) -{ - if( m_ignore->value ) - show = true; - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.showMouse, show ); + (*jni.env)->CallVoidMethod( jni.env, jni.activity, jni.saveAndroidID, (*jni.env)->NewStringUTF( jni.env, id ) ); } /* @@ -762,232 +133,7 @@ Android_ShellExecute */ void Platform_ShellExecute( const char *path, const char *parms ) { - jstring jstr; - - if( !path ) - return; // useless - - // get java.lang.String - jstr = (*jni.env)->NewStringUTF( jni.env, path ); - - // open browser - (*jni.env)->CallStaticVoidMethod(jni.env, jni.actcls, jni.shellExecute, jstr); - - // no need to free jstr +#if XASH_SDL + SDL_OpenURL( path ); +#endif // XASH_SDL } - -int Platform_GetClipboardText( char *buffer, size_t size ) -{ - // stub - if( size ) buffer[0] = 0; - return 0; -} - -void Platform_SetClipboardText( const char *buffer ) -{ - // stub -} - -void Platform_SetCursorType( VGUI_DefaultCursor cursor ) -{ - // stub -} - -key_modifier_t Platform_GetKeyModifiers( void ) -{ - // stub - return KeyModifier_None; -} - -void Platform_PreCreateMove( void ) -{ - // stub -} - -/* -======================== -Android_RunEvents - -Execute all events from queue -======================== -*/ -void Platform_RunEvents( void ) -{ - int i; - - // enter events read - Android_Lock(); - pthread_mutex_unlock( &events.framemutex ); - - for( i = 0; i < events.count; i++ ) - { - switch( events.queue[i].type ) - { - case event_touch_down: - case event_touch_up: - case event_touch_move: - IN_TouchEvent( (touchEventType)events.queue[i].type, events.queue[i].arg, - events.queue[i].touch.x, events.queue[i].touch.y, - events.queue[i].touch.dx, events.queue[i].touch.dy ); - break; - - case event_key_down: - Key_Event( events.queue[i].arg, true ); - - if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) - { - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - } - break; - case event_key_up: - Key_Event( events.queue[i].arg, false ); - - if( events.queue[i].arg == K_AUX31 || events.queue[i].arg == K_AUX29 ) - { - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - } - break; - - case event_set_pause: - // destroy EGL surface when hiding application - if( !events.queue[i].arg ) - { - SNDDMA_Activate( true ); -// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); - Android_UpdateSurface( true ); - host.status = HOST_FRAME; - SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - } - - if( events.queue[i].arg ) - { - SNDDMA_Activate( false ); - Android_UpdateSurface( false ); - host.status = HOST_NOFOCUS; -// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); - } - break; - - case event_resize: - // reinitialize EGL and change engine screen size - if( host.status == HOST_FRAME &&( refState.width != jni.width || refState.height != jni.height ) ) - { -// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); -// (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); - Android_UpdateSurface( true ); - SetBits( gl_vsync->flags, FCVAR_CHANGED ); // set swap interval - VID_SetMode(); - } - break; - case event_joyadd: - Joy_AddEvent(); - break; - case event_joyremove: - Joy_RemoveEvent(); - break; - case event_joyball: - if( !Joy_IsActive() ) - Joy_AddEvent(); - Joy_BallMotionEvent( events.queue[i].ball.ball, - events.queue[i].ball.xrel, events.queue[i].ball.yrel ); - break; - case event_joyhat: - if( !Joy_IsActive() ) - Joy_AddEvent(); - Joy_HatMotionEvent( events.queue[i].hat.hat, events.queue[i].hat.key ); - break; - case event_joyaxis: - if( !Joy_IsActive() ) - Joy_AddEvent(); - Joy_AxisMotionEvent( events.queue[i].axis.axis, events.queue[i].axis.val ); - break; - case event_joybutton: - if( !Joy_IsActive() ) - Joy_AddEvent(); - Joy_ButtonEvent( events.queue[i].button.button, (byte)events.queue[i].button.down ); - break; - case event_ondestroy: - //host.skip_configs = true; // skip config save, because engine may be killed during config save - Sys_Quit(); - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); - break; - case event_onpause: -#ifdef PARANOID_CONFIG_SAVE - switch( host.status ) - { - case HOST_INIT: - case HOST_CRASHED: - case HOST_ERR_FATAL: - Con_Reportf( S_WARN "Abnormal host state during onPause (%d), skipping config save!\n", host.status ); - break; - default: - // restore all latched cheat cvars - Cvar_SetCheatState( true ); - Host_WriteConfig(); - } -#endif - // disable sound during call/screen-off - SNDDMA_Activate( false ); -// host.status = HOST_NOFOCUS; - // stop blocking UI thread - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.notify ); - - break; - case event_onresume: - // re-enable sound after onPause -// host.status = HOST_FRAME; - SNDDMA_Activate( true ); - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - break; - case event_onfocuschange: - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - break; - } - } - - events.count = 0; // no more events - - // text input handled separately to allow unicode symbols - for( i = 0; events.inputtext[i]; i++ ) - { - int ch; - - // if engine does not use utf-8, we need to convert it to preferred encoding - if( !Q_stricmp( cl_charset->string, "utf-8" ) ) - ch = (unsigned char)events.inputtext[i]; - else - ch = Con_UtfProcessCharForce( (unsigned char)events.inputtext[i] ); - - if( !ch ) // utf-8 - continue; - - // some keyboards may send enter as text - if( ch == '\n' ) - { - Key_Event( K_ENTER, true ); - Key_Event( K_ENTER, false ); - continue; - } - - // otherwise just push it by char, text render will decode unicode strings - CL_CharEvent( ch ); - } - events.inputtext[0] = 0; // no more text - - jnimouse.x += events.mousex; - events.mousex = 0; - jnimouse.y += events.mousey; - events.mousey = 0; - - //end events read - Android_Unlock(); - pthread_mutex_lock( &events.framemutex ); -} - -#endif // XASH_DEDICATED diff --git a/engine/platform/android/android_priv.h b/engine/platform/android/android_priv.h deleted file mode 100644 index 3af27197..00000000 --- a/engine/platform/android/android_priv.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#ifndef ANDROID_PRIV_H -#define ANDROID_PRIV_H - -#include -#include -#include - -extern struct jnimethods_s -{ - jclass actcls; - JavaVM *vm; - JNIEnv *env; - jmethodID enableTextInput; - jmethodID vibrate; - jmethodID messageBox; - jmethodID notify; - jmethodID setTitle; - jmethodID setIcon; - jmethodID getAndroidId; - jmethodID saveID; - jmethodID loadID; - jmethodID showMouse; - jmethodID shellExecute; - jmethodID swapBuffers; - jmethodID toggleEGL; - jmethodID createGLContext; - jmethodID getGLAttribute; - jmethodID deleteGLContext; - jmethodID getSelectedPixelFormat; - jmethodID getSurface; - int width, height; -} jni; - - -extern struct jnimouse_s -{ - float x, y; -} jnimouse; - -// -// vid_android.c -// -void Android_UpdateSurface( qboolean active ); - -#endif // ANDROID_PRIV_H diff --git a/engine/platform/android/dlsym-weak.cpp b/engine/platform/android/dlsym-weak.c similarity index 57% rename from engine/platform/android/dlsym-weak.cpp rename to engine/platform/android/dlsym-weak.c index d585af7d..9e413434 100644 --- a/engine/platform/android/dlsym-weak.cpp +++ b/engine/platform/android/dlsym-weak.c @@ -26,47 +26,56 @@ * SUCH DAMAGE. */ -#if defined __ANDROID__ && !defined XASH_64BIT +#include "build.h" +#if XASH_ANDROID && !XASH_64BIT #include #include #include "linker.h" -static Elf_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) { - Elf_Sym* symtab = si->symtab; - const char* strtab = si->strtab; +static const Elf_Sym *soinfo_elf_lookup( const struct soinfo *si, unsigned hash, const char *name ) +{ + const Elf_Sym *symtab = si->symtab; + const char *strtab = si->strtab; + unsigned n; if( si->nbucket == 0 ) return NULL; - for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) { - Elf_Sym* s = symtab + n; - if (strcmp(strtab + s->st_name, name)) continue; + for( n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n] ) + { + const Elf_Sym *s = symtab + n; - /* only concern ourselves with global and weak symbol definitions */ - switch (ELF_ST_BIND(s->st_info)) { - case STB_GLOBAL: - case STB_WEAK: - if (s->st_shndx == SHN_UNDEF) { - continue; - } - return s; - } - } + if( strcmp( strtab + s->st_name, name )) + continue; - return NULL; + /* only concern ourselves with global and weak symbol definitions */ + switch( ELF_ST_BIND( s->st_info )) + { + case STB_GLOBAL: + case STB_WEAK: + if( s->st_shndx == SHN_UNDEF ) + continue; + return s; + } + } + + return NULL; } -static unsigned elfhash(const char* _name) { - const unsigned char* name = (const unsigned char*) _name; - unsigned h = 0, g; +static unsigned elfhash( const unsigned char *name ) +{ + unsigned h = 0; - while(*name) { - h = (h << 4) + *name++; - g = h & 0xf0000000; - h ^= g; - h ^= g >> 24; - } - return h; + while( *name ) + { + unsigned g; + + h = ( h << 4 ) + *name++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + return h; } /* This is used by dlsym(3). It performs symbol lookup only within the @@ -78,21 +87,20 @@ static unsigned elfhash(const char* _name) { Binary Interface) where in Chapter 5 it discuss resolving "Shared Object Dependencies" in breadth first search order. */ -static Elf_Sym* dlsym_handle_lookup(soinfo* si, const char* name) { - return soinfo_elf_lookup(si, elfhash(name), name); +static const Elf_Sym *dlsym_handle_lookup( const struct soinfo *si, const char *name ) +{ + return soinfo_elf_lookup( si, elfhash((const unsigned char *)name ), name ); } -extern "C" void* dlsym_weak(void* handle, const char* symbol) { +void *dlsym_weak( void *handle, const char *symbol ) +{ + const struct soinfo *found = (struct soinfo *)handle; + const Elf_Sym *sym = dlsym_handle_lookup( found, symbol ); - soinfo* found = NULL; - Elf_Sym* sym = NULL; - found = reinterpret_cast(handle); - sym = dlsym_handle_lookup(found, symbol); + if( sym != NULL ) + return (void *)( sym->st_value + found->base /*load_bias*/ ); - if (sym != NULL) { - return reinterpret_cast(sym->st_value + found->base/*load_bias*/); - } - __android_log_print(ANDROID_LOG_ERROR, "dlsym-weak", "Failed when looking up %s\n", symbol); - return NULL; + __android_log_print( ANDROID_LOG_ERROR, "dlsym-weak", "Failed when looking up %s\n", symbol ); + return NULL; } -#endif +#endif // XASH_ANDROID && !XASH_64BIT diff --git a/engine/platform/android/lib_android.c b/engine/platform/android/lib_android.c index 948d6324..939e25d6 100644 --- a/engine/platform/android/lib_android.c +++ b/engine/platform/android/lib_android.c @@ -19,44 +19,43 @@ GNU General Public License for more details. #include "platform/android/lib_android.h" #include "platform/android/dlsym-weak.h" // Android < 5.0 -void *ANDROID_LoadLibrary( const char *dllname ) +void *ANDROID_LoadLibrary( const char *path ) { - char path[MAX_SYSPATH]; - const char *libdir[2]; - int i; - void *pHandle = NULL; + const char *libdir[2], *name = COM_FileWithoutPath( path ); + char fullpath[MAX_SYSPATH]; + void *handle; - libdir[0] = getenv("XASH3D_GAMELIBDIR"); - libdir[1] = getenv("XASH3D_ENGLIBDIR"); + libdir[0] = getenv( "XASH3D_GAMELIBDIR" ); // TODO: remove this once distributing games from APKs will be deprecated + libdir[1] = NULL; // TODO: put here data directory where libraries will be downloaded to - for( i = 0; i < 2; i++ ) + for( int i = 0; i < ARRAYSIZE( libdir ); i++ ) { + // this is an APK directory, get base path + const char *p = i == 0 ? name : path; + if( !libdir[i] ) continue; - Q_snprintf( path, MAX_SYSPATH, "%s/lib%s."OS_LIB_EXT, libdir[i], dllname ); - pHandle = dlopen( path, RTLD_LAZY ); - if( pHandle ) - return pHandle; + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", libdir[i], p ); + + handle = dlopen( fullpath, RTLD_NOW ); + + if( handle ) + { + Con_Reportf( "%s: loading library %s successful\n", __func__, fullpath ); + return handle; + } COM_PushLibraryError( dlerror() ); } - // HACKHACK: keep old behaviour for compability - if( Q_strstr( dllname, "." OS_LIB_EXT ) || Q_strstr( dllname, PATH_SPLITTER )) + // find in system search path, that includes our APK + handle = dlopen( name, RTLD_NOW ); + if( handle ) { - pHandle = dlopen( dllname, RTLD_LAZY ); - if( pHandle ) - return pHandle; + Con_Reportf( "%s: loading library %s from LD_LIBRARY_PATH successful\n", __func__, name ); + return handle; } - else - { - Q_snprintf( path, MAX_SYSPATH, "lib%s."OS_LIB_EXT, dllname ); - pHandle = dlopen( path, RTLD_LAZY ); - if( pHandle ) - return pHandle; - } - COM_PushLibraryError( dlerror() ); return NULL; diff --git a/engine/platform/android/lib_android.h b/engine/platform/android/lib_android.h index 5bead1f3..ad2c6f8e 100644 --- a/engine/platform/android/lib_android.h +++ b/engine/platform/android/lib_android.h @@ -17,7 +17,10 @@ GNU General Public License for more details. #ifndef ANDROID_LIB_H #define ANDROID_LIB_H +#if !XASH_TERMUX #define Platform_POSIX_LoadLibrary( x ) ANDROID_LoadLibrary(( x )) +#endif // !XASH_TERMUX + #define Platform_POSIX_GetProcAddress( x, y ) ANDROID_GetProcAddress(( x ), ( y )) void *ANDROID_LoadLibrary( const char *dllname ); diff --git a/engine/platform/android/linker.h b/engine/platform/android/linker.h index e4454a84..6efe2566 100644 --- a/engine/platform/android/linker.h +++ b/engine/platform/android/linker.h @@ -28,174 +28,152 @@ #ifndef _LINKER_H_ #define _LINKER_H_ -#ifdef __ANDROID__ #include #include -// #include +#include #include -// #include -// #include // a1ba: we don't really need custom linker on Android 64, because // it's only intended to workaround bug which persist on Android < 4.4 -#define Elf_Ehdr Elf32_Ehdr -#define Elf_Phdr Elf32_Phdr -#define Elf_Shdr Elf32_Shdr -#define Elf_Sym Elf32_Sym -#define Elf_Rel Elf32_Rel -#define Elf_RelA Elf32_Rela -#define Elf_Dyn Elf32_Dyn -#define Elf_Half Elf32_Half -#define Elf_Word Elf32_Word -#define Elf_Sword Elf32_Sword -#define Elf_Addr Elf32_Addr -#define Elf_Off Elf32_Off -#define Elf_Nhdr Elf32_Nhdr -#define Elf_Note Elf32_Note +#define Elf_Ehdr Elf32_Ehdr +#define Elf_Phdr Elf32_Phdr +#define Elf_Shdr Elf32_Shdr +#define Elf_Sym Elf32_Sym +#define Elf_Rel Elf32_Rel +#define Elf_RelA Elf32_Rela +#define Elf_Dyn Elf32_Dyn +#define Elf_Half Elf32_Half +#define Elf_Word Elf32_Word +#define Elf_Sword Elf32_Sword +#define Elf_Addr Elf32_Addr +#define Elf_Off Elf32_Off +#define Elf_Nhdr Elf32_Nhdr +#define Elf_Note Elf32_Note // Returns the address of the page containing address 'x'. -#define PAGE_START(x) ((x) & PAGE_MASK) +#define PAGE_START( x ) (( x )&PAGE_MASK ) // Returns the offset of address 'x' in its page. -#define PAGE_OFFSET(x) ((x) & ~PAGE_MASK) +#define PAGE_OFFSET( x ) (( x ) & ~PAGE_MASK ) // Returns the address of the next page after address 'x', unless 'x' is // itself at the start of a page. -#define PAGE_END(x) PAGE_START((x) + (PAGE_SIZE-1)) +#define PAGE_END( x ) PAGE_START(( x ) + ( PAGE_SIZE - 1 )) // Magic shared structures that GDB knows about. struct link_map_t { - uintptr_t l_addr; - char* l_name; - uintptr_t l_ld; - link_map_t* l_next; - link_map_t* l_prev; + uintptr_t l_addr; + char *l_name; + uintptr_t l_ld; + struct link_map_t *l_next; + struct link_map_t *l_prev; }; // Values for r_debug->state enum { - RT_CONSISTENT, - RT_ADD, - RT_DELETE + RT_CONSISTENT, + RT_ADD, + RT_DELETE }; -#if 0 -struct r_debug { - int32_t r_version; - link_map_t* r_map; - void (*r_brk)(void); - int32_t r_state; - uintptr_t r_ldbase; -}; -#endif -#define FLAG_LINKED 0x00000001 -#define FLAG_EXE 0x00000004 // The main executable -#define FLAG_LINKER 0x00000010 // The linker itself +#define FLAG_LINKED 0x00000001 +#define FLAG_EXE 0x00000004 // The main executable +#define FLAG_LINKER 0x00000010 // The linker itself #define SOINFO_NAME_LEN 128 -typedef void (*linker_function_t)(); +typedef void (*linker_function_t)( void ); // Android uses REL for 32-bit but only uses RELA for 64-bit. -#if defined(__LP64__) +#if defined( __LP64__ ) #define USE_RELA 1 #endif struct soinfo { - public: - char name[SOINFO_NAME_LEN]; - const Elf_Phdr* phdr; - size_t phnum; - Elf_Addr entry; - Elf_Addr base; - unsigned size; + char name[SOINFO_NAME_LEN]; + const Elf_Phdr *phdr; + size_t phnum; + Elf_Addr entry; + Elf_Addr base; + unsigned size; #ifndef __LP64__ - uint32_t unused1; // DO NOT USE, maintained for compatibility. + uint32_t unused1; // DO NOT USE, maintained for compatibility. #endif - Elf_Dyn* dynamic; + Elf_Dyn *dynamic; #ifndef __LP64__ - uint32_t unused2; // DO NOT USE, maintained for compatibility - uint32_t unused3; // DO NOT USE, maintained for compatibility + uint32_t unused2; // DO NOT USE, maintained for compatibility + uint32_t unused3; // DO NOT USE, maintained for compatibility #endif - soinfo* next; - unsigned flags; + struct soinfo *next; + unsigned flags; - const char* strtab; - Elf_Sym* symtab; + const char *strtab; + Elf_Sym *symtab; - size_t nbucket; - size_t nchain; - unsigned* bucket; - unsigned* chain; + size_t nbucket; + size_t nchain; + unsigned *bucket; + unsigned *chain; -#if !defined(__LP64__) - // This is only used by 32-bit MIPS, but needs to be here for - // all 32-bit architectures to preserve binary compatibility. - unsigned* plt_got; +#if !defined( __LP64__ ) + // This is only used by 32-bit MIPS, but needs to be here for + // all 32-bit architectures to preserve binary compatibility. + unsigned *plt_got; #endif -#if defined(USE_RELA) - Elf_Rela* plt_rela; - size_t plt_rela_count; +#if defined( USE_RELA ) + Elf_RelA *plt_rela; + size_t plt_rela_count; - Elf_Rela* rela; - size_t rela_count; + Elf_RelA *rela; + size_t rela_count; #else - Elf_Rel* plt_rel; - size_t plt_rel_count; + Elf_Rel *plt_rel; + size_t plt_rel_count; - Elf_Rel* rel; - size_t rel_count; + Elf_Rel *rel; + size_t rel_count; #endif - linker_function_t* preinit_array; - size_t preinit_array_count; + linker_function_t *preinit_array; + size_t preinit_array_count; - linker_function_t* init_array; - size_t init_array_count; - linker_function_t* fini_array; - size_t fini_array_count; + linker_function_t *init_array; + size_t init_array_count; + linker_function_t *fini_array; + size_t fini_array_count; - linker_function_t init_func; - linker_function_t fini_func; + linker_function_t init_func; + linker_function_t fini_func; -#if defined(__arm__) - // ARM EABI section used for stack unwinding. - unsigned* ARM_exidx; - size_t ARM_exidx_count; -#elif defined(__mips__) - unsigned mips_symtabno; - unsigned mips_local_gotno; - unsigned mips_gotsym; +#if defined( __arm__ ) + // ARM EABI section used for stack unwinding. + unsigned *ARM_exidx; + size_t ARM_exidx_count; +#elif defined( __mips__ ) + unsigned mips_symtabno; + unsigned mips_local_gotno; + unsigned mips_gotsym; #endif - size_t ref_count; - link_map_t link_map; + size_t ref_count; + struct link_map_t link_map; - bool constructors_called; + bool constructors_called; - // When you read a virtual address from the ELF file, add this - // value to get the corresponding address in the process' address space. - Elf_Addr load_bias; + // When you read a virtual address from the ELF file, add this + // value to get the corresponding address in the process' address space. + Elf_Addr load_bias; -#if !defined(__LP64__) - bool has_text_relocations; +#if !defined( __LP64__ ) + bool has_text_relocations; #endif - bool has_DT_SYMBOLIC; - - void CallConstructors(); - void CallDestructors(); - void CallPreInitConstructors(); - - private: - void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse); - void CallFunction(const char* function_name, linker_function_t function); + bool has_DT_SYMBOLIC; }; #endif -#endif diff --git a/engine/platform/android/snd_opensles.c b/engine/platform/android/snd_opensles.c deleted file mode 100644 index 4cf5bbe0..00000000 --- a/engine/platform/android/snd_opensles.c +++ /dev/null @@ -1,277 +0,0 @@ -/* -Copyright (C) 2015 SiPlus, Chasseur de bots - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - -See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#include "common.h" -#include "platform/platform.h" -#if XASH_SOUND == SOUND_OPENSLES -#include -#include "pthread.h" -#include "sound.h" - -extern dma_t dma; - -static SLObjectItf snddma_android_engine = NULL; -static SLObjectItf snddma_android_outputMix = NULL; -static SLObjectItf snddma_android_player = NULL; -static SLBufferQueueItf snddma_android_bufferQueue; -static SLPlayItf snddma_android_play; - -static pthread_mutex_t snddma_android_mutex = PTHREAD_MUTEX_INITIALIZER; - -static int snddma_android_size; - -static const SLInterfaceID *pSL_IID_ENGINE; -static const SLInterfaceID *pSL_IID_BUFFERQUEUE; -static const SLInterfaceID *pSL_IID_PLAY; -static SLresult SLAPIENTRY (*pslCreateEngine)( - SLObjectItf *pEngine, - SLuint32 numOptions, - const SLEngineOption *pEngineOptions, - SLuint32 numInterfaces, - const SLInterfaceID *pInterfaceIds, - const SLboolean * pInterfaceRequired -); - -void SNDDMA_Activate( qboolean active ) -{ - if( !dma.initialized ) - return; - - if( active ) - { - memset( dma.buffer, 0, snddma_android_size * 2 ); - (*snddma_android_bufferQueue)->Enqueue( snddma_android_bufferQueue, dma.buffer, snddma_android_size ); - (*snddma_android_play)->SetPlayState( snddma_android_play, SL_PLAYSTATE_PLAYING ); - } - else - { - (*snddma_android_play)->SetPlayState( snddma_android_play, SL_PLAYSTATE_STOPPED ); - (*snddma_android_bufferQueue)->Clear( snddma_android_bufferQueue ); - } -} - -static void SNDDMA_Android_Callback( SLBufferQueueItf bq, void *context ) -{ - uint8_t *buffer2; - - pthread_mutex_lock( &snddma_android_mutex ); - - buffer2 = ( uint8_t * )dma.buffer + snddma_android_size; - (*bq)->Enqueue( bq, buffer2, snddma_android_size ); - memcpy( buffer2, dma.buffer, snddma_android_size ); - memset( dma.buffer, 0, snddma_android_size ); - dma.samplepos += dma.samples; - - pthread_mutex_unlock( &snddma_android_mutex ); -} - -static const char *SNDDMA_Android_Init( void ) -{ - SLresult result; - - SLEngineItf engine; - - int freq; - - SLDataLocator_BufferQueue sourceLocator; - SLDataFormat_PCM sourceFormat; - SLDataSource source; - - SLDataLocator_OutputMix sinkLocator; - SLDataSink sink; - - SLInterfaceID interfaceID; - SLboolean interfaceRequired; - - int samples; - void *handle = dlopen( "libOpenSLES.so", RTLD_LAZY ); - - if( !handle ) - return "dlopen for libOpenSLES.so"; - - pslCreateEngine = dlsym( handle, "slCreateEngine" ); - - if( !pslCreateEngine ) - return "resolve slCreateEngine"; - - pSL_IID_ENGINE = dlsym( handle, "SL_IID_ENGINE" ); - - if( !pSL_IID_ENGINE ) - return "resolve SL_IID_ENGINE"; - - pSL_IID_PLAY = dlsym( handle, "SL_IID_PLAY" ); - - if( !pSL_IID_PLAY ) - return "resolve SL_IID_PLAY"; - - pSL_IID_BUFFERQUEUE = dlsym( handle, "SL_IID_BUFFERQUEUE" ); - - if( !pSL_IID_BUFFERQUEUE ) - return "resolve SL_IID_BUFFERQUEUE"; - - - result = pslCreateEngine( &snddma_android_engine, 0, NULL, 0, NULL, NULL ); - if( result != SL_RESULT_SUCCESS ) return "slCreateEngine"; - result = (*snddma_android_engine)->Realize( snddma_android_engine, SL_BOOLEAN_FALSE ); - if( result != SL_RESULT_SUCCESS ) return "engine->Realize"; - result = (*snddma_android_engine)->GetInterface( snddma_android_engine, *pSL_IID_ENGINE, &engine ); - if( result != SL_RESULT_SUCCESS ) return "engine->GetInterface(ENGINE)"; - - result = (*engine)->CreateOutputMix( engine, &snddma_android_outputMix, 0, NULL, NULL ); - if( result != SL_RESULT_SUCCESS ) return "engine->CreateOutputMix"; - result = (*snddma_android_outputMix)->Realize( snddma_android_outputMix, SL_BOOLEAN_FALSE ); - if( result != SL_RESULT_SUCCESS ) return "outputMix->Realize"; - - freq = SOUND_DMA_SPEED; - sourceLocator.locatorType = SL_DATALOCATOR_BUFFERQUEUE; - sourceLocator.numBuffers = 2; - sourceFormat.formatType = SL_DATAFORMAT_PCM; - sourceFormat.numChannels = 2; // always stereo, because engine supports only stereo - sourceFormat.samplesPerSec = freq * 1000; - sourceFormat.bitsPerSample = 16; // always 16 bit audio - sourceFormat.containerSize = sourceFormat.bitsPerSample; - sourceFormat.channelMask = SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; - sourceFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; - source.pLocator = &sourceLocator; - source.pFormat = &sourceFormat; - - sinkLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; - sinkLocator.outputMix = snddma_android_outputMix; - sink.pLocator = &sinkLocator; - sink.pFormat = NULL; - - interfaceID = *pSL_IID_BUFFERQUEUE; - interfaceRequired = SL_BOOLEAN_TRUE; - - result = (*engine)->CreateAudioPlayer( engine, &snddma_android_player, &source, &sink, 1, &interfaceID, &interfaceRequired ); - if( result != SL_RESULT_SUCCESS ) return "engine->CreateAudioPlayer"; - result = (*snddma_android_player)->Realize( snddma_android_player, SL_BOOLEAN_FALSE ); - if( result != SL_RESULT_SUCCESS ) return "player->Realize"; - result = (*snddma_android_player)->GetInterface( snddma_android_player, *pSL_IID_BUFFERQUEUE, &snddma_android_bufferQueue ); - if( result != SL_RESULT_SUCCESS ) return "player->GetInterface(BUFFERQUEUE)"; - result = (*snddma_android_player)->GetInterface( snddma_android_player, *pSL_IID_PLAY, &snddma_android_play ); - if( result != SL_RESULT_SUCCESS ) return "player->GetInterface(PLAY)"; - result = (*snddma_android_bufferQueue)->RegisterCallback( snddma_android_bufferQueue, SNDDMA_Android_Callback, NULL ); - if( result != SL_RESULT_SUCCESS ) return "bufferQueue->RegisterCallback"; - - samples = s_samplecount.value; - if( !samples ) - samples = 4096; - - dma.format.channels = sourceFormat.numChannels; - dma.samples = samples * sourceFormat.numChannels; - dma.format.speed = freq; - snddma_android_size = dma.samples * ( sourceFormat.bitsPerSample >> 3 ); - dma.buffer = Z_Malloc( snddma_android_size * 2 ); - dma.samplepos = 0; - // dma.sampleframes = dma.samples / dma.format.channels; - dma.format.width = 2; - if( !dma.buffer ) return "malloc"; - - //snddma_android_mutex = trap_Mutex_Create(); - - dma.initialized = true; - - SNDDMA_Activate( true ); - - return NULL; -} - -qboolean SNDDMA_Init( void ) -{ - const char *initError; - - Msg( "OpenSL ES audio device initializing...\n" ); - - initError = SNDDMA_Android_Init(); - if( initError ) - { - Msg( S_ERROR "SNDDMA_Init: %s failed.\n", initError ); - SNDDMA_Shutdown(); - return false; - } - - Msg( "OpenSL ES audio initialized.\n" ); - dma.backendName = "OpenSL ES"; - return true; -} - -void SNDDMA_Shutdown( void ) -{ - Msg( "Closing OpenSL ES audio device...\n" ); - - if( snddma_android_player ) - { - (*snddma_android_player)->Destroy( snddma_android_player ); - snddma_android_player = NULL; - } - if( snddma_android_outputMix ) - { - (*snddma_android_outputMix)->Destroy( snddma_android_outputMix ); - snddma_android_outputMix = NULL; - } - if( snddma_android_engine ) - { - (*snddma_android_engine)->Destroy( snddma_android_engine ); - snddma_android_engine = NULL; - } - - if( dma.buffer ) - { - Z_Free( dma.buffer ); - dma.buffer = NULL; - } - - //if( snddma_android_mutex ) - //trap_Mutex_Destroy( &snddma_android_mutex ); - - Msg( "OpenSL ES audio device shut down.\n" ); -} - -void SNDDMA_Submit( void ) -{ - pthread_mutex_unlock( &snddma_android_mutex ); -} - -void SNDDMA_BeginPainting( void ) -{ - pthread_mutex_lock( &snddma_android_mutex ); -} - -qboolean VoiceCapture_Init( void ) -{ - return false; -} - -qboolean VoiceCapture_Activate( qboolean activate ) -{ - return false; -} - -qboolean VoiceCapture_Lock( qboolean lock ) -{ - return false; -} - -void VoiceCapture_Shutdown( void ) -{ - -} -#endif diff --git a/engine/platform/android/vid_android.c b/engine/platform/android/vid_android.c deleted file mode 100644 index f17c195e..00000000 --- a/engine/platform/android/vid_android.c +++ /dev/null @@ -1,671 +0,0 @@ -#include "platform/platform.h" -#include "input.h" -#include "client.h" -#include "filesystem.h" -#include "platform/android/android_priv.h" -#include "vid_common.h" -#include -#include -#include -#include - -static struct vid_android_s -{ - int gl_attribs[REF_GL_ATTRIBUTES_COUNT]; - qboolean gl_attribs_set[REF_GL_ATTRIBUTES_COUNT]; - EGLint gl_api; - qboolean gles1; - void *libgles1, *libgles2; - qboolean has_context; - ANativeWindow* window; -} vid_android; - -static struct nw_s -{ - void (*release)(ANativeWindow* window); - int32_t (*getWidth)(ANativeWindow* window); - int32_t (*getHeight)(ANativeWindow* window); - int32_t (*getFormat)(ANativeWindow* window); - int32_t (*setBuffersGeometry)(ANativeWindow* window, int32_t width, int32_t height, int32_t format); - int32_t (*lock)(ANativeWindow* window, ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds); - int32_t (*unlockAndPost)(ANativeWindow* window); - ANativeWindow* (*fromSurface)(JNIEnv* env, jobject surface); -} nw; - -#define NW_FF(x) {"ANativeWindow_"#x, (void*)&nw.x} - - -static dllfunc_t android_funcs[] = -{ - NW_FF(release), - NW_FF(getWidth), - NW_FF(getHeight), - NW_FF(getFormat), - NW_FF(setBuffersGeometry), - NW_FF(lock), - NW_FF(unlockAndPost), - NW_FF(fromSurface), - { NULL, NULL } -}; -#undef NW_FF -dll_info_t android_info = { "libandroid.so", android_funcs, false }; - -static struct egl_s -{ - EGLSurface (*GetCurrentSurface)(EGLint readdraw); - EGLDisplay (*GetCurrentDisplay)(void); - EGLint (*GetError)(void); - EGLBoolean (*SwapBuffers)(EGLDisplay dpy, EGLSurface surface); - EGLBoolean (*SwapInterval)(EGLDisplay dpy, EGLint interval); - void *(*GetProcAddress)(const char *procname); -} egl; -#undef GetProcAddress -#define EGL_FF(x) {"egl"#x, (void*)&egl.x} -static dllfunc_t egl_funcs[] = -{ - EGL_FF(SwapInterval), - EGL_FF(SwapBuffers), - EGL_FF(GetError), - EGL_FF(GetCurrentDisplay), - EGL_FF(GetCurrentSurface), - EGL_FF(GetProcAddress), - { NULL, NULL } -}; -#undef EGL_FF -dll_info_t egl_info = { "libEGL.so", egl_funcs, false }; - -static struct nativeegl_s -{ - qboolean valid; - void *window; - EGLDisplay dpy; - EGLSurface surface; - EGLContext context; - EGLConfig cfg; - EGLint numCfg; - - const char *extensions; -} negl; - -/* -======================== -Android_SwapInterval -======================== -*/ -static void Android_SwapInterval( int interval ) -{ - if( negl.valid ) - egl.SwapInterval( negl.dpy, interval ); -} - -/* -======================== -Android_SetTitle -======================== -*/ -static void Android_SetTitle( const char *title ) -{ - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setTitle, (*jni.env)->NewStringUTF( jni.env, title ) ); -} - -/* -======================== -Android_SetIcon -======================== -*/ -static void Android_SetIcon( const char *path ) -{ - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.setIcon, (*jni.env)->NewStringUTF( jni.env, path ) ); -} - -/* -======================== -Android_GetScreenRes - -Resolution got from last resize event -======================== -*/ -static void Android_GetScreenRes( int *width, int *height ) -{ - *width=jni.width, *height=jni.height; -} - -/* -======================== -Android_SwapBuffers - -Update screen. Use native EGL if possible -======================== -*/ -void GL_SwapBuffers( void ) -{ - if( negl.valid ) - { - egl.SwapBuffers( negl.dpy, negl.surface ); - } - else - { - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.swapBuffers ); - } -} - -/* -======================== -Android_UpdateSurface - -Check if we may use native EGL without jni calls -======================== -*/ -void Android_UpdateSurface( qboolean active ) -{ - negl.valid = false; - - if( nw.release ) - { - if( vid_android.window && !active ) - { - nw.release( vid_android.window ); - vid_android.window = NULL; - } - - if( active ) - { - jobject surf; - if( vid_android.window ) - nw.release( vid_android.window ); - surf = (*jni.env)->CallStaticObjectMethod(jni.env, jni.actcls, jni.getSurface); - Con_Printf("s %p\n", surf); - vid_android.window = nw.fromSurface(jni.env, surf); - Con_Printf("w %p\n", vid_android.window); - nw.setBuffersGeometry(vid_android.window, 0, 0, WINDOW_FORMAT_RGB_565 ); - (*jni.env)->DeleteLocalRef( jni.env, surf ); - } - return; - } - - if( !vid_android.has_context ) - return; - - if( ( active && host.status == HOST_FRAME ) || !active ) - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 0 ); - - if( active ) - (*jni.env)->CallStaticVoidMethod( jni.env, jni.actcls, jni.toggleEGL, 1 ); - - if( !Sys_CheckParm("-nativeegl") || !active ) - return; // enabled by user - - if( !egl.GetCurrentDisplay ) - return; - - negl.dpy = egl.GetCurrentDisplay(); - - if( negl.dpy == EGL_NO_DISPLAY ) - return; - - negl.surface = egl.GetCurrentSurface(EGL_DRAW); - - if( negl.surface == EGL_NO_SURFACE ) - return; - - // now check if swapBuffers does not give error - if( egl.SwapBuffers( negl.dpy, negl.surface ) == EGL_FALSE ) - return; - - // double check - if( egl.GetError() != EGL_SUCCESS ) - return; - - __android_log_print( ANDROID_LOG_VERBOSE, "Xash", "native EGL enabled" ); - - negl.valid = true; -} - -/* -======================== -Android_GetGLAttribute -======================== -*/ -static int Android_GetGLAttribute( int eglAttr ) -{ - int ret = (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getGLAttribute, eglAttr ); - // Con_Reportf( "Android_GetGLAttribute( %i ) => %i\n", eglAttr, ret ); - return ret; -} - -int Android_GetSelectedPixelFormat( void ) -{ - return (*jni.env)->CallStaticIntMethod( jni.env, jni.actcls, jni.getSelectedPixelFormat ); -} - -qboolean R_Init_Video( const int type ) -{ - string safe; - qboolean retval; - - switch( Android_GetSelectedPixelFormat() ) - { - case 1: - refState.desktopBitsPixel = 16; - break; - case 2: - refState.desktopBitsPixel = 8; - break; - default: - refState.desktopBitsPixel = 32; - break; - } - - if( FS_FileExists( GI->iconpath, true ) ) - { - if( host.rodir[0] ) - { - Android_SetIcon( va( "%s/%s/%s", host.rodir, GI->gamefolder, GI->iconpath ) ); - } - else - { - Android_SetIcon( va( "%s/%s/%s", host.rootdir, GI->gamefolder, GI->iconpath ) ); - } - } - - Android_SetTitle( GI->title ); - - VID_StartupGamma(); - - switch( type ) - { - case REF_SOFTWARE: - glw_state.software = true; - break; - case REF_GL: - glw_state.software = false; - Sys_LoadLibrary( &egl_info ); - - if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) ) - glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE ); - - break; - default: - Host_Error( "Can't initialize unknown context type %d!\n", type ); - break; - } - - if( glw_state.software ) - { - uint arg; -// Con_Reportf( S_ERROR "Native software mode isn't supported on Android yet! :(\n" ); -// return false; - Sys_LoadLibrary( &android_info ); - Android_UpdateSurface( true ); - if( !SW_CreateBuffer( jni.width, jni.height, &arg, &arg, &arg, &arg, &arg ) ) - return false; - } - - while( !(retval = VID_SetMode()) ) - { - glw_state.safe++; - if( glw_state.safe > SAFE_LAST ) - return false; - } - - switch( type ) - { - case REF_GL: - // refdll also can check extensions - ref.dllFuncs.GL_InitExtensions(); - break; - case REF_SOFTWARE: - default: - break; - } - - host.renderinfo_changed = false; - - return true; -} - -void R_Free_Video( void ) -{ - // (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.deleteGLContext ); - - // VID_DestroyWindow (); - - // R_FreeVideoModes(); - Sys_FreeLibrary( &android_info ); - Sys_FreeLibrary( &egl_info ); - vid_android.has_context = false; - ref.dllFuncs.GL_ClearExtensions(); -} - -#define COPY_ATTR_IF_SET( refattr, attr ) \ - if( vid_android.gl_attribs_set[refattr] ) \ - { \ - attribs[i++] = attr; \ - attribs[i++] = vid_android.gl_attribs[refattr]; \ - } - -static size_t VID_GenerateConfig( EGLint *attribs, size_t size ) -{ - size_t i = 0; - - memset( attribs, 0, size * sizeof( EGLint ) ); - vid_android.gles1 = false; - memset( vid_android.gl_attribs, 0, sizeof( vid_android.gl_attribs )); - memset( vid_android.gl_attribs_set, 0, sizeof( vid_android.gl_attribs_set )); - - // refdll can request some attributes - ref.dllFuncs.GL_SetupAttributes( glw_state.safe ); - - COPY_ATTR_IF_SET( REF_GL_RED_SIZE, EGL_RED_SIZE ); - COPY_ATTR_IF_SET( REF_GL_GREEN_SIZE, EGL_GREEN_SIZE ); - COPY_ATTR_IF_SET( REF_GL_BLUE_SIZE, EGL_BLUE_SIZE ); - COPY_ATTR_IF_SET( REF_GL_ALPHA_SIZE, EGL_ALPHA_SIZE ); - COPY_ATTR_IF_SET( REF_GL_DEPTH_SIZE, EGL_DEPTH_SIZE ); - COPY_ATTR_IF_SET( REF_GL_STENCIL_SIZE, EGL_STENCIL_SIZE ); - COPY_ATTR_IF_SET( REF_GL_MULTISAMPLEBUFFERS, EGL_SAMPLE_BUFFERS ); - COPY_ATTR_IF_SET( REF_GL_MULTISAMPLESAMPLES, EGL_SAMPLES ); - - if( vid_android.gl_attribs_set[REF_GL_ACCELERATED_VISUAL] ) - { - attribs[i++] = EGL_CONFIG_CAVEAT; - attribs[i++] = vid_android.gl_attribs[REF_GL_ACCELERATED_VISUAL] ? EGL_NONE : EGL_DONT_CARE; - } - - // BigGL support - attribs[i++] = EGL_RENDERABLE_TYPE; - vid_android.gl_api = EGL_OPENGL_ES_API; - - if( vid_android.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] && - !( vid_android.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK] & REF_GL_CONTEXT_PROFILE_ES )) - { - attribs[i++] = EGL_OPENGL_BIT; - vid_android.gl_api = EGL_OPENGL_API; - } - else if( vid_android.gl_attribs_set[REF_GL_CONTEXT_MAJOR_VERSION] && - vid_android.gl_attribs[REF_GL_CONTEXT_MAJOR_VERSION] >= 2 ) - { - attribs[i++] = EGL_OPENGL_ES2_BIT; - } - else - { - i--; // erase EGL_RENDERABLE_TYPE - vid_android.gles1 = true; - } - - attribs[i++] = EGL_NONE; - - return i; -} - -static size_t VID_GenerateContextConfig( EGLint *attribs, size_t size ) -{ - size_t i = 0; - - memset( attribs, 0, size * sizeof( EGLint )); - - /*if( Q_strcmp( negl.extensions, " EGL_KHR_create_context ") ) - { - if( vid_android.gl_attribs_set[REF_GL_CONTEXT_FLAGS] ) - { - attribs[i++] = 0x30FC; // EGL_CONTEXT_FLAGS_KHR - attribs[i++] = vid_android.gl_attribs[REF_GL_CONTEXT_FLAGS] & ((REF_GL_CONTEXT_ROBUST_ACCESS_FLAG << 1) - 1); - } - - if( vid_android.gl_attribs_set[REF_GL_CONTEXT_PROFILE_MASK] ) - { - int val = vid_android.gl_attribs[REF_GL_CONTEXT_PROFILE_MASK]; - - if( val & ( (REF_GL_CONTEXT_PROFILE_COMPATIBILITY << 1) - 1 ) ) - { - attribs[i++] = 0x30FD; // EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; - attribs[i++] = val; - } - } - - COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION ); - COPY_ATTR_IF_SET( REF_GL_CONTEXT_MINOR_VERSION, 0x30FB ); - } - else*/ - { - // without extension we can set only major version - COPY_ATTR_IF_SET( REF_GL_CONTEXT_MAJOR_VERSION, EGL_CONTEXT_CLIENT_VERSION ); - } - - attribs[i++] = EGL_NONE; - - return i; -} - -qboolean VID_SetMode( void ) -{ - EGLint format; - jintArray attribs, contextAttribs; - static EGLint nAttribs[32+1], nContextAttribs[32+1]; - const size_t attribsSize = ARRAYSIZE( nAttribs ); - size_t s1, s2; - - if( vid_android.has_context ) - { - R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway - return true; - } - - s1 = VID_GenerateConfig(nAttribs, attribsSize); - s2 = VID_GenerateContextConfig(nContextAttribs, attribsSize); - - attribs = (*jni.env)->NewIntArray( jni.env, s1 ); - contextAttribs = (*jni.env)->NewIntArray( jni.env, s2 ); - - (*jni.env)->SetIntArrayRegion( jni.env, attribs, 0, s1, nAttribs ); - (*jni.env)->SetIntArrayRegion( jni.env, contextAttribs, 0, s2, nContextAttribs ); - - R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway - - if( glw_state.software ) - return true; - - if( (*jni.env)->CallStaticBooleanMethod( jni.env, jni.actcls, jni.createGLContext, attribs, contextAttribs ) ) - { - vid_android.has_context = true; - return true; - } - - return false; -} - -rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) -{ - int render_w, render_h; - uint rotate = vid_rotate->value; - - Android_GetScreenRes(&width, &height); - - render_w = width; - render_h = height; - - Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height); - - if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) - { - if( rotate & 1 ) - { - int swap = render_w; - - render_w = render_h; - render_h = swap; - } - - render_h /= vid_scale->value; - render_w /= vid_scale->value; - } - else - { - Con_Printf( S_WARN "failed to setup screen transform\n" ); - } - - R_SaveVideoMode( width, height, render_w, render_h ); - - refState.wideScreen = true; // V_AdjustFov will check for widescreen - - return rserr_ok; -} - -int GL_SetAttribute( int attr, int val ) -{ - if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) - return -1; - - vid_android.gl_attribs[attr] = val; - vid_android.gl_attribs_set[attr] = true; - return 0; -} - -int GL_GetAttribute( int attr, int *val ) -{ - EGLBoolean ret; - - if( attr < 0 || attr >= REF_GL_ATTRIBUTES_COUNT ) - return -1; - - if( !val ) - return -1; - - switch( attr ) - { - case REF_GL_RED_SIZE: - *val = Android_GetGLAttribute( EGL_RED_SIZE ); - return 0; - case REF_GL_GREEN_SIZE: - *val = Android_GetGLAttribute( EGL_GREEN_SIZE ); - return 0; - case REF_GL_BLUE_SIZE: - *val = Android_GetGLAttribute( EGL_BLUE_SIZE ); - return 0; - case REF_GL_ALPHA_SIZE: - *val = Android_GetGLAttribute( EGL_ALPHA_SIZE ); - return 0; - case REF_GL_DEPTH_SIZE: - *val = Android_GetGLAttribute( EGL_DEPTH_SIZE ); - return 0; - case REF_GL_STENCIL_SIZE: - *val = Android_GetGLAttribute( EGL_STENCIL_SIZE ); - return 0; - case REF_GL_MULTISAMPLESAMPLES: - *val = Android_GetGLAttribute( EGL_SAMPLES ); - return 0; - } - - return -1; -} - -int R_MaxVideoModes( void ) -{ - return 0; -} - -vidmode_t* R_GetVideoMode( int num ) -{ - return NULL; -} - -void* GL_GetProcAddress( const char *name ) // RenderAPI requirement -{ - void *gles; - void *addr; - - if( vid_android.gles1 ) - { - if( !vid_android.libgles1 ) - vid_android.libgles1 = dlopen("libGLESv1_CM.so", RTLD_NOW); - gles = vid_android.libgles1; - } - else - { - if( !vid_android.libgles2 ) - vid_android.libgles2 = dlopen("libGLESv2.so", RTLD_NOW); - gles = vid_android.libgles2; - } - - if( gles && ( addr = dlsym(gles, name ) ) ) - return addr; - - if( !egl.GetProcAddress ) - return NULL; - - return egl.GetProcAddress( name ); -} - -void GL_UpdateSwapInterval( void ) -{ - // disable VSync while level is loading - if( cls.state < ca_active ) - { - Android_SwapInterval( 0 ); - SetBits( gl_vsync->flags, FCVAR_CHANGED ); - } - else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) - { - ClearBits( gl_vsync->flags, FCVAR_CHANGED ); - Android_SwapInterval( gl_vsync->value ); - } -} - -void *SW_LockBuffer( void ) -{ - ANativeWindow_Buffer buffer; - if( !nw.lock || !vid_android.window ) - return NULL; - if( nw.lock( vid_android.window, &buffer, NULL ) ) - return NULL; - if( buffer.width < refState.width || buffer.height < refState.height ) - return NULL; - return buffer.bits; -} - -void SW_UnlockBuffer( void ) -{ - if( nw.unlockAndPost ) - nw.unlockAndPost( vid_android.window ); -} - -qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) -{ - ANativeWindow_Buffer buffer; - int lock; - if( !nw.lock || !vid_android.window ) - return false; - nw.unlockAndPost( vid_android.window ); - - if( ( lock = nw.lock( vid_android.window, &buffer, NULL ) ) ) - { - Con_Printf( "SW_CreateBuffer: lock %d\n", lock ); - return false; - } - nw.unlockAndPost( vid_android.window ); - Con_Printf( "SW_CreateBuffer: buffer %d %d %x %d %p\n", buffer.width, buffer.height, buffer.format, buffer.stride, buffer.bits ); - if( width > buffer.width || height > buffer.height ) - { - Con_Printf( "SW_CreateBuffer: buffer too small %d %d\n", width, height ); - // resize event missed? - if( jni.width < buffer.width ) - jni.width = buffer.width; - if( jni.height < buffer.height ) - jni.width = buffer.height; - VID_SetMode(); - Android_UpdateSurface( 1 ); - return false; - } - if( buffer.format != WINDOW_FORMAT_RGB_565 ) - { - Con_Printf( "SW_CreateBuffer: wrong format %d\n", buffer.format ); - return false; - } - Con_Printf( "SW_CreateBuffer: ok\n" ); - *stride = buffer.stride; - - *bpp = 2; - *r = (((1 << 5) - 1) << (5+6)); - *g = (((1 << 6) - 1) << (5)); - *b = (((1 << 5) - 1) << (0)); - return true; -} diff --git a/engine/platform/dos/in_dos.c b/engine/platform/dos/in_dos.c index 3e2f30cc..0444341e 100644 --- a/engine/platform/dos/in_dos.c +++ b/engine/platform/dos/in_dos.c @@ -80,32 +80,11 @@ void Platform_RunEvents( void ) } -void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) -{ - *x = *y = 0; -} - - -void GAME_EXPORT Platform_SetMousePos(int x, int y) -{ - -} - -int Platform_JoyInit( int numjoy ) -{ - return 0; -} - void Platform_EnableTextInput( qboolean enable ) { keystate.chars = enable; } -void Platform_PreCreateMove( void ) -{ - -} - void Platform_MouseMove( float *x, float *y ) { static int lx,ly; diff --git a/engine/platform/dos/sys_dos.c b/engine/platform/dos/sys_dos.c index 6256b068..d79026b5 100644 --- a/engine/platform/dos/sys_dos.c +++ b/engine/platform/dos/sys_dos.c @@ -15,31 +15,11 @@ GNU General Public License for more details. #include "platform/platform.h" #include -void Platform_GetClipboardText( char *buffer, size_t size ) -{ - -} - -void Platform_SetClipboardText( const char *buffer, size_t size ) -{ - -} - - -void *Platform_GetNativeObject( const char *name ) -{ - return NULL; -} - - -void Platform_Vibrate(float life, char flags) -{ - -} void Platform_ShellExecute( const char *path, const char *parms ) { } + volatile int ticks=0; #if XASH_TIMER == TIMER_DOS @@ -47,12 +27,8 @@ double Platform_DoubleTime( void ) { return 0.005*ticks; } - -void Platform_Sleep( int msec ) -{ - //usleep( msec * 1000 ); -} #endif // XASH_TIMER == TIMER_DOS + #define PIT_FREQUENCY 0x1234DDL #define frequency 140 #define counter PIT_FREQUENCY/frequency @@ -89,7 +65,7 @@ static void __interrupt __far timerhandler() // in_dos.c extern void __interrupt __far keyhandler( void ); -void Platform_Init( void ) +void DOS_Init( void ) { // save original vectors orig_int_1c = _dos_getvect( 0x1c ); @@ -104,7 +80,7 @@ void Platform_Init( void ) } -void Platform_Shutdown( void ) +void DOS_Shutdown( void ) { // restore freq outp( 0x43, 0x34 ); diff --git a/engine/platform/dos/vid_dos.c b/engine/platform/dos/vid_dos.c index 6e96f2c0..ae5129c2 100644 --- a/engine/platform/dos/vid_dos.c +++ b/engine/platform/dos/vid_dos.c @@ -27,6 +27,11 @@ GNU General Public License for more details. #include #include +void Platform_Minimize_f( void ) +{ + // stub +} + /* ======================== Android_SwapBuffers @@ -48,8 +53,6 @@ qboolean R_Init_Video( const int type ) { qboolean retval; - VID_StartupGamma(); - if( type != REF_SOFTWARE ) return false; /// glide??? @@ -76,12 +79,12 @@ void R_Free_Video( void ) qboolean VID_SetMode( void ) { - R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway + R_ChangeDisplaySettings( 0, 0, WINDOW_MODE_FULLSCREEN ); // width and height are ignored anyway return true; } -rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) { int render_w, render_h; uint rotate = vid_rotate->value; @@ -91,7 +94,7 @@ rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) render_w = width; render_h = height; - Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height ); + Con_Reportf( "%s: forced resolution to %dx%d)\n", __func__, width, height ); if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) { @@ -144,17 +147,10 @@ static qboolean vsync; void GL_UpdateSwapInterval( void ) { - // disable VSync while level is loading - if( cls.state < ca_active ) + if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) { - // setup vsync here - vsync = false; - SetBits( gl_vsync->flags, FCVAR_CHANGED ); - } - else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) - { - ClearBits( gl_vsync->flags, FCVAR_CHANGED ); - vsync = true; + ClearBits( gl_vsync.flags, FCVAR_CHANGED ); + vsync = gl_vsync.value; } } @@ -547,4 +543,9 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * return true; } +ref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type ) +{ + return REF_WINDOW_TYPE_NULL; +} + #endif diff --git a/engine/platform/emscripten/lib_em.c b/engine/platform/emscripten/lib_em.c deleted file mode 100644 index b239056f..00000000 --- a/engine/platform/emscripten/lib_em.c +++ /dev/null @@ -1,44 +0,0 @@ -/* -em_lib.h - dynamic library code for iOS -Copyright (C) 2017-2018 mittorn - -This program is free software: you can redistribute it and/sor modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ -#ifdef __EMSCRIPTEN__ -#include -#include "common.h" -#include "library.h" -#include "filesystem.h" -#include "server.h" - -void *EMSCRIPTEN_LoadLibrary( const char *dllname ) -{ - void *pHandle = NULL; - -#ifdef EMSCRIPTEN_LIB_FS - char path[MAX_SYSPATH]; - string prefix; - Q_strcpy(prefix, getenv( "LIBRARY_PREFIX" ) ); - Q_snprintf( path, MAX_SYSPATH, "%s%s%s", prefix, dllname, getenv( "LIBRARY_SUFFIX" ) ); - pHandle = dlopen( path, RTLD_LAZY ); - if( !pHandle ) - { - COM_PushLibraryError( va("Loading %s:\n", path ) ); - COM_PushLibraryError( dlerror() ); - } - return pHandle; -#else - // get handle of preloaded library outside fs - return EM_ASM_INT( return DLFCN.loadedLibNames[Pointer_stringify($0)], (int)dllname ); -#endif -} - -#endif // __EMSCRIPTEN__ diff --git a/engine/platform/emscripten/lib_em.h b/engine/platform/emscripten/lib_em.h deleted file mode 100644 index 30cd8661..00000000 --- a/engine/platform/emscripten/lib_em.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -em_lib.h - dynamic library code for iOS -Copyright (C) 2017-2018 mittorn - -This program is free software: you can redistribute it and/sor modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ -#pragma once -#include "build.h" -#ifdef XASH_EMSCRIPTEN -#ifndef EM_LIB_H -#define EM_LIB_H - -#define Platform_POSIX_LoadLibrary( x ) EMSCRIPTEN_LoadLibrary(( x )) -#ifndef EMSCRIPTEN_LIB_FS -#define Platform_POSIX_FreeLibrary( x ) // nothing -#endif // EMSCRIPTEN_LIB_FS - -void *EMSCRIPTEN_LoadLibrary( const char *dllname ); - -#endif // EM_LIB_H -#endif // __EMSCRIPTEN__ diff --git a/engine/platform/irix/dladdr.c b/engine/platform/irix/dladdr.c new file mode 100644 index 00000000..a7985dba --- /dev/null +++ b/engine/platform/irix/dladdr.c @@ -0,0 +1,39 @@ +/* +dladdr.c - dladdr implementation for SGI IRIX +Copyright (C) 2022 Xav101 +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +/* + * From SGI IRIX's 'man dladdr' + * + * does not contain a prototype for dladdr or definition of + * Dl_info. The #include in the SYNOPSIS line is traditional, + * but contains no dladdr prototype and no IRIX library contains an + * implementation. Write your own declaration based on the code below. + * + * The following code is dependent on internal interfaces that are not + * part of the IRIX compatibility guarantee; however, there is no future + * intention to change this interface, so on a practical level, the code + * below is safe to use on IRIX. + * + * + * + * The following code has been reproduced from the manpage. + */ + +#include "dladdr.h" + +int dladdr(void *address, Dl_info* dl) +{ + void *v; + v = _rld_new_interface(_RLD_DLADDR, address, dl); + return (int)v; +} diff --git a/engine/platform/irix/dladdr.h b/engine/platform/irix/dladdr.h new file mode 100644 index 00000000..f4a4162b --- /dev/null +++ b/engine/platform/irix/dladdr.h @@ -0,0 +1,35 @@ +/* +dladdr.h - dladdr prototypes for SGI IRIX +Copyright (C) 2022 Xav101 +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +/* See engine/platform/irix/dladdr.c for the requirement for this implementation */ + +#ifndef DLADDR_IRIX_H +#define DLADDR_IRIX_H + +#include +#ifndef _RLD_INTERFACE_DLFCN_H_DLADDR +#define _RLD_INTERFACE_DLFCN_H_DLADDR +typedef struct Dl_info { + const char * dli_fname; + void * dli_fbase; + const char * dli_saddr; + int dli_version; + int dli_reserved1; + long dli_reserved[4]; +} Dl_info; +#endif +#define _RLD_DLADDR 14 + +int dladdr(void *address, Dl_info* dl); + +#endif diff --git a/engine/platform/irix/xash-exec b/engine/platform/irix/xash-exec new file mode 100755 index 00000000..c5c3c8c1 --- /dev/null +++ b/engine/platform/irix/xash-exec @@ -0,0 +1,9 @@ +#!/usr/sgug/bin/bash + +# Build path +export LD_LIBRARYN32_PATH=$PWD/filesystem:$LD_LIBRARYN32_PATH + +# Install path +export LD_LIBRARYN32_PATH=$PWD:$LD_LIBRARYN32_PATH + +exec $PWD/build/engine/xash diff --git a/engine/platform/linux/in_evdev.c b/engine/platform/linux/in_evdev.c index 638ceddb..6a3e4c00 100644 --- a/engine/platform/linux/in_evdev.c +++ b/engine/platform/linux/in_evdev.c @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "platform/platform.h" -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV #include "common.h" @@ -37,7 +37,7 @@ struct evdev_s qboolean shift; } evdev; -static convar_t *evdev_keydebug; +static CVAR_DEFINE_AUTO( evdev_keydebug, "0", 0, "print key events to console" ); static int KeycodeFromEvdev(int keycode, int value) { @@ -175,11 +175,13 @@ void Evdev_Autodetect_f( void ) if( evdev.devices >= MAX_EVDEV_DEVICES ) continue; - Q_snprintf( path, MAX_STRING, "/dev/input/%s", entry->d_name ); + Q_snprintf( path, sizeof( path ), "/dev/input/%s", entry->d_name ); for( i = 0; i < evdev.devices; i++ ) - if( !Q_strncmp( evdev.paths[i], path, MAX_STRING ) ) + { + if( !Q_strncmp( evdev.paths[i], path, sizeof( evdev.paths[i] ))) goto next; + } if( Q_strncmp( entry->d_name, "event", 5 ) ) continue; @@ -220,7 +222,7 @@ void Evdev_Autodetect_f( void ) } goto close; open: - Q_strncpy( evdev.paths[evdev.devices], path, MAX_STRING ); + Q_strncpy( evdev.paths[evdev.devices], path, sizeof( evdev.paths[0] )); evdev.fds[evdev.devices++] = fd; Con_Printf( "Opened device %s\n", path ); #if XASH_INPUT == INPUT_EVDEV @@ -260,7 +262,7 @@ void Evdev_OpenDevice ( const char *path ) for( i = 0; i < evdev.devices; i++ ) { - if( !Q_strncmp( evdev.paths[i], path, MAX_STRING ) ) + if( !Q_strncmp( evdev.paths[i], path, sizeof( evdev.paths[i] ))) { Con_Printf( "device %s already open!\n", path ); return; @@ -270,12 +272,12 @@ void Evdev_OpenDevice ( const char *path ) ret = open( path, O_RDONLY | O_NONBLOCK ); if( ret < 0 ) { - Con_Reportf( S_ERROR "Could not open input device %s: %s\n", path, strerror( errno ) ); + Con_Reportf( S_ERROR "Could not open input device %s: %s\n", path, strerror( errno ) ); return; } Con_Printf( "Input device #%d: %s opened sucessfully\n", evdev.devices, path ); evdev.fds[evdev.devices] = ret; - Q_strncpy( evdev.paths[evdev.devices++], path, MAX_STRING ); + Q_strncpy( evdev.paths[evdev.devices++], path, sizeof( evdev.paths[0] )); #if XASH_INPUT == INPUT_EVDEV if( Sys_CheckParm( "-grab" ) ) @@ -309,7 +311,7 @@ void Evdev_CloseDevice_f ( void ) if( Q_isdigit( arg ) ) i = Q_atoi( arg ); else for( i = 0; i < evdev.devices; i++ ) - if( !Q_strncmp( evdev.paths[i], arg, MAX_STRING ) ) + if( !Q_strncmp( evdev.paths[i], arg, sizeof( evdev.paths[i] ))) break; if( i >= evdev.devices ) @@ -324,7 +326,7 @@ void Evdev_CloseDevice_f ( void ) for( ; i < evdev.devices; i++ ) { - Q_strncpy( evdev.paths[i], evdev.paths[i+1], MAX_STRING ); + Q_strncpy( evdev.paths[i], evdev.paths[i+1], sizeof( evdev.paths[i] )); evdev.fds[i] = evdev.fds[i+1]; } } @@ -343,23 +345,16 @@ void IN_EvdevFrame ( void ) { switch ( ev.code ) { - case REL_X: dx += ev.value; + case REL_X: + dx += ev.value; break; - case REL_Y: dy += ev.value; + case REL_Y: + dy += ev.value; break; - case REL_WHEEL: - if( ev.value > 0) - { - Key_Event( K_MWHEELDOWN, 1 ); - Key_Event( K_MWHEELDOWN, 0 ); - } - else - { - Key_Event( K_MWHEELUP, 1 ); - Key_Event( K_MWHEELUP, 0 ); - } + case REL_WHEEL: + IN_MWheelEvent( ev.value ); break; } } @@ -367,7 +362,7 @@ void IN_EvdevFrame ( void ) { int key = KeycodeFromEvdev( ev.code, ev.value ); - if( CVAR_TO_BOOL(evdev_keydebug) ) + if( evdev_keydebug.value ) Con_Printf( "key %d %d %d\n", ev.code, key, ev.value ); Key_Event( key , ev.value ); @@ -394,11 +389,11 @@ void IN_EvdevFrame ( void ) Key_ClearStates(); } - if( CVAR_TO_BOOL(m_ignore) ) + if( m_ignore.value ) continue; - evdev.x += -dx * m_yaw->value; - evdev.y += dy * m_pitch->value; + evdev.x += -dx * m_yaw.value; + evdev.y += dy * m_pitch.value; } if( evdev.grabtime <= host.realtime ) evdev.grab = false; @@ -464,7 +459,7 @@ void Evdev_Shutdown( void ) Cmd_RemoveCommand( "evdev_open" ); Cmd_RemoveCommand( "evdev_close" ); Cmd_RemoveCommand( "evdev_autodetect" ); - evdev_keydebug = Cvar_Get( "evdev_keydebug", "0", 0, "print key events to console" ); + Cvar_RegisterVariable( &evdev_keydebug ); for( i = 0; i < evdev.devices; i++ ) { diff --git a/engine/platform/linux/s_alsa.c b/engine/platform/linux/s_alsa.c index 6875a0c0..2c6524ef 100644 --- a/engine/platform/linux/s_alsa.c +++ b/engine/platform/linux/s_alsa.c @@ -184,7 +184,7 @@ qboolean SNDDMA_Init( void ) return false; } - dma.buffer = Z_Malloc( samples * 2 ); //allocate pcm frame buffer + dma.buffer = Mem_Calloc( sndpool, samples * 2 ); //allocate pcm frame buffer dma.samplepos = 0; dma.samples = samples; dma.format.width = 2; diff --git a/engine/platform/linux/sys_linux.c b/engine/platform/linux/sys_linux.c index 23d0aa13..1788623e 100644 --- a/engine/platform/linux/sys_linux.c +++ b/engine/platform/linux/sys_linux.c @@ -13,12 +13,38 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include #include #include +#include +#include +#include +#include +#include +#include +#include #include "platform/platform.h" +#include -qboolean Sys_DebuggerPresent( void ) +// Glibc misses this macro in POSIX headers (bits/types/sigevent_t.h) +// but it does present in Linux headers (asm-generic/siginfo.h) and musl +#if !defined( sigev_notify_thread_id ) +#define sigev_notify_thread_id _sigev_un._tid +#endif // !defined( sigev_notify_thread_id ) + +static void *g_hsystemd; +static int (*g_pfn_sd_notify)( int unset_environment, const char *state ); + +int Linux_GetProcessID( void ) +{ + return syscall( __NR_gettid ); +} + +qboolean Platform_DebuggerPresent( void ) { char buf[4096]; ssize_t num_read; @@ -50,3 +76,98 @@ qboolean Sys_DebuggerPresent( void ) return false; } + +void Platform_SetStatus( const char *status ) +{ + string notify_str; + + if( !g_pfn_sd_notify ) + return; + + // TODO: report STOPPING=1 + Q_snprintf( notify_str, sizeof( notify_str ), + "READY=1\n" + "WATCHDOG=1\n" + "STATUS=%s\n", status ); + + // Quote: In order to support both service managers that implement this + // scheme and those which do not, it is generally recommended to ignore + // the return value of this call + // a1ba: ok, you asked for no error handling :) + g_pfn_sd_notify( false, notify_str ); +} + +void Linux_Init( void ) +{ + // sd_notify only for dedicated targets, don't waste time on full client + if( !Host_IsDedicated( )) + return; + + // manpage says sd_notify will send messages to socket in NOTIFY_SOCKET + // environment variable. Check if it's available. + if( getenv( "NOTIFY_SOCKET" ) == NULL ) + return; + + if(( g_hsystemd = dlopen( "libsystemd.so.0", RTLD_LAZY )) == NULL ) + return; + + if(( g_pfn_sd_notify = dlsym( g_hsystemd, "sd_notify" )) == NULL ) + { + dlclose( g_hsystemd ); + g_hsystemd = NULL; + } + + Con_Reportf( "%s: sd_notify found, will report status to systemd\n", __func__ ); +} + +void Linux_Shutdown( void ) +{ + g_pfn_sd_notify = NULL; + + if( g_hsystemd ) + { + dlclose( g_hsystemd ); + g_hsystemd = NULL; + } +} + +static void Linux_TimerHandler( int sig, siginfo_t *si, void *uc ) +{ + timer_t *tidp = si->si_value.sival_ptr; + int overrun = timer_getoverrun( *tidp ); + Con_Printf( "Frame too long (overrun %d)!\n", overrun ); +} + +#define DEBUG_TIMER_SIGNAL SIGRTMIN + +void Linux_SetTimer( float tm ) +{ + static timer_t timerid; + + if( !timerid && tm ) + { + struct sigevent sev = { 0 }; + struct sigaction sa = { 0 }; + + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = Linux_TimerHandler; + sigaction( DEBUG_TIMER_SIGNAL, &sa, NULL ); + // this path availiable in POSIX, but may signal wrong thread... + // sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_notify = SIGEV_THREAD_ID; + sev.sigev_notify_thread_id = Linux_GetProcessID(); + sev.sigev_signo = DEBUG_TIMER_SIGNAL; + sev.sigev_value.sival_ptr = &timerid; + timer_create( CLOCK_REALTIME, &sev, &timerid ); + } + + if( timerid ) + { + struct itimerspec its = {0}; + its.it_value.tv_sec = tm; + its.it_value.tv_nsec = 1000000000ULL * fmod( tm, 1.0f ); + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + timer_settime( timerid, 0, &its, NULL ); + } +} diff --git a/engine/platform/linux/vid_fbdev.c b/engine/platform/linux/vid_fbdev.c index bf9d169f..24c2be7e 100644 --- a/engine/platform/linux/vid_fbdev.c +++ b/engine/platform/linux/vid_fbdev.c @@ -28,6 +28,11 @@ struct fb_s #define DEFAULT_FBDEV "/dev/fb0" +void Platform_Minimize_f( void ) +{ + // stub +} + /* ======================== Android_SwapBuffers @@ -51,8 +56,6 @@ qboolean R_Init_Video( const int type ) string fbdev = DEFAULT_FBDEV; fb.fd = -1; - VID_StartupGamma(); - if( type != REF_SOFTWARE ) return false; @@ -62,7 +65,7 @@ qboolean R_Init_Video( const int type ) if( fb.fd < 0 ) { - Con_Printf( S_ERROR, "failed to open framebuffer device: %s\n", strerror(errno)); + Con_Printf( S_ERROR "failed to open framebuffer device: %s\n", strerror(errno)); } if( Sys_CheckParm( "-ttygfx" ) ) @@ -114,41 +117,24 @@ qboolean VID_SetMode( void ) { if( fb.tty_fd > 0 ) ioctl( fb.tty_fd, KDSETMODE, KD_GRAPHICS ); - R_ChangeDisplaySettings( 0, 0, false ); // width and height are ignored anyway + R_ChangeDisplaySettings( 0, 0, WINDOW_MODE_FULLSCREEN ); // width and height are ignored anyway return true; } -rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) { int render_w, render_h; - uint rotate = vid_rotate->value; FB_GetScreenRes( &width, &height ); render_w = width; render_h = height; - Con_Reportf( "R_ChangeDisplaySettings: forced resolution to %dx%d)\n", width, height ); + Con_Reportf( "%s: forced resolution to %dx%d)\n", __func__, width, height ); - if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) - { - if( rotate & 1 ) - { - int swap = render_w; - - render_w = render_h; - render_h = swap; - } - - render_h /= vid_scale->value; - render_w /= vid_scale->value; - } - else - { - Con_Printf( S_WARN "failed to setup screen transform\n" ); - } - R_SaveVideoMode( width, height, render_w, render_h ); + VID_SetDisplayTransform( &render_w, &render_h ); + R_SaveVideoMode( width, height, render_w, render_h, false ); return rserr_ok; } @@ -180,17 +166,10 @@ void* GL_GetProcAddress( const char *name ) // RenderAPI requirement void GL_UpdateSwapInterval( void ) { - // disable VSync while level is loading - if( cls.state < ca_active ) + if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) { - // setup fb vsync here - fb.vsync = false; - SetBits( gl_vsync->flags, FCVAR_CHANGED ); - } - else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) - { - ClearBits( gl_vsync->flags, FCVAR_CHANGED ); - fb.vsync = true; + ClearBits( gl_vsync.flags, FCVAR_CHANGED ); + fb.vsync = gl_vsync.value; } } @@ -267,51 +246,9 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * return true; } -// unrelated stubs -void Platform_GetClipboardText( char *buffer, size_t size ) +ref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type ) { - -} - -void Platform_SetClipboardText( const char *buffer, size_t size ) -{ - -} - -void Platform_PreCreateMove( void ) -{ - -} - -// will be implemented later -void Platform_RunEvents( void ) -{ - -} - -void *Platform_GetNativeObject( const char *name ) -{ - return NULL; -} - -void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) -{ - *x = *y = 0; -} - - -void GAME_EXPORT Platform_SetMousePos(int x, int y) -{ - -} - -void Platform_Vibrate(float life, char flags) -{ - -} -int Platform_JoyInit( int numjoy ) -{ - return 0; + return REF_WINDOW_TYPE_NULL; } #endif diff --git a/engine/platform/misc/lib_static.c b/engine/platform/misc/lib_static.c index f5f16552..84f5677a 100644 --- a/engine/platform/misc/lib_static.c +++ b/engine/platform/misc/lib_static.c @@ -20,19 +20,19 @@ GNU General Public License for more details. void *dlsym(void *handle, const char *symbol ) { - Con_DPrintf( "dlsym( %p, \"%s\" ): stub\n", handle, symbol ); + Con_DPrintf( "%s( %p, \"%s\" ): stub\n", __func__, handle, symbol ); return NULL; } void *dlopen(const char *name, int flag ) { - Con_DPrintf( "dlopen( \"%s\", %d ): stub\n", name, flag ); + Con_DPrintf( "%s( \"%s\", %d ): stub\n", __func__, name, flag ); return NULL; } int dlclose(void *handle) { - Con_DPrintf( "dlsym( %p ): stub\n", handle ); + Con_DPrintf( "%s( %p ): stub\n", __func__, handle ); return 0; } diff --git a/engine/platform/nswitch/icon.jpg b/engine/platform/nswitch/icon.jpg new file mode 100644 index 00000000..3bb62ec2 Binary files /dev/null and b/engine/platform/nswitch/icon.jpg differ diff --git a/engine/platform/nswitch/sys_nswitch.c b/engine/platform/nswitch/sys_nswitch.c new file mode 100644 index 00000000..c6a3c9c7 --- /dev/null +++ b/engine/platform/nswitch/sys_nswitch.c @@ -0,0 +1,124 @@ +/* +switch.c - switch backend +Copyright (C) 2021-2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int nxlink_sock = -1; + +/* HACKHACK: force-export stuff required by the dynamic libs */ + +// this is required by some std::filesystem crap in libstdc++ +// we don't have it defined in our libc +long pathconf( const char *path, int name ) { return -1; } + +// part of libunwind; required by any dynamic lib that uses C++ exceptions +extern void *_Unwind_Resume; +extern void *_Unwind_GetIPInfo; + +// these are macros in our libc, so we need to wrap them +static int tolower_fn( int c ) { return tolower( c ); } +static int toupper_fn( int c ) { return toupper( c ); } +static int isalnum_fn( int c ) { return isalnum( c ); } +static int isalpha_fn( int c ) { return isalpha( c ); } + +static const solder_export_t aux_exports[] = +{ + SOLDER_EXPORT( "tolower", tolower_fn ), + SOLDER_EXPORT( "toupper", toupper_fn ), + SOLDER_EXPORT( "isalnum", isalnum_fn ), + SOLDER_EXPORT( "isalpha", isalpha_fn ), + SOLDER_EXPORT_SYMBOL( mkdir ), + SOLDER_EXPORT_SYMBOL( remove ), + SOLDER_EXPORT_SYMBOL( rename ), + SOLDER_EXPORT_SYMBOL( pathconf ), + SOLDER_EXPORT_SYMBOL( fsync ), + SOLDER_EXPORT_SYMBOL( strchrnul ), + SOLDER_EXPORT_SYMBOL( stpcpy ), + SOLDER_EXPORT_SYMBOL( _Unwind_Resume ), + SOLDER_EXPORT_SYMBOL( _Unwind_GetIPInfo ), +}; + +const solder_export_t *__solder_aux_exports = aux_exports; +const size_t __solder_num_aux_exports = sizeof( aux_exports ) / sizeof( *aux_exports ); + +/* end of export crap */ + +void Platform_ShellExecute( const char *path, const char *parms ) +{ + Con_Reportf( S_WARN "Tried to shell execute ;%s; -- not supported\n", path ); +} + +#if XASH_MESSAGEBOX == MSGBOX_NSWITCH +void Platform_MessageBox( const char *title, const char *message, qboolean unused ) +{ + // TODO: maybe figure out how to show an actual messagebox or an on-screen console + // without murdering the renderer + // assume this is a fatal error + FILE *f = fopen( "fatal.log", "w" ); + if ( f ) + { + fprintf( f, "%s:\n%s\n", title, message ); + fclose( f ); + } + // dump to nxlink as well + fprintf( stderr, "%s:\n%s\n", title, message ); +} +#endif // XASH_MESSAGEBOX == MSGBOX_NSWITCH + +// this gets executed before main(), do not delete +void userAppInit( void ) +{ + socketInitializeDefault( ); +#ifdef NSWITCH_DEBUG + nxlink_sock = nxlinkStdio( ); +#endif + if ( solder_init( 0 ) < 0 ) + { + fprintf( stderr, "solder_init() failed: %s\n", solder_dlerror() ); + fflush( stderr ); + exit( 1 ); + } +} + +// this gets executed on exit(), do not delete +void userAppExit( void ) +{ + solder_quit( ); + if ( nxlink_sock >= 0 ) + { + close( nxlink_sock ); + nxlink_sock = -1; + } + socketExit( ); +} + +void NSwitch_Init( void ) +{ + printf( "%s\n", __func__ ); +} + +void NSwitch_Shutdown( void ) +{ + printf( "%s\n", __func__ ); +} diff --git a/engine/platform/nswitch/xash3d-fwgs.nacp b/engine/platform/nswitch/xash3d-fwgs.nacp new file mode 100644 index 00000000..7f8c9c53 Binary files /dev/null and b/engine/platform/nswitch/xash3d-fwgs.nacp differ diff --git a/engine/platform/platform.h b/engine/platform/platform.h index ead5bc6f..772ed47f 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -17,6 +17,7 @@ GNU General Public License for more details. #ifndef PLATFORM_H #define PLATFORM_H +#include #include "common.h" #include "system.h" #include "defaults.h" @@ -30,28 +31,181 @@ GNU General Public License for more details. ============================================================================== */ - -void Platform_Init( void ); -void Platform_Shutdown( void ); double Platform_DoubleTime( void ); void Platform_Sleep( int msec ); void Platform_ShellExecute( const char *path, const char *parms ); void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ); -// commented out, as this is an optional feature or maybe implemented in system API directly -// see system.c -// qboolean Sys_DebuggerPresent( void ); +void Platform_SetStatus( const char *status ); +qboolean Platform_DebuggerPresent( void ); + +// legacy iOS port functions +#if TARGET_OS_IOS +const char *IOS_GetDocsDir( void ); +void IOS_LaunchDialog( void ); +#endif // TARGET_OS_IOS + +#if XASH_WIN32 || XASH_LINUX +#define XASH_PLATFORM_HAVE_STATUS 1 +#else +#undef XASH_PLATFORM_HAVE_STATUS +#endif + +#if XASH_POSIX +void Posix_Daemonize( void ); +void Posix_SetupSigtermHandling( void ); +char *Posix_Input( void ); +#endif + +#if XASH_SDL +void SDLash_Init( const char *basedir ); +void SDLash_Shutdown( void ); +void SDLash_NanoSleep( int nsec ); +#endif #if XASH_ANDROID const char *Android_GetAndroidID( void ); const char *Android_LoadID( void ); void Android_SaveID( const char *id ); +void Android_Init( void ); +void *Android_GetNativeObject( const char *name ); +int Android_GetKeyboardHeight( void ); +void Android_Shutdown( void ); #endif #if XASH_WIN32 -void Platform_UpdateStatusLine( void ); -#else -static inline void Platform_UpdateStatusLine( void ) { } -#endif +void Win32_Init( qboolean con_showalways ); +void Win32_Shutdown( void ); +qboolean Win32_NanoSleep( int nsec ); +void Wcon_CreateConsole( qboolean con_showalways ); +void Wcon_DestroyConsole( void ); +void Wcon_InitConsoleCommands( void ); +void Wcon_ShowConsole( qboolean show ); +void Wcon_DisableInput( void ); +char *Wcon_Input( void ); +void Wcon_WinPrint( const char *pMsg ); +#endif + +#if XASH_NSWITCH +void NSwitch_Init( void ); +void NSwitch_Shutdown( void ); +#endif + +#if XASH_PSVITA +void PSVita_Init( void ); +void PSVita_Shutdown( void ); +qboolean PSVita_GetBasePath( char *buf, const size_t buflen ); +int PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv ); +void PSVita_InputUpdate( void ); +#endif + +#if XASH_DOS +void DOS_Init( void ); +void DOS_Shutdown( void ); +#endif + +#if XASH_LINUX +void Linux_Init( void ); +void Linux_Shutdown( void ); +void Linux_SetTimer( float time ); +int Linux_GetProcessID( void ); +#endif + +static inline void Platform_Init( qboolean con_showalways, const char *basedir ) +{ +#if XASH_POSIX + // daemonize as early as possible, because we need to close our file descriptors + Posix_Daemonize( ); +#endif + +#if XASH_SDL + SDLash_Init( basedir ); +#endif + +#if XASH_ANDROID + Android_Init( ); +#elif XASH_NSWITCH + NSwitch_Init( ); +#elif XASH_PSVITA + PSVita_Init( ); +#elif XASH_DOS + DOS_Init( ); +#elif XASH_WIN32 + Win32_Init( con_showalways ); +#elif XASH_LINUX + Linux_Init( ); +#endif +} + +static inline void Platform_Shutdown( void ) +{ +#if XASH_NSWITCH + NSwitch_Shutdown( ); +#elif XASH_PSVITA + PSVita_Shutdown( ); +#elif XASH_DOS + DOS_Shutdown( ); +#elif XASH_WIN32 + Win32_Shutdown( ); +#elif XASH_LINUX + Linux_Shutdown( ); +#endif + +#if XASH_SDL + SDLash_Shutdown( ); +#endif +} + +static inline qboolean Sys_DebuggerPresent( void ) +{ +#if XASH_LINUX || XASH_WIN32 + return Platform_DebuggerPresent(); +#else + return false; +#endif +} + +static inline void Platform_SetupSigtermHandling( void ) +{ +#if XASH_POSIX + Posix_SetupSigtermHandling( ); +#endif +} + +static inline qboolean Platform_NanoSleep( int nsec ) +{ +#if XASH_SDL == 3 + SDLash_NanoSleep( nsec ); + return true; + // SDL2 doesn't have nanosleep, so use low-level functions here +#elif XASH_POSIX + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = nsec, // just don't put large numbers here + }; + int ret = nanosleep( &ts, NULL ); + if( ret < 0 ) + return errno == EINTR; // ignore EINTR error, it just means sleep was interrupted + return true; +#elif XASH_WIN32 + return Win32_NanoSleep( nsec ); +#else + return false; +#endif +} + +#if XASH_WIN32 || XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE +void Sys_SetupCrashHandler( const char *argv0 ); +void Sys_RestoreCrashHandler( void ); +#else +static inline void Sys_SetupCrashHandler( const char *argv0 ) +{ +} + +static inline void Sys_RestoreCrashHandler( void ) +{ +} +#endif + /* ============================================================================== @@ -60,8 +214,13 @@ static inline void Platform_UpdateStatusLine( void ) { } ============================================================================== */ -void Platform_Vibrate( float life, char flags ); -void*Platform_GetNativeObject( const char *name ); +#if XASH_SDL >= 2 +void Platform_Vibrate( float life, char flags ); // left for compatibility +void Platform_Vibrate2( float time, int low_freq, int high_freq, uint flags ); +#else +static inline void Platform_Vibrate( float life, char flags ) {} +static inline void Platform_Vibrate2( float time, int low_freq, int high_freq, uint flags ) {} +#endif /* ============================================================================== @@ -70,38 +229,78 @@ void*Platform_GetNativeObject( const char *name ); ============================================================================== */ -// Gamepad support -int Platform_JoyInit( int numjoy ); // returns number of connected gamepads, negative if error -// Text input -void Platform_EnableTextInput( qboolean enable ); -key_modifier_t Platform_GetKeyModifiers( void ); -// System events -void Platform_RunEvents( void ); -// Mouse -void Platform_GetMousePos( int *x, int *y ); -void Platform_SetMousePos( int x, int y ); +#if XASH_SDL // only SDL based backends implements these functions void Platform_PreCreateMove( void ); -void Platform_MouseMove( float *x, float *y ); +void GAME_EXPORT Platform_GetMousePos( int *x, int *y ); +void GAME_EXPORT Platform_SetMousePos( int x, int y ); +qboolean Platform_GetMouseGrab( void ); +void Platform_SetMouseGrab( qboolean enable ); void Platform_SetCursorType( VGUI_DefaultCursor type ); -// Clipboard int Platform_GetClipboardText( char *buffer, size_t size ); void Platform_SetClipboardText( const char *buffer ); - -#if XASH_SDL == 12 -#define SDL_SetWindowGrab( wnd, state ) SDL_WM_GrabInput( (state) ) -#define SDL_MinimizeWindow( wnd ) SDL_WM_IconifyWindow() -#define SDL_IsTextInputActive() host.textmode +#else +static inline void Platform_PreCreateMove( void ) { } +static inline void GAME_EXPORT Platform_SetMousePos( int x, int y ) { } +static inline void Platform_SetMouseGrab( qboolean enable ) { } +static inline void Platform_SetCursorType( VGUI_DefaultCursor type ) { } +static inline int Platform_GetClipboardText( char *buffer, size_t size ) { return 0; } +static inline void Platform_SetClipboardText( const char *buffer ) { } +static inline qboolean Platform_GetMouseGrab( void ) { return false; } +static inline void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) +{ + if( x ) *x = 0; + if( y ) *y = 0; +} #endif -#if !XASH_SDL -#define SDL_VERSION_ATLEAST( x, y, z ) 0 +#if XASH_SDL || XASH_DOS +void Platform_RunEvents( void ); +void Platform_MouseMove( float *x, float *y ); +#else +static inline void Platform_RunEvents( void ) { } +static inline void Platform_MouseMove( float *x, float *y ) +{ + if( x ) *x = 0.0f; + if( y ) *y = 0.0f; +} #endif -#if XASH_ANDROID -void Android_ShowMouse( qboolean show ); -void Android_MouseMove( float *x, float *y ); +#if XASH_SDL >= 2 || XASH_PSVITA || XASH_DOS || XASH_USE_EVDEV +void Platform_EnableTextInput( qboolean enable ); +#else +static inline void Platform_EnableTextInput( qboolean enable ) { } #endif +#if XASH_SDL >= 2 +int Platform_JoyInit( void ); // returns number of connected gamepads, negative if error +void Platform_JoyShutdown( void ); +void Platform_CalibrateGamepadGyro( void ); +key_modifier_t Platform_GetKeyModifiers( void ); +#else +static inline int Platform_JoyInit( void ) { return 0; } +static inline void Platform_JoyShutdown( void ) { } +static inline void Platform_CalibrateGamepadGyro( void ) { } +static inline key_modifier_t Platform_GetKeyModifiers( void ) { return KeyModifier_None; } +#endif + +static inline void Platform_SetTimer( float time ) +{ +#if XASH_LINUX + Linux_SetTimer( time ); +#endif +} + +static inline char *Platform_Input( void ) +{ +#if XASH_WIN32 + return Wcon_Input(); +#elif XASH_POSIX && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY + return Posix_Input(); +#else + return NULL; +#endif +} + /* ============================================================================== @@ -118,11 +317,14 @@ typedef enum } rserr_t; struct vidmode_s; +typedef enum window_mode_e window_mode_t; +typedef enum ref_window_type_e ref_window_type_t; + // Window qboolean R_Init_Video( const int type ); void R_Free_Video( void ); qboolean VID_SetMode( void ); -rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ); +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ); int R_MaxVideoModes( void ); struct vidmode_s *R_GetVideoMode( int num ); void* GL_GetProcAddress( const char *name ); // RenderAPI requirement @@ -133,12 +335,13 @@ void GL_SwapBuffers( void ); void *SW_LockBuffer( void ); void SW_UnlockBuffer( void ); qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ); - +void Platform_Minimize_f( void ); +ref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type ); // // in_evdev.c // -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV void Evdev_SetGrab( qboolean grab ); void Evdev_Shutdown( void ); void Evdev_Init( void ); @@ -167,4 +370,93 @@ void VoiceCapture_Shutdown( void ); qboolean VoiceCapture_Activate( qboolean activate ); qboolean VoiceCapture_Lock( qboolean lock ); +// this allows to make break in current line, without entering libc code +// libc built with -fomit-frame-pointer may just eat stack frame (hello, glibc), making entering libc even more useless +// calling syscalls directly allows to make break like if it was asm("int $3") on x86 +#if XASH_LINUX && XASH_X86 + #define INLINE_RAISE(x) asm volatile( "int $3;" ); + #define INLINE_NANOSLEEP1() // nothing! +#elif XASH_LINUX && XASH_ARM && !XASH_64BIT + #include + #include + #define INLINE_RAISE(x) do \ + { \ + int raise_pid = getpid(); \ + pid_t raise_tid = Linux_GetProcessID(); \ + int raise_sig = (x); \ + __asm__ volatile ( \ + "mov r7,#268\n\t" \ + "mov r0,%0\n\t" \ + "mov r1,%1\n\t" \ + "mov r2,%2\n\t" \ + "svc 0\n\t" \ + : \ + : "r"(raise_pid), "r"(raise_tid), "r"(raise_sig) \ + : "r0", "r1", "r2", "r7", "memory" \ + ); \ + } while( 0 ) + #define INLINE_NANOSLEEP1() do \ + { \ + struct timespec ns_t1 = {1, 0}; \ + struct timespec ns_t2 = {0, 0}; \ + __asm__ volatile ( \ + "mov r7,#162\n\t" \ + "mov r0,%0\n\t" \ + "mov r1,%1\n\t" \ + "svc 0\n\t" \ + : \ + : "r"(&ns_t1), "r"(&ns_t2) \ + : "r0", "r1", "r7", "memory" \ + ); \ + } while( 0 ) +#elif XASH_LINUX && XASH_ARM && XASH_64BIT + #include + #include + #define INLINE_RAISE(x) do \ + { \ + int raise_pid = getpid(); \ + pid_t raise_tid = Linux_GetProcessID(); \ + int raise_sig = (x); \ + __asm__ volatile ( \ + "mov x8,#131\n\t" \ + "mov x0,%0\n\t" \ + "mov x1,%1\n\t" \ + "mov x2,%2\n\t" \ + "svc 0\n\t" \ + : \ + : "r"(raise_pid), "r"(raise_tid), "r"(raise_sig) \ + : "x0", "x1", "x2", "x8", "memory", "cc" \ + ); \ + } while( 0 ) + #define INLINE_NANOSLEEP1() do \ + { \ + struct timespec ns_t1 = {1, 0}; \ + struct timespec ns_t2 = {0, 0}; \ + __asm__ volatile ( \ + "mov x8,#101\n\t" \ + "mov x0,%0\n\t" \ + "mov x1,%1\n\t" \ + "svc 0\n\t" \ + : \ + : "r"(&ns_t1), "r"(&ns_t2) \ + : "x0", "x1", "x8", "memory", "cc" \ + ); \ + } while( 0 ) +#elif XASH_LINUX + #if defined( __NR_tgkill ) + #define INLINE_RAISE(x) syscall( __NR_tgkill, getpid(), Linux_GetProcessID(), x ) + #else // __NR_tgkill + #define INLINE_RAISE(x) raise(x) + #endif // __NR_tgkill + #define INLINE_NANOSLEEP1() do \ + { \ + struct timespec ns_t1 = {1, 0}; \ + struct timespec ns_t2 = {0, 0}; \ + nanosleep( &ns_t1, &ns_t2 ); \ + } while( 0 ) +#else // generic + #define INLINE_RAISE(x) raise(x) + #define INLINE_NANOSLEEP1() sleep(1) +#endif // generic + #endif // PLATFORM_H diff --git a/engine/platform/posix/con_posix.c b/engine/platform/posix/con_posix.c new file mode 100644 index 00000000..db8b9115 --- /dev/null +++ b/engine/platform/posix/con_posix.c @@ -0,0 +1,52 @@ +/* +con_posix.c - reading from stdin +Copyright (C) 2024 Flying With Gauss + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" + +#if !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY +// use with caution, running engine in Qt Creator may cause a freeze in read() call +// I have never encountered this bug anywhere else, so still enable by default +#include + +char *Posix_Input( void ) +{ + fd_set rfds; + static char line[1024]; + static int len; + struct timeval tv = { 0 }; + + if( !Host_IsDedicated( )) + return NULL; + + FD_ZERO( &rfds ); + FD_SET( 0, &rfds); // stdin + while( select( 1, &rfds, NULL, NULL, &tv ) > 0 ) + { + if( read( 0, &line[len], 1 ) != 1 ) + break; + if( line[len] == '\n' || len > ( sizeof( line ) - 2 )) + { + line[ ++len ] = 0; + len = 0; + return line; + } + len++; + tv.tv_sec = 0; + tv.tv_usec = 0; + } + + return NULL; +} +#endif // !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY diff --git a/engine/platform/posix/crash.h b/engine/platform/posix/crash.h new file mode 100644 index 00000000..69f86f19 --- /dev/null +++ b/engine/platform/posix/crash.h @@ -0,0 +1,25 @@ +/* +crash.h - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +// +// crash_libbacktrace.c +// +int Sys_CrashDetailsLibbacktrace( int logfd, char *message, int len, size_t max_len ); +qboolean Sys_SetupLibbacktrace( const char *argv0 ); + +// +// crash_glibc.c +// +int Sys_CrashDetailsExecinfo( int logfd, char *message, int len, size_t max_len ); diff --git a/engine/platform/posix/crash_glibc.c b/engine/platform/posix/crash_glibc.c new file mode 100644 index 00000000..6805caf4 --- /dev/null +++ b/engine/platform/posix/crash_glibc.c @@ -0,0 +1,49 @@ +/* +crash_glibc.c - advanced crashhandler based on glibc's execinfo API +Copyright (C) 2016 Mittorn +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +// on Glibc (which potentially might not be only Linux) systems we +// have backtrace() and backtrace_symbols() calls, which replace for us +// platform-specific code +#if HAVE_EXECINFO +#include +#include +#include "common.h" +#include "input.h" +#include "crash.h" + +int Sys_CrashDetailsExecinfo( int logfd, char *message, int len, size_t max_len ) +{ + void *addrs[16]; + int size = backtrace( addrs, sizeof( addrs ) / sizeof( addrs[0] )); + char **syms = backtrace_symbols( addrs, size ); + + for( int i = 0; i < size && syms; i++ ) + { + size_t symlen = Q_strlen( syms[i] ); + char ch = '\n'; + + write( logfd, syms[i], symlen ); + write( logfd, &ch, 1 ); + + write( STDERR_FILENO, syms[i], symlen ); + write( STDERR_FILENO, &ch, 1 ); + + len += Q_snprintf( message + len, max_len - len, "%2d: %s\n", i, syms[i] ); + } + + return len; +} +#endif // HAVE_EXECINFO diff --git a/engine/platform/posix/crash_libbacktrace.c b/engine/platform/posix/crash_libbacktrace.c new file mode 100644 index 00000000..d1a466f4 --- /dev/null +++ b/engine/platform/posix/crash_libbacktrace.c @@ -0,0 +1,155 @@ +/* +crash_libbacktrace.c - advanced crashhandler based on libbacktrace +Copyright (C) 2016 Mittorn +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#if HAVE_LIBBACKTRACE +#include +#include "common.h" +#include "backtrace.h" +#include "input.h" +#include "crash.h" + +static struct backtrace_state *g_bt_state; +static qboolean enable_libbacktrace; + +static void Sys_BacktraceError( void *data, const char *msg, int errnum ) +{ + if( errnum < 0 ) + { + Con_Printf( S_ERROR "no symbol info, no libbacktrace\n" ); + return; + } + + Con_Printf( S_ERROR "libbacktrace error: %s (%d)\n", msg, errnum ); + + enable_libbacktrace = false; +} + +struct print_data +{ + char *message; + size_t message_size; + int len; + int idx; + int logfd; +}; + +static void Sys_AppendPrint( struct print_data *pd, const char *fmt, ... ) +{ + va_list va; + int len; + + va_start( va, fmt ); + len = Q_vsnprintf( pd->message, pd->message_size, fmt, va ); + va_end( va ); + + if( len > 0 ) + { + char ch = '\n'; + + write( pd->logfd, pd->message, len ); + write( pd->logfd, &ch, 1 ); + + write( STDERR_FILENO, pd->message, len ); + write( STDERR_FILENO, &ch, 1 ); + + pd->message += len; + pd->len += len; + pd->message_size -= len; + } +} + +static void Sys_BacktracePrintError( void *data, const char *msg, int errnum ) +{ + struct print_data *pd = data; + Sys_AppendPrint( pd, "%2d: error: %s (%d)\n", pd->idx++, msg, errnum ); +} + +static void Sys_BacktracePrintSyminfo( void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize ) +{ + struct print_data *pd = data; + Dl_info dlinfo = { 0 }; + const char *module_name; + + if( dladdr((void *)pc, &dlinfo )) + module_name = dlinfo.dli_fname; + else module_name = NULL; + + if( symname ) + { + if( module_name ) + Sys_AppendPrint( pd, "%2d: <%s+%d> (%s)\n", pd->idx++, symname, pc - symval, module_name ); + else + Sys_AppendPrint( pd, "%2d: <%s+%d>\n", pd->idx++, symname, pc - symval ); + } + else + { + if( module_name ) + Sys_AppendPrint( pd, "%2d: %p (%s)\n", pd->idx++, pc, module_name ); + else + Sys_AppendPrint( pd, "%2d: %p\n", pd->idx++, pc ); + } +} + +static int Sys_BacktracePrintFull( void *data, uintptr_t pc, const char *filename, int lineno, const char *function ) +{ + struct print_data *pd = data; + Dl_info dlinfo = { 0 }; + const char *module_name; + + if( dladdr((void *)pc, &dlinfo )) + module_name = dlinfo.dli_fname; + else module_name = NULL; + + if( filename && lineno >= 0 && function ) + { + filename = COM_FileWithoutPath( filename ); + + if( module_name ) + Sys_AppendPrint( pd, "%2d: %s (%s:%d) (%s)\n", pd->idx++, function, filename, lineno, module_name ); + else + Sys_AppendPrint( pd, "%2d: %s (%s:%d)\n", pd->idx++, function, filename, lineno ); + } + else + { + backtrace_syminfo( g_bt_state, pc, Sys_BacktracePrintSyminfo, Sys_BacktraceError, data ); + } + + return 0; +} + +int Sys_CrashDetailsLibbacktrace( int logfd, char *message, int len, size_t max_len ) +{ + struct print_data pd = + { + .message = message + len, + .message_size = sizeof( message ) - len, + .logfd = logfd, + .len = len, + }; + + backtrace_full( g_bt_state, 1, Sys_BacktracePrintFull, Sys_BacktracePrintError, &pd ); + + return pd.len; +} + +qboolean Sys_SetupLibbacktrace( const char *argv0 ) +{ + enable_libbacktrace = true; + g_bt_state = backtrace_create_state( argv0, true, Sys_BacktraceError, NULL ); + return g_bt_state != NULL && enable_libbacktrace; +} + +#endif // HAVE_EXECINFO diff --git a/engine/platform/posix/crash_posix.c b/engine/platform/posix/crash_posix.c new file mode 100644 index 00000000..615f2bc2 --- /dev/null +++ b/engine/platform/posix/crash_posix.c @@ -0,0 +1,120 @@ +/* +crash_posix.c - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" + +// while this is mostly POSIX-compatible functions +// the contents of ucontext_t is platform-dependent +// before adding new OS here, check platform.h +#define _XOPEN_SOURCE 1 // required for ucontext +#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX || XASH_APPLE +#ifndef XASH_OPENBSD + #include +#endif +#include +#include +#include "library.h" +#include "input.h" +#include "crash.h" + +static qboolean have_libbacktrace = false; + +static struct sigaction oldFilter; + +static void Sys_Crash( int signal, siginfo_t *si, void *context ) +{ + char message[8192]; + int len, logfd, i = 0; + qboolean detailed_message = false; + + // flush buffers before writing directly to descriptors + fflush( stdout ); + fflush( stderr ); + + // safe actions first, stack and memory may be corrupted + len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s-%s, %s-%s)\n", + Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch() ); + +#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD && !XASH_APPLE + len += 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 ); +#else + len += 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 ); +#endif + + write( STDERR_FILENO, message, len ); + + // now get log fd and write trace directly to log + logfd = Sys_LogFileNo(); + write( logfd, message, len ); + +#if HAVE_LIBBACKTRACE + if( have_libbacktrace && !detailed_message ) + { + len = Sys_CrashDetailsLibbacktrace( logfd, message, len, sizeof( message )); + detailed_message = true; + } +#endif // HAVE_LIBBACKTRACE + +#if HAVE_EXECINFO + if( !detailed_message ) + { + len = Sys_CrashDetailsExecinfo( logfd, message, len, sizeof( message )); + detailed_message = true; + } +#endif // HAVE_EXECINFO + + // put MessageBox as Sys_Error + Msg( "%s\n", message ); +#if !XASH_DEDICATED + IN_SetMouseGrab( false ); +#endif + host.status = HOST_CRASHED; + Platform_MessageBox( "Xash Error", message, false ); + + // log saved, now we can try to save configs and close log correctly, it may crash + if( host.type == HOST_NORMAL ) + CL_Crashed(); + + Sys_Quit( "crashed" ); +} + +void Sys_SetupCrashHandler( const char *argv0 ) +{ + struct sigaction act = + { + .sa_sigaction = Sys_Crash, + .sa_flags = SA_SIGINFO | SA_ONSTACK, + }; + +#if HAVE_LIBBACKTRACE + have_libbacktrace = Sys_SetupLibbacktrace( argv0 ); +#endif // HAVE_LIBBACKTRACE + + sigaction( SIGSEGV, &act, &oldFilter ); + sigaction( SIGABRT, &act, &oldFilter ); + sigaction( SIGBUS, &act, &oldFilter ); + sigaction( SIGILL, &act, &oldFilter ); + +} + +void Sys_RestoreCrashHandler( void ) +{ + sigaction( SIGSEGV, &oldFilter, NULL ); + sigaction( SIGABRT, &oldFilter, NULL ); + sigaction( SIGBUS, &oldFilter, NULL ); + sigaction( SIGILL, &oldFilter, NULL ); +} + +#endif // XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index abfd46ad..23d1c3b1 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -12,16 +12,19 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include "platform/platform.h" #if XASH_LIB == LIB_POSIX -#include +#ifdef XASH_IRIX +#include "platform/irix/dladdr.h" +#endif #include "common.h" #include "library.h" #include "filesystem.h" #include "server.h" #include "platform/android/lib_android.h" -#include "platform/emscripten/lib_em.h" #include "platform/apple/lib_ios.h" #ifdef XASH_DLL_LOADER // wine-based dll loader @@ -41,19 +44,19 @@ const char * Loader_GetFuncName_int( void *wm , void *func); void *dlsym(void *handle, const char *symbol ) { - Con_DPrintf( "dlsym( %p, \"%s\" ): stub\n", handle, symbol ); + Con_DPrintf( "%s( %p, \"%s\" ): stub\n", __func__, handle, symbol ); return NULL; } void *dlopen(const char *name, int flag ) { - Con_DPrintf( "dlopen( \"%s\", %d ): stub\n", name, flag ); + Con_DPrintf( "%s( \"%s\", %d ): stub\n", __func__, name, flag ); return NULL; } int dlclose(void *handle) { - Con_DPrintf( "dlsym( %p ): stub\n", handle ); + Con_DPrintf( "%s( %p ): stub\n", __func__, handle ); return 0; } @@ -78,6 +81,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst = NULL; void *pHandle = NULL; + char buf[MAX_VA_STRING]; COM_ResetLibraryError(); @@ -106,7 +110,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( pHandle ) return pHandle; - COM_PushLibraryError( va( "Failed to find library %s", dllname )); + Q_snprintf( buf, sizeof( buf ), "Failed to find library %s", dllname ); + COM_PushLibraryError( buf ); COM_PushLibraryError( dlerror() ); return NULL; } @@ -114,7 +119,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( hInst->custom_loader ) { - COM_PushLibraryError( va( "Custom library loader is not available. Extract library %s and fix gameinfo.txt!", hInst->fullPath )); + Q_snprintf( buf, sizeof( buf ), "Custom library loader is not available. Extract library %s and fix gameinfo.txt!", hInst->fullPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } @@ -124,14 +130,16 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( hInst->encrypted ) { - COM_PushLibraryError( va( "Library %s is encrypted. Cannot load", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Library %s is encrypted. Cannot load", hInst->shortPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } if( !( hInst->hInstance = Loader_LoadLibrary( hInst->fullPath ) ) ) { - COM_PushLibraryError( va( "Failed to load DLL with DLL loader: %s", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to load DLL with DLL loader: %s", hInst->shortPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } @@ -191,24 +199,6 @@ void *COM_FunctionFromName( void *hInstance, const char *pName ) return COM_GetProcAddress( hInstance, pName ); } -#ifdef XASH_DYNAMIC_DLADDR -static int d_dladdr( void *sym, Dl_info *info ) -{ - static int (*dladdr_real) ( void *sym, Dl_info *info ); - - if( !dladdr_real ) - dladdr_real = dlsym( (void*)(size_t)(-1), "dladdr" ); - - memset( info, 0, sizeof( *info ) ); - - if( !dladdr_real ) - return -1; - - return dladdr_real( sym, info ); -} -#define dladdr d_dladdr -#endif - const char *COM_NameForFunction( void *hInstance, void *function ) { #ifdef XASH_DLL_LOADER @@ -221,8 +211,8 @@ const char *COM_NameForFunction( void *hInstance, void *function ) // NOTE: dladdr() is a glibc extension { Dl_info info = {0}; - dladdr( (void*)function, &info ); - if( info.dli_sname ) + int ret = dladdr( (void*)function, &info ); + if( ret && info.dli_sname ) return COM_GetPlatformNeutralName( info.dli_sname ); } #ifdef XASH_ALLOW_SAVERESTORE_OFFSETS diff --git a/engine/platform/posix/net.h b/engine/platform/posix/net.h new file mode 100644 index 00000000..58532927 --- /dev/null +++ b/engine/platform/posix/net.h @@ -0,0 +1,95 @@ +/* +net.h - WinSock to BSD sockets wrap +Copyright (C) 2022 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef NET_H +#define NET_H + +#include +#include +#if !XASH_PSVITA +#include +#endif +#if XASH_SUNOS // TODO: figure out if we need this header on other systems +#include +#endif +#include +#include +#include +#include +#include +#include +#if XASH_IRIX +#include +#endif + +#define WSAGetLastError() errno +#define WSAEINTR EINTR +#define WSAEBADF EBADF +#define WSAEACCES EACCES +#define WSAEFAULT EFAULT +#define WSAEINVAL EINVAL +#define WSAEMFILE EMFILE +#define WSAEWOULDBLOCK EWOULDBLOCK +#define WSAEINPROGRESS EINPROGRESS +#define WSAEALREADY EALREADY +#define WSAENOTSOCK ENOTSOCK +#define WSAEDESTADDRREQ EDESTADDRREQ +#define WSAEMSGSIZE EMSGSIZE +#define WSAEPROTOTYPE EPROTOTYPE +#define WSAENOPROTOOPT ENOPROTOOPT +#define WSAEPROTONOSUPPORT EPROTONOSUPPORT +#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT +#define WSAEOPNOTSUPP EOPNOTSUPP +#define WSAEPFNOSUPPORT EPFNOSUPPORT +#define WSAEAFNOSUPPORT EAFNOSUPPORT +#define WSAEADDRINUSE EADDRINUSE +#define WSAEADDRNOTAVAIL EADDRNOTAVAIL +#define WSAENETDOWN ENETDOWN +#define WSAENETUNREACH ENETUNREACH +#define WSAENETRESET ENETRESET +#define WSAECONNABORTED ECONNABORTED +#define WSAECONNRESET ECONNRESET +#define WSAENOBUFS ENOBUFS +#define WSAEISCONN EISCONN +#define WSAENOTCONN ENOTCONN +#define WSAESHUTDOWN ESHUTDOWN +#define WSAETOOMANYREFS ETOOMANYREFS +#define WSAETIMEDOUT ETIMEDOUT +#define WSAECONNREFUSED ECONNREFUSED +#define WSAELOOP ELOOP +#define WSAENAMETOOLONG ENAMETOOLONG +#define WSAEHOSTDOWN EHOSTDOWN + + +#ifndef XASH_DOS4GW +#define HAVE_GETADDRINFO +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 + +#if XASH_EMSCRIPTEN +/* All socket operations are non-blocking already */ +static int ioctl_stub( int d, unsigned long r, ... ) +{ + return 0; +} +#define ioctlsocket ioctl_stub +#elif !XASH_PSVITA // XASH_EMSCRIPTEN +#define ioctlsocket ioctl +#endif // XASH_EMSCRIPTEN +#define closesocket close +#endif +#define SOCKET int +typedef int WSAsize_t; + +#endif // NET_H diff --git a/engine/platform/posix/sys_posix.c b/engine/platform/posix/sys_posix.c index 328d11db..cc8ec653 100644 --- a/engine/platform/posix/sys_posix.c +++ b/engine/platform/posix/sys_posix.c @@ -18,6 +18,7 @@ GNU General Public License for more details. #include #include #include +#include #include "platform/platform.h" #include "menu_int.h" @@ -50,11 +51,12 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size needTrailingSlash = ( envPath[length - 1] == '/' ) ? 0 : 1; if( length + baseNameLength + needTrailingSlash < size ) { - Q_strncpy( buf, envPath, length + 1 ); - if( needTrailingSlash ) - Q_strcpy( buf + length, "/" ); - Q_strcpy( buf + length + needTrailingSlash, baseName ); - buf[length + needTrailingSlash + baseNameLength] = '\0'; + string temp; + + Q_strncpy( temp, envPath, length + 1 ); + Q_snprintf( buf, size, "%s%s%s", + temp, needTrailingSlash ? "/" : "", baseName ); + if( access( buf, X_OK ) == 0 ) return true; } @@ -67,7 +69,7 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size return false; } -#if !XASH_ANDROID +#if !XASH_ANDROID && !XASH_NSWITCH && !XASH_PSVITA void Platform_ShellExecute( const char *path, const char *parms ) { char xdgOpen[128]; @@ -95,8 +97,7 @@ void Platform_ShellExecute( const char *path, const char *parms ) void Posix_Daemonize( void ) { - // to be accessed later - if( ( host.daemonized = Sys_CheckParm( "-daemonize" ) ) ) + if( Sys_CheckParm( "-daemonize" )) { #if XASH_POSIX && defined(_POSIX_VERSION) && !defined(XASH_MOBILE_PLATFORM) pid_t daemon; @@ -145,22 +146,32 @@ void Posix_Daemonize( void ) } -#if !XASH_SDL && !XASH_ANDROID - -void Platform_Init( void ) +static void Posix_SigtermCallback( int signal ) { - Posix_Daemonize(); + string reason; + Q_snprintf( reason, sizeof( reason ), "caught signal %d", signal ); + Sys_Quit( reason ); } -void Platform_Shutdown( void ) {} + +void Posix_SetupSigtermHandling( void ) +{ +#if !XASH_PSVITA + struct sigaction act = { 0 }; + act.sa_handler = Posix_SigtermCallback; + act.sa_flags = 0; + sigaction( SIGTERM, &act, NULL ); #endif +} #if XASH_TIMER == TIMER_POSIX double Platform_DoubleTime( void ) { struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - +#if XASH_IRIX + clock_gettime( CLOCK_SGI_CYCLE, &ts ); +#else + clock_gettime( CLOCK_MONOTONIC, &ts ); +#endif return (double) ts.tv_sec + (double) ts.tv_nsec/1000000000.0; } @@ -169,3 +180,4 @@ void Platform_Sleep( int msec ) usleep( msec * 1000 ); } #endif // XASH_TIMER == TIMER_POSIX + diff --git a/engine/platform/psvita/in_psvita.c b/engine/platform/psvita/in_psvita.c new file mode 100644 index 00000000..3d14ade2 --- /dev/null +++ b/engine/platform/psvita/in_psvita.c @@ -0,0 +1,115 @@ +/* +in_psvita.h - psvita-specific input code +Copyright (C) 2021-2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +/* +the SDL fork that we use sometimes fails to call sceImeUpdate, +which leads to input being consumed forever without ever showing the keyboard, +so we just reimplement the whole thing here using SceCommonDialog +*/ + +#include "platform/platform.h" +#include +#include + +extern int SDL_SendKeyboardText( const char *text ); + +static qboolean ime_enabled; +static SceWChar16 ime_string[SCE_IME_MAX_TEXT_LENGTH + 1]; + +/* this is dumb but will probably work fine enough */ +static inline void utf2ascii( char *dst, const SceWChar16 *src, unsigned dstsize ) +{ + if ( !src || !dst || !dstsize ) + return; + while ( *src && ( dstsize-- > 0 ) ) + *(dst++) = (*(src++)) & 0xFF; + *dst = 0x00; +} + +static void IME_Open( void ) +{ + SceInt32 res; + SceImeDialogParam param; + + memset( ime_string, 0, sizeof( ime_string ) ); + + sceImeDialogParamInit( ¶m ); + param.supportedLanguages = SCE_IME_LANGUAGE_ENGLISH; + param.languagesForced = SCE_TRUE; + param.type = SCE_IME_TYPE_BASIC_LATIN; + param.title = u"Input text"; + param.maxTextLength = SCE_IME_MAX_TEXT_LENGTH; + param.initialText = (SceWChar16 *)u""; + param.inputTextBuffer = ime_string; + + res = sceImeDialogInit( ¶m ); + if ( res < 0 ) + { + Con_Reportf( S_WARN "Could not open IME keyboard: %d\n", res ); + } + else + { + ime_enabled = true; + } +} + +static void IME_Close( void ) +{ + if ( ime_enabled ) + { + ime_enabled = false; + sceImeDialogTerm( ); + } +} + +static void IME_Update( void ) +{ + char ascii_string[SCE_IME_MAX_TEXT_LENGTH + 2] = { 0 }; + SceImeDialogResult result; + SceCommonDialogStatus status = sceImeDialogGetStatus( ); + if( status == 2 ) + { + memset( &result, 0, sizeof( SceImeDialogResult ) ); + sceImeDialogGetResult( &result ); + if( result.button == SCE_IME_DIALOG_BUTTON_ENTER ) + { + utf2ascii( ascii_string, ime_string, SCE_IME_MAX_TEXT_LENGTH ); + SDL_SendKeyboardText( ascii_string ); + } + IME_Close(); + } +} + +void Platform_EnableTextInput( qboolean enable ) +{ + if ( enable ) + { + if ( ime_enabled ) + IME_Close(); + IME_Open(); + SDL_EventState( SDL_TEXTINPUT, SDL_ENABLE ); + } + else + { + SDL_EventState( SDL_TEXTINPUT, SDL_DISABLE ); + IME_Close(); + } +} + +void PSVita_InputUpdate( void ) +{ + if ( ime_enabled ) + IME_Update(); +} diff --git a/engine/platform/psvita/net_psvita.h b/engine/platform/psvita/net_psvita.h new file mode 100644 index 00000000..5684a6ae --- /dev/null +++ b/engine/platform/psvita/net_psvita.h @@ -0,0 +1,59 @@ +/* +net_psvita.h - psvita network stubs +Copyright (C) 2021-2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once +#ifndef NET_PSVITA_H +#define NET_PSVITA_H + +#include +#include "platform/posix/net.h" + +/* we're missing IPv6 support; define some trash */ +#define XASH_NO_IPV6_RESOLVE 1 + +#ifndef IN6_IS_ADDR_V4MAPPED +#define IN6_IS_ADDR_V4MAPPED( p ) ( 0 ) +#endif + +#ifndef IPPROTO_IPV6 +#define IPPROTO_IPV6 41 +#endif + +#ifndef IPV6_MULTICAST_LOOP +#define IPV6_MULTICAST_LOOP 19 +#endif + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 26 +#endif + +#ifndef FIONBIO +#define FIONBIO SO_NONBLOCK +#endif + +/* ioctlsocket() is only used to set non-blocking on sockets */ + +static inline int ioctl_psvita( int fd, int req, unsigned int *arg ) +{ + if ( req == FIONBIO ) + { + return setsockopt( fd, SOL_SOCKET, SO_NONBLOCK, arg, sizeof( *arg ) ); + } + return -ENOSYS; +} + +#define ioctlsocket ioctl_psvita + +#endif // NET_PSVITA_H diff --git a/engine/platform/psvita/sce_sys/icon0.png b/engine/platform/psvita/sce_sys/icon0.png new file mode 100644 index 00000000..5203ef32 Binary files /dev/null and b/engine/platform/psvita/sce_sys/icon0.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/bg.png b/engine/platform/psvita/sce_sys/livearea/contents/bg.png new file mode 100644 index 00000000..f1a02015 Binary files /dev/null and b/engine/platform/psvita/sce_sys/livearea/contents/bg.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/startup.png b/engine/platform/psvita/sce_sys/livearea/contents/startup.png new file mode 100644 index 00000000..c53af6f8 Binary files /dev/null and b/engine/platform/psvita/sce_sys/livearea/contents/startup.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/template.xml b/engine/platform/psvita/sce_sys/livearea/contents/template.xml new file mode 100644 index 00000000..16ef0ae9 --- /dev/null +++ b/engine/platform/psvita/sce_sys/livearea/contents/template.xml @@ -0,0 +1,20 @@ + + + + + bg.png + + + + startup.png + + + + + psla:dev + + Developer mode + + + + diff --git a/engine/platform/psvita/sys_psvita.c b/engine/platform/psvita/sys_psvita.c new file mode 100644 index 00000000..7bd0b685 --- /dev/null +++ b/engine/platform/psvita/sys_psvita.c @@ -0,0 +1,221 @@ +/* +sys_psvita.c - psvita backend +Copyright (C) 2021-2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" +#include "xash3d_mathlib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DATA_PATH "data/xash3d" +#define MAX_ARGV 5 // "" -log -dev X NULL + +// 200MB libc heap, 512K main thread stack, 40MB for loading game DLLs +// the rest goes to vitaGL +SceUInt32 sceUserMainThreadStackSize = 512 * 1024; +unsigned int _pthread_stack_default_user = 512 * 1024; +unsigned int _newlib_heap_size_user = 200 * 1024 * 1024; +#define VGL_MEM_THRESHOLD ( 40 * 1024 * 1024 ) + +// HACKHACK: create some slack at the end of the RX segment of the ELF +// for vita-elf-create to put the generated symbol table into +const char vitaelf_slack __attribute__ ((aligned (0x20000))) = 0xFF; + +/* HACKHACK: force-export stuff required by the dynamic libs */ + +extern void *__aeabi_idiv; +extern void *__aeabi_uidiv; +extern void *__aeabi_idivmod; +extern void *__aeabi_uidivmod; +extern void *__aeabi_d2ulz; +extern void *__aeabi_ul2d; +extern void *__aeabi_ul2f; + +static const vrtld_export_t aux_exports[] = +{ + VRTLD_EXPORT_SYMBOL( __aeabi_d2ulz ), + VRTLD_EXPORT_SYMBOL( __aeabi_idiv ), + VRTLD_EXPORT_SYMBOL( __aeabi_idivmod ), + VRTLD_EXPORT_SYMBOL( __aeabi_uidivmod ), + VRTLD_EXPORT_SYMBOL( __aeabi_uidiv ), + VRTLD_EXPORT_SYMBOL( __aeabi_ul2d ), + VRTLD_EXPORT_SYMBOL( __aeabi_ul2f ), + VRTLD_EXPORT_SYMBOL( _impure_ptr ), + VRTLD_EXPORT_SYMBOL( ctime ), + VRTLD_EXPORT_SYMBOL( vasprintf ), + VRTLD_EXPORT_SYMBOL( vsprintf ), + VRTLD_EXPORT_SYMBOL( vprintf ), + VRTLD_EXPORT_SYMBOL( printf ), + VRTLD_EXPORT_SYMBOL( putchar ), + VRTLD_EXPORT_SYMBOL( puts ), + VRTLD_EXPORT_SYMBOL( tolower ), + VRTLD_EXPORT_SYMBOL( toupper ), + VRTLD_EXPORT_SYMBOL( isalnum ), + VRTLD_EXPORT_SYMBOL( isalpha ), + VRTLD_EXPORT_SYMBOL( strchrnul ), + VRTLD_EXPORT_SYMBOL( strtok ), + VRTLD_EXPORT_SYMBOL( stpcpy ), + VRTLD_EXPORT_SYMBOL( rand ), + VRTLD_EXPORT_SYMBOL( srand ), + VRTLD_EXPORT_SYMBOL( rintf ), + VRTLD_EXPORT_SYMBOL( sceGxmMapMemory ), // needed by vgl_shim + VRTLD_EXPORT( "dlopen", vrtld_dlopen ), + VRTLD_EXPORT( "dlclose", vrtld_dlclose ), + VRTLD_EXPORT( "dlsym", vrtld_dlsym ), +}; + +const vrtld_export_t *__vrtld_exports = aux_exports; +const size_t __vrtld_num_exports = sizeof( aux_exports ) / sizeof( *aux_exports ); + +/* end of export crap */ + +static const char *PSVita_GetLaunchParameter( char *outbuf ) +{ + SceAppUtilAppEventParam param; + memset( ¶m, 0, sizeof( param ) ); + sceAppUtilReceiveAppEvent( ¶m ); + if( param.type == 0x05 ) + { + sceAppUtilAppEventParseLiveArea( ¶m, outbuf ); + return outbuf; + } + return NULL; +} + +void Platform_ShellExecute( const char *path, const char *parms ) +{ + Con_Reportf( S_WARN "Tried to shell execute ;%s; -- not supported\n", path ); +} + +/* +=========== +PSVita_GetArgv + +On the PS Vita under normal circumstances argv is empty, so we'll construct our own +based on which button the user pressed in the LiveArea launcher. +=========== +*/ +int PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv ) +{ + static const char *fake_argv[MAX_ARGV] = { "app0:/eboot.bin", NULL }; + int fake_argc = 1; + char tmp[2048] = { 0 }; + SceAppUtilInitParam initParam = { 0 }; + SceAppUtilBootParam bootParam = { 0 }; + + // on the Vita under normal circumstances argv is empty, unless we're launching from Change Game + sceAppUtilInit( &initParam, &bootParam ); + + if( in_argc > 1 ) + { + // probably coming from Change Game, in which case we just need to keep the old args + *out_argv = in_argv; + return in_argc; + } + + // got empty args, which means that we're probably coming from LiveArea + // construct argv based on which button the user pressed in the LiveArea launcher + if( PSVita_GetLaunchParameter( tmp )) + { + if( !Q_strcmp( tmp, "dev" )) + { + // user hit the "Developer Mode" button, inject "-log" and "-dev" arguments + fake_argv[fake_argc++] = "-log"; + fake_argv[fake_argc++] = "-dev"; + fake_argv[fake_argc++] = "2"; + } + } + + *out_argv = (char **)fake_argv; + return fake_argc; +} + +void PSVita_Init( void ) +{ + char xashdir[1024] = { 0 }; + + // cd to the base dir immediately for library loading to work + if( PSVita_GetBasePath( xashdir, sizeof( xashdir ))) + { + chdir( xashdir ); + } + + sceTouchSetSamplingState( SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_STOP ); + scePowerSetArmClockFrequency( 444 ); + scePowerSetBusClockFrequency( 222 ); + scePowerSetGpuClockFrequency( 222 ); + scePowerSetGpuXbarClockFrequency( 166 ); + sceSysmoduleLoadModule( SCE_SYSMODULE_NET ); + + if( vrtld_init( 0 ) < 0 ) + { + Sys_Error( "Could not init vrtld:\n%s\n", vrtld_dlerror() ); + } + + // init vitaGL, leaving some memory for DLL mapping + // TODO: we don't need to do this for ref_soft + vglUseVram( GL_TRUE ); + vglUseExtraMem( GL_TRUE ); + vglInitExtended( 0, 960, 544, VGL_MEM_THRESHOLD, 0 ); +} + +void PSVita_Shutdown( void ) +{ + vrtld_quit( ); +} + +qboolean PSVita_GetBasePath( char *buf, const size_t buflen ) +{ + // check if a xash3d folder exists on one of these drives + // default to the last one (ux0) + static const char *drives[] = { "uma0", "imc0", "ux0" }; + SceUID dir; + size_t i; + + for ( i = 0; i < sizeof( drives ) / sizeof( *drives ); ++i ) + { + Q_snprintf( buf, buflen, "%s:" DATA_PATH, drives[i] ); + dir = sceIoDopen( buf ); + if ( dir >= 0 ) + { + sceIoDclose( dir ); + return true; + } + } + + return false; +} + +int PSVita_GetPSID( char *buf, const size_t buflen ) +{ + SceKernelOpenPsId id; + const int datasize = Q_min( buflen, sizeof( id )); + + if( sceKernelGetOpenPsId( &id ) < 0 ) + { + return 0; + } + else + { + memcpy( buf, &id.id[0], datasize ); + return datasize; + } +} diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c deleted file mode 100644 index 86d5cde0..00000000 --- a/engine/platform/sdl/events.c +++ /dev/null @@ -1,689 +0,0 @@ -/* -events.c - SDL event system handlers -Copyright (C) 2015-2017 a1batross - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ -#if defined( XASH_SDL ) && !XASH_DEDICATED -#include -#include - -#include "common.h" -#include "keydefs.h" -#include "input.h" -#include "client.h" -#include "vgui_draw.h" -#include "events.h" -#include "sound.h" -#include "vid_common.h" - -#if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) -#define SDL_SCANCODE_A SDLK_a -#define SDL_SCANCODE_Z SDLK_z -#define SDL_SCANCODE_1 SDLK_1 -#define SDL_SCANCODE_9 SDLK_9 -#define SDL_SCANCODE_F1 SDLK_F1 -#define SDL_SCANCODE_F12 SDLK_F12 -#define SDL_SCANCODE_GRAVE SDLK_BACKQUOTE -#define SDL_SCANCODE_0 SDLK_0 -#define SDL_SCANCODE_BACKSLASH SDLK_BACKSLASH -#define SDL_SCANCODE_LEFTBRACKET SDLK_LEFTBRACKET -#define SDL_SCANCODE_RIGHTBRACKET SDLK_RIGHTBRACKET -#define SDL_SCANCODE_EQUALS SDLK_EQUALS -#define SDL_SCANCODE_MINUS SDLK_MINUS -#define SDL_SCANCODE_TAB SDLK_TAB -#define SDL_SCANCODE_RETURN SDLK_RETURN -#define SDL_SCANCODE_ESCAPE SDLK_ESCAPE -#define SDL_SCANCODE_SPACE SDLK_SPACE -#define SDL_SCANCODE_BACKSPACE SDLK_BACKSPACE -#define SDL_SCANCODE_UP SDLK_UP -#define SDL_SCANCODE_LEFT SDLK_LEFT -#define SDL_SCANCODE_DOWN SDLK_DOWN -#define SDL_SCANCODE_RIGHT SDLK_RIGHT -#define SDL_SCANCODE_LALT SDLK_LALT -#define SDL_SCANCODE_RALT SDLK_RALT -#define SDL_SCANCODE_LCTRL SDLK_LCTRL -#define SDL_SCANCODE_RCTRL SDLK_RCTRL -#define SDL_SCANCODE_LSHIFT SDLK_LSHIFT -#define SDL_SCANCODE_RSHIFT SDLK_RSHIFT -#define SDL_SCANCODE_LGUI SDLK_LMETA -#define SDL_SCANCODE_RGUI SDLK_RMETA -#define SDL_SCANCODE_INSERT SDLK_INSERT -#define SDL_SCANCODE_DELETE SDLK_DELETE -#define SDL_SCANCODE_PAGEDOWN SDLK_PAGEDOWN -#define SDL_SCANCODE_PAGEUP SDLK_PAGEUP -#define SDL_SCANCODE_HOME SDLK_HOME -#define SDL_SCANCODE_END SDLK_END -#define SDL_SCANCODE_KP_1 SDLK_KP1 -#define SDL_SCANCODE_KP_2 SDLK_KP2 -#define SDL_SCANCODE_KP_3 SDLK_KP3 -#define SDL_SCANCODE_KP_4 SDLK_KP4 -#define SDL_SCANCODE_KP_5 SDLK_KP5 -#define SDL_SCANCODE_KP_6 SDLK_KP6 -#define SDL_SCANCODE_KP_7 SDLK_KP7 -#define SDL_SCANCODE_KP_8 SDLK_KP8 -#define SDL_SCANCODE_KP_9 SDLK_KP9 -#define SDL_SCANCODE_KP_0 SDLK_KP0 -#define SDL_SCANCODE_KP_PERIOD SDLK_KP_PERIOD -#define SDL_SCANCODE_KP_ENTER SDLK_KP_ENTER -#define SDL_SCANCODE_KP_PLUS SDLK_KP_PLUS -#define SDL_SCANCODE_KP_MINUS SDLK_KP_MINUS -#define SDL_SCANCODE_KP_DIVIDE SDLK_KP_DIVIDE -#define SDL_SCANCODE_KP_MULTIPLY SDLK_KP_MULTIPLY -#define SDL_SCANCODE_NUMLOCKCLEAR SDLK_NUMLOCK -#define SDL_SCANCODE_CAPSLOCK SDLK_CAPSLOCK -#define SDL_SCANCODE_SLASH SDLK_SLASH -#define SDL_SCANCODE_PERIOD SDLK_PERIOD -#define SDL_SCANCODE_SEMICOLON SDLK_SEMICOLON -#define SDL_SCANCODE_APOSTROPHE SDLK_QUOTE -#define SDL_SCANCODE_COMMA SDLK_COMMA -#define SDL_SCANCODE_PRINTSCREEN SDLK_PRINT -#define SDL_SCANCODE_UNKNOWN SDLK_UNKNOWN -#define SDL_GetScancodeName( x ) "unknown" -#define SDL_JoystickID Uint8 -#endif - -static qboolean SDLash_IsInstanceIDAGameController( SDL_JoystickID joyId ) -{ -#if !SDL_VERSION_ATLEAST( 2, 0, 4 ) - // HACKHACK: if we're not initialized g_joy, then we're probably using gamecontroller api - // so return true - if( !g_joy ) - return true; - return false; -#else - if( SDL_GameControllerFromInstanceID( joyId ) ) - return true; - return false; -#endif -} - -/* -============= -SDLash_KeyEvent - -============= -*/ -static void SDLash_KeyEvent( SDL_KeyboardEvent key ) -{ - int down = key.state != SDL_RELEASED; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - int keynum = key.keysym.scancode; -#else - int keynum = key.keysym.sym; -#endif - qboolean numLock = SDL_GetModState() & KMOD_NUM; - - if( SDL_IsTextInputActive() && down && cls.key_dest != key_game ) - { - if( SDL_GetModState() & KMOD_CTRL ) - { - if( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z ) - { - keynum = keynum - SDL_SCANCODE_A + 1; - CL_CharEvent( keynum ); - } - - return; - } - -#if !SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( keynum >= SDLK_KP0 && keynum <= SDLK_KP9 ) - keynum -= SDLK_KP0 + '0'; - - if( isprint( keynum ) ) - { - if( SDL_GetModState() & KMOD_SHIFT ) - { - keynum = Key_ToUpper( keynum ); - } - - CL_CharEvent( keynum ); - return; - } -#endif - } - -#define DECLARE_KEY_RANGE( min, max, repl ) \ - if( keynum >= (min) && keynum <= (max) ) \ - { \ - keynum = keynum - (min) + (repl); \ - } - - DECLARE_KEY_RANGE( SDL_SCANCODE_A, SDL_SCANCODE_Z, 'a' ) - else DECLARE_KEY_RANGE( SDL_SCANCODE_1, SDL_SCANCODE_9, '1' ) - else DECLARE_KEY_RANGE( SDL_SCANCODE_F1, SDL_SCANCODE_F12, K_F1 ) - else - { - switch( keynum ) - { - case SDL_SCANCODE_GRAVE: keynum = '`'; break; - case SDL_SCANCODE_0: keynum = '0'; break; - case SDL_SCANCODE_BACKSLASH: keynum = '\\'; break; - case SDL_SCANCODE_LEFTBRACKET: keynum = '['; break; - case SDL_SCANCODE_RIGHTBRACKET: keynum = ']'; break; - case SDL_SCANCODE_EQUALS: keynum = '='; break; - case SDL_SCANCODE_MINUS: keynum = '-'; break; - case SDL_SCANCODE_TAB: keynum = K_TAB; break; - case SDL_SCANCODE_RETURN: keynum = K_ENTER; break; - case SDL_SCANCODE_ESCAPE: keynum = K_ESCAPE; break; - case SDL_SCANCODE_SPACE: keynum = K_SPACE; break; - case SDL_SCANCODE_BACKSPACE: keynum = K_BACKSPACE; break; - case SDL_SCANCODE_UP: keynum = K_UPARROW; break; - case SDL_SCANCODE_LEFT: keynum = K_LEFTARROW; break; - case SDL_SCANCODE_DOWN: keynum = K_DOWNARROW; break; - case SDL_SCANCODE_RIGHT: keynum = K_RIGHTARROW; break; - case SDL_SCANCODE_LALT: - case SDL_SCANCODE_RALT: keynum = K_ALT; break; - case SDL_SCANCODE_LCTRL: - case SDL_SCANCODE_RCTRL: keynum = K_CTRL; break; - case SDL_SCANCODE_LSHIFT: - case SDL_SCANCODE_RSHIFT: keynum = K_SHIFT; break; - case SDL_SCANCODE_LGUI: - case SDL_SCANCODE_RGUI: keynum = K_WIN; break; - case SDL_SCANCODE_INSERT: keynum = K_INS; break; - case SDL_SCANCODE_DELETE: keynum = K_DEL; break; - case SDL_SCANCODE_PAGEDOWN: keynum = K_PGDN; break; - case SDL_SCANCODE_PAGEUP: keynum = K_PGUP; break; - case SDL_SCANCODE_HOME: keynum = K_HOME; break; - case SDL_SCANCODE_END: keynum = K_END; break; - case SDL_SCANCODE_KP_1: keynum = numLock ? '1' : K_KP_END; break; - case SDL_SCANCODE_KP_2: keynum = numLock ? '2' : K_KP_DOWNARROW; break; - case SDL_SCANCODE_KP_3: keynum = numLock ? '3' : K_KP_PGDN; break; - case SDL_SCANCODE_KP_4: keynum = numLock ? '4' : K_KP_LEFTARROW; break; - case SDL_SCANCODE_KP_5: keynum = numLock ? '5' : K_KP_5; break; - case SDL_SCANCODE_KP_6: keynum = numLock ? '6' : K_KP_RIGHTARROW; break; - case SDL_SCANCODE_KP_7: keynum = numLock ? '7' : K_KP_HOME; break; - case SDL_SCANCODE_KP_8: keynum = numLock ? '8' : K_KP_UPARROW; break; - case SDL_SCANCODE_KP_9: keynum = numLock ? '9' : K_KP_PGUP; break; - case SDL_SCANCODE_KP_0: keynum = numLock ? '0' : K_KP_INS; break; - case SDL_SCANCODE_KP_PERIOD: keynum = K_KP_DEL; break; - case SDL_SCANCODE_KP_ENTER: keynum = K_KP_ENTER; break; - case SDL_SCANCODE_KP_PLUS: keynum = K_KP_PLUS; break; - case SDL_SCANCODE_KP_MINUS: keynum = K_KP_MINUS; break; - case SDL_SCANCODE_KP_DIVIDE: keynum = K_KP_SLASH; break; - case SDL_SCANCODE_KP_MULTIPLY: keynum = '*'; break; - case SDL_SCANCODE_NUMLOCKCLEAR: keynum = K_KP_NUMLOCK; break; - case SDL_SCANCODE_CAPSLOCK: keynum = K_CAPSLOCK; break; - case SDL_SCANCODE_SLASH: keynum = '/'; break; - case SDL_SCANCODE_PERIOD: keynum = '.'; break; - case SDL_SCANCODE_SEMICOLON: keynum = ';'; break; - case SDL_SCANCODE_APOSTROPHE: keynum = '\''; break; - case SDL_SCANCODE_COMMA: keynum = ','; break; - case SDL_SCANCODE_PRINTSCREEN: - { - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - break; - } -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - case SDL_SCANCODE_APPLICATION: keynum = K_WIN; break; // (compose key) ??? - // don't console spam on known functional buttons, but not used in engine - case SDL_SCANCODE_MUTE: - case SDL_SCANCODE_VOLUMEUP: - case SDL_SCANCODE_VOLUMEDOWN: - case SDL_SCANCODE_BRIGHTNESSDOWN: - case SDL_SCANCODE_BRIGHTNESSUP: - return; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - case SDL_SCANCODE_UNKNOWN: - { - if( down ) Con_Reportf( "SDLash_KeyEvent: Unknown scancode\n" ); - return; - } - default: - if( down ) Con_Reportf( "SDLash_KeyEvent: Unknown key: %s = %i\n", SDL_GetScancodeName( keynum ), keynum ); - return; - } - } - -#undef DECLARE_KEY_RANGE - - Key_Event( keynum, down ); -} - -static void SDLash_MouseKey( int key, int down, int istouch ) -{ - if( CVAR_TO_BOOL( touch_emulate ) ) - { - Touch_KeyEvent( key, down ); - } - else if( in_mouseinitialized && !m_ignore->value && !istouch ) - { - Key_Event( key, down ); - } -} - -/* -============= -SDLash_MouseEvent - -============= -*/ -static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) -{ - int down = button.state != SDL_RELEASED; - uint mstate = 0; - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( button.which == SDL_TOUCH_MOUSEID ) - return; -#endif - - switch( button.button ) - { - case SDL_BUTTON_LEFT: - IN_MouseEvent( 0, down ); - break; - case SDL_BUTTON_RIGHT: - IN_MouseEvent( 1, down ); - break; - case SDL_BUTTON_MIDDLE: - IN_MouseEvent( 2, down ); - break; - case SDL_BUTTON_X1: - IN_MouseEvent( 3, down ); - break; - case SDL_BUTTON_X2: - IN_MouseEvent( 4, down ); - break; -#if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) - case SDL_BUTTON_WHEELUP: - Key_Event( K_MWHEELUP, down ); - break; - case SDL_BUTTON_WHEELDOWN: - Key_Event( K_MWHEELDOWN, down ); - break; -#endif // ! SDL_VERSION_ATLEAST( 2, 0, 0 ) - default: - Con_Printf( "Unknown mouse button ID: %d\n", button.button ); - } -} - -/* -============= -SDLash_InputEvent - -============= -*/ -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) -static void SDLash_InputEvent( SDL_TextInputEvent input ) -{ - char *text; - VGui_ReportTextInput( input.text ); - for( text = input.text; *text; text++ ) - { - int ch; - - if( !Q_stricmp( cl_charset->string, "utf-8" ) ) - ch = (unsigned char)*text; - else - ch = Con_UtfProcessCharForce( (unsigned char)*text ); - - if( !ch ) - continue; - - CL_CharEvent( ch ); - } -} -#endif // SDL_VERSION_AT_LEAST( 2, 0, 0 ) - -static void SDLash_ActiveEvent( int gain ) -{ - if( gain ) - { - host.status = HOST_FRAME; - if( cls.key_dest == key_game ) - { - IN_ActivateMouse( ); - } - - if( dma.initialized && snd_mute_losefocus.value ) - { - SNDDMA_Activate( true ); - } - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - if( vid_fullscreen->value ) - VID_SetMode(); - } - else - { -#if TARGET_OS_IPHONE - { - // Keep running if ftp server enabled - void IOS_StartBackgroundTask( void ); - IOS_StartBackgroundTask(); - } -#endif - host.status = HOST_NOFOCUS; - if( cls.key_dest == key_game ) - { - IN_DeactivateMouse(); - } - - if( dma.initialized && snd_mute_losefocus.value ) - { - SNDDMA_Activate( false ); - } - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + 2; - VID_RestoreScreenResolution(); - } -} - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) -static size_t num_open_game_controllers = 0; - -static void SDLash_GameController_Add( int index ) -{ - extern convar_t *joy_enable; // private to input system - SDL_GameController *controller; - if( !joy_enable->value ) - return; - controller = SDL_GameControllerOpen( index ); - if( !controller ) - { - Con_Reportf( "Failed to open SDL GameController %d: %s\n", index, SDL_GetError( ) ); - SDL_ClearError( ); - return; - } -#if SDL_VERSION_ATLEAST( 2, 0, 6 ) - Con_Reportf( "Added controller: %s (%i:%i:%i)\n", - SDL_GameControllerName( controller ), - SDL_GameControllerGetVendor( controller ), - SDL_GameControllerGetProduct( controller ), - SDL_GameControllerGetProductVersion( controller )); -#endif // SDL_VERSION_ATLEAST( 2, 0, 6 ) - - ++num_open_game_controllers; - if( num_open_game_controllers == 1 ) - Joy_AddEvent( ); -} - - -static void SDLash_GameController_Remove( SDL_JoystickID joystick_id ) -{ - Con_Reportf( "Removed controller %i\n", joystick_id ); - - // `Joy_RemoveEvent` sets `joy_found` to `0`. - // We only want to do this when all the game controllers have been removed. - --num_open_game_controllers; - if( num_open_game_controllers == 0 ) - Joy_RemoveEvent( ); -} -#endif - -/* -============= -SDLash_EventFilter - -============= -*/ -static void SDLash_EventFilter( SDL_Event *event ) -{ - switch ( event->type ) - { - /* Mouse events */ - case SDL_MOUSEMOTION: - if( host.mouse_visible ) - { - SDL_GetRelativeMouseState( NULL, NULL ); - } - break; - - case SDL_MOUSEBUTTONUP: - case SDL_MOUSEBUTTONDOWN: - SDLash_MouseEvent( event->button ); - break; - - /* Keyboard events */ - case SDL_KEYDOWN: - case SDL_KEYUP: - SDLash_KeyEvent( event->key ); - break; - - /* Joystick events */ - case SDL_JOYAXISMOTION: - if ( !SDLash_IsInstanceIDAGameController( event->jaxis.which )) - Joy_AxisMotionEvent( event->jaxis.axis, event->jaxis.value ); - break; - - case SDL_JOYBALLMOTION: - if ( !SDLash_IsInstanceIDAGameController( event->jball.which )) - Joy_BallMotionEvent( event->jball.ball, event->jball.xrel, event->jball.yrel ); - break; - - case SDL_JOYHATMOTION: - if ( !SDLash_IsInstanceIDAGameController( event->jhat.which )) - Joy_HatMotionEvent( event->jhat.hat, event->jhat.value ); - break; - - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: - if ( !SDLash_IsInstanceIDAGameController( event->jbutton.which )) - Joy_ButtonEvent( event->jbutton.button, event->jbutton.state ); - break; - - case SDL_QUIT: - Sys_Quit(); - break; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - case SDL_MOUSEWHEEL: - { - int wheelbutton = event->wheel.y < 0 ? K_MWHEELDOWN : K_MWHEELUP; - Key_Event( wheelbutton, true ); - Key_Event( wheelbutton, false ); - break; - } - - /* Touch events */ - case SDL_FINGERDOWN: - case SDL_FINGERUP: - case SDL_FINGERMOTION: - { - static int scale = 0; - touchEventType type; - float x, y, dx, dy; - - if( event->type == SDL_FINGERDOWN ) - type = event_down; - else if( event->type == SDL_FINGERUP ) - type = event_up ; - else if( event->type == SDL_FINGERMOTION ) - type = event_motion; - else break; - - /* - SDL sends coordinates in [0..width],[0..height] values - on some devices - */ - if( !scale ) - { - if( ( event->tfinger.x > 0 ) && ( event->tfinger.y > 0 ) ) - { - if( ( event->tfinger.x > 2 ) && ( event->tfinger.y > 2 ) ) - { - scale = 2; - Con_Reportf( "SDL reports screen coordinates, workaround enabled!\n"); - } - else - { - scale = 1; - } - } - } - - x = event->tfinger.x; - y = event->tfinger.y; - dx = event->tfinger.dx; - dy = event->tfinger.dy; - - if( scale == 2 ) - { - x /= (float)refState.width; - y /= (float)refState.height; - dx /= (float)refState.width; - dy /= (float)refState.height; - } - - IN_TouchEvent( type, event->tfinger.fingerId, x, y, dx, dy ); - break; - } - - /* IME */ - case SDL_TEXTINPUT: - SDLash_InputEvent( event->text ); - break; - - case SDL_JOYDEVICEADDED: - Joy_AddEvent(); - break; - case SDL_JOYDEVICEREMOVED: - Joy_RemoveEvent(); - break; - - /* GameController API */ - case SDL_CONTROLLERAXISMOTION: - { - // Swap axis to follow default axis binding: - // LeftX, LeftY, RightX, RightY, TriggerRight, TriggerLeft - static int sdlControllerAxisToEngine[] = { - JOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX, - JOY_AXIS_FWD, // SDL_CONTROLLER_AXIS_LEFTY, - JOY_AXIS_PITCH, // SDL_CONTROLLER_AXIS_RIGHTX, - JOY_AXIS_YAW, // SDL_CONTROLLER_AXIS_RIGHTY, - JOY_AXIS_LT, // SDL_CONTROLLER_AXIS_TRIGGERLEFT, - JOY_AXIS_RT, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT, - }; - if( Joy_IsActive() && event->caxis.axis != (Uint8)SDL_CONTROLLER_AXIS_INVALID ) - Joy_KnownAxisMotionEvent( sdlControllerAxisToEngine[event->caxis.axis], event->caxis.value ); - break; - } - - case SDL_CONTROLLERBUTTONDOWN: - case SDL_CONTROLLERBUTTONUP: - { - static int sdlControllerButtonToEngine[] = - { - K_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON, - K_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON, - K_LSTICK, K_RSTICK, - K_L1_BUTTON, K_R1_BUTTON, - K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT - }; - - // TODO: Use joyinput funcs, for future multiple gamepads support - if( Joy_IsActive() && event->cbutton.button != (Uint8)SDL_CONTROLLER_BUTTON_INVALID ) - Key_Event( sdlControllerButtonToEngine[event->cbutton.button], event->cbutton.state ); - break; - } - - case SDL_CONTROLLERDEVICEADDED: - SDLash_GameController_Add( event->cdevice.which ); - break; - - case SDL_CONTROLLERDEVICEREMOVED: - SDLash_GameController_Remove( event->cdevice.which ); - break; - - case SDL_WINDOWEVENT: - if( event->window.windowID != SDL_GetWindowID( host.hWnd ) ) - return; - - if( host.status == HOST_SHUTDOWN || Host_IsDedicated() ) - break; // no need to activate - - switch( event->window.event ) - { - case SDL_WINDOWEVENT_MOVED: - if( !vid_fullscreen->value ) - { - Cvar_SetValue( "_window_xpos", (float)event->window.data1 ); - Cvar_SetValue( "_window_ypos", (float)event->window.data2 ); - } - break; - case SDL_WINDOWEVENT_MINIMIZED: - host.status = HOST_SLEEP; - VID_RestoreScreenResolution( ); - break; - case SDL_WINDOWEVENT_RESTORED: - host.status = HOST_FRAME; - host.force_draw_version = true; - host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; - if( vid_fullscreen->value ) - VID_SetMode(); - break; - case SDL_WINDOWEVENT_FOCUS_GAINED: - SDLash_ActiveEvent( true ); - break; - case SDL_WINDOWEVENT_FOCUS_LOST: - SDLash_ActiveEvent( false ); - break; - case SDL_WINDOWEVENT_RESIZED: - { - if( vid_fullscreen->value ) - break; - - VID_SaveWindowSize( event->window.data1, event->window.data2 ); - break; - } - default: - break; - } -#else - case SDL_VIDEORESIZE: - VID_SaveWindowSize( event->resize.w, event->resize.h ); - break; - case SDL_ACTIVEEVENT: - SDLash_ActiveEvent( event->active.gain ); - break; -#endif - } -} - -/* -============= -SDLash_RunEvents - -============= -*/ -void Platform_RunEvents( void ) -{ - SDL_Event event; - - while( !host.crashed && !host.shutdown_issued && SDL_PollEvent( &event ) ) - SDLash_EventFilter( &event ); -} - -void* Platform_GetNativeObject( const char *name ) -{ - return NULL; // SDL don't have it -} - -/* -======================== -Platform_PreCreateMove - -this should disable mouse look on client when m_ignore enabled -TODO: kill mouse in win32 clients too -======================== -*/ -void Platform_PreCreateMove( void ) -{ - if( CVAR_TO_BOOL( m_ignore ) ) - { - SDL_GetRelativeMouseState( NULL, NULL ); - SDL_ShowCursor( SDL_TRUE ); - } -} - -#endif // defined( XASH_SDL ) && !XASH_DEDICATED diff --git a/engine/platform/sdl1/host_sdl1.c b/engine/platform/sdl1/host_sdl1.c new file mode 100644 index 00000000..91cd6164 --- /dev/null +++ b/engine/platform/sdl1/host_sdl1.c @@ -0,0 +1,354 @@ +/* +host_sdl1.c - SDL event system handlers +Copyright (C) 2015-2025 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include +#include + +#include "common.h" +#include "keydefs.h" +#include "input.h" +#include "client.h" +#include "vgui_draw.h" +#include "platform_sdl1.h" +#include "sound.h" +#include "vid_common.h" + +#define SDL_SCANCODE_A SDLK_a +#define SDL_SCANCODE_Z SDLK_z +#define SDL_SCANCODE_1 SDLK_1 +#define SDL_SCANCODE_9 SDLK_9 +#define SDL_SCANCODE_F1 SDLK_F1 +#define SDL_SCANCODE_F12 SDLK_F12 +#define SDL_SCANCODE_GRAVE SDLK_BACKQUOTE +#define SDL_SCANCODE_0 SDLK_0 +#define SDL_SCANCODE_BACKSLASH SDLK_BACKSLASH +#define SDL_SCANCODE_LEFTBRACKET SDLK_LEFTBRACKET +#define SDL_SCANCODE_RIGHTBRACKET SDLK_RIGHTBRACKET +#define SDL_SCANCODE_EQUALS SDLK_EQUALS +#define SDL_SCANCODE_MINUS SDLK_MINUS +#define SDL_SCANCODE_TAB SDLK_TAB +#define SDL_SCANCODE_RETURN SDLK_RETURN +#define SDL_SCANCODE_ESCAPE SDLK_ESCAPE +#define SDL_SCANCODE_SPACE SDLK_SPACE +#define SDL_SCANCODE_BACKSPACE SDLK_BACKSPACE +#define SDL_SCANCODE_UP SDLK_UP +#define SDL_SCANCODE_LEFT SDLK_LEFT +#define SDL_SCANCODE_DOWN SDLK_DOWN +#define SDL_SCANCODE_RIGHT SDLK_RIGHT +#define SDL_SCANCODE_LALT SDLK_LALT +#define SDL_SCANCODE_RALT SDLK_RALT +#define SDL_SCANCODE_LCTRL SDLK_LCTRL +#define SDL_SCANCODE_RCTRL SDLK_RCTRL +#define SDL_SCANCODE_LSHIFT SDLK_LSHIFT +#define SDL_SCANCODE_RSHIFT SDLK_RSHIFT +#define SDL_SCANCODE_LGUI SDLK_LMETA +#define SDL_SCANCODE_RGUI SDLK_RMETA +#define SDL_SCANCODE_INSERT SDLK_INSERT +#define SDL_SCANCODE_DELETE SDLK_DELETE +#define SDL_SCANCODE_PAGEDOWN SDLK_PAGEDOWN +#define SDL_SCANCODE_PAGEUP SDLK_PAGEUP +#define SDL_SCANCODE_HOME SDLK_HOME +#define SDL_SCANCODE_END SDLK_END +#define SDL_SCANCODE_KP_1 SDLK_KP1 +#define SDL_SCANCODE_KP_2 SDLK_KP2 +#define SDL_SCANCODE_KP_3 SDLK_KP3 +#define SDL_SCANCODE_KP_4 SDLK_KP4 +#define SDL_SCANCODE_KP_5 SDLK_KP5 +#define SDL_SCANCODE_KP_6 SDLK_KP6 +#define SDL_SCANCODE_KP_7 SDLK_KP7 +#define SDL_SCANCODE_KP_8 SDLK_KP8 +#define SDL_SCANCODE_KP_9 SDLK_KP9 +#define SDL_SCANCODE_KP_0 SDLK_KP0 +#define SDL_SCANCODE_KP_PERIOD SDLK_KP_PERIOD +#define SDL_SCANCODE_KP_ENTER SDLK_KP_ENTER +#define SDL_SCANCODE_KP_PLUS SDLK_KP_PLUS +#define SDL_SCANCODE_KP_MINUS SDLK_KP_MINUS +#define SDL_SCANCODE_KP_DIVIDE SDLK_KP_DIVIDE +#define SDL_SCANCODE_KP_MULTIPLY SDLK_KP_MULTIPLY +#define SDL_SCANCODE_NUMLOCKCLEAR SDLK_NUMLOCK +#define SDL_SCANCODE_CAPSLOCK SDLK_CAPSLOCK +#define SDL_SCANCODE_SLASH SDLK_SLASH +#define SDL_SCANCODE_PERIOD SDLK_PERIOD +#define SDL_SCANCODE_SEMICOLON SDLK_SEMICOLON +#define SDL_SCANCODE_APOSTROPHE SDLK_QUOTE +#define SDL_SCANCODE_COMMA SDLK_COMMA +#define SDL_SCANCODE_PRINTSCREEN SDLK_PRINT +#define SDL_SCANCODE_UNKNOWN SDLK_UNKNOWN +#define SDL_GetScancodeName( x ) "unknown" + +/* +============= +SDLash_KeyEvent + +============= +*/ +static void SDLash_KeyEvent( SDL_KeyboardEvent key ) +{ + int down = key.state != SDL_RELEASED; + int keynum = key.keysym.sym; + + if( host.textmode && down ) + { + // this is how engine understands ctrl+c, ctrl+v and other hotkeys + if( cls.key_dest != key_game && FBitSet( SDL_GetModState(), KMOD_CTRL )) + { + if( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z ) + { + keynum = keynum - SDL_SCANCODE_A + 1; + CL_CharEvent( keynum ); + } + + return; + } + + if( keynum >= SDLK_KP0 && keynum <= SDLK_KP9 ) + keynum -= SDLK_KP0 + '0'; + + if( isprint( keynum )) + { + if( FBitSet( SDL_GetModState(), KMOD_SHIFT )) + keynum = Key_ToUpper( keynum ); + + CL_CharEvent( keynum ); + } + } + +#define DECLARE_KEY_RANGE( min, max, repl ) \ + if( keynum >= (min) && keynum <= (max) ) \ + { \ + keynum = keynum - (min) + (repl); \ + } + + DECLARE_KEY_RANGE( SDL_SCANCODE_A, SDL_SCANCODE_Z, 'a' ) + else DECLARE_KEY_RANGE( SDL_SCANCODE_1, SDL_SCANCODE_9, '1' ) + else DECLARE_KEY_RANGE( SDL_SCANCODE_F1, SDL_SCANCODE_F12, K_F1 ) + else + { + qboolean numLock = FBitSet( SDL_GetModState(), KMOD_NUM ); + + switch( keynum ) + { + case SDL_SCANCODE_GRAVE: keynum = '`'; break; + case SDL_SCANCODE_0: keynum = '0'; break; + case SDL_SCANCODE_BACKSLASH: keynum = '\\'; break; + case SDL_SCANCODE_LEFTBRACKET: keynum = '['; break; + case SDL_SCANCODE_RIGHTBRACKET: keynum = ']'; break; + case SDL_SCANCODE_EQUALS: keynum = '='; break; + case SDL_SCANCODE_MINUS: keynum = '-'; break; + case SDL_SCANCODE_TAB: keynum = K_TAB; break; + case SDL_SCANCODE_RETURN: keynum = K_ENTER; break; + case SDL_SCANCODE_ESCAPE: keynum = K_ESCAPE; break; + case SDL_SCANCODE_SPACE: keynum = K_SPACE; break; + case SDL_SCANCODE_BACKSPACE: keynum = K_BACKSPACE; break; + case SDL_SCANCODE_UP: keynum = K_UPARROW; break; + case SDL_SCANCODE_LEFT: keynum = K_LEFTARROW; break; + case SDL_SCANCODE_DOWN: keynum = K_DOWNARROW; break; + case SDL_SCANCODE_RIGHT: keynum = K_RIGHTARROW; break; + case SDL_SCANCODE_LALT: + case SDL_SCANCODE_RALT: keynum = K_ALT; break; + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_RCTRL: keynum = K_CTRL; break; + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_RSHIFT: keynum = K_SHIFT; break; + case SDL_SCANCODE_LGUI: + case SDL_SCANCODE_RGUI: keynum = K_WIN; break; + case SDL_SCANCODE_INSERT: keynum = K_INS; break; + case SDL_SCANCODE_DELETE: keynum = K_DEL; break; + case SDL_SCANCODE_PAGEDOWN: keynum = K_PGDN; break; + case SDL_SCANCODE_PAGEUP: keynum = K_PGUP; break; + case SDL_SCANCODE_HOME: keynum = K_HOME; break; + case SDL_SCANCODE_END: keynum = K_END; break; + case SDL_SCANCODE_KP_1: keynum = numLock ? '1' : K_KP_END; break; + case SDL_SCANCODE_KP_2: keynum = numLock ? '2' : K_KP_DOWNARROW; break; + case SDL_SCANCODE_KP_3: keynum = numLock ? '3' : K_KP_PGDN; break; + case SDL_SCANCODE_KP_4: keynum = numLock ? '4' : K_KP_LEFTARROW; break; + case SDL_SCANCODE_KP_5: keynum = numLock ? '5' : K_KP_5; break; + case SDL_SCANCODE_KP_6: keynum = numLock ? '6' : K_KP_RIGHTARROW; break; + case SDL_SCANCODE_KP_7: keynum = numLock ? '7' : K_KP_HOME; break; + case SDL_SCANCODE_KP_8: keynum = numLock ? '8' : K_KP_UPARROW; break; + case SDL_SCANCODE_KP_9: keynum = numLock ? '9' : K_KP_PGUP; break; + case SDL_SCANCODE_KP_0: keynum = numLock ? '0' : K_KP_INS; break; + case SDL_SCANCODE_KP_PERIOD: keynum = K_KP_DEL; break; + case SDL_SCANCODE_KP_ENTER: keynum = K_KP_ENTER; break; + case SDL_SCANCODE_KP_PLUS: keynum = K_KP_PLUS; break; + case SDL_SCANCODE_KP_MINUS: keynum = K_KP_MINUS; break; + case SDL_SCANCODE_KP_DIVIDE: keynum = K_KP_SLASH; break; + case SDL_SCANCODE_KP_MULTIPLY: keynum = '*'; break; + case SDL_SCANCODE_NUMLOCKCLEAR: keynum = K_KP_NUMLOCK; break; + case SDL_SCANCODE_CAPSLOCK: keynum = K_CAPSLOCK; break; + case SDL_SCANCODE_SLASH: keynum = '/'; break; + case SDL_SCANCODE_PERIOD: keynum = '.'; break; + case SDL_SCANCODE_SEMICOLON: keynum = ';'; break; + case SDL_SCANCODE_APOSTROPHE: keynum = '\''; break; + case SDL_SCANCODE_COMMA: keynum = ','; break; + case SDL_SCANCODE_PRINTSCREEN: + { + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + break; + } + case SDL_SCANCODE_UNKNOWN: + { + if( down ) Con_Reportf( "%s: Unknown scancode\n", __func__ ); + return; + } + default: + if( down ) Con_Reportf( "%s: Unknown key: %s = %i\n", __func__, SDL_GetScancodeName( keynum ), keynum ); + return; + } + } + +#undef DECLARE_KEY_RANGE + + Key_Event( keynum, down ); +} + +/* +============= +SDLash_MouseEvent + +============= +*/ +static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) +{ + int down; + + if( button.state == SDL_RELEASED ) + down = 0; + else + down = 1; + + switch( button.button ) + { + case SDL_BUTTON_LEFT: + IN_MouseEvent( 0, down ); + break; + case SDL_BUTTON_RIGHT: + IN_MouseEvent( 1, down ); + break; + case SDL_BUTTON_MIDDLE: + IN_MouseEvent( 2, down ); + break; + case SDL_BUTTON_X1: + IN_MouseEvent( 3, down ); + break; + case SDL_BUTTON_X2: + IN_MouseEvent( 4, down ); + break; + case SDL_BUTTON_WHEELUP: + IN_MWheelEvent( -1 ); + break; + case SDL_BUTTON_WHEELDOWN: + IN_MWheelEvent( 1 ); + break; + default: + Con_Printf( "Unknown mouse button ID: %d\n", button.button ); + } +} + +static void SDLash_ActiveEvent( int gain ) +{ + if( gain ) + { + host.status = HOST_FRAME; + if( cls.key_dest == key_game ) + IN_ActivateMouse( ); + + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + if( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN ) + VID_SetMode(); + } + else + { + host.status = HOST_NOFOCUS; + + if( cls.key_dest == key_game ) + { + Key_ClearStates(); + IN_DeactivateMouse(); + } + + host.force_draw_version_time = host.realtime + 2.0; + VID_RestoreScreenResolution(); + } +} + +/* +============= +SDLash_EventFilter + +============= +*/ +static void SDLash_EventHandler( SDL_Event *event ) +{ + switch ( event->type ) + { + /* Mouse events */ + case SDL_MOUSEMOTION: + if( host.mouse_visible ) + SDL_GetRelativeMouseState( NULL, NULL ); + break; + + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + SDLash_MouseEvent( event->button ); + break; + + /* Keyboard events */ + case SDL_KEYDOWN: + case SDL_KEYUP: + SDLash_KeyEvent( event->key ); + break; + + case SDL_QUIT: + Sys_Quit( "caught SDL_QUIT" ); + break; + case SDL_VIDEORESIZE: + VID_SaveWindowSize( event->resize.w, event->resize.h, false ); + break; + case SDL_ACTIVEEVENT: + SDLash_ActiveEvent( event->active.gain ); + break; + } +} + +/* +============= +SDLash_RunEvents + +============= +*/ +void Platform_RunEvents( void ) +{ + SDL_Event event; + + while( host.status != HOST_CRASHED && !host.shutdown_issued && SDL_PollEvent( &event ) ) + SDLash_EventHandler( &event ); +} + +/* +======================== +Platform_PreCreateMove + +this should disable mouse look on client when m_ignore enabled +TODO: kill mouse in win32 clients too +======================== +*/ +void Platform_PreCreateMove( void ) +{ + if( m_ignore.value ) + { + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_ShowCursor( SDL_TRUE ); + } +} diff --git a/engine/platform/sdl1/in_sdl1.c b/engine/platform/sdl1/in_sdl1.c new file mode 100644 index 00000000..ef594c26 --- /dev/null +++ b/engine/platform/sdl1/in_sdl1.c @@ -0,0 +1,154 @@ +/* +in_sdl1.c - SDL input component +Copyright (C) 2018-2025 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include + +#include "common.h" +#include "keydefs.h" +#include "input.h" +#include "client.h" +#include "vgui_draw.h" +#include "platform_sdl1.h" +#include "sound.h" +#include "vid_common.h" + +#define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) + +/* +============= +Platform_GetMousePos + +============= +*/ +void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) +{ + SDL_GetMouseState( x, y ); + + if( x && window_width.value && window_width.value != refState.width ) + { + float factor = refState.width / window_width.value; + *x = *x * factor; + } + + if( y && window_height.value && window_height.value != refState.height ) + { + float factor = refState.height / window_height.value; + *y = *y * factor; + } +} + +/* +============= +Platform_SetMousePos + +============ +*/ +void GAME_EXPORT Platform_SetMousePos( int x, int y ) +{ + SDL_WarpMouseInWindow( host.hWnd, x, y ); +} + +/* +======================== +Platform_MouseMove + +======================== +*/ +void Platform_MouseMove( float *x, float *y ) +{ + int m_x, m_y; + SDL_GetRelativeMouseState( &m_x, &m_y ); + *x = (float)m_x; + *y = (float)m_y; +} + +/* +============= +Platform_GetClipobardText + +============= +*/ +int Platform_GetClipboardText( char *buffer, size_t size ) +{ + buffer[0] = 0; + return 0; +} + +/* +============= +Platform_SetClipobardText + +============= +*/ +void Platform_SetClipboardText( const char *buffer ) +{ +} + +/* +======================== +Platform_SetCursorType + +======================== +*/ +void Platform_SetCursorType( VGUI_DefaultCursor type ) +{ + qboolean visible; + + switch( type ) + { + case dc_user: + case dc_none: + visible = false; + break; + default: + visible = true; + break; + } + + // never disable cursor in touch emulation mode + if( !visible && Touch_WantVisibleCursor( )) + return; + + host.mouse_visible = visible; + VGui_UpdateInternalCursorState( type ); + + if( host.mouse_visible ) + { + SDL_ShowCursor( true ); + } + else + { + SDL_ShowCursor( false ); + } +} + +/* +======================== +Platform_GetMouseGrab +======================== +*/ +qboolean Platform_GetMouseGrab( void ) +{ + return SDL_WM_GrabInput( SDL_GRAB_QUERY ) != SDL_GRAB_OFF; +} + +/* +======================== +Platform_SetMouseGrab +======================== +*/ +void Platform_SetMouseGrab( qboolean enable ) +{ + SDL_WM_GrabInput( enable ? SDL_GRAB_ON : SDL_GRAB_OFF ); +} diff --git a/engine/platform/sdl1/platform_sdl1.h b/engine/platform/sdl1/platform_sdl1.h new file mode 100644 index 00000000..0ed0f03c --- /dev/null +++ b/engine/platform/sdl1/platform_sdl1.h @@ -0,0 +1,37 @@ +/* +platform_sdl1.h - SDL backend internal header +Copyright (C) 2015-2018 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once +#ifndef KEYWRAPPER_H +#define KEYWRAPPER_H +#ifdef XASH_SDL + +#include "platform.h" + +// window management +void VID_RestoreScreenResolution( void ); +qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ); +void VID_DestroyWindow( void ); +void GL_InitExtensions( void ); +qboolean GL_DeleteContext( void ); +void VID_SaveWindowSize( int width, int height, qboolean maximized ); + +// +// joy_sdl.c +// +void SDLash_HandleGameControllerEvent( SDL_Event *ev ); + +#endif // XASH_SDL +#endif // KEYWRAPPER_H diff --git a/engine/platform/sdl1/s_sdl1.c b/engine/platform/sdl1/s_sdl1.c new file mode 100644 index 00000000..86180d9a --- /dev/null +++ b/engine/platform/sdl1/s_sdl1.c @@ -0,0 +1,268 @@ +/* +s_sdl1.c - sound hardware output +Copyright (C) 2009 Uncle Mike +Copyright (C) 2025 Xash3D FWGS contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "platform.h" +#if XASH_SOUND == SOUND_SDL + +#include "sound.h" +#include "voice.h" + +#include +#include + +#define SAMPLE_16BIT_SHIFT 1 +#define SECONDARY_BUFFER_SIZE 0x10000 + +#define SDL_GetCurrentAudioDriver() "legacysdl" +#define SDL_OpenAudioDevice( a, b, c, d, e ) SDL_OpenAudio( ( c ), ( d ) ) +#define SDL_CloseAudioDevice( a ) SDL_CloseAudio() +#define SDL_PauseAudioDevice( a, b ) SDL_PauseAudio( ( b ) ) +#define SDL_LockAudioDevice( x ) SDL_LockAudio() +#define SDL_UnlockAudioDevice( x ) SDL_UnlockAudio() +#define SDLash_IsAudioError( x ) (( x ) != 0) + +/* +======================================================================= +Global variables. Must be visible to window-procedure function +so it can unlock and free the data block after it has been played. +======================================================================= +*/ +static int sdl_dev; +static char sdl_backend_name[32]; + +static void SDL_SoundCallback( void *userdata, Uint8 *stream, int len ) +{ + const int size = dma.samples << 1; + int pos; + int wrapped; + + if( !dma.buffer ) + { + memset( stream, 0, len ); + return; + } + + pos = dma.samplepos << 1; + if( pos >= size ) + pos = dma.samplepos = 0; + + wrapped = pos + len - size; + + if( wrapped < 0 ) + { + memcpy( stream, dma.buffer + pos, len ); + dma.samplepos += len >> 1; + } + else + { + int remaining = size - pos; + + memcpy( stream, dma.buffer + pos, remaining ); + memcpy( stream + remaining, dma.buffer, wrapped ); + dma.samplepos = wrapped >> 1; + } + + if( dma.samplepos >= size ) + dma.samplepos = 0; +} + +/* +================== +SNDDMA_Init + +Try to find a sound device to mix for. +Returns false if nothing is found. +================== +*/ +qboolean SNDDMA_Init( void ) +{ + SDL_AudioSpec desired, obtained; + int samplecount; + const char *driver = NULL; + + // reinitialize SDL with our driver just in case + if( SDL_WasInit( SDL_INIT_AUDIO )) + SDL_QuitSubSystem( SDL_INIT_AUDIO ); + + if( SDL_InitSubSystem( SDL_INIT_AUDIO )) + { + Con_Reportf( S_ERROR "Audio: SDL: %s \n", SDL_GetError( ) ); + return false; + } + + memset( &desired, 0, sizeof( desired ) ); + desired.freq = SOUND_DMA_SPEED; + desired.format = AUDIO_S16LSB; + desired.samples = 1024; + desired.channels = 2; + desired.callback = SDL_SoundCallback; + + sdl_dev = SDL_OpenAudioDevice( NULL, 0, &desired, &obtained, 0 ); + + if( SDLash_IsAudioError( sdl_dev )) + { + Con_Printf( "Couldn't open SDL audio: %s\n", SDL_GetError( ) ); + return false; + } + + if( obtained.format != AUDIO_S16LSB ) + { + Con_Printf( "SDL audio format %d unsupported.\n", obtained.format ); + goto fail; + } + + if( obtained.channels != 1 && obtained.channels != 2 ) + { + Con_Printf( "SDL audio channels %d unsupported.\n", obtained.channels ); + goto fail; + } + + dma.format.speed = obtained.freq; + dma.format.channels = obtained.channels; + dma.format.width = 2; + samplecount = s_samplecount.value; + if( !samplecount ) + samplecount = 0x8000; + dma.samples = samplecount * obtained.channels; + dma.buffer = Mem_Calloc( sndpool, dma.samples * 2 ); + dma.samplepos = 0; + + Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq ); + Q_snprintf( sdl_backend_name, sizeof( sdl_backend_name ), "SDL" ); + dma.initialized = true; + dma.backendName = sdl_backend_name; + + SNDDMA_Activate( true ); + + return true; + +fail: + SNDDMA_Shutdown( ); + return false; +} + + +/* +============== +SNDDMA_BeginPainting + +Makes sure dma.buffer is valid +=============== +*/ +void SNDDMA_BeginPainting( void ) +{ + SDL_LockAudioDevice( sdl_dev ); +} + +/* +============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +Also unlocks the dsound buffer +=============== +*/ +void SNDDMA_Submit( void ) +{ + SDL_UnlockAudioDevice( sdl_dev ); +} + +/* +============== +SNDDMA_Shutdown + +Reset the sound device for exiting +=============== +*/ +void SNDDMA_Shutdown( void ) +{ + Con_Printf( "Shutting down audio.\n" ); + dma.initialized = false; + + if( sdl_dev ) + { + SNDDMA_Activate( false ); + + SDL_CloseAudioDevice( sdl_dev ); + } + + if( SDL_WasInit( SDL_INIT_AUDIO )) + SDL_QuitSubSystem( SDL_INIT_AUDIO ); + + if( dma.buffer ) + { + Mem_Free( dma.buffer ); + dma.buffer = NULL; + } +} + +/* +=========== +SNDDMA_Activate +Called when the main window gains or loses focus. +The window have been destroyed and recreated +between a deactivate and an activate. +=========== +*/ +void SNDDMA_Activate( qboolean active ) +{ + if( !dma.initialized ) + return; + + SDL_PauseAudioDevice( sdl_dev, !active ); +} + +/* +=========== +VoiceCapture_Init +=========== +*/ +qboolean VoiceCapture_Init( void ) +{ + return false; +} + +/* +=========== +VoiceCapture_Activate +=========== +*/ +qboolean VoiceCapture_Activate( qboolean activate ) +{ + return false; +} + +/* +=========== +VoiceCapture_Lock +=========== +*/ +qboolean VoiceCapture_Lock( qboolean lock ) +{ + return false; +} + +/* +========== +VoiceCapture_Shutdown +========== +*/ +void VoiceCapture_Shutdown( void ) +{ +} + +#endif // XASH_SOUND == SOUND_SDL diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl1/sys_sdl1.c similarity index 63% rename from engine/platform/sdl/sys_sdl.c rename to engine/platform/sdl1/sys_sdl1.c index 0194c291..32451bb4 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl1/sys_sdl1.c @@ -1,5 +1,5 @@ /* -sys_sdl.c - SDL2 system utils +sys_sdl1.c - SDL1 system utils Copyright (C) 2018 a1batross This program is free software: you can redistribute it and/or modify @@ -14,15 +14,15 @@ GNU General Public License for more details. */ #include -#include "platform/platform.h" -#include "events.h" +#include "platform.h" +#include "platform_sdl1.h" #if XASH_TIMER == TIMER_SDL double Platform_DoubleTime( void ) { - static longtime_t g_PerformanceFrequency; - static longtime_t g_ClockStart; - longtime_t CurrentTime; + static Uint64 g_PerformanceFrequency; + static Uint64 g_ClockStart; + Uint64 CurrentTime; if( !g_PerformanceFrequency ) { @@ -45,38 +45,17 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL ); } #endif // XASH_MESSAGEBOX == MSGBOX_SDL -void Posix_Daemonize( void ); -void Platform_Init( void ) -{ -#ifndef SDL_INIT_EVENTS -#define SDL_INIT_EVENTS 0 -#endif - if( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS ) ) +void SDLash_Init( const char *basedir ) +{ + if( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO )) { Sys_Warn( "SDL_Init failed: %s", SDL_GetError() ); host.type = HOST_DEDICATED; } - -#if XASH_SDL == 2 - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - SDL_StopTextInput(); -#endif // XASH_SDL == 2 -#if XASH_POSIX - Posix_Daemonize(); -#endif -#ifdef XASH_WIN32 - Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors -#endif - - SDLash_InitCursors(); } -void Platform_Shutdown( void ) +void SDLash_Shutdown( void ) { - SDLash_FreeCursors(); - -#ifdef XASH_WIN32 - Wcon_DestroyConsole(); -#endif + SDL_Quit(); } diff --git a/engine/platform/sdl1/vid_sdl1.c b/engine/platform/sdl1/vid_sdl1.c new file mode 100644 index 00000000..88fc8c00 --- /dev/null +++ b/engine/platform/sdl1/vid_sdl1.c @@ -0,0 +1,535 @@ +/* +vid_sdl1.c - SDL vid component +Copyright (C) 2018 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include +#include "common.h" +#include "client.h" +#include "vid_common.h" +#include "platform_sdl1.h" + +static vidmode_t *vidmodes = NULL; +static int num_vidmodes = 0; +static void GL_SetupAttributes( void ); +struct +{ + int prev_width, prev_height; +} sdlState = { 640, 480 }; + +struct +{ + int width, height; + SDL_Surface *surf; + SDL_Surface *win; +} sw; + +void Platform_Minimize_f( void ) +{ + SDL_WM_IconifyWindow(); +} + +qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) +{ + sw.width = width; + sw.height = height; + sw.win = SDL_GetVideoSurface(); + + // sdl will create renderer if hw framebuffer unavailiable, so cannot fallback here + // if it is failed, it is not possible to draw with SDL in REF_SOFTWARE mode + if( !sw.win ) + { + Sys_Warn( "failed to initialize software output, try running with -glblit flag" ); + return false; + } + + *bpp = sw.win->format->BytesPerPixel; + *r = sw.win->format->Rmask; + *g = sw.win->format->Gmask; + *b = sw.win->format->Bmask; + *stride = sw.win->pitch / sw.win->format->BytesPerPixel; + + return true; +} + +void *SW_LockBuffer( void ) +{ + sw.win = SDL_GetVideoSurface(); + + // prevent buffer overrun + if( !sw.win || sw.win->w < sw.width || sw.win->h < sw.height ) + return NULL; + + // real window pixels (x11 shm region, dma buffer, etc) + // or SDL_Renderer texture if not supported + SDL_LockSurface( sw.win ); + return sw.win->pixels; +} + +void SW_UnlockBuffer( void ) +{ + // already blitted + SDL_UnlockSurface( sw.win ); + + SDL_Flip( host.hWnd ); +} + +int R_MaxVideoModes( void ) +{ + return num_vidmodes; +} + +vidmode_t *R_GetVideoMode( int num ) +{ + if( !vidmodes || num < 0 || num >= R_MaxVideoModes() ) + { + return NULL; + } + + return vidmodes + num; +} + +static void R_InitVideoModes( void ) +{ + char buf[MAX_VA_STRING]; + SDL_Rect **modes; + int len = 0, i = 0, j; + + modes = SDL_ListModes( NULL, SDL_FULLSCREEN ); + + if( !modes || modes == (void*)-1 ) + return; + + for( len = 0; modes[len]; len++ ); + + vidmodes = Mem_Malloc( host.mempool, len * sizeof( vidmode_t ) ); + + // from smallest to largest + for( ; i < len; i++ ) + { + SDL_Rect *mode = modes[len - i - 1]; + + for( j = 0; j < num_vidmodes; j++ ) + { + if( mode->w == vidmodes[j].width && + mode->h == vidmodes[j].height ) + { + break; + } + } + if( j != num_vidmodes ) + continue; + + vidmodes[num_vidmodes].width = mode->w; + vidmodes[num_vidmodes].height = mode->h; + Q_snprintf( buf, sizeof( buf ), "%ix%i", mode->w, mode->h ); + vidmodes[num_vidmodes].desc = copystring( buf ); + + num_vidmodes++; + } +} + +static void R_FreeVideoModes( void ) +{ + int i; + + if( !vidmodes ) + return; + + for( i = 0; i < num_vidmodes; i++ ) + Mem_Free( (char*)vidmodes[i].desc ); + Mem_Free( vidmodes ); + + vidmodes = NULL; +} + +/* +================= +GL_GetProcAddress +================= +*/ +void *GL_GetProcAddress( const char *name ) +{ + void *func = SDL_GL_GetProcAddress( name ); + + if( !func ) + Con_Reportf( S_ERROR "%s failed for %s\n", __func__, name ); + + return func; +} + +/* +=============== +GL_UpdateSwapInterval +=============== +*/ +void GL_UpdateSwapInterval( void ) +{ +} + +/* +================= +GL_DeleteContext + +always return false +================= +*/ +qboolean GL_DeleteContext( void ) +{ + return false; +} + +/* +================= +GL_CreateContext +================= +*/ +static qboolean GL_CreateContext( void ) +{ + return true; +} + +/* +================= +GL_UpdateContext +================= +*/ +static qboolean GL_UpdateContext( void ) +{ + return true; +} + +void VID_SaveWindowSize( int width, int height, qboolean maximized ) +{ + int render_w = width, render_h = height; + + VID_SetDisplayTransform( &render_w, &render_h ); + R_SaveVideoMode( width, height, render_w, render_h, maximized ); +} + +static qboolean VID_SetScreenResolution( int width, int height, window_mode_t window_mode ) +{ + VID_SaveWindowSize( width, height, true ); + return true; +} + +void VID_RestoreScreenResolution( void ) +{ +} + +static qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags ) +{ + while( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST ) + { + host.hWnd = sw.surf = SDL_SetVideoMode( w, h, 16, flags ); + // we have window, exit loop + if( host.hWnd ) + break; + + Con_Reportf( S_ERROR "%s: couldn't create '%s' with safegl level %d: %s\n", __func__, wndname, glw_state.safe, SDL_GetError()); + + glw_state.safe++; + + if( !gl_msaa_samples.value && glw_state.safe == SAFE_NOMSAA ) + glw_state.safe++; // no need to skip msaa, if we already disabled it + + GL_SetupAttributes(); // re-choose attributes + + // try again create window + } + + // window creation has failed... + if( glw_state.safe >= SAFE_LAST ) + return false; + + return true; +} + +static qboolean RectFitsInDisplay( const SDL_Rect *rect, const SDL_Rect *display ) +{ + return rect->x >= display->x + && rect->y >= display->y + && rect->x + rect->w <= display->x + display->w + && rect->y + rect->h <= display->y + display->h; +} +// Function to check if the rectangle fits in any display +static qboolean RectFitsInAnyDisplay( const SDL_Rect *rect, const SDL_Rect *display_rects, int num_displays ) +{ + for( int i = 0; i < num_displays; i++ ) + { + if( RectFitsInDisplay( rect, &display_rects[i] )) + return true; // Rectangle fits in this display + } + return false; // Rectangle does not fit in any display +} + +/* +================= +VID_CreateWindow +================= +*/ +qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ) +{ + string wndname; + Uint32 flags = 0; + + Q_strncpy( wndname, GI->title, sizeof( wndname )); + + if( window_mode != WINDOW_MODE_WINDOWED ) + SetBits( flags, SDL_FULLSCREEN|SDL_HWSURFACE ); + + if( !glw_state.software ) + SetBits( flags, SDL_OPENGL ); + + if( !VID_CreateWindowWithSafeGL( wndname, 0, 0, width, height, flags )) + return false; + + VID_SaveWindowSize( width, height, false ); + + return true; +} + +/* +================= +VID_DestroyWindow +================= +*/ +void VID_DestroyWindow( void ) +{ + GL_DeleteContext(); + + VID_RestoreScreenResolution(); + if( host.hWnd ) + host.hWnd = NULL; + + if( refState.fullScreen ) + refState.fullScreen = false; +} + +/* +================== +GL_SetupAttributes +================== +*/ +static void GL_SetupAttributes( void ) +{ + ref.dllFuncs.GL_SetupAttributes( glw_state.safe ); +} + +void GL_SwapBuffers( void ) +{ + SDL_Flip( host.hWnd ); +} + +int GL_SetAttribute( int attr, int val ) +{ + switch( attr ) + { +#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_SetAttribute( SDL_##name, val ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); +#undef MAP_REF_API_ATTRIBUTE_TO_SDL + } + + return -1; +} + +int GL_GetAttribute( int attr, int *val ) +{ + switch( attr ) + { +#define MAP_REF_API_ATTRIBUTE_TO_SDL( name ) case REF_##name: return SDL_GL_GetAttribute( SDL_##name, val ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_RED_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_GREEN_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_BLUE_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ALPHA_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DOUBLEBUFFER ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_DEPTH_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_STENCIL_SIZE ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); + MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); +#undef MAP_REF_API_ATTRIBUTE_TO_SDL + } + + return 0; +} + +/* +================== +R_Init_Video +================== +*/ +qboolean R_Init_Video( const int type ) +{ + string safe; + qboolean retval; + + refState.desktopBitsPixel = 16; + + switch( type ) + { + case REF_SOFTWARE: + glw_state.software = true; + break; + case REF_GL: + if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) ) + glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE ); + + // refdll can request some attributes + GL_SetupAttributes( ); + + if( SDL_GL_LoadLibrary( NULL ) < 0 ) + { + Con_Reportf( S_ERROR "Couldn't initialize OpenGL: %s\n", SDL_GetError()); + return false; + } + break; + default: + Host_Error( "Can't initialize unknown context type %d!\n", type ); + break; + } + + if( !(retval = VID_SetMode()) ) + { + return retval; + } + + switch( type ) + { + case REF_GL: + // refdll also can check extensions + ref.dllFuncs.GL_InitExtensions(); + break; + case REF_SOFTWARE: + default: + break; + } + + R_InitVideoModes(); + + host.renderinfo_changed = false; + + return true; +} + +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) +{ + refState.fullScreen = window_mode != WINDOW_MODE_WINDOWED; + Con_Reportf( "%s: Setting video mode to %dx%d %s\n", __func__, width, height, refState.fullScreen ? "fullscreen" : "windowed" ); + + if( !host.hWnd ) + { + if( !VID_CreateWindow( width, height, window_mode )) + return rserr_invalid_mode; + } + else if( refState.fullScreen ) + { + if( !VID_SetScreenResolution( width, height, window_mode )) + return rserr_invalid_fullscreen; + } + else + { + VID_RestoreScreenResolution(); + VID_SaveWindowSize( width, height, true ); + } + + return rserr_ok; +} + +/* +================== +VID_SetMode + +Set the described video mode +================== +*/ +qboolean VID_SetMode( void ) +{ + int iScreenWidth, iScreenHeight; + rserr_t err; + window_mode_t window_mode; + + iScreenWidth = Cvar_VariableInteger( "width" ); + iScreenHeight = Cvar_VariableInteger( "height" ); + + if( iScreenWidth < VID_MIN_WIDTH || + iScreenHeight < VID_MIN_HEIGHT ) // trying to get resolution automatically by default + { + iScreenWidth = 320; + iScreenHeight = 240; + } + + window_mode = bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 ); + SetBits( gl_vsync.flags, FCVAR_CHANGED ); + + if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, window_mode )) == rserr_ok ) + { + sdlState.prev_width = iScreenWidth; + sdlState.prev_height = iScreenHeight; + } + else + { + if( err == rserr_invalid_fullscreen ) + { + Cvar_DirectSet( &vid_fullscreen, "0" ); + Con_Reportf( S_ERROR "%s: fullscreen unavailable in this mode\n", __func__ ); + Sys_Warn( "fullscreen unavailable in this mode!" ); + if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, WINDOW_MODE_WINDOWED )) == rserr_ok ) + return true; + } + else if( err == rserr_invalid_mode ) + { + Con_Reportf( S_ERROR "%s: invalid mode\n", __func__ ); + Sys_Warn( "invalid mode, engine will run in %dx%d", sdlState.prev_width, sdlState.prev_height ); + } + + // try setting it back to something safe + if(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, WINDOW_MODE_WINDOWED )) != rserr_ok ) + { + Con_Reportf( S_ERROR "%s: could not revert to safe mode\n", __func__ ); + Sys_Warn( "could not revert to safe mode!" ); + return false; + } + } + + return true; +} + +ref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type ) +{ + return REF_WINDOW_TYPE_NULL; +} + +/* +================== +R_Free_Video +================== +*/ +void R_Free_Video( void ) +{ + GL_DeleteContext (); + + VID_DestroyWindow (); + + R_FreeVideoModes(); + + ref.dllFuncs.GL_ClearExtensions(); +} diff --git a/engine/platform/sdl2/host_sdl2.c b/engine/platform/sdl2/host_sdl2.c new file mode 100644 index 00000000..dcf9d685 --- /dev/null +++ b/engine/platform/sdl2/host_sdl2.c @@ -0,0 +1,471 @@ +/* +host_sdl2.c - SDL event system handlers +Copyright (C) 2015-2025 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include +#include + +#include "common.h" +#include "keydefs.h" +#include "input.h" +#include "client.h" +#include "vgui_draw.h" +#include "platform_sdl2.h" +#include "sound.h" +#include "vid_common.h" + +/* +============= +SDLash_KeyEvent + +============= +*/ +static void SDLash_KeyEvent( SDL_KeyboardEvent key ) +{ + int down = key.state != SDL_RELEASED; + int keynum = key.keysym.scancode; + +#if XASH_ANDROID + if( keynum == SDL_SCANCODE_VOLUMEUP || keynum == SDL_SCANCODE_VOLUMEDOWN ) + { + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + } +#endif + + if( SDL_IsTextInputActive( ) && down ) + { + // this is how engine understands ctrl+c, ctrl+v and other hotkeys + if( cls.key_dest != key_game && FBitSet( SDL_GetModState(), KMOD_CTRL )) + { + if( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z ) + { + keynum = keynum - SDL_SCANCODE_A + 1; + CL_CharEvent( keynum ); + } + + return; + } + + // ignore printable keys, they are coming through SDL_TEXTINPUT + if(( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z ) + || ( keynum >= SDL_SCANCODE_1 && keynum <= SDL_SCANCODE_0 ) + || ( keynum >= SDL_SCANCODE_KP_1 && keynum <= SDL_SCANCODE_KP_0 )) + return; + } + +#define DECLARE_KEY_RANGE( min, max, repl ) \ + if( keynum >= (min) && keynum <= (max) ) \ + { \ + keynum = keynum - (min) + (repl); \ + } + + DECLARE_KEY_RANGE( SDL_SCANCODE_A, SDL_SCANCODE_Z, 'a' ) + else DECLARE_KEY_RANGE( SDL_SCANCODE_1, SDL_SCANCODE_9, '1' ) + else DECLARE_KEY_RANGE( SDL_SCANCODE_F1, SDL_SCANCODE_F12, K_F1 ) + else + { + qboolean numLock = FBitSet( SDL_GetModState(), KMOD_NUM ); + + switch( keynum ) + { + case SDL_SCANCODE_GRAVE: keynum = '`'; break; + case SDL_SCANCODE_0: keynum = '0'; break; + case SDL_SCANCODE_BACKSLASH: keynum = '\\'; break; + case SDL_SCANCODE_LEFTBRACKET: keynum = '['; break; + case SDL_SCANCODE_RIGHTBRACKET: keynum = ']'; break; + case SDL_SCANCODE_EQUALS: keynum = '='; break; + case SDL_SCANCODE_MINUS: keynum = '-'; break; + case SDL_SCANCODE_TAB: keynum = K_TAB; break; + case SDL_SCANCODE_RETURN: keynum = K_ENTER; break; + case SDL_SCANCODE_AC_BACK: + case SDL_SCANCODE_ESCAPE: keynum = K_ESCAPE; break; + case SDL_SCANCODE_SPACE: keynum = K_SPACE; break; + case SDL_SCANCODE_BACKSPACE: keynum = K_BACKSPACE; break; + case SDL_SCANCODE_UP: keynum = K_UPARROW; break; + case SDL_SCANCODE_LEFT: keynum = K_LEFTARROW; break; + case SDL_SCANCODE_DOWN: keynum = K_DOWNARROW; break; + case SDL_SCANCODE_RIGHT: keynum = K_RIGHTARROW; break; + case SDL_SCANCODE_LALT: + case SDL_SCANCODE_RALT: keynum = K_ALT; break; + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_RCTRL: keynum = K_CTRL; break; + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_RSHIFT: keynum = K_SHIFT; break; + case SDL_SCANCODE_LGUI: + case SDL_SCANCODE_RGUI: keynum = K_WIN; break; + case SDL_SCANCODE_INSERT: keynum = K_INS; break; + case SDL_SCANCODE_DELETE: keynum = K_DEL; break; + case SDL_SCANCODE_PAGEDOWN: keynum = K_PGDN; break; + case SDL_SCANCODE_PAGEUP: keynum = K_PGUP; break; + case SDL_SCANCODE_HOME: keynum = K_HOME; break; + case SDL_SCANCODE_END: keynum = K_END; break; + case SDL_SCANCODE_KP_1: keynum = numLock ? '1' : K_KP_END; break; + case SDL_SCANCODE_KP_2: keynum = numLock ? '2' : K_KP_DOWNARROW; break; + case SDL_SCANCODE_KP_3: keynum = numLock ? '3' : K_KP_PGDN; break; + case SDL_SCANCODE_KP_4: keynum = numLock ? '4' : K_KP_LEFTARROW; break; + case SDL_SCANCODE_KP_5: keynum = numLock ? '5' : K_KP_5; break; + case SDL_SCANCODE_KP_6: keynum = numLock ? '6' : K_KP_RIGHTARROW; break; + case SDL_SCANCODE_KP_7: keynum = numLock ? '7' : K_KP_HOME; break; + case SDL_SCANCODE_KP_8: keynum = numLock ? '8' : K_KP_UPARROW; break; + case SDL_SCANCODE_KP_9: keynum = numLock ? '9' : K_KP_PGUP; break; + case SDL_SCANCODE_KP_0: keynum = numLock ? '0' : K_KP_INS; break; + case SDL_SCANCODE_KP_PERIOD: keynum = K_KP_DEL; break; + case SDL_SCANCODE_KP_ENTER: keynum = K_KP_ENTER; break; + case SDL_SCANCODE_KP_PLUS: keynum = K_KP_PLUS; break; + case SDL_SCANCODE_KP_MINUS: keynum = K_KP_MINUS; break; + case SDL_SCANCODE_KP_DIVIDE: keynum = K_KP_SLASH; break; + case SDL_SCANCODE_KP_MULTIPLY: keynum = '*'; break; + case SDL_SCANCODE_NUMLOCKCLEAR: keynum = K_KP_NUMLOCK; break; + case SDL_SCANCODE_CAPSLOCK: keynum = K_CAPSLOCK; break; + case SDL_SCANCODE_SLASH: keynum = '/'; break; + case SDL_SCANCODE_PERIOD: keynum = '.'; break; + case SDL_SCANCODE_SEMICOLON: keynum = ';'; break; + case SDL_SCANCODE_APOSTROPHE: keynum = '\''; break; + case SDL_SCANCODE_COMMA: keynum = ','; break; + case SDL_SCANCODE_PRINTSCREEN: + { + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + break; + } + case SDL_SCANCODE_PAUSE: keynum = K_PAUSE; break; + case SDL_SCANCODE_SCROLLLOCK: keynum = K_SCROLLLOCK; break; + case SDL_SCANCODE_APPLICATION: keynum = K_WIN; break; // (compose key) ??? + case SDL_SCANCODE_VOLUMEUP: keynum = K_AUX32; break; + case SDL_SCANCODE_VOLUMEDOWN: keynum = K_AUX31; break; + // don't console spam on known functional buttons, not used in engine + case SDL_SCANCODE_MUTE: + case SDL_SCANCODE_BRIGHTNESSDOWN: + case SDL_SCANCODE_BRIGHTNESSUP: + case SDL_SCANCODE_SELECT: + return; + case SDL_SCANCODE_UNKNOWN: + { + if( down ) Con_Reportf( "%s: Unknown scancode\n", __func__ ); + return; + } + default: + if( down ) Con_Reportf( "%s: Unknown key: %s = %i\n", __func__, SDL_GetScancodeName( keynum ), keynum ); + return; + } + } + +#undef DECLARE_KEY_RANGE + + Key_Event( keynum, down ); +} + +/* +============= +SDLash_MouseEvent + +============= +*/ +static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) +{ + int down; + + if( button.which == SDL_TOUCH_MOUSEID ) + return; + + if( button.state == SDL_RELEASED ) + down = 0; + else if( button.clicks >= 2 ) + down = 2; // special state for double-click in UI + else + down = 1; + + switch( button.button ) + { + case SDL_BUTTON_LEFT: + IN_MouseEvent( 0, down ); + break; + case SDL_BUTTON_RIGHT: + IN_MouseEvent( 1, down ); + break; + case SDL_BUTTON_MIDDLE: + IN_MouseEvent( 2, down ); + break; + case SDL_BUTTON_X1: + IN_MouseEvent( 3, down ); + break; + case SDL_BUTTON_X2: + IN_MouseEvent( 4, down ); + break; + default: + Con_Printf( "Unknown mouse button ID: %d\n", button.button ); + } +} + +/* +============= +SDLash_InputEvent + +============= +*/ +static void SDLash_InputEvent( SDL_TextInputEvent input ) +{ + const char *text; + + VGui_ReportTextInput( input.text ); + + for( text = input.text; *text; text++ ) + { + int ch = (byte)*text; + + // do not pass UTF-8 sequence into the engine, convert it here + if( !cls.accept_utf8 ) + ch = Con_UtfProcessCharForce( ch ); + + if( !ch ) + continue; + + CL_CharEvent( ch ); + } +} + +static void SDLash_ActiveEvent( int gain ) +{ + if( gain ) + { + host.status = HOST_FRAME; + if( cls.key_dest == key_game ) + IN_ActivateMouse( ); + + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + if( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN ) + VID_SetMode(); + } + else + { +#if TARGET_OS_IPHONE + { + // Keep running if ftp server enabled + void IOS_StartBackgroundTask( void ); + IOS_StartBackgroundTask(); + } +#endif + host.status = HOST_NOFOCUS; + + if( cls.key_dest == key_game ) + { + Key_ClearStates(); + IN_DeactivateMouse(); + } + + host.force_draw_version_time = host.realtime + 2.0; + VID_RestoreScreenResolution(); + } +} + +/* +============= +SDLash_EventFilter + +============= +*/ +static void SDLash_EventHandler( SDL_Event *event ) +{ + switch ( event->type ) + { + /* Mouse events */ + case SDL_MOUSEMOTION: + if( host.mouse_visible ) + SDL_GetRelativeMouseState( NULL, NULL ); + break; + + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + SDLash_MouseEvent( event->button ); + break; + + /* Keyboard events */ + case SDL_KEYDOWN: + case SDL_KEYUP: + SDLash_KeyEvent( event->key ); + break; + + case SDL_QUIT: + Sys_Quit( "caught SDL_QUIT" ); + break; + case SDL_MOUSEWHEEL: + IN_MWheelEvent( event->wheel.y ); + break; + + /* Touch events */ + case SDL_FINGERDOWN: + case SDL_FINGERUP: + case SDL_FINGERMOTION: + { + static int scale = 0; + touchEventType type; + float x, y, dx, dy; + + if( event->type == SDL_FINGERDOWN ) + type = event_down; + else if( event->type == SDL_FINGERUP ) + type = event_up ; + else if( event->type == SDL_FINGERMOTION ) + type = event_motion; + else break; + + /* + SDL sends coordinates in [0..width],[0..height] values + on some devices + */ + if( !scale ) + { + if( ( event->tfinger.x > 0 ) && ( event->tfinger.y > 0 ) ) + { + if( ( event->tfinger.x > 2 ) && ( event->tfinger.y > 2 ) ) + { + scale = 2; + Con_Reportf( "SDL reports screen coordinates, workaround enabled!\n"); + } + else + { + scale = 1; + } + } + } + + x = event->tfinger.x; + y = event->tfinger.y; + dx = event->tfinger.dx; + dy = event->tfinger.dy; + + if( scale == 2 ) + { + x /= (float)refState.width; + y /= (float)refState.height; + dx /= (float)refState.width; + dy /= (float)refState.height; + } + + IN_TouchEvent( type, event->tfinger.fingerId, x, y, dx, dy ); + break; + } + + /* IME */ + case SDL_TEXTINPUT: + SDLash_InputEvent( event->text ); + break; + + /* GameController API */ + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + case SDL_CONTROLLERTOUCHPADDOWN: + case SDL_CONTROLLERTOUCHPADMOTION: + case SDL_CONTROLLERTOUCHPADUP: + case SDL_CONTROLLERSENSORUPDATE: +#endif + SDLash_HandleGameControllerEvent( event ); + break; + + case SDL_WINDOWEVENT: + if( event->window.windowID != SDL_GetWindowID( host.hWnd ) ) + return; + + if( host.status == HOST_SHUTDOWN || Host_IsDedicated() ) + break; // no need to activate + + switch( event->window.event ) + { + case SDL_WINDOWEVENT_MOVED: + { + char val[32]; + + Q_snprintf( val, sizeof( val ), "%d", event->window.data1 ); + Cvar_DirectSet( &window_xpos, val ); + + Q_snprintf( val, sizeof( val ), "%d", event->window.data2 ); + Cvar_DirectSet( &window_ypos, val ); + + if ( vid_fullscreen.value == WINDOW_MODE_WINDOWED ) + Cvar_DirectSet( &vid_maximized, "0" ); + break; + } + case SDL_WINDOWEVENT_MINIMIZED: + host.status = HOST_SLEEP; + Cvar_DirectSet( &vid_maximized, "0" ); + VID_RestoreScreenResolution( ); + break; + case SDL_WINDOWEVENT_RESTORED: + host.status = HOST_FRAME; + host.force_draw_version_time = host.realtime + FORCE_DRAW_VERSION_TIME; + Cvar_DirectSet( &vid_maximized, "0" ); + if( vid_fullscreen.value == WINDOW_MODE_FULLSCREEN ) + VID_SetMode(); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + SDLash_ActiveEvent( true ); + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + SDLash_ActiveEvent( false ); + break; + case SDL_WINDOWEVENT_RESIZED: +#if !XASH_MOBILE_PLATFORM + if( vid_fullscreen.value == WINDOW_MODE_WINDOWED ) +#endif + { + SDL_Window *wnd = SDL_GetWindowFromID( event->window.windowID ); + VID_SaveWindowSize( event->window.data1, event->window.data2, + FBitSet( SDL_GetWindowFlags( wnd ), SDL_WINDOW_MAXIMIZED ) != 0 ); + } + break; + case SDL_WINDOWEVENT_MAXIMIZED: + Cvar_DirectSet( &vid_maximized, "1" ); + break; + default: + break; + } + } +} + +/* +============= +SDLash_RunEvents + +============= +*/ +void Platform_RunEvents( void ) +{ + SDL_Event event; + + while( host.status != HOST_CRASHED && !host.shutdown_issued && SDL_PollEvent( &event ) ) + SDLash_EventHandler( &event ); + +#if XASH_PSVITA + PSVita_InputUpdate(); +#endif +} + +/* +======================== +Platform_PreCreateMove + +this should disable mouse look on client when m_ignore enabled +TODO: kill mouse in win32 clients too +======================== +*/ +void Platform_PreCreateMove( void ) +{ + if( m_ignore.value ) + { + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_ShowCursor( SDL_TRUE ); + } +} diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl2/in_sdl2.c similarity index 57% rename from engine/platform/sdl/in_sdl.c rename to engine/platform/sdl2/in_sdl2.c index 7f18c8ab..45b35c45 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl2/in_sdl2.c @@ -1,6 +1,6 @@ /* -vid_sdl.c - SDL input component -Copyright (C) 2018 a1batross +in_sdl.c - SDL input component +Copyright (C) 2018-2025 a1batross This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -12,7 +12,6 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#if !XASH_DEDICATED #include #include "common.h" @@ -20,20 +19,21 @@ GNU General Public License for more details. #include "input.h" #include "client.h" #include "vgui_draw.h" -#include "events.h" +#include "platform_sdl2.h" #include "sound.h" #include "vid_common.h" -SDL_Joystick *g_joy = NULL; -#if !SDL_VERSION_ATLEAST( 2, 0, 0 ) -#define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) -#else static struct { qboolean initialized; SDL_Cursor *cursors[dc_last]; } cursors; -#endif + +static struct +{ + int x, y; + qboolean pushed; +} in_visible_cursor_pos; /* ============= @@ -44,6 +44,18 @@ Platform_GetMousePos void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) { SDL_GetMouseState( x, y ); + + if( x && window_width.value && window_width.value != refState.width ) + { + float factor = refState.width / window_width.value; + *x = *x * factor; + } + + if( y && window_height.value && window_height.value != refState.height ) + { + float factor = refState.height / window_height.value; + *y = *y * factor; + } } /* @@ -79,7 +91,6 @@ Platform_GetClipobardText */ int Platform_GetClipboardText( char *buffer, size_t size ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) int textLength; char *sdlbuffer = SDL_GetClipboardText(); @@ -95,10 +106,6 @@ int Platform_GetClipboardText( char *buffer, size_t size ) } SDL_free( sdlbuffer ); return textLength; -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - buffer[0] = 0; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - return 0; } /* @@ -109,21 +116,10 @@ Platform_SetClipobardText */ void Platform_SetClipboardText( const char *buffer ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_SetClipboardText( buffer ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -/* -============= -Platform_Vibrate - -============= -*/ -void Platform_Vibrate( float time, char flags ) -{ - // stub -} +#if !XASH_PSVITA /* ============= @@ -133,124 +129,10 @@ SDLash_EnableTextInput */ void Platform_EnableTextInput( qboolean enable ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) enable ? SDL_StartTextInput() : SDL_StopTextInput(); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -/* -============= -SDLash_JoyInit_Old - -============= -*/ -static int SDLash_JoyInit_Old( int numjoy ) -{ - int num; - int i; - - Con_Reportf( "Joystick: SDL\n" ); - - if( SDL_WasInit( SDL_INIT_JOYSTICK ) != SDL_INIT_JOYSTICK && - SDL_InitSubSystem( SDL_INIT_JOYSTICK ) ) - { - Con_Reportf( "Failed to initialize SDL Joysitck: %s\n", SDL_GetError() ); - return 0; - } - - if( g_joy ) - { - SDL_JoystickClose( g_joy ); - } - - num = SDL_NumJoysticks(); - - if( num > 0 ) - Con_Reportf( "%i joysticks found:\n", num ); - else - { - Con_Reportf( "No joystick found.\n" ); - return 0; - } - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - for( i = 0; i < num; i++ ) - Con_Reportf( "%i\t: %s\n", i, SDL_JoystickNameForIndex( i ) ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - - Con_Reportf( "Pass +set joy_index N to command line, where N is number, to select active joystick\n" ); - - g_joy = SDL_JoystickOpen( numjoy ); - - if( !g_joy ) - { - Con_Reportf( "Failed to select joystick: %s\n", SDL_GetError( ) ); - return 0; - } - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - Con_Reportf( "Selected joystick: %s\n" - "\tAxes: %i\n" - "\tHats: %i\n" - "\tButtons: %i\n" - "\tBalls: %i\n", - SDL_JoystickName( g_joy ), SDL_JoystickNumAxes( g_joy ), SDL_JoystickNumHats( g_joy ), - SDL_JoystickNumButtons( g_joy ), SDL_JoystickNumBalls( g_joy ) ); - - SDL_GameControllerEventState( SDL_DISABLE ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_JoystickEventState( SDL_ENABLE ); - - return num; -} - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) -/* -============= -SDLash_JoyInit_New - -============= -*/ -static int SDLash_JoyInit_New( int numjoy ) -{ - int count, numJoysticks, i; - - Con_Reportf( "Joystick: SDL GameController API\n" ); - if( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER && - SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER ) ) - { - Con_Reportf( "Failed to initialize SDL GameController API: %s\n", SDL_GetError() ); - return 0; - } - - SDL_GameControllerAddMappingsFromFile( "controllermappings.txt" ); - - count = 0; - numJoysticks = SDL_NumJoysticks(); - for ( i = 0; i < numJoysticks; i++ ) - if( SDL_IsGameController( i ) ) - ++count; - - return count; -} -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - -/* -============= -Platform_JoyInit - -============= -*/ -int Platform_JoyInit( int numjoy ) -{ -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - // SDL_Joystick is now an old API - // SDL_GameController is preferred - if( !Sys_CheckParm( "-sdl_joy_old_api" ) ) - return SDLash_JoyInit_New(numjoy); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - return SDLash_JoyInit_Old(numjoy); -} +#endif // !XASH_PSVITA /* ======================== @@ -260,7 +142,6 @@ SDLash_InitCursors */ void SDLash_InitCursors( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( cursors.initialized ) SDLash_FreeCursors(); @@ -279,7 +160,6 @@ void SDLash_InitCursors( void ) cursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); cursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); cursors.initialized = true; -#endif } /* @@ -290,7 +170,6 @@ SDLash_FreeCursors */ void SDLash_FreeCursors( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) int i = 0; for( ; i < ARRAYSIZE( cursors.cursors ); i++ ) @@ -301,7 +180,6 @@ void SDLash_FreeCursors( void ) } cursors.initialized = false; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } /* @@ -314,14 +192,6 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) { qboolean visible; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( !cursors.initialized ) - return; -#endif - - if( cls.key_dest != key_game || cl.paused ) - return; - switch( type ) { case dc_user: @@ -333,33 +203,59 @@ void Platform_SetCursorType( VGUI_DefaultCursor type ) break; } - host.mouse_visible = visible; - - if( CVAR_TO_BOOL( touch_emulate )) + // never disable cursor in touch emulation mode + if( !visible && Touch_WantVisibleCursor( )) return; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( host.mouse_visible ) - { - SDL_SetCursor( cursors.cursors[type] ); - SDL_ShowCursor( true ); - Key_EnableTextInput( true, false ); - } - else - { - SDL_ShowCursor( false ); - Key_EnableTextInput( false, false ); - } -#else + host.mouse_visible = visible; + VGui_UpdateInternalCursorState( type ); + if( host.mouse_visible ) { + if( cursors.initialized ) + SDL_SetCursor( cursors.cursors[type] ); + SDL_ShowCursor( true ); + + // restore the last mouse position + if( in_visible_cursor_pos.pushed ) + { + SDL_WarpMouseInWindow( host.hWnd, in_visible_cursor_pos.x, in_visible_cursor_pos.y ); + in_visible_cursor_pos.pushed = false; + } } else { + // save last mouse position and warp it to the center + if( !in_visible_cursor_pos.pushed ) + { + SDL_GetMouseState( &in_visible_cursor_pos.x, &in_visible_cursor_pos.y ); + SDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y ); + in_visible_cursor_pos.pushed = true; + } + SDL_ShowCursor( false ); } -#endif +} + +/* +======================== +Platform_GetMouseGrab +======================== +*/ +qboolean Platform_GetMouseGrab( void ) +{ + return SDL_GetWindowGrab( host.hWnd ); +} + +/* +======================== +Platform_SetMouseGrab +======================== +*/ +void Platform_SetMouseGrab( qboolean enable ) +{ + SDL_SetWindowGrab( host.hWnd, enable ); } /* @@ -370,7 +266,6 @@ Platform_GetKeyModifiers */ key_modifier_t Platform_GetKeyModifiers( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_Keymod modFlags; key_modifier_t resultFlags; @@ -398,9 +293,4 @@ key_modifier_t Platform_GetKeyModifiers( void ) SetBits( resultFlags, KeyModifier_LeftSuper ); return resultFlags; -#else - return KeyModifier_None; -#endif } - -#endif // XASH_DEDICATED diff --git a/engine/platform/sdl2/joy_sdl2.c b/engine/platform/sdl2/joy_sdl2.c new file mode 100644 index 00000000..f8fb28fd --- /dev/null +++ b/engine/platform/sdl2/joy_sdl2.c @@ -0,0 +1,386 @@ +/* +joy_sdl.c - SDL gamepads +Copyright (C) 2018-2025 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include + +#include "common.h" +#include "keydefs.h" +#include "input.h" +#include "client.h" +#include "platform_sdl2.h" + +static const int g_button_mapping[] = +{ +#if XASH_NSWITCH // devkitPro/SDL has inverted Nintendo layout for SDL_GameController + K_B_BUTTON, K_A_BUTTON, K_Y_BUTTON, K_X_BUTTON, +#else + K_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON, +#endif + K_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON, + K_LSTICK, K_RSTICK, + K_L1_BUTTON, K_R1_BUTTON, + K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT, + K_MISC_BUTTON, + K_PADDLE1_BUTTON, K_PADDLE2_BUTTON, K_PADDLE3_BUTTON, K_PADDLE4_BUTTON, + K_TOUCHPAD, +}; + +// Swap axis to follow default axis binding: +static const engineAxis_t g_axis_mapping[] = +{ + JOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX, + JOY_AXIS_FWD, // SDL_CONTROLLER_AXIS_LEFTY, + JOY_AXIS_PITCH, // SDL_CONTROLLER_AXIS_RIGHTX, + JOY_AXIS_YAW, // SDL_CONTROLLER_AXIS_RIGHTY, + JOY_AXIS_LT, // SDL_CONTROLLER_AXIS_TRIGGERLEFT, + JOY_AXIS_RT, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT, +}; + +static SDL_JoystickID g_current_gamepad_id = -1; // used to send rumble to +static SDL_GameController *g_current_gamepad; +static SDL_GameController **g_gamepads; +static size_t g_num_gamepads; + +#define CALIBRATION_TIME 10.0f + +static struct +{ + float time; + vec3_t data; + vec3_t calibrated_values; + int samples; +} gyrocal; + +static void SDLash_RestartCalibration( void ) +{ + Joy_SetCalibrationState( JOY_NOT_CALIBRATED ); + + memset( &gyrocal, 0, sizeof( gyrocal )); + + gyrocal.time = host.realtime + CALIBRATION_TIME; + + Con_Printf( "Starting gyroscope calibration...\n" ); +} + +static void SDLash_FinalizeCalibration( void ) +{ + float data_rate = 10.0f; // let's say we're polling at 10Hz? + int min_samples; + +#if SDL_VERSION_ATLEAST( 2, 0, 16 ) + data_rate = SDL_GameControllerGetSensorDataRate( g_current_gamepad, SDL_SENSOR_GYRO ); + if( !data_rate ) + data_rate = 10.0f; +#endif + + min_samples = Q_rint( CALIBRATION_TIME * data_rate * 0.75f ); + + // we waited for few seconds and got too few samples + if( gyrocal.samples <= min_samples ) + { + Joy_SetCalibrationState( JOY_FAILED_TO_CALIBRATE ); + return; + } + + VectorScale( gyrocal.data, 1.0f / gyrocal.samples, gyrocal.calibrated_values ); + Joy_SetCalibrationState( JOY_CALIBRATED ); + + Con_Printf( "Calibration done. Result: %f %f %f\n", gyrocal.calibrated_values[0], gyrocal.calibrated_values[1], gyrocal.calibrated_values[2] ); + + gyrocal.time = 0.0f; +} + +static void SDLash_GameControllerAddMappings( const char *name ) +{ + fs_offset_t len = 0; + byte *p = FS_LoadFile( name, &len, false ); + + if( !p ) + return; + + if( len > 0 && len < INT32_MAX ) // function accepts int, SDL3 fixes this + { + SDL_RWops *rwops = SDL_RWFromConstMem( p, (int)len ); + SDL_GameControllerAddMappingsFromRW( rwops, true ); + } + + Mem_Free( p ); +} + +static void SDLash_SetActiveGameController( SDL_JoystickID id ) +{ + if( g_current_gamepad_id == id ) + return; + +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + // going to change active controller, disable gyro events in old + SDL_GameControllerSetSensorEnabled( g_current_gamepad, SDL_SENSOR_GYRO, SDL_FALSE ); +#endif // SDL_VERSION_ATLEAST( 2, 0, 14 ) + + g_current_gamepad_id = id; + + if( id < 0 ) + { + g_current_gamepad = NULL; + Joy_SetCapabilities( false ); + Joy_SetCalibrationState( JOY_NOT_CALIBRATED ); + } + else + { + qboolean have_gyro = false; + + g_current_gamepad = SDL_GameControllerFromInstanceID( id ); + +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + have_gyro = SDL_GameControllerHasSensor( g_current_gamepad, SDL_SENSOR_GYRO ); + + if( have_gyro ) + { + SDL_GameControllerSetSensorEnabled( g_current_gamepad, SDL_SENSOR_GYRO, SDL_TRUE ); + SDLash_RestartCalibration(); + } +#endif // SDL_VERSION_ATLEAST( 2, 0, 14 ) + + Joy_SetCapabilities( have_gyro ); + } +} + +static void SDLash_GameControllerAdded( int device_index ) +{ + SDL_GameController *gc; + SDL_GameController **list; + + gc = SDL_GameControllerOpen( device_index ); + if( !gc ) + { + Con_Printf( S_ERROR "SDL_GameControllerOpen: %s\n", SDL_GetError( )); + return; + } + + // this "game controller" only exists on Android within emulator and tries to map + // keyboard events into game controller events, which as you can expect, doesn't + // work and I don't understand the intention here. When debugging Xash in Android + // Studio emulator, just enable hardware input passthrough. +#if XASH_ANDROID + if( !Q_strcmp( SDL_GameControllerName( gc ), "qwerty2" )) + { + SDL_GameControllerClose( gc ); + return; + } +#endif // XASH_ANDROID + + list = Mem_Realloc( host.mempool, g_gamepads, sizeof( *list ) * ( g_num_gamepads + 1 )); + list[g_num_gamepads++] = gc; + + g_gamepads = list; + + // set as current device if none other set + if( g_current_gamepad_id < 0 ) + { + SDL_Joystick *joy = SDL_GameControllerGetJoystick( gc ); + + if( joy ) + SDLash_SetActiveGameController( SDL_JoystickInstanceID( joy )); + } + + Con_Printf( "Detected \"%s\" game controller.\nMapping string: %s\n", SDL_GameControllerName( gc ), SDL_GameControllerMapping( gc )); +} + +static void SDLash_GameControllerRemoved( SDL_JoystickID id ) +{ + size_t i; + + if( id == g_current_gamepad_id ) + SDLash_SetActiveGameController( -1 ); + + // now close the device + for( i = 0; i < g_num_gamepads; i++ ) + { + SDL_GameController *gc = g_gamepads[i]; + SDL_Joystick *joy; + + if( !gc ) + continue; + + joy = SDL_GameControllerGetJoystick( gc ); + + if( !joy ) + continue; + + if( SDL_JoystickInstanceID( joy ) == id ) + { + Con_Printf( "Game controller \"%s\" was disconnected\n", SDL_GameControllerName( gc )); + + SDL_GameControllerClose( gc ); + g_gamepads[i] = NULL; + } + } +} + +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) +static void SDLash_GameControllerSensorUpdate( SDL_ControllerSensorEvent sensor ) +{ + vec3_t data; + + if( sensor.which != g_current_gamepad_id ) + return; + + if( sensor.sensor != SDL_SENSOR_GYRO ) + return; + + if( gyrocal.time != 0.0f ) + { + if( host.realtime > gyrocal.time ) + SDLash_FinalizeCalibration(); + + VectorAdd( gyrocal.data, sensor.data, gyrocal.data ); + gyrocal.samples++; + + Joy_SetCalibrationState( JOY_CALIBRATING ); + return; + } + + VectorSubtract( sensor.data, gyrocal.calibrated_values, data ); + Joy_GyroEvent( data ); +} +#endif + +void SDLash_HandleGameControllerEvent( SDL_Event *ev ) +{ + int x; + + switch( ev->type ) + { + case SDL_CONTROLLERAXISMOTION: + SDLash_SetActiveGameController( ev->caxis.which ); + x = ev->caxis.axis; + if( x >= 0 && x < ARRAYSIZE( g_axis_mapping )) + Joy_AxisMotionEvent( g_axis_mapping[x], ev->caxis.value ); + break; + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + SDLash_SetActiveGameController( ev->cbutton.which ); + x = ev->cbutton.button; + if( x >= 0 && x < ARRAYSIZE( g_button_mapping )) + Key_Event( g_button_mapping[x], ev->cbutton.state ); + break; + case SDL_CONTROLLERDEVICEREMOVED: + SDLash_GameControllerRemoved( ev->cdevice.which ); + break; + case SDL_CONTROLLERDEVICEADDED: + SDLash_GameControllerAdded( ev->cdevice.which ); + break; +#if SDL_VERSION_ATLEAST( 2, 0, 14 ) + case SDL_CONTROLLERSENSORUPDATE: + SDLash_GameControllerSensorUpdate( ev->csensor ); + break; +#endif + } +} + +void Platform_CalibrateGamepadGyro( void ) +{ + SDLash_RestartCalibration(); +} + +void Platform_Vibrate2( float time, int val1, int val2, uint flags ) +{ +#if SDL_VERSION_ATLEAST( 2, 0, 9 ) + SDL_GameController *gc = g_current_gamepad; + Uint32 ms; + + if( g_current_gamepad_id < 0 || !gc ) + return; + + if( val1 < 0 ) + val1 = COM_RandomLong( 0x7FFF, 0xFFFF ); + + if( val2 < 0 ) + val2 = COM_RandomLong( 0x7FFF, 0xFFFF ); + + ms = (Uint32)ceil( time ); + SDL_GameControllerRumble( gc, val1, val2, ms ); +#endif // SDL_VERSION_ATLEAST( 2, 0, 9 ) +} + +/* +============= +Platform_Vibrate + +============= +*/ +void Platform_Vibrate( float time, char flags ) +{ + Platform_Vibrate2( time, -1, -1, flags ); +} + +/* +============= +Platform_JoyInit + +============= +*/ +int Platform_JoyInit( void ) +{ + int count, numJoysticks, i; + + Con_Reportf( "Joystick: SDL GameController API\n" ); + if( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER && + SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER )) + { + Con_Reportf( "Failed to initialize SDL GameController API: %s\n", SDL_GetError( )); + return 0; + } + + SDLash_GameControllerAddMappings( "gamecontrollerdb.txt" ); // shipped in extras.pk3 + SDLash_GameControllerAddMappings( "controllermappings.txt" ); + + count = 0; + numJoysticks = SDL_NumJoysticks(); + for ( i = 0; i < numJoysticks; i++ ) + { + if( SDL_IsGameController( i )) + ++count; + } + + return count; +} + +/* +============= +Platform_JoyShutdown + +============= +*/ +void Platform_JoyShutdown( void ) +{ + size_t i; + + SDLash_SetActiveGameController( -1 ); + + for( i = 0; i < g_num_gamepads; i++ ) + { + if( !g_gamepads[i] ) + continue; + + SDL_GameControllerClose( g_gamepads[i] ); + g_gamepads[i] = NULL; + } + + Mem_Free( g_gamepads ); + g_gamepads = NULL; + g_num_gamepads = 0; + + SDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER ); +} diff --git a/engine/platform/sdl/events.h b/engine/platform/sdl2/platform_sdl2.h similarity index 74% rename from engine/platform/sdl/events.h rename to engine/platform/sdl2/platform_sdl2.h index e42df28e..bf32d4d0 100644 --- a/engine/platform/sdl/events.h +++ b/engine/platform/sdl2/platform_sdl2.h @@ -1,5 +1,5 @@ /* -events.h - SDL backend internal header +platform_sdl2.h - SDL backend internal header Copyright (C) 2015-2018 a1batross This program is free software: you can redistribute it and/or modify @@ -18,20 +18,15 @@ GNU General Public License for more details. #define KEYWRAPPER_H #ifdef XASH_SDL -#include "platform/platform.h" +#include "platform.h" // window management void VID_RestoreScreenResolution( void ); -qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ); +qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ); void VID_DestroyWindow( void ); void GL_InitExtensions( void ); -qboolean GL_CreateContext( void ); -qboolean GL_UpdateContext( void ); qboolean GL_DeleteContext( void ); -void VID_SaveWindowSize( int width, int height ); - -// joystick events -extern SDL_Joystick *g_joy; +void VID_SaveWindowSize( int width, int height, qboolean maximized ); // // in_sdl.c @@ -39,5 +34,10 @@ extern SDL_Joystick *g_joy; void SDLash_InitCursors( void ); void SDLash_FreeCursors( void ); +// +// joy_sdl.c +// +void SDLash_HandleGameControllerEvent( SDL_Event *ev ); + #endif // XASH_SDL #endif // KEYWRAPPER_H diff --git a/engine/platform/sdl/s_sdl.c b/engine/platform/sdl2/s_sdl2.c similarity index 72% rename from engine/platform/sdl/s_sdl.c rename to engine/platform/sdl2/s_sdl2.c index 876926b3..5197c41f 100644 --- a/engine/platform/sdl/s_sdl.c +++ b/engine/platform/sdl2/s_sdl2.c @@ -14,30 +14,19 @@ GNU General Public License for more details. */ #include "common.h" -#include "platform/platform.h" +#include "platform.h" #if XASH_SOUND == SOUND_SDL #include "sound.h" #include "voice.h" #include +#include #define SAMPLE_16BIT_SHIFT 1 #define SECONDARY_BUFFER_SIZE 0x10000 -#if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) -#include -#define SDL_setenv setenv -#define SDL_GetCurrentAudioDriver() "legacysdl" -#define SDL_OpenAudioDevice( a, b, c, d, e ) SDL_OpenAudio( ( c ), ( d ) ) -#define SDL_CloseAudioDevice( a ) SDL_CloseAudio() -#define SDL_PauseAudioDevice( a, b ) SDL_PauseAudio( ( b ) ) -#define SDL_LockAudioDevice( x ) SDL_LockAudio() -#define SDL_UnlockAudioDevice( x ) SDL_UnlockAudio() -#define SDLash_IsAudioError( x ) (( x ) != 0) -#else #define SDLash_IsAudioError( x ) (( x ) == 0) -#endif /* ======================================================================= @@ -50,19 +39,17 @@ static SDL_AudioDeviceID in_dev = 0; static SDL_AudioFormat sdl_format; static char sdl_backend_name[32]; -//static qboolean snd_firsttime = true; -//static qboolean primary_format_set; - -void SDL_SoundCallback( void *userdata, Uint8 *stream, int len ) +static void SDL_SoundCallback( void *userdata, Uint8 *stream, int len ) { - int size = dma.samples << 1; - int pos = dma.samplepos << 1; - int wrapped = pos + len - size; + const int size = dma.samples << 1; + int pos; + int wrapped; -#if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( !dma.buffer ) - return; -#endif + pos = dma.samplepos << 1; + if( pos >= size ) + pos = dma.samplepos = 0; + + wrapped = pos + len - size; if( wrapped < 0 ) { @@ -72,10 +59,14 @@ void SDL_SoundCallback( void *userdata, Uint8 *stream, int len ) else { int remaining = size - pos; + memcpy( stream, dma.buffer + pos, remaining ); memcpy( stream + remaining, dma.buffer, wrapped ); dma.samplepos = wrapped >> 1; } + + if( dma.samplepos >= size ) + dma.samplepos = 0; } /* @@ -90,18 +81,48 @@ qboolean SNDDMA_Init( void ) { SDL_AudioSpec desired, obtained; int samplecount; + const char *driver = NULL; - if( SDL_Init( SDL_INIT_AUDIO ) ) - { - Con_Reportf( S_ERROR "Audio: SDL: %s \n", SDL_GetError( ) ); - return false; - } + // Modders often tend to use proprietary crappy solutions + // like FMOD to play music, sometimes even with versions outdated by a few decades! + // + // As these bullshit sound engines prefer to use DirectSound, we ask SDL2 to do + // the same. Why you might ask? If SDL2 uses another audio API, like WASAPI on + // more modern versions of Windows, it breaks the logic inside Windows, and the whole + // application could hang in WaitFor{Single,Multiple}Object function, either called by + // SDL2 if FMOD was shut down first, or deep in dsound.dll->fmod.dll if SDL2 audio + // was shut down first. + // + // I honestly don't know who is the real culprit here: FMOD, HL modders, Windows, SDL2 + // or us. + // + // Also, fun note, GoldSrc seems doesn't use SDL2 for sound stuff at all, as nothing + // reference SDL audio functions there. It's probably has DirectSound backend, that's + // why modders never stumble upon this bug. +#if XASH_WIN32 + driver = "directsound"; + + if( SDL_getenv( "SDL_AUDIODRIVER" )) + driver = NULL; // let SDL2 and user decide + + SDL_SetHint( SDL_HINT_AUDIODRIVER, driver ); +#endif // XASH_WIN32 // even if we don't have PA // we still can safely set env variables SDL_setenv( "PULSE_PROP_application.name", GI->title, 1 ); SDL_setenv( "PULSE_PROP_media.role", "game", 1 ); + // reinitialize SDL with our driver just in case + if( SDL_WasInit( SDL_INIT_AUDIO )) + SDL_QuitSubSystem( SDL_INIT_AUDIO ); + + if( SDL_InitSubSystem( SDL_INIT_AUDIO )) + { + Con_Reportf( S_ERROR "Audio: SDL: %s \n", SDL_GetError( ) ); + return false; + } + memset( &desired, 0, sizeof( desired ) ); desired.freq = SOUND_DMA_SPEED; desired.format = AUDIO_S16LSB; @@ -136,7 +157,7 @@ qboolean SNDDMA_Init( void ) if( !samplecount ) samplecount = 0x8000; dma.samples = samplecount * obtained.channels; - dma.buffer = Z_Calloc( dma.samples * 2 ); + dma.buffer = Mem_Calloc( sndpool, dma.samples * 2 ); dma.samplepos = 0; sdl_format = obtained.format; @@ -165,7 +186,7 @@ Makes sure dma.buffer is valid */ void SNDDMA_BeginPainting( void ) { -// SDL_LockAudioDevice( sdl_dev ); + SDL_LockAudioDevice( sdl_dev ); } /* @@ -178,7 +199,7 @@ Also unlocks the dsound buffer */ void SNDDMA_Submit( void ) { -// SDL_UnlockAudioDevice( sdl_dev ); + SDL_UnlockAudioDevice( sdl_dev ); } /* @@ -203,7 +224,7 @@ void SNDDMA_Shutdown( void ) } #if !XASH_EMSCRIPTEN - if( SDL_WasInit( SDL_INIT_AUDIO ) ) + if( SDL_WasInit( SDL_INIT_AUDIO )) SDL_QuitSubSystem( SDL_INIT_AUDIO ); #endif @@ -235,7 +256,7 @@ void SNDDMA_Activate( qboolean active ) SDL_SoundInputCallback =========== */ -void SDL_SoundInputCallback( void *userdata, Uint8 *stream, int len ) +static void SDL_SoundInputCallback( void *userdata, Uint8 *stream, int len ) { int size = Q_min( len, sizeof( voice.input_buffer ) - voice.input_buffer_pos ); @@ -272,11 +293,11 @@ qboolean VoiceCapture_Init( void ) if( SDLash_IsAudioError( in_dev )) { - Con_Printf( "VoiceCapture_Init: error creating capture device (%s)\n", SDL_GetError() ); + Con_Printf( "%s: error creating capture device (%s)\n", __func__, SDL_GetError() ); return false; } - Con_Printf( S_NOTE "VoiceCapture_Init: capture device creation success (%i: %s)\n", in_dev, SDL_GetAudioDeviceName( in_dev, SDL_TRUE ) ); + Con_Printf( S_NOTE "%s: capture device creation success (%i: %s)\n", __func__, in_dev, SDL_GetAudioDeviceName( in_dev, SDL_TRUE ) ); return true; } @@ -321,6 +342,7 @@ void VoiceCapture_Shutdown( void ) return; SDL_CloseAudioDevice( in_dev ); + in_dev = 0; } #endif // XASH_SOUND == SOUND_SDL diff --git a/engine/platform/sdl2/sys_sdl2.c b/engine/platform/sdl2/sys_sdl2.c new file mode 100644 index 00000000..99aedda1 --- /dev/null +++ b/engine/platform/sdl2/sys_sdl2.c @@ -0,0 +1,137 @@ +/* +sys_sdl.c - SDL2 system utils +Copyright (C) 2018 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "platform.h" +#include "platform_sdl2.h" + +#if XASH_TIMER == TIMER_SDL +double Platform_DoubleTime( void ) +{ + static Uint64 g_PerformanceFrequency; + static Uint64 g_ClockStart; + Uint64 CurrentTime; + + if( !g_PerformanceFrequency ) + { + g_PerformanceFrequency = SDL_GetPerformanceFrequency(); + g_ClockStart = SDL_GetPerformanceCounter(); + } + CurrentTime = SDL_GetPerformanceCounter(); + return (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency ); +} + +void Platform_Sleep( int msec ) +{ + SDL_Delay( msec ); +} +#endif // XASH_TIMER == TIMER_SDL + +#if XASH_MESSAGEBOX == MSGBOX_SDL +void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) +{ + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL ); +} +#endif // XASH_MESSAGEBOX == MSGBOX_SDL + +static const char *SDLash_CategoryToString( int category ) +{ + switch( category ) + { + case SDL_LOG_CATEGORY_APPLICATION: return "App"; + case SDL_LOG_CATEGORY_ERROR: return "Error"; + case SDL_LOG_CATEGORY_ASSERT: return "Assert"; + case SDL_LOG_CATEGORY_SYSTEM: return "System"; + case SDL_LOG_CATEGORY_AUDIO: return "Audio"; + case SDL_LOG_CATEGORY_VIDEO: return "Video"; + case SDL_LOG_CATEGORY_RENDER: return "Render"; + case SDL_LOG_CATEGORY_INPUT: return "Input"; + case SDL_LOG_CATEGORY_TEST: return "Test"; + default: return "Unknown"; + } +} + +static void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message ) +{ + const char *str = ""; + + switch( priority ) + { + case SDL_LOG_PRIORITY_CRITICAL: + case SDL_LOG_PRIORITY_ERROR: + str = S_ERROR; + break; + case SDL_LOG_PRIORITY_WARN: + str = S_WARN; + break; + case SDL_LOG_PRIORITY_INFO: + str = S_NOTE; + break; + } + + Con_Reportf( "%s" S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", str, SDLash_CategoryToString( category ), message ); +} + +void SDLash_Init( const char *basedir ) +{ +#if XASH_APPLE + char *path = SDL_GetBasePath(); + if( path != NULL ) + { + char buf[MAX_VA_STRING]; + + Q_snprintf( buf, sizeof( buf ), "%s%s/extras.pk3", path, basedir ); + setenv( "XASH3D_EXTRAS_PAK1", buf, true ); + } +#endif + + SDL_LogSetOutputFunction( SDLash_LogOutputFunction, NULL ); + + if( host_developer.value >= 2 ) + SDL_LogSetAllPriority( SDL_LOG_PRIORITY_VERBOSE ); + else if( host_developer.value >= 1 ) + SDL_LogSetAllPriority( SDL_LOG_PRIORITY_WARN ); + else + SDL_LogSetAllPriority( SDL_LOG_PRIORITY_ERROR ); + +#ifndef SDL_INIT_EVENTS +#define SDL_INIT_EVENTS 0 +#endif + if( SDL_Init( SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS ) ) + { + Sys_Warn( "SDL_Init failed: %s", SDL_GetError() ); + host.type = HOST_DEDICATED; + } + +#if SDL_MAJOR_VERSION >= 2 + SDL_SetHint( SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0" ); + +#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS + SDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, "0" ); +#endif // SDL_HINT_MOUSE_TOUCH_EVENTS + SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + + SDL_StopTextInput(); +#endif // XASH_SDL == 2 + + SDLash_InitCursors(); +} + +void SDLash_Shutdown( void ) +{ + SDLash_FreeCursors(); + + SDL_Quit(); +} diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl2/vid_sdl2.c similarity index 52% rename from engine/platform/sdl/vid_sdl.c rename to engine/platform/sdl2/vid_sdl2.c index e9dd87e3..4e33ec4a 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl2/vid_sdl2.c @@ -12,14 +12,15 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#if !XASH_DEDICATED #include +#include #include "common.h" #include "client.h" -#include "mod_local.h" -#include "input.h" #include "vid_common.h" -#include "platform/sdl/events.h" +#include "platform_sdl2.h" + +// include it after because it breaks definitions in net_api.h wtf +#include static vidmode_t *vidmodes = NULL; static int num_vidmodes = 0; @@ -31,28 +32,34 @@ struct struct { -#if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Renderer *renderer; SDL_Texture *tex; -#endif int width, height; SDL_Surface *surf; SDL_Surface *win; } sw; +void Platform_Minimize_f( void ) +{ + if( host.hWnd ) + SDL_MinimizeWindow( host.hWnd ); +} + qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) { sw.width = width; sw.height = height; -#if SDL_VERSION_ATLEAST(2, 0, 0) if( sw.renderer ) { unsigned int format = SDL_GetWindowPixelFormat( host.hWnd ); - SDL_RenderSetLogicalSize(sw.renderer, refState.width, refState.height); + SDL_RenderSetLogicalSize( sw.renderer, refState.width, refState.height ); if( sw.tex ) + { SDL_DestroyTexture( sw.tex ); + sw.tex = NULL; + } // guess if( format == SDL_PIXELFORMAT_UNKNOWN ) @@ -65,20 +72,16 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * // we can only copy fast 16 or 32 bits // SDL_Renderer does not allow zero-copy, so 24 bits will be ineffective - if( !( SDL_BYTESPERPIXEL(format) == 2 || SDL_BYTESPERPIXEL(format) == 4 ) ) + if( SDL_BYTESPERPIXEL( format ) != 2 && SDL_BYTESPERPIXEL( format ) != 4 ) format = SDL_PIXELFORMAT_RGBA8888; - sw.tex = SDL_CreateTexture(sw.renderer, format, - SDL_TEXTUREACCESS_STREAMING, - width, height); + sw.tex = SDL_CreateTexture( sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height ); // fallback if( !sw.tex && format != SDL_PIXELFORMAT_RGBA8888 ) { format = SDL_PIXELFORMAT_RGBA8888; - sw.tex = SDL_CreateTexture(sw.renderer, format, - SDL_TEXTUREACCESS_STREAMING, - width, height); + sw.tex = SDL_CreateTexture( sw.renderer, format, SDL_TEXTUREACCESS_STREAMING, width, height ); } if( !sw.tex ) @@ -91,44 +94,39 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * void *pixels; int pitch; - if( !SDL_LockTexture(sw.tex, NULL, &pixels, &pitch ) ) + if( !SDL_LockTexture( sw.tex, NULL, &pixels, &pitch )) { int bits; uint amask; + // lock successfull, release - SDL_UnlockTexture(sw.tex); + SDL_UnlockTexture( sw.tex ); // enough for building blitter tables SDL_PixelFormatEnumToMasks( format, &bits, r, g, b, &amask ); - *bpp = SDL_BYTESPERPIXEL(format); + *bpp = SDL_BYTESPERPIXEL( format ); *stride = pitch / *bpp; return true; } // fallback to surf - SDL_DestroyTexture(sw.tex); + SDL_DestroyTexture( sw.tex ); sw.tex = NULL; - SDL_DestroyRenderer(sw.renderer); + SDL_DestroyRenderer( sw.renderer ); sw.renderer = NULL; } } -#endif -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( !sw.renderer ) { sw.win = SDL_GetWindowSurface( host.hWnd ); -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - { - sw.win = SDL_GetVideoSurface(); -#endif // sdl will create renderer if hw framebuffer unavailiable, so cannot fallback here // if it is failed, it is not possible to draw with SDL in REF_SOFTWARE mode if( !sw.win ) { - Sys_Warn("failed to initialize software output, try enable sw_glblit"); + Sys_Warn( "failed to initialize software output, try running with -glblit flag" ); return false; } @@ -143,9 +141,10 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * { sw.surf = SDL_CreateRGBSurfaceWithFormat( 0, width, height, 16, SDL_PIXELFORMAT_RGB565 ); if( !sw.surf ) - Sys_Error(SDL_GetError()); + Sys_Error( "%s: %s", __func__, SDL_GetError( )); } #endif + return true; } // we can't create ref_soft buffer @@ -154,14 +153,13 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * void *SW_LockBuffer( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.renderer ) { void *pixels; int stride; - if( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) ) - Sys_Error("%s", SDL_GetError()); + if( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) < 0 ) + Sys_Error( "%s: %s", __func__, SDL_GetError( )); return pixels; } @@ -169,22 +167,17 @@ void *SW_LockBuffer( void ) sw.win = SDL_GetWindowSurface( host.hWnd ); //if( !sw.win ) //SDL_GetWindowSurface( host.hWnd ); -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - sw.win = SDL_GetVideoSurface(); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) // prevent buffer overrun if( !sw.win || sw.win->w < sw.width || sw.win->h < sw.height ) return NULL; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.surf ) { SDL_LockSurface( sw.surf ); return sw.surf->pixels; } else -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) { // real window pixels (x11 shm region, dma buffer, etc) // or SDL_Renderer texture if not supported @@ -195,7 +188,6 @@ void *SW_LockBuffer( void ) void SW_UnlockBuffer( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( sw.renderer ) { SDL_Rect src, dst; @@ -227,16 +219,11 @@ void SW_UnlockBuffer( void ) SDL_BlitSurface( sw.surf, &src, sw.win, &dst ); return; } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) // already blitted SDL_UnlockSurface( sw.win ); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_UpdateWindowSurface( host.hWnd ); -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_Flip( host.hWnd ); -#endif } int R_MaxVideoModes( void ) @@ -256,8 +243,13 @@ vidmode_t *R_GetVideoMode( int num ) static void R_InitVideoModes( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - int displayIndex = 0; // TODO: handle multiple displays somehow + char buf[MAX_VA_STRING]; +#if SDL_VERSION_ATLEAST( 2, 24, 0 ) + SDL_Point point = { window_xpos.value, window_ypos.value }; + int displayIndex = SDL_GetPointDisplayIndex( &point ); +#else + int displayIndex = 0; +#endif int i, modes; num_vidmodes = 0; @@ -273,7 +265,7 @@ static void R_InitVideoModes( void ) int j; SDL_DisplayMode mode; - if( SDL_GetDisplayMode( displayIndex, i, &mode ) ) + if( SDL_GetDisplayMode( displayIndex, i, &mode ) < 0 ) { Msg( "SDL_GetDisplayMode: %s\n", SDL_GetError() ); continue; @@ -295,46 +287,11 @@ static void R_InitVideoModes( void ) vidmodes[num_vidmodes].width = mode.w; vidmodes[num_vidmodes].height = mode.h; - vidmodes[num_vidmodes].desc = copystring( va( "%ix%i", mode.w, mode.h )); + Q_snprintf( buf, sizeof( buf ), "%ix%i", mode.w, mode.h ); + vidmodes[num_vidmodes].desc = copystring( buf ); num_vidmodes++; } -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_Rect **modes; - int len = 0, i = 0, j; - - modes = SDL_ListModes( NULL, SDL_FULLSCREEN ); - - if( !modes || modes == (void*)-1 ) - return; - - for( len = 0; modes[len]; len++ ); - - vidmodes = Mem_Malloc( host.mempool, len * sizeof( vidmode_t ) ); - - // from smallest to largest - for( ; i < len; i++ ) - { - SDL_Rect *mode = modes[len - i - 1]; - - for( j = 0; j < num_vidmodes; j++ ) - { - if( mode->w == vidmodes[j].width && - mode->h == vidmodes[j].height ) - { - break; - } - } - if( j != num_vidmodes ) - continue; - - vidmodes[num_vidmodes].width = mode->w; - vidmodes[num_vidmodes].height = mode->h; - vidmodes[num_vidmodes].desc = copystring( va( "%ix%i", mode->w, mode->h )); - - num_vidmodes++; - } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } @@ -352,7 +309,7 @@ static void R_FreeVideoModes( void ) vidmodes = NULL; } -#ifdef WIN32 +#if XASH_WIN32 typedef enum _XASH_DPI_AWARENESS { XASH_DPI_UNAWARE = 0, @@ -367,7 +324,7 @@ static void WIN_SetDPIAwareness( void ) BOOL ( __stdcall *pSetProcessDPIAware )( void ); BOOL bSuccess = FALSE; - if( ( hModule = LoadLibrary( "shcore.dll" ) ) ) + if( ( hModule = LoadLibraryW( L"shcore.dll" ) ) ) { if( ( pSetProcessDpiAwareness = (void*)GetProcAddress( hModule, "SetProcessDpiAwareness" ) ) ) { @@ -376,23 +333,23 @@ static void WIN_SetDPIAwareness( void ) if( hResult == S_OK ) { - Con_Reportf( "SetDPIAwareness: Success\n" ); + Con_Reportf( "%s: Success\n", __func__ ); bSuccess = TRUE; } - else if( hResult == E_INVALIDARG ) Con_Reportf( "SetDPIAwareness: Invalid argument\n" ); - else if( hResult == E_ACCESSDENIED ) Con_Reportf( "SetDPIAwareness: Access Denied\n" ); + else if( hResult == E_INVALIDARG ) Con_Reportf( "%s: Invalid argument\n", __func__ ); + else if( hResult == E_ACCESSDENIED ) Con_Reportf( "%s: Access Denied\n", __func__ ); } - else Con_Reportf( "SetDPIAwareness: Can't get SetProcessDpiAwareness\n" ); + else Con_Reportf( "%s: Can't get SetProcessDpiAwareness\n", __func__ ); FreeLibrary( hModule ); } - else Con_Reportf( "SetDPIAwareness: Can't load shcore.dll\n" ); + else Con_Reportf( "%s: Can't load shcore.dll\n", __func__ ); if( !bSuccess ) { - Con_Reportf( "SetDPIAwareness: Trying SetProcessDPIAware...\n" ); + Con_Reportf( "%s: Trying SetProcessDPIAware...\n", __func__ ); - if( ( hModule = LoadLibrary( "user32.dll" ) ) ) + if( ( hModule = LoadLibraryW( L"user32.dll" ) ) ) { if( ( pSetProcessDPIAware = ( void* )GetProcAddress( hModule, "SetProcessDPIAware" ) ) ) { @@ -401,17 +358,34 @@ static void WIN_SetDPIAwareness( void ) if( hResult ) { - Con_Reportf( "SetDPIAwareness: Success\n" ); + Con_Reportf( "%s: Success\n", __func__ ); bSuccess = TRUE; } - else Con_Reportf( "SetDPIAwareness: fail\n" ); + else Con_Reportf( "%s: fail\n", __func__ ); } - else Con_Reportf( "SetDPIAwareness: Can't get SetProcessDPIAware\n" ); + else Con_Reportf( "%s: Can't get SetProcessDPIAware\n", __func__ ); FreeLibrary( hModule ); } - else Con_Reportf( "SetDPIAwareness: Can't load user32.dll\n" ); + else Con_Reportf( "%s: Can't load user32.dll\n", __func__ ); } } + +static qboolean WIN_SetWindowIcon( HICON ico ) +{ + SDL_SysWMinfo wminfo; + + SDL_VERSION( &wminfo.version ); + + if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) == SDL_TRUE && wminfo.subsystem == SDL_SYSWM_WINDOWS ) + { + SendMessage( wminfo.info.win.window, WM_SETICON, ICON_SMALL, (LONG_PTR)ico ); + SendMessage( wminfo.info.win.window, WM_SETICON, ICON_BIG, (LONG_PTR)ico ); + return true; + } + + Con_Reportf( S_ERROR "%s: %s", __func__, SDL_GetError( )); + return false; +} #endif @@ -422,15 +396,38 @@ GL_GetProcAddress */ void *GL_GetProcAddress( const char *name ) { -#if defined( XASH_NANOGL ) - void *func = nanoGL_GetProcAddress( name ); -#else void *func = SDL_GL_GetProcAddress( name ); +#if !SDL_VERSION_ATLEAST( 2, 0, 6 ) && XASH_POSIX + if( !func && Sys_CheckParm( "-egl" )) + { + /* + * SDL2 has broken SDL_GL_GetProcAddress until this commit if using egl: + * https://github.com/libsdl-org/SDL/commit/466ba57d42d244e80357e9ad3011c50af30ed225 + * so call eglGetProcAddress directly + * */ + static void *(*peglGetProcAddress)( const char * ); + if( !peglGetProcAddress ) + { + void *lib = dlopen( "libEGL.so", RTLD_NOW ); + if( lib ) + *(void**)&peglGetProcAddress = dlsym( lib, "eglGetProcAddress" ); + } + if( peglGetProcAddress ) + func = peglGetProcAddress( name ); + } +#endif + +#if XASH_PSVITA + // try to find in main module + if( !func ) + { + func = dlsym( NULL, name ); + } #endif if( !func ) { - Con_Reportf( S_ERROR "Error: GL_GetProcAddress failed for %s\n", name ); + Con_Reportf( S_ERROR "%s failed for %s\n", __func__, name ); } return func; @@ -443,21 +440,13 @@ GL_UpdateSwapInterval */ void GL_UpdateSwapInterval( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - // disable VSync while level is loading - if( cls.state < ca_active ) + if( FBitSet( gl_vsync.flags, FCVAR_CHANGED )) { - SDL_GL_SetSwapInterval( 0 ); - SetBits( gl_vsync->flags, FCVAR_CHANGED ); - } - else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) - { - ClearBits( gl_vsync->flags, FCVAR_CHANGED ); + ClearBits( gl_vsync.flags, FCVAR_CHANGED ); - if( SDL_GL_SetSwapInterval( gl_vsync->value ) ) - Con_Reportf( S_ERROR "SDL_GL_SetSwapInterval: %s\n", SDL_GetError( ) ); + if( SDL_GL_SetSwapInterval( gl_vsync.value ) < 0 ) + Con_Reportf( S_ERROR "SDL_GL_SetSwapInterval: %s\n", SDL_GetError( )); } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } /* @@ -469,13 +458,11 @@ always return false */ qboolean GL_DeleteContext( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( glw_state.context ) { SDL_GL_DeleteContext(glw_state.context); glw_state.context = NULL; } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return false; } @@ -484,15 +471,13 @@ qboolean GL_DeleteContext( void ) GL_CreateContext ================= */ -qboolean GL_CreateContext( void ) +static qboolean GL_CreateContext( void ) { -#if SDL_VERSION_ATLEAST(2, 0, 0) if( ( glw_state.context = SDL_GL_CreateContext( host.hWnd ) ) == NULL) { - Con_Reportf( S_ERROR "GL_CreateContext: %s\n", SDL_GetError()); + Con_Reportf( S_ERROR "%s: %s\n", __func__, SDL_GetError( )); return GL_DeleteContext(); } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return true; } @@ -501,169 +486,180 @@ qboolean GL_CreateContext( void ) GL_UpdateContext ================= */ -qboolean GL_UpdateContext( void ) +static qboolean GL_UpdateContext( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context )) + if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context ) < 0 ) { - Con_Reportf( S_ERROR "GL_UpdateContext: %s\n", SDL_GetError()); + Con_Reportf( S_ERROR "%s: %s\n", __func__, SDL_GetError( )); return GL_DeleteContext(); } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) return true; } -void VID_SaveWindowSize( int width, int height ) +void VID_SaveWindowSize( int width, int height, qboolean maximized ) { int render_w = width, render_h = height; - uint rotate = vid_rotate->value; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( !glw_state.software ) SDL_GL_GetDrawableSize( host.hWnd, &render_w, &render_h ); else SDL_RenderSetLogicalSize( sw.renderer, width, height ); -#endif - if( ref.dllFuncs.R_SetDisplayTransform( rotate, 0, 0, vid_scale->value, vid_scale->value ) ) - { - if( rotate & 1 ) - { - int swap = render_w; - - render_w = render_h; - render_h = swap; - } - - render_h /= vid_scale->value; - render_w /= vid_scale->value; - } - else - { - Con_Printf( S_WARN "failed to setup screen transform\n" ); - } - - R_SaveVideoMode( width, height, render_w, render_h ); + VID_SetDisplayTransform( &render_w, &render_h ); + R_SaveVideoMode( width, height, render_w, render_h, maximized ); } -static qboolean VID_SetScreenResolution( int width, int height ) +static qboolean VID_SetScreenResolution( int width, int height, window_mode_t window_mode ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_DisplayMode want, got; + SDL_DisplayMode got; Uint32 wndFlags = 0; - static string wndname; -#if !XASH_APPLE - if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; -#endif - Q_strncpy( wndname, GI->title, sizeof( wndname )); - - want.w = width; - want.h = height; - want.driverdata = NULL; - want.format = want.refresh_rate = 0; // don't care - - if( !SDL_GetClosestDisplayMode(0, &want, &got) ) - return false; - - Con_Reportf( "Got closest display mode: %ix%i@%i\n", got.w, got.h, got.refresh_rate); - - if( SDL_SetWindowDisplayMode( host.hWnd, &got) == -1 ) - return false; - - if( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN) == -1 ) - return false; + if( vid_highdpi.value ) + SetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI ); SDL_SetWindowBordered( host.hWnd, SDL_FALSE ); - //SDL_SetWindowPosition( host.hWnd, 0, 0 ); - SDL_SetWindowSize( host.hWnd, got.w, got.h ); - VID_SaveWindowSize( got.w, got.h ); -#else - VID_SaveWindowSize( width, height ); -#endif + if( window_mode == WINDOW_MODE_BORDERLESS ) + { + if( SDL_GetDesktopDisplayMode( 0, &got ) < 0 ) + { + Con_Printf( S_ERROR "%s: SDL_GetDesktopDisplayMode: %s", __func__, SDL_GetError( )); + return false; + } + + if( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN_DESKTOP ) < 0 ) + { + Con_Printf( S_ERROR "%s: SDL_SetWindowFullscreen (borderless): %s", __func__, SDL_GetError( )); + return false; + } + } + else if( window_mode == WINDOW_MODE_FULLSCREEN ) + { + SDL_DisplayMode want = { 0 }; + want.w = width; + want.h = height; + + if( SDL_GetClosestDisplayMode( 0, &want, &got ) == NULL ) + { + Con_Printf( S_ERROR "%s: SDL_GetClosestDisplayMode: %s", __func__, SDL_GetError( )); + return false; + } + + if( got.w != want.w || got.h != want.h ) + Con_Reportf( S_NOTE "Got closest display mode: %ix%i@%i\n", got.w, got.h, got.refresh_rate ); + + if( SDL_SetWindowDisplayMode( host.hWnd, &got ) < 0 ) + { + Con_Printf( S_ERROR "%s: SDL_SetWindowDisplayMode: %s", __func__, SDL_GetError( )); + return false; + } + + if( SDL_SetWindowFullscreen( host.hWnd, SDL_WINDOW_FULLSCREEN ) < 0 ) + { + Con_Printf( S_ERROR "%s: SDL_SetWindowFullscreen (fullscreen): %s", __func__, SDL_GetError( )); + return false; + } + } + + SDL_SetWindowSize( host.hWnd, got.w, got.h ); + VID_SaveWindowSize( got.w, got.h, true ); return true; } void VID_RestoreScreenResolution( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( !Cvar_VariableInteger("fullscreen") ) + // on mobile platform fullscreen is designed to be always on + // and code below minimizes our window if we're in full screen + // don't do that on mobile devices +#if !XASH_MOBILE_PLATFORM + switch((window_mode_t)vid_fullscreen.value ) { + case WINDOW_MODE_WINDOWED: + // TODO: this line is from very old SDL video backend + // figure out why we need it, because in windowed mode we + // always have borders SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); - } - else - { + break; + case WINDOW_MODE_BORDERLESS: + // in borderless fullscreen we don't change screen resolution, so no-op + break; + case WINDOW_MODE_FULLSCREEN: + // TODO: we might want to not minimize window if current desktop mode + // and window mode are the same SDL_MinimizeWindow( host.hWnd ); SDL_SetWindowFullscreen( host.hWnd, 0 ); + break; } -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) +#endif // !XASH_MOBILE_PLATFORM } +static void VID_SetWindowIcon( SDL_Window *hWnd ) +{ + rgbdata_t *icon = NULL; + char iconpath[MAX_STRING]; +#if XASH_WIN32 // ICO support only for Win32 + const char *disk_iconpath = FS_GetDiskPath( GI->iconpath, true ); + + if( disk_iconpath ) + { + int len = MultiByteToWideChar( CP_UTF8, 0, disk_iconpath, -1, NULL, 0 ); + + if( len >= 0 ) + { + wchar_t *path = malloc( len * sizeof( *path )); + HICON ico; + + MultiByteToWideChar( CP_UTF8, 0, disk_iconpath, -1, path, len ); + ico = (HICON)LoadImageW( NULL, path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); + + free( path ); + + if( ico && WIN_SetWindowIcon( ico )) + return; + } + } +#endif // XASH_WIN32 + + Q_strncpy( iconpath, GI->iconpath, sizeof( iconpath )); + COM_ReplaceExtension( iconpath, ".tga", sizeof( iconpath )); + icon = FS_LoadImage( iconpath, NULL, 0 ); + + if( icon ) + { + SDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer, + icon->width, icon->height, 32, 4 * icon->width, + 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); + + FS_FreeImage( icon ); + + if( surface ) + { + SDL_SetWindowIcon( host.hWnd, surface ); + SDL_FreeSurface( surface ); + return; + } + } + #if XASH_WIN32 // ICO support only for Win32 -#include "SDL_syswm.h" -static void WIN_SetWindowIcon( HICON ico ) -{ - SDL_SysWMinfo wminfo; - - if( !ico ) - return; - - if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) ) - { - SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG)ico ); - } -} + WIN_SetWindowIcon( LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( 101 ))); #endif +} -/* -================= -VID_CreateWindow -================= -*/ -qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) +static qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags ) { - static string wndname; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - Uint32 wndFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_MOUSE_FOCUS; - rgbdata_t *icon = NULL; - qboolean iconLoaded = false; - char iconpath[MAX_STRING]; - int xpos, ypos; - const char *localIcoPath; - - if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; - Q_strncpy( wndname, GI->title, sizeof( wndname )); - if( glw_state.software ) - wndFlags &= ~SDL_WINDOW_OPENGL; - - if( !fullscreen ) - { - wndFlags |= SDL_WINDOW_RESIZABLE; - xpos = Cvar_VariableInteger( "_window_xpos" ); - ypos = Cvar_VariableInteger( "_window_ypos" ); - if( xpos < 0 ) xpos = SDL_WINDOWPOS_CENTERED; - if( ypos < 0 ) ypos = SDL_WINDOWPOS_CENTERED; - } - else - { - wndFlags |= SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS | SDL_WINDOW_INPUT_GRABBED; - xpos = ypos = 0; - } - while( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST ) { - host.hWnd = SDL_CreateWindow( wndname, xpos, ypos, width, height, wndFlags ); + host.hWnd = SDL_CreateWindow( wndname, xpos, ypos, w, h, flags ); // we have window, exit loop if( host.hWnd ) break; - Con_Reportf( S_ERROR "VID_CreateWindow: couldn't create '%s' with safegl level %d: %s\n", wndname, glw_state.safe, SDL_GetError()); + Con_Reportf( S_ERROR "%s: couldn't create '%s' with safegl level %d: %s\n", __func__, wndname, glw_state.safe, SDL_GetError()); glw_state.safe++; - if( !gl_msaa_samples->value && glw_state.safe == SAFE_NOMSAA ) + if( !gl_msaa_samples.value && glw_state.safe == SAFE_NOMSAA ) glw_state.safe++; // no need to skip msaa, if we already disabled it GL_SetupAttributes(); // re-choose attributes @@ -673,70 +669,125 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) // window creation has failed... if( glw_state.safe >= SAFE_LAST ) - { return false; - } - if( fullscreen ) + return true; +} + +static qboolean RectFitsInDisplay( const SDL_Rect *rect, const SDL_Rect *display ) +{ + return rect->x >= display->x + && rect->y >= display->y + && rect->x + rect->w <= display->x + display->w + && rect->y + rect->h <= display->y + display->h; +} +// Function to check if the rectangle fits in any display +static qboolean RectFitsInAnyDisplay( const SDL_Rect *rect, const SDL_Rect *display_rects, int num_displays ) +{ + for( int i = 0; i < num_displays; i++ ) { - if( !VID_SetScreenResolution( width, height ) ) + if( RectFitsInDisplay( rect, &display_rects[i] )) + return true; // Rectangle fits in this display + } + return false; // Rectangle does not fit in any display +} + +/* +================= +VID_CreateWindow +================= +*/ +qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ) +{ + string wndname; + qboolean maximized = vid_maximized.value != 0.0f; + Uint32 wndFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_MOUSE_FOCUS; + int xpos, ypos; + int num_displays = SDL_GetNumVideoDisplays(); + SDL_Rect rect = { window_xpos.value, window_ypos.value, width, height }; + + Q_strncpy( wndname, GI->title, sizeof( wndname )); + + if( vid_highdpi.value ) + SetBits( wndFlags, SDL_WINDOW_ALLOW_HIGHDPI ); + if( !glw_state.software ) + SetBits( wndFlags, SDL_WINDOW_OPENGL ); + + if( window_mode == WINDOW_MODE_WINDOWED ) + { + SDL_Rect *display_rects = ( SDL_Rect * )malloc( num_displays * sizeof( SDL_Rect )); + + SetBits( wndFlags, SDL_WINDOW_RESIZABLE ); + if( maximized ) + SetBits( wndFlags, SDL_WINDOW_MAXIMIZED ); + + if( !display_rects ) { - return false; + Con_Printf( S_ERROR "Failed to allocate memory for display rects!\n" ); + xpos = SDL_WINDOWPOS_UNDEFINED; + ypos = SDL_WINDOWPOS_UNDEFINED; } + else + { + for( int i = 0; i < num_displays; i++ ) + { + if( SDL_GetDisplayBounds( i, &display_rects[i] ) != 0 ) + { + Con_Printf( S_ERROR "Failed to get bounds for display %d! SDL_Error: %s\n", i, SDL_GetError()); + display_rects[i] = ( SDL_Rect ){ 0, 0, 0, 0 }; + } + } + // Check if the rectangle fits in any display + if( !RectFitsInAnyDisplay( &rect, display_rects, num_displays )) + { + // Rectangle doesn't fit in any display, center it + xpos = SDL_WINDOWPOS_CENTERED; + ypos = SDL_WINDOWPOS_CENTERED; + Con_Printf( S_ERROR "Rectangle does not fit in any display. Centering window.\n" ); + } + else + { + xpos = rect.x; + ypos = rect.y; + } + } + free( display_rects ); } else { - VID_RestoreScreenResolution(); - } - -#if XASH_WIN32 // ICO support only for Win32 - if(( localIcoPath = FS_GetDiskPath( GI->iconpath, true ))) - { - HICON ico; - - ico = (HICON)LoadImage( NULL, localIcoPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); - - if( ico ) + if( window_mode == WINDOW_MODE_FULLSCREEN ) + // need input grab only in true fullscreen mode + SetBits( wndFlags, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_INPUT_GRABBED ); + else + SetBits( wndFlags, SDL_WINDOW_FULLSCREEN_DESKTOP ); + SetBits( wndFlags, SDL_WINDOW_BORDERLESS ); + if ( window_xpos.value < 0 || window_ypos.value < 0 ) { - iconLoaded = true; - WIN_SetWindowIcon( ico ); + xpos = SDL_WINDOWPOS_UNDEFINED; + ypos = SDL_WINDOWPOS_UNDEFINED; } - } -#endif // _WIN32 && !XASH_64BIT - - if( !iconLoaded ) - { - Q_strcpy( iconpath, GI->iconpath ); - COM_StripExtension( iconpath ); - COM_DefaultExtension( iconpath, ".tga" ); - - icon = FS_LoadImage( iconpath, NULL, 0 ); - - if( icon ) + else { - SDL_Surface *surface = SDL_CreateRGBSurfaceFrom( icon->buffer, - icon->width, icon->height, 32, 4 * icon->width, - 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); - - if( surface ) - { - SDL_SetWindowIcon( host.hWnd, surface ); - SDL_FreeSurface( surface ); - iconLoaded = true; - } - - FS_FreeImage( icon ); + xpos = window_xpos.value; + ypos = window_ypos.value; } } -#if XASH_WIN32 // ICO support only for Win32 - if( !iconLoaded ) - { - WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ) ) ); - iconLoaded = true; - } -#endif + if( !VID_CreateWindowWithSafeGL( wndname, xpos, ypos, width, height, wndFlags )) + return false; + // update window size if it was maximized, just in case + if( FBitSet( SDL_GetWindowFlags( host.hWnd ), SDL_WINDOW_MAXIMIZED|SDL_WINDOW_FULLSCREEN_DESKTOP ) != 0 ) + SDL_GetWindowSize( host.hWnd, &width, &height ); + + if( window_mode != WINDOW_MODE_WINDOWED ) + { + if( !VID_SetScreenResolution( width, height, window_mode )) + return false; + } + else VID_RestoreScreenResolution(); + + VID_SetWindowIcon( host.hWnd ); SDL_ShowWindow( host.hWnd ); if( glw_state.software ) @@ -764,64 +815,20 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) { if( !glw_state.initialized ) { - if( !GL_CreateContext( )) - return false; - - VID_StartupGamma(); + while( !GL_CreateContext( )) + { + glw_state.safe++; + if(glw_state.safe > SAFE_DONTCARE) + return false; + GL_SetupAttributes(); // re-choose attributes + } } if( !GL_UpdateContext( )) - return false; - + return false; } -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - Uint32 flags = 0; - - if( fullscreen ) - { - flags |= SDL_FULLSCREEN|SDL_HWSURFACE; - } - - if( glw_state.software ) - { - // flags |= SDL_ASYNCBLIT; - } - else - { - flags |= SDL_OPENGL; - } - - while( glw_state.safe >= SAFE_NO && glw_state.safe < SAFE_LAST ) - { - host.hWnd = sw.surf = SDL_SetVideoMode( width, height, 16, flags ); - - // we have window, exit loop - if( host.hWnd ) - break; - - Con_Reportf( S_ERROR "VID_CreateWindow: couldn't create '%s' with safegl level %d: %s\n", wndname, glw_state.safe, SDL_GetError()); - - glw_state.safe++; - - if( !gl_msaa_samples->value && glw_state.safe == SAFE_NOMSAA ) - glw_state.safe++; // no need to skip msaa, if we already disabled it - - GL_SetupAttributes(); // re-choose attributes - - // try again create window - } - - // window creation has failed... - if( glw_state.safe >= SAFE_LAST ) - { - return false; - } - - -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - - VID_SaveWindowSize( width, height ); + VID_SaveWindowSize( width, height, maximized ); return true; } @@ -838,9 +845,7 @@ void VID_DestroyWindow( void ) VID_RestoreScreenResolution(); if( host.hWnd ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DestroyWindow ( host.hWnd ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) host.hWnd = NULL; } @@ -857,20 +862,14 @@ GL_SetupAttributes */ static void GL_SetupAttributes( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_GL_ResetAttributes(); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) ref.dllFuncs.GL_SetupAttributes( glw_state.safe ); } void GL_SwapBuffers( void ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_GL_SwapWindow( host.hWnd ); -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_Flip( host.hWnd ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } int GL_SetAttribute( int attr, int val ) @@ -888,7 +887,6 @@ int GL_SetAttribute( int attr, int val ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL ); @@ -896,15 +894,16 @@ int GL_SetAttribute( int attr, int val ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE ); case REF_GL_CONTEXT_PROFILE_MASK: +#if !XASH_WIN32 #ifdef SDL_HINT_OPENGL_ES_DRIVER if( val == REF_GL_CONTEXT_PROFILE_ES ) { - SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1"); + SDL_SetHint( SDL_HINT_OPENGL_ES_DRIVER, "1" ); SDL_SetHint( "SDL_VIDEO_X11_FORCE_EGL", "1" ); } #endif // SDL_HINT_OPENGL_ES_DRIVER - return SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, val ); #endif + return SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, val ); #if SDL_VERSION_ATLEAST( 2, 0, 4 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR ); #endif @@ -933,7 +932,6 @@ int GL_GetAttribute( int attr, int *val ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLEBUFFERS ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_MULTISAMPLESAMPLES ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_ACCELERATED_VISUAL ); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MAJOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_MINOR_VERSION ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_EGL ); @@ -941,7 +939,6 @@ int GL_GetAttribute( int attr, int *val ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_SHARE_WITH_CURRENT_CONTEXT ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_FRAMEBUFFER_SRGB_CAPABLE ); MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_PROFILE_MASK ); -#endif #if SDL_VERSION_ATLEAST( 2, 0, 4 ) MAP_REF_API_ATTRIBUTE_TO_SDL( GL_CONTEXT_RELEASE_BEHAVIOR ); #endif @@ -968,20 +965,33 @@ qboolean R_Init_Video( const int type ) { string safe; qboolean retval; -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode displayMode; - SDL_GetCurrentDisplayMode(0, &displayMode); - refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); +#if SDL_VERSION_ATLEAST( 2, 24, 0 ) + SDL_Point point = { window_xpos.value, window_ypos.value }; + int displayIndex = SDL_GetPointDisplayIndex( &point ); #else - refState.desktopBitsPixel = 16; + int displayIndex = 0; +#endif + SDL_GetCurrentDisplayMode( displayIndex, &displayMode ); + + refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); + +#ifdef SDL_HINT_QTWAYLAND_WINDOW_FLAGS + SDL_SetHint( SDL_HINT_QTWAYLAND_WINDOW_FLAGS, "OverridesSystemGestures" ); +#endif +#ifdef SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION + SDL_SetHint( SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION, "landscape" ); #endif -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) && !XASH_WIN32 + if( Sys_CheckParm( "-egl" ) ) +#if XASH_WIN32 + SDL_SetHint( "SDL_OPENGL_ES_DRIVER", "1" ); +#else + SDL_SetHint( "SDL_VIDEO_X11_FORCE_EGL", "1" ); + SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "1" ); SDL_SetHint( "SDL_VIDEO_X11_XVIDMODE", "1" ); - if( Sys_CheckParm( "-egl" ) ) - SDL_SetHint( "SDL_VIDEO_X11_FORCE_EGL", "1" ); -#endif +#endif // !XASH_WIN32 // must be initialized before creating window #if XASH_WIN32 @@ -1000,7 +1010,7 @@ qboolean R_Init_Video( const int type ) // refdll can request some attributes GL_SetupAttributes( ); - if( SDL_GL_LoadLibrary( EGL_LIB ) ) + if( SDL_GL_LoadLibrary( EGL_LIB ) < 0 ) { Con_Reportf( S_ERROR "Couldn't initialize OpenGL: %s\n", SDL_GetError()); return false; @@ -1034,45 +1044,53 @@ qboolean R_Init_Video( const int type ) return true; } -rserr_t R_ChangeDisplaySettings( int width, int height, qboolean fullscreen ) +rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mode ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode displayMode; - SDL_GetCurrentDisplayMode( 0, &displayMode ); + if( SDL_GetCurrentDisplayMode( 0, &displayMode ) < 0 ) + { + Con_Printf( S_ERROR "SDL_GetCurrentDisplayMode: %s\n", SDL_GetError( )); + return rserr_invalid_mode; + } // check our desktop attributes refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); -#endif + if( window_mode == WINDOW_MODE_BORDERLESS ) + { + width = displayMode.w; + height = displayMode.h; + } - Con_Reportf( "R_ChangeDisplaySettings: Setting video mode to %dx%d %s\n", width, height, fullscreen ? "fullscreen" : "windowed" ); - - refState.fullScreen = fullscreen; + refState.fullScreen = window_mode != WINDOW_MODE_WINDOWED; + Con_Reportf( "%s: Setting video mode to %dx%d %s\n", __func__, width, height, refState.fullScreen ? "fullscreen" : "windowed" ); if( !host.hWnd ) { - if( !VID_CreateWindow( width, height, fullscreen ) ) + if( !VID_CreateWindow( width, height, window_mode )) return rserr_invalid_mode; } - else if( fullscreen ) + else if( refState.fullScreen ) { - if( !VID_SetScreenResolution( width, height ) ) + if( !VID_SetScreenResolution( width, height, window_mode )) return rserr_invalid_fullscreen; } else { VID_RestoreScreenResolution(); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( SDL_SetWindowFullscreen( host.hWnd, 0 ) ) + + if( SDL_SetWindowFullscreen( host.hWnd, 0 ) < 0 ) + { + Con_Printf( S_ERROR "SDL_SetWindowFullscreen: %s", SDL_GetError( )); return rserr_invalid_fullscreen; + } #if SDL_VERSION_ATLEAST( 2, 0, 5 ) SDL_SetWindowResizable( host.hWnd, SDL_TRUE ); #endif SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); SDL_SetWindowSize( host.hWnd, width, height ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - VID_SaveWindowSize( width, height ); + VID_SaveWindowSize( width, height, true ); } return rserr_ok; @@ -1087,9 +1105,9 @@ Set the described video mode */ qboolean VID_SetMode( void ) { - qboolean fullscreen = false; int iScreenWidth, iScreenHeight; rserr_t err; + window_mode_t window_mode; iScreenWidth = Cvar_VariableInteger( "width" ); iScreenHeight = Cvar_VariableInteger( "height" ); @@ -1097,7 +1115,6 @@ qboolean VID_SetMode( void ) if( iScreenWidth < VID_MIN_WIDTH || iScreenHeight < VID_MIN_HEIGHT ) // trying to get resolution automatically by default { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) #if !defined( DEFAULT_MODE_WIDTH ) || !defined( DEFAULT_MODE_HEIGHT ) SDL_DisplayMode mode; @@ -1109,21 +1126,20 @@ qboolean VID_SetMode( void ) iScreenWidth = DEFAULT_MODE_WIDTH; iScreenHeight = DEFAULT_MODE_HEIGHT; #endif -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - iScreenWidth = 320; - iScreenHeight = 240; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } - if( !FBitSet( vid_fullscreen->flags, FCVAR_CHANGED ) ) - Cvar_SetValue( "fullscreen", DEFAULT_FULLSCREEN ); - else - ClearBits( vid_fullscreen->flags, FCVAR_CHANGED ); +#if XASH_MOBILE_PLATFORM + if( Q_strcmp( vid_fullscreen.string, DEFAULT_FULLSCREEN )) + { + Cvar_DirectSet( &vid_fullscreen, DEFAULT_FULLSCREEN ); + Con_Reportf( S_ERROR "%s: windowed unavailable on this platform\n", __func__ ); + } +#endif - SetBits( gl_vsync->flags, FCVAR_CHANGED ); - fullscreen = Cvar_VariableInteger("fullscreen") != 0; + window_mode = bound( 0, vid_fullscreen.value, WINDOW_MODE_COUNT - 1 ); + SetBits( gl_vsync.flags, FCVAR_CHANGED ); - if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, fullscreen )) == rserr_ok ) + if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, window_mode )) == rserr_ok ) { sdlState.prev_width = iScreenWidth; sdlState.prev_height = iScreenHeight; @@ -1132,29 +1148,93 @@ qboolean VID_SetMode( void ) { if( err == rserr_invalid_fullscreen ) { - Cvar_SetValue( "fullscreen", 0 ); - Con_Reportf( S_ERROR "VID_SetMode: fullscreen unavailable in this mode\n" ); - Sys_Warn("fullscreen unavailable in this mode!"); - if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, false )) == rserr_ok ) + Cvar_DirectSet( &vid_fullscreen, "0" ); + Con_Reportf( S_ERROR "%s: fullscreen unavailable in this mode\n", __func__ ); + Sys_Warn( "fullscreen unavailable in this mode!" ); + if(( err = R_ChangeDisplaySettings( iScreenWidth, iScreenHeight, WINDOW_MODE_WINDOWED )) == rserr_ok ) return true; } else if( err == rserr_invalid_mode ) { - Con_Reportf( S_ERROR "VID_SetMode: invalid mode\n" ); + Con_Reportf( S_ERROR "%s: invalid mode\n", __func__ ); Sys_Warn( "invalid mode, engine will run in %dx%d", sdlState.prev_width, sdlState.prev_height ); } // try setting it back to something safe - if(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, false )) != rserr_ok ) + if(( err = R_ChangeDisplaySettings( sdlState.prev_width, sdlState.prev_height, WINDOW_MODE_WINDOWED )) != rserr_ok ) { - Con_Reportf( S_ERROR "VID_SetMode: could not revert to safe mode\n" ); - Sys_Warn("could not revert to safe mode!"); + Con_Reportf( S_ERROR "%s: could not revert to safe mode\n", __func__ ); + Sys_Warn( "could not revert to safe mode!" ); return false; } } + return true; } +ref_window_type_t R_GetWindowHandle( void **handle, ref_window_type_t type ) +{ + SDL_SysWMinfo wmInfo; + + if( type == REF_WINDOW_TYPE_SDL ) + { + if( handle ) + *handle = (void *)host.hWnd; + return REF_WINDOW_TYPE_SDL; + } + + SDL_VERSION( &wmInfo.version ); + + if( SDL_GetWindowWMInfo( host.hWnd, &wmInfo )) + return REF_WINDOW_TYPE_NULL; + + switch( wmInfo.subsystem ) + { + case SDL_SYSWM_WINDOWS: + if( !type || type == REF_WINDOW_TYPE_WIN32 ) + { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + if( handle ) + *handle = (void *)wmInfo.info.win.window; + return REF_WINDOW_TYPE_WIN32; +#endif // SDL_VIDEO_DRIVER_WINDOWS + } + break; + case SDL_SYSWM_X11: + if( !type || type == REF_WINDOW_TYPE_X11 ) + { +#ifdef SDL_VIDEO_DRIVER_X11 + if( handle ) + *handle = (void *)(uintptr_t)wmInfo.info.x11.window; + return REF_WINDOW_TYPE_X11; +#endif // SDL_VIDEO_DRIVER_X11 + } + break; + case SDL_SYSWM_COCOA: + if( !type || type == REF_WINDOW_TYPE_MACOS ) + { +#ifdef SDL_VIDEO_DRIVER_COCOA + if( handle ) + *handle = (void *)wmInfo.info.cocoa.window; + return REF_WINDOW_TYPE_MACOS; +#endif // SDL_VIDEO_DRIVER_COCOA + } + break; + case SDL_SYSWM_WAYLAND: + if( !type || type == REF_WINDOW_TYPE_WAYLAND ) + { +#ifdef SDL_VIDEO_DRIVER_WAYLAND + if( handle ) + *handle = (void *)wmInfo.info.wl.surface; + return REF_WINDOW_TYPE_WAYLAND; +#endif // SDL_VIDEO_DRIVER_WAYLAND + } + break; + } + + return REF_WINDOW_TYPE_NULL; +} + /* ================== R_Free_Video @@ -1169,10 +1249,4 @@ void R_Free_Video( void ) R_FreeVideoModes(); ref.dllFuncs.GL_ClearExtensions(); - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_VideoQuit(); -#endif } - -#endif // XASH_DEDICATED diff --git a/engine/platform/sdl3/in_sdl3.c b/engine/platform/sdl3/in_sdl3.c new file mode 100644 index 00000000..2efa61d7 --- /dev/null +++ b/engine/platform/sdl3/in_sdl3.c @@ -0,0 +1,215 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "platform_sdl3.h" +#include "eiface.h" // ARRAYSIZE +#include "vid_common.h" // window_{width,height} +#include "client.h" // refState +#include "input.h" // Touch_WantVisibleCursor +#include "vgui_draw.h" // VGui_UpdateInternalCursorState + +static struct +{ + qboolean initialized; + SDL_Cursor *cursors[dc_last]; +} cursors; + +static struct +{ + float x, y; + qboolean pushed; +} in_visible_cursor_pos; + +void SDLash_InitCursors( void ) +{ + if( cursors.initialized ) + SDLash_FreeCursors(); + + // load up all default cursors + cursors.cursors[dc_none] = NULL; + cursors.cursors[dc_arrow] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT ); + cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_TEXT ); + cursors.cursors[dc_hourglass] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_WAIT ); + cursors.cursors[dc_crosshair] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_CROSSHAIR ); + cursors.cursors[dc_up] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT ); + cursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NWSE_RESIZE ); + cursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NESW_RESIZE ); + cursors.cursors[dc_sizewe] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_EW_RESIZE ); + cursors.cursors[dc_sizens] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NS_RESIZE ); + cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_MOVE ); + cursors.cursors[dc_no] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NOT_ALLOWED ); + cursors.cursors[dc_hand] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_POINTER ); + cursors.initialized = true; +} + +void SDLash_FreeCursors( void ) +{ + if( !cursors.initialized ) + return; + + for( int i = 0; i < ARRAYSIZE( cursors.cursors ); i++ ) + { + if( cursors.cursors[i] ) + { + SDL_DestroyCursor( cursors.cursors[i] ); + cursors.cursors[i] = NULL; + } + } + + cursors.initialized = false; +} + +qboolean Platform_GetMouseGrab( void ) +{ + return SDL_GetWindowMouseGrab( host.hWnd ); +} + +void Platform_SetMouseGrab( qboolean enable ) +{ + SDL_SetWindowMouseGrab( host.hWnd, enable ); +} + +void Platform_MouseMove( float *x, float *y ) +{ + SDL_GetRelativeMouseState( x, y ); +} + +void GAME_EXPORT Platform_SetMousePos( int x, int y ) +{ + SDL_WarpMouseInWindow( host.hWnd, x, y ); +} + +void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) +{ + float m_x = 0.0f, m_y = 0.0f; + + SDL_GetMouseState( &m_x, &m_y ); + + if( x ) + { + if( window_width.value && window_width.value != refState.width ) + { + float factor = refState.width / window_width.value; + *x = m_x * factor; + } + else *x = m_x; + } + + if( y ) + { + if( window_height.value && window_height.value != refState.height ) + { + float factor = refState.height / window_height.value; + *y = m_y * factor; + } + else *y = m_y; + } +} + +void Platform_SetCursorType( VGUI_DefaultCursor type ) +{ + qboolean visible = type != dc_user && type != dc_none; + + // never disable cursor in touch emulation mode + if( !visible && Touch_WantVisibleCursor( )) + return; + + host.mouse_visible = visible; + VGui_UpdateInternalCursorState( type ); + + if( visible ) + { + if( cursors.initialized ) + SDL_SetCursor( cursors.cursors[type] ); + + SDL_ShowCursor(); + + // restore the last mouse position + if( in_visible_cursor_pos.pushed ) + { + SDL_WarpMouseInWindow( host.hWnd, in_visible_cursor_pos.x, in_visible_cursor_pos.y ); + in_visible_cursor_pos.pushed = false; + } + } + else + { + // save last mouse position and warp it to the center + if( !in_visible_cursor_pos.pushed ) + { + SDL_GetMouseState( &in_visible_cursor_pos.x, &in_visible_cursor_pos.y ); + SDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y ); + in_visible_cursor_pos.pushed = true; + } + + SDL_HideCursor(); + } +} + +void Platform_EnableTextInput( qboolean enable ) +{ + enable ? SDL_StartTextInput( host.hWnd ) : SDL_StopTextInput( host.hWnd ); +} + +int Platform_GetClipboardText( char *buffer, size_t size ) +{ + int len; + char *text = SDL_GetClipboardText(); + + if( !text ) + return 0; + + if( buffer && size > 0 ) + len = Q_strncpy( buffer, text, size ); + else + len = Q_strlen( text ); + + SDL_free( text ); + + return len; +} + +void Platform_SetClipboardText( const char *buffer ) +{ + SDL_SetClipboardText( buffer ); +} + +key_modifier_t Platform_GetKeyModifiers( void ) +{ + SDL_Keymod mod_flags = SDL_GetModState(); + key_modifier_t result_flags = KeyModifier_None; + + if( FBitSet( mod_flags, SDL_KMOD_LCTRL )) + SetBits( result_flags, KeyModifier_LeftCtrl ); + if( FBitSet( mod_flags, SDL_KMOD_RCTRL )) + SetBits( result_flags, KeyModifier_RightCtrl ); + if( FBitSet( mod_flags, SDL_KMOD_RSHIFT )) + SetBits( result_flags, KeyModifier_RightShift ); + if( FBitSet( mod_flags, SDL_KMOD_LSHIFT )) + SetBits( result_flags, KeyModifier_LeftShift ); + if( FBitSet( mod_flags, SDL_KMOD_LALT )) + SetBits( result_flags, KeyModifier_LeftAlt ); + if( FBitSet( mod_flags, SDL_KMOD_RALT )) + SetBits( result_flags, KeyModifier_RightAlt ); + if( FBitSet( mod_flags, SDL_KMOD_NUM )) + SetBits( result_flags, KeyModifier_NumLock ); + if( FBitSet( mod_flags, SDL_KMOD_CAPS )) + SetBits( result_flags, KeyModifier_CapsLock ); + if( FBitSet( mod_flags, SDL_KMOD_RGUI )) + SetBits( result_flags, KeyModifier_RightSuper ); + if( FBitSet( mod_flags, SDL_KMOD_LGUI )) + SetBits( result_flags, KeyModifier_LeftSuper ); + + return result_flags; +} diff --git a/engine/platform/sdl3/platform_sdl3.h b/engine/platform/sdl3/platform_sdl3.h new file mode 100644 index 00000000..f578cba2 --- /dev/null +++ b/engine/platform/sdl3/platform_sdl3.h @@ -0,0 +1,28 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef PLATFORM_SDL3_H +#define PLATFORM_SDL3_H + +#include +#include "platform/platform.h" + +// +// in_sdl3.c +// +void SDLash_InitCursors( void ); +void SDLash_FreeCursors( void ); + +#endif // PLATFORM_SDL3_H diff --git a/engine/platform/sdl3/sys_sdl3.c b/engine/platform/sdl3/sys_sdl3.c new file mode 100644 index 00000000..6e60fcda --- /dev/null +++ b/engine/platform/sdl3/sys_sdl3.c @@ -0,0 +1,121 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "platform_sdl3.h" +#include "common.h" + +#if XASH_TIMER == TIMER_SDL +double Platform_DoubleTime( void ) +{ + static Uint64 g_PerformanceFrequency; + static Uint64 g_ClockStart; + Uint64 CurrentTime; + + if( !g_PerformanceFrequency ) + { + g_PerformanceFrequency = SDL_GetPerformanceFrequency(); + g_ClockStart = SDL_GetPerformanceCounter(); + + return 0.0; + } + + CurrentTime = SDL_GetPerformanceCounter(); + return (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency ); +} + +void Platform_Sleep( int msec ) +{ + SDL_Delay( msec ); +} +#endif // XASH_TIMER == TIMER_SDL + +#if XASH_MESSAGEBOX == MSGBOX_SDL +void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) +{ + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL ); +} +#endif + +void SDLash_NanoSleep( int nsec ) +{ + SDL_DelayNS( nsec ); +} + +static const char *SDLash_CategoryToString( int category ) +{ + switch( category ) + { + case SDL_LOG_CATEGORY_APPLICATION: return "App"; + case SDL_LOG_CATEGORY_ERROR: return "Error"; + case SDL_LOG_CATEGORY_ASSERT: return "Assert"; + case SDL_LOG_CATEGORY_SYSTEM: return "System"; + case SDL_LOG_CATEGORY_AUDIO: return "Audio"; + case SDL_LOG_CATEGORY_VIDEO: return "Video"; + case SDL_LOG_CATEGORY_RENDER: return "Render"; + case SDL_LOG_CATEGORY_INPUT: return "Input"; + case SDL_LOG_CATEGORY_TEST: return "Test"; + case SDL_LOG_CATEGORY_GPU: return "GPU"; + default: return "Unknown"; + } +} + +static void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message ) +{ + switch( priority ) + { + case SDL_LOG_PRIORITY_CRITICAL: + case SDL_LOG_PRIORITY_ERROR: + Con_Printf( S_ERROR S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + case SDL_LOG_PRIORITY_WARN: + Con_DPrintf( S_WARN S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + case SDL_LOG_PRIORITY_INFO: + Con_Reportf( S_NOTE S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + default: + Con_Reportf( S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + } +} + +void SDLash_Init( const char *basedir ) +{ + SDL_SetLogOutputFunction( SDLash_LogOutputFunction, NULL ); + + if( host_developer.value >= 2 ) + SDL_SetLogPriorities( SDL_LOG_PRIORITY_VERBOSE ); + else if( host_developer.value >= 1 ) + SDL_SetLogPriorities( SDL_LOG_PRIORITY_WARN ); + else + SDL_SetLogPriorities( SDL_LOG_PRIORITY_ERROR ); + + if( !SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS )) + { + Sys_Warn( "SDL_Init failed: %s", SDL_GetError( )); + host.type = HOST_DEDICATED; + } + + SDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, "0" ); + SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + + SDLash_InitCursors(); +} + +void SDLash_Shutdown( void ) +{ + SDLash_FreeCursors(); + SDL_Quit(); +} diff --git a/engine/platform/stub/net_stub.h b/engine/platform/stub/net_stub.h index cb555929..0c931e56 100644 --- a/engine/platform/stub/net_stub.h +++ b/engine/platform/stub/net_stub.h @@ -12,7 +12,8 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ - +#ifndef NET_STUB_H +#define NET_STUB_H #define INVALID_SOCKET (-1) #define SOCKET_ERROR (-1) @@ -92,3 +93,5 @@ struct timeval {long tv_sec;long tv_usec;}; #define WSAELOOP 34 //ELOOP #define WSAENAMETOOLONG 35 //ENAMETOOLONG #define WSAEHOSTDOWN 36 //EHOSTDOWN + +#endif // NET_STUB_H diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 4506063a..c3af611a 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -29,7 +29,10 @@ WIN32 CONSOLE typedef struct { - char title[64]; + string title; + string previousTitle; + UINT previousCodePage; + UINT previousOutputCodePage; HWND hWnd; HANDLE hInput; HANDLE hOutput; @@ -45,10 +48,7 @@ typedef struct string lineBuffer[COMMAND_HISTORY]; qboolean inputEnabled; qboolean consoleVisible; - - // log stuff - qboolean log_active; - char log_path[MAX_SYSPATH]; + qboolean attached; } WinConData; static WinConData s_wcd; @@ -129,7 +129,7 @@ static void Wcon_PrintInternal( const char *msg, int length ) void Wcon_ShowConsole( qboolean show ) { - if( !s_wcd.hWnd || show == s_wcd.consoleVisible ) + if( !s_wcd.hWnd || show == s_wcd.consoleVisible || s_wcd.attached ) return; s_wcd.consoleVisible = show; @@ -141,7 +141,7 @@ void Wcon_ShowConsole( qboolean show ) void Wcon_DisableInput( void ) { - if( host.type != HOST_DEDICATED ) + if( host.type != HOST_DEDICATED || !s_wcd.hWnd ) return; s_wcd.inputEnabled = false; @@ -465,6 +465,9 @@ print into window console */ void Wcon_WinPrint( const char *pMsg ) { + if( !s_wcd.hWnd ) + return; + int nLen; if( s_wcd.consoleTextLen ) { @@ -482,7 +485,8 @@ void Wcon_WinPrint( const char *pMsg ) Wcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen ); } - Wcon_UpdateStatusLine(); + if( !s_wcd.attached ) + Wcon_UpdateStatusLine(); } /* @@ -492,24 +496,32 @@ Con_CreateConsole create win32 console ================ */ -void Wcon_CreateConsole( void ) +void Wcon_CreateConsole( qboolean con_showalways ) { - if( Sys_CheckParm( "-log" )) - s_wcd.log_active = true; - if( host.type == HOST_NORMAL ) { - Q_strncpy( s_wcd.title, va( "Xash3D %s", XASH_VERSION ), sizeof( s_wcd.title )); - Q_strncpy( s_wcd.log_path, "engine.log", sizeof( s_wcd.log_path )); + Q_strncpy( s_wcd.title, XASH_ENGINE_NAME " " XASH_VERSION, sizeof( s_wcd.title )); } else // dedicated console { - Q_strncpy( s_wcd.title, va( "XashDS %s", XASH_VERSION ), sizeof( s_wcd.title )); - Q_strncpy( s_wcd.log_path, "dedicated.log", sizeof( s_wcd.log_path )); - s_wcd.log_active = true; // always make log + Q_strncpy( s_wcd.title, XASH_DEDICATED_SERVER_NAME " " XASH_VERSION, sizeof( s_wcd.title )); + } + + s_wcd.attached = ( AttachConsole( ATTACH_PARENT_PROCESS ) != 0 ); + if( s_wcd.attached ) + { + GetConsoleTitle( &s_wcd.previousTitle, sizeof( s_wcd.previousTitle )); + s_wcd.previousCodePage = GetConsoleCP(); + s_wcd.previousOutputCodePage = GetConsoleOutputCP(); + } + else + { + if( host.type != HOST_DEDICATED && host_developer.value == DEV_NONE ) + return; // don't initialize console in case of regular game startup, it's useless anyway + else + AllocConsole(); } - AllocConsole(); SetConsoleTitle( s_wcd.title ); SetConsoleCP( CP_UTF8 ); SetConsoleOutputCP( CP_UTF8 ); @@ -525,22 +537,25 @@ void Wcon_CreateConsole( void ) return; } - SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); + if( !s_wcd.attached ) + { + SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); - // show console if needed - if( host.con_showalways ) - { - // make console visible - ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); - UpdateWindow( s_wcd.hWnd ); - SetForegroundWindow( s_wcd.hWnd ); - SetFocus( s_wcd.hWnd ); - s_wcd.consoleVisible = true; - } - else - { - s_wcd.consoleVisible = false; - ShowWindow( s_wcd.hWnd, SW_HIDE ); + // show console if needed + if( con_showalways ) + { + // make console visible + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hWnd ); + s_wcd.consoleVisible = true; + } + else + { + s_wcd.consoleVisible = false; + ShowWindow( s_wcd.hWnd, SW_HIDE ); + } } } @@ -553,7 +568,7 @@ register console commands (dedicated only) */ void Wcon_InitConsoleCommands( void ) { - if( host.type != HOST_DEDICATED ) + if( host.type != HOST_DEDICATED || !s_wcd.hWnd ) return; Cmd_AddCommand( "clear", Wcon_Clear_f, "clear console history" ); @@ -569,21 +584,26 @@ destroy win32 console void Wcon_DestroyConsole( void ) { // last text message into console or log - Con_Reportf( "Sys_FreeLibrary: Unloading xash.dll\n" ); + Con_Reportf( "%s: Unloading xash.dll\n", __func__ ); - Sys_CloseLog(); - - if( s_wcd.hWnd ) + if( !s_wcd.attached ) { - ShowWindow( s_wcd.hWnd, SW_HIDE ); - s_wcd.hWnd = 0; + if( s_wcd.hWnd ) + { + ShowWindow( s_wcd.hWnd, SW_HIDE ); + s_wcd.hWnd = 0; + } + } + else + { + // reverts title & code page for console window that was before starting Xash3D + SetConsoleCP( s_wcd.previousCodePage ); + SetConsoleOutputCP( s_wcd.previousOutputCodePage ); + SetConsoleTitle( &s_wcd.previousTitle ); + Con_Printf( "Press Enter to continue...\n" ); } FreeConsole(); - - // place it here in case Sys_Crash working properly - if( host.hMutex ) - CloseHandle( host.hMutex ); } /* @@ -595,11 +615,11 @@ returned input text */ char *Wcon_Input( void ) { - int i; - int eventsCount; + DWORD i; + DWORD eventsCount; static INPUT_RECORD events[1024]; - if( !s_wcd.inputEnabled ) + if( !s_wcd.inputEnabled || !s_wcd.hWnd ) return NULL; while( true ) @@ -635,14 +655,14 @@ char *Wcon_Input( void ) /* ================ -Wcon_SetStatus +Platform_SetStatus set server status string in console ================ */ -void Wcon_SetStatus( const char *pStatus ) +void Platform_SetStatus( const char *pStatus ) { - if( host.type != HOST_DEDICATED ) + if( s_wcd.attached || !s_wcd.hWnd ) return; Q_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 ); diff --git a/engine/platform/win32/crash_win.c b/engine/platform/win32/crash_win.c new file mode 100644 index 00000000..7cc22906 --- /dev/null +++ b/engine/platform/win32/crash_win.c @@ -0,0 +1,339 @@ +/* +crashhandler.c - advanced crashhandler +Copyright (C) 2016 Mittorn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" +#include "input.h" + +#define DBGHELP 1 // we always enable dbghelp.dll on Windows targets + +#if XASH_SDL +#include +#endif // XASH_SDL + +#if DBGHELP +#include +#include +#include +#include + +#ifndef XASH_SDL +typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; +#endif + +static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) +{ + static HMODULE *moduleArray; + static unsigned int moduleCount; + DWORD bytesRequired; + + if( len < 3 ) + return 0; + + if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired )) + { + if( bytesRequired ) + { + LPBYTE moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); + + if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired )) + { + moduleCount = bytesRequired / sizeof( HMODULE ); + moduleArray = (HMODULE *)moduleArrayBytes; + } + } + } + + for( int i = 0; i < moduleCount; i++ ) + { + MODULEINFO info; + GetModuleInformation( process, moduleArray[i], &info, sizeof( MODULEINFO )); + + if( address > info.lpBaseOfDll && (DWORD64)address < (DWORD64)info.lpBaseOfDll + (DWORD64)info.SizeOfImage ) + return GetModuleBaseName( process, moduleArray[i], name, len ); + } + + return Q_snprintf( name, len, "???" ); +} + +static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) +{ + char message[8192]; // match *nix Sys_Crash + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + DWORD dline = 0; + DWORD options = SymGetOptions(); + CONTEXT context = *pInfo->ContextRecord; + IMAGEHLP_LINE64 line = { .SizeOfStruct = sizeof( IMAGEHLP_LINE64 ), }; + + SetBits( options, SYMOPT_DEBUG | SYMOPT_LOAD_LINES ); + SymSetOptions( options ); + + SymInitialize( process, NULL, TRUE ); + +#ifdef _M_IX86 + DWORD image = IMAGE_FILE_MACHINE_I386; + STACKFRAME64 stackframe = + { + .AddrPC.Offset = context.Eip, + .AddrPC.Mode = AddrModeFlat, + .AddrFrame.Offset = context.Ebp, + .AddrFrame.Mode = AddrModeFlat, + .AddrStack.Offset = context.Esp, + .AddrStack.Mode = AddrModeFlat, + }; +#elif _M_X64 + DWORD image = IMAGE_FILE_MACHINE_AMD64; + STACKFRAME64 stackframe = + { + .AddrPC.Offset = context.Rip, + .AddrPC.Mode = AddrModeFlat, + .AddrFrame.Offset = context.Rsp, + .AddrFrame.Mode = AddrModeFlat, + .AddrStack.Offset = context.Rsp, + .AddrStack.Mode = AddrModeFlat, + }; +#elif _M_IA64 + DWORD image = IMAGE_FILE_MACHINE_IA64; + STACKFRAME64 stackframe = + { + .AddrPC.Offset = context.StIIP, + .AddrPC.Mode = AddrModeFlat, + .AddrFrame.Offset = context.IntSp, + .AddrFrame.Mode = AddrModeFlat, + .AddrBStore.Offset = context.RsBSP, + .AddrBStore.Mode = AddrModeFlat, + .AddrStack.Offset = context.IntSp, + .AddrStack.Mode = AddrModeFlat, + }; +#elif _M_ARM + DWORD image = IMAGE_FILE_MACHINE_ARMNT; + STACKFRAME64 stackframe = + { + .AddrPC.Offset = context.Pc, + .AddrPC.Mode = AddrModeFlat, + .AddrFrame.Offset = context.R11, + .AddrFrame.Mode = AddrModeFlat, + .AddrStack.Offset = context.Sp, + .AddrStack.Mode = AddrModeFlat, + }; +#elif _M_ARM64 + DWORD image = IMAGE_FILE_MACHINE_ARM64; + STACKFRAME64 stackframe = + { + .AddrPC.Offset = context.Pc, + .AddrPC.Mode = AddrModeFlat, + .AddrFrame.Offset = context.Fp, + .AddrFrame.Mode = AddrModeFlat, + .AddrStack.Offset = context.Sp, + .AddrStack.Mode = AddrModeFlat, + }; +#elif +#error +#endif + + int len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s-%s, %s-%s)\n", + Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch()); + + len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: address %p, code %p\n", + pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); + + if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line )) + { + len += Q_snprintf( message + len, sizeof( message ) - len, "Exception: %s:%d:%d\n", + COM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline ); + } + + if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line )) + { + len += Q_snprintf( message + len, sizeof( message ) - len, "PC: %s:%d:%d\n", + COM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline ); + } + + if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line )) + { + len += Q_snprintf( message + len, sizeof( message ) - len, "Frame: %s:%d:%d\n", + COM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline ); + } + + for( size_t i = 0; i < 25; i++ ) + { + char buffer[sizeof( SYMBOL_INFO ) + MAX_SYM_NAME * sizeof( TCHAR )]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + BOOL result = StackWalk64( image, process, thread, + &stackframe, &context, NULL, + SymFunctionTableAccess64, SymGetModuleBase64, NULL ); + DWORD64 displacement = 0; + + if( !result ) + break; + + symbol->SizeOfStruct = sizeof( SYMBOL_INFO ); + symbol->MaxNameLen = MAX_SYM_NAME; + + len += Q_snprintf( message + len, sizeof( message ) - len, "%2d: %p", + i, (void*)stackframe.AddrPC.Offset ); + if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol )) + { + len += Q_snprintf( message + len, sizeof( message ) - len, " %s ", symbol->Name ); + } + + if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line )) + { + len += Q_snprintf( message + len, sizeof( message ) - len,"(%s:%d:%d) ", + COM_FileWithoutPath((char*)line.FileName ), (int)line.LineNumber, (int)dline ); + } + + len += Q_snprintf( message + len, sizeof( message ) - len, "(" ); + len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, sizeof( message ) - len ); + len += Q_snprintf( message + len, sizeof( message ) - len, ")\n" ); + } + +#if XASH_SDL == 2 + if( host.type != HOST_DEDICATED ) // let system to restart server automaticly + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, NULL ); +#endif + + Sys_PrintLog( message ); + + SymCleanup( process ); +} + +static void Sys_GetProcessName( char *processName, size_t bufferSize ) +{ + char fullpath[MAX_PATH]; + + GetModuleBaseName( GetCurrentProcess(), NULL, fullpath, sizeof( fullpath ) - 1 ); + COM_FileBase( fullpath, processName, bufferSize ); +} + +static void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName, size_t bufferSize ) +{ + time_t currentUtcTime = time( NULL ); + struct tm *currentLocalTime = localtime( ¤tUtcTime ); + + Q_snprintf( mdmpFileName, bufferSize, "%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp", + processName, + g_buildcommit, + currentLocalTime->tm_year + 1900, + currentLocalTime->tm_mon + 1, + currentLocalTime->tm_mday, + currentLocalTime->tm_hour, + currentLocalTime->tm_min, + currentLocalTime->tm_sec ); +} + +static qboolean Sys_WriteMinidump( PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType ) +{ + string processName; + string mdmpFileName; + + Sys_GetProcessName( processName, sizeof( processName )); + Sys_GetMinidumpFileName( processName, mdmpFileName, sizeof( mdmpFileName )); + + SetLastError( NOERROR ); + HANDLE fileHandle = CreateFile( mdmpFileName, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + + HRESULT errorCode = HRESULT_FROM_WIN32( GetLastError( )); + if( !SUCCEEDED( errorCode )) + { + CloseHandle( fileHandle ); + return false; + } + + MINIDUMP_EXCEPTION_INFORMATION minidumpInfo = + { + .ThreadId = GetCurrentThreadId(), + .ExceptionPointers = exceptionInfo, + .ClientPointers = FALSE + }; + + qboolean status = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), fileHandle, + minidumpType, &minidumpInfo, NULL, NULL); + + CloseHandle( fileHandle ); + return status; +} + +#endif /* DBGHELP */ + +static LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; + +static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) +{ + // save config + if( host.status != HOST_CRASHED ) + { + // check to avoid recursive call + host.status = HOST_CRASHED; + +#if !XASH_DEDICATED + IN_SetMouseGrab( false ); +#endif // XASH_SDL + +#if DBGHELP + if( Sys_CheckParm( "-minidumps" )) + { + int minidumpFlags = MiniDumpWithDataSegs | + MiniDumpWithCodeSegs | + MiniDumpWithHandleData | + MiniDumpWithFullMemory | + MiniDumpWithFullMemoryInfo | + MiniDumpWithIndirectlyReferencedMemory | + MiniDumpWithThreadInfo | + MiniDumpWithModuleHeaders; + + if( !Sys_WriteMinidump( pInfo, (MINIDUMP_TYPE)minidumpFlags )) + { + // fallback method, create minidump with minimal info in it + Sys_WriteMinidump( pInfo, MiniDumpWithDataSegs ); + } + } + + Sys_StackTrace( pInfo ); +#else + Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); +#endif + + if( host.type == HOST_NORMAL ) + CL_Crashed(); // tell client about crash + + if( host_developer.value <= 0 ) + { + // no reason to call debugger in release build - just exit + Sys_Quit( "crashed" ); + return EXCEPTION_CONTINUE_EXECUTION; + } + + // all other states keep unchanged to let debugger find bug + Sys_DestroyConsole(); + } + + if( oldFilter ) + return oldFilter( pInfo ); + return EXCEPTION_CONTINUE_EXECUTION; +} + +void Sys_SetupCrashHandler( const char *argv0 ) +{ + SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors + oldFilter = SetUnhandledExceptionFilter( Sys_Crash ); +} + +void Sys_RestoreCrashHandler( void ) +{ + // restore filter + if( oldFilter ) SetUnhandledExceptionFilter( oldFilter ); +} diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c index f4dfe4aa..cede0ac9 100644 --- a/engine/platform/win32/lib_custom_win.c +++ b/engine/platform/win32/lib_custom_win.c @@ -373,21 +373,21 @@ void *MemoryLoadLibrary( const char *name ) if( !data ) { - Q_sprintf( errorstring, "couldn't load %s", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); goto library_error; } dos_header = (PIMAGE_DOS_HEADER)data; if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) { - Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); goto library_error; } old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; if( old_header->Signature != IMAGE_NT_SIGNATURE ) { - Q_sprintf( errorstring, "%s missing PE header", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); goto library_error; } @@ -402,7 +402,7 @@ void *MemoryLoadLibrary( const char *name ) if( code == NULL ) { - Q_sprintf( errorstring, "%s can't reserve memory", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s can't reserve memory", name ); goto library_error; } @@ -436,7 +436,7 @@ void *MemoryLoadLibrary( const char *name ) // load required dlls and adjust function table of imports if( !BuildImportTable( result )) { - Q_sprintf( errorstring, "%s failed to build import table", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s failed to build import table", name ); goto library_error; } @@ -450,7 +450,7 @@ void *MemoryLoadLibrary( const char *name ) DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); if( DllEntry == 0 ) { - Q_sprintf( errorstring, "%s has no entry point", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has no entry point", name ); goto library_error; } @@ -458,7 +458,7 @@ void *MemoryLoadLibrary( const char *name ) successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); if( !successfull ) { - Q_sprintf( errorstring, "can't attach library %s", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "can't attach library %s", name ); goto library_error; } result->initialized = 1; @@ -475,4 +475,4 @@ library_error: return NULL; } -#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index 21ee1fab..f46a80de 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -18,6 +18,14 @@ GNU General Public License for more details. #if XASH_LIB == LIB_WIN32 #include "lib_win.h" +static const wchar_t *FS_PathToWideChar( const char *path ) +{ + static wchar_t pathBuffer[MAX_PATH]; + MultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH ); + return pathBuffer; +} + + static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) { int i = 0; @@ -29,7 +37,7 @@ static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) for( i = 0; i < nt_header->FileHeader.NumberOfSections; i++, sect_header++) { if( rva >= sect_header->VirtualAddress && rva < sect_header->VirtualAddress + sect_header->Misc.VirtualSize ) - break; + break; } return (rva - sect_header->VirtualAddress + sect_header->PointerToRawData); } @@ -101,55 +109,55 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) f = FS_Open( hInst->shortPath, "rb", false ); if( !f ) { - Q_sprintf( errorstring, "couldn't load %s", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", hInst->shortPath ); goto table_error; } if( FS_Read( f, &dos_header, sizeof( dos_header )) != sizeof( dos_header )) { - Q_sprintf( errorstring, "%s has corrupted EXE header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has corrupted EXE header", hInst->shortPath ); goto table_error; } if( dos_header.e_magic != IMAGE_DOS_SIGNATURE ) { - Q_sprintf( errorstring, "%s does not have a valid dll signature", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid dll signature", hInst->shortPath ); goto table_error; } if( FS_Seek( f, dos_header.e_lfanew, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s error seeking for new exe header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error seeking for new exe header", hInst->shortPath ); goto table_error; } if( FS_Read( f, &nt_signature, sizeof( nt_signature )) != sizeof( nt_signature )) { - Q_sprintf( errorstring, "%s has corrupted NT header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has corrupted NT header", hInst->shortPath ); goto table_error; } if( nt_signature != IMAGE_NT_SIGNATURE ) { - Q_sprintf( errorstring, "%s does not have a valid NT signature", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid NT signature", hInst->shortPath ); goto table_error; } if( FS_Read( f, &pe_header, sizeof( pe_header )) != sizeof( pe_header )) { - Q_sprintf( errorstring, "%s does not have a valid PE header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid PE header", hInst->shortPath ); goto table_error; } if( !pe_header.SizeOfOptionalHeader ) { - Q_sprintf( errorstring, "%s does not have an optional header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have an optional header", hInst->shortPath ); goto table_error; } if( FS_Read( f, &optional_header, sizeof( optional_header )) != sizeof( optional_header )) { - Q_sprintf( errorstring, "%s optional header probably corrupted", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s optional header probably corrupted", hInst->shortPath ); goto table_error; } @@ -159,7 +167,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) { if( FS_Read( f, §ion_header, sizeof( section_header )) != sizeof( section_header )) { - Q_sprintf( errorstring, "%s error during reading section header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading section header", hInst->shortPath ); goto table_error; } @@ -180,13 +188,13 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, exports_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid exports section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid exports section", hInst->shortPath ); goto table_error; } if( FS_Read( f, &export_directory, sizeof( export_directory )) != sizeof( export_directory )) { - Q_sprintf( errorstring, "%s does not have a valid optional header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid optional header", hInst->shortPath ); goto table_error; } @@ -194,7 +202,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( hInst->num_ordinals > MAX_LIBRARY_EXPORTS ) { - Q_sprintf( errorstring, "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); hInst->num_ordinals = 0; goto table_error; } @@ -203,7 +211,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, ordinal_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid ordinals section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid ordinals section", hInst->shortPath ); goto table_error; } @@ -211,7 +219,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, hInst->ordinals, hInst->num_ordinals * sizeof( word )) != (hInst->num_ordinals * sizeof( word ))) { - Q_sprintf( errorstring, "%s error during reading ordinals table", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading ordinals table", hInst->shortPath ); goto table_error; } @@ -219,7 +227,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, function_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid export address section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid export address section", hInst->shortPath ); goto table_error; } @@ -227,7 +235,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, hInst->funcs, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) { - Q_sprintf( errorstring, "%s error during reading export address section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading export address section", hInst->shortPath ); goto table_error; } @@ -235,7 +243,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, name_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s file does not have a valid names section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s file does not have a valid names section", hInst->shortPath ); goto table_error; } @@ -243,7 +251,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, p_Names, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) { - Q_sprintf( errorstring, "%s error during reading names table", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading names table", hInst->shortPath ); goto table_error; } @@ -264,7 +272,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( i != hInst->num_ordinals ) { - Q_sprintf( errorstring, "%s error during loading names section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during loading names section", hInst->shortPath ); goto table_error; } FS_Close( f ); @@ -289,23 +297,25 @@ table_error: if( f ) FS_Close( f ); if( p_Names ) Mem_Free( p_Names ); FreeNameFuncGlobals( hInst ); - Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + Con_Printf( S_ERROR "%s: %s\n", __func__, errorstring ); return false; } static const char *GetLastErrorAsString( void ) { + const DWORD fm_flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK; DWORD errorcode; + wchar_t wide_errormessage[256]; static string errormessage; errorcode = GetLastError(); - if ( !errorcode ) return ""; - - FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, - NULL, errorcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), - (LPSTR)&errormessage, sizeof( errormessage ), NULL ); - + if ( !errorcode ) + return ""; + + FormatMessageW( fm_flags, NULL, errorcode, 0, wide_errormessage, ARRAYSIZE( wide_errormessage ), NULL ); + Q_UTF16ToUTF8( errormessage, sizeof( errormessage ), wide_errormessage, ARRAYSIZE( wide_errormessage )); + return errormessage; } @@ -318,35 +328,35 @@ static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *dat if ( !data ) { - Con_Printf( S_ERROR "%s: couldn't load %s\n", __FUNCTION__, name ); + Con_Printf( S_ERROR "%s: couldn't load %s\n", __func__, name ); return NULL; } dosHeader = (PIMAGE_DOS_HEADER)data; if ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { - Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __FUNCTION__, name ); + Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __func__, name ); return NULL; } peHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew ); if ( peHeader->Signature != IMAGE_NT_SIGNATURE ) { - Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __FUNCTION__, name ); + Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __func__, name ); return NULL; } importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if( importDir->Size <= 0 ) { - Con_Printf( S_ERROR "%s: %s has no dependencies\n", __FUNCTION__, name ); + Con_Printf( S_ERROR "%s: %s has no dependencies\n", __func__, name ); return NULL; } *peheader = peHeader; importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) ); - return importDesc; + return importDesc; } static void ListMissingModules( dll_user_t *hInst ) @@ -354,14 +364,15 @@ static void ListMissingModules( dll_user_t *hInst ) PIMAGE_NT_HEADERS peHeader; PIMAGE_IMPORT_DESCRIPTOR importDesc; byte *data; + char buf[MAX_VA_STRING]; - if ( !hInst ) return; - - data = FS_LoadFile( hInst->dllName, NULL, false ); - if ( !data ) return; + if( !hInst || !g_fsapi.LoadFile ) return; + + data = g_fsapi.LoadFile( hInst->dllName, NULL, false ); + if( !data ) return; importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); - if ( !importDesc ) + if( !importDesc ) { Mem_Free( data ); return; @@ -372,9 +383,12 @@ static void ListMissingModules( dll_user_t *hInst ) HMODULE hMod; const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); - hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); + hMod = LoadLibraryExW( FS_PathToWideChar( importName ), NULL, LOAD_LIBRARY_AS_DATAFILE ); if ( !hMod ) - COM_PushLibraryError( va( "%s not found!", importName ) ); + { + Q_snprintf( buf, sizeof( buf ), "%s not found!", importName ); + COM_PushLibraryError( buf ); + } else FreeLibrary( hMod ); } @@ -406,9 +420,9 @@ qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname { COM_FreeLibrary( hInst ); Mem_Free( data ); - return FALSE; + return FALSE; } - + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) { const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); @@ -436,19 +450,22 @@ smart dll loader - can loading dlls from pack or wad files void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ) { dll_user_t *hInst; + char buf[MAX_VA_STRING]; COM_ResetLibraryError(); hInst = FS_FindLibrary( dllname, directpath ); if( !hInst ) { - COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to find library %s", dllname ); + COM_PushLibraryError( buf ); return NULL; } if( hInst->encrypted ) { - COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Library %s is encrypted, cannot load", hInst->shortPath ); + COM_PushLibraryError( buf ); COM_FreeLibrary( hInst ); return NULL; } @@ -461,7 +478,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d else #endif { - hInst->hInstance = LoadLibrary( hInst->fullPath ); + hInst->hInstance = LoadLibraryW( FS_PathToWideChar( hInst->fullPath )); } if( !hInst->hInstance ) @@ -480,7 +497,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to load library %s", dllname ); + COM_PushLibraryError( buf ); COM_FreeLibrary( hInst ); return NULL; } @@ -513,10 +531,10 @@ void COM_FreeLibrary( void *hInstance ) if( host.status == HOST_CRASHED ) { // we need to hold down all modules, while MSVC can find error - Con_Reportf( "Sys_FreeLibrary: hold %s for debugging\n", hInst->dllName ); + Con_Reportf( "%s: hold %s for debugging\n", __func__, hInst->dllName ); return; } - else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", hInst->dllName ); + else Con_Reportf( "%s: Unloading %s\n", __func__, hInst->dllName ); #if XASH_X86 if( hInst->custom_loader ) diff --git a/engine/platform/win32/lib_win.h b/engine/platform/win32/lib_win.h index 429dd4f8..1761b5cd 100644 --- a/engine/platform/win32/lib_win.h +++ b/engine/platform/win32/lib_win.h @@ -16,9 +16,10 @@ GNU General Public License for more details. #include "library.h" #include #include +#include STDINT_H -#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) +#define CALCULATE_ADDRESS( base, offset ) ((uint8_t *)( base ) + (uintptr_t)( offset )) FARPROC MemoryGetProcAddress( void *module, const char *name ); void MemoryFreeLibrary( void *hInstance ); -void *MemoryLoadLibrary( const char *name ); \ No newline at end of file +void *MemoryLoadLibrary( const char *name ); diff --git a/common/boneinfo.h b/engine/platform/win32/net.h similarity index 68% rename from common/boneinfo.h rename to engine/platform/win32/net.h index 1c88d7e1..465db08a 100644 --- a/common/boneinfo.h +++ b/engine/platform/win32/net.h @@ -1,6 +1,6 @@ /* -boneinfo.h - structure that send delta-compressed bones across network -Copyright (C) 2018 Uncle Mike +net.h - WinSock to BSD sockets wrap +Copyright (C) 2022 a1batross This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -12,14 +12,12 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#ifndef NET_H +#define NET_H -#ifndef BONEINFO_H -#define BONEINFO_H +#include +typedef int WSAsize_t; -typedef struct -{ - vec3_t angles; - vec3_t origin; -} boneinfo_t; +#define HAVE_GETADDRINFO -#endif//BONEINFO_H +#endif // NET_H diff --git a/engine/platform/win32/sys_win.c b/engine/platform/win32/sys_win.c index 8af51fd2..5eeaaa0a 100644 --- a/engine/platform/win32/sys_win.c +++ b/engine/platform/win32/sys_win.c @@ -18,6 +18,8 @@ GNU General Public License for more details. #include "server.h" #include +HANDLE g_waitable_timer; + #if XASH_TIMER == TIMER_WIN32 double Platform_DoubleTime( void ) { @@ -41,7 +43,68 @@ void Platform_Sleep( int msec ) } #endif // XASH_TIMER == TIMER_WIN32 -qboolean Sys_DebuggerPresent( void ) +void Win32_Init( qboolean con_showalways ) +{ + HMODULE hModule = LoadLibraryW( L"kernel32.dll" ); + if( hModule ) + { + HANDLE ( __stdcall *pfnCreateWaitableTimerExW)( LPSECURITY_ATTRIBUTES lpTimerAttributes, LPCWSTR lpTimerName, DWORD dwFlags, DWORD dwDesiredAccess ); + + if(( pfnCreateWaitableTimerExW = (void *)GetProcAddress( hModule, "CreateWaitableTimerExW" ))) + { + g_waitable_timer = pfnCreateWaitableTimerExW( + NULL, + NULL, + 0x1 /* CREATE_WAITABLE_TIMER_MANUAL_RESET */ | 0x2 /* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION */, + 0x0002 /* TIMER_MODIFY_STATE */ | SYNCHRONIZE | DELETE + ); + } + + FreeLibrary( hModule ); + } + +#if 0 // FIXME: creates object but doesn't wait for specific time for me on Windows 10, with the code above commented + if( !g_waitable_timer ) + g_waitable_timer = CreateWaitableTimer( NULL, TRUE, NULL ); +#endif + + Wcon_CreateConsole( con_showalways ); +} + +void Win32_Shutdown( void ) +{ + Wcon_DestroyConsole( ); + + if( g_waitable_timer ) + { + CloseHandle( g_waitable_timer ); + g_waitable_timer = 0; + } +} + +qboolean Win32_NanoSleep( int nsec ) +{ + LARGE_INTEGER ts; + + if( !g_waitable_timer ) + return false; + + ts.QuadPart = -nsec / 100; + + if( !SetWaitableTimer( g_waitable_timer, &ts, 0, NULL, NULL, FALSE )) + { + CloseHandle( g_waitable_timer ); + g_waitable_timer = 0; + return false; + } + + if( WaitForSingleObject( g_waitable_timer, Q_max( 1, nsec / 1000000 )) != WAIT_OBJECT_0 ) + return false; + + return true; +} + +qboolean Platform_DebuggerPresent( void ) { return IsDebuggerPresent(); } @@ -51,46 +114,13 @@ void Platform_ShellExecute( const char *path, const char *parms ) if( !Q_strcmp( path, GENERIC_UPDATE_PAGE ) || !Q_strcmp( path, PLATFORM_UPDATE_PAGE )) path = DEFAULT_UPDATE_PAGE; - ShellExecute( NULL, "open", path, parms, NULL, SW_SHOW ); -} - -void Platform_UpdateStatusLine( void ) -{ - int clientsCount; - char szStatus[128]; - static double lastTime; - - if( host.type != HOST_DEDICATED ) - return; - - // update only every 1/2 seconds - if(( sv.time - lastTime ) < 0.5f ) - return; - - clientsCount = SV_GetConnectedClientsCount( NULL ); - Q_snprintf( szStatus, sizeof( szStatus ) - 1, "%.1f fps %2i/%2i on %16s", 1.f / sv.frametime, clientsCount, svs.maxclients, host.game.levelName ); -#ifdef XASH_WIN32 - Wcon_SetStatus( szStatus ); -#endif - lastTime = sv.time; + ShellExecuteA( NULL, "open", path, parms, NULL, SW_SHOW ); } #if XASH_MESSAGEBOX == MSGBOX_WIN32 void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) { - MessageBox( parentMainWindow ? host.hWnd : NULL, message, title, MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ); + MessageBoxA( parentMainWindow ? host.hWnd : NULL, message, title, MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ); } #endif // XASH_MESSAGEBOX == MSGBOX_WIN32 -#ifndef XASH_SDL - -void Platform_Init( void ) -{ - Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors - -} -void Platform_Shutdown( void ) -{ - Wcon_DestroyConsole(); -} -#endif diff --git a/engine/ref_api.h b/engine/ref_api.h index c5215541..4c6610d2 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -28,23 +28,46 @@ GNU General Public License for more details. #include "r_efx.h" #include "com_image.h" #include "filesystem.h" +#include "common/protocol.h" // RefAPI changelog: // 1. Initial release // 2. FS functions are removed, instead we have full fs_api_t -#define REF_API_VERSION 2 +// 3. SlerpBones, CalcBonePosition/Quaternion calls were moved to libpublic/mathlib +// 4. R_StudioEstimateFrame now has time argument +// 5. Removed GetSomethingByIndex calls, renderers are supposed to cache pointer values +// Removed previously unused calls +// Simplified remapping calls +// GetRefAPI is now expected to return REF_API_VERSION +// 6. Removed timing from ref_globals_t. +// Renderers are supposed to migrate to ref_client_t/ref_host_t using PARM_GET_CLIENT_PTR and PARM_GET_HOST_PTR +// Removed functions to get internal engine structions. Use PARM_GET_*_PTR instead. +// 7. Gamma fixes. +// 8. Moved common code to engine. +// Removed REF_{SOLID,ALPHA}SKY_TEXTURE. Replaced R_InitSkyClouds by R_SetSkyCloudsTextures. +// Skybox loading is now done at engine side. +// R_SetupSky callback accepts a pointer to an array of 6 integers representing box side textures. +// Restored texture replacement from old Xash3D. +// PARM_SKY_SPHERE and PARM_SURF_SAMPLESIZE are now handled at engine side. +// VGUI rendering code is mostly moved back to engine. +// Implemented texture replacement. +// 9. Removed gamma functions. Renderer is supposed to get them through PARM_GET_*_PTR. +// Move hulls rendering back to engine +// Removed lightstyle, dynamic and entity light functions. Renderer is supposed to get them through PARM_GET_*_PTR. +// CL_RunLightStyles now accepts lightstyles array. +// Removed R_DrawTileClear and Mod_LoadMapSprite, as they're implemented on engine side +// Removed FillRGBABlend. Now FillRGBA accepts rendermode parameter. +// 10. Added R_GetWindowHandle to retrieve platform-specific window object. +#define REF_API_VERSION 10 - -#define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) -#define TF_FONT (TF_NOMIPMAP|TF_CLAMP) +#define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP|TF_ALLOW_NEAREST) +#define TF_FONT (TF_NOMIPMAP|TF_CLAMP|TF_ALLOW_NEAREST) #define TF_IMAGE (TF_NOMIPMAP|TF_CLAMP) #define TF_DECAL (TF_CLAMP) #define FCONTEXT_CORE_PROFILE BIT( 0 ) #define FCONTEXT_DEBUG_ARB BIT( 1 ) -#define FCVAR_READ_ONLY (1<<17) // cannot be set by user at all, and can't be requested by CvarGetPointer from game dlls - // screenshot types #define VID_SCREENSHOT 0 #define VID_LEVELSHOT 1 @@ -58,6 +81,7 @@ GNU General Public License for more details. #define MODEL_LIQUID BIT( 2 ) // model has only point hull #define MODEL_TRANSPARENT BIT( 3 ) // have transparent surfaces #define MODEL_COLORED_LIGHTING BIT( 4 ) // lightmaps stored as RGB + #define MODEL_WORLD BIT( 29 ) // it's a worldmodel #define MODEL_CLIENT BIT( 30 ) // client sprite @@ -67,6 +91,12 @@ GNU General Public License for more details. #define FWORLD_WATERALPHA BIT( 2 ) #define FWORLD_HAS_DELUXEMAP BIT( 3 ) +// special rendermode for screenfade modulate +// (probably will be expanded at some point) +#define kRenderScreenFadeModulate 0x1000 + +#define SKYBOX_MAX_SIDES 6 // a box can only have 6 sides + typedef enum { DEMO_INACTIVE = 0, @@ -74,6 +104,16 @@ typedef enum DEMO_QUAKE1 } demo_mode; +typedef enum ref_window_type_e +{ + REF_WINDOW_TYPE_NULL = 0, + REF_WINDOW_TYPE_WIN32, // HWND + REF_WINDOW_TYPE_X11, // Display* + REF_WINDOW_TYPE_WAYLAND, // wl_display* + REF_WINDOW_TYPE_MACOS, // NSWindow* + REF_WINDOW_TYPE_SDL, // SDL_Window* +} ref_window_type_t; + typedef struct { msurface_t *surf; @@ -84,11 +124,6 @@ typedef struct ref_globals_s { qboolean developer; - float time; // cl.time - float oldtime; // cl.oldtime - double realtime; // host.realtime - double frametime; // host.frametime - // viewport width and height int width; int height; @@ -98,7 +133,6 @@ typedef struct ref_globals_s vec3_t vieworg; vec3_t viewangles; - vec3_t vforward, vright, vup; // todo: fill this without engine help // move to local @@ -111,6 +145,26 @@ typedef struct ref_globals_s int desktopBitsPixel; } ref_globals_t; +typedef struct ref_client_s +{ + double time; + double oldtime; + int viewentity; + int playernum; + int maxclients; + int nummodels; + model_t *models[MAX_MODELS+1]; + qboolean paused; + vec3_t simorg; +} ref_client_t; + +typedef struct ref_host_s +{ + double realtime; + double frametime; + int features; +} ref_host_t; + enum { GL_KEEP_UNIT = -1, @@ -118,7 +172,6 @@ enum XASH_TEXTURE1, XASH_TEXTURE2, XASH_TEXTURE3, // g-cont. 4 units should be enough - XASH_TEXTURE4, // mittorn. bump+detail needs 5 for single-pass MAX_TEXTURE_UNITS = 32 // can't access to all over units without GLSL or cg }; @@ -133,8 +186,6 @@ enum // r_speeds counters #define REF_WHITE_TEXTURE "*white" #define REF_BLACK_TEXTURE "*black" #define REF_PARTICLE_TEXTURE "*particle" -#define REF_SOLIDSKY_TEXTURE "solid_sky" -#define REF_ALPHASKY_TEXTURE "alpha_sky" typedef enum connstate_e { @@ -233,6 +284,7 @@ typedef struct remap_info_s model_t *model; // for catch model changes } remap_info_t; +typedef struct convar_s convar_t; struct con_nprint_s; struct engine_studio_api_s; struct r_studio_interface_s; @@ -242,16 +294,33 @@ typedef enum PARM_DEV_OVERVIEW = -1, PARM_THIRDPERSON = -2, PARM_QUAKE_COMPATIBLE = -3, - PARM_PLAYER_INDEX = -4, // cl.playernum + 1 - PARM_VIEWENT_INDEX = -5, // cl.viewentity + PARM_GET_CLIENT_PTR = -4, // ref_client_t + PARM_GET_HOST_PTR = -5, // ref_host_t PARM_CONNSTATE = -6, // cls.state PARM_PLAYING_DEMO = -7, // cls.demoplayback PARM_WATER_LEVEL = -8, // cl.local.water_level - PARM_MAX_CLIENTS = -9, // cl.maxclients + PARM_GET_WORLD_PTR = -9, // world PARM_LOCAL_HEALTH = -10, // cl.local.health PARM_LOCAL_GAME = -11, PARM_NUMENTITIES = -12, // local game only - PARM_NUMMODELS = -13, // cl.nummodels + PARM_GET_MOVEVARS_PTR = -13, // clgame.movevars + PARM_GET_PALETTE_PTR = -14, // clgame.palette + PARM_GET_VIEWENT_PTR = -15, // clgame.viewent + + PARM_GET_TEXGAMMATABLE_PTR = -16, + PARM_GET_LIGHTGAMMATABLE_PTR = -17, + PARM_GET_SCREENGAMMATABLE_PTR = -18, + PARM_GET_LINEARGAMMATABLE_PTR = -19, + + PARM_GET_LIGHTSTYLES_PTR = -20, + PARM_GET_DLIGHTS_PTR = -21, + PARM_GET_ELIGHTS_PTR = -22, + + // implemented by ref_dll + + // returns non-null integer if filtering is enabled for texture + // pass -1 to query global filtering settings + PARM_TEX_FILTERING = -0x10000, } ref_parm_e; typedef struct ref_api_s @@ -262,18 +331,18 @@ typedef struct ref_api_s cvar_t *(*Cvar_Get)( const char *szName, const char *szValue, int flags, const char *description ); cvar_t *(*pfnGetCvarPointer)( const char *name, int ignore_flags ); float (*pfnGetCvarFloat)( const char *szName ); - const char *(*pfnGetCvarString)( const char *szName ); + const char *(*pfnGetCvarString)( const char *szName ) PFN_RETURNS_NONNULL; void (*Cvar_SetValue)( const char *name, float value ); void (*Cvar_Set)( const char *name, const char *value ); - void (*Cvar_RegisterVariable)( cvar_t *var ); + void (*Cvar_RegisterVariable)( convar_t *var ); void (*Cvar_FullSet)( const char *var_name, const char *value, int flags ); // command handlers int (*Cmd_AddCommand)( const char *cmd_name, void (*function)(void), const char *description ); void (*Cmd_RemoveCommand)( const char *cmd_name ); int (*Cmd_Argc)( void ); - const char *(*Cmd_Argv)( int arg ); - const char *(*Cmd_Args)( void ); + const char *(*Cmd_Argv)( int arg ) PFN_RETURNS_NONNULL; + const char *(*Cmd_Args)( void ) PFN_RETURNS_NONNULL; // cbuf void (*Cbuf_AddText)( const char *commands ); @@ -281,37 +350,31 @@ typedef struct ref_api_s void (*Cbuf_Execute)( void ); // logging - void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages - void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 - void (*Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + void (*Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // typical console allowed messages + void (*Con_DPrintf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 1 + void (*Con_Reportf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 2 // debug print - void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) FORMAT_CHECK( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) FORMAT_CHECK( 2 ); void (*CL_CenterPrint)( const char *s, float y ); void (*Con_DrawStringLen)( const char *pText, int *length, int *height ); - int (*Con_DrawString)( int x, int y, const char *string, rgba_t setColor ); + int (*Con_DrawString)( int x, int y, const char *string, const rgba_t setColor ); void (*CL_DrawCenterPrint)( void ); // entity management - struct cl_entity_s *(*GetLocalPlayer)( void ); - struct cl_entity_s *(*GetViewModel)( void ); - struct cl_entity_s *(*GetEntityByIndex)( int idx ); struct cl_entity_s *(*R_BeamGetEntity)( int index ); struct cl_entity_s *(*CL_GetWaterEntity)( const vec3_t p ); qboolean (*CL_AddVisibleEntity)( cl_entity_t *ent, int entityType ); // brushes - int (*Mod_SampleSizeForFace)( struct msurface_s *surf ); + int (*Mod_SampleSizeForFace)( const struct msurface_s *surf ); qboolean (*Mod_BoxVisible)( const vec3_t mins, const vec3_t maxs, const byte *visbits ); - struct world_static_s *(*GetWorld)( void ); // returns &world mleaf_t *(*Mod_PointInLeaf)( const vec3_t p, mnode_t *node ); - void (*Mod_CreatePolygonsForHull)( int hullnum ); + void (*R_DrawWorldHull)( void ); + void (*R_DrawModelHull)( model_t *mod ); // studio models - void (*R_StudioSlerpBones)( int numbones, vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); - void (*R_StudioCalcBoneQuaternion)( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec4_t q ); - void (*R_StudioCalcBonePosition)( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, vec3_t adj, vec3_t pos ); void *(*R_StudioGetAnim)( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ); void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); @@ -327,26 +390,18 @@ typedef struct ref_api_s // model management model_t *(*Mod_ForName)( const char *name, qboolean crash, qboolean trackCRC ); void *(*Mod_Extradata)( int type, model_t *model ); - struct model_s *(*pfnGetModelByIndex)( int index ); // CL_ModelHandle - struct model_s *(*Mod_GetCurrentLoadingModel)( void ); // loadmodel - void (*Mod_SetCurrentLoadingModel)( struct model_s* ); // loadmodel // remap + qboolean (*CL_EntitySetRemapColors)( cl_entity_t *e, model_t *mod, int top, int bottom ); struct remap_info_s *(*CL_GetRemapInfoForEntity)( cl_entity_t *e ); - void (*CL_AllocRemapInfo)( cl_entity_t *entity, model_t *model, int topcolor, int bottomcolor ); - void (*CL_FreeRemapInfo)( struct remap_info_s *info ); - void (*CL_UpdateRemapInfo)( cl_entity_t *entity, int topcolor, int bottomcolor ); // utils void (*CL_ExtraUpdate)( void ); - void (*Host_Error)( const char *fmt, ... ) _format( 1 ); + void (*Host_Error)( const char *fmt, ... ) FORMAT_CHECK( 1 ); void (*COM_SetRandomSeed)( int lSeed ); float (*COM_RandomFloat)( float rmin, float rmax ); int (*COM_RandomLong)( int rmin, int rmax ); struct screenfade_s *(*GetScreenFade)( void ); - struct client_textmessage_s *(*pfnTextMessageGet)( const char *pName ); - void (*GetPredictedOrigin)( vec3_t v ); - color24 *(*CL_GetPaletteColor)(int color); // clgame.palette[color] void (*CL_GetScreenInfo)( int *width, int *height ); // clgame.scrInfo, ptrs may be NULL void (*SetLocalLightLevel)( int level ); // cl.local.light_level int (*Sys_CheckParm)( const char *flag ); @@ -360,10 +415,13 @@ typedef struct ref_api_s int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); // memory - poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); + poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ) + WARN_UNUSED_RESULT; void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); - void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); - void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 2 ) WARN_UNUSED_RESULT; + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 3 ) WARN_UNUSED_RESULT; void (*_Mem_Free)( void *data, const char *filename, int fileline ); // library management @@ -388,15 +446,7 @@ typedef struct ref_api_s void *(*SW_LockBuffer)( void ); void (*SW_UnlockBuffer)( void ); - // gamma - void (*BuildGammaTable)( float lightgamma, float brightness ); - byte (*LightToTexGamma)( byte color ); // software gamma support - qboolean (*R_DoResetGamma)( void ); - // renderapi - lightstyle_t* (*GetLightStyle)( int number ); - dlight_t* (*GetDynamicLight)( int number ); - dlight_t* (*GetEntityLight)( int number ); int (*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); const struct ref_overview_s *( *GetOverviewParms )( void ); double (*pfnTime)( void ); // Sys_DoubleTime @@ -407,7 +457,6 @@ typedef struct ref_api_s struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe ); struct pmtrace_s *(*EV_VisTraceLine )( float *start, float *end, int flags ); struct pmtrace_s (*CL_TraceLine)( vec3_t start, vec3_t end, int flags ); - struct movevars_s *(*pfnGetMoveVars)( void ); // imagelib void (*Image_AddCmdFlags)( uint flags ); // used to check if hardware dxt is supported @@ -420,7 +469,6 @@ typedef struct ref_api_s rgbdata_t *(*FS_CopyImage)( rgbdata_t *in ); void (*FS_FreeImage)( rgbdata_t *pack ); void (*Image_SetMDLPointer)( byte *p ); - poolhandle_t (*Image_GetPool)( void ); const struct bpc_desc_s *(*Image_GetPFDesc)( int idx ); // client exports @@ -430,6 +478,9 @@ typedef struct ref_api_s // filesystem exports fs_api_t *fsapi; + + // for abstracting the engine's rendering + ref_window_type_t (*R_GetWindowHandle)( void **handle, ref_window_type_t type ); } ref_api_t; struct mip_s; @@ -449,6 +500,8 @@ typedef struct ref_interface_s void (*GL_InitExtensions)( void ); void (*GL_ClearExtensions)( void ); + // scene rendering + void (*R_GammaChanged)( qboolean do_reset_gamma ); void (*R_BeginFrame)( qboolean clearScene ); void (*R_RenderScene)( void ); void (*R_EndFrame)( void ); @@ -463,7 +516,8 @@ typedef struct ref_interface_s qboolean (*R_AddEntity)( struct cl_entity_s *clent, int type ); void (*CL_AddCustomBeam)( cl_entity_t *pEnvBeam ); - void (*R_ProcessEntData)( qboolean allocate ); + void (*R_ProcessEntData)( qboolean allocate, cl_entity_t *entities, unsigned int max_entities ); + void (*R_Flush)( unsigned int flush_flags ); // debug void (*R_ShowTextures)( void ); @@ -472,15 +526,13 @@ typedef struct ref_interface_s const byte *(*R_GetTextureOriginalBuffer)( unsigned int idx ); // not always available int (*GL_LoadTextureFromBuffer)( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ); void (*GL_ProcessTexture)( int texnum, float gamma, int topColor, int bottomColor ); - void (*R_SetupSky)( const char *skyname ); + void (*R_SetupSky)( int *skyboxTextures ); // 2D void (*R_Set2DMode)( qboolean enable ); void (*R_DrawStretchRaw)( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ); void (*R_DrawStretchPic)( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ); - void (*R_DrawTileClear)( int texnum, int x, int y, int w, int h ); - void (*FillRGBA)( float x, float y, float w, float h, int r, int g, int b, int a ); // in screen space - void (*FillRGBABlend)( float x, float y, float w, float h, int r, int g, int b, int a ); // in screen space + void (*FillRGBA)( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a ); // in screen space int (*WorldToScreen)( const vec3_t world, vec3_t screen ); // Returns 1 if it's z clipped // screenshot, cubemapshot @@ -498,14 +550,14 @@ typedef struct ref_interface_s void (*R_ClearAllDecals)( void ); // studio interface - float (*R_StudioEstimateFrame)( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); + float (*R_StudioEstimateFrame)( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void (*R_StudioLerpMovement)( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void (*CL_InitStudioAPI)( void ); // bmodel - void (*R_InitSkyClouds)( struct mip_s *mt, struct texture_s *tx, qboolean custom_palette ); - void (*GL_SubdivideSurface)( msurface_t *fa ); - void (*CL_RunLightStyles)( void ); + void (*R_SetSkyCloudsTextures)( int solidskyTexture, int alphaskyTexture ); + void (*GL_SubdivideSurface)( model_t *mod, msurface_t *fa ); + void (*CL_RunLightStyles)( lightstyle_t *ls ); // sprites void (*R_GetSpriteParms)( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite ); @@ -513,7 +565,6 @@ typedef struct ref_interface_s // model management // flags ignored for everything except spritemodels - void (*Mod_LoadMapSprite)( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded ); qboolean (*Mod_ProcessRenderData)( model_t *mod, qboolean create, const byte *buffer ); void (*Mod_StudioLoadTextures)( model_t *mod, void *data ); @@ -543,6 +594,7 @@ typedef struct ref_interface_s int (*GL_LoadTextureArray)( const char **names, int flags ); int (*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ); void (*GL_FreeTexture)( unsigned int texnum ); + void (*R_OverrideTextureSourceSize)( unsigned int texnum, unsigned int srcWidth, unsigned int srcHeight ); // used to override decal size for texture replacement // Decals manipulating (draw & remove) void (*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa ); @@ -602,33 +654,19 @@ typedef struct ref_interface_s void (*CullFace)( TRICULLSTYLE mode ); // vgui drawing implementation - void (*VGUI_DrawInit)( void ); - void (*VGUI_DrawShutdown)( void ); - void (*VGUI_SetupDrawingText)( int *pColor ); - void (*VGUI_SetupDrawingRect)( int *pColor ); - void (*VGUI_SetupDrawingImage)( int *pColor ); - void (*VGUI_BindTexture)( int id ); - void (*VGUI_EnableTexture)( qboolean enable ); - void (*VGUI_CreateTexture)( int id, int width, int height ); - void (*VGUI_UploadTexture)( int id, const char *buffer, int width, int height ); - void (*VGUI_UploadTextureBlock)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); - void (*VGUI_DrawQuad)( const vpoint_t *ul, const vpoint_t *lr ); - void (*VGUI_GetTextureSizes)( int *width, int *height ); - int (*VGUI_GenerateTexture)( void ); + void (*VGUI_SetupDrawing)( qboolean rect ); + void (*VGUI_UploadTextureBlock)( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); } ref_interface_t; typedef int (*REFAPI)( int version, ref_interface_t *pFunctionTable, ref_api_t* engfuncs, ref_globals_t *pGlobals ); #define GET_REF_API "GetRefAPI" -typedef void (*REF_HUMANREADABLE_NAME)( char *out, size_t len ); -#define GET_REF_HUMANREADABLE_NAME "GetRefHumanReadableName" - #ifdef REF_DLL #define DEFINE_ENGINE_SHARED_CVAR( x, y ) cvar_t *x = NULL; #define DECLARE_ENGINE_SHARED_CVAR( x, y ) extern cvar_t *x; #define RETRIEVE_ENGINE_SHARED_CVAR( x, y ) \ if(!( x = gEngfuncs.pfnGetCvarPointer( #y, 0 ) )) \ - gEngfuncs.Host_Error( S_ERROR "engine betrayed us and didn't gave us %s cvar pointer\n", #y ); + gEngfuncs.Host_Error( S_ERROR "engine didn't gave us %s cvar pointer\n", #y ); #define ENGINE_SHARED_CVAR_NAME( f, x, y ) f( x, y ) #define ENGINE_SHARED_CVAR( f, x ) ENGINE_SHARED_CVAR_NAME( f, x, x ) @@ -639,7 +677,9 @@ typedef void (*REF_HUMANREADABLE_NAME)( char *out, size_t len ); #define ENGINE_SHARED_CVAR_LIST( f ) \ ENGINE_SHARED_CVAR_NAME( f, vid_gamma, gamma ) \ ENGINE_SHARED_CVAR_NAME( f, vid_brightness, brightness ) \ - ENGINE_SHARED_CVAR_NAME( f, gl_showtextures, r_showtextures ) \ + ENGINE_SHARED_CVAR_NAME( f, v_lightgamma, lightgamma ) \ + ENGINE_SHARED_CVAR_NAME( f, v_direct, direct ) \ + ENGINE_SHARED_CVAR( f, r_showtextures ) \ ENGINE_SHARED_CVAR( f, r_speeds ) \ ENGINE_SHARED_CVAR( f, r_fullbright ) \ ENGINE_SHARED_CVAR( f, r_norefresh ) \ @@ -660,6 +700,7 @@ typedef void (*REF_HUMANREADABLE_NAME)( char *out, size_t len ); ENGINE_SHARED_CVAR( f, r_sprite_lighting ) \ ENGINE_SHARED_CVAR( f, r_drawviewmodel ) \ ENGINE_SHARED_CVAR( f, r_glowshellfreq ) \ + ENGINE_SHARED_CVAR( f, host_allow_materials ) \ #define DECLARE_ENGINE_SHARED_CVAR_LIST() \ ENGINE_SHARED_CVAR_LIST( DECLARE_ENGINE_SHARED_CVAR ) diff --git a/engine/sequence.h b/engine/sequence.h deleted file mode 100644 index 84854fa0..00000000 --- a/engine/sequence.h +++ /dev/null @@ -1,205 +0,0 @@ -//--------------------------------------------------------------------------- -// -// S c r i p t e d S e q u e n c e s -// -//--------------------------------------------------------------------------- -#ifndef _INCLUDE_SEQUENCE_H_ -#define _INCLUDE_SEQUENCE_H_ - - -#ifndef _DEF_BYTE_ -//typedef unsigned char byte; -#endif - -#ifndef CDLL_INT_H -//--------------------------------------------------------------------------- -// client_textmessage_t -//--------------------------------------------------------------------------- -typedef struct client_textmessage_s -{ - int effect; - byte r1, g1, b1, a1; // 2 colors for effects - byte r2, g2, b2, a2; - float x; - float y; - float fadein; - float fadeout; - float holdtime; - float fxtime; - char *pName; - char *pMessage; -} client_textmessage_t; -#endif - - -//--------------------------------------------------------------------------- -// sequenceCommandEnum_e -// -// Enumerated sequence command types. -//--------------------------------------------------------------------------- -enum sequenceCommandEnum_ -{ - SEQUENCE_COMMAND_ERROR = -1, - SEQUENCE_COMMAND_PAUSE = 0, - SEQUENCE_COMMAND_FIRETARGETS, - SEQUENCE_COMMAND_KILLTARGETS, - SEQUENCE_COMMAND_TEXT, - SEQUENCE_COMMAND_SOUND, - SEQUENCE_COMMAND_GOSUB, - SEQUENCE_COMMAND_SENTENCE, - SEQUENCE_COMMAND_REPEAT, - SEQUENCE_COMMAND_SETDEFAULTS, - SEQUENCE_COMMAND_MODIFIER, - SEQUENCE_COMMAND_POSTMODIFIER, - SEQUENCE_COMMAND_NOOP, - - SEQUENCE_MODIFIER_EFFECT, - SEQUENCE_MODIFIER_POSITION, - SEQUENCE_MODIFIER_COLOR, - SEQUENCE_MODIFIER_COLOR2, - SEQUENCE_MODIFIER_FADEIN, - SEQUENCE_MODIFIER_FADEOUT, - SEQUENCE_MODIFIER_HOLDTIME, - SEQUENCE_MODIFIER_FXTIME, - SEQUENCE_MODIFIER_SPEAKER, - SEQUENCE_MODIFIER_LISTENER, - SEQUENCE_MODIFIER_TEXTCHANNEL, -}; -typedef enum sequenceCommandEnum_ sequenceCommandEnum_e; - -//-------------------------------------------------------------------------- -// sequenceDefaultBits_e -// -// Enumerated list of possible modifiers for a command. This enumeration -// is used in a bitarray controlling what modifiers are specified for a command. -//--------------------------------------------------------------------------- -enum sequenceModifierBits -{ - SEQUENCE_MODIFIER_EFFECT_BIT = (1 << 1), - SEQUENCE_MODIFIER_POSITION_BIT = (1 << 2), - SEQUENCE_MODIFIER_COLOR_BIT = (1 << 3), - SEQUENCE_MODIFIER_COLOR2_BIT = (1 << 4), - SEQUENCE_MODIFIER_FADEIN_BIT = (1 << 5), - SEQUENCE_MODIFIER_FADEOUT_BIT = (1 << 6), - SEQUENCE_MODIFIER_HOLDTIME_BIT = (1 << 7), - SEQUENCE_MODIFIER_FXTIME_BIT = (1 << 8), - SEQUENCE_MODIFIER_SPEAKER_BIT = (1 << 9), - SEQUENCE_MODIFIER_LISTENER_BIT = (1 << 10), - SEQUENCE_MODIFIER_TEXTCHANNEL_BIT = (1 << 11), -}; -typedef enum sequenceModifierBits sequenceModifierBits_e ; - - -//--------------------------------------------------------------------------- -// sequenceCommandType_e -// -// Typeerated sequence command types. -//--------------------------------------------------------------------------- -enum sequenceCommandType_ -{ - SEQUENCE_TYPE_COMMAND, - SEQUENCE_TYPE_MODIFIER, -}; -typedef enum sequenceCommandType_ sequenceCommandType_e; - - -//--------------------------------------------------------------------------- -// sequenceCommandMapping_s -// -// A mapping of a command enumerated-value to its name. -//--------------------------------------------------------------------------- -typedef struct sequenceCommandMapping_ sequenceCommandMapping_s; -struct sequenceCommandMapping_ -{ - sequenceCommandEnum_e commandEnum; - const char* commandName; - sequenceCommandType_e commandType; -}; - - -//--------------------------------------------------------------------------- -// sequenceCommandLine_s -// -// Structure representing a single command (usually 1 line) from a -// .SEQ file entry. -//--------------------------------------------------------------------------- -typedef struct sequenceCommandLine_ sequenceCommandLine_s; -struct sequenceCommandLine_ -{ - int commandType; // Specifies the type of command - client_textmessage_t clientMessage; // Text HUD message struct - char* speakerName; // Targetname of speaking entity - char* listenerName; // Targetname of entity being spoken to - char* soundFileName; // Name of sound file to play - char* sentenceName; // Name of sentences.txt to play - char* fireTargetNames; // List of targetnames to fire - char* killTargetNames; // List of targetnames to remove - float delay; // Seconds 'till next command - int repeatCount; // If nonzero, reset execution pointer to top of block (N times, -1 = infinite) - int textChannel; // Display channel on which text message is sent - int modifierBitField; // Bit field to specify what clientmessage fields are valid - sequenceCommandLine_s* nextCommandLine; // Next command (linked list) -}; - - -//--------------------------------------------------------------------------- -// sequenceEntry_s -// -// Structure representing a single command (usually 1 line) from a -// .SEQ file entry. -//--------------------------------------------------------------------------- -typedef struct sequenceEntry_ sequenceEntry_s; -struct sequenceEntry_ -{ - char* fileName; // Name of sequence file without .SEQ extension - char* entryName; // Name of entry label in file - sequenceCommandLine_s* firstCommand; // Linked list of commands in entry - sequenceEntry_s* nextEntry; // Next loaded entry - qboolean isGlobal; // Is entry retained over level transitions? -}; - - - -//--------------------------------------------------------------------------- -// sentenceEntry_s -// Structure representing a single sentence of a group from a .SEQ -// file entry. Sentences are identical to entries in sentences.txt, but -// can be unique per level and are loaded/unloaded with the level. -//--------------------------------------------------------------------------- -typedef struct sentenceEntry_ sentenceEntry_s; -struct sentenceEntry_ -{ - char* data; // sentence data (ie "We have hostiles" ) - sentenceEntry_s* nextEntry; // Next loaded entry - qboolean isGlobal; // Is entry retained over level transitions? - unsigned int index; // this entry's position in the file. -}; - -//-------------------------------------------------------------------------- -// sentenceGroupEntry_s -// Structure representing a group of sentences found in a .SEQ file. -// A sentence group is defined by all sentences with the same name, ignoring -// the number at the end of the sentence name. Groups enable a sentence -// to be picked at random across a group. -//-------------------------------------------------------------------------- -typedef struct sentenceGroupEntry_ sentenceGroupEntry_s; -struct sentenceGroupEntry_ -{ - char* groupName; // name of the group (ie CT_ALERT ) - unsigned int numSentences; // number of sentences in group - sentenceEntry_s* firstSentence; // head of linked list of sentences in group - sentenceGroupEntry_s* nextEntry; // next loaded group -}; - -//--------------------------------------------------------------------------- -// Function declarations -//--------------------------------------------------------------------------- -sequenceEntry_s* Sequence_Get( const char* fileName, const char* entryName ); -void Sequence_ParseFile( const char* fileName, qboolean isGlobal ); -void Sequence_OnLevelLoad( const char* mapName ); -sentenceEntry_s* Sequence_PickSentence( const char *groupName, int pickMethod, int *picked ); -void Sequence_Init( void ); -void Sequence_PurgeEntries( qboolean purgeGlobals ); -sentenceEntry_s *Sequence_GetSentenceByIndex( unsigned int index ); - -#endif // _INCLUDE_SEQUENCE_H_ diff --git a/engine/server/server.h b/engine/server/server.h index c9dbc588..80fa597a 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -43,10 +43,9 @@ extern int SV_UPDATE_BACKUP; #define SVF_MERGE_VISIBILITY BIT( 1 ) // we are do portal pass // mapvalid flags -#define MAP_IS_EXIST BIT( 0 ) -#define MAP_HAS_SPAWNPOINT BIT( 1 ) -#define MAP_HAS_LANDMARK BIT( 2 ) -#define MAP_INVALID_VERSION BIT( 3 ) +#define MAP_IS_EXIST BIT( 0 ) +#define MAP_HAS_LANDMARK BIT( 2 ) +#define MAP_INVALID_VERSION BIT( 3 ) #define SV_SPAWN_TIME 0.1 @@ -67,6 +66,9 @@ extern int SV_UPDATE_BACKUP; #define MAX_PUSHED_ENTS 256 #define MAX_VIEWENTS 128 +#define MAX_LOCALINFO_STRING 32768 // localinfo used on server and not sended to the clients + +#define MAX_ENT_LEAFS( ext ) (( ext ) ? MAX_ENT_LEAFS_32 : MAX_ENT_LEAFS_16 ) #define FCL_RESEND_USERINFO BIT( 0 ) #define FCL_RESEND_MOVEVARS BIT( 1 ) @@ -92,6 +94,7 @@ typedef enum cs_free = 0, // can be reused for a new connection cs_zombie, // client has been disconnected, but don't reuse connection for a couple seconds cs_connected, // has been assigned to a sv_client_t, but not in game yet + cs_spawning, // put in game, but not spawned yet cs_spawned // client is fully in game } cl_state_t; @@ -203,85 +206,74 @@ typedef struct typedef struct sv_client_s { - cl_state_t state; - cl_upload_t upstate; // uploading state - char name[32]; // extracted from userinfo, color string allowed - uint flags; // client flags, some info - CRC32_t crcValue; + cl_state_t state; + cl_upload_t upstate; // uploading state + char name[32]; // extracted from userinfo, color string allowed + uint flags; // client flags, some info + uint extensions; // protocol extensions - char userinfo[MAX_INFO_STRING]; // name, etc (received from client) - char physinfo[MAX_INFO_STRING]; // set on server (transmit to client) + char hashedcdkey[34]; // MD5 hash is 32 hex #'s, plus trailing 0 + char userinfo[MAX_INFO_STRING]; // name, etc (received from client) + char physinfo[MAX_INFO_STRING]; // set on server (transmit to client) + char useragent[MAX_INFO_STRING]; - netchan_t netchan; - int chokecount; // number of messages rate supressed - int delta_sequence; // -1 = no compression. + byte ignorecmdtime_warned; // did we warn our server operator in the log for this batch of commands? + uint listeners; // which other clients does this guy's voice stream go to? - double next_messagetime; // time when we should send next world state update - double next_checkpingtime; // time to send all players pings to client - double next_sendinfotime; // time to send info about all players - double cl_updaterate; // client requested updaterate - double timebase; // client timebase - double connection_started; + int ignorecmdtime_warns; // how many times client time was faster than server during this session + int userid; // identifying number on server - char hashedcdkey[34]; // MD5 hash is 32 hex #'s, plus trailing 0 + netchan_t netchan; + sizebuf_t datagram; // the datagram is written to by sound calls, prints, temp ents, etc. + byte datagram_buf[MAX_DATAGRAM]; // it can be harmlessly overflowed. - customization_t customdata; // player customization linked list - resource_t resourcesonhand; - resource_t resourcesneeded; // from client (server downloading) - usercmd_t lastcmd; // for filling in big drops + int chokecount; // number of messages rate supressed + int delta_sequence; // -1 = no compression. - double connecttime; - double cmdtime; - double ignorecmdtime; + double next_messagetime; // time when we should send next world state update + double next_checkpingtime; // time to send all players pings to client + double next_sendinfotime; // time to send info about all players + double cl_updaterate; // client requested updaterate + double timebase; // client timebase + double connection_started; - int packet_loss; - float latency; + customization_t customdata; // player customization linked list + resource_t resourcesonhand; + resource_t resourcesneeded; // from client (server downloading) + usercmd_t lastcmd; // for filling in big drops - int ignored_ents; // if visibility list is full we should know how many entities will be ignored - edict_t *edict; // EDICT_NUM(clientnum+1) - edict_t *pViewEntity; // svc_setview member - edict_t *viewentity[MAX_VIEWENTS]; // list of portal cameras in player PVS - int num_viewents; // num of portal cameras that can merge PVS + int packet_loss; + double connecttime; + double cmdtime; + double ignorecmdtime; + float latency; - qboolean m_bLoopback; // Does this client want to hear his own voice? - uint listeners; // which other clients does this guy's voice stream go to? + int ignored_ents; // if visibility list is full we should know how many entities will be ignored + edict_t *edict; // EDICT_NUM(clientnum+1) + edict_t *pViewEntity; // svc_setview member + edict_t *viewentity[MAX_VIEWENTS]; // list of portal cameras in player PVS + int num_viewents; // num of portal cameras that can merge PVS - // the datagram is written to by sound calls, prints, temp ents, etc. - // it can be harmlessly overflowed. - sizebuf_t datagram; - byte datagram_buf[MAX_DATAGRAM]; + int userinfo_change_attempts; + double fullupdate_next_calltime; + double userinfo_next_changetime; + double userinfo_penalty; - client_frame_t *frames; // updates can be delta'd from here - event_state_t events; // delta-updated events cycle + double overflow_warn_time; - int challenge; // challenge of this user, randomly generated - int userid; // identifying number on server - int extensions; - char useragent[MAX_INFO_STRING]; + client_frame_t *frames; // updates can be delta'd from here + event_state_t events; // delta-updated events cycle } sv_client_t; /* ============================================================================= a client can leave the server in one of four ways: dropping properly by quiting or disconnecting - timing out if no valid messages are received for timeout.value seconds + timing out if no valid messages are received for sv_timeout.value seconds getting kicked off by the server operator a program error, like an overflowed reliable buffer ============================================================================= */ -// MAX_CHALLENGES is made large to prevent a denial -// of service attack that could cycle all of them -// out before legitimate users connected -#define MAX_CHALLENGES 1024 - -typedef struct -{ - netadr_t adr; - double time; - int challenge; - qboolean connected; -} challenge_t; - typedef struct { char name[32]; // in GoldSrc max name length is 12 @@ -322,13 +314,14 @@ typedef struct int msg_realsize; // left in bytes int msg_index; // for debug messages int msg_dest; // msg destination ( MSG_ONE, MSG_ALL etc ) + int msg_rewrite_index; + int msg_rewrite_pos; qboolean msg_started; // to avoid recursive included messages edict_t *msg_ent; // user message member entity vec3_t msg_org; // user message member origin qboolean msg_trace; // trace this message void *hInstance; // pointer to game.dll - qboolean config_executed; // should to execute config.cfg once time to restore FCVAR_ARCHIVE that specified in hl.dll edict_t *edicts; // solid array of server entities int numEntities; // actual entities count @@ -353,7 +346,6 @@ typedef struct typedef struct { qboolean initialized; // sv_init has completed - qboolean game_library_loaded; // is game library loaded in SV_InitGame double timestart; // just for profiling int maxclients; // server max clients @@ -375,16 +367,22 @@ typedef struct entity_state_t *baselines; // [GI->max_edicts] entity_state_t *static_entities; // [MAX_STATIC_ENTITIES]; - double last_heartbeat; - challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + uint32_t challenge_salt[16]; // pregenerated random numbers for generating challenged based on IP's MD5 address + + sizebuf_t testpacket; // pregenerataed testpacket, only needs CRC32 patching + byte *testpacket_buf; // check for NULL if testpacket is available + byte *testpacket_crcpos; // pointer to write pregenerated crc (unaligned!!!) + uint32_t *testpacket_crcs; // checksums lookup table + int testpacket_filepos; // file position (need to calculate lookup table pos) + int testpacket_filelen; // file and lookup table length } server_static_t; //============================================================================= -extern server_static_t svs; // persistant server info -extern server_t sv; // local server -extern svgame_static_t svgame; // persistant game info -extern areanode_t sv_areanodes[]; // AABB dynamic tree +extern server_static_t svs RENAME_SYMBOL( "svs_" ); // persistant server info +extern server_t sv RENAME_SYMBOL( "sv_" ); // local server +extern svgame_static_t svgame; // persistant game info +extern areanode_t sv_areanodes[]; // AABB dynamic tree extern convar_t mp_logecho; extern convar_t mp_logfile; @@ -394,7 +392,7 @@ extern convar_t sv_unlag; extern convar_t sv_maxunlag; extern convar_t sv_unlagpush; extern convar_t sv_unlagsamples; -extern convar_t rcon_password; +extern convar_t rcon_enable; extern convar_t sv_instancedbaseline; extern convar_t sv_background_freeze; extern convar_t sv_minupdaterate; @@ -409,21 +407,15 @@ extern convar_t sv_send_resources; extern convar_t sv_send_logos; extern convar_t sv_allow_upload; extern convar_t sv_allow_download; -extern convar_t sv_airaccelerate; -extern convar_t sv_accelerate; extern convar_t sv_friction; -extern convar_t sv_edgefriction; extern convar_t sv_gravity; extern convar_t sv_stopspeed; -extern convar_t sv_maxspeed; extern convar_t sv_wateralpha; extern convar_t sv_wateramp; extern convar_t sv_voiceenable; extern convar_t sv_voicequality; -extern convar_t sv_stepsize; extern convar_t sv_maxvelocity; -extern convar_t sv_rollangle; -extern convar_t sv_rollspeed; +extern convar_t sv_stepsize; extern convar_t sv_skyname; extern convar_t sv_skycolor_r; extern convar_t sv_skycolor_g; @@ -435,50 +427,53 @@ extern convar_t sv_consistency; extern convar_t sv_password; extern convar_t sv_uploadmax; extern convar_t sv_trace_messages; +extern convar_t sv_enttools_enable; +extern convar_t sv_enttools_maxfire; +extern convar_t sv_autosave; extern convar_t deathmatch; extern convar_t hostname; extern convar_t skill; extern convar_t coop; - -extern convar_t *sv_pausable; // allows pause in multiplayer -extern convar_t *sv_check_errors; -extern convar_t *sv_reconnect_limit; -extern convar_t *sv_lighting_modulate; -extern convar_t *sv_novis; -extern convar_t *sv_hostmap; -extern convar_t *sv_validate_changelevel; -extern convar_t *public_server; +extern convar_t sv_cheats; +extern convar_t public_server; +extern convar_t sv_nat; +extern convar_t sv_speedhack_kick; +extern convar_t sv_pausable; // allows pause in multiplayer +extern convar_t sv_check_errors; +extern convar_t sv_lighting_modulate; +extern convar_t sv_novis; +extern convar_t sv_hostmap; +extern convar_t sv_validate_changelevel; +extern convar_t sv_maxclients; +extern convar_t sv_userinfo_enable_penalty; +extern convar_t sv_userinfo_penalty_time; +extern convar_t sv_userinfo_penalty_multiplier; +extern convar_t sv_userinfo_penalty_attempts; +extern convar_t sv_fullupdate_penalty_time; +extern convar_t sv_log_outofband; +extern convar_t sv_allow_autoaim; +extern convar_t sv_aim; +extern convar_t sv_allow_testpacket; +extern convar_t sv_expose_player_list; //=========================================================== // // sv_main.c // void SV_FinalMessage( const char *message, qboolean reconnect ); -void SV_DropClient( sv_client_t *cl, qboolean crash ); +void SV_KickPlayer( sv_client_t *cl, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void SV_DropClient( sv_client_t *cl, qboolean crash ) RENAME_SYMBOL( "SV_DropClient_" ); void SV_UpdateMovevars( qboolean initialize ); int SV_ModelIndex( const char *name ); int SV_SoundIndex( const char *name ); int SV_EventIndex( const char *name ); int SV_GenericIndex( const char *name ); -int SV_CalcPacketLoss( sv_client_t *cl ); -void SV_ExecuteUserCommand (char *s); void SV_InitOperatorCommands( void ); void SV_KillOperatorCommands( void ); -void SV_UserinfoChanged( sv_client_t *cl ); void SV_RemoteCommand( netadr_t from, sizebuf_t *msg ); -void SV_PrepWorldFrame( void ); -void SV_ProcessFile( sv_client_t *cl, const char *filename ); void SV_SendResource( resource_t *pResource, sizebuf_t *msg ); -void SV_SendResourceList( sv_client_t *cl ); void SV_AddToMaster( netadr_t from, sizebuf_t *msg ); qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ); -int SV_GetConnectedClientsCount( int *bots ); -void Host_SetServerState( int state ); -qboolean SV_IsSimulating( void ); -void SV_FreeClients( void ); -void Master_Add( void ); -void Master_Heartbeat( void ); -void Master_Packet( void ); // // sv_init.c @@ -486,8 +481,23 @@ void Master_Packet( void ); qboolean SV_InitGame( void ); void SV_ActivateServer( int runPhysics ); qboolean SV_SpawnServer( const char *server, const char *startspot, qboolean background ); -model_t *SV_ModelHandle( int modelindex ); void SV_DeactivateServer( void ); +void SV_FreeTestPacket( void ); + +/* +================ +SV_ModelHandle + +get model by handle +================ +*/ +static inline model_t *GAME_EXPORT SV_ModelHandle( int modelindex ) +{ + if( unlikely( modelindex < 0 || modelindex >= MAX_MODELS )) + return NULL; + return sv.models[modelindex]; +} + // // sv_phys.c @@ -495,14 +505,9 @@ void SV_DeactivateServer( void ); void SV_Physics( void ); qboolean SV_InitPhysicsAPI( void ); void SV_CheckVelocity( edict_t *ent ); -qboolean SV_CheckWater( edict_t *ent ); -qboolean SV_RunThink( edict_t *ent ); qboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time ); -qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ); void SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace ); -qboolean SV_CanPushed( edict_t *ent ); void SV_FreeOldEntities( void ); -void SV_CheckAllEnts( void ); // // sv_move.c @@ -518,15 +523,12 @@ void SV_WaterMove( edict_t *ent ); // sv_send.c // void SV_SendClientMessages( void ); -void SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... ) _format( 2 ); -void SV_BroadcastCommand( const char *fmt, ... ) _format( 1 ); +void SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... ) FORMAT_CHECK( 2 ); // // sv_client.c // -char *SV_StatusString( void ); void SV_RefreshUserinfo( void ); -void SV_GetChallenge( netadr_t from ); void SV_TogglePause( const char *msg ); qboolean SV_ShouldUpdatePing( sv_client_t *cl ); const char *SV_GetClientIDString( sv_client_t *cl ); @@ -536,25 +538,32 @@ void SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg ); void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg ); void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss ); void SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl ); -void SV_ClientThink( sv_client_t *cl, usercmd_t *cmd ); void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg ); void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ); edict_t *SV_FakeConnect( const char *netname ); -void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ); -void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ); void SV_BuildReconnect( sizebuf_t *msg ); -qboolean SV_IsPlayerIndex( int idx ); -int SV_CalcPing( sv_client_t *cl ); -void SV_InitClientMove( void ); +int SV_CalcPing( const sv_client_t *cl ); void SV_UpdateServerInfo( void ); -void SV_EndRedirect( void ); -void SV_RejectConnection( netadr_t from, const char *fmt, ... ) _format( 2 ); +void SV_EndRedirect( host_redirect_t *rd ); +void SV_RejectConnection( netadr_t from, const char *fmt, ... ) FORMAT_CHECK( 2 ); +void SV_GetPlayerCount( int *clients, int *bots ); + +static inline qboolean SV_HavePassword( void ) +{ + if( COM_CheckStringEmpty( sv_password.string ) && Q_stricmp( sv_password.string, "none" )) + return true; + + return false; +} + +static inline qboolean SV_IsPlayerIndex( int idx ) +{ + return idx > 0 && idx <= svs.maxclients ? true : false; +} // // sv_cmds.c // -void SV_Status_f( void ); -void SV_Newgame_f( void ); void SV_InitHostCommands( void ); // @@ -576,7 +585,6 @@ void SV_RequestMissingResources( void ); // sv_filter.c // void SV_InitFilter( void ); -void SV_ShutdownFilter( void ); qboolean SV_CheckIP( netadr_t *adr ); qboolean SV_CheckID( const char *id ); @@ -584,10 +592,7 @@ qboolean SV_CheckID( const char *id ); // sv_frame.c // void SV_InactivateClients( void ); -int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to ); -void SV_WriteFrameToClient( sv_client_t *client, sizebuf_t *msg ); -void SV_BuildClientFrame( sv_client_t *client ); -void SV_SendMessagesToAll( void ); +int SV_FindBestBaseline( int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player ); void SV_SkipUpdates( void ); // @@ -600,50 +605,47 @@ edict_t *SV_AllocEdict( void ); void SV_FreeEdict( edict_t *pEdict ); void SV_InitEdict( edict_t *pEdict ); const char *SV_ClassName( const edict_t *e ); -void SV_FreePrivateData( edict_t *pEdict ); void SV_CopyTraceToGlobal( trace_t *trace ); qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ); void SV_SetMinMaxSize( edict_t *e, const float *min, const float *max, qboolean relink ); -edict_t* SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ); void SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); -void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ); int 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 ); qboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ); void SV_QueueChangeLevel( const char *level, const char *landname ); void SV_WriteEntityPatch( const char *filename ); -float SV_AngleMod( float ideal, float current, float speed ); void SV_SpawnEntities( const char *mapname ); -edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ); edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ); string_t SV_AllocString( const char *szValue ); string_t SV_MakeString( const char *szValue ); const char *SV_GetString( string_t iString ); void SV_SetStringArrayMode( qboolean dynamic ); -void SV_EmptyStringPool( void ); -#ifdef XASH_64BIT +void SV_EmptyStringPool( qboolean clear_stats ); void SV_PrintStr64Stats_f( void ); -#endif sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ); -uint SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ); +uint SV_MapIsValid( const char *filename, const char *landmark_name ); void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ); edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ); qboolean SV_CreateStaticEntity( struct sizebuf_s *msg, int index ); void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ); int pfnIndexOfEdict( const edict_t *pEdict ); -void pfnWriteBytes( const byte *bytes, int count ); void SV_UpdateBaseVelocity( edict_t *ent ); -byte *pfnSetFatPVS( const float *org ); -byte *pfnSetFatPAS( const float *org ); -int pfnPrecacheModel( const char *s ); -int pfnModelIndex( const char *m ); -void pfnRemoveEntity( edict_t* e ); void SV_RestartAmbientSounds( void ); void SV_RestartDecals( void ); void SV_RestartStaticEnts( void ); -int pfnGetCurrentPlayer( void ); -edict_t *SV_EdictNum( int n ); -char *SV_Localinfo( void ); +int pfnDropToFloor( edict_t* e ); +void SV_SetModel( edict_t *ent, const char *name ); +int pfnDecalIndex( const char *m ); +void SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ); +qboolean SV_RestoreCustomDecal( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent ); + +static inline edict_t *SV_EdictNum( int n ) +{ + if( likely( n >= 0 && n < GI->max_edicts )) + return &svgame.edicts[n]; + return NULL; +} + // // sv_log.c // @@ -656,7 +658,7 @@ void SV_SetLogAddress_f( void ); // // sv_save.c // -void SV_SaveGame( const char *pName ); +qboolean SV_SaveGame( const char *pName ); qboolean SV_LoadGame( const char *pName ); int SV_LoadGameState( char const *level ); void SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char *start, qboolean background ); @@ -667,9 +669,8 @@ void SV_ClearGameState( void ); // // sv_pmove.c // -void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ); -void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs ); -qboolean SV_PlayerIsFrozen( edict_t *pClient ); +void SV_InitClientMove( void ); +void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ); // // sv_world.c @@ -678,21 +679,20 @@ void SV_ClearWorld( void ); void SV_UnlinkEdict( edict_t *ent ); void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ); void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ); -trace_t SV_TraceHull( edict_t *ent, int hullNum, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end ); trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip ); trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ); -trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ); const char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end ); msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ); trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ); void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ); -void SV_TouchLinks( edict_t *ent, areanode_t *node ); int SV_TruePointContents( const vec3_t p ); int SV_PointContents( const vec3_t p ); -void SV_RunLightStyles( void ); void SV_SetLightStyle( int style, const char* s, float f ); -const char *SV_GetLightStyle( int style ); int SV_LightForEntity( edict_t *pEdict ); -void SV_ClearPhysEnts( void ); + +// +// sv_query.c +// +void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from ); #endif//SERVER_H diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index cbf5d912..047a68c5 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -19,21 +19,6 @@ GNU General Public License for more details. #include "net_encode.h" #include "net_api.h" -const char *clc_strings[clc_lastmsg+1] = -{ - "clc_bad", - "clc_nop", - "clc_move", - "clc_stringcmd", - "clc_delta", - "clc_resourcelist", - "clc_unused6", - "clc_fileconsistency", - "clc_voicedata", - "clc_cvarvalue", - "clc_cvarvalue2", -}; - typedef struct ucmd_s { const char *name; @@ -42,13 +27,16 @@ typedef struct ucmd_s static int g_userid = 1; +static void SV_UserinfoChanged( sv_client_t *cl ); +static void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ); + /* ================= SV_GetPlayerCount ================= */ -static void SV_GetPlayerCount( int *players, int *bots ) +void SV_GetPlayerCount( int *players, int *bots ) { int i; @@ -82,41 +70,57 @@ flood the server with invalid connection IPs. With a challenge, they must give a valid IP address. ================= */ -void SV_GetChallenge( netadr_t from ) +static int SV_GetChallenge( netadr_t from, qboolean *error ) { - int i, oldest = 0; - double oldestTime; + const netadrtype_t type = NET_NetadrType( &from ); + MD5Context_t ctx; + byte digest[16]; - oldestTime = 0x7fffffff; + *error = false; - // see if we already have a challenge for this ip - for( i = 0; i < MAX_CHALLENGES; i++ ) + MD5Init( &ctx ); + + switch( type ) { - if( !svs.challenges[i].connected && NET_CompareAdr( from, svs.challenges[i].adr )) - break; - - if( svs.challenges[i].time < oldestTime ) - { - oldestTime = svs.challenges[i].time; - oldest = i; - } + case NA_IP: + MD5Update( &ctx, from.ip, sizeof( from.ip )); + break; + case NA_IPX: + MD5Update( &ctx, from.ipx, sizeof( from.ipx )); + break; + case NA_IP6: + { + byte ip6[16]; + NET_NetadrToIP6Bytes( ip6, &from ); + MD5Update( &ctx, ip6, sizeof( ip6 )); + break; + } + case NA_LOOPBACK: + return 0; + default: + *error = true; + return 0; } - if( i == MAX_CHALLENGES ) - { - // this is the first time this client has asked for a challenge - svs.challenges[oldest].challenge = (COM_RandomLong( 0, 0xFFFF ) << 16) | COM_RandomLong( 0, 0xFFFF ); - svs.challenges[oldest].adr = from; - svs.challenges[oldest].time = host.realtime; - svs.challenges[oldest].connected = false; - i = oldest; - } + MD5Update( &ctx, (byte *)svs.challenge_salt, sizeof( svs.challenge_salt )); + MD5Final( digest, &ctx ); - // send it back - Netchan_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "challenge %i", svs.challenges[i].challenge ); + return digest[0] | digest[1] << 8 | digest[2] << 16 | digest[3] << 24; } -int SV_GetFragmentSize( void *pcl, fragsize_t mode ) +static void SV_SendChallenge( netadr_t from ) +{ + qboolean error = false; + int challenge = SV_GetChallenge( from, &error ); + + if( error ) + return; + + // send it back + Netchan_OutOfBandPrint( NS_SERVER, from, S2C_CHALLENGE" %i", challenge ); +} + +static int SV_GetFragmentSize( void *pcl, fragsize_t mode ) { sv_client_t *cl = (sv_client_t*)pcl; int cl_frag_size; @@ -176,9 +180,9 @@ void SV_RejectConnection( netadr_t from, const char *fmt, ... ) va_end( argptr ); Con_Reportf( "%s connection refused. Reason: %s\n", NET_AdrToString( from ), text ); - Netchan_OutOfBandPrint( NS_SERVER, from, "errormsg\n^1Server was reject the connection:^7 %s", text ); - Netchan_OutOfBandPrint( NS_SERVER, from, "print\n^1Server was reject the connection:^7 %s", text ); - Netchan_OutOfBandPrint( NS_SERVER, from, "disconnect\n" ); + Netchan_OutOfBandPrint( NS_SERVER, from, S2C_ERRORMSG"\n^1Server was reject the connection:^7 %s", text ); + Netchan_OutOfBandPrint( NS_SERVER, from, A2C_PRINT"\n^1Server was reject the connection:^7 %s", text ); + Netchan_OutOfBandPrint( NS_SERVER, from, S2C_REJECT"\n" ); } /* @@ -189,7 +193,7 @@ for some reasons file can't be downloaded tell the client about this problem ================ */ -void SV_FailDownload( sv_client_t *cl, const char *filename ) +static void SV_FailDownload( sv_client_t *cl, const char *filename ) { if( !COM_CheckString( filename )) return; @@ -205,37 +209,18 @@ SV_CheckChallenge Make sure connecting client is not spoofing ================ */ -int SV_CheckChallenge( netadr_t from, int challenge ) +static int SV_CheckChallenge( netadr_t from, int challenge ) { - int i; + qboolean error = false; + int challenge2 = SV_GetChallenge( from, &error ); - // see if the challenge is valid - // don't care if it is a local address. - if( NET_IsLocalAddress( from )) - return 1; - - for( i = 0; i < MAX_CHALLENGES; i++ ) - { - if( NET_CompareAdr( from, svs.challenges[i].adr )) - { - if( challenge == svs.challenges[i].challenge ) - break; // valid challenge -#if 0 - // g-cont. this breaks multiple connections from single machine - SV_RejectConnection( from, "bad challenge %i\n", challenge ); - return 0; -#endif - } - } - - if( i == MAX_CHALLENGES ) + if( error || challenge2 != challenge ) { SV_RejectConnection( from, "no challenge for your address\n" ); - return 0; + return false; } - svs.challenges[i].connected = true; - return 1; + return true; } /* @@ -245,11 +230,11 @@ SV_CheckIPRestrictions Determine if client is outside appropriate address range ================ */ -int SV_CheckIPRestrictions( netadr_t from ) +static int SV_CheckIPRestrictions( netadr_t from ) { if( sv_lan.value ) { - if( !NET_CompareClassBAdr( from, net_local ) && !NET_IsReservedAdr( from )) + if( !NET_IsReservedAdr( from )) return 0; } return 1; @@ -263,23 +248,36 @@ Get slot # and set client_t pointer for player, if possible We don't do this search on a "reconnect, we just reuse the slot ================ */ -int SV_FindEmptySlot( netadr_t from, int *pslot, sv_client_t **ppClient ) +static sv_client_t *SV_FindEmptySlot( void ) { - sv_client_t *cl; - int i; + int i; - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + for( i = 0; i < svs.maxclients; i++ ) { - if( cl->state == cs_free ) - { - *ppClient = cl; - *pslot = i; - return 1; - } + if( svs.clients[i].state == cs_free ) + return &svs.clients[i]; } - SV_RejectConnection( from, "server is full\n" ); - return 0; + return NULL; +} + +static void SV_MaybeNotifyPlayerCountChange( const sv_client_t *cl, const char *address ) +{ + int i, count = 0; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + for( i = 0; i < svs.maxclients; i++ ) + { + if( svs.clients[i].state >= cs_connected ) + count++; + } + + if( count == 1 || count == svs.maxclients ) + NET_MasterClear(); + + Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", + cl->name, cl->userid, (int)( cl - svs.clients ), address ); } /* @@ -289,18 +287,18 @@ SV_ConnectClient A connection request that did not come from the master ================== */ -void SV_ConnectClient( netadr_t from ) +static void SV_ConnectClient( netadr_t from ) { - char userinfo[MAX_INFO_STRING]; - char protinfo[MAX_INFO_STRING]; - sv_client_t *cl, *newcl = NULL; - qboolean reconnect = false; - int nClientSlot = 0; - int qport, version; - int i, count = 0; - int challenge; - const char *s; + char userinfo[MAX_INFO_STRING]; + char protinfo[MAX_INFO_STRING]; + client_frame_t *frames; + sv_client_t *newcl = NULL; + int qport, version; + int i; + int challenge; + const char *s; int extensions; + uint netchan_flags = 0; if( Cmd_Argc() < 5 ) { @@ -316,39 +314,6 @@ void SV_ConnectClient( netadr_t from ) return; } - challenge = Q_atoi( Cmd_Argv( 2 )); // get challenge - - // see if the challenge is valid (local clients don't need to challenge) - if( !SV_CheckChallenge( from, challenge )) - return; - - s = Cmd_Argv( 3 ); // protocol info - - if( !Info_IsValid( s )) - { - SV_RejectConnection( from, "invalid protinfo in connect command\n" ); - return; - } - - Q_strncpy( protinfo, s, sizeof( protinfo )); - - if( !SV_ProcessUserAgent( from, protinfo ) ) - { - return; - } - - // extract qport from protocol info - qport = Q_atoi( Info_ValueForKey( protinfo, "qport" )); - - s = Info_ValueForKey( protinfo, "uuid" ); - if( Q_strlen( s ) != 32 ) - { - SV_RejectConnection( from, "invalid authentication certificate length\n" ); - return; - } - - extensions = Q_atoi( Info_ValueForKey( protinfo, "ext" ) ); - // LAN servers restrict to class b IP addresses if( !SV_CheckIPRestrictions( from )) { @@ -356,9 +321,31 @@ void SV_ConnectClient( netadr_t from ) return; } + challenge = Q_atoi( Cmd_Argv( 2 )); // get challenge + + // see if the challenge is valid (local clients don't need to challenge) + if( !SV_CheckChallenge( from, challenge )) + return; + + s = Cmd_Argv( 3 ); + if( Q_strlen( s ) > sizeof( protinfo ) || !Info_IsValid( s )) + { + SV_RejectConnection( from, "invalid protinfo in connect command\n" ); + return; + } + + Q_strncpy( protinfo, s, sizeof( protinfo )); // protocol info + + if( !SV_ProcessUserAgent( from, protinfo )) + return; + + // extract qport from protocol info + qport = Q_atoi( Info_ValueForKey( protinfo, "qport" )); + extensions = Q_atoi( Info_ValueForKey( protinfo, "ext" )); + s = Cmd_Argv( 4 ); // user info - if( Q_strlen( s ) > MAX_INFO_STRING || !Info_IsValid( s )) + if( Q_strlen( s ) > sizeof( userinfo ) || !Info_IsValid( s )) { SV_RejectConnection( from, "invalid userinfo in connect command\n" ); return; @@ -367,62 +354,80 @@ void SV_ConnectClient( netadr_t from ) Q_strncpy( userinfo, s, sizeof( userinfo )); // check connection password (don't verify local client) - if( !NET_IsLocalAddress( from ) && sv_password.string[0] && Q_stricmp( sv_password.string, Info_ValueForKey( userinfo, "password" ))) + if( !NET_IsLocalAddress( from ) && SV_HavePassword( )) { - SV_RejectConnection( from, "invalid password\n" ); - return; + if( Q_stricmp( sv_password.string, Info_ValueForKey( userinfo, "password" ))) + { + SV_RejectConnection( from, "invalid password\n" ); + return; + } } // if there is already a slot for this ip, reuse it - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + for( i = 0; i < svs.maxclients; i++ ) { + sv_client_t *cl = &svs.clients[i]; + if( cl->state == cs_free || cl->state == cs_zombie ) continue; if( NET_CompareBaseAdr( from, cl->netchan.remote_address ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remote_address.port )) { - reconnect = true; newcl = cl; + Con_Reportf( S_NOTE "%s:reconnect\n", NET_AdrToString( from )); break; } } // A reconnecting client will re-use the slot found above when checking for reconnection. // the slot will be wiped clean. - if( !reconnect ) + if( !newcl ) { // connect the client if there are empty slots. - if( !SV_FindEmptySlot( from, &nClientSlot, &newcl )) - return; - } - else - { - Con_Reportf( S_NOTE "%s:reconnect\n", NET_AdrToString( from )); - } + newcl = SV_FindEmptySlot(); - // find a client slot - ASSERT( newcl != NULL ); + if( !newcl ) + { + SV_RejectConnection( from, "server is full\n" ); + return; + } + } // build a new connection // accept the new client - sv.current_client = newcl; - newcl->edict = EDICT_NUM( (newcl - svs.clients) + 1 ); - newcl->challenge = challenge; // save challenge for checksumming - if( newcl->frames ) Mem_Free( newcl->frames ); - newcl->frames = (client_frame_t *)Z_Calloc( sizeof( client_frame_t ) * SV_UPDATE_BACKUP ); - newcl->userid = g_userid++; // create unique userid - newcl->state = cs_connected; - newcl->extensions = extensions & (NET_EXT_SPLITSIZE); - Q_strncpy( newcl->useragent, protinfo, MAX_INFO_STRING ); + frames = Mem_Realloc( host.mempool, newcl->frames, sizeof( client_frame_t ) * SV_UPDATE_BACKUP ); + memset( frames, 0, sizeof( client_frame_t ) * SV_UPDATE_BACKUP ); + SV_ClearResourceLists( newcl ); - // reset viewentities (from previous level) - memset( newcl->viewentity, 0, sizeof( newcl->viewentity )); - newcl->num_viewents = 0; - newcl->listeners = 0; + // a1ba: preserve physinfo and viewent as it's set by game logic before client connect! + { + char physinfo[MAX_INFO_STRING]; + edict_t *viewent = newcl->pViewEntity; + + memcpy( physinfo, newcl->physinfo, sizeof( physinfo )); + + memset( newcl, 0, sizeof( *newcl )); + + memcpy( newcl->physinfo, physinfo, sizeof( newcl->physinfo )); + newcl->pViewEntity = viewent; + } + + newcl->edict = EDICT_NUM(( newcl - svs.clients ) + 1 ); + newcl->frames = frames; + newcl->userid = g_userid++; // create unique userid + newcl->state = cs_connected; // now expect "spawn" command + newcl->extensions = FBitSet( extensions, NET_EXT_SPLITSIZE ); + Q_strncpy( newcl->useragent, protinfo, sizeof( newcl->useragent )); + + // HACKHACK: can hear all players by default to avoid issues + // with server.dll without voice game manager + newcl->listeners = -1; // initailize netchan - Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize ); + if( !Host_IsLocalClient( )) + SetBits( netchan_flags, NETCHAN_USE_LZSS ); + Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize, netchan_flags ); MSG_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf Q_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, "uuid" ), 32 ); @@ -430,50 +435,27 @@ void SV_ConnectClient( netadr_t from ) // build protinfo answer protinfo[0] = '\0'; - Info_SetValueForKey( protinfo, "ext", va( "%d", newcl->extensions ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions ); // send the connect packet to the client - Netchan_OutOfBandPrint( NS_SERVER, from, "client_connect %s", protinfo ); + Netchan_OutOfBandPrint( NS_SERVER, from, S2C_CONNECTION" %s", protinfo ); newcl->upstate = us_inactive; newcl->connection_started = host.realtime; newcl->cl_updaterate = 0.05; // 20 fps as default newcl->delta_sequence = -1; - newcl->flags = 0; - - - - // reset any remaining events - memset( &newcl->events, 0, sizeof( newcl->events )); // parse some info from the info strings (this can override cl_updaterate) Q_strncpy( newcl->userinfo, userinfo, sizeof( newcl->userinfo )); + SV_UserinfoChanged( newcl ); - SV_ClearResourceLists( newcl ); -#if 0 - memset( &newcl->resourcesneeded, 0, sizeof( resource_t )); - memset( &newcl->resourcesonhand, 0, sizeof( resource_t )); - newcl->resourcesneeded.pNext = newcl->resourcesneeded.pPrev = &newcl->resourcesneeded; - newcl->resourcesonhand.pNext = newcl->resourcesonhand.pPrev = &newcl->resourcesonhand; -#endif + newcl->next_messagetime = host.realtime + newcl->cl_updaterate; - newcl->next_sendinfotime = 0.0; - newcl->ignored_ents = 0; - newcl->chokecount = 0; // reset stats newcl->next_checkpingtime = -1.0; - newcl->packet_loss = 0.0f; - // if this was the first client on the server, or the last client - // the server can hold, send a heartbeat to the master. - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) - if( cl->state >= cs_connected ) count++; - - Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", newcl->name, newcl->userid, i, NET_AdrToString( newcl->netchan.remote_address )); - - if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + SV_MaybeNotifyPlayerCountChange( newcl, NET_AdrToString( newcl->netchan.remote_address )); } /* @@ -483,68 +465,101 @@ SV_FakeConnect A connection request that came from the game module ================== */ -edict_t *SV_FakeConnect( const char *netname ) +edict_t *GAME_EXPORT SV_FakeConnect( const char *netname ) { - char userinfo[MAX_INFO_STRING]; - int i, count = 0; - sv_client_t *cl; - - if( !COM_CheckString( netname )) - netname = "Bot"; + char userinfo[MAX_INFO_STRING]; + int i, count = 0; + sv_client_t *cl; // find a client slot - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) - { - if( cl->state == cs_free ) - break; - } + cl = SV_FindEmptySlot(); - if( i == svs.maxclients ) + if( !cl ) return NULL; // server is full userinfo[0] = '\0'; + if( !COM_CheckString( netname )) + netname = "Bot"; + // setup fake client params - Info_SetValueForKey( userinfo, "name", netname, MAX_INFO_STRING ); - Info_SetValueForKey( userinfo, "model", "gordon", MAX_INFO_STRING ); - Info_SetValueForKey( userinfo, "topcolor", "1", MAX_INFO_STRING ); - Info_SetValueForKey( userinfo, "bottomcolor", "1", MAX_INFO_STRING ); + Info_SetValueForKey( userinfo, "name", netname, sizeof( userinfo )); + Info_SetValueForKey( userinfo, "model", "gordon", sizeof( userinfo )); + Info_SetValueForKey( userinfo, "topcolor", "1", sizeof( userinfo )); + Info_SetValueForKey( userinfo, "bottomcolor", "1", sizeof( userinfo )); // build a new connection // accept the new client sv.current_client = cl; + if( cl->frames ) + Mem_Free( cl->frames ); // fakeclients doesn't have frames + SV_ClearResourceLists( cl ); - if( cl->frames ) Mem_Free( cl->frames ); // fakeclients doesn't have frames - memset( cl, 0, sizeof( sv_client_t )); + memset( cl, 0, sizeof( *cl )); - cl->edict = EDICT_NUM( (cl - svs.clients) + 1 ); - cl->userid = g_userid++; // create unique userid + cl->state = cs_spawned; + cl->edict = EDICT_NUM(( cl - svs.clients ) + 1 ); + cl->userid = g_userid++; // create unique userid SetBits( cl->flags, FCL_FAKECLIENT ); // parse some info from the info strings Q_strncpy( cl->userinfo, userinfo, sizeof( cl->userinfo )); + SV_UserinfoChanged( cl ); SetBits( cl->flags, FCL_RESEND_USERINFO ); - cl->next_sendinfotime = 0.0; - - // if this was the first client on the server, or the last client - // the server can hold, send a heartbeat to the master. - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) - if( cl->state >= cs_connected ) count++; - cl = sv.current_client; - - Log_Printf( "\"%s<%i><%i><>\" connected, address \"local\"\n", cl->name, cl->userid, i ); - SetBits( cl->edict->v.flags, FL_CLIENT|FL_FAKECLIENT ); // mark it as fakeclient cl->connection_started = host.realtime; - cl->state = cs_spawned; - if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + SV_MaybeNotifyPlayerCountChange( cl, "local" ); return cl->edict; } +/* +================== +SV_Kick_f + +Kick a user off of the server +================== +*/ +void SV_KickPlayer( sv_client_t *cl, const char *fmt, ... ) +{ + const char *clientId; + va_list va; + char buf[MAX_VA_STRING]; + + if( NET_IsLocalAddress( cl->netchan.remote_address )) + { + Con_Printf( "The local player cannot be kicked!\n" ); + return; + } + + clientId = SV_GetClientIDString( cl ); + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + if( buf[0] ) + { + Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\" (message \"%s\")\n", cl->name, cl->userid, clientId, buf ); + SV_BroadcastPrintf( cl, "%s was kicked with message: \"%s\"\n", cl->name, buf ); + SV_ClientPrintf( cl, "You were kicked from the game with message: \"%s\"\n", buf ); + if( cl->useragent[0] ) + Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, S2C_ERRORMSG"\nKicked with message:\n%s\n", buf ); + } + else + { + Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\"\n", cl->name, cl->userid, clientId ); + SV_BroadcastPrintf( cl, "%s was kicked\n", cl->name ); + SV_ClientPrintf( cl, "You were kicked from the game\n" ); + if( cl->useragent[0] ) + Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, S2C_ERRORMSG"\nYou were kicked from the game\n" ); + } + + SV_DropClient( cl, false ); +} + /* ===================== SV_DropClient @@ -590,7 +605,7 @@ void SV_DropClient( sv_client_t *cl, qboolean crash ) cl->frames = NULL; if( NET_CompareBaseAdr( cl->netchan.remote_address, host.rd.address )) - SV_EndRedirect(); + SV_EndRedirect( &host.rd ); // throw away any residual garbage in the channel. Netchan_Clear( &cl->netchan ); @@ -617,7 +632,7 @@ void SV_DropClient( sv_client_t *cl, qboolean crash ) } if( i == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -627,54 +642,49 @@ SVC COMMAND REDIRECT ============================================================================== */ -void SV_BeginRedirect( netadr_t adr, rdtype_t target, char *buffer, size_t buffersize, void (*flush)) +static void SV_BeginRedirect( host_redirect_t *rd, netadr_t adr, rdtype_t target, char *buffer, size_t buffersize, void (*flush)) { - if( !target || !buffer || !buffersize || !flush ) - return; - - host.rd.target = target; - host.rd.buffer = buffer; - host.rd.buffersize = buffersize; - host.rd.flush = flush; - host.rd.address = adr; - host.rd.buffer[0] = 0; - if( host.rd.lines == 0 ) - host.rd.lines = -1; + rd->target = target; + rd->buffer = buffer; + rd->buffersize = buffersize; + rd->flush = flush; + rd->address = adr; + rd->buffer[0] = 0; + if( rd->lines == 0 ) + rd->lines = -1; } -void SV_FlushRedirect( netadr_t adr, int dest, char *buf ) +static void SV_FlushRedirect( netadr_t adr, int dest, char *buf ) { - if( sv.current_client && FBitSet( sv.current_client->flags, FCL_FAKECLIENT )) - return; - switch( dest ) { case RD_PACKET: - Netchan_OutOfBandPrint( NS_SERVER, adr, "print\n%s", buf ); + Netchan_OutOfBandPrint( NS_SERVER, adr, A2C_PRINT"\n%s", buf ); break; case RD_CLIENT: - if( !sv.current_client ) return; // client not set + if( !sv.current_client || !FBitSet( sv.current_client->flags, FCL_FAKECLIENT )) + return; // client not set MSG_BeginServerCmd( &sv.current_client->netchan.message, svc_print ); MSG_WriteString( &sv.current_client->netchan.message, buf ); break; - case RD_NONE: - Con_Printf( S_ERROR "SV_FlushRedirect: %s: invalid destination\n", NET_AdrToString( adr )); + default: + Con_Printf( S_ERROR "%s: %s: invalid destination\n", __func__, NET_AdrToString( adr )); break; } } -void SV_EndRedirect( void ) +void SV_EndRedirect( host_redirect_t *rd ) { - if( host.rd.lines > 0 ) + if( rd->lines > 0 ) return; - if( host.rd.flush ) - host.rd.flush( host.rd.address, host.rd.target, host.rd.buffer ); + if( rd->flush ) + rd->flush( rd->address, rd->target, rd->buffer ); - host.rd.target = 0; - host.rd.buffer = NULL; - host.rd.buffersize = 0; - host.rd.flush = NULL; + rd->target = RD_NONE; + rd->buffer = NULL; + rd->buffersize = 0; + rd->flush = NULL; } /* @@ -684,24 +694,26 @@ Rcon_Print Print message to rcon buffer and send to rcon redirect target ================ */ -void Rcon_Print( const char *pMsg ) +void Rcon_Print( host_redirect_t *rd, const char *pMsg ) { - if( host.rd.target && host.rd.lines && host.rd.flush && host.rd.buffer ) + size_t len; + + if( !rd->target || !rd->lines || !rd->flush || !rd->buffer ) + return; + + len = Q_strncat( rd->buffer, pMsg, rd->buffersize ); + + if( len && rd->buffer[len - 1] == '\n' ) { - size_t len = Q_strncat( host.rd.buffer, pMsg, host.rd.buffersize ); + rd->flush( rd->address, rd->target, rd->buffer ); - if( len && host.rd.buffer[len-1] == '\n' ) - { - host.rd.flush( host.rd.address, host.rd.target, host.rd.buffer ); + if( rd->lines > 0 ) + rd->lines--; - if( host.rd.lines > 0 ) - host.rd.lines--; + rd->buffer[0] = 0; - host.rd.buffer[0] = 0; - - if( !host.rd.lines ) - Msg( "End of redirection!\n" ); - } + if( !rd->lines ) + Msg( "End of redirection!\n" ); } } @@ -714,27 +726,21 @@ Returns a pointer to a static char for most likely only printing. */ const char *SV_GetClientIDString( sv_client_t *cl ) { - static char result[MAX_QPATH]; + static char result[MAX_QPATH]; - if( !cl ) return ""; + if( !cl ) + return ""; if( FBitSet( cl->flags, FCL_FAKECLIENT )) - { - Q_strncpy( result, "ID_BOT", sizeof( result )); - } - else if( NET_IsLocalAddress( cl->netchan.remote_address )) - { - Q_strncpy( result, "ID_LOOPBACK", sizeof( result )); - } - else if( sv_lan.value ) - { - Q_strncpy( result, "ID_LAN", sizeof( result )); - } - else - { - Q_snprintf( result, sizeof( result ), "ID_%s", MD5_Print( (byte *)cl->hashedcdkey )); - } + return "ID_BOT"; + if( NET_IsLocalAddress( cl->netchan.remote_address )) + return "ID_LOOPBACK"; + + if( sv_lan.value ) + return "ID_LAN"; + + Q_snprintf( result, sizeof( result ), "ID_%s", cl->hashedcdkey ); return result; } @@ -745,7 +751,7 @@ sv_client_t *SV_ClientById( int id ) ASSERT( id >= 0 ); - for( i = 0, cl = svs.clients; i < svgame.globals->maxClients; i++, cl++ ) + for( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ ) { if( !cl->state ) continue; @@ -765,7 +771,7 @@ sv_client_t *SV_ClientByName( const char *name ) if( !COM_CheckString( name )) return NULL; - for( i = 0, cl = svs.clients; i < svgame.globals->maxClients; i++, cl++ ) + for( i = 0, cl = svs.clients; cl && i < svgame.globals->maxClients; i++, cl++ ) { if( !cl->state ) continue; @@ -783,16 +789,12 @@ SV_TestBandWidth ================ */ -void SV_TestBandWidth( netadr_t from ) +static void SV_TestBandWidth( netadr_t from ) { - int version = Q_atoi( Cmd_Argv( 1 )); - int packetsize = Q_atoi( Cmd_Argv( 2 )); - byte send_buf[FRAGMENT_MAX_SIZE]; - dword crcValue = 0; - byte *filepos; - int crcpos; - file_t *test; - sizebuf_t send; + const int version = Q_atoi( Cmd_Argv( 1 )); + const int packetsize = Q_atoi( Cmd_Argv( 2 )); + uint32_t crc; + int ofs; // don't waste time of protocol mismatched if( version != PROTOCOL_VERSION ) @@ -801,32 +803,29 @@ void SV_TestBandWidth( netadr_t from ) return; } - test = FS_Open( "gfx.wad", "rb", false ); - - if( FS_FileLength( test ) < sizeof( send_buf )) + // quickly reject invalid packets + if( !sv_allow_testpacket.value || !svs.testpacket_buf || + ( packetsize <= FRAGMENT_MIN_SIZE ) || + ( packetsize > FRAGMENT_MAX_SIZE )) { // skip the test and just get challenge - SV_GetChallenge( from ); + SV_SendChallenge( from ); return; } - // write the packet header - MSG_Init( &send, "BandWidthPacket", send_buf, sizeof( send_buf )); - MSG_WriteLong( &send, -1 ); // -1 sequence means out of band - MSG_WriteString( &send, "testpacket" ); - crcpos = MSG_GetNumBytesWritten( &send ); - MSG_WriteLong( &send, 0 ); // reserve space for crc - filepos = send.pData + MSG_GetNumBytesWritten( &send ); - packetsize = packetsize - MSG_GetNumBytesWritten( &send ); // adjust the packet size - FS_Read( test, filepos, packetsize ); - FS_Close( test ); + // don't go out of bounds + ofs = packetsize - svs.testpacket_filepos - 1; + if(( ofs < 0 ) || ( ofs > svs.testpacket_filelen )) + { + SV_SendChallenge( from ); + return; + } - CRC32_ProcessBuffer( &crcValue, filepos, packetsize ); // calc CRC - MSG_SeekToBit( &send, packetsize << 3, SEEK_CUR ); - *(uint *)&send.pData[crcpos] = crcValue; + crc = svs.testpacket_crcs[ofs]; + memcpy( svs.testpacket_crcpos, &crc, sizeof( crc )); // send the datagram - NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), from ); + NET_SendPacket( NS_SERVER, packetsize, MSG_GetData( &svs.testpacket ), from ); } /* @@ -835,7 +834,7 @@ SV_Ack ================ */ -void SV_Ack( netadr_t from ) +static void SV_Ack( netadr_t from ) { Con_Printf( "ping %s\n", NET_AdrToString( from )); } @@ -848,41 +847,70 @@ Responds with short info for broadcast scans The second parameter should be the current protocol version number. ================ */ -void SV_Info( netadr_t from, int protocolVersion ) +static void SV_Info( netadr_t from, int protocolVersion ) { - char string[MAX_INFO_STRING]; + char s[512]; // ignore in single player if( svs.maxclients == 1 || !svs.initialized ) return; - string[0] = '\0'; + s[0] = '\0'; if( protocolVersion != PROTOCOL_VERSION ) { - Q_snprintf( string, sizeof( string ), "%s: wrong version\n", hostname.string ); + Q_snprintf( s, sizeof( s ), "%s: wrong version\n", hostname.string ); } else { - int i, count, bots; - qboolean havePassword = COM_CheckStringEmpty( sv_password.string ); + int count; + int bots; + int remaining; + char temp[sizeof( s )]; SV_GetPlayerCount( &count, &bots ); // a1ba: send protocol version to distinguish old engine and new - Info_SetValueForKey( string, "p", va( "%i", PROTOCOL_VERSION ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "host", hostname.string, MAX_INFO_STRING ); - Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); - Info_SetValueForKey( string, "dm", va( "%i", (int)svgame.globals->deathmatch ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "team", va( "%i", (int)svgame.globals->teamplay ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "coop", va( "%i", (int)svgame.globals->coop ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "numcl", va( "%i", count ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "maxcl", va( "%i", svs.maxclients ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING ); - Info_SetValueForKey( string, "password", havePassword ? "1" : "0", MAX_INFO_STRING ); + Info_SetValueForKeyf( s, "p", sizeof( s ), "%i", PROTOCOL_VERSION ); + Info_SetValueForKey( s, "map", sv.name, sizeof( s )); + Info_SetValueForKey( s, "dm", svgame.globals->deathmatch ? "1" : "0", sizeof( s )); + Info_SetValueForKey( s, "team", svgame.globals->teamplay ? "1" : "0", sizeof( s )); + Info_SetValueForKey( s, "coop", svgame.globals->coop ? "1" : "0", sizeof( s )); + Info_SetValueForKeyf( s, "numcl", sizeof( s ), "%i", count ); + Info_SetValueForKeyf( s, "maxcl", sizeof( s ), "%i", svs.maxclients ); + Info_SetValueForKey( s, "gamedir", GI->gamefolder, sizeof( s )); + Info_SetValueForKey( s, "password", SV_HavePassword() ? "1" : "0", sizeof( s )); + + // write host last so we can try to cut off too long hostnames + // TODO: value size limit for infostrings + remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1; + if( remaining < 0 ) + { + // should never happen? + Con_Printf( S_ERROR "%s: infostring overflow!\n", __func__ ); + return; + } + Q_strncpy( temp, hostname.string, remaining ); + Info_SetValueForKey( s, "host", temp, sizeof( s )); } - Netchan_OutOfBandPrint( NS_SERVER, from, "info\n%s", string ); + Netchan_OutOfBandPrint( NS_SERVER, from, A2A_INFO"\n%s", s ); +} + +static void SV_ConnectNatClient( netadr_t from ) +{ + netadr_t to; + + if( !sv_nat.value || !NET_IsMasterAdr( from )) + return; + + if( !NET_StringToAdr( Cmd_Argv( 1 ), &to )) + return; + + if( NET_IsReservedAdr( to )) + return; + + SV_Info( to, PROTOCOL_VERSION ); } /* @@ -892,11 +920,15 @@ SV_BuildNetAnswer Responds with long info for local and broadcast requests ================ */ -void SV_BuildNetAnswer( netadr_t from ) +static void SV_BuildNetAnswer( netadr_t from ) { - char string[MAX_INFO_STRING]; - int version, context, type; - int i, count = 0; + const cvar_t *cv; + char string[4096]; + int version; + int context; + int type; + int count = 0; + int i; // ignore in single player if( svs.maxclients == 1 || !svs.initialized ) @@ -906,80 +938,84 @@ void SV_BuildNetAnswer( netadr_t from ) context = Q_atoi( Cmd_Argv( 2 )); type = Q_atoi( Cmd_Argv( 3 )); + string[0] = 0; + if( version != PROTOCOL_VERSION ) { - // handle the unsupported protocol - string[0] = '\0'; - Info_SetValueForKey( string, "neterror", "protocol", MAX_INFO_STRING ); - // send error unsupported protocol - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string ); + Info_SetValueForKey( string, "neterror", "protocol", sizeof( string )); + Netchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO" %i %i %s\n", context, type, string ); return; } - if( type == NETAPI_REQUEST_PING ) + switch( type ) { - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, "" ); - } - else if( type == NETAPI_REQUEST_RULES ) - { - // send serverinfo - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, svs.serverinfo ); - } - else if( type == NETAPI_REQUEST_PLAYERS ) - { - string[0] = '\0'; + case NETAPI_REQUEST_PING: + break; + case NETAPI_REQUEST_RULES: + for( cv = Cvar_GetList( ); cv; cv = cv->next ) + { + if( !FBitSet( cv->flags, FCVAR_SERVER )) + continue; + if( FBitSet( cv->flags, FCVAR_PROTECTED )) + { + if( COM_CheckStringEmpty( cv->string ) && Q_stricmp( cv->string, "none" )) + Info_SetValueForKey( string, cv->name, "1", sizeof( string )); + else Info_SetValueForKey( string, cv->name, "0", sizeof( string )); + } + else Info_SetValueForKey( string, cv->name, cv->string, sizeof( string )); + + count++; + } + + Info_SetValueForKeyf( string, "rules", sizeof( string ), "%i", count ); + break; + case NETAPI_REQUEST_PLAYERS: + if( !sv_expose_player_list.value || SV_HavePassword( )) + { + Info_SetValueForKey( string, "neterror", "forbidden", sizeof( string )); + } + else + { + for( i = 0; i < svs.maxclients; i++ ) + { + const sv_client_t *cl = &svs.clients[i]; + + if( cl->state < cs_connected ) + continue; + + Info_SetValueForKey( string, va( "p%iname", count ), cl->name, sizeof( string )); + Info_SetValueForKeyf( string, va( "p%ifrags", count ), sizeof( string ), "%i", (int)cl->edict->v.frags ); + Info_SetValueForKeyf( string, va( "p%itime", count ), sizeof( string ), "%f", host.realtime - cl->connection_started ); + + count++; + } + + Info_SetValueForKeyf( string, "players", sizeof( string ), "%i", count ); + } + break; + case NETAPI_REQUEST_DETAILS: for( i = 0; i < svs.maxclients; i++ ) { if( svs.clients[i].state >= cs_connected ) - { - edict_t *ed = svs.clients[i].edict; - float time = host.realtime - svs.clients[i].connection_started; - Q_strncat( string, va( "%c\\%s\\%i\\%f\\", count, svs.clients[i].name, (int)ed->v.frags, time ), sizeof( string )); count++; - } } - // send playernames - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string ); - } - else if( type == NETAPI_REQUEST_DETAILS ) - { - for( i = 0; i < svs.maxclients; i++ ) - if( svs.clients[i].state >= cs_connected ) - count++; - - string[0] = '\0'; - Info_SetValueForKey( string, "hostname", hostname.string, MAX_INFO_STRING ); - Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING ); - Info_SetValueForKey( string, "current", va( "%i", count ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "max", va( "%i", svs.maxclients ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); - - // send serverinfo - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string ); - } - else - { - string[0] = '\0'; - Info_SetValueForKey( string, "neterror", "undefined", MAX_INFO_STRING ); - + // should match SV_SourceQuery_Details + Info_SetValueForKey( string, "hostname", hostname.string, sizeof( string )); + Info_SetValueForKey( string, "gamedir", GI->gamefolder, sizeof( string )); + Info_SetValueForKeyf( string, "current", sizeof( string ), "%i", count ); + Info_SetValueForKeyf( string, "max", sizeof( string ), "%i", svs.maxclients ); + Info_SetValueForKey( string, "map", sv.name, sizeof( string )); + break; + default: // send error undefined request type - Netchan_OutOfBandPrint( NS_SERVER, from, "netinfo %i %i %s\n", context, type, string ); + Info_SetValueForKey( string, "neterror", "undefined", sizeof( string )); + break; } -} -/* -================ -SV_Ping - -Just responds with an acknowledgement -================ -*/ -void SV_Ping( netadr_t from ) -{ - Netchan_OutOfBandPrint( NS_SERVER, from, "ack" ); + Netchan_OutOfBandPrint( NS_SERVER, from, A2A_NETINFO" %i %i %s\n", context, type, string ); } /* @@ -987,7 +1023,7 @@ void SV_Ping( netadr_t from ) Rcon_Validate ================ */ -qboolean Rcon_Validate( void ) +static qboolean Rcon_Validate( void ) { if( !COM_CheckString( rcon_password.string )) return false; @@ -1007,27 +1043,36 @@ Redirect all printfs */ void SV_RemoteCommand( netadr_t from, sizebuf_t *msg ) { - static char outputbuf[2048]; - char remaining[1024]; + const char *adr; int i; - Con_Printf( "Rcon from %s:\n%s\n", NET_AdrToString( from ), MSG_GetData( msg ) + 4 ); - Log_Printf( "Rcon: \"%s\" from \"%s\"\n", MSG_GetData( msg ) + 4, NET_AdrToString( from )); - SV_BeginRedirect( from, RD_PACKET, outputbuf, sizeof( outputbuf ) - 16, SV_FlushRedirect ); + if( !rcon_enable.value || !COM_CheckStringEmpty( rcon_password.string )) + return; + + adr = NET_AdrToString( from ); + + Con_Printf( "Rcon from %s:\n%s\n", adr, MSG_GetData( msg ) + 4 ); + Log_Printf( "Rcon: \"%s\" from \"%s\"\n", MSG_GetData( msg ) + 4, adr ); if( Rcon_Validate( )) { + static char outputbuf[2048]; + char remaining[1024]; + char *p = remaining; + remaining[0] = 0; for( i = 2; i < Cmd_Argc(); i++ ) { - Q_strcat( remaining, Cmd_Argv( i )); - Q_strcat( remaining, " " ); + p += Q_strncpy( p, "\"", sizeof( remaining ) - ( p - remaining )); + p += Q_strncpy( p, Cmd_Argv( i ), sizeof( remaining ) - ( p - remaining )); + p += Q_strncpy( p, "\" ", sizeof( remaining ) - ( p - remaining )); } + + SV_BeginRedirect( &host.rd, from, RD_PACKET, outputbuf, sizeof( outputbuf ) - 16, SV_FlushRedirect ); Cmd_ExecuteString( remaining ); + SV_EndRedirect( &host.rd ); } else Con_Printf( S_ERROR "Bad rcon_password.\n" ); - - SV_EndRedirect(); } /* @@ -1037,7 +1082,7 @@ SV_CalcPing recalc ping on current client =================== */ -int SV_CalcPing( sv_client_t *cl ) +int SV_CalcPing( const sv_client_t *cl ) { float ping = 0; int i, count; @@ -1081,7 +1126,7 @@ SV_EstablishTimeBase Finangles latency and the like. =================== */ -void SV_EstablishTimeBase( sv_client_t *cl, usercmd_t *cmds, int dropped, int numbackup, int numcmds ) +static void SV_EstablishTimeBase( sv_client_t *cl, const usercmd_t *cmds, int dropped, int numbackup, int numcmds ) { double runcmd_time = 0.0; int i, cmdnum = dropped; @@ -1115,7 +1160,7 @@ SV_CalcClientTime compute latency for client =================== */ -float SV_CalcClientTime( sv_client_t *cl ) +static float SV_CalcClientTime( sv_client_t *cl ) { float minping, maxping; float ping = 0.0f; @@ -1236,9 +1281,8 @@ otherwise see code SV_UpdateMovevars() */ void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg ) { - movevars_t nullmovevars; + const movevars_t nullmovevars = { 0 }; - memset( &nullmovevars, 0, sizeof( nullmovevars )); MSG_WriteDeltaMovevars( msg, &nullmovevars, &svgame.movevars ); } @@ -1265,19 +1309,6 @@ qboolean SV_ShouldUpdatePing( sv_client_t *cl ) return FBitSet( cl->lastcmd.buttons, IN_SCORE ) ? true : false; } -/* -=================== -SV_IsPlayerIndex - -=================== -*/ -qboolean SV_IsPlayerIndex( int idx ) -{ - if( idx > 0 && idx <= svs.maxclients ) - return true; - return false; -} - /* =================== SV_GetPlayerStats @@ -1314,7 +1345,7 @@ Called when a player connects to a server or respawns in a deathmatch. ============ */ -void SV_PutClientInServer( sv_client_t *cl ) +static void SV_PutClientInServer( sv_client_t *cl ) { static byte msg_buf[MAX_INIT_MSG + 0x200]; // MAX_INIT_MSG + some space edict_t *ent = cl->edict; @@ -1390,7 +1421,7 @@ void SV_PutClientInServer( sv_client_t *cl ) if( svgame.globals->cdAudioTrack ) { MSG_BeginServerCmd( &msg, svc_stufftext ); - MSG_WriteString( &msg, va( "cd loop %3d\n", svgame.globals->cdAudioTrack )); + MSG_WriteStringf( &msg, "cd loop %3d\n", svgame.globals->cdAudioTrack ); svgame.globals->cdAudioTrack = 0; } @@ -1447,7 +1478,7 @@ SV_UpdateClientView Resend the client viewentity (used for demos) ============ */ -void SV_UpdateClientView( sv_client_t *cl ) +static void SV_UpdateClientView( sv_client_t *cl ) { int viewEnt; @@ -1491,27 +1522,6 @@ void SV_BuildReconnect( sizebuf_t *msg ) MSG_WriteString( msg, "reconnect\n" ); } -/* -================== -SV_WriteDeltaDescriptionToClient - -send delta communication encoding -================== -*/ -void SV_WriteDeltaDescriptionToClient( sizebuf_t *msg ) -{ - int tableIndex; - int fieldIndex; - - for( tableIndex = 0; tableIndex < Delta_NumTables(); tableIndex++ ) - { - delta_info_t *dt = Delta_FindStructByIndex( tableIndex ); - - for( fieldIndex = 0; fieldIndex < dt->numFields; fieldIndex++ ) - Delta_WriteTableField( msg, tableIndex, &dt->pFields[fieldIndex] ); - } -} - /* ================ SV_SendServerdata @@ -1556,7 +1566,7 @@ void SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl ) } // send delta-encoding - SV_WriteDeltaDescriptionToClient( msg ); + Delta_WriteDescriptionToClient( msg ); // now client know delta and can reading encoded messages SV_FullUpdateMovevars( cl, msg ); @@ -1602,6 +1612,7 @@ static qboolean SV_New_f( sv_client_t *cl ) sizebuf_t msg; int i; + memset( msg_buf, 0, sizeof( msg_buf )); MSG_Init( &msg, "New", msg_buf, sizeof( msg_buf )); if( cl->state != cs_connected ) @@ -1629,7 +1640,7 @@ static qboolean SV_New_f( sv_client_t *cl ) // server info string MSG_BeginServerCmd( &msg, svc_stufftext ); - MSG_WriteString( &msg, va( "fullserverinfo \"%s\"\n", SV_Serverinfo( ))); + MSG_WriteStringf( &msg, "fullserverinfo \"%s\"\n", svs.serverinfo ); // collect the info about all the players and send to me for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ ) @@ -1686,7 +1697,7 @@ static qboolean SV_Pause_f( sv_client_t *cl ) if( UI_CreditsActive( )) return true; - if( !sv_pausable->value ) + if( !sv_pausable.value ) { SV_ClientPrintf( cl, "Pause not allowed.\n" ); return true; @@ -1706,6 +1717,53 @@ static qboolean SV_Pause_f( sv_client_t *cl ) return true; } +static qboolean SV_ShouldUpdateUserinfo( sv_client_t *cl ) +{ + qboolean allow = true; // predict state + + if( !sv_userinfo_enable_penalty.value ) + return allow; + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + return allow; + + if( Host_IsLocalGame( )) + return allow; + + // start from 1 second + if( !cl->userinfo_penalty ) + cl->userinfo_penalty = sv_userinfo_penalty_time.value; + + // player changes userinfo after limit time window, but before + // next timewindow + // he seems to be spammer, so just increase change attempts + if( host.realtime < cl->userinfo_next_changetime + cl->userinfo_penalty * sv_userinfo_penalty_multiplier.value ) + { + // player changes userinfo too quick! ignore! + if( host.realtime < cl->userinfo_next_changetime && cl->userinfo_change_attempts > 0 ) + { + Con_Reportf( "%s: ignore userinfo update for %s: penalty %f, attempts %i\n", + __func__, cl->name, cl->userinfo_penalty, cl->userinfo_change_attempts ); + allow = false; + } + + cl->userinfo_change_attempts++; + } + + // they spammed too fast, increase penalty + if( cl->userinfo_change_attempts >= (int)sv_userinfo_penalty_attempts.value ) + { + cl->userinfo_penalty *= sv_userinfo_penalty_multiplier.value; + cl->userinfo_change_attempts = 0; + + Con_Reportf( "%s: penalty set %f for %s\n", __func__, cl->userinfo_penalty, cl->name ); + } + + cl->userinfo_next_changetime = host.realtime + cl->userinfo_penalty * sv_userinfo_penalty_multiplier.value; + + return allow; +} + /* ================= SV_UserinfoChanged @@ -1714,7 +1772,7 @@ Pull specific info from a newly changed userinfo string into a more C freindly form. ================= */ -void SV_UserinfoChanged( sv_client_t *cl ) +static void SV_UserinfoChanged( sv_client_t *cl ) { int i, dupc = 1; edict_t *ent = cl->edict; @@ -1725,6 +1783,12 @@ void SV_UserinfoChanged( sv_client_t *cl ) if( !COM_CheckString( cl->userinfo )) return; + if( !SV_ShouldUpdateUserinfo( cl )) + return; + + if( !Info_IsValid( cl->userinfo )) + return; + val = Info_ValueForKey( cl->userinfo, "name" ); Q_strncpy( name2, val, sizeof( name2 )); COM_TrimSpace( name2, name1 ); @@ -1799,13 +1863,10 @@ void SV_UserinfoChanged( sv_client_t *cl ) val = Info_ValueForKey( cl->userinfo, "cl_updaterate" ); - if( COM_CheckString( val ) ) + if( COM_CheckString( val )) { - if( Q_atoi( val ) != 0 ) - { - cl->cl_updaterate = 1.0 / bound( sv_minupdaterate.value, Q_atoi( val ), sv_maxupdaterate.value ); - } - else cl->cl_updaterate = 0.0; + float rate = Q_atoi( val ); + cl->cl_updaterate = 1.0 / bound( sv_minupdaterate.value, rate, sv_maxupdaterate.value ); } // call prog code to allow overrides @@ -1816,20 +1877,6 @@ void SV_UserinfoChanged( sv_client_t *cl ) ent->v.netname = MAKE_STRING( cl->name ); } -/* -================== -SV_UpdateUserinfo_f -================== -*/ -static qboolean SV_UpdateUserinfo_f( sv_client_t *cl ) -{ - Q_strncpy( cl->userinfo, Cmd_Argv( 1 ), sizeof( cl->userinfo )); - - if( cl->state >= cs_connected ) - SetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info - return true; -} - /* ================== SV_SetInfo_f @@ -1853,7 +1900,7 @@ static qboolean SV_Noclip_f( sv_client_t *cl ) { edict_t *pEntity = cl->edict; - if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + if( sv.background || !Cvar_VariableInteger( "sv_cheats" )) return true; if( pEntity->v.movetype != MOVETYPE_NOCLIP ) @@ -1879,7 +1926,7 @@ static qboolean SV_Godmode_f( sv_client_t *cl ) { edict_t *pEntity = cl->edict; - if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + if( sv.background || !Cvar_VariableInteger( "sv_cheats" )) return true; pEntity->v.flags = pEntity->v.flags ^ FL_GODMODE; @@ -1900,7 +1947,7 @@ static qboolean SV_Notarget_f( sv_client_t *cl ) { edict_t *pEntity = cl->edict; - if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + if( sv.background || !Cvar_VariableInteger( "sv_cheats" )) return true; pEntity->v.flags = pEntity->v.flags ^ FL_NOTARGET; @@ -1921,7 +1968,7 @@ static qboolean SV_Kill_f( sv_client_t *cl ) { if( !SV_IsValidEdict( cl->edict )) return true; - + if( cl->state != cs_spawned ) { SV_ClientPrintf( cl, "Can't suicide - not connected!\n" ); @@ -1952,6 +1999,7 @@ static qboolean SV_SendRes_f( sv_client_t *cl ) if( cl->state != cs_connected ) return false; + memset( buffer, 0, sizeof( buffer )); MSG_Init( &msg, "SendResources", buffer, sizeof( buffer )); if( svs.maxclients > 1 && FBitSet( cl->flags, FCL_SEND_RESOURCES )) @@ -2042,9 +2090,9 @@ static qboolean SV_DownloadFile_f( sv_client_t *cl ) memset( &custResource, 0, sizeof( custResource ) ); COM_HexConvert( name + 4, 32, md5 ); - if( HPAK_ResourceForHash( CUSTOM_RES_PATH, md5, &custResource )) + if( HPAK_ResourceForHash( hpk_custom_file.string, md5, &custResource )) { - if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &custResource, &pbuf, &size )) + if( HPAK_GetDataPointer( hpk_custom_file.string, &custResource, &pbuf, &size )) { if( size ) { @@ -2082,6 +2130,8 @@ static qboolean SV_Spawn_f( sv_client_t *cl ) SV_PutClientInServer( cl ); + cl->state = cs_spawning; + // if we are paused, tell the clients if( sv.paused ) { @@ -2099,11 +2149,14 @@ SV_Begin_f */ static qboolean SV_Begin_f( sv_client_t *cl ) { - if( cl->state != cs_connected ) + // make sure client has passed connection process correctly + if( cl->state != cs_spawning ) return false; // now client is spawned cl->state = cs_spawned; + cl->connecttime = host.realtime; + return true; } @@ -2114,30 +2167,902 @@ SV_SendBuildInfo_f */ static qboolean SV_SendBuildInfo_f( sv_client_t *cl ) { - SV_ClientPrintf( cl, "Server running %s %s (build %i-%s, %s-%s)\n", - XASH_ENGINE_NAME, XASH_VERSION, Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() ); + if( cl->state != cs_spawned ) + return false; + + SV_ClientPrintf( cl, "Server running " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n", + Q_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() ); return true; } - -ucmd_t ucmds[] = +/* +================== +SV_ClientStatus_f +================== +*/ +static qboolean SV_ClientStatus_f( sv_client_t *cl ) +{ + netadr_t ip4, ip6; + vec3_t origin = { 0 }; + int clients, bots, i; + + if( cl->state != cs_spawned ) + return false; + + NET_GetLocalAddress( &ip4, &ip6 ); + if( cl->edict ) + VectorCopy( cl->edict->v.origin, origin ); + SV_GetPlayerCount( &clients, &bots ); + + SV_ClientPrintf( cl, + "hostname: %s\n" + "version: %i/%s %d\n", + hostname.string, + PROTOCOL_VERSION, XASH_VERSION, Q_buildnum( )); + + if( ip4.type == NA_IP ) + SV_ClientPrintf( cl, "tcp/ip: %s\n", NET_AdrToString( ip4 )); + if( ip6.type == NA_IP6 ) + SV_ClientPrintf( cl, "tcp/ipv6: %s\n", NET_AdrToString( ip6 )); + + SV_ClientPrintf( cl, + "map:\t%s at %d x, %d y, %d z\n" + "players: %i active (%i max)\n" + "# score ping dev playtime name\n", + sv.name, (int)origin[0], (int)origin[1], (int)origin[2], + clients, svs.maxclients ); + + for( i = 0; i < svs.maxclients; i++ ) + { + const sv_client_t *pcl = &svs.clients[i]; + int j = 0; + int input_devices; + const char *s; + char devices[8]; + + if( pcl->state != cs_spawned ) + continue; + + if( FBitSet( pcl->flags, FCL_FAKECLIENT )) + s = "Bot "; + else + s = va( "%i", SV_CalcPing( pcl )); + + input_devices = Q_atoi( Info_ValueForKey( pcl->useragent, "d" )); + + if( FBitSet( input_devices, INPUT_DEVICE_MOUSE )) + devices[j++] = 'm'; + + if( FBitSet( input_devices, INPUT_DEVICE_TOUCH )) + devices[j++] = 't'; + + if( FBitSet( input_devices, INPUT_DEVICE_JOYSTICK )) + devices[j++] = 'j'; + + if( FBitSet( input_devices, INPUT_DEVICE_VR )) + devices[j++] = 'v'; + + if( j == 0 ) + Q_strncpy( devices, "n/a", sizeof( devices )); + else + devices[j++] = 0; + + SV_ClientPrintf( cl, + "%2i %5i %4s %4s %g %s\n", + i, (int)pcl->edict->v.frags, s, devices, host.realtime - pcl->netchan.connect_time, pcl->name ); + } + + return true; +} + +/* +================== +SV_GetCrossEnt +================== +*/ +static edict_t *SV_GetCrossEnt( edict_t *player ) +{ + edict_t *ent = EDICT_NUM(1); + edict_t *closest = NULL; + float flMaxDot = 0.94; + vec3_t forward; + vec3_t viewPos; + int i; + float maxLen = 1000; + + AngleVectors( player->v.v_angle, forward, NULL, NULL ); + VectorAdd( player->v.origin, player->v.view_ofs, viewPos ); + + // find bmodels by trace + { + trace_t trace; + vec3_t target; + + VectorMA( viewPos, 1000, forward, target ); + trace = SV_Move( viewPos, vec3_origin, vec3_origin, target, 0, player, false ); + closest = trace.ent; + VectorSubtract( viewPos, trace.endpos, target ); + maxLen = VectorLength(target) + 30; + } + + // check untraceable entities + for ( i = 1; i < svgame.numEntities; i++, ent++ ) + { + vec3_t vecLOS; + vec3_t vecOrigin; + float flDot, traceLen; + vec3_t boxSize; + trace_t trace; + vec3_t vecTrace; + + if( ent->free ) + continue; + + if( ent->v.solid == SOLID_BSP || ent->v.movetype == MOVETYPE_PUSHSTEP ) + continue; // bsp models will be found by trace later + + // do not touch following weapons + if( ent->v.movetype == MOVETYPE_FOLLOW ) + continue; + + if( ent == player ) + continue; + + VectorAdd( ent->v.absmin, ent->v.absmax, vecOrigin ); + VectorScale( vecOrigin, 0.5, vecOrigin ); + + VectorSubtract( vecOrigin, viewPos, vecLOS ); + traceLen = VectorLength(vecLOS); + + if( traceLen > maxLen ) + continue; + + VectorCopy( ent->v.size, boxSize); + VectorScale( boxSize, 0.5, boxSize ); + + if ( vecLOS[0] > boxSize[0] ) + vecLOS[0] -= boxSize[0]; + else if ( vecLOS[0] < -boxSize[0] ) + vecLOS[0] += boxSize[0]; + else + vecLOS[0] = 0; + + if ( vecLOS[1] > boxSize[1] ) + vecLOS[1] -= boxSize[1]; + else if ( vecLOS[1] < -boxSize[1] ) + vecLOS[1] += boxSize[1]; + else + vecLOS[1] = 0; + + if ( vecLOS[2] > boxSize[2] ) + vecLOS[2] -= boxSize[2]; + else if ( vecLOS[2] < -boxSize[2] ) + vecLOS[2] += boxSize[2]; + else + vecLOS[2] = 0; + VectorNormalize( vecLOS ); + + flDot = DotProduct (vecLOS , forward); + if ( flDot <= flMaxDot ) + continue; + + trace = SV_Move( viewPos, vec3_origin, vec3_origin, vecOrigin, 0, player, false ); + VectorSubtract( trace.endpos, viewPos, vecTrace ); + if( VectorLength( vecTrace ) + 30 < traceLen ) + continue; + closest = ent, flMaxDot = flDot; + } + + return closest; +} + +/* +================== +SV_EntFindSingle +================== +*/ +static edict_t *SV_EntFindSingle( sv_client_t *cl, const char *pattern ) +{ + edict_t *ent = NULL; + int i = 0; + + if( Q_isdigit( pattern ) ) + { + i = Q_atoi( pattern ); + + if( i >= svgame.numEntities ) + return NULL; + } + else if( !Q_stricmp( pattern, "!cross" ) ) + { + ent = SV_GetCrossEnt( cl->edict ); + + if( !SV_IsValidEdict( ent ) ) + return NULL; + + i = NUM_FOR_EDICT( ent ); + } + else if( pattern[0] == '!' ) // check for correct instance with !(num)_(serial) + { + const char *p = pattern + 1; + i = Q_atoi( p ); + + while( Q_isdigit( p )) p++; + + if( *p++ != '_' ) + return NULL; + + if( i >= svgame.numEntities ) + return NULL; + + ent = EDICT_NUM( i ); + + if( ent->serialnumber != Q_atoi( p ) ) + return NULL; + } + else + { + for( i = svgame.globals->maxClients + 1; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( ent ) ) + continue; + + if( Q_stricmpext( pattern, STRING( ent->v.targetname ) ) ) + break; + } + } + + ent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( ent ) ) + return NULL; + + return ent; +} + +/* +=============== +SV_EntList_f + +Print list of entities to client +=============== +*/ +static qboolean SV_EntList_f( sv_client_t *cl ) +{ + vec3_t borigin; + edict_t *ent = NULL; + int i; + + for( i = 0; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + if( !SV_IsValidEdict( ent )) + continue; + + // filter by string + if( Cmd_Argc() > 1 ) + { + if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) ) + continue; + } + + VectorAdd( ent->v.absmin, ent->v.absmax, borigin ); + VectorScale( borigin, 0.5, borigin ); + + SV_ClientPrintf( cl, "%5i origin: %.f %.f %.f", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + SV_ClientPrintf( cl, "%5i borigin: %.f %.f %.f", i, borigin[0], borigin[1], borigin[2] ); + + if( ent->v.classname ) + SV_ClientPrintf( cl, ", class: %s", STRING( ent->v.classname )); + + if( ent->v.globalname ) + SV_ClientPrintf( cl, ", global: %s", STRING( ent->v.globalname )); + + if( ent->v.targetname ) + SV_ClientPrintf( cl, ", name: %s", STRING( ent->v.targetname )); + + if( ent->v.target ) + SV_ClientPrintf( cl, ", target: %s", STRING( ent->v.target )); + + if( ent->v.model ) + SV_ClientPrintf( cl, ", model: %s", STRING( ent->v.model )); + + SV_ClientPrintf( cl, "\n" ); + } + return true; +} + +/* +=============== +SV_EntInfo_f + +Print specified entity information to client +=============== +*/ +static qboolean SV_EntInfo_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + vec3_t borigin; + + if( Cmd_Argc() != 2 ) + { + SV_ClientPrintf( cl, "Use ent_info \n" ); + return false; + } + + ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) ); + + if( !SV_IsValidEdict( ent )) + return false; + + VectorAdd( ent->v.absmin, ent->v.absmax, borigin ); + VectorScale( borigin, 0.5, borigin ); + + SV_ClientPrintf( cl, "origin: %.f %.f %.f\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + SV_ClientPrintf( cl, "angles: %.f %.f %.f\n", ent->v.angles[0], ent->v.angles[1], ent->v.angles[2] ); + SV_ClientPrintf( cl, "borigin: %.f %.f %.f\n", borigin[0], borigin[1], borigin[2] ); + + if( ent->v.classname ) + SV_ClientPrintf( cl, "class: %s\n", STRING( ent->v.classname )); + + if( ent->v.globalname ) + SV_ClientPrintf( cl, "global: %s\n", STRING( ent->v.globalname )); + + if( ent->v.targetname ) + SV_ClientPrintf( cl, "name: %s\n", STRING( ent->v.targetname )); + + if( ent->v.target ) + SV_ClientPrintf( cl, "target: %s\n", STRING( ent->v.target )); + + if( ent->v.model ) + SV_ClientPrintf( cl, "model: %s\n", STRING( ent->v.model )); + + SV_ClientPrintf( cl, "health: %.f\n", ent->v.health ); + + if( ent->v.gravity != 1.0f ) + SV_ClientPrintf( cl, "gravity: %.2f\n", ent->v.gravity ); + + SV_ClientPrintf( cl, "movetype: %d\n", ent->v.movetype ); + SV_ClientPrintf( cl, "rendermode: %d\n", ent->v.rendermode ); + SV_ClientPrintf( cl, "renderfx: %d\n", ent->v.renderfx ); + SV_ClientPrintf( cl, "renderamt: %f\n", ent->v.renderamt ); + SV_ClientPrintf( cl, "rendercolor: %f %f %f\n", ent->v.rendercolor[0], ent->v.rendercolor[1], ent->v.rendercolor[2] ); + SV_ClientPrintf( cl, "maxspeed: %f\n", ent->v.maxspeed ); + + if( ent->v.solid ) + SV_ClientPrintf( cl, "solid: %d\n", ent->v.solid ); + + SV_ClientPrintf( cl, "flags: 0x%x\n", ent->v.flags ); + SV_ClientPrintf( cl, "spawnflags: 0x%x\n", ent->v.spawnflags ); + return true; +} + +/* +=============== +SV_EntFire_f + +Perform some actions +=============== +*/ +static qboolean SV_EntFire_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + int i = 1, count = 0; + qboolean single; // true if user specified something that match single entity + + if( Cmd_Argc() < 3 ) + { + SV_ClientPrintf( cl, "Use ent_fire []\n" + "Use ent_fire 0 help to get command list\n" ); + return false; + } + + if( ( single = Q_isdigit( Cmd_Argv( 1 ) ) ) ) + { + i = Q_atoi( Cmd_Argv( 1 ) ); + + if( i < 0 || i >= svgame.numEntities ) + return false; + + ent = EDICT_NUM( i ); + } + else if( ( single = !Q_stricmp( Cmd_Argv( 1 ), "!cross" ) ) ) + { + ent = SV_GetCrossEnt( cl->edict ); + + if (!SV_IsValidEdict(ent)) + return false; + + i = NUM_FOR_EDICT( ent ); + } + else if( ( single = ( Cmd_Argv( 1 )[0] == '!') ) ) // check for correct instance with !(num)_(serial) + { + const char *cmd = Cmd_Argv( 1 ) + 1; + i = Q_atoi( cmd ); + + while( Q_isdigit( cmd )) cmd++; + + if( *cmd++ != '_' ) + return false; + + if( i < 0 || i >= svgame.numEntities ) + return false; + + ent = EDICT_NUM( i ); + if( ent->serialnumber != Q_atoi( cmd ) ) + return false; + } + else + { + i = svgame.globals->maxClients + 1; + } + + for( ; ( i < svgame.numEntities ) && ( count < sv_enttools_maxfire.value ); i++ ) + { + ent = EDICT_NUM( i ); + if( !SV_IsValidEdict( ent )) + { + // SV_ClientPrintf( cl, PRINT_LOW, "Got invalid entity\n" ); + if( single ) + break; + continue; + } + + // if user specified not a number, try find such entity + if( !single ) + { + if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) )) + continue; + } + + SV_ClientPrintf( cl, "entity %i\n", i ); + + count++; + + if( !Q_stricmp( Cmd_Argv( 2 ), "health" ) ) + ent->v.health = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "gravity" ) ) + ent->v.gravity = Q_atof( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "movetype" ) ) + ent->v.movetype = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "solid" ) ) + ent->v.solid = Q_atoi( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "rename" ) ) + ent->v.targetname = ALLOC_STRING( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "settarget" ) ) + ent->v.target = ALLOC_STRING( Cmd_Argv ( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "setmodel" ) ) + SV_SetModel( ent, Cmd_Argv( 3 ) ); + else if( !Q_stricmp( Cmd_Argv( 2 ), "set" ) ) + { + string keyname; + string value; + KeyValueData pkvd; + if( Cmd_Argc() != 5 ) + return false; + + pkvd.szClassName = (char*)STRING( ent->v.classname ); + Q_strncpy( keyname, Cmd_Argv( 3 ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( 4 ), sizeof( value )); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + pkvd.fHandled = false; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value set successfully!\n" ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "touch" ) ) + { + if( Cmd_Argc() == 4 ) + { + edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + if( other && other->pvPrivateData ) + svgame.dllFuncs.pfnTouch( ent, other ); + } + else + svgame.dllFuncs.pfnTouch( ent, cl->edict ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "use" ) ) + { + if( Cmd_Argc() == 4 ) + { + edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + if( other && other->pvPrivateData ) + svgame.dllFuncs.pfnUse( ent, other ); + } + else + svgame.dllFuncs.pfnUse( ent, cl->edict ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "movehere" ) ) + { + ent->v.origin[2] = cl->edict->v.origin[2] + 25; + ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + SV_LinkEdict( ent, true ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "drop2floor" ) ) + { + pfnDropToFloor( ent ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "moveup" ) ) + { + float dist = 25; + if( Cmd_Argc() >= 4 ) + dist = Q_atof( Cmd_Argv( 3 ) ); + ent->v.origin[2] += dist; + if( Cmd_Argc() >= 5 ) + { + dist = Q_atof( Cmd_Argv( 4 ) ); + ent->v.origin[0] += dist * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[1] += dist * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + } + + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeowner" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.owner = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.owner = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeenemy" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.enemy = SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.enemy = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeaiment" ) ) + { + if( Cmd_Argc() == 4 ) + ent->v.aiment= SV_EntFindSingle( cl, Cmd_Argv( 3 ) ); + else + ent->v.aiment = cl->edict; + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmin" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.mins[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.mins[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.mins[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmax" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.maxs[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.maxs[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.maxs[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "rendercolor" ) ) + { + if( Cmd_Argc() != 6 ) + return false; + ent->v.rendercolor[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.rendercolor[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.rendercolor[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "renderamt" ) ) + { + ent->v.renderamt = Q_atof( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "renderfx" ) ) + { + ent->v.renderfx = Q_atoi( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "rendermode" ) ) + { + ent->v.rendermode = Q_atoi( Cmd_Argv( 3 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "angles" ) ) + { + ent->v.angles[0] = Q_atof( Cmd_Argv( 3 ) ); + ent->v.angles[1] = Q_atof( Cmd_Argv( 4 ) ); + ent->v.angles[2] = Q_atof( Cmd_Argv( 5 ) ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "setflag" ) ) + { + ent->v.flags |= 1U << Q_atoi( Cmd_Argv ( 3 ) ); + SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "clearflag" ) ) + { + ent->v.flags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) ); + SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "setspawnflag" ) ) + { + ent->v.spawnflags |= 1U << Q_atoi( Cmd_Argv ( 3 ) ); + SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.spawnflags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "clearspawnflag" ) ) + { + ent->v.spawnflags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) ); + SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.flags ); + } + else if( !Q_stricmp( Cmd_Argv( 2 ), "help" ) ) + { + SV_ClientPrintf( cl, "Available commands:\n" + "Set fields:\n" + " (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" + "Actions\n" + " rename: set entity targetname\n" + " settarget: set entity target (only targetnames)\n" + " setmodel: set entity model\n" + " set: set 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" + "Flags:\n" + " (Set/clear specified flag bit, arg is bit number)\n" + " setflag\n" + " clearflag\n" + " setspawnflag\n" + " clearspawnflag\n" + ); + return true; + } + else + { + SV_ClientPrintf( cl, "Unknown command %s!\nUse \"ent_fire 0 help\" to list commands.\n", Cmd_Argv( 2 ) ); + return false; + } + if( single ) + break; + } + return true; +} + +/* +=============== +SV_EntSendVars +=============== +*/ +static void SV_EntSendVars( sv_client_t *cl, edict_t *ent ) +{ + if( !ent ) + return; + + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_name \"%s\"\n", STRING( ent->v.targetname )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_num %i\n", NUM_FOR_EDICT( ent )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber ); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_class \"%s\"\n", STRING( ent->v.classname )); + MSG_WriteByte( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, "ent_getvars_cb\n" ); // why do we need this? +} + +/* +=============== +SV_EntCreate_f + +Create new entity with specified name. +=============== +*/ +static qboolean SV_EntCreate_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + int i = 0; + string_t classname; + + if( Cmd_Argc() < 2 ) + { + SV_ClientPrintf( cl, "Use ent_create ...\n" ); + return false; + } + + classname = ALLOC_STRING( Cmd_Argv( 1 ) ); + + ent = SV_CreateNamedEntity( 0, classname ); + + // Xash3D extension + if( !ent && svgame.physFuncs.SV_CreateEntity ) + { + ent = SV_AllocEdict(); + ent->v.classname = classname; + if( svgame.physFuncs.SV_CreateEntity( ent, (char*)STRING( classname ) ) == -1 ) + { + if( ent && !ent->free ) + SV_FreeEdict( ent ); + ent = NULL; + } + } + + // XashXT does not implement SV_CreateEntity, use saverestore export + if( !ent && svgame.physFuncs.pfnCreateEntitiesInRestoreList ) + { + ENTITYTABLE table = { + .classname = classname, + .id = -1, + .size = 1, + .flags = 1337, + }; + + SAVERESTOREDATA data = { + .tableCount = 1, + .pTable = &table + }; + + svgame.physFuncs.pfnCreateEntitiesInRestoreList( &data, table.flags, false ); + + ent = table.pent; + } + + if( !ent ) + { + SV_ClientPrintf( cl, "Invalid entity!\n" ); + return false; + } + + // choose default origin + ent->v.origin[2] = cl->edict->v.origin[2] + 25; + ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) ); + ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) ); + + SV_LinkEdict( ent, false ); + + // apply keyvalues if supported + if( svgame.dllFuncs.pfnKeyValue ) + { + for( i = 2; i < Cmd_Argc() - 1; i++ ) + { + string keyname; + string value; + KeyValueData pkvd; + + // allow split keyvalues to prespawn and postspawn + if( !Q_strcmp( Cmd_Argv( i ), "|" ) ) + break; + + Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( i ), sizeof( value )); + pkvd.fHandled = false; + pkvd.szClassName = (char*)STRING( ent->v.classname ); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue ); + } + } + + // set default targetname + if( !ent->v.targetname ) + { + string newname, clientname; + int j; + + for( j = 0; j < sizeof( cl->name ); j++ ) + { + char c = Q_tolower( cl->name[j] ); + if( c < 'a' || c > 'z' ) + c = '_'; + if( !cl->name[j] ) + { + clientname[j] = 0; + break; + } + clientname[j] = c; + } + + // generate name based on nick name and index + Q_snprintf( newname, sizeof( newname ), "%s_%i_e%i", clientname, cl->userid, NUM_FOR_EDICT( ent )); + + // i know, it may break strict aliasing rules + // but we will not lose anything in this case. + Q_strnlwr( newname, newname, sizeof( newname )); + ent->v.targetname = ALLOC_STRING( newname ); + SV_EntSendVars( cl, ent ); + } + + SV_ClientPrintf( cl, "Created %i: %s, targetname %s\n", NUM_FOR_EDICT( ent ), Cmd_Argv( 1 ), STRING( ent->v.targetname ) ); + + if( svgame.dllFuncs.pfnSpawn ) + svgame.dllFuncs.pfnSpawn( ent ); + + // now drop entity to floor. + pfnDropToFloor( ent ); + + // force think. Otherwise given weapon may crash server if player touch it before. + svgame.dllFuncs.pfnThink( ent ); + pfnDropToFloor( ent ); + + // apply postspawn keyvales if supported + if( svgame.dllFuncs.pfnKeyValue ) + { + for( i = i + 1; i < Cmd_Argc() - 1; i++ ) + { + string keyname; + string value; + KeyValueData pkvd; + + Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname )); + Q_strncpy( value, Cmd_Argv( i ), sizeof( value )); + pkvd.fHandled = false; + pkvd.szClassName = (char*)STRING( ent->v.classname ); + pkvd.szKeyName = keyname; + pkvd.szValue = value; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd ); + + if( pkvd.fHandled ) + SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue ); + } + } + return true; +} + +static qboolean SV_EntGetVars_f( sv_client_t *cl ) +{ + edict_t *ent = NULL; + + if( Cmd_Argc() != 2 ) + { + SV_ClientPrintf( cl, "Use ent_getvars \n" ); + return false; + } + + ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) ); + if( Cmd_Argc() ) + { + if( !SV_IsValidEdict( ent )) + return false; + } + + SV_EntSendVars( cl, ent ); + return true; +} + +// keep it sorted +static const ucmd_t ucmds[] = { -{ "new", SV_New_f }, -{ "god", SV_Godmode_f }, -{ "kill", SV_Kill_f }, -{ "begin", SV_Begin_f }, -{ "spawn", SV_Spawn_f }, -{ "pause", SV_Pause_f }, -{ "noclip", SV_Noclip_f }, -{ "setinfo", SV_SetInfo_f }, -{ "sendres", SV_SendRes_f }, -{ "notarget", SV_Notarget_f }, -{ "info", SV_ShowServerinfo_f }, -{ "dlfile", SV_DownloadFile_f }, -{ "disconnect", SV_Disconnect_f }, -{ "userinfo", SV_UpdateUserinfo_f }, { "_sv_build_info", SV_SendBuildInfo_f }, -{ NULL, NULL } +{ "begin", SV_Begin_f }, +{ "disconnect", SV_Disconnect_f }, +{ "dlfile", SV_DownloadFile_f }, +{ "god", SV_Godmode_f }, +{ "info", SV_ShowServerinfo_f }, +{ "kill", SV_Kill_f }, +{ "new", SV_New_f }, +{ "noclip", SV_Noclip_f }, +{ "notarget", SV_Notarget_f }, +{ "pause", SV_Pause_f }, +{ "sendres", SV_SendRes_f }, +{ "setinfo", SV_SetInfo_f }, +{ "spawn", SV_Spawn_f }, +{ "status", SV_ClientStatus_f }, +}; + +static const ucmd_t enttoolscmds[] = +{ +{ "ent_create", SV_EntCreate_f }, +{ "ent_fire", SV_EntFire_f }, +{ "ent_getvars", SV_EntGetVars_f }, +{ "ent_info", SV_EntInfo_f }, +{ "ent_list", SV_EntList_f }, }; /* @@ -2145,29 +3070,56 @@ ucmd_t ucmds[] = SV_ExecuteUserCommand ================== */ -void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ) +static void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ) { - ucmd_t *u; + int i; Cmd_TokenizeString( s ); - for( u = ucmds; u->name; u++ ) + for( i = 0; i < ARRAYSIZE( ucmds ); i++ ) { - if( !Q_strcmp( Cmd_Argv( 0 ), u->name )) + if( !Q_strcmp( Cmd_Argv( 0 ), ucmds[i].name )) { - if( !u->func( cl )) - Con_Printf( "'%s' is not valid from the console\n", u->name ); - else Con_Reportf( "ucmd->%s()\n", u->name ); - break; + if( !ucmds[i].func( cl )) + Con_Printf( "'%s' is not valid from the console\n", ucmds[i].name ); + else + Con_Reportf( "ucmd->%s()\n", ucmds[i].name ); + + return; } } - if( !u->name && sv.state == ss_active ) + if( sv.state == ss_active ) { + qboolean fullupdate; + + if( cl->state == cs_spawned && sv_enttools_enable.value > 0.0f && !sv.background ) + { + for( i = 0; i < ARRAYSIZE( enttoolscmds ); i++ ) + { + if( !Q_strcmp( Cmd_Argv( 0 ), enttoolscmds[i].name )) + { + Con_Reportf( "enttools->%s(): %s\n", enttoolscmds[i].name, s ); + Log_Printf( "\"%s<%i><%s><>\" performed: %s\n", Info_ValueForKey( cl->userinfo, "name" ), + cl->userid, SV_GetClientIDString( cl ), s ); + enttoolscmds[i].func( cl ); + return; + } + } + } + + fullupdate = !Q_strcmp( Cmd_Argv( 0 ), "fullupdate" ); + + if( fullupdate ) + { + if( sv_fullupdate_penalty_time.value && host.realtime < cl->fullupdate_next_calltime ) + return; + } + // custom client commands svgame.dllFuncs.pfnClientCommand( cl->edict ); - if( !Q_strcmp( Cmd_Argv( 0 ), "fullupdate" )) + if( fullupdate ) { // resend the ambient sounds for demo recording SV_RestartAmbientSounds(); @@ -2177,68 +3129,13 @@ void SV_ExecuteClientCommand( sv_client_t *cl, const char *s ) SV_RestartStaticEnts(); // resend the viewentity SV_UpdateClientView( cl ); + + if( sv_fullupdate_penalty_time.value ) + cl->fullupdate_next_calltime = host.realtime + sv_fullupdate_penalty_time.value; } } } -/* -================== -SV_TSourceEngineQuery -================== -*/ -void SV_TSourceEngineQuery( netadr_t from ) -{ - // A2S_INFO - char answer[1024] = ""; - int count, bots; - int index; - sizebuf_t buf; - - SV_GetPlayerCount( &count, &bots ); - - MSG_Init( &buf, "TSourceEngineQuery", answer, sizeof( answer )); - - MSG_WriteLong( &buf, -1 ); // Mark as connectionless - MSG_WriteByte( &buf, 'm' ); - MSG_WriteString( &buf, NET_AdrToString( net_local )); - MSG_WriteString( &buf, hostname.string ); - MSG_WriteString( &buf, sv.name ); - MSG_WriteString( &buf, GI->gamefolder ); - MSG_WriteString( &buf, GI->title ); - MSG_WriteByte( &buf, count ); - MSG_WriteByte( &buf, svs.maxclients ); - MSG_WriteByte( &buf, PROTOCOL_VERSION ); - MSG_WriteByte( &buf, Host_IsDedicated() ? 'D' : 'L' ); -#if defined(_WIN32) - MSG_WriteByte( &buf, 'W' ); -#else - MSG_WriteByte( &buf, 'L' ); -#endif - if( Q_stricmp( GI->gamefolder, "valve" )) - { - MSG_WriteByte( &buf, 1 ); // mod - MSG_WriteString( &buf, GI->game_url ); - MSG_WriteString( &buf, GI->update_url ); - MSG_WriteByte( &buf, 0 ); - MSG_WriteLong( &buf, (int)GI->version ); - MSG_WriteLong( &buf, GI->size ); - - if( GI->gamemode == 2 ) - MSG_WriteByte( &buf, 1 ); // multiplayer_only - else MSG_WriteByte( &buf, 0 ); - - if( Q_strstr( GI->game_dll, "hl." )) - MSG_WriteByte( &buf, 0 ); // Half-Life DLL - else MSG_WriteByte( &buf, 1 ); // Own DLL - } - else MSG_WriteByte( &buf, 0 ); // Half-Life - - MSG_WriteByte( &buf, GI->secure ); // unsecure - MSG_WriteByte( &buf, bots ); - - NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &buf ), MSG_GetData( &buf ), from ); -} - /* ================= SV_ConnectionlessPacket @@ -2251,52 +3148,114 @@ connectionless packets. */ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { - char *args; - const char *pcmd; - char buf[MAX_SYSPATH]; - int len = sizeof( buf ); + const char *pcmd, *args; // prevent flooding from banned address - if( SV_CheckIP( &from ) ) + if( SV_CheckIP( &from )) return; MSG_Clear( msg ); - MSG_ReadLong( msg );// skip the -1 marker + MSG_SeekToBit( msg, sizeof( uint32_t ) << 3, SEEK_CUR ); // skip the -1 marker args = MSG_ReadStringLine( msg ); Cmd_TokenizeString( args ); pcmd = Cmd_Argv( 0 ); - Con_Reportf( "SV_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), pcmd ); - if( !Q_strcmp( pcmd, "ping" )) SV_Ping( from ); - else if( !Q_strcmp( pcmd, "ack" )) SV_Ack( from ); - else if( !Q_strcmp( pcmd, "info" )) SV_Info( from, Q_atoi( Cmd_Argv( 1 ))); - else if( !Q_strcmp( pcmd, "bandwidth" )) SV_TestBandWidth( from ); - else if( !Q_strcmp( pcmd, "getchallenge" )) SV_GetChallenge( from ); - else if( !Q_strcmp( pcmd, "connect" )) SV_ConnectClient( from ); - else if( !Q_strcmp( pcmd, "rcon" )) SV_RemoteCommand( from, msg ); - else if( !Q_strcmp( pcmd, "netinfo" )) SV_BuildNetAnswer( from ); - else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); - else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); - else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING - else if (!Q_strcmp( pcmd, "c" )) + if( sv_log_outofband.value ) + Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), pcmd ); + + if( !svs.initialized ) { - qboolean sv_nat = Cvar_VariableInteger( "sv_nat" ); - if( sv_nat ) + // only process rcon if server not initialized + if( !Q_strcmp( pcmd, C2S_RCON )) + SV_RemoteCommand( net_from, &net_message ); + + return; + } + + if( NET_IsMasterAdr( from )) + { + if( !Q_strcmp( pcmd, M2S_CHALLENGE )) { - netadr_t to; - - if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) - SV_Info( to, PROTOCOL_VERSION ); + SV_AddToMaster( from, msg ); } + else if( !Q_strcmp( pcmd, M2S_NAT_CONNECT )) + { + SV_ConnectNatClient( from ); + } + + return; } - else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) + + if( !Q_strcmp( pcmd, A2S_GOLDSRC_INFO ) || pcmd[0] == A2S_GOLDSRC_PLAYERS || pcmd[0] == A2S_GOLDSRC_RULES ) { - // user out of band message (must be handled in CL_ConnectionlessPacket) - if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte*)buf ); + SV_SourceQuery_HandleConnnectionlessPacket( pcmd, from ); } - else Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); + else if( !Q_strcmp( pcmd, A2A_NETINFO )) + { + SV_BuildNetAnswer( from ); + } + else if( !Q_strcmp( pcmd, A2A_INFO )) + { + SV_Info( from, Q_atoi( Cmd_Argv( 1 ))); + } + else if( !Q_strcmp( pcmd, C2S_BANDWIDTHTEST )) + { + SV_TestBandWidth( from ); + } + else if( !Q_strcmp( pcmd, C2S_GETCHALLENGE )) + { + SV_SendChallenge( from ); + } + else if( !Q_strcmp( pcmd, C2S_CONNECT )) + { + SV_ConnectClient( from ); + } + else if( !Q_strcmp( pcmd, A2A_PING )) + { + Netchan_OutOfBandPrint( NS_SERVER, from, A2A_ACK ); + } + else if( !Q_strcmp( pcmd, A2A_GOLDSRC_PING )) + { + Netchan_OutOfBandPrint( NS_SERVER, from, A2A_GOLDSRC_ACK ); + } + else if( !Q_strcmp( pcmd, C2S_RCON )) + { + SV_RemoteCommand( from, msg ); + } + else if( !Q_strcmp( pcmd, A2A_ACK ) || !Q_strcmp( pcmd, A2A_GOLDSRC_ACK )) + { + SV_Ack( from ); + } + else + { + char buf[MAX_SYSPATH]; + int len = sizeof( buf ); + + if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) + { + // user out of band message (must be handled in CL_ConnectionlessPacket) + if( len > 0 ) + Netchan_OutOfBand( NS_SERVER, from, len, (byte*)buf ); + } + else if( sv_log_outofband.value ) + Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); + } +} + +static qboolean SV_PlayerIsFrozen( const edict_t *pClient ) +{ + if( sv_background_freeze.value && sv.background ) + return true; + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return false; + + if( FBitSet( pClient->v.flags, FL_FROZEN )) + return true; + + return false; } /* @@ -2313,40 +3272,29 @@ each of the backup packets. */ static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg ) { - client_frame_t *frame; - int key, size, checksum1, checksum2; - int i, numbackup, totalcmds, numcmds; - usercmd_t nullcmd, *to, *from; - usercmd_t cmds[CMD_BACKUP]; - float packet_loss; - edict_t *player; - model_t *model; + const usercmd_t nullcmd = { 0 }, *from = &nullcmd; // first cmd are starting from null-compressed usercmd_t + client_frame_t *frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK]; + usercmd_t cmds[CMD_BACKUP] = { 0 }, *to; + edict_t *player = cl->edict; + model_t *model; - player = cl->edict; + int key = MSG_GetRealBytesRead( msg ); + int checksum1 = MSG_ReadByte( msg ); + int packet_loss = MSG_ReadByte( msg ); + int numbackup = MSG_ReadByte( msg ); + int numcmds = MSG_ReadByte( msg ); + int totalcmds = numcmds + numbackup; + int i; - frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK]; - memset( &nullcmd, 0, sizeof( usercmd_t )); - memset( cmds, 0, sizeof( cmds )); - - key = MSG_GetRealBytesRead( msg ); - checksum1 = MSG_ReadByte( msg ); - packet_loss = MSG_ReadByte( msg ); - - numbackup = MSG_ReadByte( msg ); - numcmds = MSG_ReadByte( msg ); - - totalcmds = numcmds + numbackup; net_drop -= (numcmds - 1); if( totalcmds < 0 || totalcmds >= CMD_MASK ) { - Con_Reportf( S_ERROR "SV_ParseClientMove: %s sending too many commands %i\n", cl->name, totalcmds ); + Con_Reportf( S_ERROR "%s: %s sending too many commands %i\n", __func__, cl->name, totalcmds ); SV_DropClient( cl, false ); return; } - from = &nullcmd; // first cmd are starting from null-compressed usercmd_t - for( i = totalcmds - 1; i >= 0; i-- ) { to = &cmds[i]; @@ -2357,14 +3305,17 @@ static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg ) if( cl->state != cs_spawned ) return; - // if the checksum fails, ignore the rest of the packet - size = MSG_GetRealBytesRead( msg ) - key - 1; - checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence ); - - if( checksum2 != checksum1 ) + if( !Host_IsLocalClient( )) { - Con_Reportf( S_ERROR "SV_UserMove: failed command checksum for %s (%d != %d)\n", cl->name, checksum2, checksum1 ); - return; + // if the checksum fails, ignore the rest of the packet + int size = MSG_GetRealBytesRead( msg ) - key - 1; + int checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence ); + + if( checksum2 != checksum1 ) + { + Con_Reportf( S_ERROR "%s: failed command checksum for %s (%d != %d)\n", __func__, cl->name, checksum2, checksum1 ); + return; + } } cl->packet_loss = packet_loss; @@ -2420,6 +3371,10 @@ static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg ) SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i ); } + // was player kicked? stop here + if( cl->state <= cs_zombie ) + return; + cl->lastcmd = cmds[0]; // adjust latency time by 1/2 last client frame since @@ -2443,7 +3398,7 @@ SV_ParseResourceList Parse resource list =================== */ -void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg ) +static void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg ) { int totalsize; resource_t *resource; @@ -2532,7 +3487,7 @@ SV_ParseCvarValue Parse a requested value from client cvar =================== */ -void SV_ParseCvarValue( sv_client_t *cl, sizebuf_t *msg ) +static void SV_ParseCvarValue( sv_client_t *cl, sizebuf_t *msg ) { const char *value = MSG_ReadString( msg ); @@ -2548,13 +3503,13 @@ SV_ParseCvarValue2 Parse a requested value from client cvar =================== */ -void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) +static void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) { string name, value; int requestID = MSG_ReadLong( msg ); - Q_strcpy( name, MSG_ReadString( msg )); - Q_strcpy( value, MSG_ReadString( msg )); + Q_strncpy( name, MSG_ReadString( msg ), sizeof( name )); + Q_strncpy( value, MSG_ReadString( msg ), sizeof( value )); if( svgame.dllFuncs2.pfnCvarValue2 != NULL ) svgame.dllFuncs2.pfnCvarValue2( cl->edict, requestID, name, value ); @@ -2566,44 +3521,48 @@ void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) SV_ParseVoiceData =================== */ -void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg ) +static void SV_ParseVoiceData( sv_client_t *cl, sizebuf_t *msg ) { char received[4096]; - sv_client_t *cur; - int i, client; - uint length, size, frames; + int i; - cl->m_bLoopback = MSG_ReadByte( msg ); - - frames = MSG_ReadByte( msg ); - - size = MSG_ReadShort( msg ); - client = cl - svs.clients; + const qboolean loopback = !!MSG_ReadByte( msg ); + const uint frames = MSG_ReadByte( msg ); + const uint size = MSG_ReadShort( msg ); + const int client = cl - svs.clients; if( size > sizeof( received )) { - Con_DPrintf( "SV_ParseVoiceData: invalid incoming packet.\n" ); + Con_DPrintf( "%s: invalid incoming packet.\n", __func__ ); SV_DropClient( cl, false ); return; } MSG_ReadBytes( msg, received, size ); - if( !sv_voiceenable.value ) + if( !sv_voiceenable.value || svs.maxclients <= 1 || cl->state != cs_spawned ) return; - for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ ) + for( i = 0; i < svs.maxclients; i++ ) { - if ( cur->state < cs_connected && cl != cur ) - continue; - - length = size; + sv_client_t *cur = &svs.clients[i]; + const qboolean local = cl == cur; + uint length = size; + + if( !local ) + { + if( cur->state < cs_connected ) + continue; + + if( !FBitSet( cl->listeners, BIT( i ))) + continue; + } // 6 is a number of bytes for other parts of message if( MSG_GetNumBytesLeft( &cur->datagram ) < length + 6 ) continue; - if( cl == cur && !cur->m_bLoopback ) + if( cl == cur && !loopback ) length = 0; MSG_BeginServerCmd( &cur->datagram, svc_voicedata ); diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 571c4ffc..3e5616de 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -16,8 +16,6 @@ GNU General Public License for more details. #include "common.h" #include "server.h" -extern convar_t *con_gamemaps; - /* ================= SV_ClientPrintf @@ -34,7 +32,7 @@ void SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); MSG_BeginServerCmd( &cl->netchan.message, svc_print ); @@ -56,7 +54,7 @@ void SV_BroadcastPrintf( sv_client_t *ignore, const char *fmt, ... ) int i; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); if( sv.state == ss_active ) @@ -97,7 +95,7 @@ void SV_BroadcastCommand( const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); MSG_BeginServerCmd( &sv.reliable_datagram, svc_stufftext ); @@ -169,17 +167,11 @@ SV_ValidateMap check map for typically errors ================== */ -qboolean SV_ValidateMap( const char *pMapName, qboolean check_spawn ) +static qboolean SV_ValidateMap( const char *pMapName ) { - char *spawn_entity; int flags; - // determine spawn entity classname - if( !check_spawn || (int)sv_maxclients->value <= 1 ) - spawn_entity = GI->sp_entity; - else spawn_entity = GI->mp_entity; - - flags = SV_MapIsValid( pMapName, spawn_entity, NULL ); + flags = SV_MapIsValid( pMapName, NULL ); if( FBitSet( flags, MAP_INVALID_VERSION )) { @@ -193,12 +185,6 @@ qboolean SV_ValidateMap( const char *pMapName, qboolean check_spawn ) return false; } - if( check_spawn && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) - { - Con_Printf( S_ERROR "map %s doesn't have a valid spawnpoint\n", pMapName ); - return false; - } - return true; } @@ -210,7 +196,7 @@ Goes directly to a given map without any savegame archiving. For development work ================== */ -void SV_Map_f( void ) +static void SV_Map_f( void ) { char mapname[MAX_QPATH]; @@ -224,10 +210,10 @@ void SV_Map_f( void ) Q_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname )); COM_StripExtension( mapname ); - if( !SV_ValidateMap( mapname, true )) + if( !SV_ValidateMap( mapname )) return; - Cvar_DirectSet( sv_hostmap, mapname ); + Cvar_DirectSet( &sv_hostmap, mapname ); COM_LoadLevel( mapname, false ); } @@ -240,7 +226,7 @@ Lists maps according to given substring. TODO: Make it more convenient. (Timestamp check, temporary file, ...) ================== */ -void SV_Maps_f( void ) +static void SV_Maps_f( void ) { const char *separator = "-------------------"; const char *argStr = Cmd_Argv( 1 ); // Substr @@ -249,7 +235,7 @@ void SV_Maps_f( void ) if( Cmd_Argc() != 2 ) { - Msg( "Usage: maps \nmaps * for full listing\n" ); + Msg( S_USAGE "maps \nmaps * for full listing\n" ); return; } @@ -275,7 +261,7 @@ SV_MapBackground_f Set background map (enable physics in menu) ================== */ -void SV_MapBackground_f( void ) +static void SV_MapBackground_f( void ) { char mapname[MAX_QPATH]; @@ -296,13 +282,13 @@ void SV_MapBackground_f( void ) Q_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname )); COM_StripExtension( mapname ); - if( !SV_ValidateMap( mapname, false )) + if( !SV_ValidateMap( mapname )) return; // background map is always run as singleplayer Cvar_FullSet( "maxplayers", "1", FCVAR_LATCH ); - Cvar_FullSet( "deathmatch", "0", FCVAR_LATCH ); - Cvar_FullSet( "coop", "0", FCVAR_LATCH ); + Cvar_FullSet( "deathmatch", "0", FCVAR_LATCH|FCVAR_SERVER ); + Cvar_FullSet( "coop", "0", FCVAR_LATCH|FCVAR_SERVER ); COM_LoadLevel( mapname, true ); } @@ -315,14 +301,14 @@ Change map for next in alpha-bethical ordering For development work ================== */ -void SV_NextMap_f( void ) +static void SV_NextMap_f( void ) { char nextmap[MAX_QPATH]; int i, next; search_t *t; - t = FS_Search( "maps\\*.bsp", true, CVAR_TO_BOOL( con_gamemaps )); // only in gamedir - if( !t ) t = FS_Search( "maps/*.bsp", true, CVAR_TO_BOOL( con_gamemaps )); // only in gamedir + t = FS_Search( "maps\\*.bsp", true, con_gamemaps.value ); // only in gamedir + if( !t ) t = FS_Search( "maps/*.bsp", true, con_gamemaps.value ); // only in gamedir if( !t ) { @@ -337,16 +323,16 @@ void SV_NextMap_f( void ) if( Q_stricmp( ext, "bsp" )) continue; - COM_FileBase( t->filenames[i], nextmap ); - if( Q_stricmp( sv_hostmap->string, nextmap )) + COM_FileBase( t->filenames[i], nextmap, sizeof( nextmap )); + if( Q_stricmp( sv_hostmap.string, nextmap )) continue; next = ( i + 1 ) % t->numfilenames; - COM_FileBase( t->filenames[next], nextmap ); - Cvar_DirectSet( sv_hostmap, nextmap ); + COM_FileBase( t->filenames[next], nextmap, sizeof( nextmap )); + Cvar_DirectSet( &sv_hostmap, nextmap ); // found current point, check for valid - if( SV_ValidateMap( nextmap, true )) + if( SV_ValidateMap( nextmap )) { // found and valid COM_LoadLevel( nextmap, false ); @@ -366,15 +352,14 @@ SV_NewGame_f ============== */ -void SV_NewGame_f( void ) +static void SV_NewGame_f( void ) { - if( Cmd_Argc() != 1 ) - { + if( Cmd_Argc() == 1 ) + COM_NewGame( GI->startmap ); + else if( Cmd_Argc() == 2 ) + COM_NewGame( Cmd_Argv( 1 )); + else Con_Printf( S_USAGE "newgame\n" ); - return; - } - - COM_NewGame( GI->startmap ); } /* @@ -383,7 +368,7 @@ SV_HazardCourse_f ============== */ -void SV_HazardCourse_f( void ) +static void SV_HazardCourse_f( void ) { if( Cmd_Argc() != 1 ) { @@ -394,7 +379,7 @@ void SV_HazardCourse_f( void ) // special case for Gunman Chronicles: playing avi-file if( FS_FileExists( va( "media/%s.avi", GI->trainmap ), false )) { - Cbuf_AddText( va( "wait; movie %s\n", GI->trainmap )); + Cbuf_AddTextf( "wait; movie %s\n", GI->trainmap ); Host_EndGame( true, DEFAULT_ENDGAME_MESSAGE ); } else COM_NewGame( GI->trainmap ); @@ -406,7 +391,7 @@ SV_Load_f ============== */ -void SV_Load_f( void ) +static void SV_Load_f( void ) { char path[MAX_QPATH]; @@ -426,7 +411,7 @@ SV_QuickLoad_f ============== */ -void SV_QuickLoad_f( void ) +static void SV_QuickLoad_f( void ) { Cbuf_AddText( "echo Quick Loading...; wait; load quick" ); } @@ -437,20 +422,25 @@ SV_Save_f ============== */ -void SV_Save_f( void ) +static void SV_Save_f( void ) { + qboolean ret = false; + switch( Cmd_Argc( )) { case 1: - SV_SaveGame( "new" ); + ret = SV_SaveGame( "new" ); break; case 2: - SV_SaveGame( Cmd_Argv( 1 )); + ret = SV_SaveGame( Cmd_Argv( 1 )); break; default: Con_Printf( S_USAGE "save \n" ); break; } + + if( ret && CL_Active() && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + CL_HudMessage( "GAMESAVED" ); // defined in titles.txt } /* @@ -459,7 +449,7 @@ SV_QuickSave_f ============== */ -void SV_QuickSave_f( void ) +static void SV_QuickSave_f( void ) { Cbuf_AddText( "echo Quick Saving...; wait; save quick" ); } @@ -470,7 +460,7 @@ SV_DeleteSave_f ============== */ -void SV_DeleteSave_f( void ) +static void SV_DeleteSave_f( void ) { if( Cmd_Argc() != 2 ) { @@ -489,7 +479,7 @@ SV_AutoSave_f ============== */ -void SV_AutoSave_f( void ) +static void SV_AutoSave_f( void ) { if( Cmd_Argc() != 1 ) { @@ -497,7 +487,8 @@ void SV_AutoSave_f( void ) return; } - SV_SaveGame( "autosave" ); + if( sv_autosave.value ) + SV_SaveGame( "autosave" ); } /* @@ -507,7 +498,7 @@ SV_Restart_f restarts current level ================== */ -void SV_Restart_f( void ) +static void SV_Restart_f( void ) { // because restart can be multiple issued if( sv.state != ss_active ) @@ -522,14 +513,14 @@ SV_Reload_f continue from latest savedgame ================== */ -void SV_Reload_f( void ) +static void SV_Reload_f( void ) { // because reload can be multiple issued if( GameState->nextstate != STATE_RUNFRAME ) return; if( !SV_LoadGame( SV_GetLatestSave( ))) - COM_LoadLevel( sv_hostmap->string, false ); + COM_LoadLevel( sv_hostmap.string, false ); } /* @@ -539,9 +530,9 @@ SV_ChangeLevel_f classic change level ================== */ -void SV_ChangeLevel_f( void ) +static void SV_ChangeLevel_f( void ) { - if( Cmd_Argc() != 2 ) + if( Cmd_Argc() < 2 ) // allow extra arguments, for compatibility { Con_Printf( S_USAGE "changelevel \n" ); return; @@ -557,15 +548,17 @@ SV_ChangeLevel2_f smooth change level ================== */ -void SV_ChangeLevel2_f( void ) +static void SV_ChangeLevel2_f( void ) { - if( Cmd_Argc() != 3 ) + if( Cmd_Argc() < 2 ) // allow extra arguments, for compatibility { - Con_Printf( S_USAGE "changelevel2 \n" ); + Con_Printf( S_USAGE "changelevel2 [landmark]\n" ); return; } - SV_QueueChangeLevel( Cmd_Argv( 1 ), Cmd_Argv( 2 )); + if( Cmd_Argc() == 2 ) // with single argument, behaves like usual changelevel + SV_QueueChangeLevel( Cmd_Argv( 1 ), NULL ); + else SV_QueueChangeLevel( Cmd_Argv( 1 ), Cmd_Argv( 2 )); } /* @@ -575,12 +568,12 @@ SV_Kick_f Kick a user off of the server ================== */ -void SV_Kick_f( void ) +static void SV_Kick_f( void ) { sv_client_t *cl; - const char *param, *clientId; + const char *param; - if( Cmd_Argc() != 2 ) + if( Cmd_Argc() < 2 ) { Con_Printf( S_USAGE "kick <#id|name> [reason]\n" ); return; @@ -598,38 +591,7 @@ void SV_Kick_f( void ) return; } - if( NET_IsLocalAddress( cl->netchan.remote_address )) - { - Con_Printf( "The local player cannot be kicked!\n" ); - return; - } - - param = Cmd_Argv( 2 ); - - clientId = SV_GetClientIDString( cl ); - - if( *param ) - { - Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\" (message \"%s\")\n", cl->name, cl->userid, clientId, param ); - SV_BroadcastPrintf( cl, "%s was kicked with message: \"%s\"\n", cl->name, param ); - SV_ClientPrintf( cl, "You were kicked from the game with message: \"%s\"\n", param ); - } - else - { - Log_Printf( "Kick: \"%s<%i><%s><>\" was kicked by \"Console\"\n", cl->name, cl->userid, clientId ); - SV_BroadcastPrintf( cl, "%s was kicked\n", cl->name ); - SV_ClientPrintf( cl, "You were kicked from the game\n" ); - } - - if( cl->useragent[0] ) - { - if( *param ) - Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, "errormsg\nKicked with message:\n%s\n", param ); - else - Netchan_OutOfBandPrint( NS_SERVER, cl->netchan.remote_address, "errormsg\nYou were kicked from the game\n" ); - } - - SV_DropClient( cl, false ); + SV_KickPlayer( cl, "%s", Cmd_Argv( 2 )); } /* @@ -637,7 +599,7 @@ void SV_Kick_f( void ) SV_EntPatch_f ================== */ -void SV_EntPatch_f( void ) +static void SV_EntPatch_f( void ) { const char *mapname; @@ -663,11 +625,18 @@ void SV_EntPatch_f( void ) SV_Status_f ================ */ -void SV_Status_f( void ) +static void SV_Status_f( void ) { - sv_client_t *cl; int i; +#if !XASH_DEDICATED + if( !svs.clients && CL_Active( )) + { + Cmd_ForwardToServer(); + return; + } +#endif // XASH_DEDICATED + if( !svs.clients || sv.background ) { Con_Printf( "^3no server running.\n" ); @@ -675,34 +644,70 @@ void SV_Status_f( void ) } Con_Printf( "map: %s\n", sv.name ); - Con_Printf( "num score ping name lastmsg address port \n" ); - Con_Printf( "--- ----- ------- --------------- ------- --------------------- ------\n" ); + Con_Printf( "# score ping dev lastmsg qport useragent\t\tname\t\taddress\n" ); - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + for( i = 0; i < svs.maxclients; i++ ) { - int j, l; - const char *s; + const sv_client_t *cl = &svs.clients[i]; + int j = 0; + const char *s; + char devices[8]; + string version; + string os; + string arch; + int buildnum; + int input_devices; - if( !cl->state ) continue; + if( !cl->state ) + continue; - Con_Printf( "%3i ", i ); - Con_Printf( "%5i ", (int)cl->edict->v.frags ); + if( cl->state == cs_connected ) + s = "Connect "; + else if( cl->state == cs_spawning ) + s = "Spawning"; + else if( cl->state == cs_zombie ) + s = "Zombie "; + else if( FBitSet( cl->flags, FCL_FAKECLIENT )) + s = "Bot "; + else + s = va( "%8i", SV_CalcPing( cl )); - if( cl->state == cs_connected ) Con_Printf( "Connect" ); - else if( cl->state == cs_zombie ) Con_Printf( "Zombie " ); - else if( FBitSet( cl->flags, FCL_FAKECLIENT )) Con_Printf( "Bot " ); - else Con_Printf( "%7i ", SV_CalcPing( cl )); + input_devices = Q_atoi( Info_ValueForKey( cl->useragent, "d" )); + + if( FBitSet( input_devices, INPUT_DEVICE_MOUSE )) + devices[j++] = 'm'; + + if( FBitSet( input_devices, INPUT_DEVICE_TOUCH )) + devices[j++] = 't'; + + if( FBitSet( input_devices, INPUT_DEVICE_JOYSTICK )) + devices[j++] = 'j'; + + if( FBitSet( input_devices, INPUT_DEVICE_VR )) + devices[j++] = 'v'; + + if( j == 0 ) + Q_strncpy( devices, "n/a", sizeof( devices )); + else + devices[j++] = 0; + + Q_strncpy( version, Info_ValueForKey( cl->useragent, "v" ), sizeof( version )); + Q_strncpy( os, Info_ValueForKey( cl->useragent, "o" ), sizeof( os )); + Q_strncpy( arch, Info_ValueForKey( cl->useragent, "a" ), sizeof( arch )); + buildnum = Q_atoi( Info_ValueForKey( cl->useragent, "b" )); + + if( !COM_CheckStringEmpty( version )) + Q_strncpy( version, "n/a", sizeof( version )); + if( !COM_CheckStringEmpty( os )) + Q_strncpy( os, "n/a", sizeof( os )); + if( !COM_CheckStringEmpty( arch )) + Q_strncpy( arch, "n/a", sizeof( arch )); + + Con_Printf( "%2i %5i %4s %4s %.5f %5i %s (%s-%s %i)\t%8s\t%8s\n", + i, (int)cl->edict->v.frags, s, devices, host.realtime - cl->netchan.last_received, cl->netchan.qport, + version, os, arch, buildnum, + cl->name, NET_BaseAdrToString( cl->netchan.remote_address ) ); - Con_Printf( "%s", cl->name ); - l = 24 - Q_strlen( cl->name ); - for( j = 0; j < l; j++ ) Con_Printf( " " ); - Con_Printf( "%g ", ( host.realtime - cl->netchan.last_received )); - s = NET_BaseAdrToString( cl->netchan.remote_address ); - Con_Printf( "%s", s ); - l = 22 - Q_strlen( s ); - for( j = 0; j < l; j++ ) Con_Printf( " " ); - Con_Printf( "%5i", cl->netchan.qport ); - Con_Printf( "\n" ); } Con_Printf( "\n" ); } @@ -712,7 +717,7 @@ void SV_Status_f( void ) SV_ConSay_f ================== */ -void SV_ConSay_f( void ) +static void SV_ConSay_f( void ) { const char *p; char text[MAX_SYSPATH]; @@ -726,7 +731,7 @@ void SV_ConSay_f( void ) } p = Cmd_Args(); - Q_strncpy( text, *p == '"' ? p + 1 : p, MAX_SYSPATH ); + Q_strncpy( text, *p == '"' ? p + 1 : p, sizeof( text )); if( *p == '"' ) { @@ -743,9 +748,9 @@ void SV_ConSay_f( void ) SV_Heartbeat_f ================== */ -void SV_Heartbeat_f( void ) +static void SV_Heartbeat_f( void ) { - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -755,7 +760,7 @@ SV_ServerInfo_f Examine or change the serverinfo string =========== */ -void SV_ServerInfo_f( void ) +static void SV_ServerInfo_f( void ) { convar_t *var; @@ -763,7 +768,7 @@ void SV_ServerInfo_f( void ) { Con_Printf( "Server info settings:\n" ); Info_Print( svs.serverinfo ); - Con_Printf( "Total %lu symbols\n", Q_strlen( svs.serverinfo )); + Con_Printf( "Total %zu symbols\n", Q_strlen( svs.serverinfo )); return; } @@ -789,7 +794,7 @@ void SV_ServerInfo_f( void ) } Info_SetValueForStarKey( svs.serverinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_SERVERINFO_STRING ); - SV_BroadcastCommand( "fullserverinfo \"%s\"\n", SV_Serverinfo( )); + SV_BroadcastCommand( "fullserverinfo \"%s\"\n", svs.serverinfo ); } /* @@ -799,13 +804,13 @@ SV_LocalInfo_f Examine or change the localinfo string =========== */ -void SV_LocalInfo_f( void ) +static void SV_LocalInfo_f( void ) { if( Cmd_Argc() == 1 ) { Con_Printf( "Local info settings:\n" ); Info_Print( svs.localinfo ); - Con_Printf( "Total %lu symbols\n", Q_strlen( svs.localinfo )); + Con_Printf( "Total %zu symbols\n", Q_strlen( svs.localinfo )); return; } @@ -831,7 +836,7 @@ SV_ClientInfo_f Examine all a users info strings =========== */ -void SV_ClientInfo_f( void ) +static void SV_ClientInfo_f( void ) { sv_client_t *cl; @@ -857,7 +862,7 @@ SV_ClientUserAgent_f Examine useragent strings =========== */ -void SV_ClientUserAgent_f( void ) +static void SV_ClientUserAgent_f( void ) { sv_client_t *cl; @@ -882,9 +887,9 @@ SV_KillServer_f Kick everyone off, possibly in preparation for a new game =============== */ -void SV_KillServer_f( void ) +static void SV_KillServer_f( void ) { - Host_ShutdownServer(); + SV_Shutdown( "Server was killed due to shutdownserver command\n" ); } /* @@ -894,7 +899,7 @@ SV_PlayersOnly_f disable plhysics but players =============== */ -void SV_PlayersOnly_f( void ) +static void SV_PlayersOnly_f( void ) { if( !Cvar_VariableInteger( "sv_cheats" )) return; @@ -909,7 +914,7 @@ SV_EdictUsage_f =============== */ -void SV_EdictUsage_f( void ) +static void SV_EdictUsage_f( void ) { int active; @@ -931,7 +936,7 @@ SV_EntityInfo_f =============== */ -void SV_EntityInfo_f( void ) +static void SV_EntityInfo_f( void ) { edict_t *ent; int i; @@ -975,7 +980,7 @@ Rcon_Redirect_f Force redirect N lines of console output to client ================ */ -void Rcon_Redirect_f( void ) +static void Rcon_Redirect_f( void ) { int lines = 2000; @@ -992,6 +997,22 @@ void Rcon_Redirect_f( void ) Msg( "Redirection enabled for next %d lines\n", lines ); } +static void SV_ListMessages_f( void ) +{ + int i; + + Con_Printf( "num size name\n" ); + for( i = 1; i < MAX_USER_MESSAGES; i++ ) + { + if( !COM_CheckStringEmpty( svgame.msg[i].name )) + break; + + Con_Printf( "%3d\t%3d\t%s\n", svgame.msg[i].number, svgame.msg[i].size, svgame.msg[i].name ); + } + + Con_Printf( "Total %i messages\n", i - 1 ); +} + /* ================== SV_InitHostCommands @@ -1043,6 +1064,8 @@ void SV_InitOperatorCommands( void ) Cmd_AddCommand( "redirect", Rcon_Redirect_f, "force enable rcon redirection" ); Cmd_AddCommand( "logaddress", SV_SetLogAddress_f, "sets address and port for remote logging host" ); Cmd_AddCommand( "log", SV_ServerLog_f, "enables logging to file" ); + Cmd_AddCommand( "str64stats", SV_PrintStr64Stats_f, "print engine pool string statistics" ); + Cmd_AddCommand( "sv_list_messages", SV_ListMessages_f, "list registered user messages" ); if( host.type == HOST_NORMAL ) { @@ -1069,6 +1092,7 @@ void SV_KillOperatorCommands( void ) Cmd_RemoveCommand( "localinfo" ); Cmd_RemoveCommand( "serverinfo" ); Cmd_RemoveCommand( "clientinfo" ); + Cmd_RemoveCommand( "clientuseragent" ); Cmd_RemoveCommand( "playersonly" ); Cmd_RemoveCommand( "restart" ); Cmd_RemoveCommand( "entpatch" ); @@ -1077,8 +1101,10 @@ void SV_KillOperatorCommands( void ) Cmd_RemoveCommand( "shutdownserver" ); Cmd_RemoveCommand( "changelevel" ); Cmd_RemoveCommand( "changelevel2" ); + Cmd_RemoveCommand( "redirect" ); Cmd_RemoveCommand( "logaddress" ); Cmd_RemoveCommand( "log" ); + Cmd_RemoveCommand( "str64stats" ); if( host.type == HOST_NORMAL ) { diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c index ed65820e..159358be 100644 --- a/engine/server/sv_custom.c +++ b/engine/server/sv_custom.c @@ -16,7 +16,7 @@ GNU General Public License for more details. #include "common.h" #include "server.h" -void SV_CreateCustomizationList( sv_client_t *cl ) +static void SV_CreateCustomizationList( sv_client_t *cl ) { resource_t *pResource; customization_t *pList, *pCust; @@ -56,12 +56,12 @@ void SV_CreateCustomizationList( sv_client_t *cl ) } else { - Con_Printf( S_WARN "SV_CreateCustomization list, ignoring dup. resource for player %s\n", cl->name ); + Con_Printf( S_WARN "%s: ignoring dup. resource for player %s\n", __func__, cl->name ); } } } -qboolean SV_FileInConsistencyList( const char *filename, consistency_t **ppout ) +static qboolean SV_FileInConsistencyList( const char *filename, consistency_t **ppout ) { int i; @@ -121,8 +121,10 @@ void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg ) { value = MSG_ReadUBitLong( msg, 32 ); + LittleLongSW( value ); + // will be compare only first 4 bytes - if( value != *(int *)r->rgucMD5_hash ) + if( memcmp( &value, r->rgucMD5_hash, 4 )) badresindex = idx + 1; } else @@ -226,7 +228,7 @@ void SV_TransferConsistencyInfo( void ) break; case force_model_samebounds: if( !Mod_GetStudioBounds( filepath, mins, maxs )) - Host_Error( "Mod_GetStudioBounds: couldn't get bounds for %s\n", filepath ); + Host_Error( "%s: couldn't get bounds for %s\n", __func__, filepath ); memcpy( &pResource->rguc_reserved[0x01], mins, sizeof( mins )); memcpy( &pResource->rguc_reserved[0x0D], maxs, sizeof( maxs )); pResource->rguc_reserved[0] = pc->check_type; @@ -244,7 +246,7 @@ void SV_TransferConsistencyInfo( void ) sv.num_consistency = total; } -void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg ) +static void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg ) { int i, lastcheck; int delta; @@ -286,7 +288,7 @@ void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg ) MSG_WriteOneBit( msg, 0 ); } -qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) +static qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) { resource_t p; @@ -296,7 +298,7 @@ qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) { COM_HexConvert( filename + 4, 32, p.rgucMD5_hash ); - if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &p, NULL, NULL )) + if( HPAK_GetDataPointer( hpk_custom_file.string, &p, NULL, NULL )) return true; } @@ -304,7 +306,7 @@ qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) return true; MSG_BeginServerCmd( msg, svc_stufftext ); - MSG_WriteString( msg, va( "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash ))); + MSG_WriteStringf( msg, "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash )); return false; } @@ -335,7 +337,7 @@ void SV_AddToResourceList( resource_t *pResource, resource_t *pList ) pList->pPrev = pResource; } -void SV_SendCustomization( sv_client_t *cl, int playernum, resource_t *pResource ) +static void SV_SendCustomization( sv_client_t *cl, int playernum, resource_t *pResource ) { MSG_BeginServerCmd( &cl->netchan.message, svc_customization ); MSG_WriteByte( &cl->netchan.message, playernum ); // playernum @@ -391,7 +393,7 @@ int SV_EstimateNeededResources( sv_client_t *cl ) if( p->type != t_decal ) continue; - if( !HPAK_ResourceForHash( CUSTOM_RES_PATH, p->rgucMD5_hash, NULL )) + if( !HPAK_ResourceForHash( hpk_custom_file.string, p->rgucMD5_hash, NULL )) { if( p->nDownloadSize != 0 ) { @@ -408,7 +410,7 @@ int SV_EstimateNeededResources( sv_client_t *cl ) return size; } -void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSkipPlayer ) +static void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSkipPlayer ) { int i, nPlayerNumber = -1; sv_client_t *cl; @@ -433,7 +435,7 @@ void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSk } } -void SV_PropagateCustomizations( sv_client_t *pHost ) +static void SV_PropagateCustomizations( sv_client_t *pHost ) { customization_t *pCust; resource_t *pResource; @@ -457,7 +459,7 @@ void SV_PropagateCustomizations( sv_client_t *pHost ) } } -void SV_RegisterResources( sv_client_t *pHost ) +static void SV_RegisterResources( sv_client_t *pHost ) { resource_t *pResource; @@ -468,7 +470,7 @@ void SV_RegisterResources( sv_client_t *pHost ) } } -qboolean SV_UploadComplete( sv_client_t *cl ) +static qboolean SV_UploadComplete( sv_client_t *cl ) { if( &cl->resourcesneeded != cl->resourcesneeded.pNext ) return false; diff --git a/engine/server/sv_filter.c b/engine/server/sv_filter.c index fa7164a0..42aae31d 100644 --- a/engine/server/sv_filter.c +++ b/engine/server/sv_filter.c @@ -17,21 +17,13 @@ GNU General Public License for more details. #include "server.h" -typedef struct ipfilter_s -{ - float time; - float endTime; // -1 for permanent ban - struct ipfilter_s *next; - uint mask; - uint ip; -} ipfilter_t; +/* +============================================================================= -static ipfilter_t *ipfilter = NULL; - - -// TODO: Is IP filter really needed? -// TODO: Make it IPv6 compatible, for future expansion +PLAYER ID FILTER +============================================================================= +*/ typedef struct cidfilter_s { float endTime; @@ -47,7 +39,7 @@ static void SV_RemoveID( const char *id ) for( filter = cidfilter; filter; filter = filter->next ) { - if( Q_strcmp( filter->id, id ) ) + if( Q_strcmp( filter->id, id )) { prevfilter = filter; continue; @@ -67,32 +59,6 @@ static void SV_RemoveID( const char *id ) } } -static void SV_RemoveIP( uint ip, uint mask ) -{ - ipfilter_t *filter, *prevfilter = NULL; - - for( filter = ipfilter; filter; filter = filter->next ) - { - if( filter->ip != ip || mask != filter->mask ) - { - prevfilter = filter; - continue; - } - - if( filter == ipfilter ) - { - ipfilter = ipfilter->next; - Mem_Free( filter ); - return; - } - - if( prevfilter ) - prevfilter->next = filter->next; - Mem_Free( filter ); - return; - } -} - qboolean SV_CheckID( const char *id ) { qboolean ret = false; @@ -112,35 +78,7 @@ qboolean SV_CheckID( const char *id ) return false; } - if( !Q_strncmp( id, filter->id, len ) ) - { - ret = true; - break; - } - } - - return ret; -} - -qboolean SV_CheckIP( netadr_t *addr ) -{ - uint ip = addr->ip[0] << 24 | addr->ip[1] << 16 | addr->ip[2] << 8 | addr->ip[3]; - qboolean ret = false; - ipfilter_t *filter; - - for( filter = ipfilter; filter; filter = filter->next ) - { - while( filter->endTime && host.realtime > filter->endTime ) - { - uint rip = filter->ip; - uint rmask = filter->mask; - SV_RemoveIP( rip, rmask ); - filter = filter->next; - if( !filter ) - return false; - } - - if( (ip & filter->mask) == (filter->ip & filter->mask) ) + if( !Q_strncmp( id, filter->id, len )) { ret = true; break; @@ -152,7 +90,7 @@ qboolean SV_CheckIP( netadr_t *addr ) static void SV_BanID_f( void ) { - float time = Q_atof( Cmd_Argv( 1 ) ); + float time = Q_atof( Cmd_Argv( 1 )); const char *id = Cmd_Argv( 2 ); sv_client_t *cl = NULL; cidfilter_t *filter; @@ -162,66 +100,78 @@ static void SV_BanID_f( void ) if( !id[0] ) { - Con_Reportf( "Usage: banid <#userid or unique id>\n0 minutes for permanent ban\n" ); + Con_Reportf( S_USAGE "banid <#userid or unique id>\n0 minutes for permanent ban\n" ); return; } - if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 ) ) - id += 6; - if( !Q_strnicmp( id, "XASH_", 5 ) ) - id += 5; - - if( svs.clients ) + if( !svs.clients ) { - if( id[0] == '#' ) - cl = SV_ClientById( Q_atoi( id + 1 ) ); + Con_Reportf( S_ERROR "banid: no players\n" ); + return; + } + + if( id[0] == '#' ) + { + Con_Printf( S_ERROR "banid: not supported\n" ); + return; +#if 0 + int i = Q_atoi( &id[1] ); + + cl = SV_ClientById( i ); if( !cl ) { - int i; - sv_client_t *cl1; - int len = Q_strlen( id ); + Con_Printf( S_ERROR "banid: no such player with userid %d\n", i ); + return; + } +#endif + } + else + { + size_t len; + int i; - for( i = 0, cl1 = svs.clients; i < sv_maxclients->value; i++, cl1++ ) + if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 )) + id += 6; + if( !Q_strnicmp( id, "XASH_", 5 )) + id += 5; + + len = Q_strlen( id ); + + for( i = 0; i < svs.maxclients; i++ ) + { + if( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT )) + continue; + + if( svs.clients[i].state != cs_spawned ) + continue; + + if( !Q_strncmp( id, Info_ValueForKey( svs.clients[i].useragent, "uuid" ), len )) { - if( !Q_strncmp( id, Info_ValueForKey( cl1->useragent, "uuid" ), len ) ) - { - cl = cl1; - break; - } + cl = &svs.clients[i]; + break; } } if( !cl ) { - Con_DPrintf( S_WARN "banid: no such player\n" ); - } - else - id = Info_ValueForKey( cl->useragent, "uuid" ); - - if( !id[0] ) - { - Con_DPrintf( S_ERROR "Could not ban, not implemented yet\n" ); + Con_Printf( S_ERROR "banid: no such player with userid %s\n", id ); return; } } - if( !id[0] || id[0] == '#' ) - { - Con_DPrintf( S_ERROR "banid: bad id\n" ); - return; - } + id = Info_ValueForKey( cl->useragent, "uuid" ); SV_RemoveID( id ); - filter = Mem_Malloc( host.mempool, sizeof( cidfilter_t ) ); + filter = Mem_Malloc( host.mempool, sizeof( cidfilter_t )); filter->endTime = time; filter->next = cidfilter; - Q_strncpy( filter->id, id, sizeof( filter->id ) ); + Q_strncpy( filter->id, id, sizeof( filter->id )); cidfilter = filter; - if( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), "kick" ) ) - Cbuf_AddText( va( "kick #%d \"Kicked and banned\"\n", cl->userid ) ); + if( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), "kick" )) + Cbuf_AddTextf( "kick #%d \"Kicked and banned\"\n", cl->userid ); } static void SV_ListID_f( void ) @@ -251,7 +201,7 @@ static void SV_RemoveID_f( void ) { int num = Q_atoi( id + 1 ); - if( num >= sv_maxclients->value || num < 0 ) + if( num >= svs.maxclients || num < 0 ) return; id = Info_ValueForKey( svs.clients[num].useragent, "uuid" ); @@ -259,7 +209,7 @@ static void SV_RemoveID_f( void ) if( !id[0] ) { - Con_Reportf("Usage: removeid <#slotnumber or uniqueid>\n"); + Con_Reportf( S_USAGE "removeid <#slotnumber or uniqueid>\n"); return; } @@ -273,13 +223,13 @@ static void SV_WriteID_f( void ) if( !f ) { - Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "bannedcfgfile" ) ); + Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "bannedcfgfile" )); return; } FS_Printf( f, "//=======================================================================\n" ); FS_Printf( f, "//\t\tCopyright Flying With Gauss Team %s ©\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t %s - archive of id blacklist\n", Cvar_VariableString( "bannedcfgfile" ) ); + FS_Printf( f, "//\t\t %s - archive of id blacklist\n", Cvar_VariableString( "bannedcfgfile" )); FS_Printf( f, "//=======================================================================\n" ); for( filter = cidfilter; filter; filter = filter->next ) @@ -289,167 +239,25 @@ static void SV_WriteID_f( void ) FS_Close( f ); } -static qboolean StringToIP( const char *str, const char *maskstr, uint *outip, uint *outmask ) -{ - byte ip[4] = {0}; - byte mask[4] = {0}; - int i = 0; - - if( *str > '9' || *str < '0' ) - return false; - - do - { - while( *str <= '9' && *str >= '0' ) - { - ip[i] *=10; - ip[i] += *str - '0'; - str++; - } - mask[i] = 255; - i++; - if( *str != '.' ) break; - str++; - } while( i < 4 ); - - i = 0; - - if( !maskstr || *maskstr > '9' || *maskstr < '0' ) - goto end; - - do - { - byte mask1 = 0; - while( *maskstr <= '9' && *maskstr >= '0' ) - { - mask1 *=10; - mask1 += *maskstr - '0'; - maskstr++; - } - mask[i] &= mask1; - i++; - if( *maskstr != '.' ) break; - maskstr++; - } while( i < 4 ); - -end: - *outip = ip[0] << 24 | ip[1] << 16 | ip[2] << 8 | ip[3]; - if( outmask ) - *outmask = mask[0] << 24 | mask[1] << 16 | mask[2] << 8 | mask[3]; - - return true; -} - -#define IPARGS(ip) (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF -static void SV_AddIP_f( void ) -{ - float time = Q_atof( Cmd_Argv( 1 ) ); - const char *ipstr = Cmd_Argv( 2 ); - const char *maskstr = Cmd_Argv( 3 ); - uint ip, mask; - ipfilter_t *filter; - - if( time ) - time = host.realtime + time * 60.0f; - - if( !StringToIP( ipstr, maskstr, &ip, &mask ) ) - { - Con_Reportf( "Usage: addip [mask]\n0 minutes for permanent ban\n"); - return; - } - - SV_RemoveIP( ip, mask ); - - filter = Mem_Malloc( host.mempool, sizeof( ipfilter_t ) ); - filter->endTime = time; - filter->ip = ip; - filter->mask = mask; - filter->next = ipfilter; - - ipfilter = filter; -} - -static void SV_ListIP_f( void ) -{ - ipfilter_t *filter; - - Con_Reportf( "ip ban list\n" ); - Con_Reportf( "-----------\n" ); - - for( filter = ipfilter; filter; filter = filter->next ) - { - if( filter->endTime && host.realtime > filter->endTime ) - continue; // no negative time - - if( filter->endTime ) - Con_Reportf( "%d.%d.%d.%d %d.%d.%d.%d expries in %f minutes\n", IPARGS( filter->ip ), IPARGS( filter->mask ), ( filter->endTime - host.realtime ) / 60.0f ); - else - Con_Reportf( "%d.%d.%d.%d %d.%d.%d.%d permanent\n", IPARGS( filter->ip ), IPARGS( filter->mask ) ); - } -} - -static void SV_RemoveIP_f( void ) -{ - uint ip, mask; - - if( !StringToIP( Cmd_Argv(1), Cmd_Argv(2), &ip, &mask ) ) - { - Con_Reportf( "Usage: removeip [mask]\n" ); - return; - } - - SV_RemoveIP( ip, mask ); -} - -static void SV_WriteIP_f( void ) -{ - file_t *f = FS_Open( Cvar_VariableString( "listipcfgfile" ), "w", false ); - ipfilter_t *filter; - - if( !f ) - { - Con_DPrintf( S_ERROR "Could not write %s\n", Cvar_VariableString( "listipcfgfile" ) ); - return; - } - - FS_Printf( f, "//=======================================================================\n" ); - FS_Printf( f, "//\t\tCopyright Flying With Gauss Team %s ©\n", Q_timestamp( TIME_YEAR_ONLY )); - FS_Printf( f, "//\t\t %s - archive of IP blacklist\n", Cvar_VariableString( "listipcfgfile" ) ); - FS_Printf( f, "//=======================================================================\n" ); - - for( filter = ipfilter; filter; filter = filter->next ) - if( !filter->endTime ) // only permanent - FS_Printf( f, "addip 0 %d.%d.%d.%d %d.%d.%d.%d\n", IPARGS(filter->ip), IPARGS(filter->mask) ); - - FS_Close( f ); -} - -void SV_InitFilter( void ) +static void SV_InitIDFilter( void ) { Cmd_AddRestrictedCommand( "banid", SV_BanID_f, "ban player by ID" ); Cmd_AddRestrictedCommand( "listid", SV_ListID_f, "list banned players" ); Cmd_AddRestrictedCommand( "removeid", SV_RemoveID_f, "remove player from banned list" ); Cmd_AddRestrictedCommand( "writeid", SV_WriteID_f, "write banned.cfg" ); - Cmd_AddRestrictedCommand( "addip", SV_AddIP_f, "add entry to IP filter" ); - Cmd_AddRestrictedCommand( "listip", SV_ListIP_f, "list current IP filter" ); - Cmd_AddRestrictedCommand( "removeip", SV_RemoveIP_f, "remove IP filter" ); - Cmd_AddRestrictedCommand( "writeip", SV_WriteIP_f, "write listip.cfg" ); } -void SV_ShutdownFilter( void ) +static void SV_ShutdownIDFilter( void ) { - ipfilter_t *ipList, *ipNext; cidfilter_t *cidList, *cidNext; // should be called manually because banned.cfg is not executed by engine - //SV_WriteIP_f(); //SV_WriteID_f(); - for( ipList = ipfilter; ipList; ipList = ipNext ) - { - ipNext = ipList->next; - Mem_Free( ipList ); - } + Cmd_RemoveCommand( "banid" ); + Cmd_RemoveCommand( "listid" ); + Cmd_RemoveCommand( "removeid" ); + Cmd_RemoveCommand( "writeid" ); for( cidList = cidfilter; cidList; cidList = cidNext ) { @@ -458,5 +266,443 @@ void SV_ShutdownFilter( void ) } cidfilter = NULL; +} + +/* +============================================================================= + +CLIENT IP FILTER + +============================================================================= +*/ + +typedef struct ipfilter_s +{ + float endTime; + struct ipfilter_s *next; + netadr_t adr; + uint prefixlen; +} ipfilter_t; + +static ipfilter_t *ipfilter = NULL; + +static int SV_FilterToString( char *dest, size_t size, qboolean config, ipfilter_t *f ) +{ + if( config ) + { + return Q_snprintf( dest, size, "addip 0 %s/%d\n", NET_AdrToString( f->adr ), f->prefixlen ); + } + else if( f->endTime ) + { + return Q_snprintf( dest, size, "%s/%d (%f minutes)", NET_AdrToString( f->adr ), f->prefixlen, f->endTime ); + } + + return Q_snprintf( dest, size, "%s/%d (permanent)", NET_AdrToString( f->adr ), f->prefixlen ); +} + +static qboolean SV_IPFilterIncludesIPFilter( ipfilter_t *a, ipfilter_t *b ) +{ + if( NET_NetadrType( &a->adr ) != NET_NetadrType( &b->adr )) + return false; + + // can't include bigger subnet in small + if( a->prefixlen < b->prefixlen ) + return false; + + if( a->prefixlen == b->prefixlen ) + return NET_CompareAdr( a->adr, b->adr ); + + return NET_CompareAdrByMask( a->adr, b->adr, b->prefixlen ); +} + +static void SV_RemoveIPFilter( ipfilter_t *toremove, qboolean removeAll, qboolean verbose ) +{ + ipfilter_t *f, **back; + + back = &ipfilter; + while( 1 ) + { + f = *back; + if( !f ) return; + + if( SV_IPFilterIncludesIPFilter( toremove, f )) + { + if( verbose ) + { + string filterStr; + + SV_FilterToString( filterStr, sizeof( filterStr ), false, f ); + + Con_Printf( "%s removed.\n", filterStr ); + } + + *back = f->next; + back = &f->next; + + Mem_Free( f ); + + if( !removeAll ) + break; + } + else back = &f->next; + } +} + + +qboolean SV_CheckIP( netadr_t *adr ) +{ + // TODO: ip rate limit + ipfilter_t *entry = ipfilter; + + for( ; entry; entry = entry->next ) + { + if( entry->endTime && host.realtime > entry->endTime ) + continue; // expired + + switch( NET_NetadrType( &entry->adr )) + { + case NA_IP: + case NA_IP6: + if( NET_CompareAdrByMask( *adr, entry->adr, entry->prefixlen )) + return true; + break; + } + } + + return false; +} + +static void SV_AddIP_PrintUsage( void ) +{ + Con_Printf(S_USAGE "addip \n" + S_USAGE_INDENT "addip \n" + "Use 0 minutes for permanent\n" + "ipaddress A.B.C.D/24 is equivalent to A.B.C.0 and A.B.C\n" + "NOTE: IPv6 addresses only support prefix format!\n"); +} + +static void SV_RemoveIP_PrintUsage( void ) +{ + Con_Printf(S_USAGE "removeip [removeAll]\n" + S_USAGE_INDENT "removeip [removeAll]\n" + "Use removeAll to delete all ip filters which ipaddress or ipaddress/CIDR includes\n"); +} + +static void SV_ListIP_PrintUsage( void ) +{ + Con_Printf(S_USAGE "listip [ipaddress]\n" + S_USAGE_INDENT "listip [ipaddress/CIDR]\n"); +} + +static void SV_AddIP_f( void ) +{ + const char *szMinutes = Cmd_Argv( 1 ); + const char *adr = Cmd_Argv( 2 ); + ipfilter_t filter, *newfilter; + float minutes; + int i; + + if( Cmd_Argc() != 3 ) + { + // a1ba: kudos to rehlds for an idea of using CIDR prefixes + // in these commands :) + SV_AddIP_PrintUsage(); + return; + } + + minutes = Q_atof( szMinutes ); + if( minutes < 0.1f ) + minutes = 0; + + if( minutes != 0.0f ) + filter.endTime = host.realtime + minutes * 60; + else filter.endTime = 0; + + if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen )) + { + Con_Printf( "Invalid IP address!\n" ); + SV_AddIP_PrintUsage(); + return; + } + + newfilter = Mem_Malloc( host.mempool, sizeof( *newfilter )); + newfilter->endTime = filter.endTime; + newfilter->adr = filter.adr; + newfilter->prefixlen = filter.prefixlen; + newfilter->next = ipfilter; + + ipfilter = newfilter; + + for( i = 0; i < svs.maxclients; i++ ) + { + netadr_t clientadr = svs.clients[i].netchan.remote_address; + + if( !NET_CompareAdrByMask( clientadr, filter.adr, filter.prefixlen )) + continue; + + SV_ClientPrintf( &svs.clients[i], "The server operator has added you to banned list\n" ); + SV_DropClient( &svs.clients[i], false ); + } +} + +static void SV_ListIP_f( void ) +{ + qboolean haveFilter = false; + ipfilter_t filter, *f; + + if( Cmd_Argc() > 2 ) + { + SV_ListIP_PrintUsage(); + return; + } + + if( ipfilter == NULL ) + { + Con_Printf( "IP filter list is empty\n" ); + return; + } + + if( Cmd_Argc() == 2 ) + { + haveFilter = NET_StringToFilterAdr( Cmd_Argv( 1 ), &filter.adr, &filter.prefixlen ); + + if( !haveFilter ) + { + Con_Printf( "Invalid IP address!\n" ); + SV_ListIP_PrintUsage(); + return; + } + } + + Con_Printf( "IP filter list:\n" ); + + for( f = ipfilter; f; f = f->next ) + { + string filterStr; + + if( haveFilter && !SV_IPFilterIncludesIPFilter( &filter, f )) + continue; + + SV_FilterToString( filterStr, sizeof( filterStr ), false, f ); + Con_Printf( "%s\n", filterStr ); + } +} + +static void SV_RemoveIP_f( void ) +{ + const char *adr = Cmd_Argv( 1 ); + qboolean removeAll; + ipfilter_t filter; + int i; + + if( Cmd_Argc() != 2 && Cmd_Argc() != 3 ) + { + SV_RemoveIP_PrintUsage(); + return; + } + + removeAll = Cmd_Argc() == 3 && !Q_strcmp( Cmd_Argv( 2 ), "removeAll" ); + + if( !NET_StringToFilterAdr( adr, &filter.adr, &filter.prefixlen )) + { + Con_Printf( "Invalid IP address!\n" ); + SV_RemoveIP_PrintUsage(); + return; + } + + SV_RemoveIPFilter( &filter, removeAll, true ); +} + +static void SV_WriteIP_f( void ) +{ + file_t *fd = FS_Open( Cvar_VariableString( "listipcfgfile" ), "w", true ); + ipfilter_t *f; + + if( !fd ) + { + Con_Printf( "Couldn't open listip.cfg\n" ); + return; + } + + for( f = ipfilter; f; f = f->next ) + { + string filterStr; + int size; + + // do not save temporary bans + if( f->endTime ) + continue; + + size = SV_FilterToString( filterStr, sizeof( filterStr ), true, f ); + FS_Write( fd, filterStr, size ); + } + + FS_Close( fd ); +} + +static void SV_InitIPFilter( void ) +{ + Cmd_AddRestrictedCommand( "addip", SV_AddIP_f, "add entry to IP filter" ); + Cmd_AddRestrictedCommand( "listip", SV_ListIP_f, "list current IP filter" ); + Cmd_AddRestrictedCommand( "removeip", SV_RemoveIP_f, "remove IP filter" ); + Cmd_AddRestrictedCommand( "writeip", SV_WriteIP_f, "write listip.cfg" ); +} + +static void SV_ShutdownIPFilter( void ) +{ + ipfilter_t *ipList, *ipNext; + + // should be called manually because banned.cfg is not executed by engine + //SV_WriteIP_f(); + + for( ipList = ipfilter; ipList; ipList = ipNext ) + { + ipNext = ipList->next; + Mem_Free( ipList ); + } + ipfilter = NULL; } + +void SV_InitFilter( void ) +{ + SV_InitIPFilter(); + SV_InitIDFilter(); +} + +void SV_ShutdownFilter( void ) +{ + SV_ShutdownIPFilter(); + SV_ShutdownIDFilter(); +} + +#if XASH_ENGINE_TESTS + +#include "tests.h" + +static void Test_StringToFilterAdr( void ) +{ + ipfilter_t f1; + int i; + struct + { + const char *str; + qboolean valid; + int prefixlen; + int a, b, c, d; + } ipv4tests[] = + { + { "127.0.0.0/8", true, 8, 127, 0, 0, 0 }, + { "192.168", true, 16, 192, 168, 0, 0 }, + { "192.168/23", true, 23, 192, 168, 0, 0 }, + { "192.168./23", true, 23, 192, 168, 0, 0 }, + { "192.168../23", true, 23, 192, 168, 0, 0 }, + { "..192...168/23", false }, + { "", false }, + { "abcd", false } + }; + struct + { + const char *str; + qboolean valid; + int prefixlen; + uint8_t x[16]; + } ipv6tests[] = + { + { "::1", true, 128, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } }, + { "fd18:b9d4:65cf:83de::/64", true, 64, { 0xfd, 0x18, 0xb9, 0xd4, 0x65, 0xcf, 0x83, 0xde } }, + { "kkljnljkhfjnkj", false }, + { "fd8a:63d5:e014:0d62:ffff:ffff:ffff:ffff:ffff", false }, + }; + + for( i = 0; i < ARRAYSIZE( ipv4tests ); i++ ) + { + qboolean ret = NET_StringToFilterAdr( ipv4tests[i].str, &f1.adr, &f1.prefixlen ); + + TASSERT_EQi( ret, ipv4tests[i].valid ); + + if( ret ) + { + TASSERT_EQi( f1.prefixlen, ipv4tests[i].prefixlen ); + TASSERT_EQi( f1.adr.ip[0], ipv4tests[i].a ); + TASSERT_EQi( f1.adr.ip[1], ipv4tests[i].b ); + TASSERT_EQi( f1.adr.ip[2], ipv4tests[i].c ); + TASSERT_EQi( f1.adr.ip[3], ipv4tests[i].d ); + } + } + + for( i = 0; i < ARRAYSIZE( ipv6tests ); i++ ) + { + qboolean ret = NET_StringToFilterAdr( ipv6tests[i].str, &f1.adr, &f1.prefixlen ); + uint8_t x[16]; + + TASSERT_EQi( ret, ipv6tests[i].valid ); + + if( ret ) + { + TASSERT_EQi( f1.prefixlen, ipv6tests[i].prefixlen ); + + NET_NetadrToIP6Bytes( (uint8_t*)x, &f1.adr ); + + TASSERT( memcmp( x, ipv6tests[i].x, sizeof( x )) == 0 ); + } + } +} + +static void Test_IPFilterIncludesIPFilter( void ) +{ + qboolean ret; + const char *adrs[] = + { + "127.0.0.1/8", // 0 + "127.0.0.1", // 1 + "192.168/16", // 2 + "fe80::/64", // 3 + "fe80::96ab:9a49:2944:1808", // 4 + "2a00:1370:8190:f9eb::/62", // 5 + "2a00:1370:8190:f9eb:3866:6126:330c:b82b" // 6 + }; + ipfilter_t f[7]; + int i; + int tests[][3] = + { + // ipv4 + { 0, 0, true }, + { 0, 1, false }, + { 1, 0, true }, + { 0, 2, false }, + { 2, 0, false }, + + // mixed + { 0, 3, false }, + { 1, 4, false }, + + // ipv6 + { 3, 3, true }, + { 3, 4, false }, + { 4, 3, true }, + { 5, 3, false }, + { 3, 5, false }, + { 6, 5, true }, + }; + + for( i = 0; i < 7; i++ ) + { + NET_StringToFilterAdr( adrs[i], &f[i].adr, &f[i].prefixlen ); + } + + for( i = 0; i < ARRAYSIZE( tests ); i++ ) + { + ret = SV_IPFilterIncludesIPFilter( &f[tests[i][0]], &f[tests[i][1]] ); + + TASSERT_EQi( ret, tests[i][2] ); + } +} + +void Test_RunIPFilter( void ) +{ + Test_StringToFilterAdr(); + Test_IPFilterIncludesIPFilter(); +} + +#endif // XASH_ENGINE_TESTS diff --git a/engine/server/sv_frame.c b/engine/server/sv_frame.c index 55c871f5..65aa21d4 100644 --- a/engine/server/sv_frame.c +++ b/engine/server/sv_frame.c @@ -25,8 +25,8 @@ typedef struct byte sended[MAX_EDICTS_BYTES]; } sv_ents_t; -int c_fullsend; // just a debug counter -int c_notsend; +static int c_fullsend; // just a debug counter +static int c_notsend; /* ======================= @@ -178,9 +178,10 @@ Encode a client frame onto the network channel SV_FindBestBaseline trying to deltas with previous entities +set frame to NULL to check for static entities ============= */ -int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player ) +int SV_FindBestBaseline( int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player ) { int bestBitCount; int i, bitCount; @@ -193,7 +194,13 @@ int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- ) { // don't worry about underflow in circular buffer - entity_state_t *test = &svs.packet_entities[(frame->first_entity+i) % svs.num_client_entities]; + entity_state_t *test; + + // if set, then it's normal entity + if( frame != NULL ) + test = &svs.packet_entities[(frame->first_entity+i) % svs.num_client_entities]; + else + test = &svs.static_entities[i]; if( to->entityType == test->entityType ) { @@ -209,44 +216,12 @@ int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, // using delta from previous entity as baseline for current if( index != bestfound ) - *baseline = &svs.packet_entities[(frame->first_entity+bestfound) % svs.num_client_entities]; - return index - bestfound; -} - -/* -============= -SV_FindBestBaselineForStatic - -trying to deltas with previous static entities -============= -*/ -int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to ) -{ - int bestBitCount; - int i, bitCount; - int bestfound, j; - - bestBitCount = j = Delta_TestBaseline( *baseline, to, false, sv.time ); - bestfound = index; - - // lookup backward for previous 64 states and try to interpret current delta as baseline - for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- ) { - // don't worry about underflow in circular buffer - entity_state_t *test = &svs.static_entities[i]; - - bitCount = Delta_TestBaseline( test, to, false, sv.time ); - - if( bitCount < bestBitCount ) - { - bestBitCount = bitCount; - bestfound = i; - } + if( frame != NULL ) + *baseline = &svs.packet_entities[(frame->first_entity+bestfound) % svs.num_client_entities]; + else + *baseline = &svs.static_entities[bestfound]; } - - // using delta from previous entity as baseline for current - if( index != bestfound ) - *baseline = &svs.static_entities[bestfound]; return index - bestfound; } @@ -347,7 +322,7 @@ static void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_ // trying to reduce message by select optimal baseline if( !sv_instancedbaseline.value || !sv.num_instanced || sv.last_valid_baseline > newnum ) { - offset = SV_FindBestBaseline( cl, newindex, &baseline, newent, to, player ); + offset = SV_FindBestBaseline( newindex, &baseline, newent, to, player ); } else { @@ -515,16 +490,17 @@ SV_EmitPings ============= */ -void SV_EmitPings( sizebuf_t *msg ) +static void SV_EmitPings( sizebuf_t *msg ) { - sv_client_t *cl; - int packet_loss; - int i, ping; + sv_client_t *cl; + int i; MSG_BeginServerCmd( msg, svc_pings ); for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) { + int packet_loss, ping; + if( cl->state != cs_spawned ) continue; @@ -547,7 +523,7 @@ SV_WriteClientdataToMessage ================== */ -void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg ) +static void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg ) { clientdata_t nullcd; clientdata_t *from_cd, *to_cd; @@ -634,7 +610,7 @@ SV_WriteEntitiesToClient ================== */ -void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg ) +static void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg ) { client_frame_t *frame; entity_state_t *state; @@ -706,11 +682,12 @@ FRAME UPDATES SV_SendClientDatagram ======================= */ -void SV_SendClientDatagram( sv_client_t *cl ) +static void SV_SendClientDatagram( sv_client_t *cl ) { byte msg_buf[MAX_DATAGRAM]; sizebuf_t msg; + memset( msg_buf, 0, sizeof( msg_buf )); MSG_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); // always send servertime at new frame @@ -730,7 +707,11 @@ void SV_SendClientDatagram( sv_client_t *cl ) { if( MSG_GetNumBytesWritten( &cl->datagram ) < MSG_GetNumBytesLeft( &msg )) MSG_WriteBits( &msg, MSG_GetData( &cl->datagram ), MSG_GetNumBitsWritten( &cl->datagram )); - else Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow on msg\n", cl->name ); + else if( host.realtime > cl->overflow_warn_time ) + { + Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow on msg\n", cl->name ); + cl->overflow_warn_time = host.realtime + 5.0f; + } } MSG_Clear( &cl->datagram ); @@ -751,7 +732,7 @@ void SV_SendClientDatagram( sv_client_t *cl ) SV_UpdateUserInfo ======================= */ -void SV_UpdateUserInfo( sv_client_t *cl ) +static void SV_UpdateUserInfo( sv_client_t *cl ) { SV_FullClientUpdate( cl, &sv.reliable_datagram ); ClearBits( cl->flags, FCL_RESEND_USERINFO ); @@ -763,7 +744,7 @@ void SV_UpdateUserInfo( sv_client_t *cl ) SV_UpdateToReliableMessages ======================= */ -void SV_UpdateToReliableMessages( void ) +static void SV_UpdateToReliableMessages( void ) { sv_client_t *cl; int i; @@ -810,17 +791,17 @@ void SV_UpdateToReliableMessages( void ) continue; // reliables go to all connected or spawned if( MSG_GetNumBytesWritten( &sv.reliable_datagram ) < MSG_GetNumBytesLeft( &cl->netchan.message )) - MSG_WriteBits( &cl->netchan.message, MSG_GetBuf( &sv.reliable_datagram ), MSG_GetNumBitsWritten( &sv.reliable_datagram )); + MSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.reliable_datagram ), MSG_GetNumBitsWritten( &sv.reliable_datagram )); else Netchan_CreateFragments( &cl->netchan, &sv.reliable_datagram ); if( MSG_GetNumBytesWritten( &sv.datagram ) < MSG_GetNumBytesLeft( &cl->datagram )) - MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram )); + MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram )); else Con_DPrintf( S_WARN "Ignoring unreliable datagram for %s, would overflow\n", cl->name ); if( FBitSet( cl->flags, FCL_HLTV_PROXY )) { if( MSG_GetNumBytesWritten( &sv.spec_datagram ) < MSG_GetNumBytesLeft( &cl->datagram )) - MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram )); + MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram )); else Con_DPrintf( S_WARN "Ignoring spectator datagram for %s, would overflow\n", cl->name ); } } @@ -862,7 +843,7 @@ void SV_SendClientMessages( void ) continue; } - if( !host_limitlocal->value && NET_IsLocalAddress( cl->netchan.remote_address )) + if( !host_limitlocal.value && NET_IsLocalAddress( cl->netchan.remote_address )) SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); if( cl->state == cs_spawned ) @@ -912,7 +893,7 @@ void SV_SendClientMessages( void ) // now that we were able to send, reset timer to point to next possible send time. // check here also because sv_max/minupdaterate could been changed in runtime updaterate_time = bound( 1.0 / sv_maxupdaterate.value, cl->cl_updaterate, 1.0 / sv_minupdaterate.value ); - cl->next_messagetime = host.realtime + sv.frametime + updaterate_time; + cl->next_messagetime = host.realtime + sv.frametime + updaterate_time; ClearBits( cl->flags, FCL_SEND_NET_MESSAGE ); // NOTE: we should send frame even if server is not simulated to prevent overflow @@ -926,30 +907,6 @@ void SV_SendClientMessages( void ) sv.current_client = NULL; } -/* -======================= -SV_SendMessagesToAll - -e.g. before changing level -======================= -*/ -void SV_SendMessagesToAll( void ) -{ - sv_client_t *cl; - int i; - - if( sv.state == ss_dead ) - return; - - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) - { - if( cl->state >= cs_connected ) - SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); - } - - SV_SendClientMessages(); -} - /* ======================= SV_SkipUpdates @@ -1005,8 +962,13 @@ void SV_InactivateClients( void ) } if( cl->state > cs_connected ) + { cl->state = cs_connected; + // bump connect timeout + cl->connection_started = host.realtime; + } + COM_ClearCustomizationList( &cl->customdata, false ); memset( cl->physinfo, 0, MAX_PHYSINFO_STRING ); diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 61dd4fc8..44248591 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -24,25 +24,17 @@ GNU General Public License for more details. #include "render_api.h" // modelstate_t #include "ref_common.h" // decals -#define ENTVARS_COUNT ARRAYSIZE( gEntvarsDescription ) +// GameAPI functions declarations +static int GAME_EXPORT pfnModelIndex( const char *m ); // fatpvs stuff -static byte fatpvs[MAX_MAP_LEAFS/8]; -static byte fatphs[MAX_MAP_LEAFS/8]; -static byte clientpvs[MAX_MAP_LEAFS/8]; // for find client in PVS -static vec3_t viewPoint[MAX_CLIENTS]; +static byte fatphs[(MAX_MAP_LEAFS+7)/8]; +static byte clientpvs[(MAX_MAP_LEAFS+7)/8]; // for find client in PVS // exports typedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev ); typedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals ); -edict_t *SV_EdictNum( int n ) -{ - if(( n >= 0 ) && ( n < GI->max_edicts )) - return svgame.edicts + n; - return NULL; -} - #ifndef NDEBUG qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) { @@ -89,7 +81,7 @@ EntvarsDescription entavrs table for FindEntityByString ============= */ -static TYPEDESCRIPTION gEntvarsDescription[] = +static const TYPEDESCRIPTION gEntvarsDescription[] = { DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), DEFINE_ENTITY_FIELD( globalname, FIELD_STRING ), @@ -106,20 +98,6 @@ static TYPEDESCRIPTION gEntvarsDescription[] = DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), }; -/* -============= -SV_GetEntvarsDescription - -entavrs table for FindEntityByString -============= -*/ -TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) -{ - if( number < 0 || number >= ENTVARS_COUNT ) - return NULL; - return &gEntvarsDescription[number]; -} - /* ============= SV_SysError @@ -147,18 +125,6 @@ char *SV_Serverinfo( void ) return svs.serverinfo; } -/* -============= -SV_LocalInfo - -get local infostring -============= -*/ -char *SV_Localinfo( void ) -{ - return svs.localinfo; -} - /* ============= SV_AngleMod @@ -166,7 +132,7 @@ SV_AngleMod do modulo on entity angles ============= */ -float SV_AngleMod( float ideal, float current, float speed ) +static float SV_AngleMod( float ideal, float current, float speed ) { float move; @@ -257,6 +223,62 @@ void SV_CopyTraceToGlobal( trace_t *trace ) else svgame.globals->trace_ent = svgame.edicts; } +/* +============== +SV_SetModel +============== +*/ +void GAME_EXPORT SV_SetModel( edict_t *ent, const char *modelname ) +{ + char name[MAX_QPATH]; + model_t *mod; + int i = 1; + + if( !SV_IsValidEdict( ent )) + { + Con_Printf( S_WARN "%s: invalid entity %s\n", __func__, SV_ClassName( ent )); + return; + } + + if( !modelname || ((byte)modelname[0] ) <= ' ' ) + { + Con_Printf( S_WARN "%s: null name\n", __func__ ); + return; + } + + if( *modelname == '\\' || *modelname == '/' ) + modelname++; + + Q_strncpy( name, modelname, sizeof( name )); + COM_FixSlashes( name ); + + i = SV_ModelIndex( name ); + if( i == 0 ) + { + if( sv.state == ss_active ) + Con_Printf( S_ERROR "%s: failed to set model %s: world model cannot be changed\n", __func__, name ); + return; + } + + if( COM_CheckString( name )) + { + ent->v.model = MAKE_STRING( sv.model_precache[i] ); + ent->v.modelindex = i; + mod = sv.models[i]; + } + else + { + // model will be cleared + ent->v.model = ent->v.modelindex = 0; + mod = NULL; + } + + // set the model size + if( mod && mod->type != mod_studio ) + SV_SetMinMaxSize( ent, mod->mins, mod->maxs, true ); + else SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true ); +} + /* ============= SV_ConvertTrace @@ -264,7 +286,7 @@ SV_ConvertTrace convert trace_t to TraceResult ============= */ -void SV_ConvertTrace( TraceResult *dst, trace_t *src ) +static void SV_ConvertTrace( TraceResult *dst, trace_t *src ) { if( !src || !dst ) return; @@ -290,7 +312,7 @@ SV_CheckClientVisiblity Check visibility through client camera, portal camera, etc ============= */ -qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) +static qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) { int i, clientnum; vec3_t vieworg; @@ -299,13 +321,14 @@ qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) if( !mask ) return true; // GoldSrc rules clientnum = cl - svs.clients; - VectorCopy( viewPoint[clientnum], vieworg ); // Invasion issues: wrong camera position received in ENGINE_SET_PVS - if( cl->pViewEntity && !VectorCompare( vieworg, cl->pViewEntity->v.origin )) + if( cl->pViewEntity ) VectorCopy( cl->pViewEntity->v.origin, vieworg ); + else + VectorCopy( cl->edict->v.origin, vieworg ); - leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); + leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel ); if( CHECKVISBIT( mask, leaf->cluster )) return true; // visible from player view or camera view @@ -319,7 +342,7 @@ qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) continue; VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); - leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); + leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel ); if( CHECKVISBIT( mask, leaf->cluster )) return true; // visible from portal camera view @@ -382,7 +405,7 @@ static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboo case MSG_PAS: if( origin == NULL ) return false; // NOTE: GoldSource not using PHS for singleplayer - Mod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); + Mod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 ), true ); mask = fatphs; // using the FatPVS like a PHS break; case MSG_PVS_R: @@ -406,7 +429,7 @@ static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboo specproxy = reliable = true; break; default: - Host_Error( "SV_Multicast: bad dest: %i\n", dest ); + Host_Error( "%s: bad dest: %i\n", __func__, dest ); return 0; } @@ -460,7 +483,7 @@ SV_GetReliableDatagram Get shared reliable buffer ======================= */ -sizebuf_t *SV_GetReliableDatagram( void ) +static sizebuf_t *SV_GetReliableDatagram( void ) { return &sv.reliable_datagram; } @@ -555,7 +578,7 @@ qboolean SV_CreateStaticEntity( sizebuf_t *msg, int index ) state->number = 0; // trying to compress with previous delta's - offset = SV_FindBestBaselineForStatic( index, &baseline, state ); + offset = SV_FindBestBaseline( index, &baseline, state, NULL, false ); MSG_BeginServerCmd( msg, svc_spawnstatic ); MSG_WriteDeltaEntity( baseline, state, msg, true, DELTA_STATIC, sv.time, offset ); @@ -582,6 +605,19 @@ void SV_RestartStaticEnts( void ) SV_CreateStaticEntity( &sv.reliable_datagram, i ); } +/* +================= +SV_StartMusic + +================= +*/ +static void SV_StartMusic( const char *curtrack, const char *looptrack, int position ) +{ + MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); + MSG_WriteStringf( &sv.multicast, "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position ); + SV_Multicast( MSG_ALL, NULL, NULL, false, false ); +} + /* ================= SV_RestartAmbientSounds @@ -591,12 +627,16 @@ Write ambient sounds into demo */ void SV_RestartAmbientSounds( void ) { + // TODO: we don't know sounds state on remote server + // as it's used only for demos, maybe this could be implemented on client side? +#if !XASH_DEDICATED soundlist_t soundInfo[256]; string curtrack, looptrack; int i, nSounds; int position; - if( !SV_Active( )) return; + if( !SV_Active( ) || Host_IsDedicated( )) + return; nSounds = S_GetCurrentStaticSounds( soundInfo, 256 ); @@ -611,13 +651,12 @@ void SV_RestartAmbientSounds( void ) SV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); } -#if !XASH_DEDICATED // TODO: ??? // restart soundtrack - if( S_StreamGetCurrentState( curtrack, looptrack, &position )) + if( S_StreamGetCurrentState( curtrack, sizeof( curtrack ), looptrack, sizeof( looptrack ), &position )) { SV_StartMusic( curtrack, looptrack, position ); } -#endif +#endif // !XASH_DEDICATED } /* @@ -629,39 +668,33 @@ Write all the decals into demo */ void SV_RestartDecals( void ) { - decallist_t *entry; + // TODO: similar to SV_RestartAmbientSounds, this is only used for demo recording + // and better be reimplemented on client side +#if !XASH_DEDICATED + decallist_t *list; int decalIndex; int modelIndex; sizebuf_t *msg; - int i; + int i, numdecals; - if( !SV_Active( )) return; + if( !SV_Active( ) || Host_IsDedicated( )) + return; // g-cont. add space for studiodecals if present - host.decalList = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); + list = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); -#if !XASH_DEDICATED - if( !Host_IsDedicated() ) - { - host.numdecals = ref.dllFuncs.R_CreateDecalList( host.decalList ); + numdecals = ref.dllFuncs.R_CreateDecalList( list ); - // remove decals from map - ref.dllFuncs.R_ClearAllDecals(); - } - else -#endif // XASH_DEDICATED - { - // we probably running a dedicated server - host.numdecals = 0; - } + // remove decals from map + ref.dllFuncs.R_ClearAllDecals(); // write decals into reliable datagram msg = SV_GetReliableDatagram(); // restore decals and write them into network message - for( i = 0; i < host.numdecals; i++ ) + for( i = 0; i < numdecals; i++ ) { - entry = &host.decalList[i]; + decallist_t *entry = &list[i]; modelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex; // game override @@ -675,9 +708,8 @@ void SV_RestartDecals( void ) SV_CreateDecal( msg, entry->position, decalIndex, entry->entityIndex, modelIndex, entry->flags, entry->scale ); } - Z_Free( host.decalList ); - host.decalList = NULL; - host.numdecals = 0; + Z_Free( list ); +#endif // !XASH_DEDICATED } /* @@ -687,7 +719,7 @@ SV_BoxInPVS check brush boxes in fat pvs ============== */ -qboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ) +qboolean GAME_EXPORT SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ) { if( !Mod_BoxVisible( absmin, absmax, Mod_GetPVSForPoint( org ))) return false; @@ -705,7 +737,6 @@ void SV_QueueChangeLevel( const char *level, const char *landname ) { uint flags, smooth = false; char mapname[MAX_QPATH]; - char *spawn_entity; // hold mapname to other place Q_strncpy( mapname, level, sizeof( mapname )); @@ -714,12 +745,7 @@ void SV_QueueChangeLevel( const char *level, const char *landname ) if( COM_CheckString( landname )) smooth = true; - // determine spawn entity classname - if( svs.maxclients == 1 ) - spawn_entity = GI->sp_entity; - else spawn_entity = GI->mp_entity; - - flags = SV_MapIsValid( mapname, spawn_entity, landname ); + flags = SV_MapIsValid( mapname, landname ); if( FBitSet( flags, MAP_INVALID_VERSION )) { @@ -735,7 +761,7 @@ void SV_QueueChangeLevel( const char *level, const char *landname ) if( smooth && !FBitSet( flags, MAP_HAS_LANDMARK )) { - if( sv_validate_changelevel->value ) + if( sv_validate_changelevel.value ) { // NOTE: we find valid map but specified landmark it's doesn't exist // run simple changelevel like in q1, throw warning @@ -753,19 +779,10 @@ void SV_QueueChangeLevel( const char *level, const char *landname ) return; } - if( !smooth && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) - { - if( sv_validate_changelevel->value ) - { - Con_Printf( S_ERROR "changelevel: %s doesn't have a valid spawnpoint. Ignored.\n", mapname ); - return; - } - } - // bad changelevel position invoke enables in one-way transition if( sv.framecount < 15 ) { - if( sv_validate_changelevel->value ) + if( sv_validate_changelevel.value ) { Con_Printf( S_WARN "an infinite changelevel was detected and will be disabled until a next save\\restore\n" ); return; // lock with svs.spawncount here @@ -791,7 +808,7 @@ void SV_WriteEntityPatch( const char *filename ) int lumpofs = 0, lumplen = 0; byte buf[MAX_TOKEN]; // 1 kb string bspfilename; - dheader_t *header; + dlump_t entities; file_t *f; Q_snprintf( bspfilename, sizeof( bspfilename ), "maps/%s.bsp", filename ); @@ -801,17 +818,16 @@ void SV_WriteEntityPatch( const char *filename ) memset( buf, 0, MAX_TOKEN ); FS_Read( f, buf, MAX_TOKEN ); - header = (dheader_t *)buf; // check all the lumps and some other errors - if( !Mod_TestBmodelLumps( bspfilename, buf, true )) + if( !Mod_TestBmodelLumps( f, bspfilename, buf, true, &entities )) { FS_Close( f ); return; } - lumpofs = header->lumps[LUMP_ENTITIES].fileofs; - lumplen = header->lumps[LUMP_ENTITIES].filelen; + lumpofs = entities.fileofs; + lumplen = entities.filelen; if( lumplen >= 10 ) { @@ -841,7 +857,7 @@ static char *SV_ReadEntityScript( const char *filename, int *flags ) int lumpofs = 0, lumplen = 0; byte buf[MAX_TOKEN]; char *ents = NULL; - dheader_t *header; + dlump_t entities; size_t ft1, ft2; file_t *f; @@ -855,10 +871,9 @@ static char *SV_ReadEntityScript( const char *filename, int *flags ) SetBits( *flags, MAP_IS_EXIST ); memset( buf, 0, MAX_TOKEN ); FS_Read( f, buf, MAX_TOKEN ); - header = (dheader_t *)buf; // check all the lumps and some other errors - if( !Mod_TestBmodelLumps( bspfilename, buf, (host_developer.value) ? false : true )) + if( !Mod_TestBmodelLumps( f, bspfilename, buf, (host_developer.value) ? false : true, &entities )) { SetBits( *flags, MAP_INVALID_VERSION ); FS_Close( f ); @@ -866,8 +881,8 @@ static char *SV_ReadEntityScript( const char *filename, int *flags ) } // after call Mod_TestBmodelLumps we gurantee what map is valid - lumpofs = header->lumps[LUMP_ENTITIES].fileofs; - lumplen = header->lumps[LUMP_ENTITIES].filelen; + lumpofs = entities.fileofs; + lumplen = entities.filelen; // check for entfile too Q_snprintf( entfilename, sizeof( entfilename ), "maps/%s.ent", filename ); @@ -902,7 +917,7 @@ SV_MapIsValid Validate map ============== */ -uint SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ) +uint SV_MapIsValid( const char *filename, const char *landmark_name ) { uint flags = 0; char *pfile; @@ -918,45 +933,27 @@ uint SV_MapIsValid( const char *filename, const char *spawn_entity, const char * need_landmark = COM_CheckString( landmark_name ); - // g-cont. in-dev mode we can entering on map even without "info_player_start" - if( !need_landmark && host_developer.value ) + if( !need_landmark ) { - // not transition Mem_Free( ents ); - // skip spawnpoint checks in devmode - return (flags|MAP_HAS_SPAWNPOINT); + return flags; } pfile = ents; while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { - if( !Q_strcmp( token, "classname" )) - { - // check classname for spawn entity - pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); - if( !Q_strcmp( spawn_entity, check_name )) - { - SetBits( flags, MAP_HAS_SPAWNPOINT ); - - // we already find landmark, stop the parsing - if( need_landmark && FBitSet( flags, MAP_HAS_LANDMARK )) - break; - } - } - else if( need_landmark && !Q_strcmp( token, "targetname" )) + if( !Q_strcmp( token, "targetname" )) { // check targetname for landmark entity pfile = COM_ParseFile( pfile, check_name, sizeof( check_name )); if( !Q_strcmp( landmark_name, check_name )) { + // we found landmark, stop the parsing SetBits( flags, MAP_HAS_LANDMARK ); - - // we already find spawnpoint, stop the parsing - if( FBitSet( flags, MAP_HAS_SPAWNPOINT )) - break; + break; } } } @@ -974,7 +971,7 @@ SV_FreePrivateData release private edict memory ============== */ -void GAME_EXPORT SV_FreePrivateData( edict_t *pEdict ) +static void GAME_EXPORT SV_FreePrivateData( edict_t *pEdict ) { if( !pEdict || !pEdict->pvPrivateData ) return; @@ -1054,7 +1051,7 @@ SV_AllocEdict allocate new or reuse existing ============== */ -edict_t *SV_AllocEdict( void ) +edict_t *GAME_EXPORT SV_AllocEdict( void ) { edict_t *e; int i; @@ -1072,7 +1069,7 @@ edict_t *SV_AllocEdict( void ) } if( i >= GI->max_edicts ) - Host_Error( "ED_AllocEdict: no free edicts (max is %d)\n", GI->max_edicts ); + Host_Error( "%s: no free edicts (max is %d)\n", __func__, GI->max_edicts ); svgame.numEntities++; e = EDICT_NUM( i ); @@ -1088,7 +1085,7 @@ SV_GetEntityClass get pointer for entity class ============== */ -LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) +static LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) { // allocate edict private memory (passed by dlls) return (LINK_ENTITY_FUNC)COM_GetProcAddress( svgame.hInstance, pszClassName ); @@ -1099,15 +1096,22 @@ LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) SV_AllocPrivateData allocate private data for a given edict + +if customentity is NULL, no "custom" entity EXPORT is being done +if customentity is not NULL, will be set to true if "custom" export +was used to create this entity ============== */ -edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ) +static edict_t* SV_AllocPrivateData( edict_t *ent, string_t className, qboolean *customentity ) { const char *pszClassName; LINK_ENTITY_FUNC SpawnEdict; pszClassName = STRING( className ); + if( customentity ) + *customentity = false; + if( !ent ) { // allocate a new one @@ -1130,19 +1134,21 @@ edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ) if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 ) return ent; - SpawnEdict = SV_GetEntityClass( "custom" ); + if( customentity ) + { + SpawnEdict = SV_GetEntityClass( "custom" ); + *customentity = SpawnEdict != NULL; + } if( !SpawnEdict ) { - Con_Printf( S_ERROR "No spawn function for %s\n", STRING( className )); + Con_Printf( S_ERROR "No spawn function for \"%s\"\n", pszClassName ); // free entity immediately SV_FreeEdict( ent ); return NULL; } - - SetBits( ent->v.flags, FL_CUSTOMENTITY ); // it's a custom entity but not a beam! } SpawnEdict( &ent->v ); @@ -1159,12 +1165,7 @@ create specified entity, alloc private data */ edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ) { - edict_t *ed = SV_AllocPrivateData( ent, className ); - - // for some reasons this flag should be immediately cleared - if( ed ) ClearBits( ed->v.flags, FL_CUSTOMENTITY ); - - return ed; + return SV_AllocPrivateData( ent, className, NULL ); } /* @@ -1194,7 +1195,7 @@ SV_PlaybackReliableEvent reliable event is must be delivered always ============== */ -void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ) +static void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ) { event_args_t nullargs; @@ -1250,7 +1251,7 @@ static qboolean SV_IsValidCmd( const char *pCmd ) /* ============== -SV_AllocPrivateData +SV_ClientFromEdict get edict that attached to the client structure ============== @@ -1289,7 +1290,7 @@ pfnPrecacheModel ========= */ -int GAME_EXPORT pfnPrecacheModel( const char *s ) +static int GAME_EXPORT pfnPrecacheModel( const char *s ) { qboolean optional = false; int i; @@ -1311,78 +1312,13 @@ int GAME_EXPORT pfnPrecacheModel( const char *s ) return i; } -/* -================= -pfnSetModel - -================= -*/ -void GAME_EXPORT pfnSetModel( edict_t *e, const char *m ) -{ - char name[MAX_QPATH]; - qboolean found = false; - model_t *mod; - int i = 1; - - if( !SV_IsValidEdict( e )) - return; - - if( *m == '\\' || *m == '/' ) m++; - Q_strncpy( name, m, sizeof( name )); - COM_FixSlashes( name ); - - if( COM_CheckString( name )) - { - // check to see if model was properly precached - for( ; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) - { - if( !Q_stricmp( sv.model_precache[i], name )) - { - found = true; - break; - } - } - - if( !found ) - { - Con_Printf( S_ERROR "Failed to set model %s: was not precached\n", name ); - return; - } - } - - if( e == svgame.edicts ) - { - if( sv.state == ss_active ) - Con_Printf( S_ERROR "Failed to set model %s: world model cannot be changed\n", name ); - return; - } - - if( COM_CheckString( name )) - { - e->v.model = MAKE_STRING( sv.model_precache[i] ); - e->v.modelindex = i; - mod = sv.models[i]; - } - else - { - // model will be cleared - e->v.model = e->v.modelindex = 0; - mod = NULL; - } - - // set the model size - if( mod && mod->type != mod_studio ) - SV_SetMinMaxSize( e, mod->mins, mod->maxs, true ); - else SV_SetMinMaxSize( e, vec3_origin, vec3_origin, true ); -} - /* ================= pfnModelIndex ================= */ -int GAME_EXPORT pfnModelIndex( const char *m ) +static int GAME_EXPORT pfnModelIndex( const char *m ) { char name[MAX_QPATH]; int i; @@ -1410,7 +1346,7 @@ pfnModelFrames ================= */ -int GAME_EXPORT pfnModelFrames( int modelIndex ) +static int GAME_EXPORT pfnModelFrames( int modelIndex ) { model_t *pmodel = SV_ModelHandle( modelIndex ); @@ -1425,7 +1361,7 @@ pfnSetSize ================= */ -void GAME_EXPORT pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax ) +static void GAME_EXPORT pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax ) { if( !SV_IsValidEdict( e )) return; @@ -1439,7 +1375,7 @@ pfnChangeLevel ================= */ -void GAME_EXPORT pfnChangeLevel( const char *level, const char *landmark ) +static void GAME_EXPORT pfnChangeLevel( const char *level, const char *landmark ) { static uint last_spawncount = 0; char landname[MAX_QPATH]; @@ -1478,7 +1414,7 @@ pfnGetSpawnParms OBSOLETE, UNUSED ================= */ -void GAME_EXPORT pfnGetSpawnParms( edict_t *ent ) +static void GAME_EXPORT pfnGetSpawnParms( edict_t *ent ) { } @@ -1489,7 +1425,7 @@ pfnSaveSpawnParms OBSOLETE, UNUSED ================= */ -void GAME_EXPORT pfnSaveSpawnParms( edict_t *ent ) +static void GAME_EXPORT pfnSaveSpawnParms( edict_t *ent ) { } @@ -1499,7 +1435,7 @@ pfnVecToYaw ================= */ -float GAME_EXPORT pfnVecToYaw( const float *rgflVector ) +static float GAME_EXPORT pfnVecToYaw( const float *rgflVector ) { return SV_VecToYaw( rgflVector ); } @@ -1510,7 +1446,7 @@ pfnMoveToOrigin ================= */ -void GAME_EXPORT pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType ) +static void GAME_EXPORT pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType ) { if( !pflGoal || !SV_IsValidEdict( ent )) return; @@ -1524,7 +1460,7 @@ pfnChangeYaw ============== */ -void GAME_EXPORT pfnChangeYaw( edict_t* ent ) +static void GAME_EXPORT pfnChangeYaw( edict_t* ent ) { if( !SV_IsValidEdict( ent )) return; @@ -1538,7 +1474,7 @@ pfnChangePitch ============== */ -void GAME_EXPORT pfnChangePitch( edict_t* ent ) +static void GAME_EXPORT pfnChangePitch( edict_t* ent ) { if( !SV_IsValidEdict( ent )) return; @@ -1552,10 +1488,10 @@ SV_FindEntityByString ========= */ -edict_t *SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ) +static edict_t *GAME_EXPORT SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ) { - int index = 0, e = 0; - TYPEDESCRIPTION *desc = NULL; + int i = 0, e = 0; + const TYPEDESCRIPTION *desc = NULL; edict_t *ed; const char *t; @@ -1564,10 +1500,13 @@ edict_t *SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, cons if( pStartEdict ) e = NUM_FOR_EDICT( pStartEdict ); - while(( desc = SV_GetEntvarsDescirption( index++ )) != NULL ) + for( i = 0; i < ARRAYSIZE( gEntvarsDescription ); i++ ) { - if( !Q_strcmp( pszField, desc->fieldName )) + if( !Q_strcmp( pszField, gEntvarsDescription[i].fieldName )) + { + desc = &gEntvarsDescription[i]; break; + } } if( desc == NULL ) @@ -1626,21 +1565,6 @@ edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ) return pent; } -/* -============== -pfnGetEntityIllum - -returns averaged lightvalue for entity -============== -*/ -int GAME_EXPORT pfnGetEntityIllum( edict_t* pEnt ) -{ - if( !SV_IsValidEdict( pEnt )) - return -1; - - return SV_LightForEntity( pEnt ); -} - /* ================= pfnFindEntityInSphere @@ -1648,7 +1572,7 @@ pfnFindEntityInSphere find the entity in sphere ================= */ -edict_t *pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius ) +static edict_t *GAME_EXPORT pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius ) { float distSquared; int j, e = 0; @@ -1698,7 +1622,7 @@ SV_CheckClientPVS build the new client PVS ================= */ -int SV_CheckClientPVS( int check, qboolean bMergePVS ) +static int SV_CheckClientPVS( int check, qboolean bMergePVS ) { byte *pvs; vec3_t vieworg; @@ -1763,7 +1687,7 @@ pfnFindClientInPVS ================= */ -edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict ) +static edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict ) { edict_t *pClient; vec3_t view; @@ -1808,7 +1732,7 @@ edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict ) VectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view ); } - leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes ); + leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes, sv.worldmodel ); if( CHECKVISBIT( clientpvs, leaf->cluster )) return pClient; // client which currently in PVS @@ -1822,7 +1746,7 @@ pfnEntitiesInPVS ================= */ -edict_t *pfnEntitiesInPVS( edict_t *pview ) +static edict_t *pfnEntitiesInPVS( edict_t *pview ) { edict_t *pchain, *ptest; vec3_t viewpoint; @@ -1862,7 +1786,7 @@ pfnMakeVectors ============== */ -void GAME_EXPORT pfnMakeVectors( const float *rgflVector ) +static void GAME_EXPORT pfnMakeVectors( const float *rgflVector ) { AngleVectors( rgflVector, svgame.globals->v_forward, svgame.globals->v_right, svgame.globals->v_up ); } @@ -1874,7 +1798,7 @@ pfnRemoveEntity free edict private mem, unlink physics etc ============== */ -void GAME_EXPORT pfnRemoveEntity( edict_t *e ) +static void GAME_EXPORT pfnRemoveEntity( edict_t *e ) { if( !SV_IsValidEdict( e )) return; @@ -1895,7 +1819,7 @@ pfnCreateNamedEntity ============== */ -edict_t* GAME_EXPORT pfnCreateNamedEntity( string_t className ) +static edict_t *GAME_EXPORT pfnCreateNamedEntity( string_t className ) { return SV_CreateNamedEntity( NULL, className ); } @@ -1947,7 +1871,7 @@ pfnDropToFloor =============== */ -int GAME_EXPORT pfnDropToFloor( edict_t* e ) +int GAME_EXPORT pfnDropToFloor( edict_t *e ) { qboolean monsterClip; trace_t trace; @@ -1982,7 +1906,7 @@ pfnWalkMove =============== */ -int GAME_EXPORT pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode ) +static int GAME_EXPORT pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode ) { vec3_t move; @@ -2013,7 +1937,7 @@ pfnSetOrigin ================= */ -void GAME_EXPORT pfnSetOrigin( edict_t *e, const float *rgflOrigin ) +static void GAME_EXPORT pfnSetOrigin( edict_t *e, const float *rgflOrigin ) { if( !SV_IsValidEdict( e )) return; @@ -2036,31 +1960,31 @@ int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample if( vol < 0 || vol > 255 ) { - Con_Reportf( S_ERROR "SV_StartSound: volume = %i\n", vol ); + Con_Reportf( S_ERROR "%s: volume = %i\n", __func__, vol ); vol = bound( 0, vol, 255 ); } if( attn < 0.0f || attn > 4.0f ) { - Con_Reportf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); + Con_Reportf( S_ERROR "%s: attenuation %g must be in range 0-4\n", __func__, attn ); attn = bound( 0.0f, attn, 4.0f ); } if( chan < 0 || chan > 7 ) { - Con_Reportf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); + Con_Reportf( S_ERROR "%s: channel must be in range 0-7\n", __func__ ); chan = bound( 0, chan, 7 ); } if( pitch < 0 || pitch > 255 ) { - Con_Reportf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); + Con_Reportf( S_ERROR "%s: pitch = %i\n", __func__, pitch ); pitch = bound( 0, pitch, 255 ); } if( !COM_CheckString( sample )) { - Con_Reportf( S_ERROR "SV_StartSound: passed NULL sample\n" ); + Con_Reportf( S_ERROR "%s: passed NULL sample\n", __func__ ); return 0; } @@ -2082,25 +2006,26 @@ int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample } else { - // TESTTEST - if( *sample == '*' ) chan = CHAN_AUTO; + // '*' is special symbol to handle stream sounds + // (CHAN_VOICE but cannot be overriden) + // originally handled on client side + if( *sample == '*' ) + chan = CHAN_STREAM; // precache_sound can be used twice: cache sounds when loading // and return sound index when server is active sound_idx = SV_SoundIndex( sample ); - } - if( !sound_idx ) - { - Con_Printf( S_ERROR "SV_StartSound: %s not precached (%d)\n", sample, sound_idx ); - return 0; + if( !sound_idx ) + { + Con_Printf( S_ERROR "%s: %s not precached (%d)\n", __func__, sample, sound_idx ); + return 0; + } } spawn = FBitSet( flags, SND_RESTORE_POSITION ) ? false : true; - if( SV_IsValidEdict( ent ) && SV_IsValidEdict( ent->v.aiment )) - entityIndex = NUM_FOR_EDICT( ent->v.aiment ); - else if( SV_IsValidEdict( ent )) + if( SV_IsValidEdict( ent )) entityIndex = NUM_FOR_EDICT( ent ); else entityIndex = 0; // assume world @@ -2120,7 +2045,7 @@ int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample MSG_WriteUBitLong( msg, chan, MAX_SND_CHAN_BITS ); if( FBitSet( flags, SND_VOLUME )) MSG_WriteByte( msg, vol ); - if( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, attn * 64 ); + if( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, Q_min( attn * 64, 255 )); if( FBitSet( flags, SND_PITCH )) MSG_WriteByte( msg, pitch ); MSG_WriteUBitLong( msg, entityIndex, MAX_ENTITY_BITS ); @@ -2172,7 +2097,7 @@ pfnEmitAmbientSound ================= */ -void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch ) +static void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch ) { int msg_dest; @@ -2191,19 +2116,6 @@ void GAME_EXPORT pfnEmitAmbientSound( edict_t *ent, float *pos, const char *samp SV_Multicast( msg_dest, pos, NULL, false, false ); } -/* -================= -SV_StartMusic - -================= -*/ -void SV_StartMusic( const char *curtrack, const char *looptrack, int position ) -{ - MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); - MSG_WriteString( &sv.multicast, va( "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position )); - SV_Multicast( MSG_ALL, NULL, NULL, false, false ); -} - /* ================= pfnTraceLine @@ -2347,7 +2259,7 @@ pfnTraceSphere OBSOLETE, UNUSED ============= */ -void GAME_EXPORT pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ) +static void GAME_EXPORT pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ) { } @@ -2358,7 +2270,7 @@ pfnGetAimVector NOTE: speed is unused ============= */ -void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) +static void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) { edict_t *check; vec3_t start, dir, end, bestdir; @@ -2385,7 +2297,9 @@ void GAME_EXPORT pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) // try all possible entities VectorCopy( svgame.globals->v_forward, bestdir ); - bestdist = Cvar_VariableValue( "sv_aim" ); + if( sv_allow_autoaim.value ) + bestdist = sv_aim.value; + else bestdist = 0; check = EDICT_NUM( 1 ); // start at first client for( i = 1; i < svgame.numEntities; i++, check++ ) @@ -2431,7 +2345,7 @@ pfnServerCommand ========= */ -void GAME_EXPORT pfnServerCommand( const char* str ) +static void GAME_EXPORT pfnServerCommand( const char* str ) { if( !SV_IsValidCmd( str )) Con_Printf( S_ERROR "bad server command %s\n", str ); @@ -2444,24 +2358,9 @@ pfnServerExecute ========= */ -void GAME_EXPORT pfnServerExecute( void ) +static void GAME_EXPORT pfnServerExecute( void ) { Cbuf_Execute(); - - if( svgame.config_executed ) - return; - - // here we restore arhcived cvars only from game.dll - host.apply_game_config = true; - Cbuf_AddText( "exec config.cfg\n" ); - Cbuf_Execute(); - - if( host.sv_cvars_restored > 0 ) - Con_Reportf( "server executing ^2config.cfg^7 (%i cvars)\n", host.sv_cvars_restored ); - - host.apply_game_config = false; - svgame.config_executed = true; - host.sv_cvars_restored = 0; } /* @@ -2470,7 +2369,7 @@ pfnClientCommand ========= */ -void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) _format( 2 ); +void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) FORMAT_CHECK( 2 ); void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) { sv_client_t *cl; @@ -2480,7 +2379,7 @@ void GAME_EXPORT pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) if( sv.state != ss_active ) return; // early out - if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) + if(( cl = SV_ClientFromEdict( pEdict, false )) == NULL ) { Con_Printf( S_ERROR "stuffcmd: client is not spawned!\n" ); return; @@ -2508,7 +2407,7 @@ pfnParticleEffect Make sure the event gets sent to all clients ================= */ -void GAME_EXPORT pfnParticleEffect( const float *org, const float *dir, float color, float count ) +static void GAME_EXPORT pfnParticleEffect( const float *org, const float *dir, float color, float count ) { int v; @@ -2534,12 +2433,19 @@ pfnLightStyle =============== */ -void GAME_EXPORT pfnLightStyle( int style, const char* val ) +static void GAME_EXPORT pfnLightStyle( int style, const char* val ) { - if( style < 0 ) style = 0; + if( style < 0 ) + style = 0; + if( style >= MAX_LIGHTSTYLES ) - Host_Error( "SV_LightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); - if( sv.loadgame ) return; // don't let the world overwrite our restored styles + { + Host_Error( "%s: style: %i >= %d", __func__, style, MAX_LIGHTSTYLES ); + return; + } + + if( sv.loadgame ) + return; // don't let the world overwrite our restored styles SV_SetLightStyle( style, val, 0.0f ); // set correct style } @@ -2567,27 +2473,97 @@ int GAME_EXPORT pfnDecalIndex( const char *m ) return -1; } +static int SV_CanRewriteMessage( int msg_num ) +{ + // feature is disabled + if( !FBitSet( host.bugcomp, BUGCOMP_MESSAGE_REWRITE_FACILITY_FLAG )) + return 0; + + switch( msg_num ) + { + case svc_goldsrc_spawnstaticsound: + return svc_sound; + } + + return 0; +} + +static qboolean SV_RewriteMessage( void ) +{ + vec3_t origin; + const char *sample = NULL; + float vol, attn; + int ent, pitch, flags, idx; + int cmd; + + MSG_SeekToBit( &sv.multicast, svgame.msg_rewrite_pos, SEEK_SET ); + + cmd = MSG_ReadCmd( &sv.multicast, NS_SERVER ); + + switch( cmd ) + { + case svc_goldsrc_spawnstaticsound: + MSG_ReadVec3Coord( &sv.multicast, origin ); + idx = MSG_ReadShort( &sv.multicast ); + vol = MSG_ReadByte( &sv.multicast ); + attn = MSG_ReadByte( &sv.multicast ) / 64.0f; + ent = MSG_ReadShort( &sv.multicast ); + pitch = MSG_ReadByte( &sv.multicast ); + flags = MSG_ReadByte( &sv.multicast ); + + if( FBitSet( flags, SND_SENTENCE )) + sample = va( "!%i", idx ); + else if( idx >= 0 && idx < MAX_SOUNDS ) + sample = sv.sound_precache[idx]; + + if( !COM_CheckString( sample )) + { + Con_Printf( S_ERROR "%s: unrecognized sample in svc_spawnstaticsound, index %d, flags 0x%x\n", __func__, idx, flags ); + return false; + } + + MSG_SeekToBit( &sv.multicast, svgame.msg_rewrite_pos, SEEK_SET ); + return SV_BuildSoundMsg( &sv.multicast, EDICT_NUM( ent ), CHAN_STATIC, sample, vol, attn, flags, pitch, origin ); + } + + return false; +} + /* ============= pfnMessageBegin ============= */ -void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed ) +static void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed ) { int i, iSize; if( svgame.msg_started ) - Host_Error( "MessageBegin: New message started when msg '%s' has not been sent yet\n", svgame.msg_name ); + Host_Error( "%s: New message started when msg '%s' has not been sent yet\n", __func__, svgame.msg_name ); svgame.msg_started = true; // check range msg_num = bound( svc_bad, msg_num, 255 ); + svgame.msg_rewrite_index = 0; + svgame.msg_rewrite_pos = 0; + if( msg_num <= svc_lastmsg ) { - svgame.msg_index = -msg_num; // this is a system message - svgame.msg_name = svc_strings[msg_num]; + // check if we should rewrite this message into something else... + if( SV_CanRewriteMessage( msg_num )) + { + svgame.msg_index = -SV_CanRewriteMessage( msg_num ); + svgame.msg_name = svc_goldsrc_strings[msg_num] ? svc_goldsrc_strings[msg_num] : svc_strings[msg_num]; + svgame.msg_rewrite_index = msg_num; + svgame.msg_rewrite_pos = MSG_TellBit( &sv.multicast ); + } + else + { + svgame.msg_index = -msg_num; // this is a system message + svgame.msg_name = svc_strings[msg_num]; + } if( msg_num == svc_temp_entity ) iSize = -1; // temp entity have variable size @@ -2604,7 +2580,7 @@ void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigi if( i == MAX_USER_MESSAGES ) { - Host_Error( "MessageBegin: tried to send unregistered message %i\n", msg_num ); + Host_Error( "%s: tried to send unregistered message %i\n", __func__, msg_num ); return; } @@ -2636,7 +2612,7 @@ void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigi msg_num > svc_lastmsg && Q_strcmp( svgame.msg_name, "ReqState" ); - if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __FUNCTION__, msg_dest, svgame.msg_name ); + if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __func__, msg_dest, svgame.msg_name ); } /* @@ -2645,22 +2621,42 @@ pfnMessageEnd ============= */ -void GAME_EXPORT pfnMessageEnd( void ) +static void GAME_EXPORT pfnMessageEnd( void ) { const char *name = "Unknown"; float *org = NULL; + word realsize; if( svgame.msg_name ) name = svgame.msg_name; - if( !svgame.msg_started ) Host_Error( "MessageEnd: called with no active message\n" ); + if( !svgame.msg_started ) Host_Error( "%s: called with no active message\n", __func__ ); svgame.msg_started = false; if( MSG_CheckOverflow( &sv.multicast )) { - Con_Printf( S_ERROR "MessageEnd: %s has overflow multicast buffer\n", name ); + Con_Printf( S_ERROR "%s: %s has overflow multicast buffer\n", __func__, name ); MSG_Clear( &sv.multicast ); return; } + if( svgame.msg_rewrite_index != 0 ) + { + if( SV_RewriteMessage( )) + { + if( MSG_CheckOverflow( &sv.multicast )) + { + Con_Printf( S_ERROR "%s: %s has overflow multicast buffer (post-rewrite)\n", __func__, name ); + MSG_Clear( &sv.multicast ); + return; + } + } + else + { + Con_Printf( S_ERROR "%s: failed to rewrite message %s\n", __func__, name ); + MSG_Clear( &sv.multicast ); + return; + } + } + // check for system message if( svgame.msg_index < 0 ) { @@ -2669,18 +2665,19 @@ void GAME_EXPORT pfnMessageEnd( void ) // variable sized message if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) { - Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); + Con_Printf( S_ERROR "%s: %s too long (more than %d bytes)\n", __func__, name, MAX_USERMSG_LENGTH ); MSG_Clear( &sv.multicast ); return; } else if( svgame.msg_realsize < 0 ) { - Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); + Con_Printf( S_ERROR "%s: %s writes NULL message\n", __func__, name ); MSG_Clear( &sv.multicast ); return; } - *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + realsize = svgame.msg_realsize; + memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); } } else if( svgame.msg[svgame.msg_index].size != -1 ) @@ -2691,7 +2688,7 @@ void GAME_EXPORT pfnMessageEnd( void ) // compare sizes if( expsize != realsize ) { - Con_Printf( S_ERROR "SV_Multicast: %s expected %i bytes, it written %i. Ignored.\n", name, expsize, realsize ); + Con_Printf( S_ERROR "%s: %s expected %i bytes, it written %i. Ignored.\n", __func__, name, expsize, realsize ); MSG_Clear( &sv.multicast ); return; } @@ -2701,23 +2698,24 @@ void GAME_EXPORT pfnMessageEnd( void ) // variable sized message if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) { - Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); + Con_Printf( S_ERROR "%s: %s too long (more than %d bytes)\n", __func__, name, MAX_USERMSG_LENGTH ); MSG_Clear( &sv.multicast ); return; } else if( svgame.msg_realsize < 0 ) { - Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); + Con_Printf( S_ERROR "%s: %s writes NULL message\n", __func__, name ); MSG_Clear( &sv.multicast ); return; } - *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + realsize = svgame.msg_realsize; + memcpy( &sv.multicast.pData[svgame.msg_size_index], &realsize, sizeof( realsize )); } else { // this should never happen - Con_Printf( S_ERROR "SV_Multicast: %s have encountered error\n", name ); + Con_Printf( S_ERROR "%s: %s have encountered error\n", __func__, name ); MSG_Clear( &sv.multicast ); return; } @@ -2725,9 +2723,7 @@ void GAME_EXPORT pfnMessageEnd( void ) // update some messages in case their was format was changed and we want to keep backward compatibility if( svgame.msg_index < 0 ) { - int svc_msg = abs( svgame.msg_index ); - - if(( svc_msg == svc_finale || svc_msg == svc_cutscene ) && svgame.msg_realsize == 0 ) + if(( svgame.msg_index == -svc_finale || svgame.msg_index == -svc_cutscene ) && svgame.msg_realsize == 0 ) MSG_WriteChar( &sv.multicast, 0 ); // write null string } @@ -2736,7 +2732,7 @@ void GAME_EXPORT pfnMessageEnd( void ) SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); - if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__ ); + if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __func__ ); } /* @@ -2745,11 +2741,11 @@ pfnWriteByte ============= */ -void GAME_EXPORT pfnWriteByte( int iValue ) +static void GAME_EXPORT pfnWriteByte( int iValue ) { if( iValue == -1 ) iValue = 0xFF; // convert char to byte MSG_WriteByte( &sv.multicast, (byte)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, iValue ); svgame.msg_realsize++; } @@ -2759,10 +2755,10 @@ pfnWriteChar ============= */ -void GAME_EXPORT pfnWriteChar( int iValue ) +static void GAME_EXPORT pfnWriteChar( int iValue ) { MSG_WriteChar( &sv.multicast, (signed char)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, iValue ); svgame.msg_realsize++; } @@ -2772,10 +2768,10 @@ pfnWriteShort ============= */ -void GAME_EXPORT pfnWriteShort( int iValue ) +static void GAME_EXPORT pfnWriteShort( int iValue ) { MSG_WriteShort( &sv.multicast, (short)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, iValue ); svgame.msg_realsize += 2; } @@ -2785,10 +2781,10 @@ pfnWriteLong ============= */ -void GAME_EXPORT pfnWriteLong( int iValue ) +static void GAME_EXPORT pfnWriteLong( int iValue ) { MSG_WriteLong( &sv.multicast, iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, iValue ); svgame.msg_realsize += 4; } @@ -2799,12 +2795,12 @@ pfnWriteAngle this is low-res angle ============= */ -void GAME_EXPORT pfnWriteAngle( float flValue ) +static void GAME_EXPORT pfnWriteAngle( float flValue ) { int iAngle = ((int)(( flValue ) * 256 / 360) & 255); MSG_WriteChar( &sv.multicast, iAngle ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __func__, flValue ); svgame.msg_realsize += 1; } @@ -2814,88 +2810,26 @@ pfnWriteCoord ============= */ -void GAME_EXPORT pfnWriteCoord( float flValue ) +static void GAME_EXPORT pfnWriteCoord( float flValue ) { MSG_WriteCoord( &sv.multicast, flValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __func__, flValue ); svgame.msg_realsize += 2; } -/* -============= -pfnWriteBytes - -============= -*/ -void pfnWriteBytes( const byte *bytes, int count ) -{ - MSG_WriteBytes( &sv.multicast, bytes, count ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, count ); - svgame.msg_realsize += count; -} - /* ============= pfnWriteString ============= */ -void GAME_EXPORT pfnWriteString( const char *src ) +static void GAME_EXPORT pfnWriteString( const char *src ) { - static char string[MAX_USERMSG_LENGTH]; - int len = Q_strlen( src ) + 1; - int rem = sizeof( string ) - 1; - char *dst; - - if( len == 1 ) - { - MSG_WriteChar( &sv.multicast, 0 ); - svgame.msg_realsize += 1; - return; // fast exit - } - - // prepare string to sending - dst = string; - - while( 1 ) - { - // some escaped chars parsed as two symbols - merge it here - if( src[0] == '\\' && src[1] == 'n' ) - { - *dst++ = '\n'; - src += 2; - len -= 1; - } - else if( src[0] == '\\' && src[1] == 'r' ) - { - *dst++ = '\r'; - src += 2; - len -= 1; - } - else if( src[0] == '\\' && src[1] == 't' ) - { - *dst++ = '\t'; - src += 2; - len -= 1; - } - else if(( *dst++ = *src++ ) == 0 ) - break; - - if( --rem <= 0 ) - { - Con_Printf( S_ERROR "pfnWriteString: exceeds %i symbols\n", len ); - *dst = '\0'; // string end (not included in count) - len = Q_strlen( string ) + 1; - break; - } - } - - *dst = '\0'; // string end (not included in count) - MSG_WriteString( &sv.multicast, string ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __FUNCTION__, string ); + MSG_WriteString( &sv.multicast, src ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __func__, src ); // NOTE: some messages with constant string length can be marked as known sized - svgame.msg_realsize += len; + svgame.msg_realsize += Q_strlen( src ) + 1; } /* @@ -2904,22 +2838,38 @@ pfnWriteEntity ============= */ -void GAME_EXPORT pfnWriteEntity( int iValue ) +static void GAME_EXPORT pfnWriteEntity( int iValue ) { if( iValue < 0 || iValue >= svgame.numEntities ) - Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); + Host_Error( "%s: invalid entnumber %i\n", __func__, iValue ); MSG_WriteShort( &sv.multicast, (short)iValue ); - if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, iValue ); svgame.msg_realsize += 2; } +/* +============= +pfnCvar_RegisterServerVariable + +standard path to register game variable +============= +*/ +static void GAME_EXPORT pfnCvar_RegisterServerVariable( cvar_t *variable ) +{ + if( variable != NULL ) + { + SetBits( variable->flags, FCVAR_EXTDLL ); + Cvar_RegisterVariable( (convar_t *)variable ); + } +} + /* ============= pfnAlertMessage ============= */ -static void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) _format( 2 ); +static void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) FORMAT_CHECK( 2 ); static void GAME_EXPORT pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) { char buffer[2048]; @@ -2973,7 +2923,6 @@ pfnEngineFprintf OBSOLETE, UNUSED ============= */ -static void pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) _format( 2 ); static void GAME_EXPORT pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) { } @@ -2985,7 +2934,7 @@ pfnBuildSoundMsg Customizable sound message ============= */ -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 ) +static 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 ) { pfnMessageBegin( msg_dest, msg_type, pOrigin, pSend ); SV_BuildSoundMsg( &sv.multicast, pSource, chan, samp, fvol * 255, attn, fFlags, pitch, pOrigin ); @@ -2998,7 +2947,7 @@ pfnPvAllocEntPrivateData ============= */ -void *pfnPvAllocEntPrivateData( edict_t *pEdict, long cb ) +static void *GAME_EXPORT pfnPvAllocEntPrivateData( edict_t *pEdict, long cb ) { Assert( pEdict != NULL ); @@ -3020,7 +2969,7 @@ pfnPvEntPrivateData we already have copy of this function in 'enginecallback.h' :-) ============= */ -void *pfnPvEntPrivateData( edict_t *pEdict ) +static void *GAME_EXPORT pfnPvEntPrivateData( edict_t *pEdict ) { if( pEdict ) return pEdict->pvPrivateData; @@ -3028,24 +2977,22 @@ void *pfnPvEntPrivateData( edict_t *pEdict ) } -#ifdef XASH_64BIT static struct str64_s { size_t maxstringarray; qboolean allowdup; + qboolean dynamic; char *staticstringarray; char *pstringarray; char *pstringarraystatic; char *pstringbase; char *poldstringbase; char *plast; - qboolean dynamic; size_t maxalloc; size_t numdups; size_t numoverflows; size_t totalalloc; } str64; -#endif /* ================== @@ -3054,19 +3001,27 @@ SV_EmptyStringPool Free strings on server stop. Reset string pointer on 64 bits ================== */ -void SV_EmptyStringPool( void ) +void SV_EmptyStringPool( qboolean clear_stats ) { -#ifdef XASH_64BIT +#if XASH_64BIT if( str64.dynamic ) // switch only after array fill (more space for multiplayer games) + { str64.pstringbase = str64.pstringarray; + } else { str64.pstringbase = str64.poldstringbase = str64.pstringarraystatic; str64.plast = str64.pstringbase + 1; } -#else - Mem_EmptyPool( svgame.stringspool ); -#endif + + if( clear_stats ) + { + str64.maxalloc = 0; + str64.totalalloc = 0; + str64.numdups = 0; + str64.numoverflows = 0; + } +#endif // !XASH_64BIT } /* @@ -3080,20 +3035,20 @@ this helps not to lose strings that belongs to static game part */ void SV_SetStringArrayMode( qboolean dynamic ) { -#ifdef XASH_64BIT - Con_Reportf( "SV_SetStringArrayMode(%d) %d\n", dynamic, str64.dynamic ); +#if XASH_64BIT + Con_Reportf( "%s(%d) %d\n", __func__, dynamic, str64.dynamic ); if( dynamic == str64.dynamic ) return; str64.dynamic = dynamic; - SV_EmptyStringPool(); -#endif + SV_EmptyStringPool( false ); +#endif // !XASH_64BIT } -#if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE -#define USE_MMAP +#if XASH_AMD64 && XASH_LINUX && !XASH_ANDROID +#define USE_MMAP 1 #include #endif @@ -3107,24 +3062,24 @@ alloc string array somewhere if not (MAKE_STRING will not work. Always call ALLO this case need patched game dll with MAKE_STRING checking ptrdiff size ================== */ -void SV_AllocStringPool( void ) +static void SV_AllocStringPool( void ) { -#ifdef XASH_64BIT +#if XASH_64BIT void *ptr = NULL; string lenstr; - Con_Reportf( "SV_AllocStringPool()\n" ); - if( Sys_GetParmFromCmdLine( "-str64alloc", lenstr ) ) + Con_Reportf( "%s()\n", __func__ ); + if( Sys_GetParmFromCmdLine( "-str64alloc", lenstr )) { str64.maxstringarray = Q_atoi( lenstr ); if( str64.maxstringarray < 1024 || str64.maxstringarray >= INT_MAX ) - str64.maxstringarray = 65536; + str64.maxstringarray = 65536 * Q_ceil( GI->max_edicts / 1024.0f ); } - else str64.maxstringarray = 65536; + else str64.maxstringarray = 65536 * Q_ceil( GI->max_edicts / 1024.0f ); if( Sys_CheckParm( "-str64dup" ) ) str64.allowdup = true; -#ifdef USE_MMAP +#if USE_MMAP { uint flags; size_t pagesize = sysconf( _SC_PAGESIZE ); @@ -3169,44 +3124,100 @@ void SV_AllocStringPool( void ) if( ptr ) { - Con_Reportf( "SV_AllocStringPool: Allocated string array near the server library: %p %p\n", base, ptr ); - + Con_Reportf( "%s: Allocated string array near the server library: %p %p\n", __func__, base, ptr ); } else { - Con_Reportf( "SV_AllocStringPool: Failed to allocate string array near the server library!\n" ); - ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); + Con_Reportf( "%s: Failed to allocate string array near the server library!\n", __func__ ); + ptr = str64.staticstringarray = Mem_Calloc( host.mempool, str64.maxstringarray * 2 ); } } -#else - ptr = str64.staticstringarray = Mem_Calloc(host.mempool, str64.maxstringarray * 2); -#endif +#else // !USE_MMAP + ptr = str64.staticstringarray = Mem_Calloc( host.mempool, str64.maxstringarray * 2 ); +#endif // !USE_MMAP str64.pstringarray = ptr; str64.pstringarraystatic = (byte*)ptr + str64.maxstringarray; str64.pstringbase = str64.poldstringbase = ptr; str64.plast = (byte*)ptr + 1; svgame.globals->pStringBase = ptr; -#else - svgame.stringspool = Mem_AllocPool( "Server Strings" ); +#else // !XASH_64BIT svgame.globals->pStringBase = ""; -#endif +#endif // !XASH_64BIT + + svgame.stringspool = Mem_AllocPool( "Server Strings" ); } -void SV_FreeStringPool( void ) +static void SV_FreeStringPool( void ) { -#ifdef XASH_64BIT - Con_Reportf( "SV_FreeStringPool()\n" ); +#if XASH_64BIT + Con_Reportf( "%s()\n", __func__ ); -#ifdef USE_MMAP +#if USE_MMAP if( str64.pstringarray != str64.staticstringarray ) munmap( str64.pstringarray, (str64.maxstringarray * 2) & ~(sysconf( _SC_PAGESIZE ) - 1) ); else -#endif +#endif // USE_MMAP + { Mem_Free( str64.staticstringarray ); -#else + } +#else // !XASH_64BIT Mem_FreePool( &svgame.stringspool ); -#endif +#endif // !XASH_64BIT +} + +/* +============ +SV_ProcessString + +Process newly allocated string +pass NULL pointer to dst to get required length incl. null terminator +============ +*/ +static uint SV_ProcessString( char *dst, const char *src ) +{ + const char *p; + uint i = 0; + + p = src; + + while( *p ) + { + if( *p == '\\' ) + { + char replace = 0; + + switch( p[1] ) + { + case 'n': replace = '\n'; break; + // GoldSrc doesn't replace these symbols + // but old hack in pfnWriteString did + case 'r': replace = '\r'; break; + case 't': replace = '\t'; break; + } + + if( replace ) + { + if( dst ) + dst[i] = replace; + i++; + p += 2; + continue; + } + } + + if( dst ) + dst[i] = *p; + i++; + p++; + } + + // null terminator + if( dst ) + dst[i] = '\0'; + i++; + + return i; } /* @@ -3221,65 +3232,84 @@ use -str64dup to disable deduplication, -str64alloc to set array size */ string_t GAME_EXPORT SV_AllocString( const char *szValue ) { - const char *newString = NULL; - int cmp; + uint len = SV_ProcessString( NULL, szValue ); + char *processed_string = Mem_Calloc( svgame.stringspool, len ); + char *dupe_string = NULL; + qboolean found_dupe = false; + + (void)dupe_string; + (void)found_dupe; + + SV_ProcessString( processed_string, szValue ); if( svgame.physFuncs.pfnAllocString != NULL ) - return svgame.physFuncs.pfnAllocString( szValue ); - -#ifdef XASH_64BIT - cmp = 1; - - if( !str64.allowdup ) - for( newString = str64.poldstringbase + 1; - newString < str64.plast && ( cmp = Q_strcmp( newString, szValue ) ); - newString += Q_strlen( newString ) + 1 ); - - if( cmp ) { - uint len = Q_strlen( szValue ); + string_t i = svgame.physFuncs.pfnAllocString( processed_string ); + Mem_Free( processed_string ); + return i; + } - if( str64.plast - str64.poldstringbase + len + 2 > str64.maxstringarray ) +#if XASH_64BIT + if( !str64.allowdup ) + { + for( dupe_string = str64.poldstringbase + 1; dupe_string < str64.plast; dupe_string += Q_strlen( dupe_string ) + 1 ) + { + if( !Q_strcmp( dupe_string, processed_string )) + { + found_dupe = true; + break; + } + } + } + + if( !found_dupe ) + { + if( str64.plast - str64.poldstringbase + len + 1 > str64.maxstringarray ) { str64.plast = str64.pstringbase + 1; str64.poldstringbase = str64.pstringbase; str64.numoverflows++; } - //MsgDev( D_NOTE, "SV_AllocString: %ld %s\n", str64.plast - svgame.globals->pStringBase, szValue ); - memcpy( str64.plast, szValue, len + 1 ); - str64.totalalloc += len + 1; + //MsgDev( D_NOTE, "SV_AllocString: %ld %s\n", str64.plast - svgame.globals->pStringBase, processed_string ); + Q_strncpy( str64.plast, processed_string, len ); + str64.totalalloc += len; - newString = str64.plast; - str64.plast += len + 1; + dupe_string = str64.plast; + str64.plast += len; } else + { str64.numdups++; - //MsgDev( D_NOTE, "SV_AllocString: dup %ld %s\n", newString - svgame.globals->pStringBase, szValue ); + //MsgDev( D_NOTE, "SV_AllocString: dup %ld %s\n", dupe_string - svgame.globals->pStringBase, processed_string ); + } - if( newString - str64.pstringarray > str64.maxalloc ) - str64.maxalloc = newString - str64.pstringarray; + if( dupe_string - str64.pstringarray > str64.maxalloc ) + str64.maxalloc = dupe_string - str64.pstringarray; - return newString - svgame.globals->pStringBase; -#else - newString = _copystring( svgame.stringspool, szValue, __FILE__, __LINE__ ); - return newString - svgame.globals->pStringBase; -#endif + Mem_Free( processed_string ); + + return dupe_string - svgame.globals->pStringBase; +#else // !XASH_64BIT + return processed_string - svgame.globals->pStringBase; +#endif // !XASH_64BIT } -#ifdef XASH_64BIT void SV_PrintStr64Stats_f( void ) { - Msg( "====================\n" ); - Msg( "64 bit string pool statistics\n" ); - Msg( "====================\n" ); - Msg( "string array size: %lu\n", str64.maxstringarray ); - Msg( "total alloc %lu\n", str64.totalalloc ); - Msg( "maximum array usage: %lu\n", str64.maxalloc ); - Msg( "overflow counter: %lu\n", str64.numoverflows ); - Msg( "dup string counter: %lu\n", str64.numdups ); +#if XASH_64BIT + Con_Printf( "====================\n" ); + Con_Printf( "64 bit string pool statistics\n" ); + Con_Printf( "====================\n" ); + Con_Printf( "string array size: %lu\n", str64.maxstringarray ); + Con_Printf( "total alloc %lu\n", str64.totalalloc ); + Con_Printf( "maximum array usage: %lu\n", str64.maxalloc ); + Con_Printf( "overflow counter: %lu\n", str64.numoverflows ); + Con_Printf( "dup string counter: %lu\n", str64.numdups ); +#else // !XASH_64BIT + Con_Printf( "Not implemented\n" ); +#endif // !XASH_64BIT } -#endif /* ============= @@ -3292,7 +3322,7 @@ string_t SV_MakeString( const char *szValue ) { if( svgame.physFuncs.pfnMakeString != NULL ) return svgame.physFuncs.pfnMakeString( szValue ); -#ifdef XASH_64BIT +#if XASH_64BIT { long long ptrdiff = szValue - svgame.globals->pStringBase; if( ptrdiff > INT_MAX || ptrdiff < INT_MIN ) @@ -3300,9 +3330,9 @@ string_t SV_MakeString( const char *szValue ) else return (int)ptrdiff; } -#else +#else // !XASH_64BIT return szValue - svgame.globals->pStringBase; -#endif +#endif // !XASH_64BIT } /* @@ -3311,7 +3341,7 @@ SV_GetString ============= */ -const char *SV_GetString( string_t iString ) +const char *GAME_EXPORT SV_GetString( string_t iString ) { if( svgame.physFuncs.pfnGetString != NULL ) return svgame.physFuncs.pfnGetString( iString ); @@ -3324,7 +3354,7 @@ pfnGetVarsOfEnt ============= */ -entvars_t *pfnGetVarsOfEnt( edict_t *pEdict ) +static entvars_t *GAME_EXPORT pfnGetVarsOfEnt( edict_t *pEdict ) { if( pEdict ) return &pEdict->v; @@ -3337,7 +3367,7 @@ pfnPEntityOfEntOffset ============= */ -edict_t* GAME_EXPORT pfnPEntityOfEntOffset( int iEntOffset ) +static edict_t *GAME_EXPORT pfnPEntityOfEntOffset( int iEntOffset ) { return (edict_t *)((byte *)svgame.edicts + iEntOffset); } @@ -3348,7 +3378,7 @@ pfnEntOffsetOfPEntity ============= */ -int GAME_EXPORT pfnEntOffsetOfPEntity( const edict_t *pEdict ) +static int GAME_EXPORT pfnEntOffsetOfPEntity( const edict_t *pEdict ) { return (byte *)pEdict - (byte *)svgame.edicts; } @@ -3377,12 +3407,10 @@ pfnPEntityOfEntIndex ============= */ -static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) +static edict_t *pfnPEntityOfEntIndexBroken( int iEntIndex ) { // have to be bug-compatible with GoldSrc in this function - if( host.bugcomp == BUGCOMP_GOLDSRC ) - return SV_PEntityOfEntIndex( iEntIndex, false ); - return SV_PEntityOfEntIndex( iEntIndex, true ); + return SV_PEntityOfEntIndex( iEntIndex, false ); } /* @@ -3391,7 +3419,7 @@ pfnPEntityOfEntIndexAllEntities ============= */ -static edict_t *pfnPEntityOfEntIndexAllEntities( int iEntIndex ) +static edict_t *GAME_EXPORT pfnPEntityOfEntIndexAllEntities( int iEntIndex ) { return SV_PEntityOfEntIndex( iEntIndex, true ); } @@ -3403,7 +3431,7 @@ pfnFindEntityByVars debug thing ============= */ -edict_t* GAME_EXPORT pfnFindEntityByVars( entvars_t *pvars ) +static edict_t *GAME_EXPORT pfnFindEntityByVars( entvars_t *pvars ) { edict_t *pEdict; int i; @@ -3461,7 +3489,7 @@ pfnRegUserMsg ============= */ -int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) +static int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) { int i; @@ -3470,13 +3498,13 @@ int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) if( Q_strlen( pszName ) >= sizeof( svgame.msg[0].name )) { - Con_Printf( S_ERROR "REG_USER_MSG: too long name %s\n", pszName ); + Con_Printf( S_ERROR "%s: too long name %s\n", __func__, pszName ); return svc_bad; // force error } if( iSize > MAX_USERMSG_LENGTH ) { - Con_Printf( S_ERROR "REG_USER_MSG: %s has too big size %i\n", pszName, iSize ); + Con_Printf( S_ERROR "%s: %s has too big size %i\n", __func__, pszName, iSize ); return svc_bad; // force error } @@ -3488,12 +3516,12 @@ int GAME_EXPORT pfnRegUserMsg( const char *pszName, int iSize ) { // see if already registered if( !Q_strcmp( svgame.msg[i].name, pszName )) - return svc_lastmsg + i; // offset + return svgame.msg[i].number; } if( i == MAX_USER_MESSAGES ) { - Con_Printf( S_ERROR "REG_USER_MSG: user messages limit exceeded\n" ); + Con_Printf( S_ERROR "%s: user messages limit exceeded\n", __func__ ); return svc_bad; } @@ -3519,7 +3547,7 @@ pfnAnimationAutomove OBSOLETE, UNUSED ============= */ -void GAME_EXPORT pfnAnimationAutomove( const edict_t* pEdict, float flTime ) +static void GAME_EXPORT pfnAnimationAutomove( const edict_t* pEdict, float flTime ) { } @@ -3542,7 +3570,7 @@ pfnFunctionFromName ============= */ -void *pfnFunctionFromName( const char *pName ) +static void *GAME_EXPORT pfnFunctionFromName( const char *pName ) { return COM_FunctionFromName_SR( svgame.hInstance, pName ); } @@ -3553,7 +3581,7 @@ pfnNameForFunction ============= */ -const char *pfnNameForFunction( void *function ) +static const char *GAME_EXPORT pfnNameForFunction( void *function ) { return COM_NameForFunction( svgame.hInstance, function ); } @@ -3564,7 +3592,7 @@ pfnClientPrintf ============= */ -void GAME_EXPORT pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) +static void GAME_EXPORT pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) { sv_client_t *client; @@ -3597,7 +3625,7 @@ pfnServerPrint print to the server console ============= */ -void GAME_EXPORT pfnServerPrint( const char *szMsg ) +static void GAME_EXPORT pfnServerPrint( const char *szMsg ) { if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) SV_BroadcastPrintf( NULL, "%s", szMsg ); @@ -3623,7 +3651,7 @@ pfnCrosshairAngle ============= */ -void GAME_EXPORT pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw ) +static void GAME_EXPORT pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw ) { sv_client_t *client; @@ -3650,7 +3678,7 @@ pfnSetView ============= */ -void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent ) +static void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent ) { sv_client_t *client; int viewEnt; @@ -3660,7 +3688,7 @@ void GAME_EXPORT pfnSetView( const edict_t *pClient, const edict_t *pViewent ) if(( client = SV_ClientFromEdict( pClient, false )) == NULL ) { - Con_Printf( S_ERROR "PF_SetView_I: not a client!\n" ); + Con_Printf( S_ERROR "%s: not a client!\n", __func__ ); return; } @@ -3686,7 +3714,7 @@ pfnStaticDecal ============= */ -void GAME_EXPORT pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) +static void GAME_EXPORT pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) { SV_CreateDecal( &sv.signon, origin, decalIndex, entityIndex, modelIndex, FDECAL_PERMANENT, 1.0f ); } @@ -3697,7 +3725,7 @@ pfnIsDedicatedServer ============= */ -int GAME_EXPORT pfnIsDedicatedServer( void ) +static int GAME_EXPORT pfnIsDedicatedServer( void ) { return Host_IsDedicated(); } @@ -3709,7 +3737,7 @@ pfnGetPlayerWONId OBSOLETE, UNUSED ============= */ -uint GAME_EXPORT pfnGetPlayerWONId( edict_t *e ) +static uint GAME_EXPORT pfnGetPlayerWONId( edict_t *e ) { return (uint)-1; } @@ -3723,20 +3751,33 @@ vaild map must contain one info_player_deatchmatch */ int GAME_EXPORT pfnIsMapValid( char *filename ) { - uint flags = SV_MapIsValid( filename, GI->mp_entity, NULL ); + uint flags = SV_MapIsValid( filename, NULL ); - if( FBitSet( flags, MAP_IS_EXIST ) && FBitSet( flags, MAP_HAS_SPAWNPOINT )) + if( FBitSet( flags, MAP_IS_EXIST )) return true; return false; } +/* +============= +pfnCvar_RegisterEngineVariable + +use with precaution: this cvar will NOT unlinked +after game.dll is unloaded +============= +*/ +static void GAME_EXPORT pfnCvar_RegisterEngineVariable( cvar_t *variable ) +{ + Cvar_RegisterVariable( (convar_t *)variable ); +} + /* ============= pfnFadeClientVolume ============= */ -void GAME_EXPORT pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ) +static void GAME_EXPORT pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ) { sv_client_t *cl; @@ -3760,7 +3801,7 @@ pfnSetClientMaxspeed fakeclients can be changed speed to ============= */ -void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed ) +static void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed ) { sv_client_t *cl; @@ -3768,8 +3809,12 @@ void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed if(( cl = SV_ClientFromEdict( pEdict, false )) == NULL ) return; + // GoldSrc doesn't bound the value to the movevar here fNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed ); - Info_SetValueForKey( cl->physinfo, "maxspd", va( "%.f", fNewMaxspeed ), MAX_INFO_STRING ); + + // There isn't any reference to "maxspd" anywhere except some commented-out code in SDK + Info_SetValueForKeyf( cl->physinfo, "maxspd", MAX_INFO_STRING, "%.f", fNewMaxspeed ); + cl->edict->v.maxspeed = fNewMaxspeed; } @@ -3779,7 +3824,7 @@ pfnRunPlayerMove ============= */ -void GAME_EXPORT pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec ) +static void GAME_EXPORT pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec ) { sv_client_t *cl, *oldcl; usercmd_t cmd; @@ -3840,17 +3885,17 @@ pfnGetInfoKeyBuffer ============= */ -char *pfnGetInfoKeyBuffer( edict_t *e ) +static char *GAME_EXPORT pfnGetInfoKeyBuffer( edict_t *e ) { sv_client_t *cl; // NULL passes localinfo if( !SV_IsValidEdict( e )) - return SV_Localinfo(); + return svs.localinfo; // world passes serverinfo if( e == svgame.edicts ) - return SV_Serverinfo(); + return svs.serverinfo; // userinfo for specified edict if(( cl = SV_ClientFromEdict( e, false )) != NULL ) @@ -3865,7 +3910,7 @@ pfnSetValueForKey ============= */ -void GAME_EXPORT pfnSetValueForKey( char *infobuffer, char *key, char *value ) +static void GAME_EXPORT pfnSetValueForKey( char *infobuffer, char *key, char *value ) { if( infobuffer == svs.localinfo ) Info_SetValueForStarKey( infobuffer, key, value, MAX_LOCALINFO_STRING ); @@ -3880,7 +3925,7 @@ pfnSetClientKeyValue ============= */ -void GAME_EXPORT pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value ) +static void GAME_EXPORT pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value ) { sv_client_t *cl; @@ -3909,14 +3954,14 @@ pfnGetPhysicsKeyValue ============= */ -const char *pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key ) +static const char *GAME_EXPORT pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { - Con_Printf( S_ERROR "GetPhysicsKeyValue: tried to a non-client!\n" ); + Con_Printf( S_ERROR "%s: tried to a non-client!\n", __func__ ); return ""; } @@ -3929,14 +3974,14 @@ pfnSetPhysicsKeyValue ============= */ -void GAME_EXPORT pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) +static void GAME_EXPORT pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { - Con_Printf( S_ERROR "SetPhysicsKeyValue: tried to a non-client!\n" ); + Con_Printf( S_ERROR "%s: tried to a non-client!\n", __func__ ); return; } @@ -3949,14 +3994,14 @@ pfnGetPhysicsInfoString ============= */ -const char *pfnGetPhysicsInfoString( const edict_t *pClient ) +static const char *GAME_EXPORT pfnGetPhysicsInfoString( const edict_t *pClient ) { sv_client_t *cl; // pfnUserInfoChanged passed if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) { - Con_Printf( S_ERROR "GetPhysicsInfoString: tried to a non-client!\n" ); + Con_Printf( S_ERROR "%s: tried to a non-client!\n", __func__ ); return ""; } @@ -3971,7 +4016,7 @@ register or returns already registered event id a type of event is ignored at this moment ============= */ -word GAME_EXPORT pfnPrecacheEvent( int type, const char *psz ) +static word GAME_EXPORT pfnPrecacheEvent( int type, const char *psz ) { return (word)SV_EventIndex( psz ); } @@ -4000,14 +4045,14 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word // first check event for out of bounds if( eventindex < 1 || eventindex >= MAX_EVENTS ) { - Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); + Con_Printf( S_ERROR "%s: invalid eventindex %i\n", __func__, eventindex ); return; } // check event for precached if( !COM_CheckString( sv.event_precache[eventindex] )) { - Con_Printf( S_ERROR "EV_Playback: event %i was not precached\n", eventindex ); + Con_Printf( S_ERROR "%s: event %i was not precached\n", __func__, eventindex ); return; } @@ -4092,7 +4137,7 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word // setup pvs cluster for invoker if( !FBitSet( flags, FEV_GLOBAL )) { - Mod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); + Mod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 ), true ); mask = fatphs; // using the FatPVS like a PHS } @@ -4117,7 +4162,16 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word continue; } - if( FBitSet( flags, FEV_NOTHOST ) && cl == sv.current_client && FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) + // a1ba: GoldSrc never cleans up host_client pointer (similar to sv.current_client) + // so it's always points at some client and in singleplayer this check always succeedes + // in Xash, however, sv.current_client might be reset and set to NULL + // this is especially dangerous when weapons play events in Think functions + // + // IMHO, it doesn't make sense to me to compare it against current client when we have + // invoker edict pointer but to preserve behaviour check for them both + // + // if it breaks some mods, probably sv.current_client semantics must be reworked to match GoldSrc + if( FBitSet( flags, FEV_NOTHOST ) && ( cl == sv.current_client || cl->edict == pInvoker ) && FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) continue; // will be played on client side if( FBitSet( flags, FEV_HOSTONLY ) && cl->edict != pInvoker ) @@ -4179,6 +4233,21 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word } } +/* +============= +pfnGetCurrentPlayer + +============= +*/ +static int GAME_EXPORT pfnGetCurrentPlayer( void ) +{ + int idx = sv.current_client - svs.clients; + + if( idx < 0 || idx >= svs.maxclients ) + return -1; + return idx; +} + /* ============= pfnSetFatPVS @@ -4187,44 +4256,18 @@ The client will interpolate the view position, so we can't use a single PVS point ============= */ -byte *pfnSetFatPVS( const float *org ) +static byte *GAME_EXPORT pfnSetFatPVS( const float *org ) { - qboolean fullvis = false; + static byte fatpvs[(MAX_MAP_LEAFS+7)/8]; + qboolean fullvis = false, merge = false; - if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) + if( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( )) fullvis = true; - // portals can't change viewpoint! - if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) - { - vec3_t viewPos, offset; + if( FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) + merge = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - - // see code from client.cpp for understanding: - // org = pView->v.origin + pView->v.view_ofs; - // if ( pView->v.flags & FL_DUCKING ) - // { - // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); - // } - // so we have unneeded duck calculations who have affect when player - // is ducked into water. Remove offset to restore right PVS position - if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) - { - VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); - VectorSubtract( org, offset, viewPos ); - } - else VectorCopy( org, viewPos ); - - // build a new PVS frame - Mod_FatPVS( viewPos, FATPVS_RADIUS, fatpvs, world.fatbytes, false, fullvis ); - VectorCopy( viewPos, viewPoint[pfnGetCurrentPlayer()] ); - } - else - { - // merge PVS - Mod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, true, fullvis ); - } + Mod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, merge, fullvis, false ); return fatpvs; } @@ -4237,56 +4280,60 @@ The client will interpolate the hear position, so we can't use a single PHS point ============= */ -byte *pfnSetFatPAS( const float *org ) +static byte *GAME_EXPORT pfnSetFatPAS( const float *org ) { - qboolean fullvis = false; + qboolean fullvis = false, merge = false; - if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) + if( !sv.worldmodel->visdata || sv_novis.value || !org || CL_DisableVisibility( )) fullvis = true; - // portals can't change viewpoint! - if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) - { - vec3_t viewPos, offset; + if( FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) + merge = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - - // see code from client.cpp for understanding: - // org = pView->v.origin + pView->v.view_ofs; - // if ( pView->v.flags & FL_DUCKING ) - // { - // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); - // } - // so we have unneeded duck calculations who have affect when player - // is ducked into water. Remove offset to restore right PVS position - if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) - { - VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); - VectorSubtract( org, offset, viewPos ); - } - else VectorCopy( org, viewPos ); - - // build a new PHS frame - Mod_FatPVS( viewPos, FATPHS_RADIUS, fatphs, world.fatbytes, false, fullvis ); - } - else - { - // merge PHS - Mod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, true, fullvis ); - } + Mod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, merge, fullvis, true ); return fatphs; } +/* +============= +Mod_HeadnodeVisible +============= +*/ +static qboolean Mod_HeadnodeVisible( model_t *mod, mnode_t *node, const byte *visbits, int *lastleaf ) +{ + if( !node || node->contents == CONTENTS_SOLID ) + return false; + + if( node->contents < 0 ) + { + if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster )) + return false; + + if( lastleaf ) + *lastleaf = ((mleaf_t *)node)->cluster; + return true; + } + + if( Mod_HeadnodeVisible( mod, node_child( node, 0, mod ), visbits, lastleaf )) + return true; + + if( Mod_HeadnodeVisible( mod, node_child( node, 1, mod ), visbits, lastleaf )) + return true; + + return false; +} + /* ============= pfnCheckVisibility ============= */ -int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) +static int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) { int i, leafnum; + qboolean large_leafs = FBitSet( sv.worldmodel->flags, MODEL_QBSP2 ); if( !SV_IsValidEdict( ent )) return 0; @@ -4302,17 +4349,28 @@ int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) // check individual leafs for( i = 0; i < ent->num_leafs; i++ ) { - if( CHECKVISBIT( pset, ent->leafnums[i] )) - return 1; // visible passed by leaf + if( large_leafs ) + { + if( CHECKVISBIT( pset, ent->leafnums32[i] )) + return 1; // visible passed by leaf + } + else + { + if( CHECKVISBIT( pset, ent->leafnums16[i] )) + return 1; // visible passed by leaf + } } return 0; } else { - for( i = 0; i < MAX_ENT_LEAFS; i++ ) + for( i = 0; i < MAX_ENT_LEAFS( large_leafs ); i++ ) { - leafnum = ent->leafnums[i]; + if( large_leafs ) + leafnum = ent->leafnums32[i]; + else + leafnum = ent->leafnums16[i]; if( leafnum == -1 ) break; if( CHECKVISBIT( pset, leafnum )) @@ -4320,11 +4378,15 @@ int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset ) } // too many leafs for individual check, go by headnode - if( !Mod_HeadnodeVisible( &sv.worldmodel->nodes[ent->headnode], pset, &leafnum )) + if( !Mod_HeadnodeVisible( sv.worldmodel, &sv.worldmodel->nodes[ent->headnode], pset, &leafnum )) return 0; - ((edict_t *)ent)->leafnums[ent->num_leafs] = leafnum; - ((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS; + if( large_leafs ) + ((edict_t *)ent)->leafnums32[ent->num_leafs] = leafnum; + else + ((edict_t *)ent)->leafnums16[ent->num_leafs] = leafnum; + + ((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS( large_leafs ); return 2; // visible passed by headnode } @@ -4336,7 +4398,7 @@ pfnCanSkipPlayer ============= */ -int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player ) +static int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player ) { sv_client_t *cl; @@ -4346,28 +4408,13 @@ int GAME_EXPORT pfnCanSkipPlayer( const edict_t *player ) return FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) ? true : false; } -/* -============= -pfnGetCurrentPlayer - -============= -*/ -int GAME_EXPORT pfnGetCurrentPlayer( void ) -{ - int idx = sv.current_client - svs.clients; - - if( idx < 0 || idx >= svs.maxclients ) - return -1; - return idx; -} - /* ============= pfnSetGroupMask ============= */ -void GAME_EXPORT pfnSetGroupMask( int mask, int op ) +static void GAME_EXPORT pfnSetGroupMask( int mask, int op ) { svs.groupmask = mask; svs.groupop = op; @@ -4379,7 +4426,7 @@ pfnCreateInstancedBaseline ============= */ -int GAME_EXPORT pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) +static int GAME_EXPORT pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) { if( !baseline || sv.num_instanced >= MAX_CUSTOM_BASELINES ) return 0; @@ -4398,7 +4445,7 @@ pfnEndSection ============= */ -void GAME_EXPORT pfnEndSection( const char *pszSection ) +static void GAME_EXPORT pfnEndSection( const char *pszSection ) { if( !Q_stricmp( "oem_end_credits", pszSection )) Host_Credits (); @@ -4411,7 +4458,7 @@ pfnGetPlayerUserId ============= */ -int GAME_EXPORT pfnGetPlayerUserId( edict_t *e ) +static int GAME_EXPORT pfnGetPlayerUserId( edict_t *e ) { sv_client_t *cl; @@ -4426,7 +4473,7 @@ pfnGetPlayerStats ============= */ -void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) +static void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) { sv_client_t *cl; @@ -4440,13 +4487,18 @@ void GAME_EXPORT pfnGetPlayerStats( const edict_t *pClient, int *ping, int *pack if( ping ) *ping = cl->latency * 1000; } +static void GAME_EXPORT Cmd_AddServerCommand( const char *cmd_name, xcommand_t function ) +{ + Cmd_AddCommandEx( cmd_name, function, "server command", CMD_SERVERDLL, __func__ ); +} + /* ============= pfnForceUnmodified ============= */ -void GAME_EXPORT pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) +static void GAME_EXPORT pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) { consistency_t *pc; int i; @@ -4493,12 +4545,12 @@ pfnVoice_GetClientListening ============= */ -qboolean GAME_EXPORT pfnVoice_GetClientListening( int iReceiver, int iSender ) +static qboolean GAME_EXPORT pfnVoice_GetClientListening( int iReceiver, int iSender ) { iReceiver -= 1; iSender -= 1; - if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) + if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients ) return false; return (FBitSet( svs.clients[iSender].listeners, BIT( iReceiver )) != 0 ); @@ -4510,12 +4562,12 @@ pfnVoice_SetClientListening ============= */ -qboolean GAME_EXPORT pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen ) +static qboolean GAME_EXPORT pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen ) { iReceiver -= 1; iSender -= 1; - if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) + if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender >= svs.maxclients ) return false; if( bListen ) SetBits( svs.clients[iSender].listeners, BIT( iReceiver )); @@ -4533,7 +4585,7 @@ but Xash3D currently doesn't have any security checks return nullstring for now ============= */ -const char *pfnGetPlayerAuthId( edict_t *e ) +static const char *GAME_EXPORT pfnGetPlayerAuthId( edict_t *e ) { return SV_GetClientIDString( SV_ClientFromEdict( e, false )); } @@ -4545,14 +4597,14 @@ pfnQueryClientCvarValue request client cvar value ============= */ -void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cvarName ) +static void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cvarName ) { sv_client_t *cl; if( !COM_CheckString( cvarName )) return; - if(( cl = SV_ClientFromEdict( player, true )) != NULL ) + if(( cl = SV_ClientFromEdict( player, false )) != NULL ) { MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue ); MSG_WriteString( &cl->netchan.message, cvarName ); @@ -4561,7 +4613,7 @@ void GAME_EXPORT pfnQueryClientCvarValue( const edict_t *player, const char *cva { if( svgame.dllFuncs2.pfnCvarValue ) svgame.dllFuncs2.pfnCvarValue( player, "Bad Player" ); - Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); + Con_Printf( S_ERROR "%s: tried to send to a non-client!\n", __func__ ); } } @@ -4572,14 +4624,14 @@ pfnQueryClientCvarValue2 request client cvar value (bugfixed) ============= */ -void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID ) +static void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID ) { sv_client_t *cl; if( !COM_CheckString( cvarName )) return; - if(( cl = SV_ClientFromEdict( player, true )) != NULL ) + if(( cl = SV_ClientFromEdict( player, false )) != NULL ) { MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue2 ); MSG_WriteLong( &cl->netchan.message, requestID ); @@ -4589,7 +4641,7 @@ void GAME_EXPORT pfnQueryClientCvarValue2( const edict_t *player, const char *cv { if( svgame.dllFuncs2.pfnCvarValue2 ) svgame.dllFuncs2.pfnCvarValue2( player, requestID, cvarName, "Bad Player" ); - Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); + Con_Printf( S_ERROR "%s: tried to send to a non-client!\n", __func__ ); } } @@ -4605,12 +4657,60 @@ static int GAME_EXPORT pfnGetLocalizedStringLength( const char *label ) return 0; } + +/* +============= +pfnRegisterTutorMessageShown + +only exists in PlayStation version +============= +*/ +static void GAME_EXPORT pfnRegisterTutorMessageShown( int mid ) +{ +} + +/* +============= +pfnGetTimesTutorMessageShown + +only exists in PlayStation version +============= +*/ +static int GAME_EXPORT pfnGetTimesTutorMessageShown( int mid ) +{ + return 0; +} + +static void GAME_EXPORT pfnGetGameDir( char *out ) +{ + char rootdir[MAX_SYSPATH]; + + if( !out ) + return; + + if( !FBitSet( host.bugcomp, BUGCOMP_GET_GAME_DIR_FULL_PATH )) + { + Q_strncpy( out, GI->gamefolder, 256 ); + } + else + { + // in GoldSrc pre-1.1.1.1, it's a full path to game directory, limited by 256 characters + // however the full path might easily overflow that limitation + // here we check if it would overflow and just return game folder in that case + if( !g_fsapi.GetRootDirectory( rootdir, sizeof( rootdir )) + || Q_snprintf( out, 256, "%s/%s", rootdir, GI->gamefolder ) < 0 ) + { + Q_strncpy( out, GI->gamefolder, 256 ); + } + } +} + // engine callbacks static enginefuncs_t gEngfuncs = { pfnPrecacheModel, SV_SoundIndex, - pfnSetModel, + SV_SetModel, pfnModelIndex, pfnModelFrames, pfnSetSize, @@ -4623,7 +4723,7 @@ static enginefuncs_t gEngfuncs = pfnChangeYaw, pfnChangePitch, SV_FindEntityByString, - pfnGetEntityIllum, + SV_LightForEntity, pfnFindEntityInSphere, pfnFindClientInPVS, pfnEntitiesInPVS, @@ -4680,7 +4780,7 @@ static enginefuncs_t gEngfuncs = pfnPEntityOfEntOffset, pfnEntOffsetOfPEntity, pfnIndexOfEdict, - pfnPEntityOfEntIndex, + pfnPEntityOfEntIndexAllEntities, pfnFindEntityByVars, pfnGetModelPtr, pfnRegUserMsg, @@ -4706,7 +4806,7 @@ static enginefuncs_t gEngfuncs = COM_LoadFileForMe, COM_FreeFile, pfnEndSection, - COM_CompareFileTime, + pfnCompareFileTime, pfnGetGameDir, pfnCvar_RegisterEngineVariable, pfnFadeClientVolume, @@ -4745,7 +4845,7 @@ static enginefuncs_t gEngfuncs = Delta_UnsetFieldByIndex, pfnSetGroupMask, pfnCreateInstancedBaseline, - pfnCVarDirectSet, + (void*)Cvar_DirectSet, pfnForceUnmodified, pfnGetPlayerStats, Cmd_AddServerCommand, @@ -4769,6 +4869,15 @@ static enginefuncs_t gEngfuncs = pfnPEntityOfEntIndexAllEntities, }; +static void SV_FreeKeyValueStrings( KeyValueData *kvd, int numpairs ) +{ + for( int i = 0; i < numpairs; i++ ) + { + Mem_Free( kvd[i].szKeyName ); + Mem_Free( kvd[i].szValue ); + } +} + /* ==================== SV_ParseEdict @@ -4777,39 +4886,37 @@ Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. ==================== */ -qboolean SV_ParseEdict( char **pfile, edict_t *ent ) +static qboolean SV_ParseEdict( char **pfile, edict_t *ent ) { KeyValueData pkvd[256]; // per one entity - qboolean adjust_origin = false; + qboolean adjust_origin = false, customentity; int i, numpairs = 0; - char *classname = NULL; - char token[2048]; - vec3_t origin; + const char *classname = NULL; // go through all the dictionary pairs while( 1 ) { string keyname; + char value[2048]; + int len; // parse key - if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) - Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); - if( token[0] == '}' ) break; // end of desc + if(( *pfile = COM_ParseFile( *pfile, keyname, sizeof( keyname ))) == NULL ) + Host_Error( "%s: EOF without closing brace\n", __func__ ); - Q_strncpy( keyname, token, sizeof( keyname )); + if( keyname[0] == '}' ) + break; // end of desc // parse value - if(( *pfile = COM_ParseFile( *pfile, token, sizeof( token ))) == NULL ) - Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); + if(( *pfile = COM_ParseFile( *pfile, value, sizeof( value ))) == NULL ) + Host_Error( "%s: EOF without closing brace\n", __func__ ); - if( token[0] == '}' ) - Host_Error( "ED_ParseEdict: closing brace without data\n" ); - - // ignore attempts to set key "" - if( !keyname[0] ) continue; + if( value[0] == '}' ) + Host_Error( "%s: closing brace without data\n", __func__ ); + // ignore attempts to set empty key or value // "wad" field is already handled - if( !Q_strcmp( keyname, "wad" )) + if( !keyname[0] || !value[0] || !Q_strcmp( keyname, "wad" )) continue; // keynames with a leading underscore are used for @@ -4817,57 +4924,83 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && keyname[0] == '_' ) continue; - // ignore attempts to set value "" - if( !token[0] ) continue; + // classname must be first + if( !Q_strcmp( keyname, "classname" )) + { + KeyValueData kvd = { + .szClassName = NULL, + .szKeyName = keyname, + .szValue = value, + .fHandled = false + }; + + // don't allow double classnames + if( classname != NULL ) + continue; + + svgame.dllFuncs.pfnKeyValue( ent, &kvd ); + + // ideally, all game dlls should handle classname. + // throw an error for now, improve the logic if it causes + // compatibility issues with Xash-based games + if( !kvd.fHandled ) + Host_Error( "%s: game didn't handled \"%s\" classname\n", __func__, value ); + + // this lets game dll override custom entity classname + // to something bogus that's exported in game dll + classname = STRING( ent->v.classname ); + continue; + } + + // GoldSrc removes trailing spaces + // but does this after sucking out classname + // which doesn't have similar check + for( len = Q_strlen( keyname ); len > 0 && keyname[len - 1] == ' '; len-- ) + keyname[len - 1] = '\0'; // create keyvalue strings pkvd[numpairs].szClassName = (char*)""; // unknown at this moment pkvd[numpairs].szKeyName = copystring( keyname ); - pkvd[numpairs].szValue = copystring( token ); + pkvd[numpairs].szValue = copystring( value ); pkvd[numpairs].fHandled = false; + numpairs++; - if( !Q_strcmp( keyname, "classname" ) && classname == NULL ) - classname = copystring( pkvd[numpairs].szValue ); - if( ++numpairs >= 256 ) break; + if( numpairs > ARRAYSIZE( pkvd )) + { + if( classname ) + Con_Printf( S_ERROR "%s: too many keyvalue pairs for %s!\n", __func__, classname ); + else Con_Printf( S_ERROR "%s: too many keyvalue pairs!\n", __func__ ); + break; + } } if( classname == NULL ) { // release allocated strings - for( i = 0; i < numpairs; i++ ) - { - Mem_Free( pkvd[i].szKeyName ); - Mem_Free( pkvd[i].szValue ); - } + SV_FreeKeyValueStrings( pkvd, numpairs ); return false; } - ent = SV_AllocPrivateData( ent, ALLOC_STRING( classname )); + ent = SV_AllocPrivateData( ent, ent->v.classname, &customentity ); if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME )) { // release allocated strings - for( i = 0; i < numpairs; i++ ) - { - Mem_Free( pkvd[i].szKeyName ); - Mem_Free( pkvd[i].szValue ); - } + SV_FreeKeyValueStrings( pkvd, numpairs ); return false; } - if( FBitSet( ent->v.flags, FL_CUSTOMENTITY )) + if( customentity ) { - if( numpairs < 256 ) - { - pkvd[numpairs].szClassName = (char*)"custom"; - pkvd[numpairs].szKeyName = (char*)"customclass"; - pkvd[numpairs].szValue = classname; - pkvd[numpairs].fHandled = false; - numpairs++; - } + KeyValueData kvd = { + .szClassName = (char *)"custom", + .szKeyName = (char *)"customclass", + .szValue = (char *)classname, + .fHandled = false + }; - // clear it now - no longer used - ClearBits( ent->v.flags, FL_CUSTOMENTITY ); + svgame.dllFuncs.pfnKeyValue( ent, &kvd ); + // no fHandled check, GoldSrc behavior } #ifdef HACKS_RELATED_HLMODS @@ -4881,6 +5014,14 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) for( i = 0; i < numpairs; i++ ) { + char *keyname, *value; + char temp[MAX_VA_STRING]; + +#if 0 // this is stupid bug in GoldSrc, disable + if( !Q_strcmp( pkvd[i].szValue, classname )) + continue; +#endif + if( !Q_strcmp( pkvd[i].szKeyName, "angle" )) { float flYawAngle = Q_atof( pkvd[i].szValue ); @@ -4890,7 +5031,10 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) pkvd[i].szKeyName = copystring( "angles" ); if( flYawAngle >= 0.0f ) - pkvd[i].szValue = copystring( va( "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] )); + { + Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] ); + pkvd[i].szValue = copystring( temp ); + } else if( flYawAngle == -1.0f ) pkvd[i].szValue = copystring( "-90 0 0" ); else if( flYawAngle == -2.0f ) @@ -4898,39 +5042,29 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error } -#ifdef HACKS_RELATED_HLMODS if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) { - char *pstart = pkvd[i].szValue; + char *pstart = pkvd[i].szValue; + vec3_t origin; COM_ParseVector( &pstart, origin, 3 ); Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these - pkvd[i].szValue = copystring( va( "%g %g %g", origin[0], origin[1], origin[2] - 16.0f )); - } -#endif - if( !Q_strcmp( pkvd[i].szKeyName, "light" )) - { - Mem_Free( pkvd[i].szKeyName ); - pkvd[i].szKeyName = copystring( "light_level" ); + + Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f ); + pkvd[i].szValue = copystring( temp ); } - if( !pkvd[i].fHandled ) - { - pkvd[i].szClassName = classname; - svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); - } + // do not leak memory if game overwritten these pointers + keyname = pkvd[i].szKeyName; + value = pkvd[i].szValue; - // no reason to keep this data - if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szKeyName )) - Mem_Free( pkvd[i].szKeyName ); + pkvd[i].szClassName = (char *)classname; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); - if( Mem_IsAllocatedExt( host.mempool, pkvd[i].szValue )) - Mem_Free( pkvd[i].szValue ); + Mem_Free( keyname ); + Mem_Free( value ); } - if( Mem_IsAllocatedExt( host.mempool, classname )) - Mem_Free( classname ); - return true; } @@ -4946,7 +5080,7 @@ Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. ================ */ -void SV_LoadFromFile( const char *mapname, char *entities ) +static void SV_LoadFromFile( const char *mapname, char *entities ) { char token[2048]; qboolean create_world = true; @@ -4964,7 +5098,7 @@ void SV_LoadFromFile( const char *mapname, char *entities ) while(( entities = COM_ParseFile( entities, token, sizeof( token ))) != NULL ) { if( token[0] != '{' ) - Host_Error( "ED_LoadFromFile: found %s when expecting {\n", token ); + Host_Error( "%s: found %s when expecting {\n", __func__, token ); if( create_world ) { @@ -5040,6 +5174,8 @@ void SV_SpawnEntities( const char *mapname ) void SV_UnloadProgs( void ) { + pending_cvar_t *pending_cvars_list; + if( !svgame.hInstance ) return; @@ -5049,14 +5185,13 @@ void SV_UnloadProgs( void ) /// SV_UnloadProgs will be disabled //Mod_ClearUserData (); - SV_FreeStringPool(); + pending_cvars_list = Cvar_PrepareToUnlink( FCVAR_EXTDLL ); if( svgame.dllFuncs2.pfnGameShutdown != NULL ) svgame.dllFuncs2.pfnGameShutdown (); // now we can unload cvars Cvar_FullSet( "host_gameloaded", "0", FCVAR_READ_ONLY ); - Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); // free entity baselines Z_Free( svs.static_entities ); @@ -5068,12 +5203,13 @@ void SV_UnloadProgs( void ) // must unlink all game cvars, // before pointers on them will be lost... - Cvar_Unlink( FCVAR_EXTDLL ); + Cvar_UnlinkPendingCvars( pending_cvars_list ); Cmd_Unlink( CMD_SERVERDLL ); + SV_FreeStringPool(); + Mod_ResetStudioAPI (); - svs.game_library_loaded = false; COM_FreeLibrary( svgame.hInstance ); Mem_FreePool( &svgame.mempool ); memset( &svgame, 0, sizeof( svgame )); @@ -5091,7 +5227,8 @@ qboolean SV_LoadProgs( const char *name ) static playermove_t gpMove; edict_t *e; - if( svgame.hInstance ) SV_UnloadProgs(); + if( svgame.hInstance ) + return true; // fill it in svgame.pmove = &gpMove; @@ -5111,8 +5248,13 @@ qboolean SV_LoadProgs( const char *name ) // make sure what physic functions is cleared memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); + // revert fix for pfnPEntityOfEntIndex to be compatible with GoldSrc + // games that rely on this bug + if( FBitSet( host.bugcomp, BUGCOMP_PENTITYOFENTINDEX_FLAG )) + gEngfuncs.pfnPEntityOfEntIndex = pfnPEntityOfEntIndexBroken; + // make local copy of engfuncs to prevent overwrite it with bots.dll - memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); + gpEngfuncs = gEngfuncs; GetEntityAPI = (APIFUNCTION)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI" ); GetEntityAPI2 = (APIFUNCTION2)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI2" ); @@ -5121,9 +5263,9 @@ qboolean SV_LoadProgs( const char *name ) if( !GetEntityAPI && !GetEntityAPI2 ) { COM_FreeLibrary( svgame.hInstance ); - Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GetEntityAPI proc\n" ); + Con_Printf( S_ERROR "%s: failed to get address of GetEntityAPI proc\n", __func__ ); svgame.hInstance = NULL; - Mem_FreePool(&svgame.mempool); + Mem_FreePool( &svgame.mempool ); return false; } @@ -5132,9 +5274,9 @@ qboolean SV_LoadProgs( const char *name ) if( !GiveFnptrsToDll ) { COM_FreeLibrary( svgame.hInstance ); - Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GiveFnptrsToDll proc\n" ); + Con_Printf( S_ERROR "%s: failed to get address of GiveFnptrsToDll proc\n", __func__ ); svgame.hInstance = NULL; - Mem_FreePool(&svgame.mempool); + Mem_FreePool( &svgame.mempool ); return false; } @@ -5148,7 +5290,7 @@ qboolean SV_LoadProgs( const char *name ) if( !GiveNewDllFuncs( &svgame.dllFuncs2, &version )) { if( version != NEW_DLL_FUNCTIONS_VERSION ) - Con_Printf( S_WARN "SV_LoadProgs: new interface version %i should be %i\n", NEW_DLL_FUNCTIONS_VERSION, version ); + Con_Printf( S_WARN "%s: new interface version %i should be %i\n", __func__, NEW_DLL_FUNCTIONS_VERSION, version ); memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); } } @@ -5159,35 +5301,38 @@ qboolean SV_LoadProgs( const char *name ) { if( !GetEntityAPI2( &svgame.dllFuncs, &version )) { - Con_Printf( S_WARN "SV_LoadProgs: interface version %i should be %i\n", INTERFACE_VERSION, version ); + if( INTERFACE_VERSION != version ) + Con_Printf( S_WARN "%s: interface version %i should be %i\n", __func__, INTERFACE_VERSION, version ); // fallback to old API - if( !GetEntityAPI( &svgame.dllFuncs, version )) + if( GetEntityAPI && !GetEntityAPI( &svgame.dllFuncs, version )) { COM_FreeLibrary( svgame.hInstance ); - Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); + Con_Printf( S_ERROR "%s: couldn't get entity API\n", __func__ ); svgame.hInstance = NULL; - Mem_FreePool(&svgame.mempool); + Mem_FreePool( &svgame.mempool ); return false; } + else Con_Reportf( "%s: ^2initailized legacy EntityAPI ^7ver. %i\n", __func__, version ); } - else Con_Reportf( "SV_LoadProgs: ^2initailized extended EntityAPI ^7ver. %i\n", version ); + else Con_Reportf( "%s: ^2initailized extended EntityAPI ^7ver. %i\n", __func__, version ); } - else if( !GetEntityAPI( &svgame.dllFuncs, version )) + else if( GetEntityAPI && !GetEntityAPI( &svgame.dllFuncs, version )) { COM_FreeLibrary( svgame.hInstance ); - Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); + Con_Printf( S_ERROR "%s: couldn't get entity API\n", __func__ ); svgame.hInstance = NULL; - Mem_FreePool(&svgame.mempool); + Mem_FreePool( &svgame.mempool ); return false; } + else Con_Reportf( "%s: ^2initailized legacy EntityAPI ^7ver. %i\n", __func__, version ); SV_InitOperatorCommands(); Mod_InitStudioAPI(); if( !SV_InitPhysicsAPI( )) { - Con_Printf( S_WARN "SV_LoadProgs: couldn't get physics API\n" ); + Con_Printf( S_WARN "%s: couldn't get physics API\n", __func__ ); } // grab function SV_SaveGameComment diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index ab32ad44..a4bf8563 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -18,6 +18,7 @@ GNU General Public License for more details. #include "net_encode.h" #include "library.h" #include "voice.h" +#include "pm_local.h" #if XASH_LOW_MEMORY != 2 int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; @@ -26,6 +27,17 @@ server_t sv; // local server server_static_t svs; // persistant server info svgame_static_t svgame; // persistant game info +/* +================== +Host_SetServerState +================== +*/ +static void Host_SetServerState( int state ) +{ + Cvar_FullSet( "host_serverstate", va( "%i", state ), FCVAR_READ_ONLY ); + sv.state = state; +} + /* ================ SV_AddResource @@ -55,7 +67,7 @@ SV_SendSingleResource hot precache on a flying ================ */ -void SV_SendSingleResource( const char *name, resourcetype_t type, int index, byte flags ) +static void SV_SendSingleResource( const char *name, resourcetype_t type, int index, byte flags ) { resource_t *pResource = &sv.resources[sv.num_resources]; int nSize = 0; @@ -261,25 +273,35 @@ int GAME_EXPORT SV_GenericIndex( const char *filename ) return i; } -/* -================ -SV_ModelHandle - -get model by handle -================ -*/ -model_t *SV_ModelHandle( int modelindex ) +static resourcetype_t SV_DetermineResourceType( const char *filename ) { - if( modelindex < 0 || modelindex >= MAX_MODELS ) - return NULL; - return sv.models[modelindex]; + if( !Q_strncmp( filename, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ) && Sound_SupportedFileFormat( COM_FileExtension( filename ))) + return t_sound; + else + return t_generic; } -void SV_ReadResourceList( const char *filename ) +static const char *SV_GetResourceTypeName( resourcetype_t restype ) { - string token; + switch( restype ) + { + case t_decal: return "decal"; + case t_eventscript: return "eventscript"; + case t_generic: return "generic"; + case t_model: return "model"; + case t_skin: return "skin"; + case t_sound: return "sound"; + case t_world: return "world"; + default: return "unknown"; + } +} + +static void SV_ReadResourceList( const char *filename ) +{ + string token; byte *afile; char *pfile; + resourcetype_t restype; afile = FS_LoadFile( filename, NULL, false ); if( !afile ) return; @@ -294,8 +316,23 @@ void SV_ReadResourceList( const char *filename ) if( !COM_IsSafeFileToDownload( token )) continue; - Con_DPrintf( " %s\n", token ); - SV_GenericIndex( token ); + COM_FixSlashes( token ); + restype = SV_DetermineResourceType( token ); + Con_DPrintf( " %s (%s)\n", token, SV_GetResourceTypeName( restype )); + switch( restype ) + { + // TODO do we need to handle other resource types specifically too? + case t_sound: + { + const char *filepath = token; + filepath += sizeof( DEFAULT_SOUNDPATH ) - 1; // skip "sound/" part + SV_SoundIndex( filepath ); + break; + } + default: + SV_GenericIndex( token ); + break; + } } Con_DPrintf( "----------------------------------\n" ); @@ -309,16 +346,25 @@ SV_CreateGenericResources loads external resource list ================ */ -void SV_CreateGenericResources( void ) +static void SV_CreateGenericResources( void ) { string filename; + int i; Q_strncpy( filename, sv.model_precache[1], sizeof( filename )); - COM_ReplaceExtension( filename, ".res" ); + COM_ReplaceExtension( filename, ".res", sizeof( filename )); COM_FixSlashes( filename ); SV_ReadResourceList( filename ); SV_ReadResourceList( "reslist.txt" ); + + for( i = 0; i < world.wadlist.count; i++ ) + { + if( world.wadlist.wadusage[i] > 0 ) + { + SV_GenericIndex( world.wadlist.wadnames[i] ); + } + } } /* @@ -328,7 +374,7 @@ SV_CreateResourceList add resources to common list ================ */ -void SV_CreateResourceList( void ) +static void SV_CreateResourceList( void ) { qboolean ffirstsent = false; int i, nSize; @@ -393,7 +439,7 @@ void SV_CreateResourceList( void ) SV_WriteVoiceCodec ================ */ -void SV_WriteVoiceCodec( sizebuf_t *msg ) +static void SV_WriteVoiceCodec( sizebuf_t *msg ) { MSG_BeginServerCmd( msg, svc_voiceinit ); MSG_WriteString( msg, VOICE_DEFAULT_CODEC ); @@ -411,14 +457,15 @@ baseline will be transmitted INTERNAL RESOURCE ================ */ -void SV_CreateBaseline( void ) +static void SV_CreateBaseline( void ) { entity_state_t nullstate, *base; int playermodel; int delta_type; int entnum; - SV_WriteVoiceCodec( &sv.signon ); + if( svs.maxclients > 1 ) + SV_WriteVoiceCodec( &sv.signon ); if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE ); @@ -520,7 +567,7 @@ void SV_FreeOldEntities( void ) } // decrement svgame.numEntities if the highest number entities died - for( ; EDICT_NUM( svgame.numEntities - 1 )->free; svgame.numEntities-- ); + for( ; ( ent = EDICT_NUM( svgame.numEntities - 1 )) && ent->free; svgame.numEntities-- ); } /* @@ -589,6 +636,9 @@ void SV_ActivateServer( int runPhysics ) Netchan_Clear( &cl->netchan ); cl->delta_sequence = -1; + + // bump connect timeout + cl->connection_started = host.realtime; } // invoke to refresh all movevars @@ -621,17 +671,6 @@ void SV_ActivateServer( int runPhysics ) if( sv.ignored_world_decals ) Con_Printf( S_WARN "%i static decals was rejected due buffer overflow\n", sv.ignored_world_decals ); - - if( svs.maxclients > 1 ) - { - const char *cycle = Cvar_VariableString( "mapchangecfgfile" ); - - if( COM_CheckString( cycle )) - Cbuf_AddText( va( "exec %s\n", cycle )); - - if( public_server->value ) - Master_Add( ); - } } /* @@ -644,6 +683,13 @@ deactivate server, free edicts, strings etc void SV_DeactivateServer( void ) { int i; + const char *cycle = Cvar_VariableString( "disconcfgfile" ); + + if( COM_CheckString( cycle )) + Cbuf_AddTextf( "exec %s\n", cycle ); + + if( COM_CheckStringEmpty( sv.name )) + Cbuf_AddTextf( "exec maps/%s_unload.cfg\n", sv.name ); if( !svs.initialized || sv.state == ss_dead ) return; @@ -654,9 +700,10 @@ void SV_DeactivateServer( void ) SV_FreeEdicts (); - SV_ClearPhysEnts (); + PM_ClearPhysEnts( svgame.pmove ); - SV_EmptyStringPool(); + SV_EmptyStringPool( true ); + Mem_EmptyPool( svgame.stringspool ); for( i = 0; i < svs.maxclients; i++ ) { @@ -684,7 +731,7 @@ qboolean SV_InitGame( void ) { string dllpath; - if( svs.game_library_loaded ) + if( svgame.hInstance ) return true; // first initialize? @@ -699,7 +746,6 @@ qboolean SV_InitGame( void ) } // client frames will be allocated in SV_ClientConnect - svs.game_library_loaded = true; return true; } @@ -737,21 +783,22 @@ SV_SetupClients determine the game type and prepare clients ================ */ -void SV_SetupClients( void ) +static void SV_SetupClients( void ) { qboolean changed_maxclients = false; // check if clients count was really changed - if( svs.maxclients != (int)sv_maxclients->value ) + if( svs.maxclients != (int)sv_maxclients.value ) changed_maxclients = true; if( !changed_maxclients ) return; // nothing to change // if clients count was changed we need to run full shutdown procedure - if( svs.maxclients ) Host_ShutdownServer(); + if( svs.maxclients ) + SV_Shutdown( "Server was killed due to maxclients change\n" ); // copy the actual value from cvar - svs.maxclients = (int)sv_maxclients->value; + svs.maxclients = (int)sv_maxclients.value; // dedicated servers are can't be single player and are usually DM if( Host_IsDedicated() ) @@ -779,10 +826,10 @@ void SV_SetupClients( void ) // init network stuff NET_Config(( svs.maxclients > 1 ), true ); svgame.numEntities = svs.maxclients + 1; // clients + world - ClearBits( sv_maxclients->flags, FCVAR_CHANGED ); + ClearBits( sv_maxclients.flags, FCVAR_CHANGED ); } -static qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer ) +qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer ) { char headbuf[1024], buffer[1024]; int i, num_bytes, lumplen; @@ -859,6 +906,98 @@ static qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean m return 1; } +void SV_FreeTestPacket( void ) +{ + if( svs.testpacket_buf ) + { + Mem_Free( svs.testpacket_buf ); + svs.testpacket_buf = NULL; + } + + if( svs.testpacket_crcs ) + { + Mem_Free( svs.testpacket_crcs ); + svs.testpacket_crcs = NULL; + } +} + +/* +================ +SV_GenerateTestPacket +================ +*/ +static void SV_GenerateTestPacket( void ) +{ + const int maxsize = FRAGMENT_MAX_SIZE; + uint32_t crc; + file_t *file; + byte *filepos; + int i; + + if( !sv_allow_testpacket.value ) + { + SV_FreeTestPacket(); + return; + } + + // testpacket already generated once, exit + // testpacket and lookup table takes ~300k of memory + // disable for low memory mode + if( svs.testpacket_buf || XASH_LOW_MEMORY > 0 ) + return; + + // don't need in singleplayer with full client + if( svs.maxclients <= 1 ) + return; + + file = FS_Open( "gfx.wad", "rb", false ); + if( FS_FileLength( file ) < maxsize ) + { + FS_Close( file ); + return; + } + + svs.testpacket_buf = Mem_Malloc( host.mempool, sizeof( *svs.testpacket_buf ) * maxsize ); + + // write packet base data + MSG_Init( &svs.testpacket, "BandWidthTest", svs.testpacket_buf, maxsize ); + MSG_WriteLong( &svs.testpacket, -1 ); + MSG_WriteString( &svs.testpacket, S2C_BANDWIDTHTEST ); + svs.testpacket_crcpos = svs.testpacket.pData + MSG_GetNumBytesWritten( &svs.testpacket ); + MSG_WriteDword( &svs.testpacket, 0 ); // to be changed by crc + + // time to read our file + svs.testpacket_filepos = MSG_GetNumBytesWritten( &svs.testpacket ); + svs.testpacket_filelen = maxsize - svs.testpacket_filepos; + + filepos = svs.testpacket.pData + svs.testpacket_filepos; + FS_Read( file, filepos, svs.testpacket_filelen ); + FS_Close( file ); + + // now generate checksums lookup table + svs.testpacket_crcs = Mem_Malloc( host.mempool, sizeof( *svs.testpacket_crcs ) * svs.testpacket_filelen ); + crc = 0; // intentional omit of CRC32_Init because of the client + + // TODO: shrink to minimum! + for( i = 0; i < svs.testpacket_filelen; i++ ) + { + uint32_t crc2; + + CRC32_ProcessByte( &crc, filepos[i] ); + svs.testpacket_crcs[i] = crc; +#if 0 + // test + crc2 = 0; + CRC32_ProcessBuffer( &crc2, filepos, i + 1 ); + if( svs.testpacket_crcs[i] != crc2 ) + { + Con_Printf( "%d: 0x%x != 0x%x\n", i, svs.testpacket_crcs[i], crc2 ); + svs.testpacket_crcs[i] = crc = crc2; + } +#endif + } +} + /* ================ SV_SpawnServer @@ -869,14 +1008,20 @@ clients along with it. */ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean background ) { - int i, current_skill; - edict_t *ent; + int i, current_skill; + edict_t *ent; + const char *cycle; SV_SetupClients(); if( !SV_InitGame( )) return false; + Delta_Init(); // re-initialize delta + + // unlock sv_cheats in local game + ClearBits( sv_cheats.flags, FCVAR_READ_ONLY ); + svs.initialized = true; Log_Open(); Log_Printf( "Loading map \"%s\"\n", mapname ); @@ -885,6 +1030,16 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba svs.timestart = Sys_DoubleTime(); svs.spawncount++; // any partially connected client will be restarted + for( i = 0; i < ARRAYSIZE( svs.challenge_salt ); i++ ) + svs.challenge_salt[i] = COM_RandomLong( 0, 0x7FFFFFFE ); + + cycle = Cvar_VariableString( "mapchangecfgfile" ); + + if( COM_CheckString( cycle )) + Cbuf_AddTextf( "exec %s\n", cycle ); + + Cbuf_AddTextf( "exec maps/%s_load.cfg\n", mapname ); + // let's not have any servers with no name if( !COM_CheckString( hostname.string )) Cvar_Set( "hostname", svgame.dllFuncs.pfnGetGameDescription ? svgame.dllFuncs.pfnGetGameDescription() : FS_Title( )); @@ -919,6 +1074,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba current_skill = bound( 0, current_skill, 3 ); Cvar_SetValue( "skill", (float)current_skill ); + // enforce hpk_max_size + HPAK_CheckSize( hpk_custom_file.string ); + // force normal player collisions for single player if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); @@ -943,8 +1101,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba // force normal player collisions for single player if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); - // make sure what server name doesn't contain path and extension - COM_FileBase( mapname, sv.name ); + // allow loading maps from subdirectories, strip extension anyway + Q_strncpy( sv.name, mapname, sizeof( sv.name )); + COM_StripExtension( sv.name ); // precache and static commands can be issued during map initialization Host_SetServerState( ss_loading ); @@ -968,7 +1127,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba for( i = WORLD_INDEX; i < sv.worldmodel->numsubmodels; i++ ) { - Q_sprintf( sv.model_precache[i+1], "*%i", i ); + Q_snprintf( sv.model_precache[i+1], sizeof( sv.model_precache[i+1] ), "*%i", i ); sv.models[i+1] = Mod_ForName( sv.model_precache[i+1], false, false ); SetBits( sv.model_precache_flags[i+1], RES_FATALIFMISSING ); } @@ -987,7 +1146,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba } // heartbeats will always be sent to the id master - svs.last_heartbeat = MAX_HEARTBEAT; // send immediately + NET_MasterClear(); // get actual movevars SV_UpdateMovevars( true ); @@ -995,6 +1154,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba // clear physics interaction links SV_ClearWorld(); + // pregenerate test packet + SV_GenerateTestPacket(); + return true; } @@ -1013,26 +1175,6 @@ int SV_GetMaxClients( void ) return svs.maxclients; } -void SV_InitGameProgs( void ) -{ - string dllpath; - - if( svgame.hInstance ) return; // already loaded - - COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath )); - - // just try to initialize - SV_LoadProgs( dllpath ); -} - -void SV_FreeGameProgs( void ) -{ - if( svs.initialized ) return; // server is active - - // unload progs (free cvars and commands) - SV_UnloadProgs(); -} - /* ================ SV_ExecLoadLevel diff --git a/engine/server/sv_log.c b/engine/server/sv_log.c index 7c1c5d6b..19b45ac5 100644 --- a/engine/server/sv_log.c +++ b/engine/server/sv_log.c @@ -76,8 +76,8 @@ void Log_Open( void ) } if( fp ) svs.log.file = fp; - Log_Printf( "Log file started (file \"%s\") (game \"%s\") (version \"%i/%s/%d\")\n", - szTestFile, Info_ValueForKey( SV_Serverinfo(), "*gamedir" ), PROTOCOL_VERSION, XASH_VERSION, Q_buildnum() ); + Log_Printf( "Log file started (file \"%s\") (game \"%s\") (version \"%i/" XASH_VERSION "/%d\")\n", + szTestFile, Info_ValueForKey( svs.serverinfo, "*gamedir" ), PROTOCOL_VERSION, Q_buildnum() ); } void Log_Close( void ) @@ -169,11 +169,21 @@ void SV_SetLogAddress_f( void ) int port; string addr; + if( svs.log.net_log && Cmd_Argc() == 2 && !Q_strcmp( Cmd_Argv( 1 ), "off" )) + { + svs.log.net_log = false; + memset( &svs.log.net_address, 0, sizeof( netadr_t )); + + Con_Printf( "logaddress: disabled.\n" ); + + return; + } + if( Cmd_Argc() != 3 ) { - Con_Printf( "logaddress: usage\nlogaddress ip port\n" ); + Con_Printf( S_USAGE "logaddress < ip port | off >\n" ); - if( svs.log.active ) + if( svs.log.active && svs.log.net_log ) Con_Printf( "current: %s\n", NET_AdrToString( svs.log.net_address )); return; @@ -214,7 +224,7 @@ void SV_ServerLog_f( void ) { if( Cmd_Argc() != 2 ) { - Con_Printf("usage: log < on|off >\n" ); + Con_Printf( S_USAGE "log < on|off >\n" ); if( svs.log.active ) Con_Printf( "currently logging\n" ); @@ -225,7 +235,10 @@ void SV_ServerLog_f( void ) if( !Q_stricmp( Cmd_Argv( 1 ), "off" )) { if( svs.log.active ) + { Log_Close(); + svs.log.active = false; + } } else if( !Q_stricmp( Cmd_Argv( 1 ), "on" )) { diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 1122905b..e3eac09a 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -18,38 +18,40 @@ GNU General Public License for more details. #include "net_encode.h" #include "platform/platform.h" -#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes - // server cvars CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); CVAR_DEFINE_AUTO( sv_nat, "0", 0, "enable NAT bypass for this server" ); CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); +CVAR_DEFINE_AUTO( sv_allow_autoaim, "0", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option (for HL25 compatibility)" ); CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); CVAR_DEFINE_AUTO( sv_unlagpush, "0.0", 0, "interpolation bias for unlag time" ); CVAR_DEFINE_AUTO( sv_unlagsamples, "1", 0, "max samples to interpolate" ); -CVAR_DEFINE_AUTO( rcon_password, "", 0, "remote connect password" ); -CVAR_DEFINE_AUTO( sv_filterban, "1", 0, "filter banned users" ); +CVAR_DEFINE_AUTO( rcon_password, "", FCVAR_PROTECTED | FCVAR_PRIVILEGED, "remote connect password" ); +CVAR_DEFINE_AUTO( rcon_enable, "1", FCVAR_PROTECTED, "enable accepting remote commands on server" ); +// TODO: CVAR_DEFINE_AUTO( sv_filterban, "1", 0, "filter banned users" ); CVAR_DEFINE_AUTO( sv_cheats, "0", FCVAR_SERVER, "allow cheats on server" ); CVAR_DEFINE_AUTO( sv_instancedbaseline, "1", 0, "allow to use instanced baselines to saves network overhead" ); -CVAR_DEFINE_AUTO( sv_contact, "", FCVAR_ARCHIVE|FCVAR_SERVER, "server techincal support contact address or web-page" ); +static CVAR_DEFINE_AUTO( sv_contact, "", FCVAR_ARCHIVE|FCVAR_SERVER, "server techincal support contact address or web-page" ); CVAR_DEFINE_AUTO( sv_minupdaterate, "25.0", FCVAR_ARCHIVE, "minimal value for 'cl_updaterate' window" ); CVAR_DEFINE_AUTO( sv_maxupdaterate, "60.0", FCVAR_ARCHIVE, "maximal value for 'cl_updaterate' window" ); CVAR_DEFINE_AUTO( sv_minrate, "5000", FCVAR_SERVER, "min bandwidth rate allowed on server, 0 == unlimited" ); CVAR_DEFINE_AUTO( sv_maxrate, "50000", FCVAR_SERVER, "max bandwidth rate allowed on server, 0 == unlimited" ); -CVAR_DEFINE_AUTO( sv_logrelay, "0", FCVAR_ARCHIVE, "allow log messages from remote machines to be logged on this server" ); +// TODO: CVAR_DEFINE_AUTO( sv_logrelay, "0", FCVAR_ARCHIVE, "allow log messages from remote machines to be logged on this server" ); CVAR_DEFINE_AUTO( sv_newunit, "0", 0, "clear level-saves from previous SP game chapter to help keep .sav file size as minimum" ); CVAR_DEFINE_AUTO( sv_clienttrace, "1", FCVAR_SERVER, "0 = big box(Quake), 0.5 = halfsize, 1 = normal (100%), otherwise it's a scaling factor" ); -CVAR_DEFINE_AUTO( sv_timeout, "65", 0, "after this many seconds without a message from a client, the client is dropped" ); +static CVAR_DEFINE_AUTO( sv_timeout, "65", 0, "after this many seconds without a message from a client, the client is dropped" ); +static CVAR_DEFINE_AUTO( sv_connect_timeout, "60", 0, "after this many seconds without a message from a client, the client is dropped" ); CVAR_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" ); CVAR_DEFINE_AUTO( sv_password, "", FCVAR_SERVER|FCVAR_PROTECTED, "server password for entry into multiplayer games" ); -CVAR_DEFINE_AUTO( sv_proxies, "1", FCVAR_SERVER, "maximum count of allowed proxies for HLTV spectating" ); +// TODO: CVAR_DEFINE_AUTO( sv_proxies, "1", FCVAR_SERVER, "maximum count of allowed proxies for HLTV spectating" ); CVAR_DEFINE_AUTO( sv_send_logos, "1", 0, "send custom decal logo to other players so they can view his too" ); CVAR_DEFINE_AUTO( sv_send_resources, "1", 0, "allow to download missed resources for players" ); -CVAR_DEFINE_AUTO( sv_logbans, "0", 0, "print into the server log info about player bans" ); -CVAR_DEFINE_AUTO( sv_allow_upload, "1", FCVAR_SERVER, "allow uploading custom resources on a server" ); -CVAR_DEFINE_AUTO( sv_allow_download, "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); +// TODO: CVAR_DEFINE_AUTO( sv_logbans, "0", 0, "print into the server log info about player bans" ); +CVAR_DEFINE( sv_allow_upload, "sv_allowupload", "1", FCVAR_SERVER, "allow uploading custom resources on a server" ); +CVAR_DEFINE( sv_allow_download, "sv_allowdownload", "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); +static CVAR_DEFINE_AUTO( sv_allow_dlfile, "1", 0, "compatibility cvar, does nothing" ); CVAR_DEFINE_AUTO( sv_uploadmax, "0.5", FCVAR_SERVER, "max size to upload custom resources (500 kB as default)" ); CVAR_DEFINE_AUTO( sv_downloadurl, "", FCVAR_PROTECTED, "location from which clients can download missing files" ); CVAR_DEFINE( sv_consistency, "mp_consistency", "1", FCVAR_SERVER, "enbale consistency check in multiplayer" ); @@ -58,40 +60,45 @@ CVAR_DEFINE_AUTO( mp_logfile, "1", 0, "log multiplayer frags to console" ); CVAR_DEFINE_AUTO( sv_log_singleplayer, "0", FCVAR_ARCHIVE, "allows logging in singleplayer games" ); CVAR_DEFINE_AUTO( sv_log_onefile, "0", FCVAR_ARCHIVE, "logs server information to only one file" ); CVAR_DEFINE_AUTO( sv_trace_messages, "0", FCVAR_LATCH, "enable server usermessages tracing (good for developers)" ); +CVAR_DEFINE_AUTO( sv_master_response_timeout, "4", FCVAR_ARCHIVE, "master server heartbeat response timeout in seconds" ); +CVAR_DEFINE_AUTO( sv_autosave, "1", FCVAR_ARCHIVE|FCVAR_SERVER|FCVAR_PRIVILEGED, "enable autosaving" ); +CVAR_DEFINE_AUTO( sv_speedhack_kick, "10", FCVAR_ARCHIVE, "number of speedhack warns before automatic kick (0 to disable)" ); // game-related cvars -CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); -CVAR_DEFINE_AUTO( motdfile, "motd.txt", 0, "name of 'message of the day' file" ); -CVAR_DEFINE_AUTO( logsdir, "logs", 0, "place to store multiplayer logs" ); -CVAR_DEFINE_AUTO( bannedcfgfile, "banned.cfg", 0, "name of list of banned users" ); -CVAR_DEFINE_AUTO( deathmatch, "0", 0, "deathmatch mode in multiplayer game" ); -CVAR_DEFINE_AUTO( coop, "0", 0, "cooperative mode in multiplayer game" ); -CVAR_DEFINE_AUTO( teamplay, "0", 0, "team mode in multiplayer game" ); +static CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); +static CVAR_DEFINE_AUTO( motdfile, "motd.txt", 0, "name of 'message of the day' file" ); +static CVAR_DEFINE_AUTO( logsdir, "logs", 0, "place to store multiplayer logs" ); +static CVAR_DEFINE_AUTO( bannedcfgfile, "banned.cfg", 0, "name of list of banned users" ); +CVAR_DEFINE_AUTO( deathmatch, "0", FCVAR_SERVER, "deathmatch mode in multiplayer game" ); +CVAR_DEFINE_AUTO( coop, "0", FCVAR_SERVER, "cooperative mode in multiplayer game" ); +static CVAR_DEFINE_AUTO( teamplay, "0", 0, "team mode in multiplayer game (Quake only)" ); CVAR_DEFINE_AUTO( skill, "1", 0, "skill level in singleplayer game" ); -CVAR_DEFINE_AUTO( temp1, "0", 0, "temporary cvar that used by some mods" ); -CVAR_DEFINE_AUTO( listipcfgfile, "listip.cfg", 0, "name of listip.cfg file" ); -CVAR_DEFINE_AUTO( mapchangecfgfile, "", 0, "name of map change configuration file" ); +static CVAR_DEFINE_AUTO( temp1, "0", 0, "temporary cvar that used by some mods" ); +static CVAR_DEFINE_AUTO( listipcfgfile, "listip.cfg", 0, "name of listip.cfg file" ); +static CVAR_DEFINE_AUTO( mapchangecfgfile, "", 0, "name of map change configuration file" ); +static CVAR_DEFINE_AUTO( disconcfgfile, "", 0, "name of disconnect configuration file" ); +static CVAR_DEFINE_AUTO( _sv_override_scientist_mdl, "", 0, "override default scientist model name (specially for HL25 Uplink maps)" ); // physic-related variables -CVAR_DEFINE_AUTO( sv_gravity, "800", FCVAR_MOVEVARS, "world gravity value" ); -CVAR_DEFINE_AUTO( sv_stopspeed, "100", FCVAR_MOVEVARS, "how fast you come to a complete stop" ); -CVAR_DEFINE_AUTO( sv_maxspeed, "320", FCVAR_MOVEVARS, "maximum speed a player can accelerate to when on ground" ); -CVAR_DEFINE_AUTO( sv_spectatormaxspeed, "500", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "maximum speed a spectator can accelerate in air" ); -CVAR_DEFINE_AUTO( sv_accelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed" ); -CVAR_DEFINE_AUTO( sv_airaccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the air" ); -CVAR_DEFINE_AUTO( sv_wateraccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the water" ); -CVAR_DEFINE_AUTO( sv_friction, "4", FCVAR_MOVEVARS, "how fast you slow down" ); -CVAR_DEFINE( sv_edgefriction, "edgefriction", "2", FCVAR_MOVEVARS, "how much you slow down when nearing a ledge you might fall off" ); -CVAR_DEFINE_AUTO( sv_waterfriction, "1", FCVAR_MOVEVARS, "how fast you slow down in water" ); -CVAR_DEFINE_AUTO( sv_bounce, "1", FCVAR_MOVEVARS, "bounce factor for entities with MOVETYPE_BOUNCE" ); -CVAR_DEFINE_AUTO( sv_stepsize, "18", FCVAR_MOVEVARS, "how high you and NPS's can step up" ); +CVAR_DEFINE_AUTO( sv_gravity, "800", FCVAR_SERVER|FCVAR_MOVEVARS, "world gravity value" ); +CVAR_DEFINE_AUTO( sv_stopspeed, "100", FCVAR_SERVER|FCVAR_MOVEVARS, "how fast you come to a complete stop" ); +static CVAR_DEFINE_AUTO( sv_maxspeed, "320", FCVAR_SERVER|FCVAR_MOVEVARS, "maximum speed a player can accelerate to when on ground" ); +static CVAR_DEFINE_AUTO( sv_spectatormaxspeed, "500", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "maximum speed a spectator can accelerate in air" ); +static CVAR_DEFINE_AUTO( sv_accelerate, "10", FCVAR_SERVER|FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed" ); +static CVAR_DEFINE_AUTO( sv_airaccelerate, "10", FCVAR_SERVER|FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the air" ); +static CVAR_DEFINE_AUTO( sv_wateraccelerate, "10", FCVAR_SERVER|FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the water" ); +CVAR_DEFINE_AUTO( sv_friction, "4", FCVAR_SERVER|FCVAR_MOVEVARS, "how fast you slow down" ); +static CVAR_DEFINE( sv_edgefriction, "edgefriction", "2", FCVAR_SERVER|FCVAR_MOVEVARS, "how much you slow down when nearing a ledge you might fall off" ); +static CVAR_DEFINE_AUTO( sv_waterfriction, "1", FCVAR_SERVER|FCVAR_MOVEVARS, "how fast you slow down in water" ); +static CVAR_DEFINE_AUTO( sv_bounce, "1", FCVAR_SERVER|FCVAR_MOVEVARS, "bounce factor for entities with MOVETYPE_BOUNCE" ); +CVAR_DEFINE_AUTO( sv_stepsize, "18", FCVAR_SERVER|FCVAR_MOVEVARS, "how high you and NPC's can step up" ); CVAR_DEFINE_AUTO( sv_maxvelocity, "2000", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "max velocity for all things in the world" ); -CVAR_DEFINE_AUTO( sv_zmax, "4096", FCVAR_MOVEVARS|FCVAR_SPONLY, "maximum viewable distance" ); +static CVAR_DEFINE_AUTO( sv_zmax, "4096", FCVAR_MOVEVARS|FCVAR_SPONLY, "maximum viewable distance" ); CVAR_DEFINE_AUTO( sv_wateramp, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world waveheight factor" ); -CVAR_DEFINE( sv_footsteps, "mp_footsteps", "1", FCVAR_MOVEVARS, "world gravity value" ); +static CVAR_DEFINE( sv_footsteps, "mp_footsteps", "1", FCVAR_SERVER|FCVAR_MOVEVARS, "play foot steps for players" ); CVAR_DEFINE_AUTO( sv_skyname, "desert", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skybox name (can be dynamically changed in-game)" ); -CVAR_DEFINE_AUTO( sv_rollangle, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED|FCVAR_ARCHIVE, "how much to tilt the view when strafing" ); -CVAR_DEFINE_AUTO( sv_rollspeed, "200", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "how much strafing is necessary to tilt the view" ); +static CVAR_DEFINE_AUTO( sv_rollangle, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED|FCVAR_ARCHIVE, "how much to tilt the view when strafing" ); +static CVAR_DEFINE_AUTO( sv_rollspeed, "200", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "how much strafing is necessary to tilt the view" ); CVAR_DEFINE_AUTO( sv_skycolor_r, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight red component value" ); CVAR_DEFINE_AUTO( sv_skycolor_g, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight green component value" ); CVAR_DEFINE_AUTO( sv_skycolor_b, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight blue component value" ); @@ -100,41 +107,49 @@ CVAR_DEFINE_AUTO( sv_skyvec_y, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight dir CVAR_DEFINE_AUTO( sv_skyvec_z, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by z-axis" ); CVAR_DEFINE_AUTO( sv_wateralpha, "1", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world surfaces water transparency factor. 1.0 - solid, 0.0 - fully transparent" ); CVAR_DEFINE_AUTO( sv_background_freeze, "1", FCVAR_ARCHIVE, "freeze player movement on background maps (e.g. to prevent falling)" ); -CVAR_DEFINE_AUTO( showtriggers, "0", FCVAR_LATCH, "debug cvar shows triggers" ); -CVAR_DEFINE_AUTO( sv_airmove, "1", FCVAR_SERVER, "obsolete, compatibility issues" ); -CVAR_DEFINE_AUTO( sv_version, "", FCVAR_READ_ONLY, "engine version string" ); -CVAR_DEFINE_AUTO( hostname, "", FCVAR_SERVER|FCVAR_PRINTABLEONLY, "name of current host" ); -CVAR_DEFINE_AUTO( sv_fps, "0.0", FCVAR_SERVER, "server framerate" ); +static CVAR_DEFINE_AUTO( showtriggers, "0", FCVAR_LATCH|FCVAR_TEMPORARY, "debug cvar shows triggers" ); +static CVAR_DEFINE_AUTO( sv_airmove, "1", FCVAR_SERVER, "obsolete, compatibility issues" ); +static CVAR_DEFINE_AUTO( sv_version, "", FCVAR_READ_ONLY, "engine version string" ); +CVAR_DEFINE_AUTO( hostname, "", FCVAR_PRINTABLEONLY, "name of current host" ); +static CVAR_DEFINE_AUTO( sv_fps, "0.0", 0, "server framerate" ); // gore-related cvars -CVAR_DEFINE_AUTO( violence_hblood, "1", 0, "draw human blood" ); -CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" ); -CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" ); -CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" ); +static CVAR_DEFINE_AUTO( violence_hblood, "1", 0, "draw human blood" ); +static CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" ); +static CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" ); +static CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" ); // voice chat CVAR_DEFINE_AUTO( sv_voiceenable, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "enable voice support" ); -CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE|FCVAR_SERVER, "voice chat quality level, from 0 to 5, higher is better" ); +CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE, "voice chat quality level, from 0 to 5, higher is better" ); -convar_t *sv_novis; // disable server culling entities by vis -convar_t *sv_pausable; -convar_t *timeout; // seconds without any message -convar_t *sv_lighting_modulate; -convar_t *sv_maxclients; -convar_t *sv_check_errors; -convar_t *public_server; // should heartbeats be sent -convar_t *sv_reconnect_limit; // minimum seconds between connect messages -convar_t *sv_validate_changelevel; -convar_t *sv_sendvelocity; -convar_t *sv_hostmap; +// enttools +CVAR_DEFINE_AUTO( sv_enttools_enable, "0", FCVAR_ARCHIVE|FCVAR_PROTECTED, "enable powerful and dangerous entity tools" ); +CVAR_DEFINE_AUTO( sv_enttools_maxfire, "5", FCVAR_ARCHIVE|FCVAR_PROTECTED, "limit ent_fire actions count to prevent flooding" ); -convar_t *sv_allow_noinputdevices; -convar_t *sv_allow_touch; -convar_t *sv_allow_mouse; -convar_t *sv_allow_joystick; -convar_t *sv_allow_vr; +CVAR_DEFINE( public_server, "public", "0", 0, "change server type from private to public" ); -void Master_Shutdown( void ); +CVAR_DEFINE_AUTO( sv_novis, "0", 0, "force to ignore server visibility" ); // disable server culling entities by vis +CVAR_DEFINE( sv_pausable, "pausable", "1", 0, "allow players to pause or not" ); +CVAR_DEFINE( sv_maxclients, "maxplayers", "1", FCVAR_LATCH, "server max capacity" ); +CVAR_DEFINE_AUTO( sv_check_errors, "0", FCVAR_ARCHIVE, "check edicts for errors" ); +CVAR_DEFINE_AUTO( sv_validate_changelevel, "0", 0, "test change level for level-designer errors" ); +CVAR_DEFINE( sv_hostmap, "hostmap", "", 0, "keep name of last entered map" ); + +static CVAR_DEFINE_AUTO( sv_allow_joystick, "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); +static CVAR_DEFINE_AUTO( sv_allow_mouse, "1", FCVAR_ARCHIVE, "allow connect with mouse" ); +static CVAR_DEFINE_AUTO( sv_allow_touch, "1", FCVAR_ARCHIVE, "allow connect with touch controls" ); +static CVAR_DEFINE_AUTO( sv_allow_vr, "1", FCVAR_ARCHIVE, "allow connect from vr version" ); +static CVAR_DEFINE_AUTO( sv_allow_noinputdevices, "1", FCVAR_ARCHIVE, "allow connect from old versions without useragent" ); + +CVAR_DEFINE_AUTO( sv_userinfo_enable_penalty, "1", FCVAR_ARCHIVE, "enable penalty time for too fast userinfo updates(name, model, etc)" ); +CVAR_DEFINE_AUTO( sv_userinfo_penalty_time, "0.3", FCVAR_ARCHIVE, "initial penalty time" ); +CVAR_DEFINE_AUTO( sv_userinfo_penalty_multiplier, "2", FCVAR_ARCHIVE, "penalty time multiplier" ); +CVAR_DEFINE_AUTO( sv_userinfo_penalty_attempts, "4", FCVAR_ARCHIVE, "if max attempts count was exceeded, penalty time will be increased" ); +CVAR_DEFINE_AUTO( sv_fullupdate_penalty_time, "1", FCVAR_ARCHIVE, "allow fullupdate command only once in this timewindow (set 0 to disable)" ); +CVAR_DEFINE_AUTO( sv_log_outofband, "0", FCVAR_ARCHIVE, "log out of band messages, can be useful for server admins and for engine debugging" ); +CVAR_DEFINE_AUTO( sv_allow_testpacket, "1", FCVAR_ARCHIVE, "allow generating and sending a big blob of data to test maximum packet size" ); +CVAR_DEFINE_AUTO( sv_expose_player_list, "1", FCVAR_ARCHIVE, "expose player list through packets that don't require connection" ); //============================================================================ /* @@ -144,7 +159,7 @@ SV_HasActivePlayers returns true if server have spawned players ================ */ -qboolean SV_HasActivePlayers( void ) +static qboolean SV_HasActivePlayers( void ) { int i; @@ -159,41 +174,6 @@ qboolean SV_HasActivePlayers( void ) return false; } -/* -================ -SV_GetConnectedClientsCount - -returns connected clients count (and optionally bots count) -================ -*/ -int SV_GetConnectedClientsCount(int *bots) -{ - int index; - int clients; - - clients = 0; - if( svs.clients ) - { - if( bots ) - *bots = 0; - - for( index = 0; index < svs.maxclients; index++ ) - { - if( svs.clients[index].state >= cs_connected ) - { - if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) - { - if( bots ) - (*bots)++; - } - else - clients++; - } - } - } - return clients; -} - /* =================== SV_UpdateMovevars @@ -210,20 +190,15 @@ void SV_UpdateMovevars( qboolean initialize ) if( !initialize && !host.movevars_changed ) return; - // check range - if( sv_zmax.value < 256.0f ) Cvar_SetValue( "sv_zmax", 256.0f ); - - // clamp it right - if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) - { - if( sv_zmax.value > 131070.0f ) - Cvar_SetValue( "sv_zmax", 131070.0f ); - } - else - { - if( sv_zmax.value > 32767.0f ) - Cvar_SetValue( "sv_zmax", 32767.0f ); - } + // NOTE: Natural Selection mod on ns_machina map that uses model as sky + // it sets the value to 4000000 that even exceeds the coord limit, but + // it's fine until the value fits in "zmax" delta field + // However, some stupid mappers set an insane value like 999999999 which + // overflows delta. In this case, just clamp it to something bigger + if( sv_zmax.value < 256.0f ) + Cvar_DirectSet( &sv_zmax, "256" ); + else if( sv_zmax.value > 16777216.0f ) // 2^24 + Cvar_DirectSet( &sv_zmax, "16777216" ); svgame.movevars.gravity = sv_gravity.value; svgame.movevars.stopspeed = sv_stopspeed.value; @@ -257,7 +232,7 @@ void SV_UpdateMovevars( qboolean initialize ) if( initialize ) return; // too early if( MSG_WriteDeltaMovevars( &sv.reliable_datagram, &svgame.oldmovevars, &svgame.movevars )) - memcpy( &svgame.oldmovevars, &svgame.movevars, sizeof( movevars_t )); // oldstate changed + svgame.oldmovevars = svgame.movevars; // oldstate changed host.movevars_changed = false; } @@ -267,7 +242,7 @@ void SV_UpdateMovevars( qboolean initialize ) SV_CheckCmdTimes ================= */ -void SV_CheckCmdTimes( void ) +static void SV_CheckCmdTimes( void ) { sv_client_t *cl; static double lastreset = 0; @@ -279,8 +254,8 @@ void SV_CheckCmdTimes( void ) if( sv_fps.value < MIN_FPS ) Cvar_SetValue( "sv_fps", MIN_FPS ); - if( sv_fps.value > MAX_FPS ) - Cvar_SetValue( "sv_fps", MAX_FPS ); + if( sv_fps.value > MAX_FPS_HARD ) + Cvar_SetValue( "sv_fps", MAX_FPS_HARD ); } if( Host_IsLocalGame( )) @@ -303,12 +278,12 @@ void SV_CheckCmdTimes( void ) diff = cl->connecttime + cl->cmdtime - host.realtime; - if( diff > net_clockwindow->value ) + if( diff > net_clockwindow.value ) { - cl->ignorecmdtime = net_clockwindow->value + host.realtime; + cl->ignorecmdtime = net_clockwindow.value + host.realtime; cl->cmdtime = host.realtime - cl->connecttime; } - else if( diff < -net_clockwindow->value ) + else if( diff < -net_clockwindow.value ) { cl->cmdtime = host.realtime - cl->connecttime; } @@ -322,7 +297,7 @@ SV_ProcessFile process incoming file (customization) ================= */ -void SV_ProcessFile( sv_client_t *cl, const char *filename ) +static void SV_ProcessFile( sv_client_t *cl, const char *filename ) { customization_t *pList; resource_t *resource; @@ -349,7 +324,7 @@ void SV_ProcessFile( sv_client_t *cl, const char *filename ) if( resource == &cl->resourcesneeded ) { - Con_Printf( "SV_ProcessFile: Unrequested decal\n" ); + Con_Printf( "%s: Unrequested decal\n", __func__ ); return; } @@ -359,7 +334,7 @@ void SV_ProcessFile( sv_client_t *cl, const char *filename ) return; } - HPAK_AddLump( true, CUSTOM_RES_PATH, resource, cl->netchan.tempbuffer, NULL ); + HPAK_AddLump( true, hpk_custom_file.string, resource, cl->netchan.tempbuffer, NULL ); ClearBits( resource->ucFlags, RES_WASMISSING ); SV_MoveToOnHandList( cl, resource ); @@ -393,7 +368,7 @@ void SV_ProcessFile( sv_client_t *cl, const char *filename ) SV_ReadPackets ================= */ -void SV_ReadPackets( void ) +static void SV_ReadPackets( void ) { sv_client_t *cl; int i, qport; @@ -406,23 +381,7 @@ void SV_ReadPackets( void ) // check for connectionless packet (0xffffffff) first if( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) { - if( !svs.initialized ) - { - char *args; - const char *c; - - MSG_Clear( &net_message ); - MSG_ReadLong( &net_message );// skip the -1 marker - - args = MSG_ReadStringLine( &net_message ); - Cmd_TokenizeString( args ); - c = Cmd_Argv( 0 ); - - if( !Q_strcmp( c, "rcon" )) - SV_RemoteCommand( net_from, &net_message ); - } - else SV_ConnectionlessPacket( net_from, &net_message ); - + SV_ConnectionlessPacket( net_from, &net_message ); continue; } @@ -452,7 +411,7 @@ void SV_ReadPackets( void ) if( Netchan_Process( &cl->netchan, &net_message )) { - if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) + if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned )) SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame // this is a valid, sequenced packet, so process it @@ -471,7 +430,7 @@ void SV_ReadPackets( void ) { MSG_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); - if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) + if(( svs.maxclients == 1 && !host_limitlocal.value ) || ( cl->state != cs_spawned )) SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame // this is a valid, sequenced packet, so process it @@ -498,11 +457,23 @@ void SV_ReadPackets( void ) sv.current_client = NULL; } +static void SV_DropTimedOutClient( sv_client_t *cl, qboolean ban ) +{ + SV_BroadcastPrintf( NULL, "%s timed out\n", cl->name ); + SV_DropClient( cl, false ); + cl->state = cs_free; // don't bother with zombie state + + if( ban ) + { + Cbuf_AddTextf( "addip 30 %s\n", NET_BaseAdrToString( cl->netchan.remote_address )); + } +} + /* ================== SV_CheckTimeouts -If a packet has not been received from a client for timeout->value +If a packet has not been received from a client for sv_timeout.value seconds, drop the conneciton. Server frames are used instead of realtime to avoid dropping the local client while debugging. @@ -511,16 +482,16 @@ for a few seconds to make sure any final reliable message gets resent if necessary ================== */ -void SV_CheckTimeouts( void ) +static void SV_CheckTimeouts( void ) { - sv_client_t *cl; - double droppoint; - int i, numclients = 0; + int i, numclients = 0; + const double spawned_droppoint = host.realtime - sv_timeout.value; + const double connected_droppoint = host.realtime - sv_connect_timeout.value; - droppoint = host.realtime - timeout->value; - - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + for( i = 0; i < svs.maxclients; i++ ) { + sv_client_t *cl = &svs.clients[i]; + if( cl->state >= cs_connected ) { if( cl->edict && !FBitSet( cl->edict->v.flags, FL_SPECTATOR|FL_FAKECLIENT )) @@ -531,21 +502,29 @@ void SV_CheckTimeouts( void ) if( FBitSet( cl->flags, FCL_FAKECLIENT )) continue; - // FIXME: get rid of the zombie state - if( cl->state == cs_zombie ) + switch( cl->state ) { + case cs_zombie: + // FIXME: get rid of the zombie state cl->state = cs_free; // can now be reused - continue; - } - - if(( cl->state == cs_connected || cl->state == cs_spawned ) && cl->netchan.last_received < droppoint ) - { + break; + case cs_connected: + case cs_spawning: if( !NET_IsLocalAddress( cl->netchan.remote_address )) { - SV_BroadcastPrintf( NULL, "%s timed out\n", cl->name ); - SV_DropClient( cl, false ); - cl->state = cs_free; // don't bother with zombie state + if( cl->connection_started < connected_droppoint ) + SV_DropTimedOutClient( cl, true ); } + break; + case cs_spawned: + if( !NET_IsLocalAddress( cl->netchan.remote_address )) + { + if( cl->netchan.last_received < spawned_droppoint ) + SV_DropTimedOutClient( cl, false ); + } + break; + default: + break; } } @@ -564,7 +543,7 @@ This has to be done before the world logic, because player processing happens outside RunWorldFrame ================ */ -void SV_PrepWorldFrame( void ) +static void SV_PrepWorldFrame( void ) { edict_t *ent; int i; @@ -586,8 +565,11 @@ void SV_PrepWorldFrame( void ) SV_IsSimulating ================= */ -qboolean SV_IsSimulating( void ) +static qboolean SV_IsSimulating( void ) { + if( Host_IsDedicated( )) + return true; // always active for dedicated servers + if( sv.background && SV_Active() && CL_Active()) { if( CL_IsInConsole( )) @@ -595,9 +577,6 @@ qboolean SV_IsSimulating( void ) return true; // force simulating for background map } - if( Host_IsDedicated() ) - return true; // always active for dedicated servers - if( !SV_HasActivePlayers( )) return false; @@ -616,7 +595,7 @@ qboolean SV_IsSimulating( void ) SV_RunGameFrame ================= */ -qboolean SV_RunGameFrame( void ) +static qboolean SV_RunGameFrame( void ) { sv.simulating = SV_IsSimulating(); @@ -649,6 +628,43 @@ qboolean SV_RunGameFrame( void ) } } +static void SV_UpdateStatusLine( void ) +{ +#if XASH_PLATFORM_HAVE_STATUS + static double lasttime; + string status; + + if( !Host_IsDedicated( )) + return; + + // update only every 1/2 seconds + if(( host.realtime - lasttime ) < 0.5f ) + return; + + if( sv.state == ss_active ) + { + int clients, bots; + SV_GetPlayerCount( &clients, &bots ); + + Q_snprintf( status, sizeof( status ), + "%.1f fps %2i/%2i on %16s", + 1.f / sv.frametime, + clients, svs.maxclients, host.game.levelName ); + } + else if( sv.state == ss_loading ) + Q_strncpy( status, "Loading level", sizeof( status )); + else if( !svs.initialized ) + Q_strncpy( status, "Server is not loaded", sizeof( status )); + // FIXME: unreachable branch + // else if( host.status == HOST_SHUTDOWN ) + // Q_strncpy( status, "Shutting down...", sizeof( status )); + else Q_strncpy( status, "Unknown status", sizeof( status )); + + Platform_SetStatus( status ); + lasttime = host.realtime; +#endif // XASH_PLATFORM_HAVE_STATUS +} + /* ================== Host_ServerFrame @@ -657,6 +673,9 @@ Host_ServerFrame */ void Host_ServerFrame( void ) { + // update dedicated server status line in console + SV_UpdateStatusLine (); + // if server is not active, do nothing if( !svs.initialized ) return; @@ -691,76 +710,11 @@ void Host_ServerFrame( void ) // clear edict flags for next frame SV_PrepWorldFrame (); - // update dedicated server status line in console - Platform_UpdateStatusLine (); - // send a heartbeat to the master if needed - Master_Heartbeat (); -} - -/* -================== -Host_SetServerState -================== -*/ -void Host_SetServerState( int state ) -{ - Cvar_FullSet( "host_serverstate", va( "%i", state ), FCVAR_READ_ONLY ); - sv.state = state; + NET_MasterHeartbeat (); } //============================================================================ - -/* -================= -Master_Add -================= -*/ -void Master_Add( void ) -{ - NET_Config( true, false ); // allow remote - if( NET_SendToMasters( NS_SERVER, 2, "q\xFF" )) - svs.last_heartbeat = MAX_HEARTBEAT; -} - -/* -================ -Master_Heartbeat - -Send a message to the master every few minutes to -let it know we are alive, and log information -================ -*/ -void Master_Heartbeat( void ) -{ - if( !public_server->value || svs.maxclients == 1 ) - return; // only public servers send heartbeats - - // check for time wraparound - if( svs.last_heartbeat > host.realtime ) - svs.last_heartbeat = host.realtime; - - if(( host.realtime - svs.last_heartbeat ) < HEARTBEAT_SECONDS ) - return; // not time to send yet - - svs.last_heartbeat = host.realtime; - - Master_Add(); -} - -/* -================= -Master_Shutdown - -Informs all masters that this server is going down -================= -*/ -void Master_Shutdown( void ) -{ - NET_Config( true, false ); // allow remote - while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); -} - /* ================= SV_AddToMaster @@ -771,25 +725,39 @@ Master will validate challenge and this server to public list */ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { - uint challenge; - char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header - int clients = 0, bots = 0; - int len = sizeof( s ); + uint challenge, challenge2, heartbeat_challenge; + char s[MAX_INFO_STRING] = S2M_INFO; // skip 2 bytes of header + int clients, bots; + double last_heartbeat; + const int len = sizeof( s ); - if( !NET_IsMasterAdr( from )) + if( !NET_GetMaster( from, &heartbeat_challenge, &last_heartbeat )) { - Con_Printf( S_WARN "unexpected master server info query packet from %s\n", NET_AdrToString( from )); + Con_Reportf( S_WARN "unexpected master server info query packet from %s\n", NET_AdrToString( from )); return; } - clients = SV_GetConnectedClientsCount( &bots ); - challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); + challenge = MSG_ReadDword( msg ); + challenge2 = MSG_ReadDword( msg ); - Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version - Info_SetValueForKey( s, "challenge", va( "%u", challenge ), len ); // challenge number - Info_SetValueForKey( s, "players", va( "%d", clients ), len ); // current player number, without bots - Info_SetValueForKey( s, "max", va( "%d", svs.maxclients ), len ); // max_players - Info_SetValueForKey( s, "bots", va( "%d", bots ), len ); // bot count + if( challenge2 != heartbeat_challenge ) + { + Con_Reportf( S_WARN "unexpected master server info query packet (wrong challenge!)\n" ); + return; + } + + if( last_heartbeat + sv_master_response_timeout.value < host.realtime ) + { + Con_Printf( S_WARN "unexpected master server info query packet (too late? try increasing sv_master_response_timeout value)\n"); + return; + } + + SV_GetPlayerCount( &clients, &bots ); + Info_SetValueForKeyf( s, "protocol", len, "%d", PROTOCOL_VERSION ); // protocol version + Info_SetValueForKeyf( s, "challenge", len, "%u", challenge ); // challenge number + Info_SetValueForKeyf( s, "players", len, "%d", clients ); // current player number, without bots + Info_SetValueForKeyf( s, "max", len, "%d", svs.maxclients ); // max_players + Info_SetValueForKeyf( s, "bots", len, "%d", bots ); // bot count Info_SetValueForKey( s, "gamedir", GI->gamefolder, len ); // gamedir Info_SetValueForKey( s, "map", sv.name, len ); // current map Info_SetValueForKey( s, "type", (Host_IsDedicated()) ? "d" : "l", len ); // dedicated or local @@ -797,10 +765,10 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) Info_SetValueForKey( s, "os", "w", len ); // Windows Info_SetValueForKey( s, "secure", "0", len ); // server anti-cheat Info_SetValueForKey( s, "lan", "0", len ); // LAN servers doesn't send info to master - Info_SetValueForKey( s, "version", va( "%s", XASH_VERSION ), len ); // server region. 255 -- all regions + Info_SetValueForKey( s, "version", XASH_VERSION, len ); // server region. 255 -- all regions Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? - Info_SetValueForKey( s, "nat", sv_nat.string, sizeof(s) ); // Server running under NAT, use reverse connection + Info_SetValueForKey( s, "nat", sv_nat.string, len ); // Server running under NAT, use reverse connection NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); } @@ -816,8 +784,33 @@ qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ) { const char *input_devices_str = Info_ValueForKey( useragent, "d" ); const char *id = Info_ValueForKey( useragent, "uuid" ); + size_t len, i; - if( !sv_allow_noinputdevices->value && ( !input_devices_str || !input_devices_str[0] ) ) + len = Q_strlen( id ); + if( len != 32 ) + { + SV_RejectConnection( from, "invalid authentication certificate\n" ); + return false; + } + + for( i = 0; i < len; i++ ) + { + char c = id[i]; + + if( !isdigit( id[i] ) && !( c >= 'a' && c <= 'f' )) + { + SV_RejectConnection( from, "invalid authentication certificate\n" ); + return false; + } + } + + if( SV_CheckID( id )) + { + SV_RejectConnection( from, "You are banned!\n" ); + return false; + } + + if( !sv_allow_noinputdevices.value && ( !input_devices_str || !input_devices_str[0] ) ) { SV_RejectConnection( from, "This server does not allow\nconnect without input devices list.\nPlease update your engine.\n" ); return false; @@ -827,39 +820,28 @@ qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ) { int input_devices = Q_atoi( input_devices_str ); - if( !sv_allow_touch->value && ( input_devices & INPUT_DEVICE_TOUCH ) ) + if( !sv_allow_touch.value && ( input_devices & INPUT_DEVICE_TOUCH ) ) { SV_RejectConnection( from, "This server does not allow touch\nDisable it (touch_enable 0)\nto play on this server\n" ); return false; } - if( !sv_allow_mouse->value && ( input_devices & INPUT_DEVICE_MOUSE) ) + if( !sv_allow_mouse.value && ( input_devices & INPUT_DEVICE_MOUSE) ) { SV_RejectConnection( from, "This server does not allow mouse\nDisable it(m_ignore 1)\nto play on this server\n" ); return false; } - if( !sv_allow_joystick->value && ( input_devices & INPUT_DEVICE_JOYSTICK) ) + if( !sv_allow_joystick.value && ( input_devices & INPUT_DEVICE_JOYSTICK) ) { SV_RejectConnection( from, "This server does not allow joystick\nDisable it(joy_enable 0)\nto play on this server\n" ); return false; } - if( !sv_allow_vr->value && ( input_devices & INPUT_DEVICE_VR) ) + if( !sv_allow_vr.value && ( input_devices & INPUT_DEVICE_VR) ) { SV_RejectConnection( from, "This server does not allow VR\n" ); return false; } } - if( id ) - { - qboolean banned = SV_CheckID( id ); - - if( banned ) - { - SV_RejectConnection( from, "You are banned!\n" ); - return false; - } - } - return true; } @@ -878,7 +860,7 @@ void SV_Init( void ) SV_InitHostCommands(); - Cvar_Get( "protocol", va( "%i", PROTOCOL_VERSION ), FCVAR_READ_ONLY, "displays server protocol version" ); + Cvar_Getf( "protocol", FCVAR_READ_ONLY, "displays server protocol version", "%i", PROTOCOL_VERSION ); Cvar_Get( "suitvolume", "0.25", FCVAR_ARCHIVE, "HEV suit volume" ); Cvar_Get( "sv_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); Cvar_Get( "gamedir", GI->gamefolder, FCVAR_READ_ONLY, "game folder" ); @@ -908,6 +890,7 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_fps ); Cvar_RegisterVariable( &showtriggers ); Cvar_RegisterVariable( &sv_aim ); + Cvar_RegisterVariable( &sv_allow_autoaim ); Cvar_RegisterVariable( &deathmatch ); Cvar_RegisterVariable( &coop ); Cvar_RegisterVariable( &teamplay ); @@ -915,12 +898,14 @@ void SV_Init( void ) Cvar_RegisterVariable( &temp1 ); Cvar_RegisterVariable( &rcon_password ); + Cvar_RegisterVariable( &rcon_enable ); Cvar_RegisterVariable( &sv_stepsize ); Cvar_RegisterVariable( &sv_newunit ); Cvar_RegisterVariable( &hostname ); - timeout = Cvar_Get( "timeout", "125", FCVAR_SERVER, "connection timeout" ); - sv_pausable = Cvar_Get( "pausable", "1", FCVAR_SERVER, "allow players to pause or not" ); - sv_validate_changelevel = Cvar_Get( "sv_validate_changelevel", "0", 0, "test change level for level-designer errors" ); + Cvar_RegisterVariable( &sv_timeout ); + Cvar_RegisterVariable( &sv_connect_timeout ); + Cvar_RegisterVariable( &sv_pausable ); + Cvar_RegisterVariable( &sv_validate_changelevel ); Cvar_RegisterVariable( &sv_clienttrace ); Cvar_RegisterVariable( &sv_bounce ); Cvar_RegisterVariable( &sv_spectatormaxspeed ); @@ -936,11 +921,9 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_friction ); Cvar_RegisterVariable( &sv_edgefriction ); Cvar_RegisterVariable( &sv_stopspeed ); - sv_maxclients = Cvar_Get( "maxplayers", "1", FCVAR_LATCH, "server max capacity" ); - sv_check_errors = Cvar_Get( "sv_check_errors", "0", FCVAR_ARCHIVE, "check edicts for errors" ); - public_server = Cvar_Get ("public", "0", 0, "change server type from private to public" ); - sv_lighting_modulate = Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); - sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", FCVAR_ARCHIVE, "max reconnect attempts" ); + Cvar_RegisterVariable( &sv_maxclients ); + Cvar_RegisterVariable( &sv_check_errors ); + Cvar_RegisterVariable( &public_server ); Cvar_RegisterVariable( &sv_failuretime ); Cvar_RegisterVariable( &sv_unlag ); Cvar_RegisterVariable( &sv_maxunlag ); @@ -948,15 +931,18 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_unlagsamples ); Cvar_RegisterVariable( &sv_allow_upload ); Cvar_RegisterVariable( &sv_allow_download ); + Cvar_RegisterVariable( &sv_allow_dlfile ); Cvar_RegisterVariable( &sv_send_logos ); Cvar_RegisterVariable( &sv_send_resources ); Cvar_RegisterVariable( &sv_uploadmax ); Cvar_RegisterVariable( &sv_version ); Cvar_RegisterVariable( &sv_instancedbaseline ); + Cvar_RegisterVariable( &sv_contact ); Cvar_RegisterVariable( &sv_consistency ); Cvar_RegisterVariable( &sv_downloadurl ); - sv_novis = Cvar_Get( "sv_novis", "0", 0, "force to ignore server visibility" ); - sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); + Cvar_RegisterVariable( &sv_novis ); + Cvar_RegisterVariable( &sv_hostmap ); + Cvar_DirectSet( &sv_hostmap, GI->startmap ); Cvar_RegisterVariable( &sv_password ); Cvar_RegisterVariable( &sv_lan ); Cvar_RegisterVariable( &sv_nat ); @@ -968,8 +954,10 @@ void SV_Init( void ) Cvar_RegisterVariable( &mp_logfile ); Cvar_RegisterVariable( &sv_log_onefile ); Cvar_RegisterVariable( &sv_log_singleplayer ); + Cvar_RegisterVariable( &sv_master_response_timeout ); Cvar_RegisterVariable( &sv_background_freeze ); + Cvar_RegisterVariable( &sv_autosave ); Cvar_RegisterVariable( &mapcyclefile ); Cvar_RegisterVariable( &motdfile ); @@ -977,24 +965,39 @@ void SV_Init( void ) Cvar_RegisterVariable( &bannedcfgfile ); Cvar_RegisterVariable( &listipcfgfile ); Cvar_RegisterVariable( &mapchangecfgfile ); + Cvar_RegisterVariable( &disconcfgfile ); + Cvar_RegisterVariable( &_sv_override_scientist_mdl ); Cvar_RegisterVariable( &sv_voiceenable ); Cvar_RegisterVariable( &sv_voicequality ); Cvar_RegisterVariable( &sv_trace_messages ); + Cvar_RegisterVariable( &sv_enttools_enable ); + Cvar_RegisterVariable( &sv_enttools_maxfire ); - sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); - sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" ); - sv_allow_touch = Cvar_Get( "sv_allow_touch", "1", FCVAR_ARCHIVE, "allow connect with touch controls" ); - sv_allow_vr = Cvar_Get( "sv_allow_vr", "1", FCVAR_ARCHIVE, "allow connect from vr version" ); - sv_allow_noinputdevices = Cvar_Get( "sv_allow_noinputdevices", "1", FCVAR_ARCHIVE, "allow connect from old versions without useragent" ); + Cvar_RegisterVariable( &sv_speedhack_kick ); + + Cvar_RegisterVariable( &sv_allow_joystick ); + Cvar_RegisterVariable( &sv_allow_mouse ); + Cvar_RegisterVariable( &sv_allow_touch ); + Cvar_RegisterVariable( &sv_allow_vr ); + Cvar_RegisterVariable( &sv_allow_noinputdevices ); + + Cvar_RegisterVariable( &sv_userinfo_enable_penalty ); + Cvar_RegisterVariable( &sv_userinfo_penalty_time ); + Cvar_RegisterVariable( &sv_userinfo_penalty_multiplier ); + Cvar_RegisterVariable( &sv_userinfo_penalty_attempts ); + Cvar_RegisterVariable( &sv_fullupdate_penalty_time ); + Cvar_RegisterVariable( &sv_log_outofband ); + Cvar_RegisterVariable( &sv_allow_testpacket ); + Cvar_RegisterVariable( &sv_expose_player_list ); // when we in developer-mode automatically turn cheats on if( host_developer.value ) Cvar_SetValue( "sv_cheats", 1.0f ); MSG_Init( &net_message, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); - Q_snprintf( versionString, sizeof( versionString ), "%s: %s-%s(%s-%s),%i,%i", - XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() ); + Q_snprintf( versionString, sizeof( versionString ), XASH_ENGINE_NAME ": " XASH_VERSION "-%s(%s-%s),%i,%i", + g_buildcommit, Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() ); Cvar_FullSet( "sv_version", versionString, FCVAR_READ_ONLY ); @@ -1060,7 +1063,7 @@ SV_FreeClients release server clients ================ */ -void SV_FreeClients( void ) +static void SV_FreeClients( void ) { if( svs.maxclients != 0 ) { @@ -1098,24 +1101,26 @@ void SV_Shutdown( const char *finalmsg ) if( CL_IsPlaybackDemo( )) CL_Drop(); - SV_UnloadProgs (); return; } + // don't forget to reset sv_background state + Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); + if( COM_CheckString( finalmsg )) Con_Printf( "%s", finalmsg ); // rcon will be disconnected - SV_EndRedirect(); + SV_EndRedirect( &host.rd ); if( svs.clients ) SV_FinalMessage( finalmsg, false ); - if( public_server->value && svs.maxclients != 1 ) - Master_Shutdown(); + if( public_server.value && svs.maxclients != 1 ) + NET_MasterShutdown(); NET_Config( false, false ); - SV_UnloadProgs (); + SV_DeactivateServer(); CL_Drop(); // free current level @@ -1124,6 +1129,9 @@ void SV_Shutdown( const char *finalmsg ) SV_FreeClients(); svs.maxclients = 0; + // release test packet blob + SV_FreeTestPacket(); + // release all models Mod_FreeAll(); diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c index 48753f21..dbc6436f 100644 --- a/engine/server/sv_move.c +++ b/engine/server/sv_move.c @@ -66,12 +66,12 @@ realcheck: start[2] = mins[2]; if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - start[2] += svgame.movevars.stepsize; + start[2] += sv_stepsize.value; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f; start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f; - stop[2] = start[2] - 2.0f * svgame.movevars.stepsize; + stop[2] = start[2] - 2.0f * sv_stepsize.value; if( iMode == WALKMOVE_WORLDONLY ) trace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent ); @@ -96,7 +96,7 @@ realcheck: if( trace.fraction != 1.0f && trace.endpos[2] > bottom ) bottom = trace.endpos[2]; - if( trace.fraction == 1.0f || mid - trace.endpos[2] > svgame.movevars.stepsize ) + if( trace.fraction == 1.0f || mid - trace.endpos[2] > sv_stepsize.value ) return false; } } @@ -150,21 +150,9 @@ void SV_WaterMove( edict_t *ent ) if( flags & FL_INWATER ) { // leave the water. - switch( COM_RandomLong( 0, 3 )) - { - case 0: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 1: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 2: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 3: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - } + const char *snd = SoundList_GetRandom( EntityWaterExit ); + if( snd ) + SV_StartSound( ent, CHAN_BODY, snd, 1.0f, ATTN_NORM, 0, 100 ); ent->v.flags = flags & ~FL_INWATER; } @@ -197,21 +185,9 @@ void SV_WaterMove( edict_t *ent ) if( watertype == CONTENTS_WATER ) { // entering the water - switch( COM_RandomLong( 0, 3 )) - { - case 0: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 1: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 2: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - case 3: - SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); - break; - } + const char *snd = SoundList_GetRandom( EntityWaterEnter ); + if( snd ) + SV_StartSound( ent, CHAN_BODY, snd, 1.0f, ATTN_NORM, 0, 100 ); } ent->v.flags = flags | FL_INWATER; @@ -265,7 +241,7 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; // well, try it. Flying and swimming monsters are easiest. - if( ent->v.flags & ( FL_SWIM|FL_FLY )) + if( FBitSet( ent->v.flags, FL_SWIM|FL_FLY )) { // try one move with vertical motion, then one without for( i = 0; i < 2; i++ ) @@ -289,7 +265,7 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) // that move takes us out of the water. // apparently though, it's okay to travel into solids, lava, sky, etc :) - if(( ent->v.flags & FL_SWIM ) && SV_PointContents( trace.endpos ) == CONTENTS_EMPTY ) + if( FBitSet( ent->v.flags, FL_SWIM ) && SV_PointContents( trace.endpos ) == CONTENTS_EMPTY ) return 0; VectorCopy( trace.endpos, ent->v.origin ); @@ -307,7 +283,7 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) } else { - dz = svgame.movevars.stepsize; + dz = sv_stepsize.value; neworg[2] += dz; VectorCopy( neworg, end ); end[2] -= dz * 2.0f; @@ -327,11 +303,11 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) if( trace.fraction == 1.0f ) { - if( ent->v.flags & FL_PARTIALGROUND ) + if( FBitSet( ent->v.flags, FL_PARTIALGROUND )) { VectorAdd( ent->v.origin, move, ent->v.origin ); if( relink ) SV_LinkEdict( ent, true ); - ent->v.flags &= ~FL_ONGROUND; + ClearBits( ent->v.flags, FL_ONGROUND ); return 1; } return 0; @@ -342,7 +318,7 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) if( SV_CheckBottom( ent, WALKMOVE_NORMAL ) == 0 ) { - if( ent->v.flags & FL_PARTIALGROUND ) + if( FBitSet( ent->v.flags, FL_PARTIALGROUND )) { if( relink ) SV_LinkEdict( ent, true ); return 1; @@ -353,7 +329,7 @@ qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) } else { - ent->v.flags &= ~FL_PARTIALGROUND; + ClearBits( ent->v.flags, FL_PARTIALGROUND ); ent->v.groundentity = trace.ent; if( relink ) SV_LinkEdict( ent, true ); @@ -372,7 +348,7 @@ qboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink ) VectorCopy( ent->v.origin, oldorg ); VectorAdd( ent->v.origin, move, neworg ); - temp = svgame.movevars.stepsize; + temp = sv_stepsize.value; neworg[2] += temp; VectorCopy( neworg, end ); @@ -429,7 +405,7 @@ qboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink ) } } -qboolean SV_StepDirection( edict_t *ent, float yaw, float dist ) +static qboolean SV_StepDirection( edict_t *ent, float yaw, float dist ) { int ret; float cSin, cCos; @@ -445,7 +421,7 @@ qboolean SV_StepDirection( edict_t *ent, float yaw, float dist ) return ret; } -qboolean SV_FlyDirection( edict_t *ent, vec3_t move ) +static qboolean SV_FlyDirection( edict_t *ent, vec3_t move ) { int ret; @@ -455,7 +431,7 @@ qboolean SV_FlyDirection( edict_t *ent, vec3_t move ) return ret; } -void SV_NewChaseDir( edict_t *actor, vec3_t destination, float dist ) +static void SV_NewChaseDir( edict_t *actor, vec3_t destination, float dist ) { float deltax, deltay; float tempdir, olddir, turnaround; diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 069ad1c1..86e9527c 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -65,13 +65,13 @@ Utility functions SV_CheckAllEnts ================ */ -void SV_CheckAllEnts( void ) +static void SV_CheckAllEnts( void ) { static double nextcheck; edict_t *e; int i; - if( !sv_check_errors->value || sv.state != ss_active ) + if( !sv_check_errors.value || sv.state != ss_active ) return; if(( nextcheck - Sys_DoubleTime()) > 0.0 ) @@ -128,14 +128,14 @@ void SV_CheckVelocity( edict_t *ent ) { if( IS_NAN( ent->v.velocity[i] )) { - if( sv_check_errors->value ) + if( sv_check_errors.value ) Con_Printf( "Got a NaN velocity on %s\n", STRING( ent->v.classname )); ent->v.velocity[i] = 0.0f; } if( IS_NAN( ent->v.origin[i] )) { - if( sv_check_errors->value ) + if( sv_check_errors.value ) Con_Printf( "Got a NaN origin on %s\n", STRING( ent->v.classname )); ent->v.origin[i] = 0.0f; } @@ -147,7 +147,7 @@ void SV_CheckVelocity( edict_t *ent ) if( wishspd > maxspd ) { wishspd = sqrt( wishspd ); - if( sv_check_errors->value ) + if( sv_check_errors.value ) Con_Printf( "Got a velocity too high on %s ( %.2f > %.2f )\n", STRING( ent->v.classname ), wishspd, sqrt( maxspd )); wishspd = sv_maxvelocity.value / wishspd; VectorScale( ent->v.velocity, wishspd, ent->v.velocity ); @@ -183,6 +183,38 @@ void SV_UpdateBaseVelocity( edict_t *ent ) } } +/* +============ +SV_TestEntityPosition + +returns true if the entity is in solid currently +============ +*/ +static qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ) +{ + qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + trace_t trace; + + if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) + { + // to avoid falling through tracktrain update client mins\maxs here + if( FBitSet( ent->v.flags, FL_DUCKING )) + SV_SetMinMaxSize( ent, host.player_mins[1], host.player_maxs[1], true ); + else SV_SetMinMaxSize( ent, host.player_mins[0], host.player_maxs[0], true ); + } + + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip ); + + if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent )) + { + if( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker ) + return trace.startsolid; + return false; + } + + return trace.startsolid; +} + /* ============= SV_RunThink @@ -193,7 +225,7 @@ in a frame. Not used for pushmove objects, because they must be exact. Returns false if the entity removed itself. ============= */ -qboolean SV_RunThink( edict_t *ent ) +static qboolean SV_RunThink( edict_t *ent ) { float thinktime; @@ -300,7 +332,7 @@ SV_AngularMove may use friction for smooth stopping ============= */ -void SV_AngularMove( edict_t *ent, float frametime, float friction ) +static void SV_AngularMove( edict_t *ent, float frametime, float friction ) { float adjustment; int i; @@ -334,7 +366,7 @@ SV_LinearMove use friction for smooth stopping ============= */ -void SV_LinearMove( edict_t *ent, float frametime, float friction ) +static void SV_LinearMove( edict_t *ent, float frametime, float friction ) { int i; float adjustment; @@ -368,7 +400,7 @@ SV_RecursiveWaterLevel recursively recalculating the middle ============= */ -float SV_RecursiveWaterLevel( vec3_t origin, float out, float in, int count ) +static float SV_RecursiveWaterLevel( vec3_t origin, float out, float in, int count ) { vec3_t point; float offset; @@ -390,7 +422,7 @@ SV_Submerged determine how deep the entity is ============= */ -float SV_Submerged( edict_t *ent ) +static float SV_Submerged( edict_t *ent ) { float start, bottom; vec3_t point; @@ -423,7 +455,7 @@ float SV_Submerged( edict_t *ent ) SV_CheckWater ============= */ -qboolean SV_CheckWater( edict_t *ent ) +static qboolean SV_CheckWater( edict_t *ent ) { int cont, truecont; vec3_t point; @@ -491,7 +523,7 @@ SV_CheckMover test thing (applies the friction to pushables while standing on moving platform) ============= */ -qboolean SV_CheckMover( edict_t *ent ) +static qboolean SV_CheckMover( edict_t *ent ) { edict_t *gnd = ent->v.groundentity; @@ -514,7 +546,7 @@ SV_ClipVelocity Slide off of the impacting object ================== */ -int SV_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +static int SV_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { float backoff; float change; @@ -557,7 +589,7 @@ Returns the clipflags if the velocity was modified (hit something solid) 4 = dead stop ============ */ -int SV_FlyMove( edict_t *ent, float time, trace_t *steptrace ) +static int SV_FlyMove( edict_t *ent, float time, trace_t *steptrace ) { int i, j, numplanes, bumpcount, blocked; vec3_t dir, end, planes[MAX_CLIP_PLANES]; @@ -570,6 +602,7 @@ int SV_FlyMove( edict_t *ent, float time, trace_t *steptrace ) monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; VectorCopy( ent->v.velocity, original_velocity ); VectorCopy( ent->v.velocity, primal_velocity ); + VectorClear( new_velocity ); numplanes = 0; allFraction = 0.0f; @@ -701,7 +734,7 @@ SV_AddGravity ============ */ -void SV_AddGravity( edict_t *ent ) +static void SV_AddGravity( edict_t *ent ) { float ent_gravity; @@ -718,29 +751,6 @@ void SV_AddGravity( edict_t *ent ) SV_CheckVelocity( ent ); } -/* -============ -SV_AddHalfGravity - -============ -*/ -void SV_AddHalfGravity( edict_t *ent, float timestep ) -{ - float ent_gravity; - - if( ent->v.gravity ) - ent_gravity = ent->v.gravity; - else ent_gravity = 1.0f; - - // Add 1/2 of the total gravitational effects over this timestep - ent->v.velocity[2] -= ( 0.5f * ent_gravity * sv_gravity.value * timestep ); - ent->v.velocity[2] += ( ent->v.basevelocity[2] * sv.frametime ); - ent->v.basevelocity[2] = 0.0f; - - // bound velocity - SV_CheckVelocity( ent ); -} - /* =============================================================================== @@ -755,7 +765,7 @@ SV_AllowPushRotate Allows to change entity yaw? ============ */ -qboolean SV_AllowPushRotate( edict_t *ent ) +static qboolean SV_AllowPushRotate( edict_t *ent ) { model_t *mod; @@ -780,7 +790,7 @@ SV_PushEntity Does not change the entities velocity at all ============ */ -trace_t SV_PushEntity( edict_t *ent, const vec3_t lpush, const vec3_t apush, int *blocked, float flDamage ) +static trace_t SV_PushEntity( edict_t *ent, const vec3_t lpush, const vec3_t apush, int *blocked, float flDamage ) { trace_t trace; qboolean monsterBlock; @@ -842,7 +852,7 @@ SV_CanPushed filter entities for push ============ */ -qboolean SV_CanPushed( edict_t *ent ) +static qboolean SV_CanPushed( edict_t *ent ) { // filter movetypes to collide with switch( ent->v.movetype ) @@ -1141,7 +1151,7 @@ SV_Physics_Pusher ================ */ -void SV_Physics_Pusher( edict_t *ent ) +static void SV_Physics_Pusher( edict_t *ent ) { float oldtime, oldtime2; float thinktime, movetime; @@ -1215,7 +1225,7 @@ SV_Physics_Follow just copy angles and origin of parent ============= */ -void SV_Physics_Follow( edict_t *ent ) +static void SV_Physics_Follow( edict_t *ent ) { edict_t *parent; @@ -1243,7 +1253,7 @@ SV_Physics_Compound a glue two entities together ============= */ -void SV_Physics_Compound( edict_t *ent ) +static void SV_Physics_Compound( edict_t *ent ) { edict_t *parent; @@ -1323,7 +1333,7 @@ SV_PhysicsNoclip A moving object that doesn't obey physics ============= */ -void SV_Physics_Noclip( edict_t *ent ) +static void SV_Physics_Noclip( edict_t *ent ) { // regular thinking if( !SV_RunThink( ent )) return; @@ -1350,7 +1360,7 @@ SV_CheckWaterTransition ============= */ -void SV_CheckWaterTransition( edict_t *ent ) +static void SV_CheckWaterTransition( edict_t *ent ) { vec3_t point; int cont; @@ -1375,7 +1385,9 @@ void SV_CheckWaterTransition( edict_t *ent ) if( ent->v.watertype == CONTENTS_EMPTY ) { // just crossed into water - SV_StartSound( ent, CHAN_AUTO, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); + const char *snd = SoundList_GetRandom( PlayerWaterEnter ); + if( snd ) + SV_StartSound( ent, CHAN_AUTO, snd, 1.0f, ATTN_NORM, 0, 100 ); ent->v.velocity[2] *= 0.5f; } @@ -1409,7 +1421,9 @@ void SV_CheckWaterTransition( edict_t *ent ) if( ent->v.watertype != CONTENTS_EMPTY ) { // just crossed into water - SV_StartSound( ent, CHAN_AUTO, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); + const char *snd = SoundList_GetRandom( PlayerWaterExit ); + if( snd ) + SV_StartSound( ent, CHAN_AUTO, snd, 1.0f, ATTN_NORM, 0, 100 ); } ent->v.watertype = CONTENTS_EMPTY; ent->v.waterlevel = 0; @@ -1423,7 +1437,7 @@ SV_Physics_Toss Toss, bounce, and fly movement. When onground, do nothing. ============= */ -void SV_Physics_Toss( edict_t *ent ) +static void SV_Physics_Toss( edict_t *ent ) { trace_t trace; vec3_t move; @@ -1569,7 +1583,7 @@ This is also used for objects that have become still on the ground, but will fall if the floor is pulled out from under them. ============= */ -void SV_Physics_Step( edict_t *ent ) +static void SV_Physics_Step( edict_t *ent ) { qboolean inwater; qboolean wasonground; @@ -1701,7 +1715,7 @@ SV_PhysicsNone Non moving objects can only think ============= */ -void SV_Physics_None( edict_t *ent ) +static void SV_Physics_None( edict_t *ent ) { SV_RunThink( ent ); } @@ -1759,7 +1773,7 @@ static void SV_Physics_Entity( edict_t *ent ) SV_Physics_Pusher( ent ); break; case MOVETYPE_WALK: - Host_Error( "SV_Physics: bad movetype %i\n", ent->v.movetype ); + Host_Error( "%s: bad movetype %i\n", __func__, ent->v.movetype ); break; } @@ -1769,6 +1783,28 @@ static void SV_Physics_Entity( edict_t *ent ) SV_FreeEdict( ent ); } +static void SV_RunLightStyles( void ) +{ + int i; + + // run lightstyles animation + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + lightstyle_t *ls = &sv.lightstyles[i]; + int ofs; + + ls->time += sv.frametime; + ofs = (ls->time * 10); + + if( ls->length == 0 ) + ls->value = 1.0f; // disable this light + else if( ls->length == 1 ) + ls->value = ls->map[0] / 12.0f; + else + ls->value = ls->map[ofs % ls->length] / 12.0f; + } +} + /* ================ SV_Physics @@ -1813,8 +1849,10 @@ void SV_Physics( void ) // increase framecount sv.framecount++; +#if 0 // figure out why this causes memory corruption // decrement svgame.numEntities if the highest number entities died - for( ; EDICT_NUM( svgame.numEntities - 1 )->free; svgame.numEntities-- ); + for( ; ( ent = EDICT_NUM( svgame.numEntities - 1 )) && ent->free; svgame.numEntities-- ); +#endif } /* @@ -1824,7 +1862,7 @@ SV_GetServerTime Inplementation for new physics interface ================ */ -double SV_GetServerTime( void ) +static double GAME_EXPORT SV_GetServerTime( void ) { return sv.time; } @@ -1836,7 +1874,7 @@ SV_GetFrameTime Inplementation for new physics interface ================ */ -double SV_GetFrameTime( void ) +static double GAME_EXPORT SV_GetFrameTime( void ) { return sv.frametime; } @@ -1848,7 +1886,7 @@ SV_GetHeadNode Inplementation for new physics interface ================ */ -areanode_t *SV_GetHeadNode( void ) +static areanode_t *GAME_EXPORT SV_GetHeadNode( void ) { return sv_areanodes; } @@ -1860,7 +1898,7 @@ SV_ServerState Inplementation for new physics interface ================ */ -int SV_ServerState( void ) +static int GAME_EXPORT SV_ServerState( void ) { return sv.state; } @@ -1920,7 +1958,23 @@ void SV_DrawOrthoTriangles( void ) } } -void SV_UpdateFogSettings( unsigned int packed_fog ) +/* +================== +SV_GetLightStyle + +needs to get correct working SV_LightPoint +================== +*/ +static const char *GAME_EXPORT SV_GetLightStyle( int style ) +{ + if( style < 0 ) style = 0; + if( style >= MAX_LIGHTSTYLES ) + Host_Error( "%s: style: %i >= %d", __func__, style, MAX_LIGHTSTYLES ); + + return sv.lightstyles[style].pattern; +} + +static void GAME_EXPORT SV_UpdateFogSettings( unsigned int packed_fog ) { svgame.movevars.fog_settings = packed_fog; host.movevars_changed = true; // force to transmit @@ -1932,7 +1986,7 @@ pfnGetFilesList ========= */ -static char **pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) +static char **GAME_EXPORT pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) { static search_t *t = NULL; @@ -1950,12 +2004,12 @@ static char **pfnGetFilesList( const char *pattern, int *numFiles, int gamediron return t->filenames; } -static void *pfnMem_Alloc( size_t cb, const char *filename, const int fileline ) +static void *GAME_EXPORT pfnMem_Alloc( size_t cb, const char *filename, const int fileline ) { return _Mem_Alloc( svgame.mempool, cb, true, filename, fileline ); } -static void pfnMem_Free( void *mem, const char *filename, const int fileline ) +static void GAME_EXPORT pfnMem_Free( void *mem, const char *filename, const int fileline ) { if( !mem ) return; _Mem_Free( mem, filename, fileline ); @@ -1981,7 +2035,25 @@ static int GAME_EXPORT pfnPointContents( const float *pos, int groupmask ) return cont; } -const byte *pfnLoadImagePixels( const char *filename, int *width, int *height ) +static 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 ) +{ + return SV_Move( start, mins, maxs, end, type, e, false ); +} + +/* +============= +pfnWriteBytes + +============= +*/ +static void GAME_EXPORT pfnWriteBytes( const byte *bytes, int count ) +{ + MSG_WriteBytes( &sv.multicast, bytes, count ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __func__, count ); + svgame.msg_realsize += count; +} + +static const byte *GAME_EXPORT pfnLoadImagePixels( const char *filename, int *width, int *height ) { rgbdata_t *pic = FS_LoadImage( filename, NULL, 0 ); byte *buffer; @@ -1997,14 +2069,14 @@ const byte *pfnLoadImagePixels( const char *filename, int *width, int *height ) return buffer; } -const char* pfnGetModelName( int modelindex ) +static const char *GAME_EXPORT pfnGetModelName( int modelindex ) { if( modelindex < 0 || modelindex >= MAX_MODELS ) return NULL; return sv.model_precache[modelindex]; } -static const byte *GL_TextureData( unsigned int texnum ) +static const byte *GAME_EXPORT GL_TextureData( unsigned int texnum ) { #if !XASH_DEDICATED return Host_IsDedicated() ? NULL : ref.dllFuncs.GL_TextureData( texnum ); @@ -2053,6 +2125,7 @@ static server_physics_api_t gPhysicsAPI = COM_SaveFile, pfnLoadImagePixels, pfnGetModelName, + Sys_GetNativeObject }; /* @@ -2071,23 +2144,23 @@ qboolean SV_InitPhysicsAPI( void ) { if( pPhysIface( SV_PHYSICS_INTERFACE_VERSION, &gPhysicsAPI, &svgame.physFuncs )) { - Con_Reportf( "SV_LoadProgs: ^2initailized extended PhysicAPI ^7ver. %i\n", SV_PHYSICS_INTERFACE_VERSION ); + Con_Reportf( "%s: ^2initailized extended PhysicAPI ^7ver. %i\n", __func__, SV_PHYSICS_INTERFACE_VERSION ); if( svgame.physFuncs.SV_CheckFeatures != NULL ) { // grab common engine features (it will be shared across the network) - host.features = svgame.physFuncs.SV_CheckFeatures(); - Host_PrintEngineFeatures (); + Host_ValidateEngineFeatures( ENGINE_FEATURES_MASK, svgame.physFuncs.SV_CheckFeatures( )); } return true; } // make sure what physic functions is cleared memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); - + Host_ValidateEngineFeatures( ENGINE_FEATURES_MASK, 0 ); return false; // just tell user about problems } // physic interface is missed + Host_ValidateEngineFeatures( ENGINE_FEATURES_MASK, 0 ); return true; } diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index 53337e80..8a0f26ef 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -21,27 +21,7 @@ GNU General Public License for more details. #include "studio.h" static qboolean has_update = false; - -void SV_ClearPhysEnts( void ) -{ - svgame.pmove->numtouch = 0; - svgame.pmove->numvisent = 0; - svgame.pmove->nummoveent = 0; - svgame.pmove->numphysent = 0; -} - -qboolean SV_PlayerIsFrozen( edict_t *pClient ) -{ - if( sv_background_freeze.value && sv.background ) - return true; - - if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - return false; - - if( FBitSet( pClient->v.flags, FL_FROZEN )) - return true; - return false; -} +static void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ); void SV_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr ) { @@ -59,7 +39,7 @@ void SV_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_ } } -qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) +static qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) { model_t *mod = SV_ModelHandle( ed->v.modelindex ); @@ -87,8 +67,8 @@ qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) } else { - // otherwise copy the classname - Q_strncpy( pe->name, STRING( ed->v.classname ), sizeof( pe->name )); + // otherwise copy the modelname + Q_strncpy( pe->name, mod->name, sizeof( pe->name )); } pe->model = pe->studiomodel = NULL; @@ -153,7 +133,7 @@ qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) return true; } -qboolean SV_ShouldUnlagForPlayer( sv_client_t *cl ) +static qboolean SV_ShouldUnlagForPlayer( sv_client_t *cl ) { // can't unlag in singleplayer if( svs.maxclients <= 1 ) @@ -173,7 +153,7 @@ qboolean SV_ShouldUnlagForPlayer( sv_client_t *cl ) return true; } -void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ) +static void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ) { if( !SV_ShouldUnlagForPlayer( cl )) return; @@ -185,7 +165,7 @@ void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ) VectorCopy( svgame.interp[edictnum-1].oldpos, origin ); } -void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs ) +static void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs ) { if( !SV_ShouldUnlagForPlayer( cl )) return; @@ -207,7 +187,7 @@ SV_AddLinksToPmove collect solid entities ==================== */ -void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) +static void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) { link_t *l, *next; edict_t *check, *pl; @@ -299,7 +279,7 @@ void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t SV_AddLaddersToPmove ==================== */ -void SV_AddLaddersToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) +static void SV_AddLaddersToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) { link_t *l, *next; edict_t *check; @@ -347,7 +327,7 @@ static void GAME_EXPORT pfnParticle( const float *origin, int color, float life, if( !origin ) { - Con_Reportf( S_ERROR "SV_StartParticle: NULL origin. Ignored\n" ); + Con_Reportf( S_ERROR "%s: NULL origin. Ignored\n", __func__ ); return; } @@ -362,11 +342,6 @@ static void GAME_EXPORT pfnParticle( const float *origin, int color, float life, MSG_WriteByte( &sv.reliable_datagram, bound( 0, life * 8, 255 )); } -int SV_TestLine( const vec3_t start, const vec3_t end, int flags ) -{ - return PM_TestLineExt( svgame.pmove, svgame.pmove->physents, svgame.pmove->numphysent, start, end, flags ); -} - static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) { return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, NULL ); @@ -374,33 +349,12 @@ static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) static void GAME_EXPORT pfnStuckTouch( int hitent, pmtrace_t *tr ) { - int i; - - for( i = 0; i < svgame.pmove->numtouch; i++ ) - { - if( svgame.pmove->touchindex[i].ent == hitent ) - return; - } - - if( svgame.pmove->numtouch >= MAX_PHYSENTS ) - return; - - VectorCopy( svgame.pmove->velocity, tr->deltavelocity ); - tr->ent = hitent; - - svgame.pmove->touchindex[svgame.pmove->numtouch++] = *tr; + PM_StuckTouch( svgame.pmove, hitent, tr ); } static int GAME_EXPORT pfnPointContents( float *p, int *truecontents ) { - int cont, truecont; - - truecont = cont = PM_PointContents( svgame.pmove, p ); - if( truecontents ) *truecontents = truecont; - - if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) - cont = CONTENTS_WATER; - return cont; + return PM_PointContentsPmove( svgame.pmove, p, truecontents ); } static int GAME_EXPORT pfnTruePointContents( float *p ) @@ -408,101 +362,29 @@ static int GAME_EXPORT pfnTruePointContents( float *p ) return PM_TruePointContents( svgame.pmove, p ); } -static int GAME_EXPORT pfnHullPointContents( struct hull_s *hull, int num, float *p ) -{ - return PM_HullPointContents( hull, num, p ); -} - static pmtrace_t GAME_EXPORT pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe ) { return PM_PlayerTraceExt( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL ); } -static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) +static pmtrace_t *GAME_EXPORT pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) { - static pmtrace_t tr; - int old_usehull; - - old_usehull = svgame.pmove->usehull; - svgame.pmove->usehull = usehull; - - switch( flags ) - { - case PM_TRACELINE_PHYSENTSONLY: - tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL ); - break; - case PM_TRACELINE_ANYVISIBLE: - tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, ignore_pe, NULL ); - break; - } - - svgame.pmove->usehull = old_usehull; - - return &tr; + return PM_TraceLine( svgame.pmove, start, end, flags, usehull, ignore_pe ); } -static hull_t *pfnHullForBsp( physent_t *pe, float *offset ) +static hull_t *GAME_EXPORT pfnHullForBsp( physent_t *pe, float *offset ) { return PM_HullForBsp( pe, svgame.pmove, offset ); } static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace ) { - int old_usehull; - vec3_t start_l, end_l; - vec3_t offset, temp; - qboolean rotated; - matrix4x4 matrix; - hull_t *hull; - - PM_InitTrace( trace, end ); - - old_usehull = svgame.pmove->usehull; - svgame.pmove->usehull = 2; - - hull = PM_HullForBsp( pe, svgame.pmove, offset ); - - svgame.pmove->usehull = old_usehull; - - if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) - rotated = true; - else rotated = false; - - if( rotated ) - { - Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); - Matrix4x4_VectorITransform( matrix, start, start_l ); - Matrix4x4_VectorITransform( matrix, end, end_l ); - } - else - { - VectorSubtract( start, offset, start_l ); - VectorSubtract( end, offset, end_l ); - } - - PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace ); - trace->ent = NULL; - - if( rotated ) - { - VectorCopy( trace->plane.normal, temp ); - Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); - } - - VectorLerp( start, trace->fraction, end, trace->endpos ); - - return trace->fraction; + return PM_TraceModel( svgame.pmove, pe, start, end, trace ); } -static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) +static const char *GAME_EXPORT pfnTraceTexture( int ground, float *vstart, float *vend ) { - physent_t *pe; - - if( ground < 0 || ground >= svgame.pmove->numphysent ) - return NULL; // bad ground - - pe = &svgame.pmove->physents[ground]; - return PM_TraceTexture( pe, vstart, vend ); + return PM_TraceTexture( svgame.pmove, ground, vstart, vend ); } static void GAME_EXPORT pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ) @@ -523,10 +405,8 @@ static void GAME_EXPORT pfnPlaybackEventFull( int flags, int clientindex, word e ent = EDICT_NUM( clientindex + 1 ); if( !SV_IsValidEdict( ent )) return; - if( Host_IsDedicated() ) - flags |= FEV_NOTHOST; // no local clients for dedicated server - - SV_PlaybackEventFull( flags, ent, eventindex, + // GoldSrc always sets FEV_NOTHOST in PMove version of this function + SV_PlaybackEventFull( flags | FEV_NOTHOST, ent, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, @@ -543,38 +423,14 @@ static int GAME_EXPORT pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, p return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, pmFilter ); } -static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) +static pmtrace_t *GAME_EXPORT pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) { - static pmtrace_t tr; - int old_usehull; - - old_usehull = svgame.pmove->usehull; - svgame.pmove->usehull = usehull; - - switch( flags ) - { - case PM_TRACELINE_PHYSENTSONLY: - tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter ); - break; - case PM_TRACELINE_ANYVISIBLE: - tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, -1, pmFilter ); - break; - } - - svgame.pmove->usehull = old_usehull; - - return &tr; + return PM_TraceLineEx( svgame.pmove, start, end, flags, usehull, pmFilter ); } -static struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) +static struct msurface_s *GAME_EXPORT pfnTraceSurface( int ground, float *vstart, float *vend ) { - physent_t *pe; - - if( ground < 0 || ground >= svgame.pmove->numphysent ) - return NULL; // bad ground - - pe = &svgame.pmove->physents[ground]; - return PM_TraceSurface( pe, vstart, vend ); + return PM_TraceSurfacePmove( svgame.pmove, ground, vstart, vend ); } /* @@ -616,7 +472,7 @@ void SV_InitClientMove( void ) svgame.pmove->PM_StuckTouch = pfnStuckTouch; svgame.pmove->PM_PointContents = pfnPointContents; svgame.pmove->PM_TruePointContents = pfnTruePointContents; - svgame.pmove->PM_HullPointContents = pfnHullPointContents; + svgame.pmove->PM_HullPointContents = (void*)PM_HullPointContents; svgame.pmove->PM_PlayerTrace = pfnPlayerTrace; svgame.pmove->PM_TraceLine = pfnTraceLine; svgame.pmove->RandomLong = COM_RandomLong; @@ -702,7 +558,7 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd if( pmove->multiplayer ) pmove->onground = -1; pmove->waterlevel = clent->v.waterlevel; pmove->watertype = clent->v.watertype; - pmove->maxspeed = svgame.movevars.maxspeed; + pmove->maxspeed = svgame.movevars.maxspeed; // GoldSrc uses sv_maxspeed here? pmove->clientmaxspeed = clent->v.maxspeed; pmove->iuser1 = clent->v.iuser1; pmove->iuser2 = clent->v.iuser2; @@ -719,7 +575,7 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd pmove->cmd = *ucmd; // setup current cmds pmove->runfuncs = true; - Q_strncpy( pmove->physinfo, physinfo, MAX_INFO_STRING ); + Q_strncpy( pmove->physinfo, physinfo, sizeof( pmove->physinfo )); // setup physents pmove->numvisent = 0; @@ -802,13 +658,13 @@ static void SV_FinishPMove( playermove_t *pmove, sv_client_t *cl ) clent->v.angles[YAW] = clent->v.v_angle[YAW]; } - SV_SetMinMaxSize( clent, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], false ); + SV_SetMinMaxSize( clent, host.player_mins[pmove->usehull], host.player_maxs[pmove->usehull], false ); // all next calls ignore footstep sounds pmove->runfuncs = false; } -entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame ) +static entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame ) { entity_state_t *state; int i; @@ -823,7 +679,7 @@ entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame ) return NULL; } -qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos ) +static qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos ) { int i; @@ -835,7 +691,7 @@ qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos ) return false; } -void SV_SetupMoveInterpolant( sv_client_t *cl ) +static void SV_SetupMoveInterpolant( sv_client_t *cl ) { int i, j, clientnum; float finalpush, lerp_msec; @@ -985,7 +841,7 @@ void SV_SetupMoveInterpolant( sv_client_t *cl ) } } -void SV_RestoreMoveInterpolant( sv_client_t *cl ) +static void SV_RestoreMoveInterpolant( sv_client_t *cl ) { sv_client_t *check; sv_interp_t *oldlerp; @@ -1036,16 +892,32 @@ void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ) vec3_t oldvel; usercmd_t cmd; + // if the player got kicked, do not process commands + if( cl->state <= cs_zombie ) + return; + clent = cl->edict; cmd = *ucmd; if( cl->ignorecmdtime > host.realtime ) { + if( !cl->ignorecmdtime_warned && !FBitSet( cl->flags, FCL_FAKECLIENT )) + { + // report to server op + Con_Reportf( S_WARN "%s time is faster than server time (speed hack?)\n", cl->name ); + cl->ignorecmdtime_warned = true; + cl->ignorecmdtime_warns++; + + // automatically kick player + if( sv_speedhack_kick.value && cl->ignorecmdtime_warns > sv_speedhack_kick.value ) + SV_KickPlayer( cl, "Speed hacks aren't allowed on this server" ); + } cl->cmdtime += ((double)ucmd->msec / 1000.0 ); return; } cl->ignorecmdtime = 0.0; + cl->ignorecmdtime_warned = false; // chop up very long commands if( cmd.msec > 50 ) @@ -1080,12 +952,6 @@ void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ) clent->v.light_level = ucmd->lightlevel; if( ucmd->impulse ) clent->v.impulse = ucmd->impulse; - if( ucmd->impulse == 204 ) - { - // force client.dll update - SV_RefreshUserinfo(); - } - svgame.globals->time = cl->timebase; svgame.dllFuncs.pfnPlayerPreThink( clent ); SV_PlayerRunThink( clent, frametime, cl->timebase ); diff --git a/engine/server/sv_query.c b/engine/server/sv_query.c new file mode 100644 index 00000000..7f3a4048 --- /dev/null +++ b/engine/server/sv_query.c @@ -0,0 +1,189 @@ +/* +sv_query.c - Source-engine like server querying +Copyright (C) 2023 jeefo + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "common.h" +#include "server.h" + +/* +================== +SV_SourceQuery_Details +================== +*/ +static void SV_SourceQuery_Details( netadr_t from ) +{ + sizebuf_t buf; + char answer[2048]; + int bot_count, client_count; + + SV_GetPlayerCount( &client_count, &bot_count ); + client_count += bot_count; // bots are counted as players in this reply + + MSG_Init( &buf, "TSourceEngineQuery", answer, sizeof( answer )); + + MSG_WriteDword( &buf, 0xFFFFFFFFU ); + MSG_WriteByte( &buf, S2A_GOLDSRC_INFO ); + MSG_WriteByte( &buf, PROTOCOL_VERSION ); + + MSG_WriteString( &buf, hostname.string ); + MSG_WriteString( &buf, sv.name ); + MSG_WriteString( &buf, GI->gamefolder ); + MSG_WriteString( &buf, svgame.dllFuncs.pfnGetGameDescription( )); + + MSG_WriteShort( &buf, 0 ); + MSG_WriteByte( &buf, client_count ); + MSG_WriteByte( &buf, svs.maxclients ); + MSG_WriteByte( &buf, bot_count ); + + MSG_WriteByte( &buf, Host_IsDedicated( ) ? 'd' : 'l' ); +#if XASH_WIN32 + MSG_WriteByte( &buf, 'w' ); +#elif XASH_APPLE + MSG_WriteByte( &buf, 'm' ); +#else + MSG_WriteByte( &buf, 'l' ); +#endif + + if( SV_HavePassword( )) + MSG_WriteByte( &buf, 1 ); + else MSG_WriteByte( &buf, 0 ); + MSG_WriteByte( &buf, GI->secure ); + MSG_WriteString( &buf, XASH_VERSION ); + + NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &buf ), MSG_GetData( &buf ), from ); +} + +/* +================== +SV_SourceQuery_Rules +================== +*/ +static void SV_SourceQuery_Rules( netadr_t from ) +{ + const cvar_t *cvar; + sizebuf_t buf; + char answer[MAX_PRINT_MSG - 4]; + int pos; + uint cvar_count = 0; + + MSG_Init( &buf, "TSourceEngineQueryRules", answer, sizeof( answer )); + + MSG_WriteDword( &buf, 0xFFFFFFFFU ); + MSG_WriteByte( &buf, S2A_GOLDSRC_RULES ); + + pos = MSG_GetNumBitsWritten( &buf ); + MSG_WriteShort( &buf, 0 ); + + for( cvar = Cvar_GetList( ); cvar; cvar = cvar->next ) + { + if( !FBitSet( cvar->flags, FCVAR_SERVER )) + continue; + + MSG_WriteString( &buf, cvar->name ); + + if( FBitSet( cvar->flags, FCVAR_PROTECTED )) + { + if( COM_CheckStringEmpty( cvar->string ) && Q_stricmp( cvar->string, "none" )) + MSG_WriteString( &buf, "1" ); + else MSG_WriteString( &buf, "0" ); + } + else MSG_WriteString( &buf, cvar->string ); + + cvar_count++; + } + + if( cvar_count != 0 ) + { + int total = MSG_GetNumBytesWritten( &buf ); + + MSG_SeekToBit( &buf, pos, SEEK_SET ); + MSG_WriteShort( &buf, cvar_count ); + + NET_SendPacket( NS_SERVER, total, MSG_GetData( &buf ), from ); + } +} + +/* +================== +SV_SourceQuery_Players +================== +*/ +static void SV_SourceQuery_Players( netadr_t from ) +{ + sizebuf_t buf; + char answer[MAX_PRINT_MSG - 4]; + int i, count = 0; + int pos; + + // respect players privacy + if( !sv_expose_player_list.value || SV_HavePassword( )) + return; + + MSG_Init( &buf, "TSourceEngineQueryPlayers", answer, sizeof( answer )); + + MSG_WriteDword( &buf, 0xFFFFFFFFU ); + MSG_WriteByte( &buf, S2A_GOLDSRC_PLAYERS ); + + pos = MSG_GetNumBitsWritten( &buf ); + MSG_WriteByte( &buf, 0 ); + + for( i = 0; i < svs.maxclients; i++ ) + { + const sv_client_t *cl = &svs.clients[i]; + + if( cl->state < cs_connected ) + continue; + + MSG_WriteByte( &buf, count ); + MSG_WriteString( &buf, cl->name ); + MSG_WriteLong( &buf, cl->edict->v.frags ); + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + MSG_WriteFloat( &buf, -1.0f ); + else MSG_WriteFloat( &buf, host.realtime - cl->connection_started ); + + count++; + } + + if( count != 0 ) + { + int total = MSG_GetNumBytesWritten( &buf ); + + MSG_SeekToBit( &buf, pos, SEEK_SET ); + MSG_WriteByte( &buf, count ); + + NET_SendPacket( NS_SERVER, total, MSG_GetData( &buf ), from ); + } +} + +/* +================== +SV_SourceQuery_HandleConnnectionlessPacket +================== +*/ +void SV_SourceQuery_HandleConnnectionlessPacket( const char *c, netadr_t from ) +{ + if( !Q_strcmp( c, A2S_GOLDSRC_INFO )) + { + SV_SourceQuery_Details( from ); + } + else switch( c[0] ) + { + case A2S_GOLDSRC_RULES: + SV_SourceQuery_Rules( from ); + break; + case A2S_GOLDSRC_PLAYERS: + SV_SourceQuery_Players( from ); + break; + } +} diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 0fcf66e4..f5ca6b56 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -35,7 +35,6 @@ half-life implementation of saverestore system #define SAVE_HEAPSIZE 0x400000 // reserve 4Mb for now #define SAVE_HASHSTRINGS 0xFFF // 4095 unique strings -#define SAVE_AGED_COUNT 2 // savedata headers typedef struct @@ -83,7 +82,7 @@ typedef struct float time; } SAVE_LIGHTSTYLE; -void (__cdecl *pfnSaveGameComment)( char *buffer, int max_length ) = NULL; +static void (__cdecl *pfnSaveGameComment)( char *buffer, int max_length ) = NULL; static TYPEDESCRIPTION gGameHeader[] = { @@ -142,7 +141,8 @@ static TYPEDESCRIPTION gSaveClient[] = DEFINE_ARRAY( SAVE_CLIENT, introTrack, FIELD_CHARACTER, 64 ), DEFINE_ARRAY( SAVE_CLIENT, mainTrack, FIELD_CHARACTER, 64 ), DEFINE_FIELD( SAVE_CLIENT, trackPosition, FIELD_INTEGER ), - DEFINE_FIELD( SAVE_CLIENT, viewentity, FIELD_SHORT ), + // mods based on HLU SDK disallow usage of FIELD_SHORT + DEFINE_ARRAY( SAVE_CLIENT, viewentity, FIELD_CHARACTER, sizeof( short )), DEFINE_FIELD( SAVE_CLIENT, wateralpha, FIELD_FLOAT ), DEFINE_FIELD( SAVE_CLIENT, wateramp, FIELD_FLOAT ), }; @@ -151,7 +151,8 @@ static TYPEDESCRIPTION gDecalEntry[] = { DEFINE_FIELD( decallist_t, position, FIELD_VECTOR ), DEFINE_ARRAY( decallist_t, name, FIELD_CHARACTER, 64 ), - DEFINE_FIELD( decallist_t, entityIndex, FIELD_SHORT ), + // mods based on HLU SDK disallow usage of FIELD_SHORT + DEFINE_ARRAY( decallist_t, entityIndex, FIELD_CHARACTER, sizeof( short )), DEFINE_FIELD( decallist_t, depth, FIELD_CHARACTER ), DEFINE_FIELD( decallist_t, flags, FIELD_CHARACTER ), DEFINE_FIELD( decallist_t, scale, FIELD_FLOAT ), @@ -159,6 +160,7 @@ static TYPEDESCRIPTION gDecalEntry[] = DEFINE_ARRAY( decallist_t, studio_state, FIELD_CHARACTER, sizeof( modelstate_t )), }; +// Can use any FIELD type here because only Xash3D games will spawn static entities static TYPEDESCRIPTION gStaticEntry[] = { DEFINE_FIELD( entity_state_t, messagenum, FIELD_MODELNAME ), // HACKHACK: store model into messagenum @@ -201,7 +203,8 @@ static TYPEDESCRIPTION gStaticEntry[] = static TYPEDESCRIPTION gSoundEntry[] = { DEFINE_ARRAY( soundlist_t, name, FIELD_CHARACTER, 64 ), - DEFINE_FIELD( soundlist_t, entnum, FIELD_SHORT ), + // mods based on HLU SDK disallow usage of FIELD_SHORT + DEFINE_ARRAY( soundlist_t, entnum, FIELD_CHARACTER, sizeof( short )), DEFINE_FIELD( soundlist_t, origin, FIELD_VECTOR ), DEFINE_FIELD( soundlist_t, volume, FIELD_FLOAT ), DEFINE_FIELD( soundlist_t, attenuation, FIELD_FLOAT ), @@ -219,7 +222,7 @@ static TYPEDESCRIPTION gTempEntvars[] = DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), }; -struct +static const struct { const char *mapname; const char *titlename; @@ -653,7 +656,7 @@ static void DirectoryCopy( const char *pPath, file_t *pFile ) fileSize = FS_FileLength( pCopy ); memset( szName, 0, sizeof( szName )); // clearing the string to prevent garbage in output file - Q_strncpy( szName, COM_FileWithoutPath( t->filenames[i] ), MAX_OSPATH ); + Q_strncpy( szName, COM_FileWithoutPath( t->filenames[i] ), sizeof( szName )); FS_Write( pFile, szName, MAX_OSPATH ); FS_Write( pFile, &fileSize, sizeof( int )); FS_FileCopy( pFile, pCopy, fileSize ); @@ -767,33 +770,6 @@ static void SaveFinish( SAVERESTOREDATA *pSaveData ) Mem_Free( pSaveData ); } -/* -============= -DumpHashStrings - -debug thing -============= -*/ -static void DumpHashStrings( SAVERESTOREDATA *pSaveData, const char *pMessage ) -{ - int i, count = 0; - - if( pSaveData && pSaveData->pTokens ) - { - Con_Printf( "%s\n", pMessage ); - - for( i = 0; i < pSaveData->tokenCount; i++ ) - { - if( !pSaveData->pTokens[i] ) - continue; - - Con_Printf( "#%i %s\n", count, pSaveData->pTokens[i] ); - count++; - } - Con_Printf( "total %i actual %i\n", pSaveData->tokenCount, count ); - } -} - /* ============= StoreHashTable @@ -985,7 +961,10 @@ static void ParseSaveTables( SAVERESTOREDATA *pSaveData, SAVE_HEADER *pHeader, i InitEntityTable( pSaveData, pSaveData->tableCount ); for( i = 0; i < pSaveData->tableCount; i++ ) + { svgame.dllFuncs.pfnSaveReadFields( pSaveData, "ETABLE", &pSaveData->pTable[i], gEntityTable, ARRAYSIZE( gEntityTable )); + pSaveData->pTable[i].pent = NULL; + } pSaveData->pBaseData = pSaveData->pCurrentData; pSaveData->size = 0; @@ -1195,41 +1174,36 @@ static void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int char name[MAX_QPATH]; int i, id, version; char *pTokenData; - decallist_t *decalList; - SAVE_CLIENT header; + decallist_t *decalList = NULL; + SAVE_CLIENT header = { 0 }; file_t *pFile; // clearing the saving buffer to reuse SaveClear( pSaveData ); - memset( &header, 0, sizeof( header )); - - // g-cont. add space for studiodecals if present - decalList = (decallist_t *)Z_Calloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); + header.entityCount = sv.num_static_entities; // initialize client header #if !XASH_DEDICATED - if( !Host_IsDedicated() ) + if( !Host_IsDedicated( )) { - header.decalCount = ref.dllFuncs.R_CreateDecalList( decalList ); - } - else -#endif // XASH_DEDICATED - { - // we probably running a dedicated server - header.decalCount = 0; - } - header.entityCount = sv.num_static_entities; + // g-cont. add space for studiodecals if present + decalList = (decallist_t *)Mem_Calloc( host.mempool, sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); - if( !changelevel ) - { - // sounds won't going across transition - header.soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS ); -#if !XASH_DEDICATED - // music not reqiured to save position: it's just continue playing on a next level - S_StreamGetCurrentState( header.introTrack, header.mainTrack, &header.trackPosition ); -#endif + header.decalCount = ref.dllFuncs.R_CreateDecalList( decalList ); + + if( !changelevel ) // sounds won't going across transition + { + header.soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS ); + + // music not reqiured to save position: it's just continue playing on a next level + S_StreamGetCurrentState( + header.introTrack, sizeof( header.introTrack ), + header.mainTrack, sizeof( header.mainTrack ), + &header.trackPosition ); + } } +#endif // XASH_DEDICATED // save viewentity to allow camera works after save\restore if( SV_IsValidEdict( cl->pViewEntity ) && cl->pViewEntity != cl->edict ) @@ -1242,7 +1216,7 @@ static void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "ClientHeader", &header, gSaveClient, ARRAYSIZE( gSaveClient )); // store decals - for( i = 0; i < header.decalCount; i++ ) + for( i = 0; decalList != NULL && i < header.decalCount; i++ ) { // NOTE: apply landmark offset only for brush entities without origin brushes if( pSaveData->fUseLandmark && FBitSet( decalList[i].flags, FDECAL_USE_LANDMARK )) @@ -1250,7 +1224,9 @@ static void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "DECALLIST", &decalList[i], gDecalEntry, ARRAYSIZE( gDecalEntry )); } - Z_Free( decalList ); + + if( decalList ) + Mem_Free( decalList ); // write client entities for( i = 0; i < header.entityCount; i++ ) @@ -1389,7 +1365,7 @@ static void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboo { // NOTE: music is automatically goes across transition, never restore it on changelevel MSG_BeginServerCmd( &sv.signon, svc_stufftext ); - MSG_WriteString( &sv.signon, va( "music \"%s\" \"%s\" %i\n", header.introTrack, header.mainTrack, header.trackPosition )); + MSG_WriteStringf( &sv.signon, "music \"%s\" \"%s\" %i\n", header.introTrack, header.mainTrack, header.trackPosition ); } // don't go camera across the levels @@ -1630,6 +1606,10 @@ static int LoadGameState( char const *level, qboolean changelevel ) pSaveData = LoadSaveData( level ); if( !pSaveData ) return 0; // couldn't load the file + // must set mapname before calling into DLL + Q_strncpy( sv.name, level, sizeof( sv.name )); + svgame.globals->mapname = MAKE_STRING( sv.name ); + ParseSaveTables( pSaveData, &header, true ); EntityPatchRead( pSaveData, level ); @@ -1637,8 +1617,6 @@ static int LoadGameState( char const *level, qboolean changelevel ) sv.loadgame = sv.paused = true; Cvar_SetValue( "skill", header.skillLevel ); - Q_strncpy( sv.name, header.mapName, sizeof( sv.name )); - svgame.globals->mapname = MAKE_STRING( sv.name ); Cvar_Set( "sv_skyname", header.skyName ); // restore sky parms @@ -1693,7 +1671,7 @@ SaveGameSlot do a save game ============= */ -static int SaveGameSlot( const char *pSaveName, const char *pSaveComment ) +static qboolean SaveGameSlot( const char *pSaveName, const char *pSaveComment ) { char hlPath[MAX_QPATH]; char name[MAX_QPATH]; @@ -1704,7 +1682,7 @@ static int SaveGameSlot( const char *pSaveName, const char *pSaveComment ) file_t *pFile; pSaveData = SaveGameState( false ); - if( !pSaveData ) return 0; + if( !pSaveData ) return false; SaveFinish( pSaveData ); pSaveData = SaveInit( SAVE_HEAPSIZE, SAVE_HASHSTRINGS ); // re-init the buffer @@ -1727,19 +1705,21 @@ static int SaveGameSlot( const char *pSaveName, const char *pSaveComment ) COM_FixSlashes( name ); // output to disk - if( !Q_stricmp( pSaveName, "quick" ) || !Q_stricmp( pSaveName, "autosave" )) - AgeSaveList( pSaveName, SAVE_AGED_COUNT ); + if( !Q_stricmp( pSaveName, "quick" )) + AgeSaveList( pSaveName, GI->quicksave_aged_count ); + else if( !Q_stricmp( pSaveName, "autosave" )) + AgeSaveList( pSaveName, GI->autosave_aged_count ); // output to disk if(( pFile = FS_Open( name, "wb", true )) == NULL ) { // something bad is happens SaveFinish( pSaveData ); - return 0; + return false; } // pending the preview image for savegame - Cbuf_AddText( va( "saveshot \"%s\"\n", pSaveName )); + Cbuf_AddTextf( "saveshot \"%s\"\n", pSaveName ); Con_Printf( "Saving game to %s...\n", name ); version = SAVEGAME_VERSION; @@ -1759,7 +1739,7 @@ static int SaveGameSlot( const char *pSaveName, const char *pSaveComment ) SaveFinish( pSaveData ); FS_Close( pFile ); - return 1; + return true; } /* @@ -2042,12 +2022,12 @@ void SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char if( start ) { - Q_strncpy( _startspot, start, MAX_STRING ); + Q_strncpy( _startspot, start, sizeof( _startspot )); startspot = _startspot; } - Q_strncpy( level, mapname, MAX_STRING ); - Q_strncpy( oldlevel, sv.name, MAX_STRING ); + Q_strncpy( level, mapname, sizeof( level )); + Q_strncpy( oldlevel, sv.name, sizeof( oldlevel )); if( loadfromsavedgame ) { @@ -2133,7 +2113,7 @@ qboolean SV_LoadGame( const char *pPath ) if( validload ) { // now check for map problems - flags = SV_MapIsValid( gameHeader.mapName, GI->sp_entity, NULL ); + flags = SV_MapIsValid( gameHeader.mapName, NULL ); if( FBitSet( flags, MAP_INVALID_VERSION )) { @@ -2169,17 +2149,17 @@ qboolean SV_LoadGame( const char *pPath ) SV_SaveGame ================== */ -void SV_SaveGame( const char *pName ) +qboolean SV_SaveGame( const char *pName ) { char comment[80]; - int result; string savename; if( !COM_CheckString( pName )) - return; + return false; // can we save at this point? - if( !IsValidSave( )) return; + if( !IsValidSave( )) + return false; if( !Q_stricmp( pName, "new" )) { @@ -2197,7 +2177,7 @@ void SV_SaveGame( const char *pName ) if( n == 1000 ) { Con_Printf( S_ERROR "no free slots for savegame\n" ); - return; + return false; } } else Q_strncpy( savename, pName, sizeof( savename )); @@ -2208,12 +2188,20 @@ void SV_SaveGame( const char *pName ) #endif // XASH_DEDICATED SaveBuildComment( comment, sizeof( comment )); - result = SaveGameSlot( savename, comment ); + return SaveGameSlot( savename, comment ); +} -#if !XASH_DEDICATED - if( result && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - CL_HudMessage( "GAMESAVED" ); // defined in titles.txt -#endif // XASH_DEDICATED +static int SV_CompareFileTime( int ft1, int ft2 ) +{ + if( ft1 < ft2 ) + { + return -1; + } + else if( ft1 > ft2 ) + { + return 1; + } + return 0; } /* @@ -2241,7 +2229,7 @@ const char *SV_GetLatestSave( void ) if( ft > 0 ) { // should we use the matched? - if( !found || Host_CompareFileTime( newest, ft ) < 0 ) + if( !found || SV_CompareFileTime( newest, ft ) < 0 ) { Q_strncpy( savename, t->filenames[i], sizeof( savename )); newest = ft; @@ -2291,7 +2279,7 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) if( tag == 0x0065 ) { - Q_strncpy( comment, "", MAX_STRING ); + Q_strncpy( comment, "", MAX_STRING ); FS_Close( f ); return 0; } @@ -2414,17 +2402,17 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) uint flags; // now check for map problems - flags = SV_MapIsValid( mapName, GI->sp_entity, NULL ); + flags = SV_MapIsValid( mapName, NULL ); if( FBitSet( flags, MAP_INVALID_VERSION )) { - Q_strncpy( comment, va( "", mapName ), MAX_STRING ); + Q_snprintf( comment, MAX_STRING, "", mapName ); return 0; } if( !FBitSet( flags, MAP_IS_EXIST )) { - Q_strncpy( comment, va( "", mapName ), MAX_STRING ); + Q_snprintf( comment, MAX_STRING, "", mapName ); return 0; } @@ -2433,10 +2421,10 @@ int GAME_EXPORT SV_GetSaveComment( const char *savename, char *comment ) // split comment to sections if( Q_strstr( savename, "quick" )) - Q_strncat( comment, "[quick]", CS_SIZE ); + Q_snprintf( comment, CS_SIZE, "[quick]%s", description ); else if( Q_strstr( savename, "autosave" )) - Q_strncat( comment, "[autosave]", CS_SIZE ); - Q_strncat( comment, description, CS_SIZE ); + Q_snprintf( comment, CS_SIZE, "[autosave]%s", description ); + else Q_strncpy( comment, description, CS_SIZE ); strftime( timestring, sizeof ( timestring ), "%b%d %Y", file_tm ); Q_strncpy( comment + CS_SIZE, timestring, CS_TIME ); strftime( timestring, sizeof( timestring ), "%H:%M", file_tm ); diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c index 71079e65..b992912b 100644 --- a/engine/server/sv_world.c +++ b/engine/server/sv_world.c @@ -40,9 +40,8 @@ HULL BOXES =============================================================================== */ -static hull_t box_hull; -static mclipnode_t box_clipnodes[6]; -static mplane_t box_planes[6]; +static hull_t box_hull; +static mplane_t box_planes[6]; /* =================== @@ -52,25 +51,17 @@ Set up the planes and clipnodes so that the six floats of a bounding box can just be stored out and get a proper hull_t structure. =================== */ -void SV_InitBoxHull( void ) +static void SV_InitBoxHull( void ) { - int i, side; + int i; - box_hull.clipnodes = box_clipnodes; + box_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16; box_hull.planes = box_planes; box_hull.firstclipnode = 0; box_hull.lastclipnode = 5; for( i = 0; i < 6; i++ ) { - box_clipnodes[i].planenum = i; - - side = i & 1; - - box_clipnodes[i].children[side] = CONTENTS_EMPTY; - if( i != 5 ) box_clipnodes[i].children[side^1] = i + 1; - else box_clipnodes[i].children[side^1] = CONTENTS_SOLID; - box_planes[i].type = i>>1; box_planes[i].normal[i>>1] = 1; box_planes[i].signbits = 0; @@ -84,7 +75,7 @@ StudioPlayerBlend ==================== */ -void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +static void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing *pBlend = (*pPitch * 3); @@ -115,7 +106,7 @@ SV_CheckSphereIntersection check clients only ==================== */ -qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end ) +static qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end ) { int i, sequence; float radiusSquared; @@ -158,7 +149,7 @@ To keep everything totally uniform, bounding boxes are turned into small BSP trees instead of being compared directly. =================== */ -hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) +static hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) { box_planes[0].dist = maxs[0]; box_planes[1].dist = mins[0]; @@ -167,49 +158,14 @@ hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) box_planes[4].dist = maxs[2]; box_planes[5].dist = mins[2]; + if( world.version == QBSP2_VERSION ) + box_hull.clipnodes32 = (mclipnode32_t *)box_clipnodes32; + else + box_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16; + return &box_hull; } -/* -================== -SV_HullAutoSelect - -select the apropriate hull automatically -================== -*/ -hull_t *SV_HullAutoSelect( model_t *model, const vec3_t mins, const vec3_t maxs, const vec3_t size, vec3_t offset ) -{ - float curdiff; - float lastdiff = 999; - int i, hullNumber = 0; // assume we fail - vec3_t clip_size; - hull_t *hull; - - // NOTE: this is not matched with hardcoded values in some cases... - for( i = 0; i < MAX_MAP_HULLS; i++ ) - { - VectorSubtract( model->hulls[i].clip_maxs, model->hulls[i].clip_mins, clip_size ); - curdiff = floor( VectorAvg( size )) - floor( VectorAvg( clip_size )); - curdiff = fabs( curdiff ); - - if( curdiff < lastdiff ) - { - hullNumber = i; - lastdiff = curdiff; - } - } - - // TraceHull stuff - hull = &model->hulls[hullNumber]; - - // calculate an offset value to center the origin - // NOTE: never get offset of drawing hull - if( !hullNumber ) VectorCopy( hull->clip_mins, offset ); - else VectorSubtract( hull->clip_mins, mins, offset ); - - return hull; -} - /* ================== SV_HullForBsp @@ -217,7 +173,7 @@ SV_HullForBsp forcing to select BSP hull ================== */ -hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset ) +static hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset ) { hull_t *hull; model_t *model; @@ -289,7 +245,7 @@ Offset is filled in to contain the adjustment that must be added to the testing object's origin to get a point to use with the returned hull. ================ */ -hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset ) +static hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset ) { hull_t *hull; vec3_t hullmins, hullmaxs; @@ -322,7 +278,7 @@ SV_HullForStudioModel ==================== */ -hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes ) +static hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes ) { qboolean useComplexHull; float scale = 0.5f; @@ -398,6 +354,53 @@ hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t of return SV_HullForEntity( ent, mins, maxs, offset ); } +/* +=============================================================================== + + ENTITY LINKING + +=============================================================================== +*/ +/* +=============== +ClearLink + +ClearLink is used for new headnodes +=============== +*/ +static void ClearLink( link_t *l ) +{ + l->prev = l->next = l; +} + +/* +=============== +RemoveLink + +remove link from chain +=============== +*/ +static void RemoveLink( link_t *l ) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +/* +=============== +InsertLinkBefore + +kept trigger and solid entities seperate +=============== +*/ +static void InsertLinkBefore( link_t *l, link_t *before ) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + /* =============================================================================== @@ -416,7 +419,7 @@ SV_CreateAreaNode builds a uniformly subdivided tree for the given world size =============== */ -areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs ) +static areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs ) { areanode_t *anode; vec3_t size; @@ -500,7 +503,7 @@ void SV_UnlinkEdict( edict_t *ent ) SV_TouchLinks ==================== */ -void SV_TouchLinks( edict_t *ent, areanode_t *node ) +static void SV_TouchLinks( edict_t *ent, areanode_t *node ) { link_t *l, *next; edict_t *touch; @@ -587,7 +590,7 @@ SV_FindTouchedLeafs =============== */ -void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) +static void SV_FindTouchedLeafs( edict_t *ent, model_t *mod, mnode_t *node, int *headnode ) { int sides; mleaf_t *leaf; @@ -598,16 +601,19 @@ void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) // add an efrag if the node is a leaf if( node->contents < 0 ) { - if( ent->num_leafs > ( MAX_ENT_LEAFS - 1 )) + if( ent->num_leafs >= MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 ))) { // continue counting leafs, // so we know how many it's overrun - ent->num_leafs = (MAX_ENT_LEAFS + 1); + ent->num_leafs = (MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 )) + 1); } else { leaf = (mleaf_t *)node; - ent->leafnums[ent->num_leafs] = leaf->cluster; + if( FBitSet( mod->flags, MODEL_QBSP2 )) + ent->leafnums32[ent->num_leafs] = leaf->cluster; + else + ent->leafnums16[ent->num_leafs] = leaf->cluster; ent->num_leafs++; } return; @@ -617,11 +623,13 @@ void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) sides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane ); if(( sides == 3 ) && ( *headnode == -1 )) - *headnode = node - sv.worldmodel->nodes; + *headnode = node - mod->nodes; // recurse down the contacted sides - if( sides & 1 ) SV_FindTouchedLeafs( ent, node->children[0], headnode ); - if( sides & 2 ) SV_FindTouchedLeafs( ent, node->children[1], headnode ); + if( sides & 1 ) + SV_FindTouchedLeafs( ent, mod, node_child( node, 0, mod ), headnode ); + if( sides & 2 ) + SV_FindTouchedLeafs( ent, mod, node_child( node, 1, mod ), headnode ); } /* @@ -629,7 +637,7 @@ void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) SV_LinkEdict =============== */ -void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) +void GAME_EXPORT SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) { areanode_t *node; int headnode; @@ -643,7 +651,7 @@ void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) if( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment )) { - memcpy( ent->leafnums, ent->v.aiment->leafnums, sizeof( ent->leafnums )); + memcpy( ent->leafnums32, ent->v.aiment->leafnums32, sizeof( ent->leafnums32 )); ent->num_leafs = ent->v.aiment->num_leafs; ent->headnode = ent->v.aiment->headnode; } @@ -655,11 +663,11 @@ void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) headnode = -1; if( ent->v.modelindex ) - SV_FindTouchedLeafs( ent, sv.worldmodel->nodes, &headnode ); + SV_FindTouchedLeafs( ent, sv.worldmodel, sv.worldmodel->nodes, &headnode ); - if( ent->num_leafs > MAX_ENT_LEAFS ) + if( ent->num_leafs > MAX_ENT_LEAFS( FBitSet( sv.worldmodel->flags, MODEL_QBSP2 ))) { - memset( ent->leafnums, -1, sizeof( ent->leafnums )); + memset( ent->leafnums32, -1, sizeof( ent->leafnums32 )); ent->num_leafs = 0; // so we use headnode instead ent->headnode = headnode; } @@ -704,7 +712,7 @@ POINT TESTING IN HULLS =============================================================================== */ -void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node ) +static void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node ) { link_t *l, *next; edict_t *touch; @@ -810,40 +818,6 @@ int GAME_EXPORT SV_PointContents( const vec3_t p ) return cont; } -//=========================================================================== - -/* -============ -SV_TestEntityPosition - -returns true if the entity is in solid currently -============ -*/ -qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ) -{ - qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; - trace_t trace; - - if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) - { - // to avoid falling through tracktrain update client mins\maxs here - if( FBitSet( ent->v.flags, FL_DUCKING )) - SV_SetMinMaxSize( ent, svgame.pmove->player_mins[1], svgame.pmove->player_maxs[1], true ); - else SV_SetMinMaxSize( ent, svgame.pmove->player_mins[0], svgame.pmove->player_maxs[0], true ); - } - - trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip ); - - if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent )) - { - if( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker ) - return trace.startsolid; - return false; - } - - return trace.startsolid; -} - /* =============================================================================== @@ -992,7 +966,7 @@ if the bsp behind it prevents out origin from getting through. so if the trace w continue the trace to the edges of the portal cutout instead. ================== */ -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 ) +static 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 ) { vec4_t planes[6]; //far, near, right, left, up, down int plane, k; @@ -1297,7 +1271,7 @@ SV_ClipToWorldBrush Mins and maxs enclose the entire area swept by the move ==================== */ -void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip ) +static void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip ) { link_t *l, *next; edict_t *touch; @@ -1339,17 +1313,16 @@ SV_Move */ trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip ) { - moveclip_t clip; - vec3_t trace_endpos; - float trace_fraction; + moveclip_t clip = { 0 }; - memset( &clip, 0, sizeof( moveclip_t )); SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace ); if( clip.trace.fraction != 0.0f ) { + const float trace_fraction = clip.trace.fraction; + vec3_t trace_endpos; VectorCopy( clip.trace.endpos, trace_endpos ); - trace_fraction = clip.trace.fraction; + clip.trace.fraction = 1.0f; clip.start = start; clip.end = trace_endpos; @@ -1387,17 +1360,12 @@ trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, return clip.trace; } -trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) -{ - return SV_Move( start, mins, maxs, end, type, e, false ); -} - /* ================== SV_MoveNoEnts ================== */ -trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) +trace_t GAME_EXPORT SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) { moveclip_t clip; vec3_t trace_endpos; @@ -1444,7 +1412,7 @@ find the face where the traceline hit assume pTextureEntity is valid ================== */ -msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ) +msurface_t *GAME_EXPORT SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ) { matrix4x4 matrix; model_t *bmodel; @@ -1510,7 +1478,7 @@ trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ) VectorCopy( tossent->v.velocity, original_velocity ); VectorCopy( tossent->v.angles, original_angles ); VectorCopy( tossent->v.avelocity, original_avelocity ); - gravity = tossent->v.gravity * svgame.movevars.gravity * 0.05f; + gravity = tossent->v.gravity * sv_gravity.value * 0.05f; for( i = 0; i < 200; i++ ) { @@ -1540,24 +1508,18 @@ trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ) =============================================================================== */ -static vec3_t sv_pointColor; - /* ================= SV_RecursiveLightPoint ================= */ -static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end ) +static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end, vec3_t point_color ) { - float front, back, scale, frac; - int i, map, side, size; - float ds, dt, s, t; - int sample_size; - msurface_t *surf; - mtexinfo_t *tex; - mextrasurf_t *info; - color24 *lm; - vec3_t mid; + float front, back, frac; + int i, side; + vec3_t mid; + mnode_t *children[2]; + int numsurfaces, firstsurface; // didn't hit anything if( !node || node->contents < 0 ) @@ -1567,30 +1529,34 @@ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec front = PlaneDiff( start, node->plane ); back = PlaneDiff( end, node->plane ); + node_children( children, node, model ); + side = front < 0.0f; if(( back < 0.0f ) == side ) - return SV_RecursiveLightPoint( model, node->children[side], start, end ); + return SV_RecursiveLightPoint( model, children[side], start, end, point_color ); frac = front / ( front - back ); VectorLerp( start, frac, end, mid ); // co down front side - if( SV_RecursiveLightPoint( model, node->children[side], start, mid )) + if( SV_RecursiveLightPoint( model, children[side], start, mid, point_color )) return true; // hit something if(( back < 0.0f ) == side ) - return false;// didn't hit anything + return false; // didn't hit anything // check for impact on this node - surf = model->surfaces + node->firstsurface; - - for( i = 0; i < node->numsurfaces; i++, surf++ ) + numsurfaces = node_numsurfaces( node, model ); + firstsurface = node_firstsurface( node, model ); + for( i = 0; i < numsurfaces; i++ ) { - int smax, tmax; - - tex = surf->texinfo; - info = surf->info; + const msurface_t *surf = &model->surfaces[firstsurface + i]; + const mextrasurf_t *info = surf->info; + int smax, tmax, map, size; + int sample_size; + float ds, dt, s, t; + const color24 *lm; if( FBitSet( surf->flags, SURF_DRAWTILED )) continue; // no lightmaps @@ -1616,46 +1582,27 @@ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec ds /= sample_size; dt /= sample_size; - VectorClear( sv_pointColor ); + VectorClear( point_color ); lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); size = smax * tmax; for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) { - scale = sv.lightstyles[surf->styles[map]].value; + float scale = sv.lightstyles[surf->styles[map]].value; - sv_pointColor[0] += lm->r * scale; - sv_pointColor[1] += lm->g * scale; - sv_pointColor[2] += lm->b * scale; + point_color[0] += lm->r * scale; + point_color[1] += lm->g * scale; + point_color[2] += lm->b * scale; lm += size; // skip to next lightmap } + return true; } // go down back side - return SV_RecursiveLightPoint( model, node->children[!side], mid, end ); -} - -void SV_RunLightStyles( void ) -{ - int i, ofs; - lightstyle_t *ls; - float scale; - - scale = sv_lighting_modulate->value; - - // run lightstyles animation - for( i = 0, ls = sv.lightstyles; i < MAX_LIGHTSTYLES; i++, ls++ ) - { - ls->time += sv.frametime; - ofs = (ls->time * 10); - - if( ls->length == 0 ) ls->value = scale; // disable this light - else if( ls->length == 1 ) ls->value = ( ls->map[0] / 12.0f ) * scale; - else ls->value = ( ls->map[ofs % ls->length] / 12.0f ) * scale; - } + return SV_RecursiveLightPoint( model, children[!side], mid, end, point_color ); } /* @@ -1669,10 +1616,8 @@ void SV_SetLightStyle( int style, const char* s, float f ) { int j, k; - Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern )); + j = Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern )); sv.lightstyles[style].time = f; - - j = Q_strlen( s ); sv.lightstyles[style].length = j; for( k = 0; k < j; k++ ) @@ -1687,22 +1632,6 @@ void SV_SetLightStyle( int style, const char* s, float f ) MSG_WriteFloat( &sv.reliable_datagram, sv.lightstyles[style].time ); } -/* -================== -SV_GetLightStyle - -needs to get correct working SV_LightPoint -================== -*/ -const char *SV_GetLightStyle( int style ) -{ - if( style < 0 ) style = 0; - if( style >= MAX_LIGHTSTYLES ) - Host_Error( "SV_GetLightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); - - return sv.lightstyles[style].pattern; -} - /* ================== SV_LightForEntity @@ -1712,12 +1641,16 @@ grab the ambient lighting color for current point */ int SV_LightForEntity( edict_t *pEdict ) { - vec3_t start, end; + vec3_t point_color = { 1.0f, 1.0f, 1.0f }; + vec3_t start, end; + + if( !SV_IsValidEdict( pEdict )) + return -1; if( FBitSet( pEdict->v.effects, EF_FULLBRIGHT ) || !sv.worldmodel->lightdata ) return 255; - // player has more precision light level that come from client-side + // player has more precise light level that come from client-side if( FBitSet( pEdict->v.flags, FL_CLIENT )) return pEdict->v.light_level; @@ -1727,9 +1660,8 @@ int SV_LightForEntity( edict_t *pEdict ) if( FBitSet( pEdict->v.effects, EF_INVLIGHT )) end[2] = start[2] + world.size[2]; else end[2] = start[2] - world.size[2]; - VectorSet( sv_pointColor, 1.0f, 1.0f, 1.0f ); - SV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end ); + SV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end, point_color ); - return VectorAvg( sv_pointColor ); + return VectorAvg( point_color ); } diff --git a/engine/sprite.h b/engine/sprite.h index 1ff1d8b5..641403a6 100644 --- a/engine/sprite.h +++ b/engine/sprite.h @@ -16,6 +16,10 @@ #ifndef SPRITE_H #define SPRITE_H +#include "build.h" +#include STDINT_H +#include "synctype.h" + /* ============================================================================== @@ -31,16 +35,6 @@ SPRITE MODELS #define SPRITE_VERSION_HL 2 // Half-Life sprites #define SPRITE_VERSION_32 32 // Captain Obvious mode on -// must match definition in alias.h -#ifndef SYNCTYPE_T -#define SYNCTYPE_T -typedef enum -{ - ST_SYNC = 0, - ST_RAND -} synctype_t; -#endif - typedef enum { FRAME_SINGLE = 0, @@ -74,55 +68,69 @@ typedef enum // generic helper typedef struct { - int ident; // LittleLong 'ISPR' - int version; // current version 2 + int32_t ident; // LittleLong 'ISPR' + int32_t version; // current version 2 } dsprite_t; +STATIC_CHECK_SIZEOF( dsprite_t, 8, 8 ); + typedef struct { - int ident; // LittleLong 'ISPR' - int version; // current version 2 - int type; // camera align + int32_t ident; // LittleLong 'ISPR' + int32_t version; // current version 2 + int32_t type; // camera align float boundingradius; // quick face culling - int bounds[2]; // mins\maxs - int numframes; // including groups + int32_t bounds[2]; // mins\maxs + int32_t numframes; // including groups float beamlength; // ??? - synctype_t synctype; // animation synctype + uint32_t synctype; // animation synctype, was synctype_t } dsprite_q1_t; +STATIC_CHECK_SIZEOF( dsprite_q1_t, 36, 36 ); + typedef struct { - int ident; // LittleLong 'ISPR' - int version; // current version 2 - angletype_t type; // camera align - drawtype_t texFormat; // rendering mode - int boundingradius; // quick face culling - int bounds[2]; // mins\maxs - int numframes; // including groups - facetype_t facetype; // cullface (Xash3D ext) - synctype_t synctype; // animation synctype + int32_t ident; // LittleLong 'ISPR' + int32_t version; // current version 2 + uint32_t type; // camera align, was angletype_t + uint32_t texFormat; // rendering mode, was drawtype_t + int32_t boundingradius; // quick face culling + int32_t bounds[2]; // mins\maxs + int32_t numframes; // including groups + uint32_t facetype; // cullface (Xash3D ext), was facetype_t + uint32_t synctype; // animation synctype, was synctype_t } dsprite_hl_t; -typedef struct -{ - int origin[2]; - int width; - int height; -} dspriteframe_t; +STATIC_CHECK_SIZEOF( dsprite_hl_t, 40, 40 ); typedef struct { - int numframes; + int32_t origin[2]; + int32_t width; + int32_t height; +} dspriteframe_t; + +STATIC_CHECK_SIZEOF( dspriteframe_t, 16, 16 ); + +typedef struct +{ + int32_t numframes; } dspritegroup_t; +STATIC_CHECK_SIZEOF( dspritegroup_t, 4, 4 ); + typedef struct { float interval; } dspriteinterval_t; +STATIC_CHECK_SIZEOF( dspriteinterval_t, 4, 4 ); + typedef struct { - frametype_t type; + uint32_t type; // was frametype_t } dframetype_t; +STATIC_CHECK_SIZEOF( dframetype_t, 4, 4 ); + #endif//SPRITE_H diff --git a/engine/vgui_api.h b/engine/vgui_api.h index 2ce89226..e5a6c218 100644 --- a/engine/vgui_api.h +++ b/engine/vgui_api.h @@ -170,9 +170,9 @@ typedef struct vguiapi_s void (*SetupDrawingImage)( int *pColor ); void (*BindTexture)( int id ); void (*EnableTexture)( qboolean enable ); - void (*CreateTexture)( int id, int width, int height ); + void (*Reserved0)( int id, int width, int height ); void (*UploadTexture)( int id, const char *buffer, int width, int height ); - void (*UploadTextureBlock)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); + void (*Reserved1)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); void (*DrawQuad)( const vpoint_t *ul, const vpoint_t *lr ); void (*GetTextureSizes)( int *width, int *height ); int (*GenerateTexture)( void ); @@ -180,7 +180,7 @@ typedef struct vguiapi_s void (*CursorSelect)( VGUI_DefaultCursor cursor ); byte (*GetColor)( int i, int j ); qboolean (*IsInGame)( void ); - void (*Unused)( void ); + void (*EnableTextInput)( qboolean enable, qboolean force ); void (*GetCursorPos)( int *x, int *y ); int (*ProcessUtfChar)( int ch ); int (*GetClipboardText)( char *buffer, size_t bufferSize ); diff --git a/engine/wscript b/engine/wscript index eb64e7b4..e894a639 100644 --- a/engine/wscript +++ b/engine/wscript @@ -5,189 +5,293 @@ from waflib import Logs from waflib.extras import pthread import os +from copy import copy top = '.' +EXECINFO_TEST = '''#include +int main(int argc, char **argv) +{ + backtrace(0, 0); + backtrace_symbols(0, 0); +}''' + +FFMPEG_CHECK_FRAGMENT=''' +#include +#include +#include +#include +#include +int main(int argc, char **argv) +{ + swresample_version(); + avformat_version(); + avcodec_version(); + swscale_version(); + avutil_version(); + return 0; +} +''' + def options(opt): grp = opt.add_option_group('Engine options') - grp.add_option('--enable-stdin-input', action = 'store_true', dest = 'USE_SELECT', default = False, - help = 'enable console input from stdin (always enabled for dedicated) [default: %default]') - grp.add_option('--enable-fbdev', action = 'store_true', dest = 'FBDEV_SW', default = False, help = 'build fbdev-only software-only engine') - grp.add_option('--disable-async-resolve', action = 'store_true', dest = 'NO_ASYNC_RESOLVE', default = False, - help = 'disable multithreaded operations(asynchronous name resolution)') - grp.add_option('--enable-custom-swap', action = 'store_true', dest = 'CUSTOM_SWAP', default = False, help = 'enable custom swap allocator. For devices with no swap support') - grp.add_option('--enable-legacy-sdl', action = 'store_true', dest = 'SDL12', default = False, - help = 'enable using SDL1.2 instead of SDL2(not recommended) [default: %default]') + grp.add_option('--use-sdl1', action = 'store_true', dest = 'SDL12', default = False, + help = 'enable using SDL1.2 instead of SDL2 (not supported or recommended) [default: %(default)s]') grp.add_option('--enable-static-binary', action = 'store_true', dest = 'STATIC', default = False, - help = 'build static binary(not recommended, --single-binary required) [default: %default]') + help = 'build static binary(not recommended, --single-binary required) [default: %(default)s]') grp.add_option('--enable-engine-tests', action = 'store_true', dest = 'ENGINE_TESTS', default = False, - help = 'embed tests into the engine, jump into them by -runtests command line switch [default: %default]') + help = 'embed tests into the engine, jump into them by -runtests command line switch [default: %(default)s]') grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, - help = 'add LLVM libFuzzer [default: %default]' ) + help = 'add LLVM libFuzzer [default: %(default)s]' ) + + grp.add_option('--enable-ffmpeg', action = 'store_true', dest = 'FFMPEG', default = False, + help = 'enable ffmpeg based movie playback [default: %(default)s') + + grp.add_option('--enable-ffmpeg-dlopen', action = 'store_true', dest = 'FFMPEG_DLOPEN', default = False, + help = 'load ffmpeg libraries in runtime [default: %(default)s]') opt.load('sdl2') -def configure(conf): - # check for dedicated server build - if conf.options.DEDICATED: - if conf.env.DEST_OS == 'linux': - conf.check_cc(lib='rt') - conf.define('XASH_DEDICATED', 1) - elif conf.env.DEST_OS == 'dos': - conf.options.STATIC = True - conf.options.NO_ASYNC_RESOLVE = True - if not conf.check_cc( fragment='int main(){ int i = socket();}', lib = 'wattcpwl', mandatory=False ): - conf.define('XASH_NO_NETWORK',1) - elif conf.env.DEST_OS == 'android': # Android doesn't need SDL2 - for i in ['log']: - conf.check_cc(lib = i) - elif conf.options.FBDEV_SW: - # unused, XASH_LINUX without XASH_SDL gives fbdev & alsa support - # conf.define('XASH_FBDEV', 1) - conf.check_cc( lib = 'asound' ) - conf.check_cc( lib = 'rt' ) - elif conf.options.SDL12: - conf.define('XASH_SDL', 12) - conf.check_cfg(package='sdl', args='--cflags --libs', uselib_store='SDL2' ) - if conf.env.DEST_OS == 'linux': - conf.check_cc( lib='rt' ) - conf.env.HAVE_SDL2 = True - else: - conf.load('sdl2') - if not conf.env.HAVE_SDL2: - conf.fatal('SDL2 not available! If you want to build dedicated server, specify --dedicated') - conf.define('XASH_SDL', 2) +def find_sdl(conf): + err_msg = '%s not available! If you want to build dedicated server, specify --dedicated' + if conf.env.DEST_OS in ['nswitch', 'psvita']: + err_msg = '%s not available!' - if conf.env.DEST_OS == 'haiku': + conf.load('sdl2') + if conf.options.SDL3: + if not conf.env.HAVE_SDL3: + conf.fatal(err_msg % 'SDL3') + conf.env.XASH_SDL = 3 + else: + if not conf.env.HAVE_SDL2: + conf.fatal(err_msg % 'SDL2') + conf.env.XASH_SDL = 2 + +def configure(conf): + have_async_resolve = True + + # Common dependencies, will be linked to both dedicated server and client + if conf.env.DEST_OS == 'linux': + conf.check_cc(lib='rt') + if conf.env.MAGX: + conf.define('XASH_CRASHHANDLER', 0) + elif conf.env.DEST_OS == 'haiku': conf.env.LIB_HAIKU = ['network'] conf.env.LIBPATH_HAIKU = ['/boot/system/lib'] - - if conf.options.USE_SELECT == None: - conf.options.USE_SELECT = conf.options.DEDICATED - - if conf.options.STATIC: - conf.env.STATIC = True - conf.define('XASH_NO_LIBDL',1) - - if not conf.env.DEST_OS in ['win32', 'android'] and not conf.options.NO_ASYNC_RESOLVE: - conf.check_pthreads() - - if hasattr(conf.options, 'DLLEMU'): - conf.define_cond('XASH_DLL_LOADER', conf.options.DLLEMU) - - if conf.env.MAGX: - conf.define('XASH_CRASHHANDLER', 0) - - conf.env.ENGINE_TESTS = conf.options.ENGINE_TESTS + elif conf.env.DEST_OS == 'wasi': + have_async_resolve = False + conf.env.CFLAGS += ['-mllvm', '-wasm-enable-sjlj'] + elif conf.env.DEST_OS == 'emscripten': + have_async_resolve = False + elif conf.env.DEST_OS == 'sunos': + conf.check_cc(lib='socket') + elif conf.env.DEST_OS == 'dos': + conf.options.STATIC = True + have_async_resolve = False if conf.options.ENGINE_FUZZ: conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link') conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') - conf.define_cond('XASH_ENGINE_TESTS', conf.env.ENGINE_TESTS) + # Client-only dependencies + if conf.env.CLIENT: + if conf.env.DEST_OS in ['nswitch', 'psvita']: + extra_libs = ['-lm'] + if conf.env.DEST_OS == 'psvita': + extra_libs = ['-lstdc++', '-lm', '-lSceShaccCgExt', '-lSceShaccCg_stub', '-ltaihen_stub', '-lSceKernelDmacMgr_stub'] + + conf.env.append_unique('CXXFLAGS', ['-Wl,--no-undefined']) + conf.env.append_unique('CFLAGS', ['-Wl,--no-undefined']) + + conf.env.LDFLAGS += extra_libs + find_sdl(conf) + for lib in extra_libs: conf.env.LDFLAGS.remove(lib) + + if conf.env.DEST_OS == 'nswitch': # remove libstdc++ linking from SDL2, we link it later + conf.env.LIB_SDL2.remove('stdc++') + elif conf.env.DEST_OS == 'linux' and conf.options.FBDEV_SW: + conf.check_cc(lib='asound') + elif conf.options.SDL12: # TODO: move to sdl2.py + conf.check_cfg(package='sdl', args='--cflags --libs', uselib_store='SDL1') + conf.env.XASH_SDL = 1 + have_async_resolve = False + conf.env.HAVE_SDL1 = True + else: + find_sdl(conf) + + + if conf.options.STATIC: + conf.env.STATIC = True + conf.define('XASH_NO_LIBDL', 1) + + if not conf.env.DEST_OS in ['win32', 'android'] and have_async_resolve: + conf.check_pthreads(mode='c') + + if hasattr(conf.options, 'DLLEMU'): + conf.define_cond('XASH_DLL_LOADER', conf.options.DLLEMU) + + conf.check_cc(fragment=EXECINFO_TEST, msg='Checking for glibc backtrace()', mandatory=False, define_name='HAVE_EXECINFO') + + conf.define('ENGINE_DLL', 1) + conf.define_cond('XASH_ENGINE_TESTS', conf.options.ENGINE_TESTS) + + if conf.options.FFMPEG: + pkgconf_args = '--cflags' if conf.options.FFMPEG_DLOPEN else '--cflags --libs' + features = 'c' if conf.options.FFMPEG_DLOPEN else 'c cprogram' + + # add ffmpeg libraries into single uselib package + conf.check_cfg(package='libswresample', uselib_store='SWRESAMPLE', args=pkgconf_args) + conf.check_cfg(package='libavformat', uselib_store='AVFORMAT', args=pkgconf_args) + conf.check_cfg(package='libavcodec', uselib_store='AVCODEC', args=pkgconf_args) + conf.check_cfg(package='libswscale', uselib_store='SWSCALE', args=pkgconf_args) + conf.check_cfg(package='libavutil', uselib_store='AVUTIL', args=pkgconf_args) + + # validate ffmpeg libs installation + conf.check(features=features, fragment=FFMPEG_CHECK_FRAGMENT, use='SWRESAMPLE AVCODEC AVFORMAT AVUTIL SWSCALE', msg='Checking for ffmpeg sanity') + + conf.env.FFMPEG = True + conf.define('HAVE_FFMPEG', True) + conf.define_cond('XASH_FFMPEG_DLOPEN', conf.options.FFMPEG_DLOPEN) + conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING) conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) - conf.define_cond('SINGLE_BINARY', conf.env.SINGLE_BINARY) - conf.define_cond('XASH_NO_ASYNC_NS_RESOLVE', conf.options.NO_ASYNC_RESOLVE) - conf.define_cond('XASH_USE_SELECT', conf.options.USE_SELECT or conf.options.DEDICATED) - conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT) - conf.define_cond('XASH_64BIT', conf.env.DEST_SIZEOF_VOID_P != 4) - conf.define_cond('DBGHELP', conf.env.DEST_OS == 'win32') + conf.define_cond('XASH_NO_ASYNC_NS_RESOLVE', not have_async_resolve) conf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1 + for refdll in conf.refdlls: + refdll.register_define(conf) + def build(bld): # public includes for renderers and utils use bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes') - is_cxx_link = False - libs = [ 'engine_includes', 'public', 'dllemu' ] + libs = ['engine_includes', 'public', 'dllemu', 'werror', 'backtrace'] + includes = ['server', 'client', 'client/vgui', 'common/soundlib', 'platform'] # basic build: dedicated only - source = bld.path.ant_glob([ - 'common/*.c', - 'common/imagelib/*.c', - 'common/soundlib/*.c', - 'common/soundlib/libmpg/*.c', - 'server/*.c']) + source = bld.path.ant_glob(['common/*.c', 'common/imagelib/*.c', 'common/soundlib/*.c', 'server/*.c']) - if bld.env.ENGINE_TESTS: - source += bld.path.ant_glob(['tests/*.c']) + # include platform-specific sources, this shall not fail if directory doesn't exist + source += bld.path.ant_glob('platform/%s/*.c' % bld.env.DEST_OS) + + # Android _is_ Linux based, after all + if bld.env.DEST_OS == 'android': + source += bld.path.ant_glob('platform/linux/*.c') + + # include common POSIX conformant code + if bld.env.DEST_OS not in ['win32', 'dos']: + source += bld.path.ant_glob('platform/posix/*.c') + + # include sources for optional features + if bld.get_define('XASH_CUSTOM_SWAP'): + source += ['platform/misc/kmalloc.c', 'platform/misc/sbrk.c'] + + if bld.get_define('XASH_STATIC_LIBS'): + source += ['platform/misc/lib_static.c'] if bld.env.DEST_OS == 'win32': - libs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32' ] - source += bld.path.ant_glob(['platform/win32/*.c']) - elif bld.env.DEST_OS != 'dos': #posix - libs += [ 'M', 'RT', 'PTHREAD', 'ASOUND'] + libs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32'] + elif bld.env.DEST_OS == 'nswitch': + libs += ['SOLDER'] + # HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting + # we can't do this right away because std::filesystem will complain about not having pathconf(), + # which we have defined in sys_nswitch.c + bld.env.LDFLAGS += ['-v', '-Wl,--whole-archive', '-lstdc++', '-Wl,--no-whole-archive', '-lm'] + elif bld.env.DEST_OS == 'psvita': + libs += ['VRTLD'] + # HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting + # also link in all the funky dependencies that aren't in SDL2's LDFLAGS + bld.env.LDFLAGS += ['-Wl,--whole-archive', '-lstdc++', '-lpthread', '-Wl,--no-whole-archive', + '-lm', '-lSceShaccCgExt', '-lkubridge_stub', '-ltaihen_stub', '-lSceShaccCg_stub', '-lSceKernelModulemgr_stub', '-lSceSblSsMgr_stub', '-lSceVshBridge_stub', '-lSceKernelDmacMgr_stub', '-lSceLibKernel_stub', + ] + else: # POSIX + libs += ['M', 'RT', 'PTHREAD', 'ASOUND', 'HAIKU', 'MAGX', 'LOG', 'SOCKET'] if not bld.env.STATIC: libs += ['DL'] - source += bld.path.ant_glob(['platform/posix/*.c']) - if bld.env.DEST_OS == 'linux': - source += bld.path.ant_glob(['platform/linux/*.c']) + if bld.env.SERVER: + # TODO: avoid possible name collision when client is built without launcher + # but dedicated server is enabled. They're both called 'xash' in this case + bld.program( + source = copy(source), + target = 'xash', + use = copy(libs), + includes = includes, + defines = 'XASH_ENABLE_MAIN=1 XASH_DEDICATED=1', + rpath = bld.env.DEFAULT_RPATH, + install_path = bld.env.BINDIR, + ) - if bld.env.DEST_OS == 'dos': - source += bld.path.ant_glob(['platform/dos/*.c']) - source += bld.path.ant_glob(['platform/stub/s_stub.c']) + if bld.env.CLIENT: + defines = [] + if bld.env.XASH_SDL: + libs += ['SDL%d' % bld.env.XASH_SDL] + source += bld.path.ant_glob(['platform/sdl%d/*.c' % bld.env.XASH_SDL]) + defines += ['XASH_SDL=%d' % bld.env.XASH_SDL] - if bld.env.DEST_OS == 'haiku': - libs.append('HAIKU') + if bld.get_define('HAVE_FFMPEG'): + libs.append('SWRESAMPLE') + libs.append('AVCODEC') + libs.append('AVFORMAT') + libs.append('AVUTIL') + libs.append('SWSCALE') - if bld.get_define('XASH_CUSTOM_SWAP'): - source += bld.path.ant_glob(['platform/misc/kmalloc.c', 'platform/misc/sbrk.c']) + # add client files + if not bld.env.DEDICATED: + source += bld.path.ant_glob('client/**/*.c') + libs += ['bzip2', 'MultiEmulator', 'opus', 'opusfile', 'vorbis', 'vorbisfile'] - if bld.env.STATIC_LINKING: - source += bld.path.ant_glob(['platform/misc/lib_static.c']) - is_cxx_link = True + # Android port is linked differently + if bld.env.DEST_OS == 'android': + f = bld.shlib + install_path = bld.env.LIBDIR + defines += ['XASH_ENABLE_MAIN=1', 'XASH_SDLMAIN=1'] + elif not bld.env.LAUNCHER: + f = bld.program + install_path = bld.env.BINDIR + defines += ['XASH_ENABLE_MAIN=1'] + else: + f = bld.shlib + install_path = bld.env.LIBDIR - if bld.env.HAVE_SDL2: - libs.append('SDL2') - source += bld.path.ant_glob(['platform/sdl/*.c']) + if bld.env.DEST_OS in ['nswitch', 'psvita', 'emscripten']: + install_path = None - if bld.env.MAGX: - libs.append('MAGX') - source += bld.path.ant_glob(['platform/magx/*.cpp']) - is_cxx_link = True + linkflags = [] - if bld.env.DEST_OS == 'android': - libs += ['LOG'] - source += bld.path.ant_glob(['platform/android/*.cpp', 'platform/android/*.c', 'platform/linux/*.c']) + if bld.env.DEST_OS == 'emscripten': + linkflags = ['-sINITIAL_MEMORY=134217728', '-sALLOW_MEMORY_GROWTH=1', '-sSTACK_SIZE=16777216'] - # add client files - if not bld.env.DEDICATED: - source += bld.path.ant_glob([ - 'client/*.c', - 'client/vgui/*.c', - 'client/avi/*.c']) - libs += ['opus'] + linkflags += ['-sASYNCIFY=1'] - includes = ['server', 'client', 'client/vgui' ] + node = bld.srcnode.find_node('rodir') + if not node: + bld.fatal('Put game data into rodir folder in source tree root') + linkflags += ['--preload-file', '%s@/rodir' % node.path_from(bld.bldnode)] - if bld.env.SINGLE_BINARY: - install_path = bld.env.BINDIR - program = 'cxxprogram' if is_cxx_link else 'cprogram' - if bld.env.STATIC: - program += '_static' - features = ['c', program] - else: - install_path = bld.env.LIBDIR - features = ['c', 'cxxshlib' if is_cxx_link else 'cshlib'] - - bld(source = source, - target = 'xash', - features = features, - includes = includes, - use = libs, - install_path = install_path, - subsystem = bld.env.MSVC_SUBSYSTEM, - rpath = '$ORIGIN' - ) + f(source = source, + target = 'xash', + includes = includes, + features = 'cxx c', + use = libs, + defines = defines, + install_path = install_path, + special_install_path = bld.env.BINDIR, + nacp = 'platform/nswitch/xash3d-fwgs.nacp', + icon = 'platform/nswitch/icon.jpg', + sce_sys = 'platform/psvita/sce_sys', + title_id = 'XASH10000', + app_name = 'xash3d-fwgs', + rpath = bld.env.DEFAULT_RPATH, + linkflags = linkflags, + ) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index 7bfee6ba..6b462f70 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -1,6 +1,6 @@ /* VFileSystem009.h - C++ interface for filesystem_stdio -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2022-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,9 +21,11 @@ GNU General Public License for more details. #include "filesystem.h" #include "filesystem_internal.h" #include "VFileSystem009.h" +#include "common/com_strings.h" #if __cplusplus < 201103L #define override +#define nullptr NULL #endif // GoldSrc Directories and ID @@ -35,48 +37,62 @@ GNU General Public License for more details. // PLATFORM platform // CONFIG platform/config -static inline qboolean IsIdGamedir( const char *id ) +// This is a macro because pointers returned by alloca +// shouldn't leave current scope +#define FixupPath( var, str ) \ + const size_t var ## _size = Q_strlen(( str )) + 1; \ + char * const var = static_cast( alloca( var ## _size )); \ + CopyAndFixSlashes( var, ( str ), var ## _size ) + +static inline bool IsIdGamedir( const char *id ) { return !Q_strcmp( id, "GAME" ) || !Q_strcmp( id, "GAMECONFIG" ) || !Q_strcmp( id, "GAMEDOWNLOAD" ); } -static inline const char* IdToDir( const char *id ) +static inline const char *IdToDir( char *dir, size_t size, const char *id ) { if( !Q_strcmp( id, "GAME" )) return GI->gamefolder; - else if( !Q_strcmp( id, "GAMEDOWNLOAD" )) - return va( "%s/downloaded", GI->gamefolder ); - else if( !Q_strcmp( id, "GAMECONFIG" )) - return fs_writedir; // full path here so it's totally our write allowed directory - else if( !Q_strcmp( id, "PLATFORM" )) + + if( !Q_strcmp( id, "GAMEDOWNLOAD" )) + { + Q_snprintf( dir, size, "%s/" DEFAULT_DOWNLOADED_DIRECTORY , GI->gamefolder ); + return dir; + } + + if( !Q_strcmp( id, "GAMECONFIG" )) + return fs_writepath->filename; // full path here so it's totally our write allowed directory + + if( !Q_strcmp( id, "PLATFORM" )) return "platform"; // stub - else if( !Q_strcmp( id, "CONFIG" )) + + if( !Q_strcmp( id, "CONFIG" )) return "platform/config"; // stub - else // ROOT || BASE - return fs_rootdir; // give at least root directory + + // ROOT || BASE + return fs_rootdir; // give at least root directory } -static inline void CopyAndFixSlashes( char *p, const char *in ) +static inline void CopyAndFixSlashes( char *p, const char *in, size_t size ) { - Q_strcpy( p, in ); + Q_strncpy( p, in, size ); COM_FixSlashes( p ); } -class CXashFS : public IVFileSystem009 +class CXashFS : public IFileSystem { private: class CSearchState { public: CSearchState( CSearchState **head, search_t *search ) : - next( *head ), search( search ), index( 0 ) + next( *head ), + search( search ), + index( 0 ), + handle( *head ? ( *head )->handle + 1 : 0 ) { - if( *head ) - handle = (*head)->handle + 1; - else handle = 0; - *head = this; } ~CSearchState() @@ -97,17 +113,15 @@ private: for( CSearchState *state = searchHead; state; state = state->next ) { if( state->handle == handle ) - { return state; - } } Con_DPrintf( "Can't find search state by handle %d\n", handle ); - return NULL; + return nullptr; } public: - CXashFS() : searchHead( NULL ) + CXashFS() : searchHead( nullptr ) { } @@ -118,17 +132,13 @@ public: void AddSearchPath( const char *path, const char *id ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( p, path ); FS_AddGameDirectory( p, FS_CUSTOM_PATH ); } void AddSearchPathNoWrite( const char *path, const char *id ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( p, path ); FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); } @@ -140,120 +150,134 @@ public: void RemoveFile( const char *path, const char *id ) override { - FS_Delete( path ); // FS_Delete is aware of slashes + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; + + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); + FS_Delete( fullpath ); // FS_Delete is aware of slashes } void CreateDirHierarchy( const char *path, const char *id ) override { - FS_CreatePath( va( "%s/%s", IdToDir( id ), path )); // FS_CreatePath is aware of slashes + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; + + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); + FS_CreatePath( fullpath ); // FS_CreatePath is aware of slashes } bool FileExists( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( p, path ); return FS_FileExists( p, false ); } bool IsDirectory( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( p, path ); return FS_SysFolderExists( p ); } FileHandle_t Open( const char *path, const char *mode, const char *id ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); + file_t *fd; - file_t *fd = FS_Open( p, mode, IsIdGamedir( id ) ); + FixupPath( p, path ); + fd = FS_Open( p, mode, IsIdGamedir( id )); return fd; } void Close( FileHandle_t handle ) override { - FS_Close( (file_t *)handle ); + FS_Close( static_cast( handle )); } void Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override { int whence_ = SEEK_SET; + switch( whence ) { - case FILESYSTEM_SEEK_HEAD: whence_ = SEEK_SET; break; - case FILESYSTEM_SEEK_CURRENT: whence_ = SEEK_CUR; break; - case FILESYSTEM_SEEK_TAIL: whence_ = SEEK_END; break; + case FILESYSTEM_SEEK_HEAD: + whence_ = SEEK_SET; + break; + case FILESYSTEM_SEEK_CURRENT: + whence_ = SEEK_CUR; + break; + case FILESYSTEM_SEEK_TAIL: + whence_ = SEEK_END; + break; } - FS_Seek( (file_t *)handle, offset, whence_ ); + FS_Seek( static_cast( handle ), offset, whence_ ); } unsigned int Tell( FileHandle_t handle ) override { - return FS_Tell( (file_t *)handle ); + return FS_Tell( static_cast( handle )); } unsigned int Size( FileHandle_t handle ) override { - file_t *fd = (file_t *)handle; - return fd->real_length; + return static_cast( handle )->real_length; } unsigned int Size( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); + FixupPath( p, path ); return FS_FileSize( p, false ); } long int GetFileTime( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); + FixupPath( p, path ); + return FS_FileTime( p, false ); + } + + long int GetFileModificationTime( const char *path ) override + { + // TODO: properly reverse-engineer this + FixupPath( p, path ); return FS_FileTime( p, false ); } void FileTimeToString( char *p, int size, long int time ) override { - time_t curtime = time; + const time_t curtime = time; char *buf = ctime( &curtime ); + Q_strncpy( p, buf, size ); } bool IsOk( FileHandle_t handle ) override { - return !FS_Eof( (file_t *)handle ); + return !FS_Eof( static_cast( handle )); } void Flush( FileHandle_t handle ) override { - FS_Flush( (file_t *)handle ); + FS_Flush( static_cast( handle )); } bool EndOfFile( FileHandle_t handle ) override { - return FS_Eof( (file_t *)handle ); + return FS_Eof( static_cast( handle )); } int Read( void *buf, int size, FileHandle_t handle ) override { - return FS_Read( (file_t *)handle, buf, size ); + return FS_Read( static_cast( handle ), buf, size ); } int Write( const void *buf, int size, FileHandle_t handle ) override { - return FS_Write( (file_t *)handle, buf, size ); + return FS_Write( static_cast( handle ), buf, size ); } char *ReadLine( char *buf, int size, FileHandle_t handle ) override { - int c = FS_Gets( (file_t *)handle, (byte*)buf, size ); + const int c = FS_Gets( static_cast( handle ), buf, size ); - return c >= 0 ? buf : NULL; + return c >= 0 ? buf : nullptr; } int FPrintf( FileHandle_t handle, char *fmt, ... ) override @@ -262,41 +286,47 @@ public: int ret; va_start( ap, fmt ); - ret = FS_VPrintf( (file_t *)handle, fmt, ap ); + ret = FS_VPrintf( static_cast( handle ), fmt, ap ); va_end( ap ); return ret; } - void * GetReadBuffer(FileHandle_t, int *size, bool) override + void *GetReadBuffer( FileHandle_t, int *size, bool ) override { // deprecated by Valve *size = 0; - return NULL; + return nullptr; } - void ReleaseReadBuffer(FileHandle_t, void *) override + void ReleaseReadBuffer( FileHandle_t, void * ) override { // deprecated by Valve return; } - const char *FindFirst(const char *pattern, FileFindHandle_t *handle, const char *id) override + const char *FindFirst( const char *pattern, FileFindHandle_t *handle, const char *id ) override { - if( !handle || !pattern ) - return NULL; + CSearchState *state; + search_t *search; - char *p = (char *)alloca( Q_strlen( pattern ) + 1 ); - CopyAndFixSlashes( p, pattern ); - search_t *search = FS_Search( p, true, IsIdGamedir( id )); + if( !handle || !pattern ) + return nullptr; + + FixupPath( p, pattern ); + search = FS_Search( p, true, IsIdGamedir( id )); if( !search ) - return NULL; + return nullptr; - CSearchState *state = new CSearchState( &searchHead, search ); + state = new CSearchState( &searchHead, search ); + if( !state ) + { + Mem_Free( search ); + return nullptr; + } *handle = state->handle; - return state->search->filenames[0]; } @@ -304,10 +334,11 @@ public: { CSearchState *state = GetSearchStateByHandle( handle ); - if( !state ) return NULL; + if( !state ) + return nullptr; if( state->index + 1 >= state->search->numfilenames ) - return NULL; + return nullptr; return state->search->filenames[++state->index]; } @@ -327,32 +358,35 @@ public: void FindClose( FileFindHandle_t handle ) override { - for( CSearchState *state = searchHead, **prev = NULL; - state; - *prev = state, state = state->next ) + CSearchState *prev; + CSearchState *i; + + for( prev = nullptr, i = searchHead; + i != nullptr && i->handle != handle; + prev = i, i = i->next ); + + if( i == nullptr ) { - if( state->handle == handle ) - { - if( prev ) - (*prev)->next = state->next; - else searchHead = state->next; - - delete state; - - return; - } + Con_DPrintf( "%s: Can't find search state by handle %d\n", __func__, handle ); + return; } - Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle ); - return; + if( prev != nullptr ) + prev->next = i->next; + else + searchHead = i->next; + + delete i; } - const char * GetLocalPath( const char *name, char *buf, int size ) override + const char *GetLocalPath( const char *name, char *buf, int size ) override { - if( !name ) return NULL; + const char *fullpath; - char *p = (char *)alloca( Q_strlen( name ) + 1 ); - CopyAndFixSlashes( p, name ); + if( !name ) + return nullptr; + + FixupPath( p, name ); #if !XASH_WIN32 if( p[0] == '/' ) @@ -365,10 +399,9 @@ public: return buf; } - - const char *fullpath = FS_GetDiskPath( p, false ); + fullpath = FS_GetDiskPath( p, false ); if( !fullpath ) - return NULL; + return nullptr; Q_strncpy( buf, fullpath, size ); return buf; @@ -377,9 +410,12 @@ public: char *ParseFile( char *buf, char *token, bool *quoted ) override { qboolean qquoted; + char *p; - char *p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, NULL, &qquoted ); - if( quoted ) *quoted = qquoted; + p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, nullptr, &qquoted ); + + if( quoted ) + *quoted = qquoted; return p; } @@ -392,30 +428,14 @@ public: return false; } - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - searchpath_t *sp; + FixupPath( p, path ); - for( sp = fs_searchpaths; sp; sp = sp->next ) - { - size_t splen = Q_strlen( sp->filename ); - - if( !Q_strnicmp( sp->filename, p, splen )) - { - Q_strcpy( out, p + splen + 1 ); - return true; - } - } - - Q_strcpy( out, p ); - return false; + return FS_FullPathToRelativePath( out, p, 512 ); } bool GetCurrentDirectory( char *p, int size ) override { - Q_strncpy( p, fs_rootdir, size ); - - return true; + return FS_GetRootDirectory( p, size ); } void PrintOpenedFiles() override @@ -424,13 +444,13 @@ public: return; } - void SetWarningFunc(void (*)(const char *, ...)) override + void SetWarningFunc( void (*)( const char *, ... )) override { // TODO: return; } - void SetWarningLevel(FileWarningLevel_t) override + void SetWarningLevel( FileWarningLevel_t ) override { // TODO: return; @@ -442,23 +462,24 @@ public: return 0; } - void GetInterfaceVersion(char *p, int size) override + void GetInterfaceVersion( char *p, int size ) override { Q_strncpy( p, "Stdio", size ); } bool AddPackFile( const char *path, const char *id ) override { - char *p = va( "%s/%s", IdToDir( id ), path ); - CopyAndFixSlashes( p, path ); + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; - return !!FS_AddPak_Fullpath( p, NULL, FS_CUSTOM_PATH ); + IdToDir( dir, sizeof( dir ), id ); + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", dir, path ); + COM_FixSlashes( fullpath ); + return FS_MountArchive_Fullpath( fullpath, FS_CUSTOM_PATH ) != NULL; } FileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); + FixupPath( p, path ); return FS_OpenReadFile( p, mode, IsIdGamedir( id )); } @@ -466,44 +487,52 @@ public: // stubs void Mount() override {} void Unmount() override {} - void GetLocalCopy(const char *) override {} - void LogLevelLoadStarted(const char *) override {} - void LogLevelLoadFinished(const char *) override {} - void CancelWaitForResources(WaitForResourcesHandle_t) override {} - int HintResourceNeed(const char *, int) override { return 0; } - WaitForResourcesHandle_t WaitForResources(const char *) override { return 0; } + void GetLocalCopy( const char * ) override {} + void LogLevelLoadStarted( const char * ) override {} + void LogLevelLoadFinished( const char * ) override {} + void CancelWaitForResources( WaitForResourcesHandle_t ) override {} + int HintResourceNeed( const char *, int ) override { return 0; } + WaitForResourcesHandle_t WaitForResources( const char * ) override { return 0; } int PauseResourcePreloading() override { return 0; } int ResumeResourcePreloading() override { return 0; } - bool IsAppReadyForOfflinePlay(int) override { return true; } - bool IsFileImmediatelyAvailable(const char *) override { return true; } - bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *pProgress, bool *pOverride) override + bool IsAppReadyForOfflinePlay( int ) override { return true; } + bool IsFileImmediatelyAvailable( const char * ) override { return true; } + bool GetWaitForResourcesProgress( WaitForResourcesHandle_t, float *pProgress, bool *pOverride ) override { - if( pProgress ) *pProgress = 0; - if( pOverride ) *pOverride = true; + if( pProgress ) + *pProgress = 0; + + if( pOverride ) + *pOverride = true; + return false; } } g_VFileSystem009; extern "C" void EXPORT *CreateInterface( const char *interface, int *retval ) { - if( !Q_strcmp( interface, "VFileSystem009" )) + if( !Q_strcmp( interface, FILESYSTEM_INTERFACE_VERSION )) { - if( retval ) *retval = 0; + if( retval ) + *retval = 0; + return &g_VFileSystem009; } if( !Q_strcmp( interface, FS_API_CREATEINTERFACE_TAG )) { - // return a copy, to disallow overriding - static fs_api_t copy = { 0 }; + static fs_api_t copy; // return a copy, to disallow overriding - if( !copy.InitStdio ) - memcpy( ©, &g_api, sizeof( copy )); + copy = g_api; + + if( retval ) + *retval = 0; - if( retval ) *retval = 0; return © } - if( retval ) *retval = 1; - return NULL; + if( retval ) + *retval = 1; + + return nullptr; } diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h index a0b73123..f8e0dab5 100644 --- a/filesystem/VFileSystem009.h +++ b/filesystem/VFileSystem009.h @@ -1,6 +1,6 @@ /* VFileSystem009.h - C++ interface for filesystem_stdio -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2022-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,8 +40,7 @@ public: virtual ~IBaseInterface() {} }; -class IVFileSystem009 : public IBaseInterface -{ +class IFileSystem : public IBaseInterface { public: virtual void Mount() = 0; /* linkage=_ZN11IFileSystem5MountEv */ @@ -148,6 +147,8 @@ public: virtual FileHandle_t OpenFromCacheForRead(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20OpenFromCacheForReadEPKcS1_S1_ */ virtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */ + + virtual long int GetFileModificationTime(const char *) = 0; /* linkage=_ZN11IFileSystem23GetFileModificationTimeEPKc */ }; #endif // VFILESYSTEM009_H diff --git a/filesystem/android.c b/filesystem/android.c new file mode 100644 index 00000000..c9ebca99 --- /dev/null +++ b/filesystem/android.c @@ -0,0 +1,346 @@ +/* +android.c - android support for filesystem +Copyright (C) 2022 Velaron + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "port.h" + +#if XASH_ANDROID + +#include +#include +#include +#include +#include +#include +#include STDINT_H +#include "filesystem_internal.h" +#include "crtlib.h" +#include "xash3d_mathlib.h" +#include "common/com_strings.h" + +#include +#include +#include +#include + +struct android_assets_s +{ + string package_name; + qboolean engine; + AAssetManager *asset_manager; + AAssetDir *dir; +}; + +struct jni_methods_s +{ + JNIEnv *env; + jobject activity; + jclass activity_class; + jmethodID getPackageName; + jmethodID getCallingPackage; + jmethodID getAssetsList; + jmethodID getAssets; +} jni; + +static void Android_GetAssetManager( android_assets_t *assets ) +{ + jobject assetManager; + + assetManager = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAssets, assets->engine ); + + if( assetManager ) + assets->asset_manager = AAssetManager_fromJava( jni.env, assetManager ); + else if( assets->engine ) + Con_Reportf( S_WARN "Couldn't add engine assets!" ); +} + +static const char *Android_GetPackageName( qboolean engine ) +{ + static string pkg; + jstring resultJNIStr; + const char *resultCStr; + + resultJNIStr = (*jni.env)->CallObjectMethod( jni.env, jni.activity, engine ? jni.getPackageName : jni.getCallingPackage ); + + if( !resultJNIStr ) + return NULL; + + resultCStr = (*jni.env)->GetStringUTFChars( jni.env, resultJNIStr, NULL ); + Q_strncpy( pkg, resultCStr, sizeof( pkg )); + (*jni.env)->ReleaseStringUTFChars( jni.env, resultJNIStr, resultCStr ); + + return pkg; +} + +static void Android_ListDirectory( stringlist_t *list, const char *path, qboolean engine ) +{ + jstring JStr = (*jni.env)->NewStringUTF( jni.env, path ); + jobjectArray JNIArray = (*jni.env)->CallObjectMethod( jni.env, jni.activity, jni.getAssetsList, engine, JStr ); + int JNIArraySize = (*jni.env)->GetArrayLength( jni.env, JNIArray ); + + for( int i = 0; i < JNIArraySize; i++ ) + { + jstring JNIStr = (*jni.env)->GetObjectArrayElement( jni.env, JNIArray, i ); + const char *CStr = (*jni.env)->GetStringUTFChars( jni.env, JNIStr, NULL ); + + stringlistappend( list, (char *)CStr ); + (*jni.env)->ReleaseStringUTFChars( jni.env, JNIStr, CStr ); + } +} + +static void FS_CloseAndroidAssets( android_assets_t *assets ) +{ + if( assets->dir ) + AAssetDir_close( assets->dir ); + + Mem_Free( assets ); +} + +static android_assets_t *FS_LoadAndroidAssets( qboolean engine ) +{ + android_assets_t *assets = Mem_Calloc( fs_mempool, sizeof( *assets )); + + assets->engine = engine; + + Android_GetAssetManager( assets ); + if( !assets->asset_manager ) + { + Con_Printf( S_ERROR "%s: Can't get asset manager\n", __func__ ); + FS_CloseAndroidAssets( assets ); + return NULL; + } + + assets->dir = AAssetManager_openDir( assets->asset_manager, "" ); + if( !assets->dir ) + { + Con_Printf( S_ERROR "%s: Can't open root asset directory\n", __func__ ); + FS_CloseAndroidAssets( assets ); + return NULL; + } + + return assets; +} + +static int FS_FileTime_AndroidAssets( searchpath_t *search, const char *filename ) +{ + static time_t time; + + if( !time ) + { + struct tm file_tm; + + strptime( g_buildcommit_date, "%Y-%m-%d %H:%M:%S", &file_tm ); + time = mktime( &file_tm ); + } + + return time; +} + +static int FS_FindFile_AndroidAssets( struct searchpath_s *search, const char *path, char *fixedname, size_t len ) +{ + AAsset *assets = AAssetManager_open( search->assets->asset_manager, path, AASSET_MODE_UNKNOWN ); + + if( assets ) + { + AAsset_close( assets ); + + Q_strncpy( fixedname, path, len ); + return 0; + } + + return -1; +} + +static void FS_PrintInfo_AndroidAssets( searchpath_t *search, char *dst, size_t size ) +{ + Q_snprintf( dst, size, "%s", search->assets->package_name ); +} + +static void FS_Close_AndroidAssets( searchpath_t *search ) +{ + FS_CloseAndroidAssets( search->assets ); +} + +static void FS_Search_AndroidAssets( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive ) +{ + string temp; + stringlist_t dirlist; + const char *slash, *backslash, *colon, *separator; + int basepathlength, dirlistindex, resultlistindex; + char *basepath; + + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + + separator = Q_max( slash, backslash ); + separator = Q_max( separator, colon ); + + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); + if( basepathlength ) + memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = '\0'; + + stringlistinit( &dirlist ); + Android_ListDirectory( &dirlist, basepath, search->assets->engine ); + + Q_strncpy( temp, basepath, sizeof( temp )); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_strncpy( &temp[basepathlength], dirlist.strings[dirlistindex], sizeof( temp ) - basepathlength ); + + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < list->numstrings; resultlistindex++ ) + { + if( !Q_strcmp( list->strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == list->numstrings ) + stringlistappend( list, temp ); + } + } + + stringlistfreecontents( &dirlist ); + + Mem_Free( basepath ); +} + +static file_t *FS_OpenFile_AndroidAssets( searchpath_t *search, const char *filename, const char *mode, int pack_ind ) +{ + file_t *file = Mem_Calloc( fs_mempool, sizeof( *file )); + AAsset *assets = AAssetManager_open( search->assets->asset_manager, filename, AASSET_MODE_RANDOM ); + + file->handle = AAsset_openFileDescriptor( assets, &file->offset, &file->real_length ); + + file->position = 0; + file->ungetc = EOF; + file->searchpath = search; + + AAsset_close( assets ); + + return file; +} + +static byte *FS_LoadAndroidAssetsFile( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *filesize, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * )) +{ + byte *buf; + off_t size; + AAsset *asset; + + if( filesize ) *filesize = 0; + + asset = AAssetManager_open( search->assets->asset_manager, path, AASSET_MODE_BUFFER ); + if( !asset ) + return NULL; + + size = AAsset_getLength( asset ); + + buf = (byte *)pfnAlloc( size + 1 ); + if( unlikely( !buf )) + { + Con_Reportf( "%s: can't alloc %d bytes, no free memory\n", __func__, size + 1 ); + AAsset_close( asset ); + return NULL; + } + + buf[size] = '\0'; + + if( AAsset_read( asset, buf, size ) < 0 ) + { + pfnFree( buf ); + AAsset_close( asset ); + return NULL; + } + + AAsset_close( asset ); + if( filesize ) *filesize = size; + + return buf; +} + +searchpath_t *FS_AddAndroidAssets_Fullpath( const char *path, int flags ) +{ + searchpath_t *search; + android_assets_t *assets = NULL; + qboolean engine = true; + + if( !jni.getPackageName || !jni.getCallingPackage || !jni.getAssetsList || !jni.getAssets ) + return NULL; + + if( FBitSet( flags, FS_STATIC_PATH | FS_CUSTOM_PATH )) + return NULL; + + if( FBitSet( flags, FS_GAMEDIR_PATH ) && Q_stricmp( GI->basedir, GI->gamefolder )) + engine = false; + + assets = FS_LoadAndroidAssets( engine ); + + if( !assets ) + { + Con_Reportf( S_ERROR "%s: unable to load Android assets \"%s\"\n", __func__, Android_GetPackageName( engine )); + return NULL; + } + + Q_strncpy( assets->package_name, Android_GetPackageName( engine ), sizeof( assets->package_name )); + + search = Mem_Calloc( fs_mempool, sizeof( *search )); + + Q_strncpy( search->filename, assets->package_name, sizeof( search->filename )); + search->assets = assets; + search->type = SEARCHPATH_ANDROID_ASSETS; + SetBits( search->flags, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + + search->pfnPrintInfo = FS_PrintInfo_AndroidAssets; + search->pfnClose = FS_Close_AndroidAssets; + search->pfnOpenFile = FS_OpenFile_AndroidAssets; + search->pfnFileTime = FS_FileTime_AndroidAssets; + search->pfnFindFile = FS_FindFile_AndroidAssets; + search->pfnSearch = FS_Search_AndroidAssets; + search->pfnLoadFile = FS_LoadAndroidAssetsFile; + + Con_Reportf( "Adding Android assets: %s\n", assets->package_name ); + + return search; +} + +void FS_InitAndroid( void ) +{ + jmethodID getContext; + + jni.env = (JNIEnv *)Sys_GetNativeObject( "JNIEnv" ); + jni.activity_class = Sys_GetNativeObject( "ActivityClass" ); + + if( !jni.env || !jni.activity_class ) + { + Con_Reportf( S_WARN "%s: unable to get JNI env to load Android assets\n", __func__ ); + return; + } + + getContext = (*jni.env)->GetStaticMethodID( jni.env, jni.activity_class, "getContext", "()Landroid/content/Context;" ); + jni.activity = (*jni.env)->CallStaticObjectMethod( jni.env, jni.activity_class, getContext ); + + jni.getPackageName = (*jni.env)->GetMethodID( jni.env, jni.activity_class, "getPackageName", "()Ljava/lang/String;" ); + jni.getCallingPackage = (*jni.env)->GetMethodID( jni.env, jni.activity_class, "getCallingPackage", "()Ljava/lang/String;" ); + jni.getAssetsList = (*jni.env)->GetMethodID( jni.env, jni.activity_class, "getAssetsList", "(ZLjava/lang/String;)[Ljava/lang/String;" ); + jni.getAssets = (*jni.env)->GetMethodID( jni.env, jni.activity_class, "getAssets", "(Z)Landroid/content/res/AssetManager;" ); + + if( !jni.getPackageName || !jni.getCallingPackage || !jni.getAssetsList || !jni.getAssets ) + Con_Reportf( S_WARN "%s: unable to find required JNI interfaces to load Android assets\n", __func__ ); +} + +#endif // XASH_ANDROID diff --git a/filesystem/dir.c b/filesystem/dir.c new file mode 100644 index 00000000..68ae040c --- /dev/null +++ b/filesystem/dir.c @@ -0,0 +1,515 @@ +/* +dir.c - caseinsensitive directory operations +Copyright (C) 2022 Alibek Omarov, Velaron +Copyright (C) 2023 Xash3D FWGS contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "build.h" +#include +#include +#include +#include +#include +#if XASH_POSIX +#include +#if !XASH_PSVITA +#include +#endif +#endif +#if XASH_LINUX +#include +#ifndef FS_CASEFOLD_FL // for compatibility with older distros +#define FS_CASEFOLD_FL 0x40000000 +#endif // FS_CASEFOLD_FL +#endif // XASH_LINUX + +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "xash3d_mathlib.h" +#include "common/com_strings.h" + +enum +{ + DIRENTRY_EMPTY_DIRECTORY = 0, // don't care if it's not directory or it's empty + DIRENTRY_NOT_SCANNED = -1, + DIRENTRY_CASEINSENSITIVE = -2, // directory is already caseinsensitive, just copy whatever is left +}; + +typedef struct dir_s +{ + string name; + int numentries; + struct dir_s *entries; // sorted +} dir_t; + +static qboolean Platform_GetDirectoryCaseSensitivity( const char *dir ) +{ +#if XASH_WIN32 || XASH_PSVITA || XASH_NSWITCH + return false; +#elif XASH_ANDROID + // on Android, doing code below causes crash in MediaProviderGoogle.apk!libfuse_jni.so + // which in turn makes vold (Android's Volume Daemon) to umount /storage/emulated/0 + // and because you can't unmount a filesystem when there is file descriptors open + // it has no other choice but to terminate and then kill our program + return true; +#elif XASH_LINUX && defined( FS_IOC_GETFLAGS ) + int flags = 0; + int fd; + + fd = open( dir, O_RDONLY | O_NONBLOCK ); + if( fd < 0 ) + return true; + + if( ioctl( fd, FS_IOC_GETFLAGS, &flags ) < 0 ) + { + close( fd ); + return true; + } + + close( fd ); + + return !FBitSet( flags, FS_CASEFOLD_FL ); +#else + return true; +#endif +} + +static int FS_SortDirEntries( const void *_a, const void *_b ) +{ + const dir_t *a = _a; + const dir_t *b = _b; + return Q_stricmp( a->name, b->name ); +} + +static void FS_FreeDirEntries( dir_t *dir ) +{ + if( dir->entries ) + { + int i; + for( i = 0; i < dir->numentries; i++ ) + FS_FreeDirEntries( &dir->entries[i] ); + dir->entries = NULL; + } + + dir->numentries = DIRENTRY_NOT_SCANNED; +} + +static void FS_InitDirEntries( dir_t *dir, const stringlist_t *list ) +{ + int i; + + dir->numentries = list->numstrings; + dir->entries = Mem_Malloc( fs_mempool, sizeof( dir_t ) * dir->numentries ); + + for( i = 0; i < list->numstrings; i++ ) + { + dir_t *entry = &dir->entries[i]; + + Q_strncpy( entry->name, list->strings[i], sizeof( entry->name )); + entry->numentries = DIRENTRY_NOT_SCANNED; + entry->entries = NULL; + } + + qsort( dir->entries, dir->numentries, sizeof( dir->entries[0] ), FS_SortDirEntries ); +} + +static void FS_PopulateDirEntries( dir_t *dir, const char *path ) +{ + stringlist_t list; + + if( !FS_SysFolderExists( path )) + { + dir->numentries = DIRENTRY_EMPTY_DIRECTORY; + dir->entries = NULL; + return; + } + + if( !Platform_GetDirectoryCaseSensitivity( path )) + { + dir->numentries = DIRENTRY_CASEINSENSITIVE; + dir->entries = NULL; + return; + } + + stringlistinit( &list ); + listdirectory( &list, path, false ); + if( !list.numstrings ) + { + dir->numentries = DIRENTRY_EMPTY_DIRECTORY; + dir->entries = NULL; + } + else + { + FS_InitDirEntries( dir, &list ); + } + stringlistfreecontents( &list ); +} + +static int FS_FindDirEntry( dir_t *dir, const char *name ) +{ + int left, right; + + // look for the file (binary search) + left = 0; + right = dir->numentries - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff; + + diff = Q_stricmp( dir->entries[middle].name, name ); + + // found it + if( !diff ) + return middle; + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + return -1; +} + +static void FS_MergeDirEntries( dir_t *dir, const stringlist_t *list ) +{ + int i; + dir_t temp; + + // glorified realloc for sorted dir entries + // make new array and copy old entries with same name and subentries + // everything else get freed + + FS_InitDirEntries( &temp, list ); + + for( i = 0; i < dir->numentries; i++ ) + { + dir_t *oldentry = &dir->entries[i]; + dir_t *newentry; + int j; + + // don't care about directories without subentries + if( oldentry->entries == NULL ) + continue; + + // try to find this directory in new tree + j = FS_FindDirEntry( &temp, oldentry->name ); + + // not found, free memory + if( j < 0 ) + { + FS_FreeDirEntries( oldentry ); + continue; + } + + // found directory, move all entries + newentry = &temp.entries[j]; + + newentry->numentries = oldentry->numentries; + newentry->entries = oldentry->entries; + } + + // now we can free old tree and replace it with temporary + // do not add null check there! If we hit it, it's probably a logic error! + Mem_Free( dir->entries ); + dir->numentries = temp.numentries; + dir->entries = temp.entries; +} + +static int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *entryname ) +{ + stringlist_t list; + int ret; + + stringlistinit( &list ); + listdirectory( &list, path, false ); + + if( list.numstrings == 0 ) // empty directory + { + FS_FreeDirEntries( dir ); + dir->numentries = DIRENTRY_EMPTY_DIRECTORY; + ret = -1; + } + else if( dir->numentries <= DIRENTRY_EMPTY_DIRECTORY ) // not initialized or was empty + { + FS_InitDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); + } + else if( list.numstrings != dir->numentries ) // quick update + { + FS_MergeDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); + } + else + { + // do heavy compare if directory now have an entry we need + int i; + + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( list.strings[i], entryname )) + break; + } + + if( i != list.numstrings ) + { + FS_MergeDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); + } + else ret = -1; + } + + stringlistfreecontents( &list ); + return ret; +} + +static inline qboolean FS_AppendToPath( char *dst, size_t *pi, const size_t len, const char *src, const char *path, const char *err ) +{ + size_t i = *pi; + + i += Q_strncpy( &dst[i], src, len - i ); + *pi = i; + + if( i >= len ) + { + Con_Printf( S_ERROR "%s: overflow while appending %s (%s)\n", __func__, path, err ); + return false; + } + return true; +} + +qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath ) +{ + const char *prev; + const char *next; + size_t i = 0; + + if( !FS_AppendToPath( dst, &i, len, dir->name, path, "init" )) + return false; + + // nothing to fix + if( !COM_CheckStringEmpty( path )) + return true; + + for( prev = path, next = Q_strchrnul( prev, '/' ); + ; + prev = next + 1, next = Q_strchrnul( prev, '/' )) + { + qboolean uptodate = false; // do not run second scan if we're just updated our directory list + size_t temp; + char entryname[MAX_SYSPATH]; + int ret; + + if( dir->numentries == DIRENTRY_NOT_SCANNED ) + { + // read directory first time + FS_PopulateDirEntries( dir, dst ); + uptodate = true; + } + + // this subdirectory is case insensitive, just slam everything that's left + if( dir->numentries == DIRENTRY_CASEINSENSITIVE ) + { + if( !FS_AppendToPath( dst, &i, len, prev, path, "caseinsensitive entry" )) + return false; + + // check file existense + return createpath ? true : FS_SysFileOrFolderExists( dst ); + } + + // get our entry name + Q_strncpy( entryname, prev, next - prev + 1 ); + + // didn't found, but does it exists in FS? + if(( ret = FS_FindDirEntry( dir, entryname )) < 0 ) + { + // if we're creating files or folders, we don't care if path doesn't exist + // so copy everything that's left and exit without an error + if( uptodate || ( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 ) + return createpath ? FS_AppendToPath( dst, &i, len, prev, path, "create path" ) : false; + + uptodate = true; + } + + dir = &dir->entries[ret]; + temp = i; + if( !FS_AppendToPath( dst, &temp, len, dir->name, path, "case fix" )) + return false; + + if( !uptodate && !FS_SysFileOrFolderExists( dst )) // file not found, rescan... + { + dst[i] = 0; // strip failed part + + // if we're creating files or folders, we don't care if path doesn't exist + // so copy everything that's left and exit without an error + if(( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 ) + return createpath ? FS_AppendToPath( dst, &i, len, prev, path, "create path rescan" ) : false; + + dir = &dir->entries[ret]; + if( !FS_AppendToPath( dst, &temp, len, dir->name, path, "case fix rescan" )) + return false; + } + i = temp; + + // end of string, found file, return + if( next[0] == '\0' || ( next[0] == '/' && next[1] == '\0' )) + break; + + if( !FS_AppendToPath( dst, &i, len, "/", path, "path separator" )) + return false; + } + + return true; +} + +static void FS_Close_DIR( searchpath_t *search ) +{ + FS_FreeDirEntries( search->dir ); + Mem_Free( search->dir ); +} + +static void FS_PrintInfo_DIR( searchpath_t *search, char *dst, size_t size ) +{ + Q_strncpy( dst, search->filename, size ); +} + +static int FS_FindFile_DIR( searchpath_t *search, const char *path, char *fixedname, size_t len ) +{ + char netpath[MAX_SYSPATH]; + + if( !FS_FixFileCase( search->dir, path, netpath, sizeof( netpath ), false )) + return -1; + + if( FS_SysFileExists( netpath )) + { + // return fixed case file name only local for that searchpath + if( fixedname ) + Q_strncpy( fixedname, netpath + Q_strlen( search->filename ), len ); + return 0; + } + + return -1; +} + +static void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive ) +{ + string netpath, temp; + stringlist_t dirlist; + const char *slash, *backslash, *colon, *separator; + int basepathlength, dirlistindex, resultlistindex; + char *basepath; + + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + + separator = Q_max( slash, backslash ); + separator = Q_max( separator, colon ); + + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); + if( basepathlength ) memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = '\0'; + + if( !FS_FixFileCase( search->dir, basepath, netpath, sizeof( netpath ), false )) + { + Mem_Free( basepath ); + return; + } + + stringlistinit( &dirlist ); + listdirectory( &dirlist, netpath, false ); + + Q_strncpy( temp, basepath, sizeof( temp )); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_strncpy( &temp[basepathlength], dirlist.strings[dirlistindex], sizeof( temp ) - basepathlength ); + + if( matchpattern( temp, (char *)pattern, true ) ) + { + for( resultlistindex = 0; resultlistindex < list->numstrings; resultlistindex++ ) + { + if( !Q_strcmp( list->strings[resultlistindex], temp ) ) + break; + } + + if( resultlistindex == list->numstrings ) + stringlistappend( list, temp ); + } + } + + stringlistfreecontents( &dirlist ); + + Mem_Free( basepath ); +} + +static int FS_FileTime_DIR( searchpath_t *search, const char *filename ) +{ + char path[MAX_SYSPATH]; + + Q_snprintf( path, sizeof( path ), "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); +} + +static file_t *FS_OpenFile_DIR( searchpath_t *search, const char *filename, const char *mode, int pack_ind ) +{ + file_t *f; + char path[MAX_SYSPATH]; + + Q_snprintf( path, sizeof( path ), "%s%s", search->filename, filename ); + f = FS_SysOpen( path, mode ); + if( !f ) + return NULL; + + f->searchpath = search; + + return f; +} + +void FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int flags ) +{ + memset( search, 0, sizeof( searchpath_t )); + + Q_strncpy( search->filename, path, sizeof( search->filename ) - 1 ); + COM_PathSlashFix( search->filename ); + + if( !Q_stricmp( COM_FileExtension( path ), "pk3dir" )) + search->type = SEARCHPATH_PK3DIR; + else search->type = SEARCHPATH_PLAIN; + search->flags = flags; + search->pfnPrintInfo = FS_PrintInfo_DIR; + search->pfnClose = FS_Close_DIR; + search->pfnOpenFile = FS_OpenFile_DIR; + search->pfnFileTime = FS_FileTime_DIR; + search->pfnFindFile = FS_FindFile_DIR; + search->pfnSearch = FS_Search_DIR; + + // create cache root + search->dir = Mem_Malloc( fs_mempool, sizeof( dir_t )); + Q_strncpy( search->dir->name, search->filename, sizeof( search->dir->name )); + FS_PopulateDirEntries( search->dir, path ); +} + +searchpath_t *FS_AddDir_Fullpath( const char *path, int flags ) +{ + searchpath_t *search; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + FS_InitDirectorySearchpath( search, path, flags ); + Con_Printf( "Adding directory: %s\n", path ); + + return search; +} diff --git a/filesystem/exports.txt b/filesystem/exports.txt new file mode 100644 index 00000000..b520b53a --- /dev/null +++ b/filesystem/exports.txt @@ -0,0 +1 @@ +GetFSAPI diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 55826b1b..9d2048fa 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1,6 +1,9 @@ /* filesystem.c - game filesystem based on DP fs +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,26 +16,29 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#define _GNU_SOURCE 1 + #include "build.h" #include +#include #include #include +#include #if XASH_WIN32 #include #include #elif XASH_DOS4GW #include -#include #else #include -#include #endif #include #include -#if XASH_LINUX -#include +#if HAVE_MEMFD_CREATE +#include #endif #include "port.h" +#include "defaults.h" #include "const.h" #include "crtlib.h" #include "crclib.h" @@ -43,20 +49,73 @@ GNU General Public License for more details. #include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) +#define SAVE_AGED_COUNT 2 // the default count of quick and auto saves fs_globals_t FI; -qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes poolhandle_t fs_mempool; -searchpath_t *fs_searchpaths = NULL; // chain -char fs_rodir[MAX_SYSPATH]; char fs_rootdir[MAX_SYSPATH]; -char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) +searchpath_t *fs_writepath; -static searchpath_t fs_directpath; // static direct path -static char fs_basedir[MAX_SYSPATH]; // base game directory -static char fs_gamedir[MAX_SYSPATH]; // game current directory -#if !XASH_WIN32 -static qboolean fs_caseinsensitive = true; // try to search missing files +static searchpath_t *fs_searchpaths = NULL; // chain +static char fs_basedir[MAX_SYSPATH]; // base game directory +static char fs_gamedir[MAX_SYSPATH]; // game current directory +static char fs_rodir[MAX_SYSPATH]; +static string fs_language; +static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes + +typedef struct fs_archive_s +{ + const char *ext; + int type; + FS_ADDARCHIVE_FULLPATH pfnAddArchive_Fullpath; + qboolean load_wads; // load wads from this archive + qboolean real_archive; +} fs_archive_t; + +// add archives in specific order PAK -> PK3 -> WAD +// so raw WADs takes precedence over WADs included into PAKs and PK3s +static const fs_archive_t g_archives[] = +{ + { + .ext = "pak", + .type = SEARCHPATH_PAK, + .pfnAddArchive_Fullpath = FS_AddPak_Fullpath, + .load_wads = true, + .real_archive = true, + }, { + .ext = "pk3", + .type = SEARCHPATH_ZIP, + .pfnAddArchive_Fullpath = FS_AddZip_Fullpath, + .load_wads = true, + .real_archive = true, + }, { + .ext = "pk3dir", + .type = SEARCHPATH_PK3DIR, + .pfnAddArchive_Fullpath = FS_AddDir_Fullpath, + .load_wads = true, + .real_archive = false, + }, { + .ext = "wad", + .type = SEARCHPATH_WAD, + .pfnAddArchive_Fullpath = FS_AddWad_Fullpath, + .load_wads = false, + .real_archive = true, + }, +}; + +// special fs_archive_t for plain directories +static const fs_archive_t g_directory_archive = +{ + .type = SEARCHPATH_PLAIN, + .pfnAddArchive_Fullpath = FS_AddDir_Fullpath, +}; + +#if XASH_ANDROID +static const fs_archive_t g_android_archive = +{ + .type = SEARCHPATH_ANDROID_ASSETS, + .pfnAddArchive_Fullpath = FS_AddAndroidAssets_Fullpath +}; #endif #ifdef XASH_REDUCE_FD @@ -100,8 +159,6 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) file->backup_options = options; } } - - #else static void FS_EnsureOpenFile( file_t *file ) {} static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} @@ -110,6 +167,16 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} static void FS_InitMemory( void ); static void FS_Purge( file_t* file ); +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + g_engfuncs._Mem_Free( data, filename, fileline ); +} + +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return g_engfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline ); +} + /* ============================================================================= @@ -117,12 +184,12 @@ FILEMATCH COMMON SYSTEM ============================================================================= */ -static void stringlistinit( stringlist_t *list ) +void stringlistinit( stringlist_t *list ) { memset( list, 0, sizeof( *list )); } -static void stringlistfreecontents( stringlist_t *list ) +void stringlistfreecontents( stringlist_t *list ) { int i; @@ -141,7 +208,7 @@ static void stringlistfreecontents( stringlist_t *list ) list->strings = NULL; } -void stringlistappend( stringlist_t *list, char *text ) +void stringlistappend( stringlist_t *list, const char *text ) { size_t textlen; @@ -160,7 +227,7 @@ void stringlistappend( stringlist_t *list, char *text ) list->numstrings++; } -static void stringlistsort( stringlist_t *list ) +void stringlistsort( stringlist_t *list ) { char *temp; int i, j; @@ -180,7 +247,8 @@ static void stringlistsort( stringlist_t *list ) } } -// convert names to lowercase because windows doesn't care, but pattern matching code often does +#if XASH_DOS4GW +// convert names to lowercase because dos doesn't care, but pattern matching code often does static void listlowercase( stringlist_t *list ) { char *c; @@ -192,22 +260,16 @@ static void listlowercase( stringlist_t *list ) *c = Q_tolower( *c ); } } - -static void listdirectory( stringlist_t *list, const char *path, qboolean lowercase ) -{ - int i; - signed char *c; -#if XASH_WIN32 - char pattern[4096]; - struct _finddata_t n_file; - intptr_t hFile; -#else - DIR *dir; - struct dirent *entry; #endif +void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only ) +{ #if XASH_WIN32 - Q_snprintf( pattern, sizeof( pattern ), "%s*", path ); + char pattern[4096]; + struct _finddata_t n_file; + intptr_t hFile; + + Q_snprintf( pattern, sizeof( pattern ), "%s/*", path ); // ask for the directory listing handle hFile = _findfirst( pattern, &n_file ); @@ -217,15 +279,33 @@ static void listdirectory( stringlist_t *list, const char *path, qboolean lowerc stringlistappend( list, n_file.name ); // iterate through the directory while( _findnext( hFile, &n_file ) == 0 ) + { + if( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR )) + continue; + stringlistappend( list, n_file.name ); + } _findclose( hFile ); #else - if( !( dir = opendir( path ) ) ) + DIR *dir; + struct dirent *entry; + + dir = opendir( path ); + + if( !dir ) return; // iterate through the directory - while( ( entry = readdir( dir ) )) + while(( entry = readdir( dir ))) + { +#if HAVE_DIRENT_D_TYPE + if( dirs_only && entry->d_type != DT_DIR && entry->d_type != DT_LNK && entry->d_type != DT_UNKNOWN ) + continue; +#endif + stringlistappend( list, entry->d_name ); + } + closedir( dir ); #endif @@ -243,103 +323,6 @@ OTHER PRIVATE FUNCTIONS ============================================================================= */ -/* -================== -FS_FixFileCase - -emulate WIN32 FS behaviour when opening local file -================== -*/ -const char *FS_FixFileCase( const char *path ) -{ -#if defined __DOS__ & !defined __WATCOM_LFN__ - // not fix, but convert to 8.3 CAPS and rotate slashes - // it is still recommended to package game data - static char out[PATH_MAX]; - int i = 0; - int last = 0; - while(*path) - { - char c = *path++; - - if(c == '/') c = '\\'; - else c = toupper(c); - out[i++] = c; - if(c == '\\' || c == '.') - { - if( i - last > 8 ) - { - char *l = &out[last]; - l[6] = '~'; - l[7] = '1'; - l[8] = c; - i = last + 9; - } - last = i; - } - } - - out[i] = 0; - return out; -#elif !XASH_WIN32 && !XASH_IOS // assume case insensitive - DIR *dir; - struct dirent *entry; - char path2[PATH_MAX], *fname; - - if( !fs_caseinsensitive ) - return path; - - if( path[0] != '/' ) - Q_snprintf( path2, sizeof( path2 ), "./%s", path ); - else Q_strncpy( path2, path, PATH_MAX ); - - fname = Q_strrchr( path2, '/' ); - - if( fname ) - *fname++ = 0; - else - { - fname = (char*)path; - Q_strcpy( path2, "."); - } - - /* android has too slow directory scanning, - so drop out some not useful cases */ - if( fname - path2 > 4 ) - { - char *point; - // too many wad textures - if( !Q_stricmp( fname - 5, ".wad") ) - return path; - point = Q_strchr( fname, '.' ); - if( point ) - { - if( !Q_strcmp( point, ".mip") || !Q_strcmp( point, ".dds" ) || !Q_strcmp( point, ".ent" ) ) - return path; - if( fname[0] == '{' ) - return path; - } - } - - //Con_Reportf( "FS_FixFileCase: %s\n", path ); - - if( !( dir = opendir( path2 ) ) ) - return path; - - while( ( entry = readdir( dir ) ) ) - { - if( Q_stricmp( entry->d_name, fname ) ) - continue; - - path = va( "%s/%s", path2, entry->d_name ); - //Con_Reportf( "FS_FixFileCase: %s %s %s\n", path2, fname, entry->d_name ); - break; - } - closedir( dir ); -#endif - return path; -} - #if XASH_WIN32 /* ==================== @@ -380,90 +363,128 @@ void FS_CreatePath( char *path ) } } +static searchpath_t *FS_AddArchive_Fullpath( const fs_archive_t *archive, const char *file, int flags ) +{ + searchpath_t *search; + if( !archive ) + { + int i; + const char *ext = COM_FileExtension( file ); + + for( i = 0; i < sizeof( g_archives ) / sizeof( g_archives[0] ); i++ ) + { + if( !Q_stricmp( g_archives[i].ext, ext )) + { + archive = &g_archives[i]; + break; + } + } + + if( !archive ) + { + Con_Printf( S_ERROR "%s: unknown archive format %s, not mounted\n", __func__, file ); + return NULL; + } + } + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == archive->type && !Q_stricmp( search->filename, file )) + return search; // already loaded + } + + search = archive->pfnAddArchive_Fullpath( file, flags ); + + if( !search ) + return NULL; + + search->next = fs_searchpaths; + fs_searchpaths = search; + + // time to add in search list all the wads from this archive + if( archive->load_wads && !FBitSet( flags, FS_SKIP_ARCHIVED_WADS )) + { + stringlist_t list; + int i; + + stringlistinit( &list ); + search->pfnSearch( search, &list, "*.wad", true ); + stringlistsort( &list ); // keep always sorted + + for( i = 0; i < list.numstrings; i++ ) + { + searchpath_t *wad; + char fullpath[MAX_SYSPATH]; + + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", file, list.strings[i] ); + if(( wad = FS_AddWad_Fullpath( fullpath, flags | FS_LOAD_PACKED_WAD ))) + { + wad->next = fs_searchpaths; + fs_searchpaths = wad; + } + } + + stringlistfreecontents( &list ); + } + + return search; +} /* ================ FS_AddArchive_Fullpath ================ */ -static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_loaded, int flags ) +searchpath_t *FS_MountArchive_Fullpath( const char *file, int flags ) { - const char *ext = COM_FileExtension( file ); - - if( !Q_stricmp( ext, "pk3" ) ) - return FS_AddZip_Fullpath( file, already_loaded, flags ); - else if ( !Q_stricmp( ext, "pak" )) - return FS_AddPak_Fullpath( file, already_loaded, flags ); - - // skip wads, this function only meant to be used for extras - return false; + return FS_AddArchive_Fullpath( NULL, file, flags ); } /* ================ FS_AddGameDirectory -Sets fs_writedir, adds the directory to the head of the path, +Sets fs_writepath, adds the directory to the head of the path, then loads and adds pak1.pak pak2.pak ... ================ */ void FS_AddGameDirectory( const char *dir, uint flags ) { - stringlist_t list; - searchpath_t *search; - string fullpath; - int i; + stringlist_t list; + searchpath_t *search; + int i, j; - if( !FBitSet( flags, FS_NOWRITE_PATH )) - Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); stringlistinit( &list ); listdirectory( &list, dir, false ); stringlistsort( &list ); - // add any PAK package in the directory - for( i = 0; i < list.numstrings; i++ ) + for( j = 0; j < sizeof( g_archives ) / sizeof( g_archives[0] ); j++ ) { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) - { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddPak_Fullpath( fullpath, NULL, flags ); - } - } + char fullpath[MAX_SYSPATH]; + int i; - // add any Zip package in the directory - for( i = 0; i < list.numstrings; i++ ) - { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) + for( i = 0; i < list.numstrings; i++ ) { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddZip_Fullpath( fullpath, NULL, flags ); - } - } + if( Q_stricmp( COM_FileExtension( list.strings[i] ), g_archives[j].ext )) + continue; - FS_AllowDirectPaths( true ); - - // add any WAD package in the directory - for( i = 0; i < list.numstrings; i++ ) - { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) - { - Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); + Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", dir, list.strings[i] ); + FS_AddArchive_Fullpath( &g_archives[j], fullpath, flags ); } } stringlistfreecontents( &list ); - FS_AllowDirectPaths( false ); + +#if XASH_ANDROID + FS_AddArchive_Fullpath( &g_android_archive, dir, flags ); +#endif // add the directory to the search path // (unpacked files have the priority over packed files) - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - Q_strncpy( search->filename, dir, sizeof ( search->filename )); - search->next = fs_searchpaths; - search->type = SEARCHPATH_PLAIN; - search->flags = flags; - fs_searchpaths = search; + search = FS_AddArchive_Fullpath( &g_directory_archive, dir, flags ); + if( !FBitSet( flags, FS_NOWRITE_PATH )) + fs_writepath = search; } /* @@ -473,37 +494,34 @@ FS_ClearSearchPath */ void FS_ClearSearchPath( void ) { - while( fs_searchpaths ) + searchpath_t *cur, **prev; + int i; + + prev = &fs_searchpaths; + + while( true ) { - searchpath_t *search = fs_searchpaths; + cur = *prev; - if( !search ) break; + if( !cur ) + break; - if( FBitSet( search->flags, FS_STATIC_PATH )) + // never delete static paths + if( FBitSet( cur->flags, FS_STATIC_PATH )) { - // skip read-only pathes - if( search->next ) - fs_searchpaths = search->next->next; - else break; - } - else fs_searchpaths = search->next; - - switch( search->type ) - { - case SEARCHPATH_PAK: - FS_ClosePAK( search->pack ); - break; - case SEARCHPATH_WAD: - FS_CloseWAD( search->wad ); - break; - case SEARCHPATH_ZIP: - FS_CloseZIP( search->zip ); - break; - default: - break; + prev = &cur->next; + continue; } - Mem_Free( search ); + *prev = cur->next; + cur->pfnClose( cur ); + Mem_Free( cur ); + } + + for( i = 0; i < FI.numgames; i++ ) + { + if( FI.games[i] ) + FI.games[i]->added = false; } } @@ -516,7 +534,7 @@ Return true if the path should be rejected due to one of the following: or are just not a good idea for a mod to be using. ==================== */ -int FS_CheckNastyPath (const char *path, qboolean isgamedir) +static int FS_CheckNastyPath( const char *path ) { // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless if( !COM_CheckString( path )) return 2; @@ -538,8 +556,10 @@ int FS_CheckNastyPath (const char *path, qboolean isgamedir) // Windows and UNIXes: don't allow absolute paths if( path[0] == '/') return 2; // attempt to go outside the game directory +#if 0 // all: forbid trailing slash on gamedir if( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2; +#endif // all: forbid leading dot on any filename for any reason if( Q_strstr(path, "/.")) return 2; // attempt to go outside the game directory @@ -556,14 +576,15 @@ FS_WriteGameInfo assume GameInfo is valid ================ */ -static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) +static qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) { file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode int i, write_ambients = false; - if( !f ) Sys_Error( "FS_WriteGameInfo: can't write %s\n", filepath ); // may be disk-space is out? + if( !f ) + return false; - FS_Printf( f, "// generated by %s %s-%s (%s-%s)\n\n\n", XASH_ENGINE_NAME, XASH_VERSION, Q_buildcommit(), Q_buildos(), Q_buildarch() ); + FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", g_buildcommit, Q_buildos(), Q_buildarch() ); if( COM_CheckStringEmpty( GameInfo->basedir ) ) FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir ); @@ -589,7 +610,7 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) FS_Printf( f, "version\t\t%g\n", GameInfo->version ); if( GameInfo->size != 0 ) - FS_Printf( f, "size\t\t%lu\n", GameInfo->size ); + FS_Printf( f, "size\t\t%zu\n", GameInfo->size ); if( COM_CheckStringEmpty( GameInfo->game_url ) ) FS_Printf( f, "url_info\t\t\"%s\"\n", GameInfo->game_url ); @@ -615,7 +636,6 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) if( COM_CheckStringEmpty( GameInfo->game_dll_osx ) ) FS_Printf( f, "gamedll_osx\t\t\"%s\"\n", GameInfo->game_dll_osx ); - if( COM_CheckStringEmpty( GameInfo->iconpath )) FS_Printf( f, "icon\t\t\"%s\"\n", GameInfo->iconpath ); @@ -660,42 +680,85 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) } } - FS_Print( f, "\n\n\n" ); + if( GameInfo->noskills ) + FS_Printf( f, "noskills\t\t\"%i\"\n", GameInfo->noskills ); + + if( GameInfo->quicksave_aged_count != SAVE_AGED_COUNT ) + FS_Printf( f, "quicksave_aged_count\t\t%d\n", GameInfo->quicksave_aged_count ); + + if( GameInfo->autosave_aged_count != SAVE_AGED_COUNT ) + FS_Printf( f, "autosave_aged_count\t\t%d\n", GameInfo->autosave_aged_count ); + + // HL25 compatibility + if( GameInfo->animated_title ) + FS_Printf( f, "animated_title\t\t%i\n", GameInfo->animated_title ); + + if( GameInfo->hd_background ) + FS_Printf( f, "hd_background\t\t%i\n", GameInfo->hd_background ); + + // always expose our extensions :) + FS_Printf( f, "internal_vgui_support\t\t%s\n", GameInfo->internal_vgui_support ? "1" : "0" ); + FS_Printf( f, "render_picbutton_text\t\t%s\n", GameInfo->render_picbutton_text ? "1" : "0" ); + + if( COM_CheckStringEmpty( GameInfo->demomap )) + FS_Printf( f, "demomap\t\t\"%s\"\n", GameInfo->demomap ); + FS_Close( f ); // all done + + return true; } -void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir ) +static void FS_MakeGameInfo( void ) +{ + if( FS_WriteGameInfo( "gameinfo.txt", FI.GameInfo )) + Con_Printf( "Successfully generated %s/gameinfo.txt\n", FI.GameInfo->gamefolder ); + else + Con_Printf( S_ERROR "Can't open %s/gameinfo.txt for write\n", FI.GameInfo->gamefolder ); +} + +static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime ) { memset( GameInfo, 0, sizeof( *GameInfo )); // filesystem info + GameInfo->mtime = mtime; Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder )); - Q_strncpy( GameInfo->basedir, "valve", sizeof( GameInfo->basedir )); - GameInfo->falldir[0] = 0; - Q_strncpy( GameInfo->startmap, "c0a0", sizeof( GameInfo->startmap )); - Q_strncpy( GameInfo->trainmap, "t0a0", sizeof( GameInfo->trainmap )); - Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); - GameInfo->version = 1.0f; - - // .dll pathes - Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); - Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); - Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux )); - Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx )); - - // .ico path - Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); - Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity )); Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity )); + Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); - GameInfo->max_edicts = 900; // default value if not specified + if( quake ) + { + Q_strncpy( GameInfo->basedir, "id1", sizeof( GameInfo->basedir )); + Q_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title )); + Q_strncpy( GameInfo->startmap, "start", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->dll_path, "bin", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "bin/progs.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->game_dll_linux, "bin/progs.so", sizeof( GameInfo->game_dll_linux )); + Q_strncpy( GameInfo->game_dll_osx, "bin/progs.dylib", sizeof( GameInfo->game_dll_osx )); + } + else + { + Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir )); + Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux )); + Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx )); + } + + GameInfo->max_edicts = DEFAULT_MAX_EDICTS; // default value if not specified GameInfo->max_tents = 500; GameInfo->max_beams = 128; GameInfo->max_particles = 4096; + GameInfo->version = 1.0f; + + GameInfo->quicksave_aged_count = SAVE_AGED_COUNT; + GameInfo->autosave_aged_count = SAVE_AGED_COUNT; } -void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qboolean isGameInfo ) +static void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qboolean isGameInfo ) { char *pfile = (char*) buf; qboolean found_linux = false, found_osx = false; @@ -759,35 +822,42 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool { pfile = COM_ParseFile( pfile, GameInfo->iconpath, sizeof( GameInfo->iconpath )); COM_FixSlashes( GameInfo->iconpath ); - COM_DefaultExtension( GameInfo->iconpath, ".ico" ); + COM_DefaultExtension( GameInfo->iconpath, ".ico", sizeof( GameInfo->iconpath )); } else if( !Q_stricmp( token, "type" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - if( !isGameInfo && !Q_stricmp( token, "singleplayer_only" )) + if( isGameInfo ) { - // TODO: Remove this ugly hack too. - // This was made because Half-Life has multiplayer, - // but for some reason it's marked as singleplayer_only. - // Old WON version is fine. - if( !Q_stricmp( GameInfo->gamefolder, "valve") ) - GameInfo->gamemode = GAME_NORMAL; - else - GameInfo->gamemode = GAME_SINGLEPLAYER_ONLY; - Q_strncpy( GameInfo->type, "Single", sizeof( GameInfo->type )); - } - else if( !isGameInfo && !Q_stricmp( token, "multiplayer_only" )) - { - GameInfo->gamemode = GAME_MULTIPLAYER_ONLY; - Q_strncpy( GameInfo->type, "Multiplayer", sizeof( GameInfo->type )); + Q_strncpy( GameInfo->type, token, sizeof( GameInfo->type )); } else { - // pass type without changes - if( !isGameInfo ) - GameInfo->gamemode = GAME_NORMAL; - Q_strncpy( GameInfo->type, token, sizeof( GameInfo->type )); + if( !Q_stricmp( token, "singleplayer_only" )) + { + // TODO: Remove this ugly hack too. + // This was made because Half-Life has multiplayer, + // but for some reason it's marked as singleplayer_only. + // Old WON version is fine. + if( !Q_stricmp( GameInfo->gamefolder, "valve") ) + GameInfo->gamemode = GAME_NORMAL; + else + GameInfo->gamemode = GAME_SINGLEPLAYER_ONLY; + Q_strncpy( GameInfo->type, "Single", sizeof( GameInfo->type )); + } + else if( !Q_stricmp( token, "multiplayer_only" )) + { + GameInfo->gamemode = GAME_MULTIPLAYER_ONLY; + Q_strncpy( GameInfo->type, "Multiplayer", sizeof( GameInfo->type )); + } + else + { + // pass type without changes + if( !isGameInfo ) + GameInfo->gamemode = GAME_NORMAL; + Q_strncpy( GameInfo->type, token, sizeof( GameInfo->type )); + } } } // valid for both @@ -802,11 +872,6 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool pfile = COM_ParseFile( pfile, token, sizeof( token )); GameInfo->size = Q_atoi( token ); } - else if( !Q_stricmp( token, "edicts" )) - { - pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->max_edicts = Q_atoi( token ); - } else if( !Q_stricmp( token, isGameInfo ? "mp_entity" : "mpentity" )) { pfile = COM_ParseFile( pfile, GameInfo->mp_entity, sizeof( GameInfo->mp_entity )); @@ -819,19 +884,30 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "secure" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->secure = Q_atoi( token ); + GameInfo->secure = Q_atoi( token ) ? true : false; } // valid for both else if( !Q_stricmp( token, "nomodels" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->nomodels = Q_atoi( token ); + GameInfo->nomodels = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, isGameInfo ? "max_edicts" : "edicts" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); GameInfo->max_edicts = bound( MIN_EDICTS, Q_atoi( token ), MAX_EDICTS ); } + // valid for both + else if( !Q_stricmp( token, "hd_background" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->hd_background = Q_atoi( token ) ? true : false; + } + else if( !Q_stricmp( token, "animated_title" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->animated_title = Q_atoi( token ) ? true : false; + } // only for gameinfo else if( isGameInfo ) { @@ -867,7 +943,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "max_particles" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->max_particles = bound( 4096, Q_atoi( token ), 32768 ); + GameInfo->max_particles = bound( 1024, Q_atoi( token ), 131072 ); } else if( !Q_stricmp( token, "gamemode" )) { @@ -885,7 +961,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool { int ambientNum = Q_atoi( token + 7 ); - if( ambientNum < 0 || ambientNum > ( NUM_AMBIENTS - 1 )) + if( ambientNum < 0 || ambientNum >= NUM_AMBIENTS ) ambientNum = 0; pfile = COM_ParseFile( pfile, GameInfo->ambientsound[ambientNum], sizeof( GameInfo->ambientsound[ambientNum] )); @@ -893,16 +969,43 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "noskills" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->noskills = Q_atoi( token ); + GameInfo->noskills = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, "render_picbutton_text" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->render_picbutton_text = Q_atoi( token ); + GameInfo->render_picbutton_text = Q_atoi( token ) ? true : false; + } + else if( !Q_stricmp( token, "internal_vgui_support" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->internal_vgui_support = Q_atoi( token ) ? true : false; + } + else if( !Q_stricmp( token, "quicksave_aged_count" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->quicksave_aged_count = bound( 2, Q_atoi( token ), 99 ); + } + else if( !Q_stricmp( token, "autosave_aged_count" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->autosave_aged_count = bound( 2, Q_atoi( token ), 99 ); + } + else if( !Q_stricmp( token, "demomap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->demomap, sizeof( GameInfo->demomap )); } } } + // demomap only valid for gameinfo.txt but HL1 after 25th anniversary update + // comes with demo chapter. Set the demomap here. + if( !COM_CheckStringEmpty( GameInfo->demomap )) + { + if( !Q_stricmp( GameInfo->title, "Half-Life" )) // original check from GameUI + Q_strncpy( GameInfo->demomap, "hldemo1", sizeof( GameInfo->demomap )); + } + if( !found_linux || !found_osx ) { // just replace extension from dll to so/dylib @@ -918,23 +1021,18 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", fs_rootdir, GameInfo->falldir ))) - GameInfo->falldir[0] = '\0'; -} - -/* -================ -FS_CreateDefaultGameInfo -================ -*/ -void FS_CreateDefaultGameInfo( const char *filename ) -{ - gameinfo_t defGI; - - FS_InitGameInfo( &defGI, fs_basedir ); - - // make simple gameinfo.txt - FS_WriteGameInfo( filename, &defGI ); + // a1ba: why we are doing this??? + Q_snprintf( token, sizeof( token ), "%s/%s", fs_rootdir, GameInfo->falldir ); + if( !FS_SysFolderExists( token )) + { + if( COM_CheckStringEmpty( fs_rodir )) + { + Q_snprintf( token, sizeof( token ), "%s/%s", fs_rodir, GameInfo->falldir ); + if( !FS_SysFolderExists( token )) + GameInfo->falldir[0] = 0; + } + else GameInfo->falldir[0] = 0; + } } /* @@ -942,16 +1040,14 @@ void FS_CreateDefaultGameInfo( const char *filename ) FS_ParseLiblistGam ================ */ -static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime ) { - char *afile; + char *afile = FS_LoadDirectFile( filename, NULL ); - if( !GameInfo ) return false; - afile = (char *)FS_LoadDirectFile( filename, NULL ); - if( !afile ) return false; - - FS_InitGameInfo( GameInfo, gamedir ); + if( !afile ) + return false; + FS_InitGameInfo( GameInfo, gamedir, false, mtime ); FS_ParseGenericGameInfo( GameInfo, afile, false ); Mem_Free( afile ); @@ -964,18 +1060,15 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g FS_ConvertGameInfo ================ */ -static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path ) +static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path, gameinfo_t *gi, time_t liblist_mtime ) { - gameinfo_t GameInfo; + memset( gi, 0, sizeof( *gi )); - memset( &GameInfo, 0, sizeof( GameInfo )); - - if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo )) + // liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir! + if( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime )) { Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path ); - FS_WriteGameInfo( gameinfo_path, &GameInfo ); - - return true; + return FS_WriteGameInfo( gameinfo_path, gi ); } return false; @@ -986,15 +1079,14 @@ static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_pa FS_ReadGameInfo ================ */ -static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime ) { - char *afile; + char *afile = FS_LoadDirectFile( filename, NULL ); - afile = (char *)FS_LoadFile( filepath, NULL, false ); - if( !afile ) return false; - - FS_InitGameInfo( GameInfo, gamedir ); + if( !afile ) + return false; + FS_InitGameInfo( GameInfo, gamedir, false, mtime ); FS_ParseGenericGameInfo( GameInfo, afile, true ); Mem_Free( afile ); @@ -1004,24 +1096,66 @@ static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, game /* ================ -FS_CheckForGameDir +FS_CheckForQuakeGameDir + +Checks if game directory resembles Quake Engine game directory +(some of checks may as well work with Xash gamedirs, it's not a bug) ================ */ -static qboolean FS_CheckForGameDir( const char *gamedir ) +static qboolean FS_CheckForQuakeGameDir( const char *gamedir ) { - // if directory contain config.cfg it's 100% gamedir - if( FS_FileExists( va( "%s/config.cfg", gamedir ), false )) - return true; + // if directory contain quake.rc or progs.dat it's 100% quake gamedir + // quake mods probably always archived, so check pak0.pak too + const char *files[] = { "progs.dat", "quake.rc" }; + char buf[MAX_SYSPATH]; + int i; - // if directory contain progs.dat it's 100% gamedir - if( FS_FileExists( va( "%s/progs.dat", gamedir ), false )) - return true; + // try to read pak0.pak first, most quake mods are archived + if( Q_snprintf( buf, sizeof( buf ), "%s/pak0.pak", gamedir ) > 0 ) + { + if( FS_SysFileExists( buf )) + { + if( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] ))) + return true; + } + } - // quake mods probably always archived but can missed config.cfg before first running - if( FS_FileExists( va( "%s/pak0.pak", gamedir ), false )) - return true; + // search it in the filesystem + for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) + { + if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 ) + { + if( FS_SysFileExists( buf )) + return true; + } + } - // NOTE; adds here some additional checks if you wished + return false; +} + +/* +=============== +FS_CheckForXashGameDir + +Checks if game directory resembles Xash3D game directory +=============== +*/ +static qboolean FS_CheckForXashGameDir( const char *gamedir ) +{ + // if directory contain gameinfo.txt or liblist.gam it's 100% gamedir + const char *files[] = { "gameinfo.txt", "liblist.gam" }; + int i; + + for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) + { + char buf[MAX_SYSPATH]; + + if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 ) + { + if( FS_SysFileExists( buf )) + return true; + } + } return false; } @@ -1031,80 +1165,73 @@ static qboolean FS_CheckForGameDir( const char *gamedir ) FS_ParseGameInfo ================ */ -static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) +static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir ) { - string liblist_path, gameinfo_path; - string default_gameinfo_path; - gameinfo_t tmpGameInfo; - qboolean haveUpdate = false; + char liblist_path[MAX_SYSPATH]; + char gameinfo_path[MAX_SYSPATH]; + char gamedir_path[MAX_SYSPATH]; + time_t liblist_mtime = -1; + time_t gameinfo_mtime = -1; - Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir ); - Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir ); - Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); + if( rodir ) + Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rodir, gamedir ); + else + Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s", gamedir ); - // here goes some RoDir magic... - if( COM_CheckStringEmpty( fs_rodir ) ) + if( !FS_CheckForXashGameDir( gamedir_path )) { - string filepath_ro, liblist_ro; - fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; - - FS_AllowDirectPaths( true ); - - Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir ); - - roLibListTime = FS_SysFileTime( liblist_ro ); - roGameInfoTime = FS_SysFileTime( filepath_ro ); - rwGameInfoTime = FS_SysFileTime( gameinfo_path ); - - if( roLibListTime > rwGameInfoTime ) + // check if we need to generate gameinfo for Quake + if( FS_CheckForQuakeGameDir( gamedir_path )) { - haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro ); - } - else if( roGameInfoTime > rwGameInfoTime ) - { - char *afile_ro = (char *)FS_LoadDirectFile( filepath_ro, NULL ); - - if( afile_ro ) - { - gameinfo_t gi; - - haveUpdate = true; - - FS_InitGameInfo( &gi, gamedir ); - FS_ParseGenericGameInfo( &gi, afile_ro, true ); - FS_WriteGameInfo( gameinfo_path, &gi ); - Mem_Free( afile_ro ); - } + // just generate stub gameinfo in memory + FS_InitGameInfo( GameInfo, gamedir, true, -1 ); + GameInfo->rodir = rodir; + return true; } - FS_AllowDirectPaths( false ); + // don't add empty or addon directories + return false; } - // if user change liblist.gam update the gameinfo.txt - if( FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false )) - FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); + Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir_path ); + Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir_path ); - // force to create gameinfo for specified game if missing - if(( FS_CheckForGameDir( gamedir ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) + liblist_mtime = FS_SysFileTime( liblist_path ); + gameinfo_mtime = FS_SysFileTime( gameinfo_path ); + + // in this function we never write new files for RoDir compatibility + // since RoDir is only FWGS feature, do gameinfo.txt conversion only + // for RwDir for those who worked with original Xash3D + if( !rodir ) { - memset( &tmpGameInfo, 0, sizeof( tmpGameInfo )); - - if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo )) + // !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!! + if( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime ) { - // now we have copy of game info from basedir but needs to change gamedir - Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path ); - Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder )); - FS_WriteGameInfo( gameinfo_path, &tmpGameInfo ); + if( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime )) + return true; } - else FS_CreateDefaultGameInfo( gameinfo_path ); } - if( !GameInfo || !FS_FileExists( gameinfo_path, false )) - return false; // no dest + // can we parse gameinfo.txt? + if( gameinfo_mtime >= 0 ) + { + if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime )) + { + GameInfo->rodir = rodir; + return true; + } + } + + // can we parse liblist.gam? + if( liblist_mtime >= 0 ) + { + if( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime )) + { + GameInfo->rodir = rodir; + return true; + } + } - if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo )) - return true; return false; } @@ -1117,47 +1244,92 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) { int i; qboolean isGameDir = flags & FS_GAMEDIR_PATH; - - GI->added = true; + char buf[MAX_VA_STRING]; if( !COM_CheckString( dir )) return; + Con_Printf( "%s( %s )\n", __func__, dir ); + // add the common game directory // recursive gamedirs // for example, czeror->czero->cstrike->valve for( i = 0; i < FI.numgames; i++ ) { - if( !Q_strnicmp( FI.games[i]->gamefolder, dir, 64 )) + if( !Q_stricmp( FI.games[i]->gamefolder, dir )) { - Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, FI.games[i]->gamefolder, FI.games[i]->basedir ); - if( !FI.games[i]->added && Q_stricmp( FI.games[i]->gamefolder, FI.games[i]->basedir )) - { - FI.games[i]->added = true; - FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); - } + dir = FI.games[i]->gamefolder; // fixup directory case + + if( FI.games[i]->added ) // already added, refusing + break; + + // don't add our game directory as base dir to prevent cyclic dependency + if( Q_stricmp( FI.games[i]->basedir, GI->gamefolder )) + break; + + // don't add directory which basedir is equal to this gamefolder to prevent + // endless loop + if( Q_stricmp( FI.games[i]->basedir, FI.games[i]->gamefolder )) + break; + + Con_Reportf( "%s: adding recursive basedir %s\n", __func__, FI.games[i]->basedir ); + FI.games[i]->added = true; + FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH)); break; } } - if( COM_CheckStringEmpty( fs_rodir ) ) + if( COM_CheckStringEmpty( fs_rodir )) { // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH - uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + uint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); if( isGameDir ) - newFlags |= FS_GAMERODIR_PATH; + SetBits( new_flags, FS_GAMERODIR_PATH ); FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rodir, dir ); + FS_AddGameDirectory( buf, new_flags ); FS_AllowDirectPaths( false ); } if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); + { + Q_snprintf( buf, sizeof( buf ), "%s/" DEFAULT_DOWNLOADED_DIRECTORY, dir ); + FS_AddGameDirectory( buf, FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } + Q_snprintf( buf, sizeof( buf ), "%s/", dir ); + FS_AddGameDirectory( buf, flags ); + + if( FBitSet( flags, FS_MOUNT_HD )) + { + Q_snprintf( buf, sizeof( buf ), "%s_hd/", dir ); + FS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } + + if( FBitSet( flags, FS_MOUNT_ADDON )) + { + Q_snprintf( buf, sizeof( buf ), "%s_addon/", dir ); + FS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } + + if( FBitSet( flags, FS_MOUNT_LV )) + { + Q_snprintf( buf, sizeof( buf ), "%s_lv/", dir ); + FS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } + + if( FBitSet( flags, FS_MOUNT_L10N ) && COM_CheckStringEmpty( fs_language ) && Q_isalpha( fs_language )) + { + Q_snprintf( buf, sizeof( buf ), "%s_%s/", dir, fs_language ); + FS_AddGameDirectory( buf, flags|FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } + if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + { + Q_snprintf( buf, sizeof( buf ), "%s/" DEFAULT_CUSTOM_DIRECTORY, dir ); + FS_AddGameDirectory( buf, FS_NOWRITE_PATH|FS_CUSTOM_PATH ); + } } /* @@ -1165,34 +1337,50 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) FS_Rescan ================ */ -void FS_Rescan( void ) +void FS_Rescan( uint32_t flags, const char *language ) { const char *str; - const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; - Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + Con_Reportf( "%s( %s )\n", __func__, GI->title ); FS_ClearSearchPath(); -#if XASH_IOS - { - FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); - FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); - } -#else + flags &= FS_MOUNT_HD|FS_MOUNT_LV|FS_MOUNT_ADDON|FS_MOUNT_L10N; + + if( FBitSet( flags, FS_MOUNT_L10N )) + Q_strncpy( fs_language, language, sizeof( fs_language )); + else + fs_language[0] = 0; + str = getenv( "XASH3D_EXTRAS_PAK1" ); if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + FS_MountArchive_Fullpath( str, FS_NOWRITE_PATH|FS_CUSTOM_PATH ); str = getenv( "XASH3D_EXTRAS_PAK2" ); if( COM_CheckString( str )) - FS_AddArchive_Fullpath( str, NULL, extrasFlags ); -#endif + FS_MountArchive_Fullpath( str, FS_NOWRITE_PATH|FS_CUSTOM_PATH ); if( Q_stricmp( GI->basedir, GI->gamefolder )) - FS_AddGameHierarchy( GI->basedir, 0 ); + FS_AddGameHierarchy( GI->basedir, flags ); if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) - FS_AddGameHierarchy( GI->falldir, 0 ); - FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); + FS_AddGameHierarchy( GI->falldir, flags ); + + GI->added = true; + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH | flags ); +} + +/* +=============== +FS_Gamedir + +Allows engine to know game directory before gameinfo is initialized +=============== +*/ +static const char *FS_Gamedir( void ) +{ + if( GI ) + return GI->gamefolder; + + return fs_gamedir; } /* @@ -1202,17 +1390,16 @@ FS_LoadGameInfo can be passed null arg ================ */ -void FS_LoadGameInfo( const char *rootfolder ) +static void FS_LoadGameInfo( uint32_t flags, const char *language ) { int i; // lock uplevel of gamedir for read\write - fs_ext_path = false; + FS_AllowDirectPaths( false ); - if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); - Con_Reportf( "FS_LoadGameInfo( %s )\n", fs_gamedir ); + Con_Reportf( "%s( %s, 0x%x, %s )\n", __func__, fs_gamedir, flags, language ); - // clear any old pathes + // clear any old paths FS_ClearSearchPath(); // validate gamedir @@ -1227,7 +1414,15 @@ void FS_LoadGameInfo( const char *rootfolder ) FI.GameInfo = FI.games[i]; - FS_Rescan(); // create new filesystem + if( FI.GameInfo->rodir ) + { + // ensure we have directory in rwdir + char buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space + Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rootdir, FI.GameInfo->gamefolder ); + FS_CreatePath( buf ); + } + + FS_Rescan( flags, language ); // create new filesystem } /* @@ -1242,6 +1437,10 @@ static qboolean FS_CheckForCrypt( const char *dllname ) file_t *f; int key; + // this encryption is specific to DLLs + if( Q_stricmp( COM_FileExtension( dllname ), "dll" )) + return false; + f = FS_Open( dllname, "rb", false ); if( !f ) return false; @@ -1252,6 +1451,26 @@ static qboolean FS_CheckForCrypt( const char *dllname ) return ( key == 0x12345678 ) ? true : false; } +static int FS_StripIdiotRelativePath( const char *dllname, const char *gamefolder ) +{ + string idiot_relpath; + int len; + + if(( len = Q_snprintf( idiot_relpath, sizeof( idiot_relpath ), "../%s/", gamefolder )) >= 4 ) + { + if( !Q_strnicmp( dllname, idiot_relpath, len )) + return len; + + // try backslashes + idiot_relpath[1] = '\\'; + idiot_relpath[len - 1] = '\\'; + if( !Q_strnicmp( dllname, idiot_relpath, len )) + return len; + } + + return 0; +} + /* ================== FS_FindLibrary @@ -1261,105 +1480,119 @@ search for library, assume index is valid */ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo ) { + string fixedname; searchpath_t *search; - int index, start = 0, i, len; - - fs_ext_path = directpath; + int index, start = 0, len; // check for bad exports if( !COM_CheckString( dllname )) return false; - // HACKHACK remove absoulte path to valve folder - if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) - start += 9; + FS_AllowDirectPaths( directpath ); - // replace all backward slashes - len = Q_strlen( dllname ); - - for( i = 0; i < len; i++ ) + // HACKHACK remove relative path to game folder + if( !Q_strnicmp( dllname, "..", 2 )) { - if( dllname[i+start] == '\\' ) dllInfo->shortPath[i] = '/'; - else dllInfo->shortPath[i] = Q_tolower( dllname[i+start] ); + // some modders put relative path to themselves??? + len = FS_StripIdiotRelativePath( dllname, GI->gamefolder ); + + if( len == 0 ) // or put relative path to Half-Life game libs + len = FS_StripIdiotRelativePath( dllname, "valve" ); + start += len; } - dllInfo->shortPath[i] = '\0'; - COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + Q_strnlwr( &dllname[start], dllInfo->shortPath, sizeof( dllInfo->shortPath )); // always in lower case (why?) + COM_FixSlashes( dllInfo->shortPath ); // replace all backward slashes + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT, sizeof( dllInfo->shortPath )); // apply ext if forget - search = FS_FindFile( dllInfo->shortPath, &index, false ); + search = FS_FindFile( dllInfo->shortPath, &index, fixedname, sizeof( fixedname ), false ); - if( !search && !directpath ) + if( search ) { - fs_ext_path = false; + Q_strncpy( dllInfo->shortPath, fixedname, sizeof( dllInfo->shortPath )); + } + else if( !directpath ) + { + FS_AllowDirectPaths( false ); // trying check also 'bin' folder for indirect paths - Q_strncpy( dllInfo->shortPath, dllname, sizeof( dllInfo->shortPath )); - search = FS_FindFile( dllInfo->shortPath, &index, false ); - if( !search ) return false; // unable to find + search = FS_FindFile( dllname, &index, fixedname, sizeof( fixedname ), false ); + if( !search ) + return false; // unable to find + + Q_strncpy( dllInfo->shortPath, fixedname, sizeof( dllInfo->shortPath )); } - dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); + dllInfo->encrypted = dllInfo->custom_loader = false; // predict state - if( index < 0 && !dllInfo->encrypted && search ) + if( search && index >= 0 ) // when library is available through VFS { - Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), - "%s%s", search->filename, dllInfo->shortPath ); - dllInfo->custom_loader = false; // we can loading from disk and use normal debugging - } - else - { - // NOTE: if search is NULL let the OS found library himself - Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); - if( search && search->type != SEARCHPATH_PLAIN ) + if( search->type == SEARCHPATH_PLAIN ) // is it on the disk? (intentionally omit pk3dir here) { -#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) - Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " - "and will be removed in the future\n", __FUNCTION__ ); - dllInfo->custom_loader = true; -#else - Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " - "this platform\n", __FUNCTION__ ); - dllInfo->custom_loader = false; -#endif + // NOTE: gamedll might resolve it's own path using dladdr() and expects absolute path + // NOTE: the only allowed case when searchpath is set by absolute path is the RoDir + // rather than figuring out whether path is absolute, just check if it matches + if( COM_CheckStringEmpty( fs_rodir ) && !Q_strnicmp( search->filename, fs_rodir, Q_strlen( fs_rodir ))) + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), "%s%s", search->filename, dllInfo->shortPath ); + } + else + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), "%s/%s%s", fs_rootdir, search->filename, dllInfo->shortPath ); + } } else { - dllInfo->custom_loader = false; + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), "%s", dllInfo->shortPath ); +#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) + Con_Printf( S_WARN "%s: loading libraries from archives is non portable and might fail on other platforms\n", __func__ ); + dllInfo->custom_loader = true; +#else + Con_Printf( S_WARN "%s: loading libraries from archives is unsupported on this platform\n", __func__ ); +#endif } } - fs_ext_path = false; // always reset direct paths + else + { + // NOTE: if search is NULL let OS to find the library + Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + } + + FS_AllowDirectPaths( false ); // always reset direct paths return true; } -poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) +static poolhandle_t Mem_AllocPoolStub( const char *name, const char *filename, int fileline ) { - return 0xDEADC0DE; + return (poolhandle_t)0xDEADC0DE; } -void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) +static void Mem_FreePoolStub( poolhandle_t *poolptr, const char *filename, int fileline ) { // stub } -void* _Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +static void *Mem_AllocStub( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) { - if( clear ) return calloc( 1, size ); - return malloc( size ); + void *ptr = malloc( size ); + if( clear ) memset( ptr, 0, size ); + return ptr; } -void* _Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +static void *Mem_ReallocStub( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) { return realloc( memptr, size ); } -void _Mem_Free( void *data, const char *filename, int fileline ) +static void Mem_FreeStub( void *data, const char *filename, int fileline ) { free( data ); } -void _Con_Printf( const char *fmt, ... ) +static void Con_PrintfStub( const char *fmt, ... ) { va_list ap; @@ -1368,7 +1601,7 @@ void _Con_Printf( const char *fmt, ... ) va_end( ap ); } -void _Sys_Error( const char *fmt, ... ) +static void Sys_ErrorStub( const char *fmt, ... ) { va_list ap; @@ -1379,95 +1612,165 @@ void _Sys_Error( const char *fmt, ... ) exit( 1 ); } +static void *Sys_GetNativeObjectStub( const char *object ) +{ + return NULL; +} + +static void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir ) +{ + stringlist_t dirs; + int i; + + stringlistinit( &dirs ); + listdirectory( &dirs, path, true ); + stringlistsort( &dirs ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !FS_SysFolderExists( dirs.strings[i] )) + continue; + + if( !Q_stricmp( fs_basedir, dirs.strings[i] )) + *has_base_dir = true; + + if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) + *has_game_dir = true; + + if( *has_base_dir && *has_game_dir ) + break; + } + + stringlistfreecontents( &dirs ); +} /* ================ FS_Init ================ */ -qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) +qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { - stringlist_t dirs; - qboolean hasBaseDir = false; - qboolean hasGameDir = false; - int i; + stringlist_t dirs; + int i, rodir_num_games; + char buf[MAX_SYSPATH]; FS_InitMemory(); -#if !XASH_WIN32 - fs_caseinsensitive = caseinsensitive; + +#if XASH_ANDROID + FS_InitAndroid(); #endif Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); + fs_language[0] = 0; - // add readonly directories first + // validate user input + if( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir )) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; + } + + // look for game directories in RwDir first + // this whole check only required to fallback to base directory + // (i.e. default game directory hardcoded in the executable file) + { + qboolean has_base_dir = false; + qboolean has_game_dir = false; + + FS_ValidateDirectories( "./", &has_base_dir, &has_game_dir ); + + if( !has_game_dir ) + { + // look for game directories in RoDir now + if( COM_CheckStringEmpty( fs_rodir )) + FS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir ); + + if( !has_game_dir ) + { + Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir ); + + // revert to base game directory + if( has_base_dir ) + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); + } + } + } + + // now start building first level of directory hierarchy if( COM_CheckStringEmpty( fs_rodir )) { - if( !Q_stricmp( fs_rodir, fs_rootdir )) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - return false; - } + Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir ); + FS_AddGameDirectory( buf, FS_STATIC_PATH|FS_NOWRITE_PATH ); + } + FS_AddGameDirectory( "./", FS_STATIC_PATH ); + // but scan rodir for games first + if( COM_CheckStringEmpty( fs_rodir )) + { stringlistinit( &dirs ); - listdirectory( &dirs, fs_rodir, false ); + listdirectory( &dirs, fs_rodir, true ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rodir, dirs.strings[i] ); - char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rootdir, dirs.strings[i] ); - - // check if it's a directory - if( !FS_SysFolderExists( roPath )) + Q_snprintf( buf, sizeof( buf ), "%s/%s", fs_rodir, dirs.strings[i] ); + if( !FS_SysFolderExists( buf )) continue; - // no need to check folders here, FS_CreatePath will not fail - FS_CreatePath( rwPath ); + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] )); + + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true )) + FI.numgames++; } stringlistfreecontents( &dirs ); } - // validate directories + rodir_num_games = FI.numgames; + stringlistinit( &dirs ); - listdirectory( &dirs, "./", false ); + listdirectory( &dirs, "./", true ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - if( !Q_stricmp( fs_basedir, dirs.strings[i] )) - hasBaseDir = true; + int j; - if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) - hasGameDir = true; - } - - if( !hasGameDir ) - { - Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir ); - if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); - } - - // build list of game directories here - FS_AddGameDirectory( "./", 0 ); - - for( i = 0; i < dirs.numstrings; i++ ) - { - if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_strcmp( dirs.strings[i], ".." ) && !fs_ext_path )) + if( !FS_SysFolderExists( dirs.strings[i] )) continue; - if( FI.games[FI.numgames] == NULL ) - FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + // update gameinfo from rwdir, if it's newer + // with rodir we should never create new gameinfos anymore, + // so this only catches possible user changes + for( j = 0; j < rodir_num_games; j++ ) + { + if( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder )) + { + gameinfo_t gi; + if( FS_ParseGameInfo( dirs.strings[i], &gi, false )) + { + if( gi.mtime > FI.games[j]->mtime ) + *FI.games[j] = gi; + } + break; + } + } - if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] )); + + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false )) FI.numgames++; // added } stringlistfreecontents( &dirs ); - Con_Reportf( "FS_Init: done\n" ); + + Con_Reportf( "%s: done\n", __func__ ); return true; } @@ -1485,9 +1788,17 @@ FS_Shutdown void FS_ShutdownStdio( void ) { int i; + // release gamedirs for( i = 0; i < FI.numgames; i++ ) - if( FI.games[i] ) Mem_Free( FI.games[i] ); + { + if( FI.games[i] ) + { + Mem_Free( FI.games[i] ); + FI.games[i] = NULL; + } + } + FI.numgames = 0; FS_ClearSearchPath(); // release all wad files too Mem_FreePool( &fs_mempool ); @@ -1510,21 +1821,7 @@ void FS_Path_f( void ) { string info; - switch( s->type ) - { - case SEARCHPATH_PAK: - FS_PrintPAKInfo( info, sizeof( info ), s->pack ); - break; - case SEARCHPATH_WAD: - FS_PrintWADInfo( info, sizeof( info ), s->wad ); - break; - case SEARCHPATH_ZIP: - FS_PrintZIPInfo( info, sizeof( info ), s->zip ); - break; - case SEARCHPATH_PLAIN: - Q_strncpy( info, s->filename, sizeof( info )); - break; - } + s->pfnPrintInfo( s, info, sizeof(info) ); Con_Printf( "%s", info ); @@ -1547,9 +1844,13 @@ Internal function used to determine filetime */ int FS_SysFileTime( const char *filename ) { +#if XASH_WIN32 + struct _stat buf; + if( _wstat( FS_PathToWideChar( filename ), &buf ) < 0 ) +#else struct stat buf; - - if( stat( filename, &buf ) == -1 ) + if( stat( filename, &buf ) < 0 ) +#endif return -1; return buf.st_mtime; @@ -1564,9 +1865,10 @@ Internal function used to create a file_t and open the relevant non-packed file */ file_t *FS_SysOpen( const char *filepath, const char *mode ) { - file_t *file; - int mod, opt; - uint ind; + file_t *file; + int mod, opt, fd = -1; + qboolean memfile = false; + uint ind; // Parse the mode string switch( mode[0] ) @@ -1606,36 +1908,53 @@ file_t *FS_SysOpen( const char *filepath, const char *mode ) } } - file = (file_t *)Mem_Calloc( fs_mempool, sizeof( *file )); - file->filetime = FS_SysFileTime( filepath ); - file->ungetc = EOF; - -#if XASH_WIN32 - file->handle = _wopen( FS_PathToWideChar(filepath), mod | opt, 0666 ); -#else - file->handle = open( filepath, mod|opt, 0666 ); -#endif - -#if !XASH_WIN32 - if( file->handle < 0 ) + // the 'm' flag let's user to create temporary file in memory + // through so-called "anonymous files" + if( Q_strchr( mode, 'm' )) { - const char *ffilepath = FS_FixFileCase( filepath ); - if( ffilepath != filepath ) - file->handle = open( ffilepath, mod|opt, 0666 ); - if( file->handle >= 0 ) - FS_BackupFileName( file, ffilepath, mod|opt ); +#if HAVE_MEMFD_CREATE +#ifndef MFD_NOEXEC_SEAL +#define MFD_NOEXEC_SEAL 8U +#endif + fd = memfd_create( filepath, MFD_CLOEXEC | MFD_NOEXEC_SEAL ); + + // through fcntl() and MFD_ALLOW_SEALING we could enforce + // read-write flags but we don't really care about them yet + if( fd < 0 ) + Con_Printf( S_ERROR "%s: can't create anonymous file %s: %s", __func__, filepath, strerror( errno )); + else memfile = true; +#endif + // if it's unsupported, we can open it on disk } - else - FS_BackupFileName( file, filepath, mod|opt ); -#endif - if( file->handle < 0 ) + if( fd < 0 ) { - Mem_Free( file ); +#if XASH_WIN32 + fd = _wopen( FS_PathToWideChar( filepath ), mod | opt, 0666 ); +#else + fd = open( filepath, mod|opt, 0666 ); +#endif + } + + if( fd < 0 ) + { + if( errno != ENOENT ) + Con_Printf( S_ERROR "%s: can't open file %s: %s\n", __func__, filepath, strerror( errno )); + return NULL; } + file = (file_t *)Mem_Calloc( fs_mempool, sizeof( *file )); + file->filetime = memfile ? 0 : FS_SysFileTime( filepath ); + file->ungetc = EOF; + file->handle = fd; +#if !XASH_WIN32 + if( !memfile ) + FS_BackupFileName( file, filepath, mod|opt ); +#endif + + file->searchpath = NULL; file->real_length = lseek( file->handle, 0, SEEK_END ); // uncomment do disable write @@ -1661,16 +1980,23 @@ static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos } */ -file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +file_t *FS_OpenHandle( searchpath_t *searchpath, int handle, fs_offset_t offset, fs_offset_t len ) { file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); #ifndef XASH_REDUCE_FD #ifdef HAVE_DUP file->handle = dup( handle ); #else - file->handle = open( syspath, O_RDONLY|O_BINARY ); + file->handle = open( searchpath->filename, O_RDONLY|O_BINARY ); #endif + if( file->handle < 0 ) + { + Con_Printf( S_ERROR "%s: couldn't create fd for %s:0x%lx: %s\n", __func__, searchpath->filename, (long)offset, strerror( errno )); + Mem_Free( file ); + return NULL; + } + if( lseek( file->handle, offset, SEEK_SET ) == -1 ) { Mem_Free( file ); @@ -1688,10 +2014,19 @@ file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_o file->offset = offset; file->position = 0; file->ungetc = EOF; + file->searchpath = searchpath; return file; } +#if !defined( S_ISREG ) +#define S_ISREG( m ) ( FBitSet( m, S_IFMT ) == S_IFREG ) +#endif + +#if !defined( S_ISDIR ) +#define S_ISDIR( m ) ( FBitSet( m, S_IFMT ) == S_IFDIR ) +#endif + /* ================== FS_SysFileExists @@ -1699,55 +2034,18 @@ FS_SysFileExists Look for a file in the filesystem only ================== */ -qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) +qboolean FS_SysFileExists( const char *path ) { #if XASH_WIN32 - int desc; - - if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 ) - return false; - - close( desc ); - return true; -#elif XASH_POSIX - int ret; + struct _stat buf; + if( _wstat( FS_PathToWideChar( path ), &buf ) < 0 ) +#else struct stat buf; - - ret = stat( path, &buf ); - - // speedup custom path search - if( caseinsensitive && ( ret < 0 ) ) - { - const char *fpath = FS_FixFileCase( path ); - if( fpath != path ) - ret = stat( fpath, &buf ); - } - - if( ret < 0 ) + if( stat( path, &buf ) < 0 ) +#endif return false; return S_ISREG( buf.st_mode ); -#else -#error -#endif -} - -/* -================== -FS_SetCurrentDirectory - -Sets current directory, path should be in UTF-8 encoding -================== -*/ -int FS_SetCurrentDirectory( const char *path ) -{ -#if XASH_WIN32 - return SetCurrentDirectoryW( FS_PathToWideChar(path) ); -#elif XASH_POSIX - return !chdir( path ); -#else -#error -#endif } /* @@ -1760,21 +2058,93 @@ Look for a existing folder qboolean FS_SysFolderExists( const char *path ) { #if XASH_WIN32 - DWORD dwFlags = GetFileAttributes( path ); - - return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); -#elif XASH_POSIX + struct _stat buf; + if( _wstat( FS_PathToWideChar( path ), &buf ) < 0 ) +#else struct stat buf; - if( stat( path, &buf ) < 0 ) +#endif return false; return S_ISDIR( buf.st_mode ); +} + +/* +============== +FS_SysFileOrFolderExists + +Check if filesystem entry exists at all, don't mind the type +============== +*/ +qboolean FS_SysFileOrFolderExists( const char *path ) +{ +#if XASH_WIN32 + struct _stat buf; + return _wstat( FS_PathToWideChar( path ), &buf ) >= 0; #else -#error + struct stat buf; + return stat( path, &buf ) >= 0; #endif } +/* +================== +FS_SetCurrentDirectory + +Sets current directory, path should be in UTF-8 encoding +TODO: make this non-fatal +================== +*/ +int FS_SetCurrentDirectory( const char *path ) +{ +#if XASH_WIN32 + if( !SetCurrentDirectoryW( FS_PathToWideChar( path ))) + { + const DWORD fm_flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK; + DWORD errorcode; + wchar_t wide_buf[1024]; + char buf[1024]; + + FormatMessageW( fm_flags, NULL, GetLastError(), 0, wide_buf, sizeof( wide_buf ) / sizeof( wide_buf[0] ), NULL ); + Q_UTF16ToUTF8( buf, sizeof( buf ), wide_buf, sizeof( wide_buf ) / sizeof( wide_buf[0] )); + + Sys_Error( "Changing directory to %s failed: %s\n", path, buf ); + return false; + } +#elif XASH_POSIX + if( chdir( path ) < 0 ) + { + Sys_Error( "Changing directory to %s failed: %s\n", path, strerror( errno )); + return false; + } +#else + // it may be fine for some systems to skip chdir + Con_Printf( "%s: not implemented, ignoring...\n", __func__ ); + return true; +#endif + + Con_Printf( "%s is working directory now\n", path ); + return true; +} + +/* +================== +FS_GetRootDirectory + +Returns writable root directory path +================== +*/ +qboolean FS_GetRootDirectory( char *path, size_t size ) +{ + size_t dirlen = Q_strlen( fs_rootdir ); + + if( dirlen >= size ) // check for possible overflow + return false; + + Q_strncpy( path, fs_rootdir, size ); + return true; +} + /* ==================== FS_FindFile @@ -1785,77 +2155,65 @@ Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ -searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +searchpath_t *FS_FindFile( const char *name, int *index, char *fixedname, size_t len, qboolean gamedironly ) { searchpath_t *search; - char *pEnvPath; // search through the path, one element at a time for( search = fs_searchpaths; search; search = search->next ) { + int pack_ind; + if( gamedironly & !FBitSet( search->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) continue; - // is the element a pak file? - if( search->type == SEARCHPATH_PAK ) + pack_ind = search->pfnFindFile( search, name, fixedname, len ); + if( pack_ind >= 0 ) { - int pack_ind = FS_FindFilePAK( search->pack, name ); - if( pack_ind >= 0 ) - { - if( index ) *index = pack_ind; - return search; - } - } - else if( search->type == SEARCHPATH_WAD ) - { - int pack_ind = FS_FindFileWAD( search->wad, name ); - if( pack_ind >= 0 ) - { - if( index ) *index = pack_ind; - return search; - } - } - else if( search->type == SEARCHPATH_ZIP ) - { - int pack_ind = FS_FindFileZIP( search->zip, name ); - if( pack_ind >= 0 ) - { - if( index ) *index = pack_ind; - return search; - } - } - else - { - char netpath[MAX_SYSPATH]; - - Q_sprintf( netpath, "%s%s", search->filename, name ); - - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) - { - if( index != NULL ) *index = -1; - return search; - } + if( index ) + *index = pack_ind; + return search; } } if( fs_ext_path ) { - char netpath[MAX_SYSPATH]; + char netpath[MAX_SYSPATH], dirpath[MAX_SYSPATH]; - // clear searchpath - search = &fs_directpath; - memset( search, 0, sizeof( searchpath_t )); + // HACKHACK: when the code wants to access to root game directory + // it often uses ../ in conjunction with FS_AllowDirectPath + // it results in the access above the root game directory + // FS_Open with "write" flag doesn't have this problem because + // it looks up relative to fs_writepath + // the correct solution MIGHT be using fs_writepath instead of fs_rootdir here? + // but this need to be properly tested so as a temporary solution + // just strip ../ + if( !Q_strncmp( name, "../", 3 )) + name += 3; - // root folder has a more priority than netpath - Q_strncpy( search->filename, fs_rootdir, sizeof( search->filename )); - Q_strcat( search->filename, PATH_SPLITTER ); - Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); + Q_snprintf( dirpath, sizeof( dirpath ), "%s/", fs_rootdir ); + Q_snprintf( netpath, sizeof( netpath ), "%s%s", dirpath, name ); - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) + if( FS_SysFileExists( netpath )) { + static searchpath_t fs_directpath; + + // clear old dir cache, if needed + if( 0 != Q_strcmp( fs_directpath.filename, dirpath )) + { + if( fs_directpath.pfnClose ) + fs_directpath.pfnClose( &fs_directpath ); + FS_InitDirectorySearchpath( &fs_directpath, dirpath, 0 ); + } + + // just copy the name, we don't do case sensitivity fix there + if( fixedname ) + Q_strncpy( fixedname, name, len ); + if( index != NULL ) - *index = -1; - return search; + *index = 0; + + return &fs_directpath; } } @@ -1865,6 +2223,33 @@ searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) return NULL; } +/* +=========================== +FS_FullPathToRelativePath + +Converts full path to the relative path considering current searchpaths +(do not use this function, implemented only for VFileSystem009) +=========================== +*/ +qboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size ) +{ + searchpath_t *sp; + + for( sp = fs_searchpaths; sp; sp = sp->next ) + { + size_t splen = Q_strlen( sp->filename ); + + if( !Q_strnicmp( sp->filename, src, splen )) + { + Q_strncpy( dst, src + splen + 1, size ); + return true; + } + } + + Q_strncpy( dst, src, size ); + return false; +} + /* =========== @@ -1875,35 +2260,17 @@ Look for a file in the search paths and open it in read-only mode */ file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ) { - searchpath_t *search; - int pack_ind; + searchpath_t *search; + char netpath[MAX_SYSPATH]; + int pack_ind; - search = FS_FindFile( filename, &pack_ind, gamedironly ); + search = FS_FindFile( filename, &pack_ind, netpath, sizeof( netpath ), gamedironly ); // not found? if( search == NULL ) return NULL; - switch( search->type ) - { - case SEARCHPATH_PAK: - return FS_OpenPackedFile( search->pack, pack_ind ); - case SEARCHPATH_WAD: - return NULL; // let W_LoadFile get lump correctly - case SEARCHPATH_ZIP: - return FS_OpenZipFile( search->zip, pack_ind ); - default: - if( pack_ind < 0 ) - { - char path [MAX_SYSPATH]; - - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysOpen( path, mode ); - } - } - - return NULL; + return search->pfnOpenFile( search, netpath, mode, pack_ind ); } /* @@ -1922,14 +2289,17 @@ Open a file. The syntax is the same as fopen */ file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) { - // some stupid mappers used leading '/' or '\' in path to models or sounds + if( !fs_searchpaths ) + return NULL; + + // some mappers used leading '/' or '\' in path to models or sounds if( filepath[0] == '/' || filepath[0] == '\\' ) filepath++; if( filepath[0] == '/' || filepath[0] == '\\' ) filepath++; - if( FS_CheckNastyPath( filepath, false )) + if( FS_CheckNastyPath( filepath )) return NULL; // if the file is opened in "write", "append", or "read/write" mode @@ -1938,8 +2308,11 @@ file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) char real_path[MAX_SYSPATH]; // open the file on disk directly - Q_sprintf( real_path, "%s/%s", fs_writedir, filepath ); - FS_CreatePath( real_path );// Create directories up to the file + if( !FS_FixFileCase( fs_writepath->dir, filepath, real_path, sizeof( real_path ), true )) + return NULL; + + FS_CreatePath( real_path ); // Create directories up to the file + return FS_SysOpen( real_path, mode ); } @@ -1961,8 +2334,16 @@ int FS_Close( file_t *file ) FS_BackupFileName( file, NULL, 0 ); if( file->handle >= 0 ) + { if( close( file->handle )) return EOF; + } + + if( file->ztk ) + { + inflateEnd( &file->ztk->zstream ); + Mem_Free( file->ztk ); + } Mem_Free( file ); return 0; @@ -2015,7 +2396,7 @@ fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ) FS_Purge( file ); // write the buffer and update the position - result = write( file->handle, data, (fs_offset_t)datasize ); + result = write( file->handle, data, datasize ); file->position = lseek( file->handle, 0, SEEK_CUR ); if( file->real_length < file->position ) @@ -2035,8 +2416,9 @@ Read up to "buffersize" bytes from a file */ fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ) { - fs_offset_t count, done; + fs_offset_t done; fs_offset_t nb; + fs_offset_t count; // nothing to copy if( buffersize == 0 ) return 1; @@ -2055,19 +2437,106 @@ fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ) if( file->buff_ind < file->buff_len ) { count = file->buff_len - file->buff_ind; + count = ( buffersize > count ) ? count : (fs_offset_t)buffersize; - done += ((fs_offset_t)buffersize > count ) ? count : (fs_offset_t)buffersize; - memcpy( buffer, &file->buff[file->buff_ind], done ); - file->buff_ind += done; + done += count; + memcpy( buffer, &file->buff[file->buff_ind], count ); + file->buff_ind += count; - buffersize -= done; + buffersize -= count; if( buffersize == 0 ) return done; } // NOTE: at this point, the read buffer is always empty - FS_EnsureOpenFile( file ); + FS_EnsureOpenFile( file ); // FIXME: broken XASH_REDUCE_FD in case of compressed files! + + if( FBitSet( file->flags, FILE_DEFLATED )) + { + // If the file is compressed, it's more complicated... + // We cycle through a few operations until we have read enough data + while( buffersize > 0 ) + { + ztoolkit_t *ztk = file->ztk; + int error; + + // NOTE: at this point, the read buffer is always empty + + // If "input" is also empty, we need to refill it + if( ztk->in_ind == ztk->in_len ) + { + // If we are at the end of the file + if( file->position == file->real_length ) + return done; + + count = (fs_offset_t)( ztk->comp_length - ztk->in_position ); + if( count > (fs_offset_t)sizeof( ztk->input )) + count = (fs_offset_t)sizeof( ztk->input ); + lseek( file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET ); + if( read( file->handle, ztk->input, count ) != count ) + { + Con_Printf( "%s: unexpected end of file\n", __func__ ); + break; + } + + ztk->in_ind = 0; + ztk->in_len = count; + ztk->in_position += count; + } + + ztk->zstream.next_in = &ztk->input[ztk->in_ind]; + ztk->zstream.avail_in = (unsigned int)( ztk->in_len - ztk->in_ind ); + + // Now that we are sure we have compressed data available, we need to determine + // if it's better to inflate it in "file->buff" or directly in "buffer" + + // Inflate the data in "file->buff" + if( buffersize < sizeof( file->buff ) / 2 ) + { + ztk->zstream.next_out = file->buff; + ztk->zstream.avail_out = sizeof( file->buff ); + } + else + { + ztk->zstream.next_out = &((unsigned char*)buffer)[done]; + ztk->zstream.avail_out = (unsigned int)buffersize; + } + + error = inflate( &ztk->zstream, Z_SYNC_FLUSH ); + if( error != Z_OK && error != Z_STREAM_END ) + { + Con_Printf( "%s: Can't inflate file (%d)\n", __func__, error ); + break; + } + ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; + + if( buffersize < sizeof( file->buff ) / 2 ) + { + file->buff_len = (fs_offset_t)sizeof( file->buff ) - ztk->zstream.avail_out; + file->position += file->buff_len; + + // Copy the requested data in "buffer" (as much as we can) + count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; + memcpy( &((unsigned char*)buffer)[done], file->buff, count ); + file->buff_ind = count; + } + else + { + count = (fs_offset_t)( buffersize - ztk->zstream.avail_out ); + file->position += count; + + // Purge cached data + FS_Purge( file ); + } + + done += count; + buffersize -= count; + } + + return done; + } + // we must take care to not read after the end of the file count = file->real_length - file->position; @@ -2077,7 +2546,7 @@ fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ) if( count > (fs_offset_t)buffersize ) count = (fs_offset_t)buffersize; lseek( file->handle, file->offset + file->position, SEEK_SET ); - nb = read (file->handle, &((byte *)buffer)[done], count ); + nb = read( file->handle, &((byte *)buffer)[done], count ); if( nb > 0 ) { @@ -2159,7 +2628,7 @@ int FS_VPrintf( file_t *file, const char *format, va_list ap ) while( 1 ) { tempbuff = (char *)Mem_Malloc( fs_mempool, buff_size ); - len = Q_vsprintf( tempbuff, format, ap ); + len = Q_vsnprintf( tempbuff, buff_size, format, ap ); if( len >= 0 && len < buff_size ) break; @@ -2198,7 +2667,7 @@ FS_UnGetc Put a character back into the read buffer (only supports one character!) ==================== */ -int FS_UnGetc( file_t *file, byte c ) +int FS_UnGetc( file_t *file, char c ) { // If there's already a character waiting to be read if( file->ungetc != EOF ) @@ -2215,7 +2684,7 @@ FS_Gets Same as fgets ==================== */ -int FS_Gets( file_t *file, byte *string, size_t bufsize ) +int FS_Gets( file_t *file, char *string, size_t bufsize ) { int c, end = 0; @@ -2237,7 +2706,7 @@ int FS_Gets( file_t *file, byte *string, size_t bufsize ) c = FS_Getc( file ); if( c != '\n' ) - FS_UnGetc( file, (byte)c ); + FS_UnGetc( file, c ); } return c; @@ -2248,6 +2717,8 @@ int FS_Gets( file_t *file, byte *string, size_t bufsize ) FS_Seek Move the position index in a file +NOTE: when porting code, check return value! +NOTE: it's not compatible with lseek! ==================== */ int FS_Seek( file_t *file, fs_offset_t offset, int whence ) @@ -2281,11 +2752,59 @@ int FS_Seek( file_t *file, fs_offset_t offset, int whence ) // Purge cached data FS_Purge( file ); + if( FBitSet( file->flags, FILE_DEFLATED )) + { + // Seeking in compressed files is more a hack than anything else, + // but we need to support it, so here we go. + ztoolkit_t *ztk = file->ztk; + unsigned char *buffer; + fs_offset_t buffersize; + + // If we have to go back in the file, we need to restart from the beginning + if( offset <= file->position ) + { + ztk->in_ind = 0; + ztk->in_len = 0; + ztk->in_position = 0; + file->position = 0; + if( lseek( file->handle, file->offset, SEEK_SET ) == -1 ) + Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n"); + + // Reset the Zlib stream + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + inflateReset( &ztk->zstream ); + } + + // We need a big buffer to force inflating into it directly + buffersize = 2 * sizeof( file->buff ); + buffer = (unsigned char *)Mem_Malloc( fs_mempool, buffersize ); + + // Skip all data until we reach the requested offset + while( offset > ( file->position - file->buff_len + file->buff_ind )) + { + fs_offset_t diff = offset - ( file->position - file->buff_len + file->buff_ind ); + fs_offset_t count; + + count = ( diff > buffersize ) ? buffersize : diff; + if( FS_Read( file, buffer, count ) != count ) + { + Mem_Free( buffer ); + return -1; + } + } + + Mem_Free( buffer ); + return 0; + } + if( lseek( file->handle, file->offset + offset, SEEK_SET ) == -1 ) return -1; file->position = offset; return 0; + + } /* @@ -2328,6 +2847,51 @@ static void FS_Purge( file_t *file ) file->ungetc = EOF; } +static void *FS_CustomAlloc( size_t size ) +{ + return Mem_Malloc( fs_mempool, size ); +} + +static void FS_CustomFree( void *data ) +{ + Mem_Free( data ); +} + +static byte *FS_LoadFileFromArchive( searchpath_t *sp, const char *path, int pack_ind, fs_offset_t *filesizeptr, const qboolean sys_malloc ) +{ + fs_offset_t filesize; + file_t *file; + byte *buf; + void *( *pfnAlloc )( size_t ) = sys_malloc ? malloc : FS_CustomAlloc; + void ( *pfnFree )( void * ) = sys_malloc ? free : FS_CustomFree; + + // custom load file function for compressed files + if( sp->pfnLoadFile ) + return sp->pfnLoadFile( sp, path, pack_ind, filesizeptr, pfnAlloc, pfnFree ); + + file = sp->pfnOpenFile( sp, path, "rb", pack_ind ); + + if( !file ) // TODO: indicate errors + return NULL; + + filesize = file->real_length; + buf = (byte *)pfnAlloc( filesize + 1 ); + + if( unlikely( !buf )) // TODO: indicate errors + { + Con_Reportf( "%s: can't alloc %li bytes, no free memory\n", __func__, (long)filesize + 1 ); + FS_Close( file ); + return NULL; + } + + buf[filesize] = '\0'; + FS_Read( file, buf, filesize ); + FS_Close( file ); + if( filesizeptr ) *filesizeptr = filesize; + + return buf; +} + /* ============ FS_LoadFile @@ -2336,36 +2900,38 @@ Filename are relative to the xash directory. Always appends a 0 byte. ============ */ +static byte *FS_LoadFile_( const char *path, fs_offset_t *filesizeptr, const qboolean gamedironly, const qboolean custom_alloc ) +{ + searchpath_t *search; + char netpath[MAX_SYSPATH]; + int pack_ind; + + // some mappers used leading '/' or '\' in path to models or sounds + if( path[0] == '/' || path[0] == '\\' ) + path++; + + if( path[0] == '/' || path[0] == '\\' ) + path++; + + if( !fs_searchpaths || FS_CheckNastyPath( path )) + return NULL; + + search = FS_FindFile( path, &pack_ind, netpath, sizeof( netpath ), gamedironly ); + + if( !search ) + return NULL; + + return FS_LoadFileFromArchive( search, netpath, pack_ind, filesizeptr, !custom_alloc ); +} + +byte *FS_LoadFileMalloc( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) +{ + return FS_LoadFile_( path, filesizeptr, gamedironly, false ); +} + byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) { - file_t *file; - byte *buf = NULL; - fs_offset_t filesize = 0; - - file = FS_Open( path, "rb", gamedironly ); - - if( file ) - { - filesize = file->real_length; - - buf = (byte *)Mem_Malloc( fs_mempool, filesize + 1 ); - buf[filesize] = '\0'; - FS_Read( file, buf, filesize ); - FS_Close( file ); - } - else - { - buf = FS_LoadWADFile( path, &filesize, gamedironly ); - - if( !buf ) - buf = FS_LoadZIPFile( path, &filesize, gamedironly ); - - } - - if( filesizeptr ) - *filesizeptr = filesize; - - return buf; + return FS_LoadFile_( path, filesizeptr, gamedironly, true ); } qboolean CRC32_File( dword *crcvalue, const char *filename ) @@ -2396,25 +2962,20 @@ qboolean CRC32_File( dword *crcvalue, const char *filename ) qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ) { file_t *file; - byte buffer[1024]; - MD5Context_t MD5_Hash; - int bytes; + MD5Context_t MD5_Hash = { 0 }; if(( file = FS_Open( pszFileName, "rb", false )) == NULL ) return false; - memset( &MD5_Hash, 0, sizeof( MD5Context_t )); - MD5Init( &MD5_Hash ); if( seed ) - { MD5Update( &MD5_Hash, (const byte *)seed, 16 ); - } while( 1 ) { - bytes = FS_Read( file, buffer, sizeof( buffer )); + byte buffer[1024]; + int bytes = FS_Read( file, buffer, sizeof( buffer )); if( bytes > 0 ) MD5Update( &MD5_Hash, buffer, bytes ); @@ -2446,9 +3007,7 @@ byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) file = FS_SysOpen( path, "rb" ); if( !file ) - { return NULL; - } // Try to load filesize = file->real_length; @@ -2479,7 +3038,7 @@ qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ) if( !file ) { - Con_Reportf( S_ERROR "FS_WriteFile: failed on %s\n", filename); + Con_Reportf( S_ERROR "%s: failed on %s\n", __func__, filename ); return false; } @@ -2505,9 +3064,7 @@ Look for a file in the packages and in the filesystem */ int GAME_EXPORT FS_FileExists( const char *filename, int gamedironly ) { - if( FS_FindFile( filename, NULL, gamedironly )) - return true; - return false; + return FS_FindFile( filename, NULL, NULL, 0, gamedironly ) != NULL; } /* @@ -2520,21 +3077,38 @@ return NULL for file in pack */ const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) { - int index; - searchpath_t *search; + static char diskpath[MAX_SYSPATH]; - search = FS_FindFile( name, &index, gamedironly ); - - if( search ) - { - if( index != -1 ) // file in pack or wad - return NULL; - return va( "%s%s", search->filename, name ); - } + if( FS_GetFullDiskPath( diskpath, sizeof( diskpath ), name, gamedironly )) + return diskpath; return NULL; } +/* +================== +FS_GetFullDiskPath + +Build full path for file on disk +return false for file in pack +================== +*/ +qboolean FS_GetFullDiskPath( char *buffer, size_t size, const char *name, qboolean gamedironly ) +{ + searchpath_t *search; + char temp[MAX_SYSPATH]; + + search = FS_FindFile( name, NULL, temp, sizeof( temp ), gamedironly ); + + if( search && search->type == SEARCHPATH_PLAIN ) + { + Q_snprintf( buffer, size, "%s%s", search->filename, temp ); + return true; + } + + return false; +} + /* ================== FS_FileSize @@ -2582,32 +3156,14 @@ return time of creation file in seconds */ int FS_FileTime( const char *filename, qboolean gamedironly ) { - searchpath_t *search; - int pack_ind; + searchpath_t *search; + char netpath[MAX_SYSPATH]; + int pack_ind; - search = FS_FindFile( filename, &pack_ind, gamedironly ); + search = FS_FindFile( filename, &pack_ind, netpath, sizeof( netpath ), gamedironly ); if( !search ) return -1; // doesn't exist - switch( search->type ) - { - case SEARCHPATH_PAK: - return FS_FileTimePAK( search->pack ); - case SEARCHPATH_WAD: - return FS_FileTimeWAD( search->wad ); - case SEARCHPATH_ZIP: - return FS_FileTimeZIP( search->zip ); - default: - if( pack_ind < 0 ) - { - char path [MAX_SYSPATH]; - - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysFileTime( path ); - } - } - - return -1; // doesn't exist + return search->pfnFileTime( search, netpath ); } /* @@ -2619,21 +3175,47 @@ rename specified file from gamefolder */ qboolean FS_Rename( const char *oldname, const char *newname ) { - char oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; - qboolean iRet; + char oldname2[MAX_SYSPATH], newname2[MAX_SYSPATH], oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; + int ret; - if( !oldname || !newname || !*oldname || !*newname ) + // a1ba: disallow path traversal + if( FS_CheckNastyPath( oldname ) || FS_CheckNastyPath( newname )) return false; - Q_snprintf( oldpath, sizeof( oldpath ), "%s%s", fs_writedir, oldname ); - Q_snprintf( newpath, sizeof( newpath ), "%s%s", fs_writedir, newname ); + if( !fs_writepath ) + return false; - COM_FixSlashes( oldpath ); - COM_FixSlashes( newpath ); + if( !COM_CheckString( oldname ) || !COM_CheckString( newname )) + return false; - iRet = rename( oldpath, newpath ); + // no work done + if( !Q_stricmp( oldname, newname )) + return true; - return (iRet == 0); + // fix up slashes + Q_strncpy( oldname2, oldname, sizeof( oldname2 )); + Q_strncpy( newname2, newname, sizeof( newname2 )); + + COM_FixSlashes( oldname2 ); + COM_FixSlashes( newname2 ); + + // file does not exist + if( !FS_FixFileCase( fs_writepath->dir, oldname2, oldpath, sizeof( oldpath ), false )) + return false; + + // exit if overflowed + if( !FS_FixFileCase( fs_writepath->dir, newname2, newpath, sizeof( newpath ), true )) + return false; + + ret = rename( oldpath, newpath ); + if( ret < 0 ) + { + Con_Printf( "%s: failed to rename file %s (%s) to %s (%s): %s\n", + __func__, oldpath, oldname2, newpath, newname2, strerror( errno )); + return false; + } + + return true; } /* @@ -2645,17 +3227,30 @@ delete specified file from gamefolder */ qboolean GAME_EXPORT FS_Delete( const char *path ) { - char real_path[MAX_SYSPATH]; - qboolean iRet; + char path2[MAX_SYSPATH], real_path[MAX_SYSPATH]; + int ret; - if( !path || !*path ) + // a1ba: disallow path traversal + if( FS_CheckNastyPath( path )) return false; - Q_snprintf( real_path, sizeof( real_path ), "%s%s", fs_writedir, path ); - COM_FixSlashes( real_path ); - iRet = remove( real_path ); + if( !fs_writepath || !COM_CheckString( path )) + return false; - return (iRet == 0); + Q_strncpy( path2, path, sizeof( path2 )); + COM_FixSlashes( path2 ); + + if( !FS_FixFileCase( fs_writepath->dir, path2, real_path, sizeof( real_path ), true )) + return true; + + ret = remove( real_path ); + if( ret < 0 && errno != ENOENT ) + { + Con_Printf( "%s: failed to delete file %s (%s): %s\n", __func__, real_path, path, strerror( errno )); + return false; + } + + return true; } /* @@ -2678,7 +3273,7 @@ qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ) if(( readSize = FS_Read( pInput, buf, size )) < size ) { - Con_Reportf( S_ERROR "FS_FileCopy: unexpected end of input file (%d < %d)\n", readSize, size ); + Con_Reportf( S_ERROR "%s: unexpected end of input file (%d < %d)\n", __func__, readSize, size ); fileSize = 0; done = false; break; @@ -2701,33 +3296,15 @@ Allocate and fill a search structure with information on matching filenames. */ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) { - search_t *search = NULL; - searchpath_t *searchpath; - pack_t *pak; - wfile_t *wad; - zip_t *zip; - int i, basepathlength, numfiles, numchars; - int resultlistindex, dirlistindex; - const char *slash, *backslash, *colon, *separator; - string netpath, temp; - stringlist_t resultlist; - stringlist_t dirlist; - char *basepath; + search_t *search = NULL; + searchpath_t *searchpath; + int i, numfiles, numchars; + stringlist_t resultlist; if( pattern[0] == '.' || pattern[0] == ':' || pattern[0] == '/' || pattern[0] == '\\' ) return NULL; // punctuation issues stringlistinit( &resultlist ); - stringlistinit( &dirlist ); - slash = Q_strrchr( pattern, '/' ); - backslash = Q_strrchr( pattern, '\\' ); - colon = Q_strrchr( pattern, ':' ); - separator = Q_max( slash, backslash ); - separator = Q_max( separator, colon ); - basepathlength = separator ? (separator + 1 - pattern) : 0; - basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); - if( basepathlength ) memcpy( basepath, pattern, basepathlength ); - basepath[basepathlength] = 0; // search through the path, one element at a time for( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next ) @@ -2735,46 +3312,7 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) continue; - // is the element a pak file? - if( searchpath->type == SEARCHPATH_PAK ) - { - // look through all the pak file elements - FS_SearchPAK( &resultlist, searchpath->pack, pattern ); - } - else if( searchpath->type == SEARCHPATH_ZIP ) - { - FS_SearchZIP( &resultlist, searchpath->zip, pattern ); - } - else if( searchpath->type == SEARCHPATH_WAD ) - { - FS_SearchWAD( &resultlist, searchpath->wad, pattern ); - } - else - { - // get a directory listing and look at each name - Q_sprintf( netpath, "%s%s", searchpath->filename, basepath ); - stringlistinit( &dirlist ); - listdirectory( &dirlist, netpath, caseinsensitive ); - - for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) - { - Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] ); - - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - } - - stringlistfreecontents( &dirlist ); - } + searchpath->pfnSearch( searchpath, &resultlist, pattern, caseinsensitive ); } if( resultlist.numstrings ) @@ -2783,21 +3321,21 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) numfiles = resultlist.numstrings; numchars = 0; - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1; + for( i = 0; i < resultlist.numstrings; i++ ) + numchars += (int)Q_strlen( resultlist.strings[i]) + 1; search = Mem_Calloc( fs_mempool, sizeof(search_t) + numchars + numfiles * sizeof( char* )); search->filenames = (char **)((char *)search + sizeof( search_t )); search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* )); search->numfilenames = (int)numfiles; numfiles = numchars = 0; - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + for( i = 0; i < resultlist.numstrings; i++ ) { size_t textlen; search->filenames[numfiles] = search->filenamesbuffer + numchars; - textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1; - memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen ); + textlen = Q_strlen(resultlist.strings[i]) + 1; + memcpy( search->filenames[numfiles], resultlist.strings[i], textlen ); numfiles++; numchars += (int)textlen; } @@ -2805,11 +3343,51 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) stringlistfreecontents( &resultlist ); - Mem_Free( basepath ); - return search; } +static qboolean FS_IsArchiveExtensionSupported( const char *ext, uint flags ) +{ + int i; + + if( ext == NULL ) + return false; + + for( i = 0; i < sizeof( g_archives ) / sizeof( g_archives[0] ); i++ ) + { + if( FBitSet( flags, IAES_ONLY_REAL_ARCHIVES ) && !g_archives[i].real_archive ) + continue; + + if( !Q_stricmp( ext, g_archives[i].ext )) + return true; + } + + return false; +} + +static searchpath_t *FS_GetArchiveByName( const char *name, searchpath_t *prev ) +{ + searchpath_t *sp = prev ? prev->next : fs_searchpaths; + + for( ; sp; sp = sp->next ) + { + if( !Q_stricmp( COM_FileWithoutPath( sp->filename ), name )) + return sp; + } + + return NULL; +} + +static int FS_FindFileInArchive( searchpath_t *sp, const char *path, char *truepath, size_t len ) +{ + return sp->pfnFindFile( sp, path, truepath, len ); +} + +static file_t *FS_OpenFileFromArchive( searchpath_t *sp, const char *path, const char *mode, int pack_ind ) +{ + return sp->pfnOpenFile( sp, path, mode, pack_ind ); +} + void FS_InitMemory( void ) { fs_mempool = Mem_AllocPool( "FileSystem Pool" ); @@ -2818,18 +3396,19 @@ void FS_InitMemory( void ) fs_interface_t g_engfuncs = { - _Con_Printf, - _Con_Printf, - _Con_Printf, - _Sys_Error, - _Mem_AllocPool, - _Mem_FreePool, - _Mem_Alloc, - _Mem_Realloc, - _Mem_Free + Con_PrintfStub, + Con_PrintfStub, + Con_PrintfStub, + Sys_ErrorStub, + Mem_AllocPoolStub, + Mem_FreePoolStub, + Mem_AllocStub, + Mem_ReallocStub, + Mem_FreeStub, + Sys_GetNativeObjectStub, }; -static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) +static qboolean FS_InitInterface( int version, const fs_interface_t *engfuncs ) { // to be extended in future interface revisions if( version != FS_API_VERSION ) @@ -2868,10 +3447,16 @@ static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); } + if( engfuncs->_Sys_GetNativeObject ) + { + g_engfuncs._Sys_GetNativeObject = engfuncs->_Sys_GetNativeObject; + Con_Reportf( "filesystem_stdio: custom platform-specific functions found\n" ); + } + return true; } -fs_api_t g_api = +const fs_api_t g_api = { FS_InitStdio, FS_ShutdownStdio, @@ -2888,6 +3473,7 @@ fs_api_t g_api = FS_Path_f, // gameinfo utils + FS_Gamedir, FS_LoadGameInfo, // file ops @@ -2925,14 +3511,31 @@ fs_api_t g_api = FS_Delete, FS_SysFileExists, FS_GetDiskPath, + + NULL, + (void *)FS_MountArchive_Fullpath, + + FS_GetFullDiskPath, + FS_LoadFileMalloc, + + FS_IsArchiveExtensionSupported, + FS_GetArchiveByName, + FS_FindFileInArchive, + FS_OpenFileFromArchive, + FS_LoadFileFromArchive, + + FS_GetRootDirectory, + + FS_MakeGameInfo, }; +int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ); int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ) { - if( !FS_InitInterface( version, engfuncs )) + if( engfuncs && !FS_InitInterface( version, engfuncs )) return 0; - memcpy( api, &g_api, sizeof( *api )); + *api = g_api; *globals = &FI; return FS_API_VERSION; diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h index 7f184f7c..46621cc6 100644 --- a/filesystem/filesystem.h +++ b/filesystem/filesystem.h @@ -1,6 +1,9 @@ /* filesystem.h - engine FS +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,14 +25,16 @@ GNU General Public License for more details. #include "xash3d_types.h" #include "const.h" #include "com_model.h" +#include "gameinfo.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus -#define FS_API_VERSION 1 // not stable yet! -#define FS_API_CREATEINTERFACE_TAG "XashFileSystem001" // follow FS_API_VERSION!!! +#define FS_API_VERSION 4 // not stable yet! +#define FS_API_CREATEINTERFACE_TAG "XashFileSystem004" // follow FS_API_VERSION!!! +#define FILESYSTEM_INTERFACE_VERSION "VFileSystem009" // never change this! // search path flags enum @@ -40,9 +45,26 @@ enum FS_CUSTOM_PATH = BIT( 3 ), // gamedir but with custom/mod data FS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only + FS_SKIP_ARCHIVED_WADS = BIT( 5 ), // don't mount wads inside archives automatically + FS_LOAD_PACKED_WAD = BIT( 6 ), // this wad is packed inside other archive + + FS_MOUNT_HD = BIT( 7 ), // mount high definition content folder + FS_MOUNT_LV = BIT( 8 ), // mount low violence content folder + FS_MOUNT_ADDON = BIT( 9 ), // mount addon folder + FS_MOUNT_L10N = BIT( 10 ), // mount localization folder + FS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH }; +typedef struct searchpath_s searchpath_t; + +// IsArchiveExtensionSupported flags +enum +{ + // excludes directories and pk3dir, i.e. archives that cannot be represented as a single file + IAES_ONLY_REAL_ARCHIVES = BIT( 0 ), +}; + typedef struct { int numfilenames; @@ -76,10 +98,11 @@ typedef struct gameinfo_s size_t size; int gamemode; - qboolean secure; // prevent to console acess - qboolean nomodels; // don't let player to choose model (use player.mdl always) - qboolean noskills; // disable skill menu selection - qboolean render_picbutton_text; // use font renderer to render WON buttons + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons + qboolean internal_vgui_support; // skip loading VGUI, pass ingame UI support API to client char sp_entity[32]; // e.g. info_player_start char mp_entity[32]; // e.g. info_player_deathmatch @@ -96,21 +119,26 @@ typedef struct gameinfo_s char game_dll_osx[64]; // custom path for game.dll qboolean added; -} gameinfo_t; -typedef enum -{ - GAME_NORMAL, - GAME_SINGLEPLAYER_ONLY, - GAME_MULTIPLAYER_ONLY -} gametype_t; + int quicksave_aged_count; // min is 1, max is 99 + int autosave_aged_count; // min is 1, max is 99 + + // HL25 compatibility keys + qboolean hd_background; + qboolean animated_title; + + char demomap[MAX_QPATH]; + + qboolean rodir; // if true, parsed from rodir + int64_t mtime; +} gameinfo_t; typedef struct fs_dllinfo_t { - string fullPath; - string shortPath; - qboolean encrypted; - qboolean custom_loader; + char fullPath[2048]; // absolute disk path + string shortPath; // vfs path + qboolean encrypted; // do we need encrypted DLL loader? + qboolean custom_loader; // do we need memory DLL loader? } fs_dllinfo_t; typedef struct fs_globals_t @@ -120,16 +148,15 @@ typedef struct fs_globals_t int numgames; } fs_globals_t; -typedef void (*fs_event_callback_t)( const char *path ); - +typedef struct file_s file_t; typedef struct fs_api_t { - qboolean (*InitStdio)( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); + qboolean (*InitStdio)( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); void (*ShutdownStdio)( void ); // search path utils - void (*Rescan)( void ); + void (*Rescan)( uint32_t flags, const char *language ); void (*ClearSearchPath)( void ); void (*AllowDirectPaths)( qboolean enable ); void (*AddGameDirectory)( const char *dir, uint flags ); @@ -140,7 +167,8 @@ typedef struct fs_api_t void (*Path_f)( void ); // gameinfo utils - void (*LoadGameInfo)( const char *rootfolder ); + const char *(*Gamedir)( void ); + void (*LoadGameInfo)( uint32_t flags, const char *language ); // file ops file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); @@ -151,11 +179,11 @@ typedef struct fs_api_t qboolean (*Eof)( file_t *file ); int (*Flush)( file_t *file ); int (*Close)( file_t *file ); - int (*Gets)( file_t *file, byte *string, size_t bufsize ); - int (*UnGetc)( file_t *file, byte c ); + int (*Gets)( file_t *file, char *string, size_t bufsize ); + int (*UnGetc)( file_t *file, char c ); int (*Getc)( file_t *file ); int (*VPrintf)( file_t *file, const char *format, va_list ap ); - int (*Printf)( file_t *file, const char *format, ... ) _format( 2 ); + int (*Printf)( file_t *file, const char *format, ... ) FORMAT_CHECK( 2 ); int (*Print)( file_t *file, const char *msg ); fs_offset_t (*FileLength)( file_t *f ); qboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize ); @@ -175,32 +203,67 @@ typedef struct fs_api_t fs_offset_t (*FileSize)( const char *filename, qboolean gamedironly ); qboolean (*Rename)( const char *oldname, const char *newname ); qboolean (*Delete)( const char *path ); - qboolean (*SysFileExists)( const char *path, qboolean casesensitive ); + qboolean (*SysFileExists)( const char *path ); const char *(*GetDiskPath)( const char *name, qboolean gamedironly ); - // file watcher - void (*WatchFrame)( void ); // engine will read all events and call appropriate callbacks - qboolean (*AddWatch)( const char *path, fs_event_callback_t callback ); + const char *(*ArchivePath)( file_t *f ); // returns path to archive from which file was opened or "plain" + void *(*MountArchive_Fullpath)( const char *path, int flags ); // mounts the archive by path, if supported + + qboolean (*GetFullDiskPath)( char *buffer, size_t size, const char *name, qboolean gamedironly ); + + // like LoadFile but returns pointer that can be free'd using standard library function + byte *(*LoadFileMalloc)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); + + // **** archive interface **** + // query supported formats + qboolean (*IsArchiveExtensionSupported)( const char *ext, uint flags ); + + // to speed up archive lookups, this function can be used to get the archive object by it's name + // because archive can share the name, you can call this function repeatedly to get all archives + searchpath_t *(*GetArchiveByName)( const char *name, searchpath_t *prev ); + + // return an index into the archive and a true path, if possible + int (*FindFileInArchive)( searchpath_t *sp, const char *path, char *outpath, size_t len ); + + // similarly to Open, opens file but from specified archive + // NOTE: for speed reasons, path is case-sensitive here! + // Use FindFileInArchive to retrieve real path from caseinsensitive FS emulation! + file_t *(*OpenFileFromArchive)( searchpath_t *, const char *path, const char *mode, int pack_ind ); + + // similarly to LoadFile, loads whole file into memory from specified archive + // NOTE: for speed reasons, path is case-sensitive here! + // Use FindFileInArchive to retrieve real path from caseinsensitive FS emulation! + byte *(*LoadFileFromArchive)( searchpath_t *sp, const char *path, int pack_ind, fs_offset_t *filesizeptr, const qboolean sys_malloc ); + + // gets current root directory, set by InitStdio + qboolean (*GetRootDirectory)( char *path, size_t size ); + + void (*MakeGameInfo)( void ); } fs_api_t; typedef struct fs_interface_t { // logging - void (*_Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages - void (*_Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 - void (*_Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + void (*_Con_Printf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // typical console allowed messages + void (*_Con_DPrintf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 1 + void (*_Con_Reportf)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // -dev 2 - void (*_Sys_Error)( const char *fmt, ... ) _format( 1 ); + void (*_Sys_Error)( const char *fmt, ... ) FORMAT_CHECK( 1 ); // memory poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); - void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); - void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 2 ) WARN_UNUSED_RESULT; + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 3 ) WARN_UNUSED_RESULT; void (*_Mem_Free)( void *data, const char *filename, int fileline ); + + // platform + void *(*_Sys_GetNativeObject)( const char *object ); } fs_interface_t; -typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); +typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, const fs_interface_t *interface ); #define GET_FS_API "GetFSAPI" #ifdef __cplusplus diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index 99250189..39023dfe 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -1,6 +1,9 @@ /* filesystem.h - engine FS +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,31 +19,56 @@ GNU General Public License for more details. #ifndef FILESYSTEM_INTERNAL_H #define FILESYSTEM_INTERNAL_H +#include #include "xash3d_types.h" #include "filesystem.h" +#include "miniz.h" + +#if XASH_ANDROID +#include +#endif #ifdef __cplusplus extern "C" { #endif +typedef struct searchpath_s searchpath_t; +typedef struct dir_s dir_t; typedef struct zip_s zip_t; typedef struct pack_s pack_t; typedef struct wfile_s wfile_t; +typedef struct android_assets_s android_assets_t; -#define FILE_BUFF_SIZE (2048) +#define FILE_BUFF_SIZE (2048) +#define FILE_DEFLATED BIT( 0 ) + +typedef struct ztoolkit_s +{ + z_stream zstream; + size_t comp_length; + size_t in_ind, in_len; + size_t in_position; + byte input[FILE_BUFF_SIZE]; +} ztoolkit_t; struct file_s { - int handle; // file descriptor - int ungetc; // single stored character from ungetc, cleared to EOF when read - fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) - fs_offset_t position; // current position in the file - fs_offset_t offset; // offset into the package (0 if external file) - time_t filetime; // pak, wad or real filetime - // contents buffer - fs_offset_t buff_ind, buff_len; // buffer current index and length - byte buff[FILE_BUFF_SIZE]; // intermediate buffer + int handle; // file descriptor + int ungetc; // single stored character from ungetc, cleared to EOF when read + time_t filetime; // pak, wad or real filetime + searchpath_t *searchpath; + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + uint32_t flags; + ztoolkit_t *ztk; // if not NULL, all read functions must go through decompression + + // contents buffer + fs_offset_t buff_ind; // buffer current index + fs_offset_t buff_len; // buffer current length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer + #ifdef XASH_REDUCE_FD const char *backup_path; fs_offset_t backup_position; @@ -48,13 +76,15 @@ struct file_s #endif }; -enum +typedef enum searchpathtype_e { SEARCHPATH_PLAIN = 0, SEARCHPATH_PAK, SEARCHPATH_WAD, - SEARCHPATH_ZIP -}; + SEARCHPATH_ZIP, + SEARCHPATH_PK3DIR, // it's actually a plain directory but it must behave like a ZIP archive, + SEARCHPATH_ANDROID_ASSETS +} searchpathtype_t; typedef struct stringlist_s { @@ -66,34 +96,45 @@ typedef struct stringlist_s typedef struct searchpath_s { - string filename; - int type; - int flags; + string filename; + searchpathtype_t type; + int flags; + union { + dir_t *dir; pack_t *pack; wfile_t *wad; zip_t *zip; + android_assets_t *assets; }; + struct searchpath_s *next; + + void (*pfnPrintInfo)( struct searchpath_s *search, char *dst, size_t size ); + void (*pfnClose)( struct searchpath_s *search ); + file_t *(*pfnOpenFile)( struct searchpath_s *search, const char *filename, const char *mode, int pack_ind ); + int (*pfnFileTime)( struct searchpath_s *search, const char *filename ); + int (*pfnFindFile)( struct searchpath_s *search, const char *path, char *fixedname, size_t len ); + void (*pfnSearch)( struct searchpath_s *search, stringlist_t *list, const char *pattern, int caseinsensitive ); + byte *(*pfnLoadFile)( struct searchpath_s *search, const char *path, int pack_ind, fs_offset_t *filesize, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * )); } searchpath_t; +typedef searchpath_t *(*FS_ADDARCHIVE_FULLPATH)( const char *path, int flags ); + extern fs_globals_t FI; -extern searchpath_t *fs_searchpaths; +extern searchpath_t *fs_writepath; extern poolhandle_t fs_mempool; extern fs_interface_t g_engfuncs; -extern qboolean fs_ext_path; -extern char fs_rodir[MAX_SYSPATH]; extern char fs_rootdir[MAX_SYSPATH]; -extern char fs_writedir[MAX_SYSPATH]; -extern fs_api_t g_api; +extern const fs_api_t g_api; #define GI FI.GameInfo -#define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) -#define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) #define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) -#define Mem_Free( mem ) g_engfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ ) #define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) #define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) @@ -101,112 +142,117 @@ extern fs_api_t g_api; #define Con_DPrintf (*g_engfuncs._Con_DPrintf) #define Con_Reportf (*g_engfuncs._Con_Reportf) #define Sys_Error (*g_engfuncs._Sys_Error) +#define Sys_GetNativeObject (*g_engfuncs._Sys_GetNativeObject) // // filesystem.c // qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); void FS_ShutdownStdio( void ); +searchpath_t *FS_MountArchive_Fullpath( const char *file, int flags ); +void _Mem_Free( void *data, const char *filename, int fileline ); +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; // search path utils -void FS_Rescan( void ); +void FS_Rescan( uint32_t flags, const char *language ); void FS_ClearSearchPath( void ); void FS_AllowDirectPaths( qboolean enable ); void FS_AddGameDirectory( const char *dir, uint flags ); void FS_AddGameHierarchy( const char *dir, uint flags ); -search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; int FS_SetCurrentDirectory( const char *path ); +qboolean FS_GetRootDirectory( char *path, size_t size ); void FS_Path_f( void ); -// gameinfo utils -void FS_LoadGameInfo( const char *rootfolder ); - // file ops -file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +int FS_Close( file_t *file ); +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) + MALLOC_LIKE( FS_Close, 1 ) WARN_UNUSED_RESULT; fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); int FS_Seek( file_t *file, fs_offset_t offset, int whence ); fs_offset_t FS_Tell( file_t *file ); qboolean FS_Eof( file_t *file ); int FS_Flush( file_t *file ); -int FS_Close( file_t *file ); -int FS_Gets( file_t *file, byte *string, size_t bufsize ); -int FS_UnGetc( file_t *file, byte c ); +int FS_Gets( file_t *file, char *string, size_t bufsize ); +int FS_UnGetc( file_t *file, char c ); int FS_Getc( file_t *file ); int FS_VPrintf( file_t *file, const char *format, va_list ap ); -int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); +int FS_Printf( file_t *file, const char *format, ... ) FORMAT_CHECK( 2 ); int FS_Print( file_t *file, const char *msg ); fs_offset_t FS_FileLength( file_t *f ); qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); // file buffer ops -byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; +byte *FS_LoadFileMalloc( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ) + MALLOC_LIKE( free, 1 ) WARN_UNUSED_RESULT; +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ) + MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); // file hashing qboolean CRC32_File( dword *crcvalue, const char *filename ); qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); +// stringlist ops +void stringlistinit( stringlist_t *list ); +void stringlistfreecontents( stringlist_t *list ); +void stringlistappend( stringlist_t *list, const char *text ); +void stringlistsort( stringlist_t *list ); +void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only ); + // filesystem ops int FS_FileExists( const char *filename, int gamedironly ); int FS_FileTime( const char *filename, qboolean gamedironly ); fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); qboolean FS_Rename( const char *oldname, const char *newname ); qboolean FS_Delete( const char *path ); -qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); +qboolean FS_SysFileExists( const char *path ); const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); -void stringlistappend( stringlist_t *list, char *text ); +qboolean FS_GetFullDiskPath( char *buffer, size_t size, const char *name, qboolean gamedironly ); void FS_CreatePath( char *path ); qboolean FS_SysFolderExists( const char *path ); +qboolean FS_SysFileOrFolderExists( const char *path ); file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ); int FS_SysFileTime( const char *filename ); -file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ); +file_t *FS_OpenHandle( searchpath_t *search, int handle, fs_offset_t offset, fs_offset_t len ); file_t *FS_SysOpen( const char *filepath, const char *mode ); -const char *FS_FixFileCase( const char *path ); -searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); +searchpath_t *FS_FindFile( const char *name, int *index, char *fixedname, size_t len, qboolean gamedironly ); +qboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size ); // // pak.c // -int FS_FileTimePAK( pack_t *pack ); -int FS_FindFilePAK( pack_t *pack, const char *name ); -void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ); -void FS_ClosePAK( pack_t *pack ); -void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ); -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ); -qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ); +qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files ); +searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags ); // // wad.c // -int FS_FileTimeWAD( wfile_t *wad ); -int FS_FindFileWAD( wfile_t *wad, const char *name ); -void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ); -void FS_CloseWAD( wfile_t *wad ); -void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ); -byte *FS_LoadWADFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); -qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ); - -// -// watch.c -// -qboolean FS_WatchInitialize( void ); -int FS_AddWatch( const char *path, fs_event_callback_t callback ); -void FS_WatchFrame( void ); +searchpath_t *FS_AddWad_Fullpath( const char *wadfile, int flags ); // // zip.c // -int FS_FileTimeZIP( zip_t *zip ); -int FS_FindFileZIP( zip_t *zip, const char *name ); -void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ); -void FS_CloseZIP( zip_t *zip ); -void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ); -byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ); -qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ); +searchpath_t *FS_AddZip_Fullpath( const char *zipfile, int flags ); + +// +// dir.c +// +searchpath_t *FS_AddDir_Fullpath( const char *path, int flags ); +qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath ); +void FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int flags ); + +// +// android.c +// +void FS_InitAndroid( void ); +searchpath_t *FS_AddAndroidAssets_Fullpath( const char *path, int flags ); #ifdef __cplusplus } diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h index bebfdd29..d33d9e11 100644 --- a/filesystem/fscallback.h +++ b/filesystem/fscallback.h @@ -1,6 +1,9 @@ /* fscallback.h - common filesystem callbacks -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors +Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,22 +36,23 @@ extern fs_globals_t *FI; #define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths) #define FS_AddGameDirectory (*g_fsapi.AddGameDirectory) #define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy) +#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE #define FS_Search (*g_fsapi.Search) +#endif #define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) #define FS_Path_f (*g_fsapi.Path_f) -// gameinfo utils -#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) - // file ops +#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE #define FS_Open (*g_fsapi.Open) +#define FS_Close (*g_fsapi.Close) +#endif #define FS_Write (*g_fsapi.Write) #define FS_Read (*g_fsapi.Read) #define FS_Seek (*g_fsapi.Seek) #define FS_Tell (*g_fsapi.Tell) #define FS_Eof (*g_fsapi.Eof) #define FS_Flush (*g_fsapi.Flush) -#define FS_Close (*g_fsapi.Close) #define FS_Gets (*g_fsapi.Gets) #define FS_UnGetc (*g_fsapi.UnGetc) #define FS_Getc (*g_fsapi.Getc) @@ -59,8 +63,10 @@ extern fs_globals_t *FI; #define FS_FileCopy (*g_fsapi.FileCopy) // file buffer ops +#ifndef FSCALLBACK_OVERRIDE_MALLOC_LIKE #define FS_LoadFile (*g_fsapi.LoadFile) #define FS_LoadDirectFile (*g_fsapi.LoadDirectFile) +#endif #define FS_WriteFile (*g_fsapi.WriteFile) // file hashing diff --git a/filesystem/pak.c b/filesystem/pak.c index 1cc0037a..6aef428e 100644 --- a/filesystem/pak.c +++ b/filesystem/pak.c @@ -1,7 +1,9 @@ /* pak.c - PAK support for filesystem +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -65,54 +67,22 @@ typedef struct struct pack_s { - string filename; - int handle; + file_t *handle; int numfiles; - time_t filetime; // common for all packed files - dpackfile_t *files; + dpackfile_t files[]; // flexible }; /* ==================== -FS_AddFileToPack +FS_SortPak -Add a file to the list of files contained into a package ==================== */ -static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +static int FS_SortPak( const void *_a, const void *_b ) { - int left, right, middle; - dpackfile_t *pfile; + const dpackfile_t *a = _a, *b = _b; - // look for the slot we should put that file into (binary search) - left = 0; - right = pack->numfiles - 1; - - while( left <= right ) - { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); - - // If we found the file, there's a problem - if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // We have to move the right of the list by one slot to free the one we need - pfile = &pack->files[left]; - memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); - pack->numfiles++; - - Q_strncpy( pfile->name, name, sizeof( pfile->name )); - pfile->filepos = offset; - pfile->filelen = size; - - return pfile; + return Q_stricmp( a->name, b->name ); } /* @@ -128,37 +98,31 @@ of the list so they override previous pack files. static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) { dpackheader_t header; - int packhandle; - int i, numpackfiles; + file_t *packhandle; + int numpackfiles; pack_t *pack; - dpackfile_t *info; fs_size_t c; - packhandle = open( packfile, O_RDONLY|O_BINARY ); + // TODO: use FS_Open to allow PK3 to be included into other archives + // Currently, it doesn't work with rodir due to FS_FindFile logic + // when it will use FS_Open, check that FS_CheckForQuakePak correctly + // detects Quake gamedirs in RoDir + packhandle = FS_SysOpen( packfile, "rb" ); -#if !XASH_WIN32 - if( packhandle < 0 ) - { - const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile != packfile ) - packhandle = open( fpackfile, O_RDONLY|O_BINARY ); - } -#endif - - if( packhandle < 0 ) + if( packhandle == NULL ) { Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); if( error ) *error = PAK_LOAD_COULDNT_OPEN; return NULL; } - c = read( packhandle, (void *)&header, sizeof( header )); + c = FS_Read( packhandle, (void *)&header, sizeof( header )); if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) { Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); if( error ) *error = PAK_LOAD_BAD_HEADER; - close( packhandle ); + FS_Close( packhandle ); return NULL; } @@ -166,7 +130,7 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) { Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); if( error ) *error = PAK_LOAD_BAD_FOLDERS; - close( packhandle ); + FS_Close( packhandle ); return NULL; } @@ -176,7 +140,7 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) { Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); if( error ) *error = PAK_LOAD_TOO_MANY_FILES; - close( packhandle ); + FS_Close( packhandle ); return NULL; } @@ -184,32 +148,28 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) { Con_Reportf( "%s has no files. Ignored.\n", packfile ); if( error ) *error = PAK_LOAD_NO_FILES; - close( packhandle ); + FS_Close( packhandle ); return NULL; } - info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); - lseek( packhandle, header.dirofs, SEEK_SET ); + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t ) + sizeof( dpackfile_t ) * numpackfiles ); + FS_Seek( packhandle, header.dirofs, SEEK_SET ); - if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + if( header.dirlen != FS_Read( packhandle, (void *)pack->files, header.dirlen )) { Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); - if( error ) *error = PAK_LOAD_CORRUPTED; - close( packhandle ); - Mem_Free( info ); + if( error ) + *error = PAK_LOAD_CORRUPTED; + FS_Close( packhandle ); + Mem_Free( pack ); return NULL; } - pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); - Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); - pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); - pack->filetime = FS_SysFileTime( packfile ); - pack->handle = packhandle; - pack->numfiles = 0; + // TODO: validate directory? - // parse the directory - for( i = 0; i < numpackfiles; i++ ) - FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + pack->handle = packhandle; + pack->numfiles = numpackfiles; + qsort( pack->files, pack->numfiles, sizeof( pack->files[0] ), FS_SortPak ); #ifdef XASH_REDUCE_FD // will reopen when needed @@ -217,8 +177,8 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) pack->handle = -1; #endif - if( error ) *error = PAK_LOAD_OK; - Mem_Free( info ); + if( error ) + *error = PAK_LOAD_OK; return pack; } @@ -230,101 +190,40 @@ FS_OpenPackedFile Open a packed file using its package file descriptor =========== */ -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +static file_t *FS_OpenFile_PAK( searchpath_t *search, const char *filename, const char *mode, int pack_ind ) { dpackfile_t *pfile; - pfile = &pack->files[pack_ind]; + pfile = &search->pack->files[pack_ind]; - return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); + return FS_OpenHandle( search, search->pack->handle->handle, pfile->filepos, pfile->filelen ); } /* -================ -FS_AddPak_Fullpath +=========== +FS_FindFile_PAK -Adds the given pack to the search path. -The pack type is autodetected by the file extension. - -Returns true if the file was successfully added to the -search path or if it was already included. - -If keep_plain_dirs is set, the pack will be added AFTER the first sequence of -plain directories. -================ +=========== */ -qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - pack_t *pak = NULL; - const char *ext = COM_FileExtension( pakfile ); - int i, errorcode = PAK_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->type == SEARCHPATH_PAK && !Q_stricmp( search->pack->filename, pakfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "pak" )) - pak = FS_LoadPackPAK( pakfile, &errorcode ); - - if( pak ) - { - string fullpath; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->pack = pak; - search->type = SEARCHPATH_PAK; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < pak->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - return true; - } - else - { - if( errorcode != PAK_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); - return false; - } -} - -int FS_FindFilePAK( pack_t *pack, const char *name ) +static int FS_FindFile_PAK( searchpath_t *search, const char *path, char *fixedname, size_t len ) { int left, right, middle; // look for the file (binary search) left = 0; - right = pack->numfiles - 1; + right = search->pack->numfiles - 1; while( left <= right ) { int diff; middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); + diff = Q_stricmp( search->pack->files[middle].name, path ); // Found it if( !diff ) { + if( fixedname ) + Q_strncpy( fixedname, search->pack->files[middle].name, len ); return middle; } @@ -337,15 +236,21 @@ int FS_FindFilePAK( pack_t *pack, const char *name ) return -1; } -void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) +/* +=========== +FS_Search_PAK + +=========== +*/ +static void FS_Search_PAK( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive ) { string temp; const char *slash, *backslash, *colon, *separator; int j, i; - for( i = 0; i < pack->numfiles; i++ ) + for( i = 0; i < search->pack->numfiles; i++ ) { - Q_strncpy( temp, pack->files[i].name, sizeof( temp )); + Q_strncpy( temp, search->pack->files[i].name, sizeof( temp )); while( temp[0] ) { if( matchpattern( temp, pattern, true )) @@ -377,21 +282,129 @@ void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) } } -int FS_FileTimePAK( pack_t *pack ) +/* +=========== +FS_FileTime_PAK + +=========== +*/ +static int FS_FileTime_PAK( searchpath_t *search, const char *filename ) { - return pack->filetime; + return search->pack->handle->filetime; } -void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ) +/* +=========== +FS_PrintInfo_PAK + +=========== +*/ +static void FS_PrintInfo_PAK( searchpath_t *search, char *dst, size_t size ) { - Q_snprintf( dst, size, "%s (%i files)", pack->filename, pack->numfiles ); + if( search->pack->handle->searchpath ) + Q_snprintf( dst, size, "%s (%i files)" S_CYAN " from %s" S_DEFAULT, search->filename, search->pack->numfiles, search->pack->handle->searchpath->filename ); + else Q_snprintf( dst, size, "%s (%i files)", search->filename, search->pack->numfiles ); } -void FS_ClosePAK( pack_t *pack ) +/* +=========== +FS_Close_PAK + +=========== +*/ +static void FS_Close_PAK( searchpath_t *search ) { - if( pack->files ) - Mem_Free( pack->files ); - if( pack->handle >= 0 ) - close( pack->handle ); - Mem_Free( pack ); + if( search->pack->handle != NULL ) + FS_Close( search->pack->handle ); + Mem_Free( search->pack ); +} + + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags ) +{ + searchpath_t *search; + pack_t *pak; + int errorcode = PAK_LOAD_COULDNT_OPEN; + + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( !pak ) + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "%s: unable to load pak \"%s\"\n", __func__, pakfile ); + return NULL; + } + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + Q_strncpy( search->filename, pakfile, sizeof( search->filename )); + search->pack = pak; + search->type = SEARCHPATH_PAK; + search->flags = flags; + + search->pfnPrintInfo = FS_PrintInfo_PAK; + search->pfnClose = FS_Close_PAK; + search->pfnOpenFile = FS_OpenFile_PAK; + search->pfnFileTime = FS_FileTime_PAK; + search->pfnFindFile = FS_FindFile_PAK; + search->pfnSearch = FS_Search_PAK; + + Con_Reportf( "Adding PAK: %s (%i files)\n", pakfile, pak->numfiles ); + + return search; +} + +/* +================ +FS_CheckForQuakePak + +To generate fake gameinfo for Quake directory, we need to parse pak0.pak +and find progs.dat in it +================ +*/ +qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files ) +{ + qboolean is_quake = false; + pack_t *pak; + int i; + + pak = FS_LoadPackPAK( pakfile, NULL ); + if( !pak ) + return false; + + for( i = 0; i < num_files; i++ ) + { + int j; + + for( j = 0; j < pak->numfiles; j++ ) + { + if( Q_strchr( pak->files[j].name, '/' )) + continue; // exclude subdirectories + + if( !Q_stricmp( pak->files[j].name, files[i] )) + { + is_quake = true; + break; + } + } + + if( is_quake ) + break; + } + + Mem_Free( pak ); + return is_quake; } diff --git a/filesystem/tests/caseinsensitive.c b/filesystem/tests/caseinsensitive.c new file mode 100644 index 00000000..f0478630 --- /dev/null +++ b/filesystem/tests/caseinsensitive.c @@ -0,0 +1,122 @@ +#include "port.h" +#include "build.h" +#include +#include +#include +#include "filesystem.h" +#if XASH_POSIX +#include +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +#elif XASH_WIN32 +#include +#endif + +void *g_hModule; +FSAPI g_pfnGetFSAPI; +fs_api_t g_fs; +fs_globals_t *g_nullglobals; + + +static qboolean LoadFilesystem( void ) +{ + g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT ); + if( !g_hModule ) + return false; + + g_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API ); + if( !g_pfnGetFSAPI ) + return false; + + if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL )) + return false; + + return true; +} + +static qboolean CheckFileContents( const char *path, const void *buf, fs_offset_t size ) +{ + fs_offset_t len; + byte *data; + + data = g_fs.LoadFile( path, &len, true ); + if( !data ) + { + printf( "LoadFile fail\n" ); + return false; + } + + if( len != size ) + { + printf( "LoadFile sizeof fail\n" ); + free( data ); + return false; + } + + if( memcmp( data, buf, size )) + { + printf( "LoadFile magic fail\n" ); + free( data ); + return false; + } + + free( data ); + return true; +} + +static qboolean TestCaseinsensitive( void ) +{ + file_t *f1; + FILE *f2; + int magic = rand(); + + // create game dir for us + g_fs.AddGameDirectory( "./", FS_GAMEDIR_PATH ); + + // create some files first and write data + f1 = g_fs.Open( "FOO/Bar.bin", "wb", true ); + g_fs.Write( f1, &magic, sizeof( magic )); + g_fs.Close( f1 ); + + // try to search it with different file name + if( !g_fs.FileExists( "fOO/baR.bin", true )) + { + printf( "FileExists fail\n" ); + return false; + } + + // create a file directly, to check if cache can re-read + f2 = fopen( "FOO/Baz.bin", "wb" ); + fwrite( &magic, sizeof( magic ), 1, f2 ); + fclose( f2 ); + + // try to open first file back but with different file name case + if( !CheckFileContents( "foo/bar.BIN", &magic, sizeof( magic ))) + return false; + + // try to open second file that we created directly + if( !CheckFileContents( "Foo/BaZ.Bin", &magic, sizeof( magic ))) + return false; + + g_fs.Delete( "foo/Baz.biN" ); + g_fs.Delete( "foo/bar.bin" ); + g_fs.Delete( "Foo" ); + + return true; +} + +int main( void ) +{ + if( !LoadFilesystem() ) + return EXIT_FAILURE; + + srand( time( NULL )); + + if( !TestCaseinsensitive()) + return EXIT_FAILURE; + + printf( "success\n" ); + + return EXIT_SUCCESS; +} diff --git a/filesystem/tests/interface.cpp b/filesystem/tests/interface.cpp new file mode 100644 index 00000000..833127e0 --- /dev/null +++ b/filesystem/tests/interface.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include "port.h" +#include "build.h" +#include "VFileSystem009.h" +#include "filesystem.h" + +#if XASH_POSIX +#include +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +typedef void *HMODULE; +#elif XASH_WIN32 +#include +#endif + +HMODULE g_hModule; +FSAPI g_pfnGetFSAPI; +pfnCreateInterface_t g_pfnCreateInterface; +fs_api_t g_fs; +fs_globals_t *g_nullglobals; + +static bool LoadFilesystem() +{ + int temp = -1; + + g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT ); + if( !g_hModule ) + return false; + + // check our C-style interface existence + g_pfnGetFSAPI = reinterpret_cast( GetProcAddress( g_hModule, GET_FS_API )); + if( !g_pfnGetFSAPI ) + return false; + + g_nullglobals = NULL; + if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL )) + return false; + + if( !g_nullglobals ) + return false; + + // check Valve-style interface existence + g_pfnCreateInterface = reinterpret_cast( GetProcAddress( g_hModule, "CreateInterface" )); + if( !g_pfnCreateInterface ) + return false; + + if( !g_pfnCreateInterface( FILESYSTEM_INTERFACE_VERSION, &temp ) || temp != 0 ) + return false; + + temp = -1; + + if( !g_pfnCreateInterface( FS_API_CREATEINTERFACE_TAG, &temp ) || temp != 0 ) + return false; + + return true; +} + +int main() +{ + if( !LoadFilesystem() ) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/filesystem/tests/no-init.c b/filesystem/tests/no-init.c new file mode 100644 index 00000000..61de215b --- /dev/null +++ b/filesystem/tests/no-init.c @@ -0,0 +1,94 @@ +#include "port.h" +#include "build.h" +#include +#include +#include +#include "filesystem.h" +#if XASH_POSIX +#include +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +#elif XASH_WIN32 +#include +#endif + +void *g_hModule; +FSAPI g_pfnGetFSAPI; +fs_api_t g_fs; +fs_globals_t *g_nullglobals; + + +static qboolean LoadFilesystem( void ) +{ + g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT ); + if( !g_hModule ) + return false; + + g_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API ); + if( !g_pfnGetFSAPI ) + return false; + + if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL )) + return false; + + return true; +} + +static int TestNoInit( void ) +{ + char nice[69]; + fs_dllinfo_t dllinfo; + void *p; + + // here are the APIs that shouldn't fail without calling InitStdio + g_fs.ShutdownStdio(); + g_fs.ClearSearchPath(); + g_fs.AllowDirectPaths( true ); + g_fs.AllowDirectPaths( false ); + if( g_fs.Search( "asfjkajk", 0, 0 ) != 0 ) + return 0; + g_fs.SetCurrentDirectory( "." ); // must succeed! + g_fs.FindLibrary( "kek", true, &dllinfo ); + if( g_fs.Open( "afbvasvwerf", "w+", true ) != 0 ) + return 0; + if( g_fs.LoadFile( "hehe", NULL, true ) != 0 ) + return 0; + p = g_fs.LoadDirectFile( "hehe", NULL ); + if( p ) free( p ); + + if( !g_fs.IsArchiveExtensionSupported( "pk3", 0 )) return 0; + if( !g_fs.IsArchiveExtensionSupported( "wad", 0 )) return 0; + if( !g_fs.IsArchiveExtensionSupported( "pak", 0 )) return 0; + if( !g_fs.IsArchiveExtensionSupported( "pk3dir", 0 )) return 0; + + if( !g_fs.IsArchiveExtensionSupported( "pk3", IAES_ONLY_REAL_ARCHIVES )) return 0; + if( g_fs.IsArchiveExtensionSupported( "pk3dir", IAES_ONLY_REAL_ARCHIVES )) return 0; + if( g_fs.IsArchiveExtensionSupported( "vpk", 0)) return 0; + + g_fs.FileExists( "asdcv", 0 ); + g_fs.FileTime( "zxcasdfd", 0 ); + g_fs.FileSize( "asdqwe", 1 ); + g_fs.Rename( "cafqefv", "zvewfw" ); + g_fs.SysFileExists( "zbarbgaer" ); + g_fs.GetDiskPath( "aggasdfbaergba", 0 ); + g_fs.GetFullDiskPath( nice, sizeof( nice ), "oh my!", true ); + g_fs.Delete( "blabla" ); + + return 1; +} + +int main( void ) +{ + if( !LoadFilesystem() ) + return EXIT_FAILURE; + + if( !TestNoInit( )) + return EXIT_FAILURE; + + FreeLibrary( g_hModule ); + + printf( "success\n" ); + + return EXIT_SUCCESS; +} diff --git a/filesystem/wad.c b/filesystem/wad.c index 646476d3..bc2ba7a9 100644 --- a/filesystem/wad.c +++ b/filesystem/wad.c @@ -1,7 +1,9 @@ /* wad.c - WAD support for filesystem +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2015-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,34 +46,13 @@ file_n: byte[dwadinfo_t[num]->disksize] infotable dlumpinfo_t[dwadinfo_t->numlumps] ======================================================================== */ -#define WAD3_NAMELEN 16 #define HINT_NAMELEN 5 // e.g. _mask, _norm #define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount #include "const.h" -typedef struct -{ - int ident; // should be WAD3 - int numlumps; // num files - int infotableofs; // LUT offset -} dwadinfo_t; - -typedef struct -{ - int filepos; // file offset in WAD - int disksize; // compressed or uncompressed - int size; // uncompressed - signed char type; // TYP_* - signed char attribs; // file attribs - signed char pad0; - signed char pad1; - char name[WAD3_NAMELEN]; // must be null terminated -} dlumpinfo_t; - struct wfile_s { - string filename; int infotableofs; int numlumps; poolhandle_t mempool; // W_ReadLump temp buffers @@ -91,20 +72,19 @@ struct wfile_s typedef struct wadtype_s { - const char *ext; - signed char type; + char ext[4]; + signed char type; } wadtype_t; // associate extension with wad type -static const wadtype_t wad_types[7] = +static const wadtype_t wad_types[] = { -{ "pal", TYP_PALETTE }, // palette -{ "dds", TYP_DDSTEX }, // DDS image -{ "lmp", TYP_GFXPIC }, // quake1, hl pic -{ "fnt", TYP_QFONT }, // hl qfonts -{ "mip", TYP_MIPTEX }, // hl/q1 mip -{ "txt", TYP_SCRIPT }, // scripts -{ NULL, TYP_NONE } +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts }; /* @@ -116,18 +96,19 @@ Extracts file type from extension */ static signed char W_TypeFromExt( const char *lumpname ) { - const char *ext = COM_FileExtension( lumpname ); - const wadtype_t *type; + const char *ext = COM_FileExtension( lumpname ); + int i; // we not known about filetype, so match only by filename - if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + if( !Q_strcmp( ext, "*" ) || !COM_CheckStringEmpty( ext )) return TYP_ANY; - for( type = wad_types; type->ext; type++ ) + for( i = 0; i < sizeof( wad_types ) / sizeof( wad_types[0] ); i++ ) { - if( !Q_stricmp( ext, type->ext )) - return type->type; + if( !Q_stricmp( ext, wad_types[i].ext )) + return wad_types[i].type; } + return TYP_NONE; } @@ -140,263 +121,21 @@ Convert type to extension */ static const char *W_ExtFromType( signed char lumptype ) { - const wadtype_t *type; + int i; // we not known aboyt filetype, so match only by filename if( lumptype == TYP_NONE || lumptype == TYP_ANY ) return ""; - for( type = wad_types; type->ext; type++ ) + for( i = 0; i < sizeof( wad_types ) / sizeof( wad_types[0] ); i++ ) { - if( lumptype == type->type ) - return type->ext; + if( lumptype == wad_types[i].type ) + return wad_types[i].ext; } + return ""; } -/* -==================== -W_AddFileToWad - -Add a file to the list of files contained into a package -and sort LAT in alpha-bethical order -==================== -*/ -static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) -{ - int left, right; - dlumpinfo_t *plump; - - // look for the slot we should put that file into (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = ( left + right ) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if( wad->lumps[middle].type < newlump->type ) - diff = 1; - else if( wad->lumps[middle].type > newlump->type ) - diff = -1; - else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); - } - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // we have to move the right of the list by one slot to free the one we need - plump = &wad->lumps[left]; - memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); - wad->numlumps++; - - *plump = *newlump; - memcpy( plump->name, name, sizeof( plump->name )); - - return plump; -} - -/* -=========== -FS_CloseWAD - -finalize wad or just close -=========== -*/ -void FS_CloseWAD( wfile_t *wad ) -{ - Mem_FreePool( &wad->mempool ); - if( wad->handle != NULL ) - FS_Close( wad->handle ); - Mem_Free( wad ); // free himself -} - -/* -=========== -W_Open - -open the wad for reading & writing -=========== -*/ -static wfile_t *W_Open( const char *filename, int *error ) -{ - wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); - const char *basename; - int i, lumpcount; - dlumpinfo_t *srclumps; - size_t lat_size; - dwadinfo_t header; - - // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) basename = filename; - else basename = COM_FileWithoutPath( filename ); - - wad->handle = FS_Open( basename, "rb", false ); - - // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready - if( COM_CheckStringEmpty( fs_rodir ) && fs_ext_path && wad->handle == NULL ) - wad->handle = FS_SysOpen( filename, "rb" ); - - if( wad->handle == NULL ) - { - Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); - if( error ) *error = WAD_LOAD_COULDNT_OPEN; - FS_CloseWAD( wad ); - return NULL; - } - - // copy wad name - Q_strncpy( wad->filename, filename, sizeof( wad->filename )); - wad->filetime = FS_SysFileTime( filename ); - wad->mempool = Mem_AllocPool( filename ); - - if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) - { - Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - FS_CloseWAD( wad ); - return NULL; - } - - if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) - { - Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - FS_CloseWAD( wad ); - return NULL; - } - - lumpcount = header.numlumps; - - if( lumpcount >= MAX_FILES_IN_WAD ) - { - Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); - if( error ) *error = WAD_LOAD_TOO_MANY_FILES; - } - else if( lumpcount <= 0 ) - { - Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); - if( error ) *error = WAD_LOAD_NO_FILES; - FS_CloseWAD( wad ); - return NULL; - } - else if( error ) *error = WAD_LOAD_OK; - - wad->infotableofs = header.infotableofs; // save infotableofs position - - if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); - if( error ) *error = WAD_LOAD_BAD_FOLDERS; - FS_CloseWAD( wad ); - return NULL; - } - - lat_size = lumpcount * sizeof( dlumpinfo_t ); - - // NOTE: lumps table can be reallocated for O_APPEND mode - srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); - - if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) - { - Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); - if( error ) *error = WAD_LOAD_CORRUPTED; - Mem_Free( srclumps ); - FS_CloseWAD( wad ); - return NULL; - } - - // starting to add lumps - wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); - wad->numlumps = 0; - - // sort lumps for binary search - for( i = 0; i < lumpcount; i++ ) - { - char name[16]; - int k; - - // cleanup lumpname - Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); - - // check for '*' symbol issues (quake1) - k = Q_strlen( Q_strrchr( name, '*' )); - if( k ) name[Q_strlen( name ) - k] = '!'; - - // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) - if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) - srclumps[i].type = TYP_GFXPIC; - - W_AddFileToWad( name, wad, &srclumps[i] ); - } - - // release source lumps - Mem_Free( srclumps ); - - // and leave the file open - return wad; -} - -/* -==================== -FS_AddWad_Fullpath -==================== -*/ -qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - wfile_t *wad = NULL; - const char *ext = COM_FileExtension( wadfile ); - int errorcode = WAD_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->type == SEARCHPATH_WAD && !Q_stricmp( search->wad->filename, wadfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "wad" )) - wad = W_Open( wadfile, &errorcode ); - - if( wad ) - { - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->wad = wad; - search->type = SEARCHPATH_WAD; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); - return true; - } - else - { - if( errorcode != WAD_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); - return false; - } -} - -/* -============================================================================= - -WADSYSTEM PRIVATE ROUTINES - -============================================================================= -*/ - /* =========== W_FindLump @@ -440,103 +179,267 @@ static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed cha } /* -=========== -W_ReadLump +==================== +W_AddFileToWad -reading lump into temp buffer -=========== +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== */ -static byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +static dlumpinfo_t *W_AddFileToWad( const char *wadfile, const char *name, wfile_t *wad, dlumpinfo_t *newlump ) { - size_t oldpos, size = 0; - byte *buf; + int left, right; + dlumpinfo_t *plump; - // assume error - if( lumpsizeptr ) *lumpsizeptr = 0; + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; - // no wads loaded - if( !wad || !lump ) return NULL; - - oldpos = FS_Tell( wad->handle ); // don't forget restore original position - - if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + while( left <= right ) { - Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - return NULL; + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wadfile, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; } - buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); - size = FS_Read( wad->handle, buf, lump->disksize ); + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; - if( size < lump->disksize ) - { - Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - Mem_Free( buf ); - return NULL; - } + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); - if( lumpsizeptr ) *lumpsizeptr = lump->disksize; - FS_Seek( wad->handle, oldpos, SEEK_SET ); - - return buf; + return plump; } /* =========== -FS_LoadWADFile +FS_CloseWAD -loading lump into the tmp buffer +finalize wad or just close =========== */ -byte *FS_LoadWADFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +static void FS_CloseWAD( wfile_t *wad ) { - searchpath_t *search; - int index; + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} - search = FS_FindFile( path, &index, gamedironly ); - if( search && search->type == SEARCHPATH_WAD ) - return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); +/* +=========== +FS_Close_WAD +=========== +*/ +static void FS_Close_WAD( searchpath_t *search ) +{ + FS_CloseWAD( search->wad ); +} + +/* +=========== +FS_OpenFile_WAD +=========== +*/ +static file_t *FS_OpenFile_WAD( searchpath_t *search, const char *filename, const char *mode, int pack_ind ) +{ return NULL; } -int FS_FileTimeWAD( wfile_t *wad ) +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +static wfile_t *W_Open( const char *filename, int *error, uint flags ) { - return wad->filetime; + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + if( FBitSet( flags, FS_LOAD_PACKED_WAD )) + { + const char *basename = COM_FileWithoutPath( filename ); + wad->handle = FS_Open( basename, "rb", false ); + } + else + { + wad->handle = FS_SysOpen( filename, "rb" ); + } + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "%s: couldn't open %s: %s\n", __func__, filename, strerror( errno )); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + FS_CloseWAD( wad ); + return NULL; + } + + // copy wad name + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "%s: %s can't read header\n", __func__, filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "%s: %s is not a WAD2 or WAD3 file\n", __func__, filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "%s: %s is full (%i lumps)\n", __func__, filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "%s: %s has no lumps\n", __func__, filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + FS_CloseWAD( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "%s: %s can't find lump allocation table\n", __func__, filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + FS_CloseWAD( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "%s: %s has corrupted lump allocation table\n", __func__, filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + FS_CloseWAD( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( filename, name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; } -void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ) +/* +=========== +FS_FileTime_WAD + +=========== +*/ +static int FS_FileTime_WAD( searchpath_t *search, const char *filename ) { - Q_snprintf( dst, size, "%s (%i files)", wad->filename, wad->numlumps ); + return search->wad->filetime; } -int FS_FindFileWAD( wfile_t *wad, const char *name ) +/* +=========== +FS_PrintInfo_WAD + +=========== +*/ +static void FS_PrintInfo_WAD( searchpath_t *search, char *dst, size_t size ) +{ + if( search->wad->handle->searchpath ) + Q_snprintf( dst, size, "%s (%i files)" S_CYAN " from %s" S_DEFAULT, search->filename, search->wad->numlumps, search->wad->handle->searchpath->filename ); + else Q_snprintf( dst, size, "%s (%i files)", search->filename, search->wad->numlumps ); +} + +/* +=========== +FS_FindFile_WAD + +=========== +*/ +static int FS_FindFile_WAD( searchpath_t *search, const char *path, char *fixedname, size_t len ) { dlumpinfo_t *lump; - signed char type = W_TypeFromExt( name ); + signed char type = W_TypeFromExt( path ); qboolean anywadname = true; - string wadname, wadfolder; + string wadname; string shortname; // quick reject by filetype if( type == TYP_NONE ) return -1; - COM_ExtractFilePath( name, wadname ); - wadfolder[0] = '\0'; + COM_ExtractFilePath( path, wadname ); - if( COM_CheckStringEmpty( wadname ) ) + if( COM_CheckStringEmpty( wadname )) { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); + string wadbasename; + + COM_FileBase( wadname, wadbasename, sizeof( wadbasename )); + Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename ); anywadname = false; } // make wadname from wad fullpath - COM_FileBase( wad->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); + COM_FileBase( search->filename, shortname, sizeof( shortname )); + COM_DefaultExtension( shortname, ".wad", sizeof( shortname )); // quick reject by wadname if( !anywadname && Q_stricmp( wadname, shortname )) @@ -544,20 +447,27 @@ int FS_FindFileWAD( wfile_t *wad, const char *name ) // NOTE: we can't using long names for wad, // because we using original wad names[16]; - COM_FileBase( name, shortname ); + COM_FileBase( path, shortname, sizeof( shortname )); - lump = W_FindLump( wad, shortname, type ); + lump = W_FindLump( search->wad, shortname, type ); if( lump ) { - return lump - wad->lumps; + if( fixedname ) + Q_strncpy( fixedname, lump->name, len ); + return lump - search->wad->lumps; } return -1; - } -void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) +/* +=========== +FS_Search_WAD + +=========== +*/ +static void FS_Search_WAD( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive ) { string wadpattern, wadname, temp2; signed char type = W_TypeFromExt( pattern ); @@ -565,39 +475,43 @@ void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) string wadfolder, temp; int j, i; const char *slash, *backslash, *colon, *separator; + char buf[MAX_VA_STRING]; // quick reject by filetype if( type == TYP_NONE ) return; COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); + COM_FileBase( pattern, wadpattern, sizeof( wadpattern )); wadfolder[0] = '\0'; if( COM_CheckStringEmpty( wadname )) { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); + string wadbasename; + + COM_FileBase( wadname, wadbasename, sizeof( wadbasename )); + + Q_strncpy( wadfolder, wadbasename, sizeof( wadfolder )); + Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename ); anywadname = false; } // make wadname from wad fullpath - COM_FileBase( wad->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); + COM_FileBase( search->filename, temp2, sizeof( temp2 )); + COM_DefaultExtension( temp2, ".wad", sizeof( temp2 )); // quick reject by wadname if( !anywadname && Q_stricmp( wadname, temp2 )) return; - for( i = 0; i < wad->numlumps; i++ ) + for( i = 0; i < search->wad->numlumps; i++ ) { // if type not matching, we already have no chance ... - if( type != TYP_ANY && wad->lumps[i].type != type ) + if( type != TYP_ANY && search->wad->lumps[i].type != type ) continue; // build the lumpname with image suffix (if present) - Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + Q_strncpy( temp, search->wad->lumps[i].name, sizeof( temp )); while( temp[0] ) { @@ -612,8 +526,9 @@ void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) if( j == list->numstrings ) { // build path: wadname/lumpname.ext - Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); - COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + Q_snprintf( temp2, sizeof( temp2 ), "%s/%s", wadfolder, temp ); + Q_snprintf( buf, sizeof( buf ), ".%s", W_ExtFromType( search->wad->lumps[i].type )); + COM_DefaultExtension( temp2, buf, sizeof( temp2 )); stringlistappend( list, temp2 ); } } @@ -634,3 +549,94 @@ void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) } } } + + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +static byte *W_ReadLump( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *lumpsizeptr, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * )) +{ + const wfile_t *wad = search->wad; + const dlumpinfo_t *lump = &wad->lumps[pack_ind]; + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "%s: %s is corrupted\n", __func__, lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)pfnAlloc( lump->disksize ); + if( unlikely( !buf )) + { + Con_Reportf( S_ERROR "%s: can't alloc %d bytes, no free memory\n", __func__, lump->disksize ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + size = FS_Read( wad->handle, buf, lump->disksize ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "%s: %s is probably corrupted\n", __func__, lump->name ); + pfnFree( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + + return buf; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +searchpath_t *FS_AddWad_Fullpath( const char *wadfile, int flags ) +{ + searchpath_t *search; + wfile_t *wad; + int errorcode = WAD_LOAD_COULDNT_OPEN; + + wad = W_Open( wadfile, &errorcode, flags ); + + if( !wad ) + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "%s: unable to load wad \"%s\"\n", __func__, wadfile ); + return NULL; + } + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + Q_strncpy( search->filename, wadfile, sizeof( search->filename )); + search->wad = wad; + search->type = SEARCHPATH_WAD; + search->flags = flags; + + search->pfnPrintInfo = FS_PrintInfo_WAD; + search->pfnClose = FS_Close_WAD; + search->pfnOpenFile = FS_OpenFile_WAD; + search->pfnFileTime = FS_FileTime_WAD; + search->pfnFindFile = FS_FindFile_WAD; + search->pfnSearch = FS_Search_WAD; + search->pfnLoadFile = W_ReadLump; + + Con_Reportf( "Adding WAD: %s (%i files)\n", wadfile, wad->numlumps ); + return search; +} diff --git a/filesystem/watch.c b/filesystem/watch.c deleted file mode 100644 index 1d4cf7ea..00000000 --- a/filesystem/watch.c +++ /dev/null @@ -1,117 +0,0 @@ -#if 0 -#include "build.h" -#if XASH_LINUX -#include -#include -#include -#endif -#include "filesystem_internal.h" -#include "common/com_strings.h" - -#define MAX_FS_WATCHES 256 - -struct -{ -#if XASH_LINUX - int fd; - int count; - struct - { - fs_event_callback_t callback; - int fd; - } watch[MAX_FS_WATCHES]; -#endif // XASH_LINUX -} fsnotify; - -#if XASH_LINUX -static qboolean FS_InotifyInit( void ) -{ - int fd; - - if(( fd = inotify_init1( IN_NONBLOCK )) < 0 ) - { - Con_Printf( S_ERROR "inotify_init1 failed: %s", strerror( errno )); - return false; - } - - fsnotify.fd = fd; - return true; -} - -static qboolean FS_InotifyWasInit( void ) -{ - return fsnotify.fd >= 0; -} -#endif - -/* -=============== -FS_AddWatch - -Adds on-disk path to filesystem watcher list -Every file modification will call back -=============== -*/ -int FS_AddWatch( const char *path, fs_event_callback_t callback ) -{ -#if XASH_LINUX - int fd; - const uint mask = IN_CREATE | IN_DELETE | IN_MODIFY; - - if( !FS_InotifyWasInit() && !FS_InotifyInit()) - return false; - - if(( fd = inotify_add_watch( fsnotify.fd, path, mask )) < 0 ) - { - Con_Printf( S_ERROR "inotify_add_watch failed: %s", strerror( errno )); - return false; - } - - fsnotify.watch[fsnotify.count].fd = fd; - fsnotify.watch[fsnotify.count].callback = callback; - - return true; -#else - return false; -#endif -} - -/* -=============== -FS_WatchFrame - -Polls any changes and runs call backs -=============== -*/ -void FS_WatchFrame( void ) -{ -#if XASH_LINUX - int i; - - for( i = 0; i < fsnotify.count; i++ ) - { - struct inotify_event events; - } - -#endif -} - -/* -=============== -FS_WatchInitialize - -initializes filesystem watcher subsystem -=============== -*/ -qboolean FS_WatchInitialize( void ) -{ -#if XASH_LINUX - fsnotify.fd = -1; // only call inotify init when requested - fsnotify.count = 0; - - return true; -#else - return false; -#endif -} -#endif // 0 diff --git a/filesystem/wscript b/filesystem/wscript index 2b8d8a8b..b80ec79e 100644 --- a/filesystem/wscript +++ b/filesystem/wscript @@ -1,24 +1,63 @@ #!/usr/bin/env python +MEMFD_CREATE_TEST = '''#define _GNU_SOURCE +#include +int main(int argc, char **argv) { return memfd_create(argv[0], 0); }''' + +DIRENT_D_TYPE_TEST = '''#define _GNU_SOURCE +#include +int main(int argc, char **argv) { struct dirent entry; entry.d_type = DT_DIR; return 0; } +''' + def options(opt): pass def configure(conf): - nortti = { - 'msvc': ['/GR-'], - 'default': ['-fno-rtti', '-fno-exceptions'] - } - conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) + if conf.env.COMPILER_CXX != 'msvc': + conf.env.append_unique('CXXFLAGS', ['-fno-exceptions']) + if conf.env.DEST_OS == 'android': + conf.check_cc(lib='android') + + # remove lib prefix, except on Android which has issues unpacking libraries without prefix when debuggable=false if conf.env.DEST_OS != 'android': if conf.env.cxxshlib_PATTERN.startswith('lib'): conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False): + conf.define('HAVE_MEMFD_CREATE', 1) + + if conf.check_cc(fragment=DIRENT_D_TYPE_TEST, msg='Checking for d_type field in struct dirent', mandatory=False): + conf.define('HAVE_DIRENT_D_TYPE', 1) + def build(bld): bld(name = 'filesystem_includes', export_includes = '.') + + libs = [ 'filesystem_includes', 'sdk_includes', 'werror' ] + + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS != 'psvita': + libs += [ 'public', 'ANDROID' ] + bld.shlib(target = 'filesystem_stdio', - features = 'cxx', + features = 'seq', source = bld.path.ant_glob(['*.c', '*.cpp']), - use = 'filesystem_includes public', - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM) + use = libs, + install_path = bld.env.LIBDIR) + + if bld.env.TESTS: + # build in same module, so dynamic linking will work + # for now (until we turn libpublic to shared module lol) + tests = { + 'interface' : 'tests/interface.cpp', + 'caseinsensitive' : 'tests/caseinsensitive.c', + 'no-init': 'tests/no-init.c' + } + + for i in tests: + bld.program(features = 'test seq', + source = tests[i], + target = 'test_%s' % i, + use = libs + ['DL'], + rpath = bld.env.DEFAULT_RPATH, + install_path = None) diff --git a/filesystem/zip.c b/filesystem/zip.c index 829d1553..7760ba82 100644 --- a/filesystem/zip.c +++ b/filesystem/zip.c @@ -1,7 +1,7 @@ /* zip.c - ZIP support for filesystem Copyright (C) 2019 Mr0maks -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2019-2023 Xash3D FWGS contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,9 +27,8 @@ GNU General Public License for more details. #include "filesystem_internal.h" #include "crtlib.h" #include "common/com_strings.h" -#include "miniz.h" -#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) +#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) #define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') #define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') @@ -43,16 +42,16 @@ GNU General Public License for more details. #pragma pack( push, 1 ) typedef struct zip_header_s { - unsigned int signature; // little endian ZIP_HEADER - unsigned short version; // version of pkzip need to unpack - unsigned short flags; // flags (16 bits == 16 flags) - unsigned short compression_flags; // compression flags (bits) - unsigned int dos_date; // file modification time and file modification date - unsigned int crc32; //crc32 - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; + uint32_t signature; // little endian ZIP_HEADER + uint16_t version; // version of pkzip need to unpack + uint16_t flags; // flags (16 bits == 16 flags) + uint16_t compression_flags; // compression flags (bits) + uint32_t dos_date; // file modification time and file modification date + uint32_t crc32; //crc32 + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extrafield_len; } zip_header_t; /* @@ -62,52 +61,55 @@ typedef struct zip_header_s typedef struct zip_header_extra_s { - unsigned int signature; // ZIP_HEADER_SPANNED - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; + uint32_t signature; // ZIP_HEADER_SPANNED + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; } zip_header_extra_t; typedef struct zip_cdf_header_s { - unsigned int signature; - unsigned short version; - unsigned short version_need; - unsigned short generalPurposeBitFlag; - unsigned short flags; - unsigned short modification_time; - unsigned short modification_date; - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; - unsigned short file_commentary_len; - unsigned short disk_start; - unsigned short internal_attr; - unsigned int external_attr; - unsigned int local_header_offset; + uint32_t signature; + uint16_t version; + uint16_t version_need; + uint16_t generalPurposeBitFlag; + uint16_t flags; + uint16_t modification_time; + uint16_t modification_date; + uint32_t crc32; + uint32_t compressed_size; + uint32_t uncompressed_size; + uint16_t filename_len; + uint16_t extrafield_len; + uint16_t file_commentary_len; + uint16_t disk_start; + uint16_t internal_attr; + uint32_t external_attr; + uint32_t local_header_offset; } zip_cdf_header_t; typedef struct zip_header_eocd_s { - unsigned short disk_number; - unsigned short start_disk_number; - unsigned short number_central_directory_record; - unsigned short total_central_directory_record; - unsigned int size_of_central_directory; - unsigned int central_directory_offset; - unsigned short commentary_len; + uint16_t disk_number; + uint16_t start_disk_number; + uint16_t number_central_directory_record; + uint16_t total_central_directory_record; + uint32_t size_of_central_directory; + uint32_t central_directory_offset; + uint16_t commentary_len; } zip_header_eocd_t; #pragma pack( pop ) // ZIP errors -#define ZIP_LOAD_OK 0 -#define ZIP_LOAD_COULDNT_OPEN 1 -#define ZIP_LOAD_BAD_HEADER 2 -#define ZIP_LOAD_BAD_FOLDERS 3 -#define ZIP_LOAD_NO_FILES 5 -#define ZIP_LOAD_CORRUPTED 6 +enum +{ + ZIP_LOAD_OK = 0, + ZIP_LOAD_COULDNT_OPEN, + ZIP_LOAD_BAD_HEADER, + ZIP_LOAD_BAD_FOLDERS, + ZIP_LOAD_NO_FILES, + ZIP_LOAD_CORRUPTED +}; typedef struct zipfile_s { @@ -115,50 +117,41 @@ typedef struct zipfile_s fs_offset_t offset; // offset of local file header fs_offset_t size; //original file size fs_offset_t compressed_size; // compressed file size - unsigned short flags; + uint16_t flags; } zipfile_t; struct zip_s { - string filename; - int handle; + file_t *handle; int numfiles; - time_t filetime; - zipfile_t *files; + zipfile_t files[]; // flexible }; -#ifdef XASH_REDUCE_FD -static void FS_EnsureOpenZip( zip_t *zip ) +// #define ENABLE_CRC_CHECK // known to be buggy because of possible libpublic crc32 bug, disabled + +/* +============ +FS_CloseZIP +============ +*/ +static void FS_CloseZIP( zip_t *zip ) { - if( fs_last_zip == zip ) - return; - - if( fs_last_zip && (fs_last_zip->handle != -1) ) - { - close( fs_last_zip->handle ); - fs_last_zip->handle = -1; - } - fs_last_zip = zip; - if( zip && (zip->handle == -1) ) - zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); -} -#else -static void FS_EnsureOpenZip( zip_t *zip ) {} -#endif - -void FS_CloseZIP( zip_t *zip ) -{ - if( zip->files ) - Mem_Free( zip->files ); - - FS_EnsureOpenZip( NULL ); - - if( zip->handle >= 0 ) - close( zip->handle ); + if( zip->handle != NULL ) + FS_Close( zip->handle ); Mem_Free( zip ); } +/* +============ +FS_Close_ZIP +============ +*/ +static void FS_Close_ZIP( searchpath_t *search ) +{ + FS_CloseZIP( search->zip ); +} + /* ============ FS_SortZip @@ -166,7 +159,7 @@ FS_SortZip */ static int FS_SortZip( const void *a, const void *b ) { - return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); + return Q_stricmp(((zipfile_t *)a )->name, ((zipfile_t *)b )->name ); } /* @@ -180,24 +173,17 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) zip_cdf_header_t header_cdf; zip_header_eocd_t header_eocd; uint32_t signature; - fs_offset_t filepos = 0, length; + fs_offset_t filepos = 0; zipfile_t *info = NULL; char filename_buffer[MAX_SYSPATH]; zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); fs_size_t c; - zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + // TODO: use FS_Open to allow PK3 to be included into other archives + // Currently, it doesn't work with rodir due to FS_FindFile logic + zip->handle = FS_SysOpen( zipfile, "rb" ); -#if !XASH_WIN32 - if( zip->handle < 0 ) - { - const char *fzipfile = FS_FixFileCase( zipfile ); - if( fzipfile != zipfile ) - zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); - } -#endif - - if( zip->handle < 0 ) + if( zip->handle == NULL ) { Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); @@ -208,9 +194,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) return NULL; } - length = lseek( zip->handle, 0, SEEK_END ); - - if( length > UINT_MAX ) + if( zip->handle->real_length > UINT32_MAX ) { Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); @@ -221,9 +205,9 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) return NULL; } - lseek( zip->handle, 0, SEEK_SET ); + FS_Seek( zip->handle, 0, SEEK_SET ); - c = read( zip->handle, &signature, sizeof( signature ) ); + c = FS_Read( zip->handle, &signature, sizeof( signature )); if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) { @@ -248,13 +232,12 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) } // Find oecd - lseek( zip->handle, 0, SEEK_SET ); - filepos = length; + filepos = zip->handle->real_length; - while ( filepos > 0 ) + while( filepos > 0 ) { - lseek( zip->handle, filepos, SEEK_SET ); - c = read( zip->handle, &signature, sizeof( signature ) ); + FS_Seek( zip->handle, filepos, SEEK_SET ); + c = FS_Read( zip->handle, &signature, sizeof( signature )); if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) break; @@ -273,7 +256,7 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) return NULL; } - c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + c = FS_Read( zip->handle, &header_eocd, sizeof( header_eocd )); if( c != sizeof( header_eocd )) { @@ -286,15 +269,27 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) return NULL; } + if( header_eocd.total_central_directory_record == 0 ) // refuse to load empty ZIP archives + { + Con_Reportf( S_WARN "%s has no files (total records is zero). Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; + } + // Move to CDF start - lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); + FS_Seek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); + zip = (zip_t *)Mem_Realloc( fs_mempool, zip, sizeof( *zip ) + sizeof( *info ) * header_eocd.total_central_directory_record ); + info = zip->files; for( i = 0; i < header_eocd.total_central_directory_record; i++ ) { - c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + c = FS_Read( zip->handle, &header_cdf, sizeof( header_cdf )); if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) { @@ -303,15 +298,14 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) if( error ) *error = ZIP_LOAD_BAD_HEADER; - Mem_Free( info ); FS_CloseZIP( zip ); return NULL; } - if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + if( header_cdf.uncompressed_size && header_cdf.filename_len && header_cdf.filename_len < sizeof( filename_buffer )) { - memset( &filename_buffer, '\0', MAX_SYSPATH ); - c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + memset( &filename_buffer, '\0', sizeof( filename_buffer )); + c = FS_Read( zip->handle, &filename_buffer, header_cdf.filename_len ); if( c != header_cdf.filename_len ) { @@ -320,26 +314,36 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) if( error ) *error = ZIP_LOAD_CORRUPTED; - Mem_Free( info ); FS_CloseZIP( zip ); return NULL; } - Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); - + Q_strncpy( info[numpackfiles].name, filename_buffer, sizeof( info[numpackfiles].name )); info[numpackfiles].size = header_cdf.uncompressed_size; info[numpackfiles].compressed_size = header_cdf.compressed_size; info[numpackfiles].offset = header_cdf.local_header_offset; numpackfiles++; } else - lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + FS_Seek( zip->handle, header_cdf.filename_len, SEEK_CUR ); if( header_cdf.extrafield_len ) - lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + FS_Seek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); if( header_cdf.file_commentary_len ) - lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + FS_Seek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + } + + // refuse to load empty files again + if( numpackfiles == 0 ) + { + Con_Reportf( S_WARN "%s has no files (recalculated). Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; } // recalculate offsets @@ -347,8 +351,8 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) { zip_header_t header; - lseek( zip->handle, info[i].offset, SEEK_SET ); - c = read( zip->handle, &header, sizeof( header ) ); + FS_Seek( zip->handle, info[i].offset, SEEK_SET ); + c = FS_Read( zip->handle, &header, sizeof( header ) ); if( c != sizeof( header )) { @@ -357,7 +361,6 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) if( error ) *error = ZIP_LOAD_CORRUPTED; - Mem_Free( info ); FS_CloseZIP( zip ); return NULL; } @@ -366,19 +369,9 @@ static zip_t *FS_LoadZip( const char *zipfile, int *error ) info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); } - Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); - zip->filetime = FS_SysFileTime( zipfile ); zip->numfiles = numpackfiles; - zip->files = info; - qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(zip->handle); - zip->handle = -1; -#endif - if( error ) *error = ZIP_LOAD_OK; @@ -392,68 +385,99 @@ FS_OpenZipFile Open a packed file using its package file descriptor =========== */ -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +static file_t *FS_OpenFile_ZIP( searchpath_t *search, const char *filename, const char *mode, int pack_ind ) { - zipfile_t *pfile; - pfile = &zip->files[pack_ind]; + zipfile_t *pfile = &search->zip->files[pack_ind]; + file_t *f = FS_OpenHandle( search, search->zip->handle->handle, pfile->offset, pfile->size ); - // compressed files handled in Zip_LoadFile - if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + if( !f ) + return NULL; + + if( pfile->flags == ZIP_COMPRESSION_DEFLATED ) { - Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); + ztoolkit_t *ztk; + + SetBits( f->flags, FILE_DEFLATED ); + + ztk = Mem_Calloc( fs_mempool, sizeof( *ztk )); + ztk->comp_length = pfile->compressed_size; + ztk->zstream.next_in = ztk->input; + ztk->zstream.avail_in = 0; + + if( inflateInit2( &ztk->zstream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( "%s: inflate init error (file: %s)\n", __func__, filename ); + FS_Close( f ); + Mem_Free( ztk ); + return NULL; + } + + ztk->zstream.next_out = f->buff; + ztk->zstream.avail_out = sizeof( f->buff ); + + f->ztk = ztk; + } + else if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + { + Con_Reportf( S_ERROR "%s: %s: file compressed with unknown algorithm.\n", __func__, filename ); + FS_Close( f ); return NULL; } - return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); + return f; } -byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +/* +=========== +FS_LoadZIPFile + +=========== +*/ +static byte *FS_LoadZIPFile( searchpath_t *search, const char *path, int pack_ind, fs_offset_t *sizeptr, void *( *pfnAlloc )( size_t ), void ( *pfnFree )( void * )) { - searchpath_t *search; - int index; - zipfile_t *file = NULL; + zipfile_t *file; byte *compressed_buffer = NULL, *decompressed_buffer = NULL; int zlib_result = 0; - dword test_crc, final_crc; z_stream decompress_stream; size_t c; +#ifdef ENABLE_CRC_CHECK + dword test_crc, final_crc; +#endif // ENABLE_CRC_CHECK if( sizeptr ) *sizeptr = 0; - search = FS_FindFile( path, &index, gamedironly ); + file = &search->zip->files[pack_ind]; - if( !search || search->type != SEARCHPATH_ZIP ) - return NULL; - - file = &search->zip->files[index]; - - FS_EnsureOpenZip( search->zip ); - - if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + if( FS_Seek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) return NULL; - /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + /*if( FS_Read( search->zip->handle, &header, sizeof( header )) < 0 ) return NULL; if( header.signature != ZIP_HEADER_LF ) { - Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + Con_Reportf( S_ERROR "%s: %s signature error\n", __func__, file->name ); return NULL; }*/ + decompressed_buffer = (byte *)pfnAlloc( file->size + 1 ); + if( unlikely( !decompressed_buffer )) + { + Con_Reportf( S_ERROR "%s: can't alloc %li bytes, no free memory\n", __func__, (long)file->size + 1 ); + return NULL; + } + decompressed_buffer[file->size] = '\0'; + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) { - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - c = read( search->zip->handle, decompressed_buffer, file->size ); + c = FS_Read( search->zip->handle, decompressed_buffer, file->size ); if( c != file->size ) { - Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + Con_Reportf( S_ERROR "%s: %s size doesn't match\n", __func__, file->name ); return NULL; } -#if 0 +#ifdef ENABLE_CRC_CHECK CRC32_Init( &test_crc ); CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); @@ -461,26 +485,24 @@ byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamediron if( final_crc != file->crc32 ) { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); + Con_Reportf( S_ERROR "%s: %s file crc32 mismatch\n", __func__, file->name ); + pfnFree( decompressed_buffer ); return NULL; } -#endif +#endif // ENABLE_CRC_CHECK + if( sizeptr ) *sizeptr = file->size; - FS_EnsureOpenZip( NULL ); return decompressed_buffer; } else if( file->flags == ZIP_COMPRESSION_DEFLATED ) { - compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; + compressed_buffer = (byte *)Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + c = FS_Read( search->zip->handle, compressed_buffer, file->compressed_size ); if( c != file->compressed_size ) { - Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + Con_Reportf( S_ERROR "%s: %s compressed size doesn't match\n", __func__, file->name ); return NULL; } @@ -497,7 +519,7 @@ byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamediron if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) { - Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Con_Printf( S_ERROR "%s: inflateInit2 failed\n", __func__ ); Mem_Free( compressed_buffer ); Mem_Free( decompressed_buffer ); return NULL; @@ -509,7 +531,7 @@ byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamediron if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) { Mem_Free( compressed_buffer ); // finaly free compressed buffer -#if 0 +#if ENABLE_CRC_CHECK CRC32_Init( &test_crc ); CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); @@ -517,117 +539,85 @@ byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamediron if( final_crc != file->crc32 ) { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); + Con_Reportf( S_ERROR "%s: %s file crc32 mismatch\n", __func__, file->name ); + pfnFree( decompressed_buffer ); return NULL; } #endif if( sizeptr ) *sizeptr = file->size; - FS_EnsureOpenZip( NULL ); return decompressed_buffer; } else { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Con_Reportf( S_ERROR "%s: %s: error while file decompressing. Zlib return code %d.\n", __func__, file->name, zlib_result ); Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); + pfnFree( decompressed_buffer ); return NULL; } } else { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + Con_Reportf( S_ERROR "%s: %s: file compressed with unknown algorithm.\n", __func__, file->name ); + pfnFree( decompressed_buffer ); return NULL; } - FS_EnsureOpenZip( NULL ); return NULL; } +/* +=========== +FS_FileTime_ZIP -qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +=========== +*/ +static int FS_FileTime_ZIP( searchpath_t *search, const char *filename ) { - searchpath_t *search; - zip_t *zip = NULL; - const char *ext = COM_FileExtension( zipfile ); - int errorcode = ZIP_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->type == SEARCHPATH_ZIP && !Q_stricmp( search->zip->filename, zipfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) *already_loaded = false; - - if( !Q_stricmp( ext, "pk3" ) ) - zip = FS_LoadZip( zipfile, &errorcode ); - - if( zip ) - { - string fullpath; - int i; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); - search->zip = zip; - search->type = SEARCHPATH_ZIP; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < zip->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - return true; - } - else - { - if( errorcode != ZIP_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); - return false; - } + return search->zip->handle->filetime; } -int FS_FileTimeZIP( zip_t *zip ) +/* +=========== +FS_PrintInfo_ZIP + +=========== +*/ +static void FS_PrintInfo_ZIP( searchpath_t *search, char *dst, size_t size ) { - return zip->filetime; + if( search->zip->handle->searchpath ) + Q_snprintf( dst, size, "%s (%i files)" S_CYAN " from %s" S_DEFAULT, search->filename, search->zip->numfiles, search->zip->handle->searchpath->filename ); + else Q_snprintf( dst, size, "%s (%i files)", search->filename, search->zip->numfiles ); } -void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ) -{ - Q_snprintf( dst, size, "%s (%i files)", zip->filename, zip->numfiles ); -} +/* +=========== +FS_FindFile_ZIP -int FS_FindFileZIP( zip_t *zip, const char *name ) +=========== +*/ +static int FS_FindFile_ZIP( searchpath_t *search, const char *path, char *fixedname, size_t len ) { int left, right, middle; // look for the file (binary search) left = 0; - right = zip->numfiles - 1; + right = search->zip->numfiles - 1; while( left <= right ) { int diff; middle = (left + right) / 2; - diff = Q_stricmp( zip->files[middle].name, name ); + diff = Q_stricmp( search->zip->files[middle].name, path ); // Found it if( !diff ) + { + if( fixedname ) + Q_strncpy( fixedname, search->zip->files[middle].name, len ); return middle; + } // if we're too far in the list if( diff > 0 ) @@ -638,15 +628,21 @@ int FS_FindFileZIP( zip_t *zip, const char *name ) return -1; } -void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) +/* +=========== +FS_Search_ZIP + +=========== +*/ +static void FS_Search_ZIP( searchpath_t *search, stringlist_t *list, const char *pattern, int caseinsensitive ) { string temp; const char *slash, *backslash, *colon, *separator; int j, i; - for( i = 0; i < zip->numfiles; i++ ) + for( i = 0; i < search->zip->numfiles; i++ ) { - Q_strncpy( temp, zip->files[i].name, sizeof( temp )); + Q_strncpy( temp, search->zip->files[i].name, sizeof( temp )); while( temp[0] ) { if( matchpattern( temp, pattern, true )) @@ -678,3 +674,42 @@ void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) } } +/* +=========== +FS_AddZip_Fullpath + +=========== +*/ +searchpath_t *FS_AddZip_Fullpath( const char *zipfile, int flags ) +{ + searchpath_t *search; + zip_t *zip; + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + zip = FS_LoadZip( zipfile, &errorcode ); + + if( !zip ) + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "%s: unable to load zip \"%s\"\n", __func__, zipfile ); + return NULL; + } + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + Q_strncpy( search->filename, zipfile, sizeof( search->filename )); + search->zip = zip; + search->type = SEARCHPATH_ZIP; + search->flags = flags; + + search->pfnPrintInfo = FS_PrintInfo_ZIP; + search->pfnClose = FS_Close_ZIP; + search->pfnOpenFile = FS_OpenFile_ZIP; + search->pfnFileTime = FS_FileTime_ZIP; + search->pfnFindFile = FS_FindFile_ZIP; + search->pfnSearch = FS_Search_ZIP; + search->pfnLoadFile = FS_LoadZIPFile; + + Con_Reportf( "Adding ZIP: %s (%i files)\n", zipfile, zip->numfiles ); + return search; +} + diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 704c9f16..f708fe7a 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -23,14 +23,11 @@ GNU General Public License for more details. #if XASH_POSIX #define XASHLIB "libxash." OS_LIB_EXT -#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) -#define GetProcAddress( x, y ) dlsym( x, y ) #define FreeLibrary( x ) dlclose( x ) #elif XASH_WIN32 #include // CommandLineToArgvW -#define XASHLIB "xash.dll" -#define SDL2LIB "SDL2.dll" -#define dlerror() GetStringLastError() +#define XASHLIB L"xash.dll" +#define SDL2LIB L"SDL2.dll" extern "C" { @@ -44,23 +41,22 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #error // port me! #endif -#define E_GAME "XASH3D_GAME" // default env dir to start from #ifndef XASH_GAMEDIR -#define XASH_GAMEDIR "valve" +#define XASH_GAMEDIR "valve" // !!! Replace with your default (base) game directory !!! #endif typedef void (*pfnChangeGame)( const char *progname ); typedef int (*pfnInit)( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ); typedef void (*pfnShutdown)( void ); -static pfnInit Xash_Main; -static pfnShutdown Xash_Shutdown = NULL; +static pfnInit Host_Main; +static pfnShutdown Host_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir static int szArgc; static char **szArgv; static HINSTANCE hEngine; -static void Xash_Error( const char *szFmt, ... ) +static void Launch_Error( const char *szFmt, ... ) { static char buffer[16384]; // must support > 1k messages va_list args; @@ -69,7 +65,7 @@ static void Xash_Error( const char *szFmt, ... ) vsnprintf( buffer, sizeof(buffer), szFmt, args ); va_end( args ); -#if defined( _WIN32 ) +#if XASH_WIN32 MessageBoxA( NULL, buffer, "Xash Error", MB_OK ); #else fprintf( stderr, "Xash Error: %s\n", buffer ); @@ -78,7 +74,7 @@ static void Xash_Error( const char *szFmt, ... ) exit( 1 ); } -#ifdef _WIN32 +#if XASH_WIN32 static const char *GetStringLastError() { static char buf[1024]; @@ -94,40 +90,62 @@ static const char *GetStringLastError() static void Sys_LoadEngine( void ) { #if XASH_WIN32 - HMODULE hSdl; + HMODULE hSDL = LoadLibraryExW( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE ); - if (( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE )) == NULL ) + if( !hSDL ) { - Xash_Error("Unable to load the " SDL2LIB ": %s", dlerror() ); + Launch_Error("Unable to load %ls: %s", SDL2LIB, GetStringLastError( )); + return; } - else + + FreeLibrary( hSDL ); + + hEngine = LoadLibraryW( XASHLIB ); + if( !hEngine ) { - FreeLibrary( hSdl ); + Launch_Error( "Unable to load %ls: %s", XASHLIB, GetStringLastError( )); + return; } + + Host_Main = (pfnInit)GetProcAddress( hEngine, "Host_Main" ); + + if( !Host_Main ) + { + Launch_Error( "%ls missed 'Host_Main' export: %s", XASHLIB, GetStringLastError( )); + return; + } + + Host_Shutdown = (pfnShutdown)GetProcAddress( hEngine, "Host_Shutdown" ); +#elif XASH_POSIX + hEngine = dlopen( XASHLIB, RTLD_NOW ); + if( !hEngine ) + { + Launch_Error( "Unable to load %s: %s", XASHLIB, dlerror( )); + return; + } + + Host_Main = (pfnInit)dlsym( hEngine, "Host_Main" ); + + if( !Host_Main ) + { + Launch_Error( "%s missed 'Host_Main' export: %s", XASHLIB, dlerror( )); + return; + } + + Host_Shutdown = (pfnShutdown)dlsym( hEngine, "Host_Shutdown" ); +#else +#error "port me!" #endif - - if(( hEngine = LoadLibrary( XASHLIB )) == NULL ) - { - Xash_Error("Unable to load the " XASHLIB ": %s", dlerror() ); - } - - if(( Xash_Main = (pfnInit)GetProcAddress( hEngine, "Host_Main" )) == NULL ) - { - Xash_Error( XASHLIB " missed 'Host_Main' export: %s", dlerror() ); - } - - // this is non-fatal for us but change game will not working - Xash_Shutdown = (pfnShutdown)GetProcAddress( hEngine, "Host_Shutdown" ); } static void Sys_UnloadEngine( void ) { - if( Xash_Shutdown ) Xash_Shutdown( ); + if( Host_Shutdown ) Host_Shutdown( ); if( hEngine ) FreeLibrary( hEngine ); hEngine = NULL; - Xash_Main = NULL; - Xash_Shutdown = NULL; + Host_Main = NULL; + Host_Shutdown = NULL; } static void Sys_ChangeGame( const char *progname ) @@ -135,35 +153,50 @@ static void Sys_ChangeGame( const char *progname ) // a1ba: may never be called within engine // if platform supports execv() function if( !progname || !progname[0] ) - Xash_Error( "Sys_ChangeGame: NULL gamedir" ); + { + Launch_Error( "Sys_ChangeGame: NULL gamedir" ); + return; + } - if( Xash_Shutdown == NULL ) - Xash_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); + if( Host_Shutdown == NULL ) + { + Launch_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); + return; + } strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Sys_UnloadEngine(); Sys_LoadEngine (); - Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); + Host_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); } -_inline int Sys_Start( void ) +static int Sys_Start( void ) { int ret; pfnChangeGame changeGame = NULL; - const char *game = getenv( E_GAME ); - if( !game ) - game = XASH_GAMEDIR; +#if XASH_SAILFISH + const char *home = getenv( "HOME" ); + char buf[1024]; - strncpy( szGameDir, game, sizeof( szGameDir ) - 1 ); + snprintf( buf, sizeof( buf ), "%s/xash", home ); + setenv( "XASH3D_BASEDIR", buf, true ); +#if XASH_AURORAOS + setenv( "XASH3D_RODIR", "/usr/share/su.xash.Engine/rodir", true ); +#else + setenv( "XASH3D_RODIR", "/usr/share/harbour-xash3d-fwgs/rodir", true ); +#endif // XASH_AURORAOS +#endif // XASH_SAILFISH + + strncpy( szGameDir, XASH_GAMEDIR, sizeof( szGameDir ) - 1 ); Sys_LoadEngine(); - if( Xash_Shutdown ) + if( Host_Shutdown ) changeGame = Sys_ChangeGame; - ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame ); + ret = Host_Main( szArgc, szArgv, szGameDir, 0, XASH_DISABLE_MENU_CHANGEGAME ? NULL : changeGame ); Sys_UnloadEngine(); diff --git a/game_launch/icon-xash-material.ico b/game_launch/icon-xash-material.ico index 7c1ac3cc..27b7d5db 100644 Binary files a/game_launch/icon-xash-material.ico and b/game_launch/icon-xash-material.ico differ diff --git a/game_launch/icon-xash-material.png b/game_launch/icon-xash-material.png index 2f320750..5984d70c 100644 Binary files a/game_launch/icon-xash-material.png and b/game_launch/icon-xash-material.png differ diff --git a/game_launch/wscript b/game_launch/wscript index 138d6911..1247e46b 100644 --- a/game_launch/wscript +++ b/game_launch/wscript @@ -12,29 +12,28 @@ def options(opt): grp = opt.add_option_group('Game launcher options') grp.add_option('--disable-menu-changegame', action = 'store_true', dest = 'DISABLE_MENU_CHANGEGAME', default = False, - help = 'disable changing the game from the menu [default: %default]') + help = 'disable changing the game from the menu [default: %(default)s]') def configure(conf): if conf.env.DEST_OS == 'win32': conf.load('winres') - conf.define_cond('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME) + conf.define('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME) def build(bld): - libs = ['sdk_includes'] source = ['game.cpp'] - if bld.env.DEST_OS != 'win32': - libs += [ 'DL' ] - else: - libs += ['USER32', 'SHELL32'] + if bld.env.DEST_OS == 'win32': source += ['game.rc'] - bld(source = source, + # Half-Life 25th anniversary update doesn't have server library explicitly linked to libm + if bld.env.DEST_OS == 'linux': + bld.env.LDFLAGS += ['-Wl,--no-as-needed', '-lm'] + + bld.program(source = source, target = 'xash3d', # hl.exe - features = 'c cxx cxxprogram', - use = libs, - rpath = '$ORIGIN', + use = 'sdk_includes DL USER32 SHELL32 werror', + rpath = bld.env.DEFAULT_RPATH, install_path = bld.env.BINDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/pm_shared/pm_defs.h b/pm_shared/pm_defs.h index ce0727f6..6d17ca00 100644 --- a/pm_shared/pm_defs.h +++ b/pm_shared/pm_defs.h @@ -216,6 +216,7 @@ typedef struct playermove_s pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe )); int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe )); struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe )); - struct msurface_s *(*PM_TraceSurface)( int ground, float *vstart, float *vend ); + + struct msurface_s *(*PM_TraceSurface)( int ground, float *vstart, float *vend ); // Xash3D-specific } playermove_t; #endif//PM_DEFS_H diff --git a/public/build.c b/public/build.c index b9662c53..49a82112 100644 --- a/public/build.c +++ b/public/build.c @@ -13,46 +13,60 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include #include "crtlib.h" +#include "buildenums.h" -static const char *date = __DATE__ ; -static const char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -// returns days since Apr 1 2015 -int Q_buildnum( void ) +int Q_buildnum_iso( const char *date ) { - int m = 0, d = 0, y = 0; - static int b = 0; + int y, m, d, b, i; - if( b != 0 ) return b; + if( sscanf( date, "%d-%d-%d", &y, &m, &d ) != 3 || y <= 1900 || m <= 0 || d <= 0 ) + return -1; - for( m = 0; m < 11; m++ ) - { - if( !Q_strnicmp( &date[0], mon[m], 3 )) - break; - d += mond[m]; - } + // fixup day and month + m--; + d--; - d += Q_atoi( &date[4] ) - 1; - y = Q_atoi( &date[7] ) - 1900; + for( i = 0; i < m; i++ ) + d += mond[i]; + + y -= 1900; b = d + (int)((y - 1) * 365.25f ); if((( y % 4 ) == 0 ) && m > 1 ) - { b += 1; - } b -= 41728; // Apr 1 2015 return b; } +/* +=============== +Q_buildnum + +returns days since Apr 1 2015 +=============== +*/ +int Q_buildnum( void ) +{ + static int b = 0; + + if( b ) return b; + + b = Q_buildnum_iso( g_buildcommit_date ); + + return b; +} + /* ============= Q_buildnum_compat Returns a Xash3D build number. This is left for compability with original Xash3D. -IMPORTANT: this value must be changed ONLY after updating to newer Xash3D +IMPORTANT: this value must be changed ONLY after updating to newer Xash3D base IMPORTANT: this value must be acquired through "build" cvar. ============= */ @@ -62,46 +76,146 @@ int Q_buildnum_compat( void ) return 4529; } +/* +============ +Q_GetPlatformStringByID + +Returns name of operating system by ID. Without any spaces. + +TODO: add platform-dependent ABI variants, for example, different libc on Linux +============ +*/ +const char *Q_PlatformStringByID( const int platform ) +{ + switch( platform ) + { + case PLATFORM_WIN32: + return "win32"; + case PLATFORM_ANDROID: + return "android"; + case PLATFORM_LINUX: + return "linux"; + case PLATFORM_APPLE: + return "apple"; + case PLATFORM_FREEBSD: + return "freebsd"; + case PLATFORM_NETBSD: + return "netbsd"; + case PLATFORM_OPENBSD: + return "openbsd"; + case PLATFORM_EMSCRIPTEN: + return "emscripten"; + case PLATFORM_DOS4GW: + return "DOS4GW"; + case PLATFORM_HAIKU: + return "haiku"; + case PLATFORM_SERENITY: + return "serenity"; + case PLATFORM_IRIX: + return "irix"; + case PLATFORM_NSWITCH: + return "nswitch"; + case PLATFORM_PSVITA: + return "psvita"; + case PLATFORM_WASI: + return "wasi"; + case PLATFORM_SUNOS: + return "sunos"; + case PLATFORM_HURD: + return "hurd"; + } + + assert( 0 ); + return "unknown"; +} + /* ============ Q_buildos -Returns current name of operating system. Without any spaces. +Shortcut for Q_buildos_ ============ */ const char *Q_buildos( void ) { - const char *osname; + return Q_PlatformStringByID( XASH_PLATFORM ); +} -#if XASH_MINGW - osname = "win32-mingw"; -#elif XASH_WIN32 - osname = "win32"; -#elif XASH_ANDROID - osname = "android"; -#elif XASH_LINUX - osname = "linux"; -#elif XASH_APPLE - osname = "apple"; -#elif XASH_FREEBSD - osname = "freebsd"; -#elif XASH_NETBSD - osname = "netbsd"; -#elif XASH_OPENBSD - osname = "openbsd"; -#elif XASH_EMSCRIPTEN - osname = "emscripten"; -#elif XASH_DOS4GW - osname = "DOS4GW"; -#elif XASH_HAIKU - osname = "haiku"; -#elif XASH_SERENITY - osname = "serenityos"; -#else -#error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" -#endif - return osname; +/* +============ +Q_ArchitectureStringByID + +Returns name of the architecture by it's ID. Without any spaces. +============ +*/ +const char *Q_ArchitectureStringByID( const int arch, const uint abi, const int endianness, const qboolean is64 ) +{ + // I don't want to change this function prototype + // and don't want to use static buffer either + // so encode all possible variants... :) + switch( arch ) + { + case ARCHITECTURE_AMD64: + return "amd64"; + case ARCHITECTURE_X86: + return "i386"; + case ARCHITECTURE_E2K: + return "e2k"; + case ARCHITECTURE_JS: + return "javascript"; + case ARCHITECTURE_PPC: + return endianness == ENDIANNESS_LITTLE ? + ( is64 ? "ppc64el" : "ppcel" ): + ( is64 ? "ppc64" : "ppc" ); + case ARCHITECTURE_MIPS: + return endianness == ENDIANNESS_LITTLE ? + ( is64 ? "mips64el" : "mipsel" ): + ( is64 ? "mips64" : "mips" ); + case ARCHITECTURE_ARM: + // no support for big endian ARM here + if( endianness == ENDIANNESS_LITTLE ) + { + const uint ver = ( abi >> ARCH_ARM_VER_SHIFT ) & ARCH_ARM_VER_MASK; + const qboolean hardfp = FBitSet( abi, ARCH_ARM_HARDFP ); + + if( is64 ) + return "arm64"; // keep as arm64, it's not aarch64! + + switch( ver ) + { + case 8: + return hardfp ? "armv8_32hf" : "armv8_32l"; + case 7: + return hardfp ? "armv7hf" : "armv7l"; + case 6: + return "armv6l"; + case 5: + return "armv5l"; + case 4: + return "armv4l"; + } + } + break; + case ARCHITECTURE_RISCV: + switch( abi ) + { + case ARCH_RISCV_FP_SOFT: + return is64 ? "riscv64" : "riscv32"; + case ARCH_RISCV_FP_SINGLE: + return is64 ? "riscv64f" : "riscv32f"; + case ARCH_RISCV_FP_DOUBLE: + return is64 ? "riscv64d" : "riscv32d"; + } + break; + case ARCHITECTURE_WASM: + return is64 ? "wasm64" : "wasm32"; + } + + assert( 0 ); + return is64 ? + ( endianness == ENDIANNESS_LITTLE ? "unknown64el" : "unknownel" ) : + ( endianness == ENDIANNESS_LITTLE ? "unknown64be" : "unknownbe" ); } /* @@ -113,84 +227,15 @@ Returns current name of the architecture. Without any spaces. */ const char *Q_buildarch( void ) { - const char *archname; - -#if XASH_AMD64 - archname = "amd64"; -#elif XASH_X86 - archname = "i386"; -#elif XASH_ARM && XASH_64BIT - archname = "arm64"; -#elif XASH_ARM - archname = "armv" - #if XASH_ARM == 8 - "8_32" // for those who (mis)using 32-bit OS on 64-bit CPU - #elif XASH_ARM == 7 - "7" - #elif XASH_ARM == 6 - "6" - #elif XASH_ARM == 5 - "5" - #elif XASH_ARM == 4 - "4" - #endif - - #if XASH_ARM_HARDFP - "hf" - #else - "l" - #endif - ; -#elif XASH_MIPS && XASH_BIG_ENDIAN - archname = "mips" - #if XASH_64BIT - "64" - #endif - #if XASH_LITTLE_ENDIAN - "el" - #endif - ; -#elif XASH_RISCV - archname = "riscv" - #if XASH_64BIT - "64" - #else - "32" - #endif - #if XASH_RISCV_SINGLEFP - "d" - #elif XASH_RISCV_DOUBLEFP - "f" - #endif - ; -#elif XASH_JS - archname = "javascript"; -#elif XASH_E2K - archname = "e2k"; + return Q_ArchitectureStringByID( + XASH_ARCHITECTURE, + XASH_ARCHITECTURE_ABI, + XASH_ENDIANNESS, +#if XASH_64BIT + true #else -#error "Place your architecture name here! If this is a mistake, try to fix conditions above and report a bug" -#endif - - return archname; -} - -/* -============= -Q_buildcommit - -Returns a short hash of current commit in VCS as string. -XASH_BUILD_COMMIT must be passed in quotes - -if XASH_BUILD_COMMIT is not defined, -Q_buildcommit will identify this build as "notset" -============= -*/ -const char *Q_buildcommit( void ) -{ -#ifdef XASH_BUILD_COMMIT - return XASH_BUILD_COMMIT; -#else - return "notset"; + false #endif + ); } diff --git a/public/build.h b/public/build.h index 57a7735f..bba120be 100644 --- a/public/build.h +++ b/public/build.h @@ -30,15 +30,22 @@ For more information, please refer to #ifndef BUILD_H #define BUILD_H -// All XASH_* macros set by this header are guaranteed to have positive value otherwise not defined +/* +All XASH_* macros set by this header are guaranteed to have positive value +otherwise not defined. -// Any new define must be undefined at first -// You can generate #undef list below with this oneliner: -// $ cat build.h | sed 's/\t//g' | grep '^#define XASH' | awk '{ print $2 }' | sort | uniq | awk '{ print "#undef " $1 }' -// -// So in various buildscripts you can grep for ^#undef XASH and select only second word -// or in another oneliner: -// $ cat build.h | grep '^#undef XASH' | awk '{ print $2 }' +Every macro is intended to be the unified interface for buildsystems that lack +platform & CPU detection, and a neat quick way for checks in platform code +For Q_build* macros, refer to buildenums.h + +Any new define must be undefined at first +You can generate #undef list below with this oneliner: + $ sed 's/\t//g' build.h | grep '^#define XASH' | awk '{ print $2 }' | \ + sort | uniq | awk '{ print "#undef " $1 }' + +Then you can use another oneliner to query all variables: + $ grep '^#undef XASH' build.h | awk '{ print $2 }' +*/ #undef XASH_64BIT #undef XASH_AMD64 @@ -53,87 +60,97 @@ For more information, please refer to #undef XASH_ARMv7 #undef XASH_ARMv8 #undef XASH_BIG_ENDIAN -#undef XASH_BSD #undef XASH_DOS4GW #undef XASH_E2K #undef XASH_EMSCRIPTEN #undef XASH_FREEBSD #undef XASH_HAIKU +#undef XASH_HURD #undef XASH_IOS +#undef XASH_IRIX #undef XASH_JS #undef XASH_LINUX #undef XASH_LITTLE_ENDIAN -#undef XASH_MINGW #undef XASH_MIPS #undef XASH_MOBILE_PLATFORM -#undef XASH_MSVC #undef XASH_NETBSD #undef XASH_OPENBSD #undef XASH_POSIX +#undef XASH_PPC #undef XASH_RISCV #undef XASH_RISCV_DOUBLEFP #undef XASH_RISCV_SINGLEFP #undef XASH_RISCV_SOFTFP #undef XASH_SERENITY +#undef XASH_SUNOS +#undef XASH_TERMUX #undef XASH_WIN32 -#undef XASH_WIN64 #undef XASH_X86 +#undef XASH_NSWITCH +#undef XASH_PSVITA +#undef XASH_WASI +#undef XASH_WASM //================================================================ // -// OPERATING SYSTEM DEFINES +// PLATFORM DETECTION CODE // //================================================================ -#if defined(_WIN32) +#if defined _WIN32 #define XASH_WIN32 1 - #if defined(__MINGW32__) - #define XASH_MINGW 1 - #elif defined(_MSC_VER) - #define XASH_MSVC 1 - #endif - - #if defined(_WIN64) - #define XASH_WIN64 1 - #endif -#elif defined(__linux__) - #define XASH_LINUX 1 - #if defined(__ANDROID__) - #define XASH_ANDROID 1 - #endif // defined(__ANDROID__) - #define XASH_POSIX 1 -#elif defined(__APPLE__) - #include - #define XASH_APPLE 1 - #if TARGET_OS_IOS - #define XASH_IOS 1 - #endif // TARGET_OS_IOS - #define XASH_POSIX 1 -#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) - #define XASH_BSD 1 - #if defined(__FreeBSD__) - #define XASH_FREEBSD 1 - #elif defined(__NetBSD__) - #define XASH_NETBSD 1 - #elif defined(__OpenBSD__) - #define XASH_OPENBSD 1 - #endif - #define XASH_POSIX 1 -#elif defined __EMSCRIPTEN__ - #define XASH_EMSCRIPTEN 1 #elif defined __WATCOMC__ && defined __DOS__ #define XASH_DOS4GW 1 - #define XASH_LITTLE_ENDIAN 1 -#elif defined __HAIKU__ - #define XASH_HAIKU 1 +#else // POSIX compatible #define XASH_POSIX 1 -#elif defined __serenity__ - #define XASH_SERENITY 1 - #define XASH_POSIX 1 -#else -#error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" + #if defined __linux__ + #if defined __ANDROID__ + #define XASH_ANDROID 1 + #if defined __TERMUX__ + #define XASH_TERMUX 1 + #endif + #endif + #define XASH_LINUX 1 + #elif defined __FreeBSD__ + #define XASH_FREEBSD 1 + #elif defined __NetBSD__ + #define XASH_NETBSD 1 + #elif defined __OpenBSD__ + #define XASH_OPENBSD 1 + #elif defined __HAIKU__ + #define XASH_HAIKU 1 + #elif defined __serenity__ + #define XASH_SERENITY 1 + #elif defined __sgi + #define XASH_IRIX 1 + #elif defined __APPLE__ + #include + #define XASH_APPLE 1 + #if TARGET_OS_IOS + #define XASH_IOS 1 + #endif // TARGET_OS_IOS + #elif defined __SWITCH__ + #define XASH_NSWITCH 1 + #elif defined __vita__ + #define XASH_PSVITA 1 + #elif defined __wasi__ + #define XASH_WASI 1 + #elif defined __sun__ + #define XASH_SUNOS 1 + #elif defined __EMSCRIPTEN__ + #define XASH_EMSCRIPTEN 1 + #elif defined __gnu_hurd__ + #define XASH_HURD 1 + #else + #error + #endif #endif -#if defined XASH_ANDROID || defined XASH_IOS +// XASH_SAILFISH is special: SailfishOS by itself is a normal GNU/Linux platform +// It doesn't make sense to split it to separate platform +// but we still need XASH_MOBILE_PLATFORM for the engine. +// So this macro is defined entirely in build-system: see main wscript +// HLSDK/PrimeXT/other SDKs users note: you may ignore this macro +#if ( XASH_ANDROID && !XASH_TERMUX ) || XASH_IOS || XASH_NSWITCH || XASH_PSVITA || XASH_SAILFISH #define XASH_MOBILE_PLATFORM 1 #endif @@ -143,27 +160,17 @@ For more information, please refer to // //================================================================ -#if defined(XASH_FORCE_LITTLE_ENDIAN) && defined(XASH_FORCE_BIG_ENDIAN) - #error "Both XASH_FORCE_LITTLE_ENDIAN and XASH_FORCE_BIG_ENDIAN are defined" -#elif defined(XASH_FORCE_LITTLE_ENDIAN) - #define XASH_LITTLE_ENDIAN 1 -#elif defined(XASH_FORCE_BIG_ENDIAN) - #define XASH_BIG_ENDIAN 1 -#endif - -#if !defined(XASH_LITTLE_ENDIAN) && !defined(XASH_BIG_ENDIAN) - #if defined XASH_MSVC || __LITTLE_ENDIAN__ +#if !defined XASH_ENDIANNESS + #if defined XASH_WIN32 || __LITTLE_ENDIAN__ //!!! Probably all WinNT installations runs in little endian #define XASH_LITTLE_ENDIAN 1 #elif __BIG_ENDIAN__ #define XASH_BIG_ENDIAN 1 - #elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && defined(__ORDER_LITTLE_ENDIAN__) // some compilers define this + #elif defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ && defined __ORDER_LITTLE_ENDIAN__ // some compilers define this #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define XASH_BIG_ENDIAN 1 #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define XASH_LITTLE_ENDIAN 1 - #else - #error "Unknown endianness!" #endif #else #include @@ -171,8 +178,6 @@ For more information, please refer to #define XASH_BIG_ENDIAN 1 #elif __BYTE_ORDER == __LITTLE_ENDIAN #define XASH_LITTLE_ENDIAN 1 - #else - #error "Unknown endianness!" #endif #endif // !XASH_WIN32 #endif @@ -182,18 +187,34 @@ For more information, please refer to // CPU ARCHITECTURE DEFINES // //================================================================ -#if defined(__x86_64__) || defined(_M_X64) +#if defined __x86_64__ || defined _M_X64 #define XASH_64BIT 1 #define XASH_AMD64 1 -#elif defined(__i386__) || defined(_X86_) || defined(_M_IX86) +#elif defined __i386__ || defined _X86_ || defined _M_IX86 #define XASH_X86 1 #elif defined __aarch64__ || defined _M_ARM64 #define XASH_64BIT 1 - #define XASH_ARM 8 -#elif defined __arm__ || defined _M_ARM + #define XASH_ARM 8 +#elif defined __mips__ + #define XASH_MIPS 1 +// commented out to avoid misdetection, modern Emscripten versions target WASM only +//#elif defined __EMSCRIPTEN__ +// #define XASH_JS 1 +#elif defined __e2k__ + #define XASH_64BIT 1 + #define XASH_E2K 1 +#elif defined __PPC__ || defined __powerpc__ + #define XASH_PPC 1 + #if defined __PPC64__ || defined __powerpc64__ + #define XASH_64BIT 1 + #endif +#elif defined _M_ARM // msvc + #define XASH_ARM 7 + #define XASH_ARM_HARDFP 1 +#elif defined __arm__ #if __ARM_ARCH == 8 || __ARM_ARCH_8__ #define XASH_ARM 8 - #elif __ARM_ARCH == 7 || __ARM_ARCH_7__ || defined _M_ARM // msvc can only armv7 in 32 bit + #elif __ARM_ARCH == 7 || __ARM_ARCH_7__ #define XASH_ARM 7 #elif __ARM_ARCH == 6 || __ARM_ARCH_6__ || __ARM_ARCH_6J__ #define XASH_ARM 6 @@ -205,29 +226,17 @@ For more information, please refer to #error "Unknown ARM" #endif - #if defined _M_ARM - #error "No WinMobile port yet! Need to determine which ARM float ABI msvc uses if applicable" - #endif - #if defined __SOFTFP__ || __ARM_PCS_VFP == 0 #define XASH_ARM_SOFTFP 1 #else // __SOFTFP__ #define XASH_ARM_HARDFP 1 #endif // __SOFTFP__ -#elif defined __mips__ - #define XASH_MIPS 1 -#elif defined __EMSCRIPTEN__ - #define XASH_JS 1 -#elif defined __e2k__ - #define XASH_64BIT 1 - #define XASH_E2K 1 #elif defined __riscv #define XASH_RISCV 1 + #if __riscv_xlen == 64 #define XASH_64BIT 1 - #elif __riscv_xlen == 32 - // ... - #else + #elif __riscv_xlen != 32 #error "Unknown RISC-V ABI" #endif @@ -240,12 +249,17 @@ For more information, please refer to #else #error "Unknown RISC-V float ABI" #endif +#elif defined __wasm__ + #if defined __wasm64__ + #define XASH_64BIT 1 + #endif + #define XASH_WASM 1 #else #error "Place your architecture name here! If this is a mistake, try to fix conditions above and report a bug" #endif -#if defined(XASH_WAF_DETECTED_64BIT) && !defined(XASH_64BIT) - #define XASH_64BIT 1 +#if !XASH_64BIT && ( defined( __LP64__ ) || defined( _LP64 )) +#define XASH_64BIT 1 #endif #if XASH_ARM == 8 diff --git a/public/build_vcs.c b/public/build_vcs.c new file mode 100644 index 00000000..786d4232 --- /dev/null +++ b/public/build_vcs.c @@ -0,0 +1,18 @@ +/* +build_vcs.c - info from VCS +Copyright (C) 2025 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +const char *g_buildcommit = XASH_BUILD_COMMIT; +const char *g_buildbranch = XASH_BUILD_BRANCH; +const char *g_buildcommit_date = XASH_BUILD_COMMIT_DATE; diff --git a/public/buildenums.h b/public/buildenums.h new file mode 100644 index 00000000..f4f3e155 --- /dev/null +++ b/public/buildenums.h @@ -0,0 +1,175 @@ +/* +build.h - compile-time build information +Copyright (C) 2023 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#pragma once +#ifndef BUILDENUMS_H +#define BUILDENUMS_H + +#include "build.h" + +// This header defines the enumeration values that can be passed to Q_build* +// functions and get current value through XASH_PLATFORM, XASH_ARCHITECTURE and +// XASH_ARCHITECTURE_ABI defines + +//================================================================ +// +// OPERATING SYSTEM DEFINES +// +//================================================================ +#define PLATFORM_WIN32 1 +#define PLATFORM_LINUX 2 +#define PLATFORM_FREEBSD 3 +#define PLATFORM_ANDROID 4 +#define PLATFORM_APPLE 5 +#define PLATFORM_NETBSD 6 +#define PLATFORM_OPENBSD 7 +#define PLATFORM_EMSCRIPTEN 8 +#define PLATFORM_DOS4GW 9 +#define PLATFORM_HAIKU 10 +#define PLATFORM_SERENITY 11 +#define PLATFORM_IRIX 12 +#define PLATFORM_NSWITCH 13 +#define PLATFORM_PSVITA 14 +#define PLATFORM_WASI 15 +#define PLATFORM_SUNOS 16 +#define PLATFORM_HURD 17 + +#if XASH_WIN32 + #define XASH_PLATFORM PLATFORM_WIN32 +#elif XASH_ANDROID + #define XASH_PLATFORM PLATFORM_ANDROID +#elif XASH_LINUX + #define XASH_PLATFORM PLATFORM_LINUX +#elif XASH_APPLE + #define XASH_PLATFORM PLATFORM_APPLE +#elif XASH_FREEBSD + #define XASH_PLATFORM PLATFORM_FREEBSD +#elif XASH_NETBSD + #define XASH_PLATFORM PLATFORM_NETBSD +#elif XASH_OPENBSD + #define XASH_PLATFORM PLATFORM_OPENBSD +#elif XASH_EMSCRIPTEN + #define XASH_PLATFORM PLATFORM_EMSCRIPTEN +#elif XASH_DOS4GW + #define XASH_PLATFORM PLATFORM_DOS4GW +#elif XASH_HAIKU + #define XASH_PLATFORM PLATFORM_HAIKU +#elif XASH_SERENITY + #define XASH_PLATFORM PLATFORM_SERENITY +#elif XASH_IRIX + #define XASH_PLATFORM PLATFORM_IRIX +#elif XASH_NSWITCH + #define XASH_PLATFORM PLATFORM_NSWITCH +#elif XASH_PSVITA + #define XASH_PLATFORM PLATFORM_PSVITA +#elif XASH_WASI + #define XASH_PLATFORM PLATFORM_WASI +#elif XASH_SUNOS + #define XASH_PLATFORM PLATFORM_SUNOS +#elif XASH_HURD + #define XASH_PLATFORM PLATFORM_HURD +#else + #error +#endif + +//================================================================ +// +// CPU ARCHITECTURE DEFINES +// +//================================================================ +#define ARCHITECTURE_X86 1 +#define ARCHITECTURE_AMD64 2 +#define ARCHITECTURE_ARM 3 +#define ARCHITECTURE_MIPS 4 +#define ARCHITECTURE_JS 6 +#define ARCHITECTURE_E2K 7 +#define ARCHITECTURE_RISCV 8 +#define ARCHITECTURE_PPC 9 +#define ARCHITECTURE_WASM 10 + +#if XASH_AMD64 + #define XASH_ARCHITECTURE ARCHITECTURE_AMD64 +#elif XASH_X86 + #define XASH_ARCHITECTURE ARCHITECTURE_X86 +#elif XASH_ARM + #define XASH_ARCHITECTURE ARCHITECTURE_ARM +#elif XASH_MIPS + #define XASH_ARCHITECTURE ARCHITECTURE_MIPS +#elif XASH_JS + #define XASH_ARCHITECTURE ARCHITECTURE_JS +#elif XASH_E2K + #define XASH_ARCHITECTURE ARCHITECTURE_E2K +#elif XASH_RISCV + #define XASH_ARCHITECTURE ARCHITECTURE_RISCV +#elif XASH_PPC + #define XASH_ARCHITECTURE ARCHITECTURE_PPC +#elif XASH_WASM + #define XASH_ARCHITECTURE ARCHITECTURE_WASM +#else + #error +#endif + +//================================================================ +// +// ENDIANNESS DEFINES +// +//================================================================ +#define ENDIANNESS_LITTLE 1 +#define ENDIANNESS_BIG 2 + +#if XASH_LITTLE_ENDIAN + #define XASH_ENDIANNESS ENDIANNESS_LITTLE +#elif XASH_BIG_ENDIAN + #define XASH_ENDIANNESS ENDIANNESS_BIG +#else + #error +#endif + +//================================================================ +// +// APPLICATION BINARY INTERFACE +// +//================================================================ +#define BIT( n ) ( 1U << ( n )) + +#define ARCH_ARM_VER_MASK ( BIT( 5 ) - 1 ) +#define ARCH_ARM_VER_SHIFT 0 +#define ARCH_ARM_HARDFP BIT( 5 ) + +#define ARCH_RISCV_FP_SOFT 0 +#define ARCH_RISCV_FP_SINGLE 1 +#define ARCH_RISCV_FP_DOUBLE 2 + +#if XASH_ARCHITECTURE == ARCHITECTURE_ARM + #if XASH_ARM_HARDFP + #define XASH_ARCHITECTURE_ABI ( ARCH_ARM_HARDFP | XASH_ARM ) + #else + #define XASH_ARCHITECTURE_ABI ( XASH_ARM ) + #endif +#elif XASH_ARCHITECTURE == ARCHITECTURE_RISCV + #if XASH_RISCV_SOFTFP + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SOFT + #elif XASH_RISCV_SINGLEFP + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SINGLE + #elif XASH_RISCV_DOUBLEFP + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_DOUBLE + #else + #error + #endif +#else + #define XASH_ARCHITECTURE_ABI 0 // unused +#endif + + +#endif // BUILDENUMS_H diff --git a/public/crclib.c b/public/crclib.c index 95013b58..8b13a752 100644 --- a/public/crclib.c +++ b/public/crclib.c @@ -19,10 +19,8 @@ GNU General Public License for more details. #include #define NUM_BYTES 256 -#define CRC32_INIT_VALUE 0xFFFFFFFFUL -#define CRC32_XOR_VALUE 0xFFFFFFFFUL -static const dword crc32table[NUM_BYTES] = +static const uint32_t crc32table[NUM_BYTES] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, @@ -90,87 +88,54 @@ static const dword crc32table[NUM_BYTES] = 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; -void GAME_EXPORT CRC32_Init( dword *pulCRC ) +void GAME_EXPORT CRC32_ProcessByte( uint32_t *pulCRC, byte ch ) { - *pulCRC = CRC32_INIT_VALUE; + uint32_t ulCrc = *pulCRC; + + *pulCRC = crc32table[((byte)ulCrc ^ ch)] ^ (ulCrc >> 8); } -dword GAME_EXPORT CRC32_Final( dword pulCRC ) +void GAME_EXPORT CRC32_ProcessBuffer( uint32_t *pulCRC, const void *pBuffer, int nBuffer ) { - return pulCRC ^ CRC32_XOR_VALUE; -} - -void GAME_EXPORT CRC32_ProcessByte( dword *pulCRC, byte ch ) -{ - dword ulCrc = *pulCRC; - - ulCrc ^= ch; - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - *pulCRC = ulCrc; -} - -void GAME_EXPORT CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ) -{ - dword ulCrc = *pulCRC, tmp; + uint32_t ulCrc = *pulCRC, tmp; byte *pb = (byte *)pBuffer; - uint nFront; - int nMain; -JustAfew: - switch( nBuffer ) + + while( nBuffer >= sizeof( uint64_t )) { - case 7: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 6: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 5: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 4: - memcpy( &tmp, pb, sizeof(dword)); - ulCrc ^= tmp; // warning, this only works on little-endian. + memcpy( &tmp, pb, sizeof( tmp )); + ulCrc ^= LittleLong( tmp ); ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - *pulCRC = ulCrc; - return; - case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 0: *pulCRC = ulCrc; - return; + memcpy( &tmp, pb + sizeof( tmp ), sizeof( tmp )); + ulCrc ^= LittleLong( tmp ); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + nBuffer -= sizeof( uint64_t ); + pb += sizeof( uint64_t ); } - // We may need to do some alignment work up front, and at the end, so that - // the main loop is aligned and only has to worry about 8 byte at a time. - // The low-order two bits of pb and nBuffer in total control the - // upfront work. - nFront = ((uint)pb) & 3; - nBuffer -= nFront; - - switch( nFront ) + if( nBuffer & sizeof( uint32_t )) { - case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough - case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); // fallthrough + memcpy( &tmp, pb, sizeof( tmp )); + ulCrc ^= LittleLong( tmp ); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + nBuffer -= sizeof( uint32_t ); + pb += sizeof( uint32_t ); } - nMain = nBuffer >> 3; - while( nMain-- ) + while( nBuffer-- ) { - memcpy( &tmp, pb, sizeof(dword)); - ulCrc ^= tmp; // warning, this only works on little-endian. - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - memcpy( &tmp, pb + 4, sizeof(dword)); - ulCrc ^= tmp; // warning, this only works on little-endian. - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); - pb += 8; + ulCrc = crc32table[((byte)ulCrc ^ *pb++)] ^ (ulCrc >> 8); } - nBuffer &= 7; - goto JustAfew; + *pulCRC = ulCrc; } /* @@ -182,7 +147,7 @@ For proxy protecting */ byte CRC32_BlockSequence( byte *base, int length, int sequence ) { - dword CRC; + uint32_t CRC; char *ptr; char buffer[64]; @@ -208,24 +173,6 @@ byte CRC32_BlockSequence( byte *base, int length, int sequence ) void MD5Transform( uint buf[4], const uint in[16] ); -/* -================== -MD5Init - -Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. -================== -*/ -void MD5Init( MD5Context_t *ctx ) -{ - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - /* =================== MD5Update @@ -428,6 +375,33 @@ void MD5Transform( uint buf[4], const uint in[16] ) buf[3] += d; } +/* +============ +COM_Hex2Char +============ +*/ +static char COM_Hex2Char( uint8_t hex ) +{ + if( hex >= 0x0 && hex <= 0x9 ) + hex += '0'; + else if( hex >= 0xA && hex <= 0xF ) + hex += '7'; + + return (char)hex; +} + +/* +============ +COM_Hex2String +============ +*/ +static void COM_Hex2String( uint8_t hex, char *str ) +{ + *str++ = COM_Hex2Char( hex >> 4 ); + *str++ = COM_Hex2Char( hex & 0x0F ); + *str = '\0'; +} + /* ================= MD5_Print @@ -457,10 +431,14 @@ returns hash key for string */ uint COM_HashKey( const char *string, uint hashSize ) { - uint i, hashKey = 0; + uint hashKey = 5381; + unsigned char i; - for( i = 0; string[i]; i++ ) - hashKey = (hashKey + i) * 37 + Q_tolower( string[i] ); + while(( i = *string++ )) + { + i = Q_tolower( i ); + hashKey = ( hashKey << 5 ) + hashKey + ( i & 0xDF ); + } - return (hashKey % hashSize); + return hashKey & ( hashSize - 1 ); } diff --git a/public/crclib.h b/public/crclib.h index a32cc3e8..69233729 100644 --- a/public/crclib.h +++ b/public/crclib.h @@ -26,13 +26,41 @@ typedef struct uint in[16]; } MD5Context_t; +#define CRC32_INIT_VALUE 0xFFFFFFFFUL +#define CRC32_XOR_VALUE 0xFFFFFFFFUL + +static inline void CRC32_Init( uint32_t *pulCRC ) +{ + *pulCRC = CRC32_INIT_VALUE; +} + +static inline uint32_t CRC32_Final( uint32_t pulCRC ) +{ + return pulCRC ^ CRC32_XOR_VALUE; +} -void CRC32_Init( dword *pulCRC ); byte CRC32_BlockSequence( byte *base, int length, int sequence ); -void CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ); -void CRC32_ProcessByte( dword *pulCRC, byte ch ); -dword CRC32_Final( dword pulCRC ); -void MD5Init( MD5Context_t *ctx ); +void CRC32_ProcessBuffer( uint32_t *pulCRC, const void *pBuffer, int nBuffer ); +void CRC32_ProcessByte( uint32_t *pulCRC, byte ch ); + +/* +================== +MD5Init + +Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. +================== +*/ +static inline void MD5Init( MD5Context_t *ctx ) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + void MD5Update( MD5Context_t *ctx, const byte *buf, uint len ); void MD5Final( byte digest[16], MD5Context_t *ctx ); uint COM_HashKey( const char *string, uint hashSize ); diff --git a/public/crtlib.c b/public/crtlib.c index 6dba1ec4..6657128c 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -1,6 +1,7 @@ /* crtlib.c - internal stdlib Copyright (C) 2011 Uncle Mike +Copyright (c) QuakeSpasm contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,163 +18,49 @@ GNU General Public License for more details. #include "const.h" #include #include -#include #include #include "stdio.h" #include "crtlib.h" #include "xash3d_mathlib.h" -void Q_strnupr( const char *in, char *out, size_t size_out ) -{ - if( size_out == 0 ) return; - - while( *in && size_out > 1 ) - { - if( *in >= 'a' && *in <= 'z' ) - *out++ = *in++ + 'A' - 'a'; - else *out++ = *in++; - size_out--; - } - *out = '\0'; -} - void Q_strnlwr( const char *in, char *out, size_t size_out ) { - if( size_out == 0 ) return; + size_t len, i; - while( *in && size_out > 1 ) - { - if( *in >= 'A' && *in <= 'Z' ) - *out++ = *in++ + 'a' - 'A'; - else *out++ = *in++; - size_out--; - } - *out = '\0'; + len = Q_strncpy( out, in, size_out ); + + for( i = 0; i < len; i++ ) + out[i] = Q_tolower( out[i] ); } -qboolean Q_isdigit( const char *str ) +int Q_atoi_hex( int sign, const char *str ) { - if( str && *str ) + int c, val = 0; + + if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) + str += 2; + + while( 1 ) { - while( isdigit( *str )) str++; - if( !*str ) return true; + c = *str++; + if( c >= '0' && c <= '9' ) val = (val<<4) + c - '0'; + else if( c >= 'a' && c <= 'f' ) val = (val<<4) + c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) val = (val<<4) + c - 'A' + 10; + else return val * sign; } - return false; } -qboolean Q_isspace( const char *str ) +static int Q_atoi_character( int sign, const char *str ) { - if( str && *str ) - { - while( isspace( *str ) ) str++; - if( !*str ) return true; - } - return false; + return sign * str[1]; } -size_t Q_colorstr( const char *string ) +static const char *Q_atoi_strip_whitespace( const char *str ) { - size_t len; - const char *p; + while( str && *str == ' ' ) + str++; - if( !string ) return 0; - - len = 0; - p = string; - while( *p ) - { - if( IsColorString( p )) - { - len += 2; - p += 2; - continue; - } - p++; - } - - return len; -} - -char Q_toupper( const char in ) -{ - char out; - - if( in >= 'a' && in <= 'z' ) - out = in + 'A' - 'a'; - else out = in; - - return out; -} - -char Q_tolower( const char in ) -{ - char out; - - if( in >= 'A' && in <= 'Z' ) - out = in + 'a' - 'A'; - else out = in; - - return out; -} - -size_t Q_strncat( char *dst, const char *src, size_t size ) -{ - register char *d = dst; - register const char *s = src; - register size_t n = size; - size_t dlen; - - if( !dst || !src || !size ) - return 0; - - // find the end of dst and adjust bytes left but don't go past end - while( n-- != 0 && *d != '\0' ) d++; - dlen = d - dst; - n = size - dlen; - - if( n == 0 ) return( dlen + Q_strlen( s )); - - while( *s != '\0' ) - { - if( n != 1 ) - { - *d++ = *s; - n--; - } - s++; - } - - *d = '\0'; - return( dlen + ( s - src )); // count does not include NULL -} - -size_t Q_strncpy( char *dst, const char *src, size_t size ) -{ - register char *d = dst; - register const char *s = src; - register size_t n = size; - - if( !dst || !src || !size ) - return 0; - - // copy as many bytes as will fit - if( n != 0 && --n != 0 ) - { - do - { - if(( *d++ = *s++ ) == 0 ) - break; - } while( --n != 0 ); - } - - // not enough room in dst, add NULL and traverse rest of src - if( n == 0 ) - { - if( size != 0 ) - *d = '\0'; // NULL-terminate dst - while( *s++ ); - } - return ( s - src - 1 ); // count does not include NULL + return str; } int Q_atoi( const char *str ) @@ -181,13 +68,13 @@ int Q_atoi( const char *str ) int val = 0; int c, sign; - if( !str ) return 0; + if( !COM_CheckString( str )) + return 0; - // check for empty charachters in string - while( str && *str == ' ' ) - str++; + str = Q_atoi_strip_whitespace( str ); - if( !str ) return 0; + if( !COM_CheckString( str )) + return 0; if( *str == '-' ) { @@ -198,21 +85,11 @@ int Q_atoi( const char *str ) // check for hex if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) - { - str += 2; - while( 1 ) - { - c = *str++; - if( c >= '0' && c <= '9' ) val = (val<<4) + c - '0'; - else if( c >= 'a' && c <= 'f' ) val = (val<<4) + c - 'a' + 10; - else if( c >= 'A' && c <= 'F' ) val = (val<<4) + c - 'A' + 10; - else return val * sign; - } - } + return Q_atoi_hex( sign, str ); // check for character if( str[0] == '\'' ) - return sign * str[1]; + return Q_atoi_character( sign, str ); // assume decimal while( 1 ) @@ -230,13 +107,13 @@ float Q_atof( const char *str ) double val = 0; int c, sign, decimal, total; - if( !str ) return 0.0f; + if( !COM_CheckString( str )) + return 0; - // check for empty charachters in string - while( str && *str == ' ' ) - str++; + str = Q_atoi_strip_whitespace( str ); - if( !str ) return 0.0f; + if( !COM_CheckString( str )) + return 0; if( *str == '-' ) { @@ -247,20 +124,11 @@ float Q_atof( const char *str ) // check for hex if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) - { - str += 2; - while( 1 ) - { - c = *str++; - if( c >= '0' && c <= '9' ) val = (val * 16) + c - '0'; - else if( c >= 'a' && c <= 'f' ) val = (val * 16) + c - 'a' + 10; - else if( c >= 'A' && c <= 'F' ) val = (val * 16) + c - 'A' + 10; - else return val * sign; - } - } + return Q_atoi_hex( sign, str ); // check for character - if( str[0] == '\'' ) return sign * str[1]; + if( str[0] == '\'' ) + return Q_atoi_character( sign, str ); // assume decimal decimal = -1; @@ -295,13 +163,11 @@ float Q_atof( const char *str ) void Q_atov( float *vec, const char *str, size_t siz ) { - string buffer; - char *pstr, *pfront; + const char *pstr, *pfront; int j; - Q_strncpy( buffer, str, sizeof( buffer )); - memset( vec, 0, sizeof( vec_t ) * siz ); - pstr = pfront = buffer; + memset( vec, 0, sizeof( *vec ) * siz ); + pstr = pfront = str; for( j = 0; j < siz; j++ ) { @@ -372,12 +238,38 @@ qboolean Q_stricmpext( const char *pattern, const char *text ) return Q_strnicmpext( pattern, text, ~((size_t)0) ); } +const byte *Q_memmem( const byte *haystack, size_t haystacklen, const byte *needle, size_t needlelen ) +{ + const byte *i; + + // quickly find first matching symbol + while( haystacklen && ( i = memchr( haystack, needle[0], haystacklen ))) + { + if( !memcmp( i, needle, needlelen )) + return i; + + // skip one byte + i++; + + haystacklen -= i - haystack; + haystack = i; + } + + return NULL; +} + +void Q_memor( byte *XASH_RESTRICT dst, const byte *XASH_RESTRICT src, size_t len ) +{ + size_t i; + for( i = 0; i < len; i++ ) // msvc likes to optimize this loop form + dst[i] |= src[i]; +} + const char* Q_timestamp( int format ) { static string timestamp; time_t crt_time; const struct tm *crt_tm; - string timestring; time( &crt_time ); crt_tm = localtime( &crt_time ); @@ -386,37 +278,37 @@ const char* Q_timestamp( int format ) { case TIME_FULL: // Build the full timestamp (ex: "Apr03 2007 [23:31.55]"); - strftime( timestring, sizeof( timestring ), "%b%d %Y [%H:%M.%S]", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%b%d %Y [%H:%M.%S]", crt_tm ); break; case TIME_DATE_ONLY: // Build the date stamp only (ex: "Apr03 2007"); - strftime( timestring, sizeof( timestring ), "%b%d %Y", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%b%d %Y", crt_tm ); break; case TIME_TIME_ONLY: // Build the time stamp only (ex: "23:31.55"); - strftime( timestring, sizeof( timestring ), "%H:%M.%S", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%H:%M.%S", crt_tm ); break; case TIME_NO_SECONDS: // Build the time stamp exclude seconds (ex: "13:46"); - strftime( timestring, sizeof( timestring ), "%H:%M", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%H:%M", crt_tm ); break; case TIME_YEAR_ONLY: // Build the date stamp year only (ex: "2006"); - strftime( timestring, sizeof( timestring ), "%Y", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%Y", crt_tm ); break; case TIME_FILENAME: // Build a timestamp that can use for filename (ex: "Nov2006-26 (19.14.28)"); - strftime( timestring, sizeof( timestring ), "%b%Y-%d_%H.%M.%S", crt_tm ); + strftime( timestamp, sizeof( timestamp ), "%b%Y-%d_%H.%M.%S", crt_tm ); + break; + default: + Q_snprintf( timestamp, sizeof( timestamp ), "%s: unknown format %d", __func__, format ); break; - default: return NULL; } - Q_strncpy( timestamp, timestring, sizeof( timestamp )); - return timestamp; } -#if !defined( HAVE_STRCASESTR ) +#if !HAVE_STRCASESTR char *Q_stristr( const char *string, const char *string2 ) { int c; @@ -447,6 +339,9 @@ int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list ar { int result; + if( unlikely( buffersize == 0 )) + return -1; // report as overflow + #ifndef _MSC_VER result = vsnprintf( buffer, buffersize, format, args ); #else @@ -484,72 +379,24 @@ int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) return result; } -int Q_sprintf( char *buffer, const char *format, ... ) -{ - va_list args; - int result; - - va_start( args, format ); - result = Q_vsnprintf( buffer, 99999, format, args ); - va_end( args ); - - return result; -} - -char *Q_strpbrk(const char *s, const char *accept) -{ - for( ; *s; s++ ) - { - const char *k; - - for( k = accept; *k; k++ ) - { - if( *s == *k ) - return (char*)s; - } - } - - return NULL; -} - void COM_StripColors( const char *in, char *out ) { - while ( *in ) + while( *in ) { - if ( IsColorString( in ) ) + if( IsColorString( in )) in += 2; else *out++ = *in++; } *out = '\0'; } -uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) -{ - uint i, hashKey = 0; - - if( caseinsensitive ) - { - for( i = 0; string[i]; i++) - hashKey += (i * 119) * Q_tolower( string[i] ); - } - else - { - for( i = 0; string[i]; i++ ) - hashKey += (i + 119) * (int)string[i]; - } - - hashKey = ((hashKey ^ (hashKey >> 10)) ^ (hashKey >> 20)) & (hashSize - 1); - - return hashKey; -} - char *Q_pretifymem( float value, int digitsafterdecimal ) { static char output[8][32]; static int current; - float onekb = 1024.0f; - float onemb = onekb * onekb; - char suffix[8]; + const float onekb = 1024.0f; + const float onemb = onekb * onekb; + const char *suffix; char *out = output[current]; char val[32], *i, *o, *dot; int pos; @@ -560,39 +407,31 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) if( value > onemb ) { value /= onemb; - Q_sprintf( suffix, " Mb" ); + suffix = "Mb"; } else if( value > onekb ) { value /= onekb; - Q_sprintf( suffix, " Kb" ); - } - else Q_sprintf( suffix, " bytes" ); - - // clamp to >= 0 - digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); - - // if it's basically integral, don't do any decimals - if( fabs( value - (int)value ) < 0.00001f ) - { - Q_sprintf( val, "%i%s", (int)value, suffix ); + suffix = "Kb"; } else { - char fmt[32]; - - // otherwise, create a format string for the decimals - Q_sprintf( fmt, "%%.%if%s", digitsafterdecimal, suffix ); - Q_sprintf( val, fmt, (double)value ); + suffix = "bytes"; } + // if it's basically integral, don't do any decimals + if( fabs( value - (int)value ) < 0.00001f || digitsafterdecimal <= 0 ) + Q_snprintf( val, sizeof( val ), "%i %s", (int)Q_rint( value ), suffix ); + else if( digitsafterdecimal >= 1 ) + Q_snprintf( val, sizeof( val ), "%.*f %s", digitsafterdecimal, (double)value, suffix ); + // copy from in to out i = val; o = out; // search for decimal or if it was integral, find the space after the raw number - dot = Q_strstr( i, "." ); - if( !dot ) dot = Q_strstr( i, " " ); + dot = Q_strchr( i, '.' ); + if( !dot ) dot = Q_strchr( i, ' ' ); pos = dot - i; // compute position of dot pos -= 3; // don't put a comma if it's <= 3 long @@ -615,69 +454,42 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) return out; } -/* -============ -va - -does a varargs printf into a temp buffer, -so I don't need to have varargs versions -of all text functions. -============ -*/ -char *va( const char *format, ... ) -{ - va_list argptr; - static char string[16][1024], *s; - static int stringindex = 0; - - s = string[stringindex]; - stringindex = (stringindex + 1) & 15; - va_start( argptr, format ); - Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); - va_end( argptr ); - - return s; -} - /* ============ COM_FileBase Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +a1ba: adapted and simplified version from QuakeSpasm ============ */ -void COM_FileBase( const char *in, char *out ) +void COM_FileBase( const char *in, char *out, size_t size ) { - int len, start, end; + const char *dot, *slash, *s; + size_t len; - len = Q_strlen( in ); - if( !len ) return; + if( unlikely( !COM_CheckString( in ) || size <= 1 )) + { + out[0] = 0; + return; + } - // scan backward for '.' - end = len - 1; + slash = in; + dot = NULL; + for( s = in; *s; s++ ) + { + if( *s == '/' || *s == '\\' ) + slash = s + 1; - while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) - end--; + if( *s == '.' ) + dot = s; + } - if( in[end] != '.' ) - end = len-1; // no '.', copy to end - else end--; // found ',', copy to left of '.' + if( dot == NULL || dot < slash ) + dot = s; - // scan backward for '/' - start = len - 1; + len = Q_min( size - 1, dot - slash ); - while( start >= 0 && in[start] != '/' && in[start] != '\\' ) - start--; - - if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) - start = 0; - else start++; - - // length of new sting - len = end - start + 1; - - // Copy partial string - Q_strncpy( out, &in[start], len + 1 ); + memcpy( out, slash, len ); out[len] = 0; } @@ -688,22 +500,16 @@ COM_FileExtension */ const char *COM_FileExtension( const char *in ) { - const char *separator, *backslash, *colon, *dot; - - separator = Q_strrchr( in, '/' ); - backslash = Q_strrchr( in, '\\' ); - - if( !separator || separator < backslash ) - separator = backslash; - - colon = Q_strrchr( in, ':' ); - - if( !separator || separator < colon ) - separator = colon; + const char *dot; dot = Q_strrchr( in, '.' ); - if( dot == NULL || ( separator && ( dot < separator ))) + // quickly exit if there is no dot at all + if( dot == NULL ) + return ""; + + // if there are any of these special symbols after the dot, the file has no extension + if( Q_strpbrk( dot + 1, "\\/:" )) return ""; return dot + 1; @@ -742,15 +548,15 @@ void COM_ExtractFilePath( const char *path, char *dest ) const char *src = path + Q_strlen( path ) - 1; // back up until a \ or the start - while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) + while( src > path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) src--; - if( src != path ) + if( src > path ) { memcpy( dest, path, src - path ); dest[src - path - 1] = 0; // cutoff backslash } - else Q_strcpy( dest, "" ); // file without path + else dest[0] = 0; // file without path } /* @@ -782,13 +588,15 @@ void COM_StripExtension( char *path ) COM_DefaultExtension ================== */ -void COM_DefaultExtension( char *path, const char *extension ) +void COM_DefaultExtension( char *path, const char *extension, size_t size ) { const char *src; + size_t len; // if path doesn't have a .EXT, append extension // (extension should include the .) - src = path + Q_strlen( path ) - 1; + len = Q_strlen( path ); + src = path + len - 1; while( *src != '/' && src != path ) { @@ -797,7 +605,7 @@ void COM_DefaultExtension( char *path, const char *extension ) src--; } - Q_strcat( path, extension ); + Q_strncpy( &path[len], extension, size - len ); } /* @@ -805,10 +613,10 @@ void COM_DefaultExtension( char *path, const char *extension ) COM_ReplaceExtension ================== */ -void COM_ReplaceExtension( char *path, const char *extension ) +void COM_ReplaceExtension( char *path, const char *extension, size_t size ) { COM_StripExtension( path ); - COM_DefaultExtension( path, extension ); + COM_DefaultExtension( path, extension, size ); } /* @@ -816,74 +624,37 @@ void COM_ReplaceExtension( char *path, const char *extension ) COM_RemoveLineFeed ============ */ -void COM_RemoveLineFeed( char *str ) +void COM_RemoveLineFeed( char *str, size_t bufsize ) { - while( *str != '\0' ) + size_t i; + + for( i = 0; i < bufsize && *str != '\0'; i++, str++ ) { if( *str == '\r' || *str == '\n' ) *str = '\0'; - - ++str; - } -} - -/* -============ -COM_FixSlashes - -Changes all '/' characters into '\' characters, in place. -============ -*/ -void COM_FixSlashes( char *pname ) -{ - while( *pname ) - { - if( *pname == '\\' ) - *pname = '/'; - pname++; } } /* ============ COM_PathSlashFix + +ensure directory path always ends on forward slash ============ */ void COM_PathSlashFix( char *path ) { - size_t len; + size_t len = Q_strlen( path ); - len = Q_strlen( path ); - - if( path[len - 1] != '\\' && path[len - 1] != '/' ) - Q_strcpy( &path[len], "/" ); -} - -/* -============ -COM_Hex2Char -============ -*/ -char COM_Hex2Char( uint8_t hex ) -{ - if( hex >= 0x0 && hex <= 0x9 ) - hex += '0'; - else if( hex >= 0xA && hex <= 0xF ) - hex += '7'; - - return (char)hex; -} - -/* -============ -COM_Hex2String -============ -*/ -void COM_Hex2String( uint8_t hex, char *str ) -{ - *str++ = COM_Hex2Char( hex >> 4 ); - *str++ = COM_Hex2Char( hex & 0x0F ); - *str = '\0'; + if( path[len - 1] == '\\' ) + { + path[len - 1] = '/'; + } + else if( path[len - 1] != '/' ) + { + path[len] = '/'; + path[len + 1] = '\0'; + } } /* @@ -944,8 +715,8 @@ skipwhite: data++; } - // skip // comments - if( c == '/' && data[1] == '/' ) + // skip // or #, if requested, comments + if(( c == '/' && data[1] == '/' ) || ( c == '#' && FBitSet( flags, PFILE_IGNOREHASHCMT ))) { while( *data && *data != '\n' ) data++; @@ -1047,7 +818,12 @@ skipwhite: int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ) { - return matchpattern_with_separator( in, pattern, caseinsensitive, "/\\:", false ); + const char *separators = "/\\:"; + + if( !Q_strcmp( pattern, "*" )) + separators = ""; + + return matchpattern_with_separator( in, pattern, caseinsensitive, separators, false ); } // wildcard_least_one: if true * matches 1 or more characters diff --git a/public/crtlib.h b/public/crtlib.h index f3013a8f..18633662 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -1,6 +1,7 @@ /* crtlib.h - internal stdlib Copyright (C) 2011 Uncle Mike +Copyright (C) Free Software Foundation, Inc This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +19,7 @@ GNU General Public License for more details. #include #include +#include #include "build.h" #include "xash3d_types.h" @@ -38,39 +40,39 @@ enum }; // a1ba: not using BIT macro, so flags can be copypasted into -// exported APIs headers and will get nice warning in case of changing values +// exported APIs headers and will not get warning in case of changing values #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) +#define PFILE_IGNOREHASHCMT (1<<2) + #define PFILE_TOKEN_MAX_LENGTH 1024 #define PFILE_FS_TOKEN_MAX_LENGTH 512 +#ifdef __cplusplus +#define restrict +#endif // __cplusplus + // // build.c // int Q_buildnum( void ); +int Q_buildnum_iso( const char *date ); int Q_buildnum_compat( void ); +const char *Q_PlatformStringByID( const int platform ); const char *Q_buildos( void ); +const char *Q_ArchitectureStringByID( const int arch, const uint abi, const int endianness, const qboolean is64 ); const char *Q_buildarch( void ); -const char *Q_buildcommit( void ); +extern const char *g_buildcommit; +extern const char *g_buildbranch; +extern const char *g_build_date; +extern const char *g_buildcommit_date; // // crtlib.c // -#define Q_strupr( in, out ) Q_strnupr( in, out, 99999 ) -void Q_strnupr( const char *in, char *out, size_t size_out ); -#define Q_strlwr( in, out ) Q_strnlwr( in, out, 99999 ) void Q_strnlwr( const char *in, char *out, size_t size_out ); #define Q_strlen( str ) (( str ) ? strlen(( str )) : 0 ) -size_t Q_colorstr( const char *string ); -char Q_toupper( const char in ); -char Q_tolower( const char in ); -#define Q_strcat( dst, src ) Q_strncat( dst, src, 99999 ) -size_t Q_strncat( char *dst, const char *src, size_t siz ); -#define Q_strcpy( dst, src ) Q_strncpy( dst, src, 99999 ) -size_t Q_strncpy( char *dst, const char *src, size_t siz ); -uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ); -qboolean Q_isdigit( const char *str ); -qboolean Q_isspace( const char *str ); +int Q_atoi_hex( int sign, const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); @@ -78,28 +80,24 @@ void Q_atov( float *vec, const char *str, size_t siz ); #define Q_strrchr strrchr qboolean Q_stricmpext( const char *pattern, const char *text ); qboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlen ); -const char *Q_timestamp( int format ); -#define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) +const byte *Q_memmem( const byte *haystack, size_t haystacklen, const byte *needle, size_t needlelen ); +void Q_memor( byte *XASH_RESTRICT dst, const byte *XASH_RESTRICT src, size_t len ); +const char *Q_timestamp( int format ) RETURNS_NONNULL; int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); -int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); -int Q_sprintf( char *buffer, const char *format, ... ) _format( 2 ); -char *Q_strpbrk(const char *s, const char *accept); +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) FORMAT_CHECK( 3 ); +#define Q_strpbrk strpbrk void COM_StripColors( const char *in, char *out ); #define Q_memprint( val ) Q_pretifymem( val, 2 ) char *Q_pretifymem( float value, int digitsafterdecimal ); -char *va( const char *format, ... ) _format( 1 ); -void COM_FileBase( const char *in, char *out ); -const char *COM_FileExtension( const char *in ); -void COM_DefaultExtension( char *path, const char *extension ); -void COM_ReplaceExtension( char *path, const char *extension ); +void COM_FileBase( const char *in, char *out, size_t size ); +const char *COM_FileExtension( const char *in ) RETURNS_NONNULL; +void COM_DefaultExtension( char *path, const char *extension, size_t size ); +void COM_ReplaceExtension( char *path, const char *extension, size_t size ); void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); -void COM_RemoveLineFeed( char *str ); -void COM_FixSlashes( char *pname ); +void COM_RemoveLineFeed( char *str, size_t bufsize ); void COM_PathSlashFix( char *path ); -char COM_Hex2Char( uint8_t hex ); -void COM_Hex2String( uint8_t hex, char *str ); // return 0 on empty or null string, 1 otherwise #define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 ) #define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 ) @@ -108,58 +106,243 @@ char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int f int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); -// libc implementations +// +// dllhelpers.c +// +typedef struct dllfunc_s +{ + const char *name; + void **func; +} dllfunc_t; + +void ClearExports( const dllfunc_t *funcs, size_t num_funcs ); +qboolean ValidateExports( const dllfunc_t *funcs, size_t num_funcs ); + +static inline char Q_toupper( const char in ) +{ + char out; + + if( in >= 'a' && in <= 'z' ) + out = in + 'A' - 'a'; + else out = in; + + return out; +} + +static inline char Q_tolower( const char in ) +{ + char out; + + if( in >= 'A' && in <= 'Z' ) + out = in + 'a' - 'A'; + else out = in; + + return out; +} + +static inline qboolean Q_istype( const char *str, int (*istype)( int c )) +{ + if( likely( str && *str )) + { + while( istype( *str )) str++; + if( !*str ) return true; + } + return false; +} + +static inline qboolean Q_isdigit( const char *str ) +{ + return Q_istype( str, isdigit ); +} + +static inline qboolean Q_isalpha( const char *str ) +{ + return Q_istype( str, isalpha ); +} + +static inline qboolean Q_isspace( const char *str ) +{ + return Q_istype( str, isspace ); +} + static inline int Q_strcmp( const char *s1, const char *s2 ) { - return unlikely(!s1) ? - ( !s2 ? 0 : -1 ) : - ( unlikely(!s2) ? 1 : strcmp( s1, s2 )); + if( likely( s1 && s2 )) + return strcmp( s1, s2 ); + return ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 ); } static inline int Q_strncmp( const char *s1, const char *s2, size_t n ) { - return unlikely(!s1) ? - ( !s2 ? 0 : -1 ) : - ( unlikely(!s2) ? 1 : strncmp( s1, s2, n )); + if( likely( s1 && s2 )) + return strncmp( s1, s2, n ); + return ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 ); } static inline char *Q_strstr( const char *s1, const char *s2 ) { - return unlikely( !s1 || !s2 ) ? NULL : (char*)strstr( s1, s2 ); + if( likely( s1 && s2 )) + return (char *)strstr( s1, s2 ); + return NULL; } -// libc extensions, be careful +// libc extensions, be careful what to enable or what not +static inline size_t Q_strnlen( const char *str, size_t size ) +{ +#if HAVE_STRNLEN + return strnlen( str, size ); +#else + const char *p = (const char *)memchr( str, 0, size ); + return p ? p - str : size; +#endif +} -#if XASH_WIN32 -#define strcasecmp stricmp -#define strncasecmp strnicmp -#endif // XASH_WIN32 +static inline size_t Q_strncpy( char *dst, const char *src, size_t size ) +{ + if( unlikely( !dst || !src || !size )) + return 0; +#if HAVE_STRLCPY + return strlcpy( dst, src, size ); +#else + { + size_t len = strlen( src ); + if( len >= size ) // check if truncate + { + memcpy( dst, src, size ); + dst[size - 1] = 0; + } + else memcpy( dst, src, len + 1 ); + + return len; // count does not include NULL + } +#endif +} + +static inline size_t Q_strncat( char *dst, const char *src, size_t size ) +{ + if( unlikely( !dst || !src || !size )) + return 0; +#if HAVE_STRLCAT + return strlcat( dst, src, size ); +#else + { + size_t slen = strlen( src ); + size_t dlen = Q_strnlen( dst, size ); + + if( dlen != size ) + { + size_t copy = size - dlen - 1; + + if( copy > slen ) + copy = slen; + + memcpy( &dst[dlen], src, copy ); + dst[dlen + copy] = 0; + } + + return dlen + slen; + } +#endif +} + +#if HAVE_STRICMP || HAVE_STRCASECMP static inline int Q_stricmp( const char *s1, const char *s2 ) { - return unlikely(!s1) ? - ( !s2 ? 0 : -1 ) : - ( unlikely(!s2) ? 1 : strcasecmp( s1, s2 )); + if( likely( s1 && s2 )) + { +#if HAVE_STRICMP + return stricmp( s1, s2 ); +#elif HAVE_STRCASECMP + return strcasecmp( s1, s2 ); +#endif + } + return ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 ); } +#else +int Q_stricmp( const char *s1, const char *s2 ); +#endif +#if HAVE_STRICMP || HAVE_STRCASECMP static inline int Q_strnicmp( const char *s1, const char *s2, size_t n ) { - return unlikely(!s1) ? - ( !s2 ? 0 : -1 ) : - ( unlikely(!s2) ? 1 : strncasecmp( s1, s2, n )); -} - -#if defined( HAVE_STRCASESTR ) -#if XASH_WIN32 -#define strcasestr stristr + if( likely( s1 && s2 )) + { +#if HAVE_STRICMP + return strnicmp( s1, s2, n ); +#elif HAVE_STRCASECMP + return strncasecmp( s1, s2, n ); #endif + } + return ( s1 ? 1 : 0 ) - ( s2 ? 1 : 0 ); +} +#else +int Q_strnicmp( const char *s1, const char *s2, size_t n ); +#endif + + +#if HAVE_STRCASESTR static inline char *Q_stristr( const char *s1, const char *s2 ) { - return unlikely( !s1 || !s2 ) ? NULL : (char *)strcasestr( s1, s2 ); + if( likely( s1 && s2 )) + return (char *)strcasestr( s1, s2 ); + return NULL; } -#else // defined( HAVE_STRCASESTR ) +#else // !HAVE_STRCASESTR char *Q_stristr( const char *s1, const char *s2 ); -#endif // defined( HAVE_STRCASESTR ) +#endif // !HAVE_STRCASESTR + +#if HAVE_STRCHRNUL +#define Q_strchrnul strchrnul +#else // !HAVE_STRCHRNUL +static inline char *Q_strchrnul( const char *s, int c ) +{ + char *p = (char *)Q_strchr( s, c ); + if( p ) return p; + return (char *)s + Q_strlen( s ); +} +#endif // !HAVE_STRCHRNUL + +/* +=========== +Q_splitstr + +splits strings by a character +if handler returns nonzero value, exists with that value +=========== +*/ +static inline int Q_splitstr( char *str, int delim, void *userdata, + int (*handler)( char *prev, char *next, void *userdata )) +{ + char *prev = str; + char *next = Q_strchrnul( prev, delim ); + int ret = 0; + + for( ; ; prev = next + 1, next = Q_strchrnul( prev, delim )) + { + int ch = *next; // save next value if it's modified by handler + + ret = handler( prev, next, userdata ); + + if( !ch || ret != 0 ) + break; + } + + return ret; +} + +/* +============ +COM_FixSlashes + +Changes all '\' characters into '/' characters, in place. +============ +*/ +static inline void COM_FixSlashes( char *pname ) +{ + while(( pname = Q_strchr( pname, '\\' ))) + *pname = '/'; +} #ifdef __cplusplus } diff --git a/public/dllhelpers.c b/public/dllhelpers.c new file mode 100644 index 00000000..9cd63aee --- /dev/null +++ b/public/dllhelpers.c @@ -0,0 +1,36 @@ +/* +dllhelpers.c - dll exports helpers +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "crtlib.h" + +void ClearExports( const dllfunc_t *funcs, size_t num_funcs ) +{ + size_t i; + + for( i = 0; i < num_funcs; i++ ) + *(funcs[i].func) = NULL; +} + +qboolean ValidateExports( const dllfunc_t *funcs, size_t num_funcs ) +{ + size_t i; + + for( i = 0; i < num_funcs; i++ ) + { + if( *(funcs[i].func) == NULL ) + return false; + } + + return true; +} diff --git a/public/getopt.c b/public/getopt.c new file mode 100644 index 00000000..3da07d9b --- /dev/null +++ b/public/getopt.c @@ -0,0 +1,131 @@ +/* $NetBSD: getopt.c,v 1.29 2014/06/05 22:00:22 christos Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "build.h" +#if XASH_WIN32 +#include +#include +#include + + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +static char EMSG[] = ""; +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt(int nargc, char * const nargv[], const char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + + if (optreset || *place == 0) { /* update scanning pointer */ + optreset = 0; + place = nargv[optind]; + if (optind >= nargc || *place++ != '-') { + /* Argument is absent or is not an option */ + place = EMSG; + return (-1); + } + optopt = *place++; + if (optopt == '-' && *place == 0) { + /* "--" => end of options */ + ++optind; + place = EMSG; + return (-1); + } + if (optopt == 0) { + /* Solitary '-', treat as a '-' option + if the program (eg su) is looking for it. */ + place = EMSG; + if (strchr(ostr, '-') == NULL) + return (-1); + optopt = '-'; + } + } else + optopt = *place++; + + /* See if option letter is one the caller wanted... */ + if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { + if (*place == 0) + ++optind; + if (opterr && *ostr != ':') + (void)fprintf(stderr, + "%s: illegal option -- %c\n", nargv[0], + optopt); + return (BADCH); + } + + /* Does this option need an argument? */ + if (oli[1] != ':') { + /* don't need argument */ + optarg = NULL; + if (*place == 0) + ++optind; + } else { + /* Option-argument is either the rest of this argument or the + entire next argument. */ + if (*place) + optarg = place; + else if (oli[2] == ':') + /* + * GNU Extension, for optional arguments if the rest of + * the argument is empty, we return NULL + */ + optarg = NULL; + else if (nargc > ++optind) + optarg = nargv[optind]; + else { + /* option-argument absent */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (opterr) + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", + nargv[0], optopt); + return (BADCH); + } + place = EMSG; + ++optind; + } + return (optopt); /* return option letter */ +} +#endif // XASH_WIN32 diff --git a/public/getopt.h b/public/getopt.h new file mode 100644 index 00000000..b71968e3 --- /dev/null +++ b/public/getopt.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef GETOPT_H +#define GETOPT_H +#if XASH_WIN32 +extern int opterr; +extern int optind; +extern int optopt; +extern int optreset; +extern char *optarg; + +int getopt( int nargc, char * const nargv[], const char *ostr ); +#endif // XASH_WIN32 +#endif // GETOPT_H diff --git a/public/matrixlib.c b/public/matrixlib.c index 435474d5..8e09abc5 100644 --- a/public/matrixlib.c +++ b/public/matrixlib.c @@ -19,13 +19,6 @@ GNU General Public License for more details. #include "com_model.h" #include "xash3d_mathlib.h" -const matrix3x4 matrix3x4_identity = -{ -{ 1, 0, 0, 0 }, // PITCH [forward], org[0] -{ 0, 1, 0, 0 }, // YAW [right] , org[1] -{ 0, 0, 1, 0 }, // ROLL [up] , org[2] -}; - /* ======================================================================== @@ -83,20 +76,6 @@ void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matri out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; } -void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z ) -{ - out[0][3] = x; - out[1][3] = y; - out[2][3] = z; -} - -void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out ) -{ - out[0] = in[0][3]; - out[1] = in[1][3]; - out[2] = in[2][3]; -} - void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ) { float xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] ); @@ -217,62 +196,6 @@ void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_ } } -void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) -{ - float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); - float iscale = 1.0f / scale; - - out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; - out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; - out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; - *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); -} - -void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ) -{ - // we only support uniform scaling, so assume the first row is enough - // (note the lack of sqrt here, because we're trying to undo the scaling, - // this means multiplying by the inverse scale twice - squaring it, which - // makes the sqrt a waste of time) - float scale = 1.0f / (in1[0][0] * in1[0][0] + in1[0][1] * in1[0][1] + in1[0][2] * in1[0][2]); - - // invert the rotation by transposing and multiplying by the squared - // recipricol of the input matrix scale as described above - out[0][0] = in1[0][0] * scale; - out[0][1] = in1[1][0] * scale; - out[0][2] = in1[2][0] * scale; - out[1][0] = in1[0][1] * scale; - out[1][1] = in1[1][1] * scale; - out[1][2] = in1[2][1] * scale; - out[2][0] = in1[0][2] * scale; - out[2][1] = in1[1][2] * scale; - out[2][2] = in1[2][2] * scale; - - // invert the translate - out[0][3] = -(in1[0][3] * out[0][0] + in1[1][3] * out[0][1] + in1[2][3] * out[0][2]); - out[1][3] = -(in1[0][3] * out[1][0] + in1[1][3] * out[1][1] + in1[2][3] * out[1][2]); - out[2][3] = -(in1[0][3] * out[2][0] + in1[1][3] * out[2][1] + in1[2][3] * out[2][2]); -} - -void Matrix3x4_Transpose( matrix3x4 out, const matrix3x4 in1 ) -{ - // transpose only rotational component - out[0][0] = in1[0][0]; - out[0][1] = in1[1][0]; - out[0][2] = in1[2][0]; - out[1][0] = in1[0][1]; - out[1][1] = in1[1][1]; - out[1][2] = in1[2][1]; - out[2][0] = in1[0][2]; - out[2][1] = in1[1][2]; - out[2][2] = in1[2][2]; - - // copy origin - out[0][3] = in1[0][3]; - out[1][3] = in1[1][3]; - out[2][3] = in1[2][3]; -} - /* ================== Matrix3x4_TransformAABB @@ -287,22 +210,14 @@ void Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const ve VectorSubtract( maxs, localCenter, localExtents ); Matrix3x4_VectorTransform( world, localCenter, worldCenter ); - worldExtents[0] = DotProductAbs( localExtents, world[0] ); // auto-transposed! - worldExtents[1] = DotProductAbs( localExtents, world[1] ); - worldExtents[2] = DotProductAbs( localExtents, world[2] ); + worldExtents[0] = DotProductFabs( localExtents, world[0] ); // auto-transposed! + worldExtents[1] = DotProductFabs( localExtents, world[1] ); + worldExtents[2] = DotProductFabs( localExtents, world[2] ); VectorSubtract( worldCenter, worldExtents, absmin ); VectorAdd( worldCenter, worldExtents, absmax ); } -const matrix4x4 matrix4x4_identity = -{ -{ 1, 0, 0, 0 }, // PITCH -{ 0, 1, 0, 0 }, // YAW -{ 0, 0, 1, 0 }, // ROLL -{ 0, 0, 0, 1 }, // ORIGIN -}; - /* ======================================================================== @@ -360,40 +275,6 @@ void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matri out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; } -void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ) -{ - out[0][3] = x; - out[1][3] = y; - out[2][3] = z; -} - -void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ) -{ - out[0] = in[0][3]; - out[1] = in[1][3]; - out[2] = in[2][3]; -} - -void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ) -{ - out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; - out[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; - out[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; - out[0][3] = origin[0]; - out[0][1] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2]; - out[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2]; - out[2][1] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0]; - out[1][3] = origin[1]; - out[0][2] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1]; - out[1][2] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0]; - out[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1]; - out[2][3] = origin[2]; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ) { float angle, sr, sp, sy, cr, cp, cy; @@ -525,17 +406,6 @@ void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); } -void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) -{ - float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); - float iscale = 1.0f / scale; - - out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; - out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; - out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; - *dist = d * scale - ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); -} - void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) { // we only support uniform scaling, so assume the first row is enough @@ -568,26 +438,6 @@ void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) out[3][3] = 1.0f; } -void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ) -{ - out[0][0] = in1[0][0]; - out[0][1] = in1[1][0]; - out[0][2] = in1[2][0]; - out[0][3] = in1[3][0]; - out[1][0] = in1[0][1]; - out[1][1] = in1[1][1]; - out[1][2] = in1[2][1]; - out[1][3] = in1[3][1]; - out[2][0] = in1[0][2]; - out[2][1] = in1[1][2]; - out[2][2] = in1[2][2]; - out[2][3] = in1[3][2]; - out[3][0] = in1[0][3]; - out[3][1] = in1[1][3]; - out[3][2] = in1[2][3]; - out[3][3] = in1[3][3]; -} - qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ) { float *temp; diff --git a/public/miniz.c b/public/miniz.c new file mode 100644 index 00000000..8d0032f9 --- /dev/null +++ b/public/miniz.c @@ -0,0 +1,7833 @@ +#include "miniz.h" +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* 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/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +#ifndef MINIZ_NO_DEFLATE_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* 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.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((mz_uint64)(source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflateReset(mz_streamp pStream) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + + pDecomp = (inflate_state *)pStream->state; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + /* pDecomp->m_window_bits = window_bits */; + + return MZ_OK; +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + 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); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* 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. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} +int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((mz_uint64)(*pSource_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)*pSource_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + *pSource_len = *pSource_len - stream.avail_in; + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_uncompress2(pDest, pDest_len, pSource, &source_len); +} + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { 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" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifndef MINIZ_NO_DEFLATE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Compression (independent from all decompression API's) */ + +/* Purposely making these tables static for faster init and thread safety. */ +static const mz_uint16 s_tdefl_len_sym[256] = + { + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 + }; + +static const mz_uint8 s_tdefl_len_extra[256] = + { + 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, + 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, + 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, + 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 + }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = + { + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 + }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = + { + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 7, 7, 7, 7, 7, 7, 7, 7 + }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = + { + 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, + 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, + 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 + }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = + { + 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, + 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, + 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 + }; + +/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ +typedef struct +{ + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_ARR(hist); + for (i = 0; i < num_syms; i++) + { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) + { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) + { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) + { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) + { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) + { + used++; + root--; + } + while (avbl > used) + { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +/* Limits canonical Huffman code table's max code size. */ +enum +{ + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) + { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_ARR(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) + { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_ARR(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_ARR(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do \ + { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) \ + { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) \ + { \ + if (rle_repeat_count < 3) \ + { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } \ + else \ + { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) \ + { \ + if (rle_z_count < 3) \ + { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } \ + else if (rle_z_count <= 10) \ + { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } \ + else \ + { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static 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 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + 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; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0]; + mz_uint match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + 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]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + memcpy(pOutput_buf, &bit_buffer, sizeof(mz_uint64)); + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + 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]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static const mz_uint s_tdefl_num_probes[11]; + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + 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; + 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; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + const mz_uint8 cmf = 0x78; + mz_uint8 flg, flevel = 3; + mz_uint header, i, mz_un = sizeof(s_tdefl_num_probes) / sizeof(mz_uint); + + /* Determine compression level by reversing the process in tdefl_create_comp_flags_from_zip_params() */ + for (i = 0; i < mz_un; i++) + if (s_tdefl_num_probes[i] == (d->m_flags & 0xFFF)) break; + + if (i < 2) + flevel = 0; + else if (i < 6) + flevel = 1; + else if (i == 6) + flevel = 2; + + header = cmf << 8 | (flevel << 6); + header += 31 - (header % 31); + flg = header & 0xFF; + + TDEFL_PUT_BITS(cmf, 8); + TDEFL_PUT_BITS(flg, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static 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) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static 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) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) +{ + mz_uint32 ret; + memcpy(&ret, p, sizeof(mz_uint32)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) +#endif +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + 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; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + 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)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); +#else + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; +#endif + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + 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; + 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]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc ? pSrc + num_bytes_to_process : NULL; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + 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); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + 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))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((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)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +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) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_ARR(d->m_hash); + MZ_CLEAR_ARR(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_ARR(d->m_hash); + 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; + 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; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + *d->m_pLZ_flags = 0; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_ARR(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +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) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +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) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* 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). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +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) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* 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) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc(void) +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifndef MINIZ_NO_INFLATE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* 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 */ +/* 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 */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree) \ + do \ + { \ + temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* 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 */ +/* 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 */ +/* 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. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* 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 */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pLookUp, pTree) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pLookUp, pTree); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = pLookUp[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = pTree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +static void tinfl_clear_tree(tinfl_decompressor *r) +{ + if (r->m_type == 0) + MZ_CLEAR_ARR(r->m_tree_0); + else if (r->m_type == 1) + MZ_CLEAR_ARR(r->m_tree_1); + else + MZ_CLEAR_ARR(r->m_tree_2); +} + +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) +{ + 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 }; + 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 }; + 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 }; + 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 }; + 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 }; + static const mz_uint16 s_min_table_sizes[3] = { 257, 1, 4 }; + + mz_int16 *pTrees[3]; + mz_uint8 *pCode_sizes[3]; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next ? pOut_buf_next + *pOut_buf_size : NULL; + 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; + + /* 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). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + pTrees[0] = r->m_tree_0; + pTrees[1] = r->m_tree_1; + pTrees[2] = r->m_tree_2; + pCode_sizes[0] = r->m_code_size_0; + pCode_sizes[1] = r->m_code_size_1; + pCode_sizes[2] = r->m_code_size_2; + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)((size_t)1 << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + 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)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_code_size_0; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_code_size_1, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_ARR(r->m_code_size_2); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_code_size_2[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + mz_int16 *pLookUp; + mz_int16 *pTree; + mz_uint8 *pCode_size; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pLookUp = r->m_look_up[r->m_type]; + pTree = pTrees[r->m_type]; + pCode_size = pCode_sizes[r->m_type]; + MZ_CLEAR_ARR(total_syms); + TINFL_MEMSET(pLookUp, 0, sizeof(r->m_look_up[0])); + tinfl_clear_tree(r); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pCode_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pCode_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pLookUp[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pLookUp[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTree[-tree_cur - 1]) + { + pTree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, r->m_look_up[2], r->m_tree_2); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_code_size_0, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_code_size_1, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, r->m_look_up[0], r->m_tree_0); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_look_up[0][bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tree_0[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, r->m_look_up[1], r->m_tree_1); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +#else + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; +#endif + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* 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. */ + /* 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. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= ~(~(tinfl_bit_buf_t)0 << num_bits); + 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). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* 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. */ + /* 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. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & ~(~(tinfl_bit_buf_t)0 << num_bits); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + 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, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +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) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + 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); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +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) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + memset(pDict,0,TINFL_LZ_DICT_SIZE); + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +#ifndef MINIZ_NO_MALLOC +tinfl_decompressor *tinfl_decompressor_alloc(void) +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) + +#define WIN32_LEAN_AND_MEAN +#include + +static WCHAR* mz_utf8z_to_widechar(const char* str) +{ + int reqChars = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + WCHAR* wStr = (WCHAR*)malloc(reqChars * sizeof(WCHAR)); + MultiByteToWideChar(CP_UTF8, 0, str, -1, wStr, reqChars); + return wStr; +} + +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + WCHAR* wFilename = mz_utf8z_to_widechar(pFilename); + WCHAR* wMode = mz_utf8z_to_widechar(pMode); + FILE* pFile = NULL; + errno_t err = _wfopen_s(&pFile, wFilename, wMode); + free(wFilename); + free(wMode); + return err ? NULL : pFile; +} + +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + WCHAR* wPath = mz_utf8z_to_widechar(pPath); + WCHAR* wMode = mz_utf8z_to_widechar(pMode); + FILE* pFile = NULL; + errno_t err = _wfreopen_s(&pFile, wPath, wMode, pStream); + free(wPath); + free(wMode); + return err ? NULL : pFile; +} + +static int mz_stat64(const char *path, struct __stat64 *buffer) +{ + WCHAR* wPath = mz_utf8z_to_widechar(path); + int res = _wstat64(wPath, buffer); + free(wPath); + return res; +} + +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat64 +#define MZ_FILE_STAT mz_stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove + +#elif defined(__MINGW32__) || defined(__WATCOMC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove + +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove + +#elif defined(__USE_LARGEFILE64) /* gcc, clang */ +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove + +#elif defined(__APPLE__) || defined(__FreeBSD__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* 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. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + mz_uint32 m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* 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.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + if (n > 0) + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static 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) +{ + 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; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + 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); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* 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.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* 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. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + 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) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + 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) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + 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); + 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); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < (mz_uint64)pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, 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. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + 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); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* 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). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + 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) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + 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) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_PTR(pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + 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)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +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) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + 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)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* 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. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static 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) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + 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)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = 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); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* 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). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static 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) +{ + 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; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const mz_uint32 size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + mz_uint32 file_index = pIndices[(mz_uint32)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + 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)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + 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); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +static +mz_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) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (st) { + file_stat = *st; + } else + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + 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); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + 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)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +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) +{ + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); +} + +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) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size, NULL); +} + +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) +{ + return mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, buf_size, flags, NULL, 0, NULL); +} + +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) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_uint64 alloc_size; + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return NULL; + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem_no_alloc1(pZip, file_index, pBuf, (size_t)alloc_size, flags, NULL, 0, &file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +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) +{ + int status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32 = MZ_CRC32_INIT; +#endif + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* 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) */ + cur_file_ofs = file_stat.m_local_header_ofs; + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + 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); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + 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); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +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) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + 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)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + 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) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + 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); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + 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) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + 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); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (mz_uint8*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + 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) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + goto handle_failure; + } + + if (local_header_filename_len) + { + 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) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* 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. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + 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) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + 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)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + 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)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + mz_uint32 i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* 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) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + 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))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +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) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + 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)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +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) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_ARR(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* 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?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +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) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static 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) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static 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) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_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, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_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, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* 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. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +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, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + 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); +} + +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, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if (((mz_uint64)buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* 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.) */ + 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))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_ARR(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + 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) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + 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, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +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, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (max_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (max_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_ARR(local_dir_header); + if (pState->m_zip64) + { + if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + else + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, + NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (max_size) + { + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (1) + { + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if (n == 0) + break; + + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + cur_archive_file_ofs += n; + } + uncomp_size = file_ofs; + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + 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) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + if (n == 0) + flush = TDEFL_FINISH; + + status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + uncomp_size = file_ofs; + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + { + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + 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), + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, + uncomp_crc32, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + cur_archive_header_file_ofs = local_dir_header_ofs; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + if (pExtra_data != NULL) + { + cur_archive_header_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_header_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_header_file_ofs += extra_size; + } + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + 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, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO + +static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pSrc_file); +} + +mz_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, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + return 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, + user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); +} + +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) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + 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); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static 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) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* 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) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + 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) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* 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). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + 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) + + 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; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* 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 */ + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + 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))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint8 *pSrc_descriptor = (const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0); + const mz_uint32 src_crc32 = MZ_READ_LE32(pSrc_descriptor); + const mz_uint64 src_comp_size = MZ_READ_LE32(pSrc_descriptor + sizeof(mz_uint32)); + const mz_uint64 src_uncomp_size = MZ_READ_LE32(pSrc_descriptor + 2*sizeof(mz_uint32)); + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* 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. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + 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)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + 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)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + 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)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((mz_uint64)pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + 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)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_ARR(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + 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)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_ARR(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_ARR(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + 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) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +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) +{ + 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); +} + +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) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* 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. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* 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.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +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) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write callback failed"; + case MZ_ZIP_TOTAL_ERRORS: + return "total errors"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/public/miniz.h b/public/miniz.h index cef9453d..32d68abb 100644 --- a/public/miniz.h +++ b/public/miniz.h @@ -1,4 +1,7 @@ -/* miniz.c 2.1.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing +#ifndef MINIZ_EXPORT +#define MINIZ_EXPORT +#endif +/* miniz.c 3.0.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing See "unlicense" statement at the end of this file. Rich Geldreich , last updated Oct. 13, 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt @@ -95,7 +98,7 @@ possibility that the archive's central directory could be lost with this method if anything goes wrong, though. - ZIP archive support limitations: - No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. Requires streams capable of seeking. * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the @@ -114,10 +117,8 @@ - - -/* Defines to completely disable specific portions of miniz.c: - If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32 and adler-32. */ /* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ #define MINIZ_NO_STDIO @@ -127,6 +128,12 @@ /* The current downside is the times written to your archives will be from 1979. */ #define MINIZ_NO_TIME +/* Define MINIZ_NO_DEFLATE_APIS to disable all compression API's. */ +/*#define MINIZ_NO_DEFLATE_APIS */ + +/* Define MINIZ_NO_INFLATE_APIS to disable all decompression API's. */ +/*#define MINIZ_NO_INFLATE_APIS */ + /* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ #define MINIZ_NO_ARCHIVE_APIS @@ -139,12 +146,20 @@ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ /*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ -/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ /*#define MINIZ_NO_MALLOC */ +#ifdef MINIZ_NO_INFLATE_APIS +#define MINIZ_NO_ARCHIVE_APIS +#endif + +#ifdef MINIZ_NO_DEFLATE_APIS +#define MINIZ_NO_ARCHIVE_WRITING_APIS +#endif + #if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) /* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ #define MINIZ_NO_TIME @@ -163,13 +178,35 @@ #define MINIZ_X86_OR_X64_CPU 0 #endif -#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN only if not set */ +#if !defined(MINIZ_LITTLE_ENDIAN) +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) /* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ #define MINIZ_LITTLE_ENDIAN 1 #else #define MINIZ_LITTLE_ENDIAN 0 #endif +#else + +#if MINIZ_X86_OR_X64_CPU +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +#endif +#endif + +/* Using unaligned loads and stores causes errors when using UBSan */ +#if defined(__has_feature) +#if __has_feature(undefined_behavior_sanitizer) +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU @@ -198,15 +235,15 @@ extern "C" { typedef unsigned long mz_ulong; /* 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. */ -void mz_free(void *p); +MINIZ_EXPORT void mz_free(void *p); #define MZ_ADLER32_INIT (1) /* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); #define MZ_CRC32_INIT (0) /* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ -mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); /* Compression strategies. */ enum @@ -222,7 +259,7 @@ enum #define MZ_DEFLATED 8 /* Heap allocation callbacks. -Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. */ +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); typedef void (*mz_free_func)(void *opaque, void *address); typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); @@ -238,10 +275,10 @@ enum MZ_DEFAULT_COMPRESSION = -1 }; -#define MZ_VERSION "10.1.0" -#define MZ_VERNUM 0xA100 -#define MZ_VER_MAJOR 10 -#define MZ_VER_MINOR 1 +#define MZ_VERSION "11.0.2" +#define MZ_VERNUM 0xB002 +#define MZ_VER_MAJOR 11 +#define MZ_VER_MINOR 2 #define MZ_VER_REVISION 0 #define MZ_VER_SUBREVISION 0 @@ -304,7 +341,9 @@ typedef struct mz_stream_s typedef mz_stream *mz_streamp; /* Returns the version string of miniz.c. */ -const char *mz_version(void); +MINIZ_EXPORT const char *mz_version(void); + +#ifndef MINIZ_NO_DEFLATE_APIS /* mz_deflateInit() initializes a compressor with default options: */ /* Parameters: */ @@ -317,17 +356,17 @@ const char *mz_version(void); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if the input parameters are bogus. */ /* MZ_MEM_ERROR on out of memory. */ -int mz_deflateInit(mz_streamp pStream, int level); +MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); /* mz_deflateInit2() is like mz_deflate(), except with more control: */ /* Additional parameters: */ /* method must be MZ_DEFLATED */ /* 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) */ /* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); +MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ -int mz_deflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); /* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ /* Parameters: */ @@ -339,34 +378,38 @@ int mz_deflateReset(mz_streamp pStream); /* MZ_STREAM_ERROR if the stream is bogus. */ /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* 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.) */ -int mz_deflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); /* mz_deflateEnd() deinitializes a compressor: */ /* Return values: */ /* MZ_OK on success. */ /* MZ_STREAM_ERROR if the stream is bogus. */ -int mz_deflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); /* 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. */ -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); /* Single-call compression functions mz_compress() and mz_compress2(): */ /* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); +MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); /* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ -mz_ulong mz_compressBound(mz_ulong source_len); +MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS /* Initializes a decompressor. */ -int mz_inflateInit(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); /* 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: */ /* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ -int mz_inflateInit2(mz_streamp pStream, int window_bits); +MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); /* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ -int mz_inflateReset(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); /* 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. */ /* Parameters: */ @@ -382,17 +425,19 @@ int mz_inflateReset(mz_streamp pStream); /* MZ_PARAM_ERROR if one of the parameters is invalid. */ /* 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 */ /* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ -int mz_inflate(mz_streamp pStream, int flush); +MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); /* Deinitializes a decompressor. */ -int mz_inflateEnd(mz_streamp pStream); +MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); /* Single-call decompression. */ /* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ /* Returns a string description of the specified error code, or NULL if the error code is invalid. */ -const char *mz_error(int err); +MINIZ_EXPORT const char *mz_error(int err); /* 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. */ /* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ @@ -440,6 +485,8 @@ typedef void *const voidpc; #define free_func mz_free_func #define internal_state mz_internal_state #define z_stream mz_stream + +#ifndef MINIZ_NO_DEFLATE_APIS #define deflateInit mz_deflateInit #define deflateInit2 mz_deflateInit2 #define deflateReset mz_deflateReset @@ -449,12 +496,18 @@ typedef void *const voidpc; #define compress mz_compress #define compress2 mz_compress2 #define compressBound mz_compressBound +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ + +#ifndef MINIZ_NO_INFLATE_APIS #define inflateInit mz_inflateInit #define inflateInit2 mz_inflateInit2 #define inflateReset mz_inflateReset #define inflate mz_inflate #define inflateEnd mz_inflateEnd #define uncompress mz_uncompress +#define uncompress2 mz_uncompress2 +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + #define crc32 mz_crc32 #define adler32 mz_adler32 #define MAX_WBITS 15 @@ -475,12 +528,19 @@ typedef void *const voidpc; #ifdef __cplusplus } #endif + + + + + #pragma once #include #include #include #include + + /* ------------------- Types and macros */ typedef unsigned char mz_uint8; typedef signed short mz_int16; @@ -511,7 +571,8 @@ typedef int mz_bool; #ifdef MINIZ_NO_TIME typedef struct mz_dummy_time_t_tag { - int m_dummy; + mz_uint32 m_dummy1; + mz_uint32 m_dummy2; } mz_dummy_time_t; #define MZ_TIME_T mz_dummy_time_t #else @@ -533,6 +594,8 @@ typedef struct mz_dummy_time_t_tag #define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) +#define MZ_CLEAR_ARR(obj) memset((obj), 0, sizeof(obj)) +#define MZ_CLEAR_PTR(obj) memset((obj), 0, sizeof(*obj)) #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) @@ -556,9 +619,9 @@ typedef struct mz_dummy_time_t_tag extern "C" { #endif -extern void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); -extern void miniz_def_free_func(void *opaque, void *address); -extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); #define MZ_UINT16_MAX (0xFFFFU) #define MZ_UINT32_MAX (0xFFFFFFFFU) @@ -569,6 +632,8 @@ extern void *miniz_def_realloc_func(void *opaque, void *address, size_t items, s #pragma once +#ifndef MINIZ_NO_DEFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -616,11 +681,11 @@ enum /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must free() the returned block when it's no longer needed. */ -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ /* Returns 0 on failure. */ -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); +MINIZ_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); /* Compresses an image to a compressed PNG file in memory. */ /* On entry: */ @@ -632,14 +697,14 @@ size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void /* Function returns a pointer to the compressed data, or NULL on failure. */ /* *pLen_out will be set to the size of the PNG image file. */ /* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ -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); -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); +MINIZ_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); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); /* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); /* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ -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); +MINIZ_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); enum { @@ -727,39 +792,43 @@ typedef struct /* 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. */ /* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ /* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); +MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); /* 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. */ -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); +MINIZ_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); /* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ /* tdefl_compress_buffer() always consumes the entire input buffer. */ -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); +MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); -mz_uint32 tdefl_get_adler32(tdefl_compressor *d); +MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); /* Create tdefl_compress() flags given zlib-style compression parameters. */ /* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ /* window_bits may be -15 (raw deflate) or 15 (zlib) */ /* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #ifndef MINIZ_NO_MALLOC /* Allocate the tdefl_compressor structure in C so that */ /* non-C language bindings to tdefl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc(void); -void tdefl_compressor_free(tdefl_compressor *pComp); +MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); +MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); #endif #ifdef __cplusplus } #endif + +#endif /*#ifndef MINIZ_NO_DEFLATE_APIS*/ #pragma once /* ------------------- Low-level Decompression API Definitions */ +#ifndef MINIZ_NO_INFLATE_APIS + #ifdef __cplusplus extern "C" { #endif @@ -784,17 +853,17 @@ enum /* Function returns a pointer to the decompressed data, or NULL on failure. */ /* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ /* The caller must call mz_free() on the returned block when it's no longer needed. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); +MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); /* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ /* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -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); +MINIZ_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); /* 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. */ /* Returns 1 on success or 0 on failure. */ typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); -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); +MINIZ_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); struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; @@ -803,8 +872,8 @@ typedef struct tinfl_decompressor_tag tinfl_decompressor; /* Allocate the tinfl_decompressor structure in C so that */ /* non-C language bindings to tinfl_ API don't need to worry about */ /* structure size and allocation mechanism. */ -tinfl_decompressor *tinfl_decompressor_alloc(void); -void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); +MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); #endif /* Max size of LZ dictionary. */ @@ -855,7 +924,7 @@ typedef enum { /* 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. */ /* 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. */ -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); +MINIZ_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); /* Internal/private bits follow. */ enum @@ -868,12 +937,6 @@ enum TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS }; -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; -} tinfl_huff_table; - #if MINIZ_HAS_64BIT_REGISTERS #define TINFL_USE_64BIT_BITBUF 1 #else @@ -893,7 +956,13 @@ struct tinfl_decompressor_tag 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]; tinfl_bit_buf_t m_bit_buf; size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_int16 m_look_up[TINFL_MAX_HUFF_TABLES][TINFL_FAST_LOOKUP_SIZE]; + mz_int16 m_tree_0[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; + mz_int16 m_tree_1[TINFL_MAX_HUFF_SYMBOLS_1 * 2]; + mz_int16 m_tree_2[TINFL_MAX_HUFF_SYMBOLS_2 * 2]; + mz_uint8 m_code_size_0[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_uint8 m_code_size_1[TINFL_MAX_HUFF_SYMBOLS_1]; + mz_uint8 m_code_size_2[TINFL_MAX_HUFF_SYMBOLS_2]; mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; }; @@ -901,2953 +970,453 @@ struct tinfl_decompressor_tag } #endif -#ifndef MINIZ_HEADER_FILE_ONLY -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ +#endif /*#ifndef MINIZ_NO_INFLATE_APIS*/ + +#pragma once +/* ------------------- ZIP archive reading/writing */ -typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; -typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; -typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; +#ifndef MINIZ_NO_ARCHIVE_APIS #ifdef __cplusplus extern "C" { #endif -/* ------------------- zlib-style API's */ - -mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) -{ - mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); - size_t block_len = buf_len % 5552; - if (!ptr) - return MZ_ADLER32_INIT; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; - s1 += ptr[1], s2 += s1; - s1 += ptr[2], s2 += s1; - s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; - s1 += ptr[5], s2 += s1; - s1 += ptr[6], s2 += s1; - s1 += ptr[7], s2 += s1; - } - for (; i < block_len; ++i) - s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; - buf_len -= block_len; - block_len = 5552; - } - return (s2 << 16) + s1; -} - -/* 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/ */ -#if 0 - mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) - { - static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; - mz_uint32 crcu32 = (mz_uint32)crc; - if (!ptr) - return MZ_CRC32_INIT; - crcu32 = ~crcu32; - while (buf_len--) - { - mz_uint8 b = *ptr++; - crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; - crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; - } - return ~crcu32; - } -#else -/* Faster, but larger CPU cache footprint. - */ -mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) -{ - static const mz_uint32 s_crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, - 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, - 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, - 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, - 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, - 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, - 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, - 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, - 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, - 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, - 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, - 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, - 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, - 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, - 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, - 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, - 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, - 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, - 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, - 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, - 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, - 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, - 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, - 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, - 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, - 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, - 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, - 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, - 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, - 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, - 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, - 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, - 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; - const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; - - while (buf_len >= 4) - { - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; - pByte_buf += 4; - buf_len -= 4; - } - - while (buf_len) - { - crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; - ++pByte_buf; - --buf_len; - } - - return ~crc32; -} -#endif - -void mz_free(void *p) -{ - MZ_FREE(p); -} - -void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) -{ - (void)opaque, (void)items, (void)size; - return MZ_MALLOC(items * size); -} -void miniz_def_free_func(void *opaque, void *address) -{ - (void)opaque, (void)address; - MZ_FREE(address); -} -void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) -{ - (void)opaque, (void)address, (void)items, (void)size; - return MZ_REALLOC(address, items * size); -} - -const char *mz_version(void) -{ - return MZ_VERSION; -} - -#ifndef MINIZ_NO_ZLIB_APIS - -int mz_deflateInit(mz_streamp pStream, int level) -{ - return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); -} - -int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) -{ - tdefl_compressor *pComp; - mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); - - if (!pStream) - return MZ_STREAM_ERROR; - if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) - return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = MZ_ADLER32_INIT; - pStream->msg = NULL; - pStream->reserved = 0; - pStream->total_in = 0; - pStream->total_out = 0; - if (!pStream->zalloc) - pStream->zalloc = miniz_def_alloc_func; - if (!pStream->zfree) - pStream->zfree = miniz_def_free_func; - - pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); - if (!pComp) - return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pComp; - - if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) - { - mz_deflateEnd(pStream); - return MZ_PARAM_ERROR; - } - - return MZ_OK; -} - -int mz_deflateReset(mz_streamp pStream) -{ - if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) - return MZ_STREAM_ERROR; - pStream->total_in = pStream->total_out = 0; - tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); - return MZ_OK; -} - -int mz_deflate(mz_streamp pStream, int flush) -{ - size_t in_bytes, out_bytes; - mz_ulong orig_total_in, orig_total_out; - int mz_status = MZ_OK; - - if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) - return MZ_STREAM_ERROR; - if (!pStream->avail_out) - return MZ_BUF_ERROR; - - if (flush == MZ_PARTIAL_FLUSH) - flush = MZ_SYNC_FLUSH; - - if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) - return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; - - orig_total_in = pStream->total_in; - orig_total_out = pStream->total_out; - for (;;) - { - tdefl_status defl_status; - in_bytes = pStream->avail_in; - out_bytes = pStream->avail_out; - - defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); - - pStream->next_out += (mz_uint)out_bytes; - pStream->avail_out -= (mz_uint)out_bytes; - pStream->total_out += (mz_uint)out_bytes; - - if (defl_status < 0) - { - mz_status = MZ_STREAM_ERROR; - break; - } - else if (defl_status == TDEFL_STATUS_DONE) - { - mz_status = MZ_STREAM_END; - break; - } - else if (!pStream->avail_out) - break; - else if ((!pStream->avail_in) && (flush != MZ_FINISH)) - { - if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) - break; - return MZ_BUF_ERROR; /* Can't make forward progress without some input. - */ - } - } - return mz_status; -} - -int mz_deflateEnd(mz_streamp pStream) -{ - if (!pStream) - return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; -} - -mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) -{ - (void)pStream; - /* 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.) */ - return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); -} - -int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) -{ - int status; - mz_stream stream; - memset(&stream, 0, sizeof(stream)); - - /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) - return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_deflateInit(&stream, level); - if (status != MZ_OK) - return status; - - status = mz_deflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_deflateEnd(&stream); - return (status == MZ_OK) ? MZ_BUF_ERROR : status; - } - - *pDest_len = stream.total_out; - return mz_deflateEnd(&stream); -} - -int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); -} - -mz_ulong mz_compressBound(mz_ulong source_len) -{ - return mz_deflateBound(NULL, source_len); -} - -typedef struct -{ - tinfl_decompressor m_decomp; - mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; - int m_window_bits; - mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; - tinfl_status m_last_status; -} inflate_state; - -int mz_inflateInit2(mz_streamp pStream, int window_bits) -{ - inflate_state *pDecomp; - if (!pStream) - return MZ_STREAM_ERROR; - if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) - return MZ_PARAM_ERROR; - - pStream->data_type = 0; - pStream->adler = 0; - pStream->msg = NULL; - pStream->total_in = 0; - pStream->total_out = 0; - pStream->reserved = 0; - if (!pStream->zalloc) - pStream->zalloc = miniz_def_alloc_func; - if (!pStream->zfree) - pStream->zfree = miniz_def_free_func; - - pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); - if (!pDecomp) - return MZ_MEM_ERROR; - - pStream->state = (struct mz_internal_state *)pDecomp; - - tinfl_init(&pDecomp->m_decomp); - pDecomp->m_dict_ofs = 0; - pDecomp->m_dict_avail = 0; - pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; - pDecomp->m_first_call = 1; - pDecomp->m_has_flushed = 0; - pDecomp->m_window_bits = window_bits; - - return MZ_OK; -} - -int mz_inflateInit(mz_streamp pStream) -{ - return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); -} - -int mz_inflateReset(mz_streamp pStream) -{ - inflate_state *pDecomp; - if (!pStream) - return MZ_STREAM_ERROR; - - pStream->data_type = 0; - pStream->adler = 0; - pStream->msg = NULL; - pStream->total_in = 0; - pStream->total_out = 0; - pStream->reserved = 0; - - pDecomp = (inflate_state *)pStream->state; - - tinfl_init(&pDecomp->m_decomp); - pDecomp->m_dict_ofs = 0; - pDecomp->m_dict_avail = 0; - pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; - pDecomp->m_first_call = 1; - pDecomp->m_has_flushed = 0; - /* pDecomp->m_window_bits = window_bits */; - - return MZ_OK; -} - -int mz_inflate(mz_streamp pStream, int flush) -{ - inflate_state *pState; - mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; - size_t in_bytes, out_bytes, orig_avail_in; - tinfl_status status; - - if ((!pStream) || (!pStream->state)) - return MZ_STREAM_ERROR; - if (flush == MZ_PARTIAL_FLUSH) - flush = MZ_SYNC_FLUSH; - if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) - return MZ_STREAM_ERROR; - - pState = (inflate_state *)pStream->state; - if (pState->m_window_bits > 0) - decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; - orig_avail_in = pStream->avail_in; - - first_call = pState->m_first_call; - pState->m_first_call = 0; - if (pState->m_last_status < 0) - return MZ_DATA_ERROR; - - if (pState->m_has_flushed && (flush != MZ_FINISH)) - return MZ_STREAM_ERROR; - pState->m_has_flushed |= (flush == MZ_FINISH); - - if ((flush == MZ_FINISH) && (first_call)) - { - /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ - decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; - in_bytes = pStream->avail_in; - out_bytes = pStream->avail_out; - status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); - pState->m_last_status = status; - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tinfl_get_adler32(&pState->m_decomp); - pStream->next_out += (mz_uint)out_bytes; - pStream->avail_out -= (mz_uint)out_bytes; - pStream->total_out += (mz_uint)out_bytes; - - if (status < 0) - return MZ_DATA_ERROR; - else if (status != TINFL_STATUS_DONE) - { - pState->m_last_status = TINFL_STATUS_FAILED; - return MZ_BUF_ERROR; - } - return MZ_STREAM_END; - } - /* flush != MZ_FINISH then we must assume there's more input. */ - if (flush != MZ_FINISH) - decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; - - if (pState->m_dict_avail) - { - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; - pStream->avail_out -= n; - pStream->total_out += n; - pState->m_dict_avail -= n; - pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; - } - - for (;;) - { - in_bytes = pStream->avail_in; - out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; - - 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); - pState->m_last_status = status; - - pStream->next_in += (mz_uint)in_bytes; - pStream->avail_in -= (mz_uint)in_bytes; - pStream->total_in += (mz_uint)in_bytes; - pStream->adler = tinfl_get_adler32(&pState->m_decomp); - - pState->m_dict_avail = (mz_uint)out_bytes; - - n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); - memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); - pStream->next_out += n; - pStream->avail_out -= n; - pStream->total_out += n; - pState->m_dict_avail -= n; - pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); - - if (status < 0) - return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ - else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) - return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ - else if (flush == MZ_FINISH) - { - /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ - if (status == TINFL_STATUS_DONE) - return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; - /* 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. */ - else if (!pStream->avail_out) - return MZ_BUF_ERROR; - } - else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) - break; - } - - return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; -} - -int mz_inflateEnd(mz_streamp pStream) -{ - if (!pStream) - return MZ_STREAM_ERROR; - if (pStream->state) - { - pStream->zfree(pStream->opaque, pStream->state); - pStream->state = NULL; - } - return MZ_OK; -} - -int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) -{ - mz_stream stream; - int status; - memset(&stream, 0, sizeof(stream)); - - /* In case mz_ulong is 64-bits (argh I hate longs). */ - if ((source_len | *pDest_len) > 0xFFFFFFFFU) - return MZ_PARAM_ERROR; - - stream.next_in = pSource; - stream.avail_in = (mz_uint32)source_len; - stream.next_out = pDest; - stream.avail_out = (mz_uint32)*pDest_len; - - status = mz_inflateInit(&stream); - if (status != MZ_OK) - return status; - - status = mz_inflate(&stream, MZ_FINISH); - if (status != MZ_STREAM_END) - { - mz_inflateEnd(&stream); - return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; - } - *pDest_len = stream.total_out; - - return mz_inflateEnd(&stream); -} - -const char *mz_error(int err) -{ - static struct - { - int m_err; - const char *m_pDesc; - } s_error_descs[] = - { - { 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" } - }; - mz_uint i; - for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) - if (s_error_descs[i].m_err == err) - return s_error_descs[i].m_pDesc; - return NULL; -} - -#endif /*MINIZ_NO_ZLIB_APIS */ - -#ifdef __cplusplus -} -#endif - -/* - This is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this software, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this software dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - For more information, please refer to -*/ -/************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ - - - - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- Low-level Compression (independent from all decompression API's) */ - -/* Purposely making these tables static for faster init and thread safety. */ -static const mz_uint16 s_tdefl_len_sym[256] = - { - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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 - }; - -static const mz_uint8 s_tdefl_len_extra[256] = - { - 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, - 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, - 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, - 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 - }; - -static const mz_uint8 s_tdefl_small_dist_sym[512] = - { - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 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 - }; - -static const mz_uint8 s_tdefl_small_dist_extra[512] = - { - 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, - 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, - 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, - 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, - 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, - 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, - 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, - 7, 7, 7, 7, 7, 7, 7, 7 - }; - -static const mz_uint8 s_tdefl_large_dist_sym[128] = - { - 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, - 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, - 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 - }; - -static const mz_uint8 s_tdefl_large_dist_extra[128] = - { - 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, - 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, - 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 - }; - -/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ -typedef struct -{ - mz_uint16 m_key, m_sym_index; -} tdefl_sym_freq; -static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) -{ - mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; - tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; - MZ_CLEAR_OBJ(hist); - for (i = 0; i < num_syms; i++) - { - mz_uint freq = pSyms0[i].m_key; - hist[freq & 0xFF]++; - hist[256 + ((freq >> 8) & 0xFF)]++; - } - while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) - total_passes--; - for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) - { - const mz_uint32 *pHist = &hist[pass << 8]; - mz_uint offsets[256], cur_ofs = 0; - for (i = 0; i < 256; i++) - { - offsets[i] = cur_ofs; - cur_ofs += pHist[i]; - } - for (i = 0; i < num_syms; i++) - pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; - { - tdefl_sym_freq *t = pCur_syms; - pCur_syms = pNew_syms; - pNew_syms = t; - } - } - return pCur_syms; -} - -/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ -static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) -{ - int root, leaf, next, avbl, used, dpth; - if (n == 0) - return; - else if (n == 1) - { - A[0].m_key = 1; - return; - } - A[0].m_key += A[1].m_key; - root = 0; - leaf = 2; - for (next = 1; next < n - 1; next++) - { - if (leaf >= n || A[root].m_key < A[leaf].m_key) - { - A[next].m_key = A[root].m_key; - A[root++].m_key = (mz_uint16)next; - } - else - A[next].m_key = A[leaf++].m_key; - if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) - { - A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); - A[root++].m_key = (mz_uint16)next; - } - else - A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); - } - A[n - 2].m_key = 0; - for (next = n - 3; next >= 0; next--) - A[next].m_key = A[A[next].m_key].m_key + 1; - avbl = 1; - used = dpth = 0; - root = n - 2; - next = n - 1; - while (avbl > 0) - { - while (root >= 0 && (int)A[root].m_key == dpth) - { - used++; - root--; - } - while (avbl > used) - { - A[next--].m_key = (mz_uint16)(dpth); - avbl--; - } - avbl = 2 * used; - dpth++; - used = 0; - } -} - -/* Limits canonical Huffman code table's max code size. */ enum { - TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 }; -static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) -{ - int i; - mz_uint32 total = 0; - if (code_list_len <= 1) - return; - for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) - pNum_codes[max_code_size] += pNum_codes[i]; - for (i = max_code_size; i > 0; i--) - total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); - while (total != (1UL << max_code_size)) - { - pNum_codes[max_code_size]--; - for (i = max_code_size - 1; i > 0; i--) - if (pNum_codes[i]) - { - pNum_codes[i]--; - pNum_codes[i + 1] += 2; - break; - } - total--; - } -} - -static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) -{ - int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; - mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; - MZ_CLEAR_OBJ(num_codes); - if (static_table) - { - for (i = 0; i < table_len; i++) - num_codes[d->m_huff_code_sizes[table_num][i]]++; - } - else - { - tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; - int num_used_syms = 0; - const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; - for (i = 0; i < table_len; i++) - if (pSym_count[i]) - { - syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; - syms0[num_used_syms++].m_sym_index = (mz_uint16)i; - } - - pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); - tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); - - for (i = 0; i < num_used_syms; i++) - num_codes[pSyms[i].m_key]++; - - tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); - - MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); - MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); - for (i = 1, j = num_used_syms; i <= code_size_limit; i++) - for (l = num_codes[i]; l > 0; l--) - d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); - } - - next_code[1] = 0; - for (j = 0, i = 2; i <= code_size_limit; i++) - next_code[i] = j = ((j + num_codes[i - 1]) << 1); - - for (i = 0; i < table_len; i++) - { - mz_uint rev_code = 0, code, code_size; - if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) - continue; - code = next_code[code_size]++; - for (l = code_size; l > 0; l--, code >>= 1) - rev_code = (rev_code << 1) | (code & 1); - d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; - } -} - -#define TDEFL_PUT_BITS(b, l) \ - do \ - { \ - mz_uint bits = b; \ - mz_uint len = l; \ - MZ_ASSERT(bits <= ((1U << len) - 1U)); \ - d->m_bit_buffer |= (bits << d->m_bits_in); \ - d->m_bits_in += len; \ - while (d->m_bits_in >= 8) \ - { \ - if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ - *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ - d->m_bit_buffer >>= 8; \ - d->m_bits_in -= 8; \ - } \ - } \ - MZ_MACRO_END - -#define TDEFL_RLE_PREV_CODE_SIZE() \ - { \ - if (rle_repeat_count) \ - { \ - if (rle_repeat_count < 3) \ - { \ - d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ - while (rle_repeat_count--) \ - packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ - } \ - else \ - { \ - d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 16; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ - } \ - rle_repeat_count = 0; \ - } \ - } - -#define TDEFL_RLE_ZERO_CODE_SIZE() \ - { \ - if (rle_z_count) \ - { \ - if (rle_z_count < 3) \ - { \ - d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ - while (rle_z_count--) \ - packed_code_sizes[num_packed_code_sizes++] = 0; \ - } \ - else if (rle_z_count <= 10) \ - { \ - d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 17; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ - } \ - else \ - { \ - d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ - packed_code_sizes[num_packed_code_sizes++] = 18; \ - packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ - } \ - rle_z_count = 0; \ - } \ - } - -static 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 }; - -static void tdefl_start_dynamic_block(tdefl_compressor *d) -{ - int num_lit_codes, num_dist_codes, num_bit_lengths; - mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; - 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; - - d->m_huff_count[0][256] = 1; - - tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); - tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); - - for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) - if (d->m_huff_code_sizes[0][num_lit_codes - 1]) - break; - for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) - if (d->m_huff_code_sizes[1][num_dist_codes - 1]) - break; - - memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); - memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); - total_code_sizes_to_pack = num_lit_codes + num_dist_codes; - num_packed_code_sizes = 0; - rle_z_count = 0; - rle_repeat_count = 0; - - memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); - for (i = 0; i < total_code_sizes_to_pack; i++) - { - mz_uint8 code_size = code_sizes_to_pack[i]; - if (!code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - if (++rle_z_count == 138) - { - TDEFL_RLE_ZERO_CODE_SIZE(); - } - } - else - { - TDEFL_RLE_ZERO_CODE_SIZE(); - if (code_size != prev_code_size) - { - TDEFL_RLE_PREV_CODE_SIZE(); - d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); - packed_code_sizes[num_packed_code_sizes++] = code_size; - } - else if (++rle_repeat_count == 6) - { - TDEFL_RLE_PREV_CODE_SIZE(); - } - } - prev_code_size = code_size; - } - if (rle_repeat_count) - { - TDEFL_RLE_PREV_CODE_SIZE(); - } - else - { - TDEFL_RLE_ZERO_CODE_SIZE(); - } - - tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); - - TDEFL_PUT_BITS(2, 2); - - TDEFL_PUT_BITS(num_lit_codes - 257, 5); - TDEFL_PUT_BITS(num_dist_codes - 1, 5); - - for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) - if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) - break; - num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); - TDEFL_PUT_BITS(num_bit_lengths - 4, 4); - for (i = 0; (int)i < num_bit_lengths; i++) - TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); - - for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) - { - mz_uint code = packed_code_sizes[packed_code_sizes_index++]; - MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); - TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); - if (code >= 16) - TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); - } -} - -static void tdefl_start_static_block(tdefl_compressor *d) -{ - mz_uint i; - mz_uint8 *p = &d->m_huff_code_sizes[0][0]; - - for (i = 0; i <= 143; ++i) - *p++ = 8; - for (; i <= 255; ++i) - *p++ = 9; - for (; i <= 279; ++i) - *p++ = 7; - for (; i <= 287; ++i) - *p++ = 8; - - memset(d->m_huff_code_sizes[1], 5, 32); - - tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); - tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); - - TDEFL_PUT_BITS(1, 2); -} - -static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - mz_uint8 *pOutput_buf = d->m_pOutput_buf; - mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; - mz_uint64 bit_buffer = d->m_bit_buffer; - mz_uint bits_in = d->m_bits_in; - -#define TDEFL_PUT_BITS_FAST(b, l) \ - { \ - bit_buffer |= (((mz_uint64)(b)) << bits_in); \ - bits_in += (l); \ - } - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - - if (flags & 1) - { - mz_uint s0, s1, n0, n1, sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); - pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - 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]]); - TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ - s0 = s_tdefl_small_dist_sym[match_dist & 511]; - n0 = s_tdefl_small_dist_extra[match_dist & 511]; - s1 = s_tdefl_large_dist_sym[match_dist >> 8]; - n1 = s_tdefl_large_dist_extra[match_dist >> 8]; - sym = (match_dist < 512) ? s0 : s1; - num_extra_bits = (match_dist < 512) ? n0 : n1; - - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); - } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - - if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) - { - flags >>= 1; - lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - } - } - } - - if (pOutput_buf >= d->m_pOutput_buf_end) - return MZ_FALSE; - - *(mz_uint64 *)pOutput_buf = bit_buffer; - pOutput_buf += (bits_in >> 3); - bit_buffer >>= (bits_in & ~7); - bits_in &= 7; - } - -#undef TDEFL_PUT_BITS_FAST - - d->m_pOutput_buf = pOutput_buf; - d->m_bits_in = 0; - d->m_bit_buffer = 0; - - while (bits_in) - { - mz_uint32 n = MZ_MIN(bits_in, 16); - TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); - bit_buffer >>= n; - bits_in -= n; - } - - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - - return (d->m_pOutput_buf < d->m_pOutput_buf_end); -} -#else -static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) -{ - mz_uint flags; - mz_uint8 *pLZ_codes; - - flags = 1; - for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) - { - if (flags == 1) - flags = *pLZ_codes++ | 0x100; - if (flags & 1) - { - mz_uint sym, num_extra_bits; - mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); - pLZ_codes += 3; - - MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); - 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]]); - TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); - - if (match_dist < 512) - { - sym = s_tdefl_small_dist_sym[match_dist]; - num_extra_bits = s_tdefl_small_dist_extra[match_dist]; - } - else - { - sym = s_tdefl_large_dist_sym[match_dist >> 8]; - num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; - } - MZ_ASSERT(d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); - TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); - } - else - { - mz_uint lit = *pLZ_codes++; - MZ_ASSERT(d->m_huff_code_sizes[0][lit]); - TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); - } - } - - TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); - - return (d->m_pOutput_buf < d->m_pOutput_buf_end); -} -#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ - -static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) -{ - if (static_block) - tdefl_start_static_block(d); - else - tdefl_start_dynamic_block(d); - return tdefl_compress_lz_codes(d); -} - -static int tdefl_flush_block(tdefl_compressor *d, int flush) -{ - mz_uint saved_bit_buf, saved_bits_in; - mz_uint8 *pSaved_output_buf; - mz_bool comp_block_succeeded = MZ_FALSE; - 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; - 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; - - d->m_pOutput_buf = pOutput_buf_start; - d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; - - MZ_ASSERT(!d->m_output_flush_remaining); - d->m_output_flush_ofs = 0; - d->m_output_flush_remaining = 0; - - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); - d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); - - if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) - { - TDEFL_PUT_BITS(0x78, 8); - TDEFL_PUT_BITS(0x01, 8); - } - - TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); - - pSaved_output_buf = d->m_pOutput_buf; - saved_bit_buf = d->m_bit_buffer; - saved_bits_in = d->m_bits_in; - - if (!use_raw_block) - comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); - - /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ - if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && - ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) - { - mz_uint i; - d->m_pOutput_buf = pSaved_output_buf; - d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - TDEFL_PUT_BITS(0, 2); - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) - { - TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); - } - for (i = 0; i < d->m_total_lz_bytes; ++i) - { - TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); - } - } - /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ - else if (!comp_block_succeeded) - { - d->m_pOutput_buf = pSaved_output_buf; - d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; - tdefl_compress_block(d, MZ_TRUE); - } - - if (flush) - { - if (flush == TDEFL_FINISH) - { - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) - { - mz_uint i, a = d->m_adler32; - for (i = 0; i < 4; i++) - { - TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); - a <<= 8; - } - } - } - else - { - mz_uint i, z = 0; - TDEFL_PUT_BITS(0, 3); - if (d->m_bits_in) - { - TDEFL_PUT_BITS(0, 8 - d->m_bits_in); - } - for (i = 2; i; --i, z ^= 0xFFFF) - { - TDEFL_PUT_BITS(z & 0xFFFF, 16); - } - } - } - - MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); - - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; - d->m_pLZ_flags = d->m_lz_code_buf; - d->m_num_flags_left = 8; - d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; - d->m_total_lz_bytes = 0; - d->m_block_index++; - - if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) - { - if (d->m_pPut_buf_func) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) - return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); - } - else if (pOutput_buf_start == d->m_output_buf) - { - int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); - d->m_out_buf_ofs += bytes_to_copy; - if ((n -= bytes_to_copy) != 0) - { - d->m_output_flush_ofs = bytes_to_copy; - d->m_output_flush_remaining = n; - } - } - else - { - d->m_out_buf_ofs += n; - } - } - - return d->m_output_flush_remaining; -} - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES -#ifdef MINIZ_UNALIGNED_USE_MEMCPY -static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) -{ - mz_uint16 ret; - memcpy(&ret, p, sizeof(mz_uint16)); - return ret; -} -static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) -{ - mz_uint16 ret; - memcpy(&ret, p, sizeof(mz_uint16)); - return ret; -} -#else -#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) -#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) -#endif -static 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) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; - mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); - if (max_match_len <= match_len) - return; - for (;;) - { - for (;;) - { - if (--num_probes_left == 0) - return; -#define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ - return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ - break; - TDEFL_PROBE; - TDEFL_PROBE; - TDEFL_PROBE; - } - if (!dist) - break; - q = (const mz_uint16 *)(d->m_dict + probe_pos); - if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) - continue; - p = s; - probe_len = 32; - do - { - } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && - (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); - if (!probe_len) - { - *pMatch_dist = dist; - *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); - break; - } - else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) - { - *pMatch_dist = dist; - if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) - break; - c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); - } - } -} -#else -static 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) -{ - mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; - mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; - const mz_uint8 *s = d->m_dict + pos, *p, *q; - mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; - MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); - if (max_match_len <= match_len) - return; - for (;;) - { - for (;;) - { - if (--num_probes_left == 0) - return; -#define TDEFL_PROBE \ - next_probe_pos = d->m_next[probe_pos]; \ - if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ - return; \ - probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ - if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ - break; - TDEFL_PROBE; - TDEFL_PROBE; - TDEFL_PROBE; - } - if (!dist) - break; - p = s; - q = d->m_dict + probe_pos; - for (probe_len = 0; probe_len < max_match_len; probe_len++) - if (*p++ != *q++) - break; - if (probe_len > match_len) - { - *pMatch_dist = dist; - if ((*pMatch_len = match_len = probe_len) == max_match_len) - return; - c0 = d->m_dict[pos + match_len]; - c1 = d->m_dict[pos + match_len - 1]; - } - } -} -#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN -#ifdef MINIZ_UNALIGNED_USE_MEMCPY -static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) -{ - mz_uint32 ret; - memcpy(&ret, p, sizeof(mz_uint32)); - return ret; -} -#else -#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) -#endif -static mz_bool tdefl_compress_fast(tdefl_compressor *d) -{ - /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ - 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; - mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; - mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - - while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) - { - const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; - mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); - d->m_src_buf_left -= num_bytes_to_process; - lookahead_size += num_bytes_to_process; - - while (num_bytes_to_process) - { - mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); - memcpy(d->m_dict + dst_pos, d->m_pSrc, n); - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); - d->m_pSrc += n; - dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; - num_bytes_to_process -= n; - } - - dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); - if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) - break; - - while (lookahead_size >= 4) - { - mz_uint cur_match_dist, cur_match_len = 1; - mz_uint8 *pCur_dict = d->m_dict + cur_pos; - mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; - mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; - mz_uint probe_pos = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)lookahead_pos; - - 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)) - { - const mz_uint16 *p = (const mz_uint16 *)pCur_dict; - const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); - mz_uint32 probe_len = 32; - do - { - } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && - (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); - cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); - if (!probe_len) - cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; - - if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) - { - cur_match_len = 1; - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - else - { - mz_uint32 s0, s1; - cur_match_len = MZ_MIN(cur_match_len, lookahead_size); - - MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); - - cur_match_dist--; - - pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); -#ifdef MINIZ_UNALIGNED_USE_MEMCPY - memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); -#else - *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; -#endif - pLZ_code_buf += 3; - *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); - - s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; - s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; - d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; - - d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; - } - } - else - { - *pLZ_code_buf++ = (mz_uint8)first_trigram; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - d->m_huff_count[0][(mz_uint8)first_trigram]++; - } - - if (--num_flags_left == 0) - { - num_flags_left = 8; - pLZ_flags = pLZ_code_buf++; - } - - total_lz_bytes += cur_match_len; - lookahead_pos += cur_match_len; - dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; - MZ_ASSERT(lookahead_size >= cur_match_len); - lookahead_size -= cur_match_len; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; - pLZ_code_buf = d->m_pLZ_code_buf; - pLZ_flags = d->m_pLZ_flags; - num_flags_left = d->m_num_flags_left; - } - } - - while (lookahead_size) - { - mz_uint8 lit = d->m_dict[cur_pos]; - - total_lz_bytes++; - *pLZ_code_buf++ = lit; - *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); - if (--num_flags_left == 0) - { - num_flags_left = 8; - pLZ_flags = pLZ_code_buf++; - } - - d->m_huff_count[0][lit]++; - - lookahead_pos++; - dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); - cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; - lookahead_size--; - - if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) - { - int n; - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - total_lz_bytes = d->m_total_lz_bytes; - pLZ_code_buf = d->m_pLZ_code_buf; - pLZ_flags = d->m_pLZ_flags; - num_flags_left = d->m_num_flags_left; - } - } - } - - d->m_lookahead_pos = lookahead_pos; - d->m_lookahead_size = lookahead_size; - d->m_dict_size = dict_size; - d->m_total_lz_bytes = total_lz_bytes; - d->m_pLZ_code_buf = pLZ_code_buf; - d->m_pLZ_flags = pLZ_flags; - d->m_num_flags_left = num_flags_left; - return MZ_TRUE; -} -#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ - -static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) -{ - d->m_total_lz_bytes++; - *d->m_pLZ_code_buf++ = lit; - *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); - if (--d->m_num_flags_left == 0) - { - d->m_num_flags_left = 8; - d->m_pLZ_flags = d->m_pLZ_code_buf++; - } - d->m_huff_count[0][lit]++; -} - -static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) -{ - mz_uint32 s0, s1; - - MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); - - d->m_total_lz_bytes += match_len; - - d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); - - match_dist -= 1; - d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); - d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); - d->m_pLZ_code_buf += 3; - - *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); - if (--d->m_num_flags_left == 0) - { - d->m_num_flags_left = 8; - d->m_pLZ_flags = d->m_pLZ_code_buf++; - } - - s0 = s_tdefl_small_dist_sym[match_dist & 511]; - s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; - d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; - - if (match_len >= TDEFL_MIN_MATCH_LEN) - d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; -} - -static mz_bool tdefl_compress_normal(tdefl_compressor *d) -{ - const mz_uint8 *pSrc = d->m_pSrc; - size_t src_buf_left = d->m_src_buf_left; - tdefl_flush flush = d->m_flush; - - while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) - { - mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; - /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ - if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) - { - 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; - 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]; - mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); - const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; - src_buf_left -= num_bytes_to_process; - d->m_lookahead_size += num_bytes_to_process; - while (pSrc != pSrc_end) - { - mz_uint8 c = *pSrc++; - d->m_dict[dst_pos] = c; - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)(ins_pos); - dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; - ins_pos++; - } - } - else - { - while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - { - mz_uint8 c = *pSrc++; - mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; - src_buf_left--; - d->m_dict[dst_pos] = c; - if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) - d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; - if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) - { - mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; - 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); - d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; - d->m_hash[hash] = (mz_uint16)(ins_pos); - } - } - } - d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); - if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) - break; - - /* Simple lazy/greedy parsing state machine. */ - len_to_move = 1; - cur_match_dist = 0; - cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); - cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; - if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) - { - if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) - { - mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; - cur_match_len = 0; - while (cur_match_len < d->m_lookahead_size) - { - if (d->m_dict[cur_pos + cur_match_len] != c) - break; - cur_match_len++; - } - if (cur_match_len < TDEFL_MIN_MATCH_LEN) - cur_match_len = 0; - else - cur_match_dist = 1; - } - } - else - { - tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); - } - 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))) - { - cur_match_dist = cur_match_len = 0; - } - if (d->m_saved_match_len) - { - if (cur_match_len > d->m_saved_match_len) - { - tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); - if (cur_match_len >= 128) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - d->m_saved_match_len = 0; - len_to_move = cur_match_len; - } - else - { - d->m_saved_lit = d->m_dict[cur_pos]; - d->m_saved_match_dist = cur_match_dist; - d->m_saved_match_len = cur_match_len; - } - } - else - { - tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); - len_to_move = d->m_saved_match_len - 1; - d->m_saved_match_len = 0; - } - } - else if (!cur_match_dist) - tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); - else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) - { - tdefl_record_match(d, cur_match_len, cur_match_dist); - len_to_move = cur_match_len; - } - else - { - d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; - d->m_saved_match_dist = cur_match_dist; - d->m_saved_match_len = cur_match_len; - } - /* Move the lookahead forward by len_to_move bytes. */ - d->m_lookahead_pos += len_to_move; - MZ_ASSERT(d->m_lookahead_size >= len_to_move); - d->m_lookahead_size -= len_to_move; - d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); - /* Check if it's time to flush the current LZ codes to the internal output buffer. */ - if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || - ((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)))) - { - int n; - d->m_pSrc = pSrc; - d->m_src_buf_left = src_buf_left; - if ((n = tdefl_flush_block(d, 0)) != 0) - return (n < 0) ? MZ_FALSE : MZ_TRUE; - } - } - - d->m_pSrc = pSrc; - d->m_src_buf_left = src_buf_left; - return MZ_TRUE; -} - -static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) -{ - if (d->m_pIn_buf_size) - { - *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; - } - - if (d->m_pOut_buf_size) - { - size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); - memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); - d->m_output_flush_ofs += (mz_uint)n; - d->m_output_flush_remaining -= (mz_uint)n; - d->m_out_buf_ofs += n; - - *d->m_pOut_buf_size = d->m_out_buf_ofs; - } - - return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; -} - -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) -{ - if (!d) - { - if (pIn_buf_size) - *pIn_buf_size = 0; - if (pOut_buf_size) - *pOut_buf_size = 0; - return TDEFL_STATUS_BAD_PARAM; - } - - d->m_pIn_buf = pIn_buf; - d->m_pIn_buf_size = pIn_buf_size; - d->m_pOut_buf = pOut_buf; - d->m_pOut_buf_size = pOut_buf_size; - d->m_pSrc = (const mz_uint8 *)(pIn_buf); - d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; - d->m_out_buf_ofs = 0; - d->m_flush = flush; - - if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || - (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) - { - if (pIn_buf_size) - *pIn_buf_size = 0; - if (pOut_buf_size) - *pOut_buf_size = 0; - return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); - } - d->m_wants_to_finish |= (flush == TDEFL_FINISH); - - if ((d->m_output_flush_remaining) || (d->m_finished)) - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && - ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && - ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) - { - if (!tdefl_compress_fast(d)) - return d->m_prev_return_status; - } - else -#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ - { - if (!tdefl_compress_normal(d)) - return d->m_prev_return_status; - } - - if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) - d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); - - if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) - { - if (tdefl_flush_block(d, flush) < 0) - return d->m_prev_return_status; - d->m_finished = (flush == TDEFL_FINISH); - if (flush == TDEFL_FULL_FLUSH) - { - MZ_CLEAR_OBJ(d->m_hash); - MZ_CLEAR_OBJ(d->m_next); - d->m_dict_size = 0; - } - } - - return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); -} - -tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) -{ - MZ_ASSERT(d->m_pPut_buf_func); - return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); -} - -tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - d->m_pPut_buf_func = pPut_buf_func; - d->m_pPut_buf_user = pPut_buf_user; - d->m_flags = (mz_uint)(flags); - d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; - d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; - d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; - if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_hash); - 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; - 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; - d->m_pLZ_code_buf = d->m_lz_code_buf + 1; - d->m_pLZ_flags = d->m_lz_code_buf; - d->m_num_flags_left = 8; - d->m_pOutput_buf = d->m_output_buf; - d->m_pOutput_buf_end = d->m_output_buf; - d->m_prev_return_status = TDEFL_STATUS_OKAY; - d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; - d->m_adler32 = 1; - d->m_pIn_buf = NULL; - d->m_pOut_buf = NULL; - d->m_pIn_buf_size = NULL; - d->m_pOut_buf_size = NULL; - d->m_flush = TDEFL_NO_FLUSH; - d->m_pSrc = NULL; - d->m_src_buf_left = 0; - d->m_out_buf_ofs = 0; - if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) - MZ_CLEAR_OBJ(d->m_dict); - memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); - memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); - return TDEFL_STATUS_OKAY; -} - -tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) -{ - return d->m_prev_return_status; -} - -mz_uint32 tdefl_get_adler32(tdefl_compressor *d) -{ - return d->m_adler32; -} - -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) -{ - tdefl_compressor *pComp; - mz_bool succeeded; - if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) - return MZ_FALSE; - pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); - if (!pComp) - return MZ_FALSE; - succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); - succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); - MZ_FREE(pComp); - return succeeded; -} typedef struct { - size_t m_size, m_capacity; - mz_uint8 *m_pBuf; - mz_bool m_expandable; -} tdefl_output_buffer; + /* Central directory file index. */ + mz_uint32 m_file_index; -static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) -{ - tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; - size_t new_size = p->m_size + len; - if (new_size > p->m_capacity) - { - size_t new_capacity = p->m_capacity; - mz_uint8 *pNew_buf; - if (!p->m_expandable) - return MZ_FALSE; - do - { - new_capacity = MZ_MAX(128U, new_capacity << 1U); - } while (new_size > new_capacity); - pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); - if (!pNew_buf) - return MZ_FALSE; - p->m_pBuf = pNew_buf; - p->m_capacity = new_capacity; - } - memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); - p->m_size = new_size; - return MZ_TRUE; -} + /* 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. */ + mz_uint64 m_central_dir_ofs; -void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tdefl_output_buffer out_buf; - MZ_CLEAR_OBJ(out_buf); - if (!pOut_len) - return MZ_FALSE; - else - *pOut_len = 0; - out_buf.m_expandable = MZ_TRUE; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) - return NULL; - *pOut_len = out_buf.m_size; - return out_buf.m_pBuf; -} + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; -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) -{ - tdefl_output_buffer out_buf; - MZ_CLEAR_OBJ(out_buf); - if (!pOut_buf) - return 0; - out_buf.m_pBuf = (mz_uint8 *)pOut_buf; - out_buf.m_capacity = out_buf_len; - if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) - return 0; - return out_buf.m_size; -} + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; -static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + /* File's compressed size. */ + mz_uint64 m_comp_size; -/* 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). */ -mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) -{ - mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); - if (window_bits > 0) - comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + /* 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. */ + mz_uint64 m_uncomp_size; - if (!level) - comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; - else if (strategy == MZ_FILTERED) - comp_flags |= TDEFL_FILTER_MATCHES; - else if (strategy == MZ_HUFFMAN_ONLY) - comp_flags &= ~TDEFL_MAX_PROBES_MASK; - else if (strategy == MZ_FIXED) - comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; - else if (strategy == MZ_RLE) - comp_flags |= TDEFL_RLE_MATCHES; + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; - return comp_flags; -} + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ -#endif + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; -/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at - http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. - This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ -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) -{ - /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ - static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); - tdefl_output_buffer out_buf; - int i, bpl = w * num_chans, y, z; - mz_uint32 c; - *pLen_out = 0; - if (!pComp) - return NULL; - MZ_CLEAR_OBJ(out_buf); - out_buf.m_expandable = MZ_TRUE; - out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); - if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) - { - MZ_FREE(pComp); - return NULL; - } - /* write dummy header */ - for (z = 41; z; --z) - tdefl_output_buffer_putter(&z, 1, &out_buf); - /* compress image data */ - tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); - for (y = 0; y < h; ++y) - { - tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); - tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); - } - if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) - { - MZ_FREE(pComp); - MZ_FREE(out_buf.m_pBuf); - return NULL; - } - /* write real header */ - *pLen_out = out_buf.m_size - 41; - { - static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; - mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, - 0x0a, 0x1a, 0x0a, 0x00, 0x00, - 0x00, 0x0d, 0x49, 0x48, 0x44, - 0x52, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x49, 0x44, 0x41, - 0x54 }; - pnghdr[18] = (mz_uint8)(w >> 8); - pnghdr[19] = (mz_uint8)w; - pnghdr[22] = (mz_uint8)(h >> 8); - pnghdr[23] = (mz_uint8)h; - pnghdr[25] = chans[num_chans]; - pnghdr[33] = (mz_uint8)(*pLen_out >> 24); - pnghdr[34] = (mz_uint8)(*pLen_out >> 16); - pnghdr[35] = (mz_uint8)(*pLen_out >> 8); - pnghdr[36] = (mz_uint8)*pLen_out; - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); - for (i = 0; i < 4; ++i, c <<= 8) - ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); - memcpy(out_buf.m_pBuf, pnghdr, 41); - } - /* write footer (IDAT CRC-32, followed by IEND chunk) */ - if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) - { - *pLen_out = 0; - MZ_FREE(pComp); - MZ_FREE(out_buf.m_pBuf); - return NULL; - } - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); - for (i = 0; i < 4; ++i, c <<= 8) - (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); - /* compute final size of file, grab compressed data buffer and return */ - *pLen_out += 57; - MZ_FREE(pComp); - return out_buf.m_pBuf; -} -void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) -{ - /* 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) */ - return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); -} + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; -#ifndef MINIZ_NO_MALLOC -/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ -/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ -/* structure size and allocation mechanism. */ -tdefl_compressor *tdefl_compressor_alloc( void ) -{ - return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); -} + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; -void tdefl_compressor_free(tdefl_compressor *pComp) -{ - MZ_FREE(pComp); -} -#endif + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; -#ifdef _MSC_VER -#pragma warning(pop) -#endif + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; -#ifdef __cplusplus -} -#endif - /************************************************************************** - * - * Copyright 2013-2014 RAD Game Tools and Valve Software - * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC - * All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - **************************************************************************/ + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; - - -#ifdef __cplusplus -extern "C" { -#endif - -/* ------------------- Low-level Decompression (completely independent from all compression API's) */ - -#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) -#define TINFL_MEMSET(p, c, l) memset(p, c, l) - -#define TINFL_CR_BEGIN \ - switch (r->m_state) \ - { \ - case 0: -#define TINFL_CR_RETURN(state_index, result) \ - do \ - { \ - status = result; \ - r->m_state = state_index; \ - goto common_exit; \ - case state_index:; \ - } \ - MZ_MACRO_END -#define TINFL_CR_RETURN_FOREVER(state_index, result) \ - do \ - { \ - for (;;) \ - { \ - TINFL_CR_RETURN(state_index, result); \ - } \ - } \ - MZ_MACRO_END -#define TINFL_CR_FINISH } - -#define TINFL_GET_BYTE(state_index, c) \ - do \ - { \ - while (pIn_buf_cur >= pIn_buf_end) \ - { \ - TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ - } \ - c = *pIn_buf_cur++; \ - } \ - MZ_MACRO_END - -#define TINFL_NEED_BITS(state_index, n) \ - do \ - { \ - mz_uint c; \ - TINFL_GET_BYTE(state_index, c); \ - bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ - num_bits += 8; \ - } while (num_bits < (mz_uint)(n)) -#define TINFL_SKIP_BITS(state_index, n) \ - do \ - { \ - if (num_bits < (mz_uint)(n)) \ - { \ - TINFL_NEED_BITS(state_index, n); \ - } \ - bit_buf >>= (n); \ - num_bits -= (n); \ - } \ - MZ_MACRO_END -#define TINFL_GET_BITS(state_index, b, n) \ - do \ - { \ - if (num_bits < (mz_uint)(n)) \ - { \ - TINFL_NEED_BITS(state_index, n); \ - } \ - b = bit_buf & ((1 << (n)) - 1); \ - bit_buf >>= (n); \ - num_bits -= (n); \ - } \ - MZ_MACRO_END - -/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ -/* 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 */ -/* 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 */ -/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ - do \ - { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ - if (temp >= 0) \ - { \ - code_len = temp >> 9; \ - if ((code_len) && (num_bits >= code_len)) \ - break; \ - } \ - else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ - { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do \ - { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while ((temp < 0) && (num_bits >= (code_len + 1))); \ - if (temp >= 0) \ - break; \ - } \ - TINFL_GET_BYTE(state_index, c); \ - bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ - num_bits += 8; \ - } while (num_bits < 15); - -/* 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 */ -/* 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 */ -/* 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. */ -/* The slow path is only executed at the very end of the input buffer. */ -/* 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 */ -/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ - do \ - { \ - int temp; \ - mz_uint code_len, c; \ - if (num_bits < 15) \ - { \ - if ((pIn_buf_end - pIn_buf_cur) < 2) \ - { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ - } \ - else \ - { \ - bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ - pIn_buf_cur += 2; \ - num_bits += 16; \ - } \ - } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ - code_len = temp >> 9, temp &= 511; \ - else \ - { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do \ - { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while (temp < 0); \ - } \ - sym = temp; \ - bit_buf >>= code_len; \ - num_bits -= code_len; \ - } \ - MZ_MACRO_END - -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) -{ - static const int 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 }; - static const int 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 }; - static const int 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 }; - static const int 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 }; - 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 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; - - tinfl_status status = TINFL_STATUS_FAILED; - mz_uint32 num_bits, dist, counter, num_extra; - tinfl_bit_buf_t bit_buf; - const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; - 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; - - /* 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). */ - if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) - { - *pIn_buf_size = *pOut_buf_size = 0; - return TINFL_STATUS_BAD_PARAM; - } - - num_bits = r->m_num_bits; - bit_buf = r->m_bit_buf; - dist = r->m_dist; - counter = r->m_counter; - num_extra = r->m_num_extra; - dist_from_out_buf_start = r->m_dist_from_out_buf_start; - TINFL_CR_BEGIN - - bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; - r->m_z_adler32 = r->m_check_adler32 = 1; - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_GET_BYTE(1, r->m_zhdr0); - TINFL_GET_BYTE(2, r->m_zhdr1); - counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); - if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); - if (counter) - { - TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); - } - } - - do - { - TINFL_GET_BITS(3, r->m_final, 3); - r->m_type = r->m_final >> 1; - if (r->m_type == 0) - { - TINFL_SKIP_BITS(5, num_bits & 7); - for (counter = 0; counter < 4; ++counter) - { - if (num_bits) - TINFL_GET_BITS(6, r->m_raw_header[counter], 8); - else - TINFL_GET_BYTE(7, r->m_raw_header[counter]); - } - 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)))) - { - TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); - } - while ((counter) && (num_bits)) - { - TINFL_GET_BITS(51, dist, 8); - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = (mz_uint8)dist; - counter--; - } - while (counter) - { - size_t n; - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); - } - while (pIn_buf_cur >= pIn_buf_end) - { - TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); - } - n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); - TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); - pIn_buf_cur += n; - pOut_buf_cur += n; - counter -= (mz_uint)n; - } - } - else if (r->m_type == 3) - { - TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); - } - else - { - if (r->m_type == 1) - { - mz_uint8 *p = r->m_tables[0].m_code_size; - mz_uint i; - r->m_table_sizes[0] = 288; - r->m_table_sizes[1] = 32; - TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); - for (i = 0; i <= 143; ++i) - *p++ = 8; - for (; i <= 255; ++i) - *p++ = 9; - for (; i <= 279; ++i) - *p++ = 7; - for (; i <= 287; ++i) - *p++ = 8; - } - else - { - for (counter = 0; counter < 3; counter++) - { - TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); - r->m_table_sizes[counter] += s_min_table_sizes[counter]; - } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); - for (counter = 0; counter < r->m_table_sizes[2]; counter++) - { - mz_uint s; - TINFL_GET_BITS(14, s, 3); - r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; - } - r->m_table_sizes[2] = 19; - } - for (; (int)r->m_type >= 0; r->m_type--) - { - int tree_next, tree_cur; - tinfl_huff_table *pTable; - mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; - pTable = &r->m_tables[r->m_type]; - MZ_CLEAR_OBJ(total_syms); - MZ_CLEAR_OBJ(pTable->m_look_up); - MZ_CLEAR_OBJ(pTable->m_tree); - for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) - total_syms[pTable->m_code_size[i]]++; - used_syms = 0, total = 0; - next_code[0] = next_code[1] = 0; - for (i = 1; i <= 15; ++i) - { - used_syms += total_syms[i]; - next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); - } - if ((65536 != total) && (used_syms > 1)) - { - TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); - } - for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) - { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; - if (!code_size) - continue; - cur_code = next_code[code_size]++; - for (l = code_size; l > 0; l--, cur_code >>= 1) - rev_code = (rev_code << 1) | (cur_code & 1); - if (code_size <= TINFL_FAST_LOOKUP_BITS) - { - mz_int16 k = (mz_int16)((code_size << 9) | sym_index); - while (rev_code < TINFL_FAST_LOOKUP_SIZE) - { - pTable->m_look_up[rev_code] = k; - rev_code += (1 << code_size); - } - continue; - } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) - { - pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; - tree_cur = tree_next; - tree_next -= 2; - } - rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); - for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) - { - tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) - { - pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; - tree_cur = tree_next; - tree_next -= 2; - } - else - tree_cur = pTable->m_tree[-tree_cur - 1]; - } - tree_cur -= ((rev_code >>= 1) & 1); - pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; - } - if (r->m_type == 2) - { - for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) - { - mz_uint s; - TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); - if (dist < 16) - { - r->m_len_codes[counter++] = (mz_uint8)dist; - continue; - } - if ((dist == 16) && (!counter)) - { - TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); - } - num_extra = "\02\03\07"[dist - 16]; - TINFL_GET_BITS(18, s, num_extra); - s += "\03\03\013"[dist - 16]; - TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); - counter += s; - } - if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) - { - TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); - } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); - TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); - } - } - for (;;) - { - mz_uint8 *pSrc; - for (;;) - { - if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) - { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); - if (counter >= 256) - break; - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = (mz_uint8)counter; - } - else - { - int sym2; - mz_uint code_len; -#if TINFL_USE_64BIT_BITBUF - if (num_bits < 30) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 4; - num_bits += 32; - } +#ifdef MINIZ_NO_TIME + MZ_TIME_T m_padding; #else - if (num_bits < 15) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 2; - num_bits += 16; - } + MZ_TIME_T m_time; #endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; - do - { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; - } while (sym2 < 0); - } - counter = sym2; - bit_buf >>= code_len; - num_bits -= code_len; - if (counter & 256) - break; +} mz_zip_archive_file_stat; -#if !TINFL_USE_64BIT_BITBUF - if (num_bits < 15) - { - bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); - pIn_buf_cur += 2; - num_bits += 16; - } -#endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; - do - { - sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; - } while (sym2 < 0); - } - bit_buf >>= code_len; - num_bits -= code_len; +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); - pOut_buf_cur[0] = (mz_uint8)counter; - if (sym2 & 256) - { - pOut_buf_cur++; - counter = sym2; - break; - } - pOut_buf_cur[1] = (mz_uint8)sym2; - pOut_buf_cur += 2; - } - } - if ((counter &= 511) == 256) - break; +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; - num_extra = s_length_extra[counter - 257]; - counter = s_length_base[counter - 257]; - if (num_extra) - { - mz_uint extra_bits; - TINFL_GET_BITS(25, extra_bits, num_extra); - counter += extra_bits; - } +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); - num_extra = s_dist_extra[dist]; - dist = s_dist_base[dist]; - if (num_extra) - { - mz_uint extra_bits; - TINFL_GET_BITS(27, extra_bits, num_extra); - dist += extra_bits; - } +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + 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) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + 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 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, + /*After adding a compressed file, seek back + to local file header and set the correct sizes*/ + MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 +} mz_zip_flags; - dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - { - TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); - } +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; - pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; - if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) - { - while (counter--) - { - while (pOut_buf_cur >= pOut_buf_end) - { - TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); - } - *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; - } - continue; - } -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES - else if ((counter >= 9) && (counter <= dist)) - { - const mz_uint8 *pSrc_end = pSrc + (counter & ~7); - do - { -#ifdef MINIZ_UNALIGNED_USE_MEMCPY - memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; + + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +#ifdef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint padding; #else - ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; - ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + mz_uint file_crc32; #endif - pOut_buf_cur += 8; - } while ((pSrc += 8) < pSrc_end); - if ((counter &= 7) < 3) - { - if (counter) - { - pOut_buf_cur[0] = pSrc[0]; - if (counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - continue; - } - } + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* 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. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +MINIZ_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); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* 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. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); #endif - while(counter>2) - { - pOut_buf_cur[0] = pSrc[0]; - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur[2] = pSrc[2]; - pOut_buf_cur += 3; - pSrc += 3; - counter -= 3; - } - if (counter > 0) - { - pOut_buf_cur[0] = pSrc[0]; - if (counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - } - } - } while (!(r->m_final & 1)); - /* 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. */ - /* 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. */ - TINFL_SKIP_BITS(32, num_bits & 7); - while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) - { - --pIn_buf_cur; - num_bits -= 8; - } - bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); - 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). */ +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - for (counter = 0; counter < 4; ++counter) - { - mz_uint s; - if (num_bits) - TINFL_GET_BITS(41, s, 8); - else - TINFL_GET_BYTE(42, s); - r->m_z_adler32 = (r->m_z_adler32 << 8) | s; - } - } - TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); +/* -------- ZIP reading or writing */ - TINFL_CR_FINISH +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); -common_exit: - /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ - /* 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. */ - /* 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. */ - if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) - { - while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) - { - --pIn_buf_cur; - num_bits -= 8; - } - } - r->m_num_bits = num_bits; - r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); - r->m_dist = dist; - r->m_counter = counter; - r->m_num_extra = num_extra; - r->m_dist_from_out_buf_start = dist_from_out_buf_start; - *pIn_buf_size = pIn_buf_cur - pIn_buf_next; - *pOut_buf_size = pOut_buf_cur - pOut_buf_next; - if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) - { - const mz_uint8 *ptr = pOut_buf_next; - size_t buf_len = *pOut_buf_size; - mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; - size_t block_len = buf_len % 5552; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; - s1 += ptr[1], s2 += s1; - s1 += ptr[2], s2 += s1; - s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; - s1 += ptr[5], s2 += s1; - s1 += ptr[6], s2 += s1; - s1 += ptr[7], s2 += s1; - } - for (; i < block_len; ++i) - s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; - buf_len -= block_len; - block_len = 5552; - } - r->m_check_adler32 = (s2 << 16) + s1; - if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) - status = TINFL_STATUS_ADLER32_MISMATCH; - } - return status; -} +MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); -/* Higher level helper functions. */ -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tinfl_decompressor decomp; - void *pBuf = NULL, *pNew_buf; - size_t src_buf_ofs = 0, out_buf_capacity = 0; - *pOut_len = 0; - tinfl_init(&decomp); - for (;;) - { - size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; - 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, - (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) - { - MZ_FREE(pBuf); - *pOut_len = 0; - return NULL; - } - src_buf_ofs += src_buf_size; - *pOut_len += dst_buf_size; - if (status == TINFL_STATUS_DONE) - break; - new_out_buf_capacity = out_buf_capacity * 2; - if (new_out_buf_capacity < 128) - new_out_buf_capacity = 128; - pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); - if (!pNew_buf) - { - MZ_FREE(pBuf); - *pOut_len = 0; - return NULL; - } - pBuf = pNew_buf; - out_buf_capacity = new_out_buf_capacity; - } - return pBuf; -} +/* Returns the total number of files in the archive. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); -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) -{ - tinfl_decompressor decomp; - tinfl_status status; - tinfl_init(&decomp); - 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); - return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; -} +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); -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) -{ - int result = 0; - tinfl_decompressor decomp; - mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); - size_t in_buf_ofs = 0, dict_ofs = 0; - if (!pDict) - return TINFL_STATUS_FAILED; - tinfl_init(&decomp); - for (;;) - { - size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, - (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); - in_buf_ofs += in_buf_size; - if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) - break; - if (status != TINFL_STATUS_HAS_MORE_OUTPUT) - { - result = (status == TINFL_STATUS_DONE); - break; - } - dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); - } - MZ_FREE(pDict); - *pIn_buf_size = in_buf_ofs; - return result; -} +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); -#ifndef MINIZ_NO_MALLOC -tinfl_decompressor *tinfl_decompressor_alloc( void ) -{ - tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); - if (pDecomp) - tinfl_init(pDecomp); - return pDecomp; -} +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); -void tinfl_decompressor_free(tinfl_decompressor *pDecomp) -{ - MZ_FREE(pDecomp); -} +/* MZ_TRUE if the archive file entry is a directory entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* 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. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +MINIZ_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); + +/* Returns detailed information about an archive file entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* 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. */ +MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +MINIZ_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); +MINIZ_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); + +/* Extracts a archive file to a memory buffer. */ +MINIZ_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); +MINIZ_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); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +MINIZ_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); +MINIZ_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); + +/* Extract a file iteratively */ +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +MINIZ_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); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); #endif +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + mz_uint64 mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_uint64 mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, mz_uint64 new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* 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. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +#ifndef MINIZ_NO_STDIO +MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); +#endif + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +MINIZ_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); + +#ifndef MINIZ_NO_STDIO +MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +MINIZ_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); +MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* 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. */ +/* 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). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* 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. */ +MINIZ_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); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +MINIZ_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, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +MINIZ_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, + 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, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ +/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ +MINIZ_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, + 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, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* 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. */ +MINIZ_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); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +MINIZ_EXPORT mz_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, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* 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. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a pointer to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* 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). */ +/* 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. */ +/* 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. */ +MINIZ_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); +MINIZ_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); + +#ifndef MINIZ_NO_STDIO +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +MINIZ_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); +#endif + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + #ifdef __cplusplus } #endif -#endif // MINIZ_HEADER_FILE_ONLY +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/public/tests/test_atoi.c b/public/tests/test_atoi.c new file mode 100644 index 00000000..3f1b4393 --- /dev/null +++ b/public/tests/test_atoi.c @@ -0,0 +1,108 @@ +#include "crtlib.h" +#include "xash3d_mathlib.h" + +static int Test_Atoi( void ) +{ + struct { + const char *str; + int result; + } test_data[] = { + { "", 0 }, + { " 123", 123 }, + { "-123", -123 }, + { "0xa1ba", 0xa1ba }, + { "-0xa1ba", -0xa1ba }, + { "0XA1BA", 0xa1ba }, + { "-0XA1BA", -0xa1ba }, + { "'a'", (byte)'a' }, + { "-'c'", - ((byte)'c') }, + }; + int i; + + for( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ ) + { + if( Q_atoi( test_data[i].str ) != test_data[i].result ) + return i + 1; + } + + return 0; +} + +static int Test_Atof( void ) +{ + struct { + const char *str; + float result; + } test_data[] = { + { "", 0 }, + { " 123.123", 123.123 }, + { "-123.13 ", -123.13 }, + { "0xa1ba", 0xa1ba }, + { "-0xa1ba", -0xa1ba }, + { "0XA1BA", 0xa1ba }, + { "-0XA1BA", -0xa1ba }, + { "'a'", (byte)'a' }, + { "-'c'", - ((byte)'c') } + }; + int i; + + for( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ ) + { + float result = Q_atof( test_data[i].str ); + + if( !Q_equal( result, test_data[i].result )) + return i + 1; + } + + return 0; +} + +static int Test_Atov( void ) +{ + struct { + const char *str; + int N; + float result[3]; + } test_data[] = { + { "1.0 1.2 3", 3, { 1.0f, 1.2f, 3.0f }}, + { "1.234 1.32", 2, { 1.234f, 1.32f }}, + }; + int i; + + for( i = 0; i < sizeof( test_data ) / sizeof( test_data[0] ); i++ ) + { + float result[3]; + int j; + + memset( result, 0, sizeof( result )); + Q_atov( result, test_data[i].str, test_data[i].N ); + + for( j = 0; j < 3; j++ ) // check that Q_atov didn't parsed more than requested + { + if( !Q_equal( result[j], test_data[i].result[j] )) + return i * 4 + j + 1; + } + } + + return 0; +} + +int main( void ) +{ + int ret = Test_Atoi(); + + if( ret > 0 ) + return ret; + + ret = Test_Atof(); + + if( ret > 0 ) + return ret + 32; + + ret = Test_Atov(); + + if( ret > 0 ) + return ret + 64; + + return 0; +} diff --git a/public/tests/test_build.c b/public/tests/test_build.c new file mode 100644 index 00000000..edcabee9 --- /dev/null +++ b/public/tests/test_build.c @@ -0,0 +1,188 @@ +#include +#include "build.h" +#include "buildenums.h" +#include "crtlib.h" + +// THESE DEFINITIONS MUST NEVER CHANGE +static struct +{ + int id; + const char *string; +} expected_platforms[] = +{ +{ PLATFORM_WIN32, "win32" }, +{ PLATFORM_ANDROID, "android" }, +{ PLATFORM_LINUX, "linux" }, +{ PLATFORM_APPLE, "apple" }, +{ PLATFORM_FREEBSD, "freebsd" }, +{ PLATFORM_NETBSD, "netbsd" }, +{ PLATFORM_OPENBSD, "openbsd" }, +{ PLATFORM_EMSCRIPTEN, "emscripten" }, +{ PLATFORM_DOS4GW, "DOS4GW" }, +{ PLATFORM_HAIKU, "haiku" }, +{ PLATFORM_SERENITY, "serenity" }, +{ PLATFORM_IRIX, "irix" }, +{ PLATFORM_NSWITCH, "nswitch" }, +{ PLATFORM_PSVITA, "psvita" }, +{ PLATFORM_WASI, "wasi" }, +{ PLATFORM_HURD, "hurd" }, +}; + +static struct +{ + int id; + int abi; + int endianness; + int is64; + const char *string; +} expected_architectures[] = +{ +{ ARCHITECTURE_AMD64, 0, -1, -1, "amd64" }, +{ ARCHITECTURE_X86, 0, -1, -1, "i386" }, +{ ARCHITECTURE_E2K, 0, -1, -1, "e2k" }, +{ ARCHITECTURE_JS, 0, -1, -1, "javascript" }, + +// all possible WebAssembly names +{ ARCHITECTURE_WASM, 0, -1, true, "wasm64" }, +{ ARCHITECTURE_WASM, 0, -1, false, "wasm32" }, + +// all possible MIPS names +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG, true, "mips64" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG, false, "mips" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, true, "mips64el" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, false, "mipsel" }, + +// all possible PowerPC names +{ ARCHITECTURE_PPC, 0, ENDIANNESS_BIG, true, "ppc64" }, +{ ARCHITECTURE_PPC, 0, ENDIANNESS_BIG, false, "ppc" }, +{ ARCHITECTURE_PPC, 0, ENDIANNESS_LITTLE, true, "ppc64el" }, +{ ARCHITECTURE_PPC, 0, ENDIANNESS_LITTLE, false, "ppcel" }, + +// All ARM is little endian only (for now?) +// Arm64 is always arm64, no matter the version (for now) +// Arm64 don't care about hardfp bit +{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 8, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 0 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 0, ENDIANNESS_LITTLE, true, "arm64" }, + +// ARMv6 and below don't care about hardfp bit +{ ARCHITECTURE_ARM, 4 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv4l" }, +{ ARCHITECTURE_ARM, 4, ENDIANNESS_LITTLE, false, "armv4l" }, +{ ARCHITECTURE_ARM, 5 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv5l" }, +{ ARCHITECTURE_ARM, 5, ENDIANNESS_LITTLE, false, "armv5l" }, +{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6, ENDIANNESS_LITTLE, false, "armv6l" }, + +// ARMv7 and ARMv8 in 32-bit mode, hardfp bit is important +{ ARCHITECTURE_ARM, 7 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv7hf" }, +{ ARCHITECTURE_ARM, 7, ENDIANNESS_LITTLE, false, "armv7l" }, +{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv8_32hf" }, +{ ARCHITECTURE_ARM, 8, ENDIANNESS_LITTLE, false, "armv8_32l" }, + +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT, -1, true, "riscv64" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT, -1, false, "riscv32" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, true, "riscv64f" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, false, "riscv32f" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, true, "riscv64d" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, false, "riscv32d" }, +}; + +static int TestPlatformString( void ) +{ + int i; + + for( i = 0; i < sizeof( expected_platforms ) / sizeof( expected_platforms[0] ); i++ ) + { + if( Q_strcmp( Q_PlatformStringByID( expected_platforms[i].id ), expected_platforms[i].string )) + return i + 1; + } + + return 0; +} + +static qboolean TestArchitectureString_NoEndianness( int id, int abi, int is64, const char *string ) +{ + return !Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_LITTLE, is64 ), string ) && + !Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_BIG, is64 ), string ); +} + +static qboolean TestArchitectureString_No64( int id, int abi, int endianness, const char *string ) +{ + if( endianness == -1 ) + { + return TestArchitectureString_NoEndianness( id, abi, false, string ) && + TestArchitectureString_NoEndianness( id, abi, true, string ); + } + + return !Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, true ), string ) && + !Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, false ), string ); +} + +static int TestArchitectureString( void ) +{ + int i; + + for( i = 0; i < sizeof( expected_architectures ) / sizeof( expected_architectures[0] ); i++ ) + { + if( expected_architectures[i].is64 == -1 ) + { + if( !TestArchitectureString_No64( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].endianness, + expected_architectures[i].string )) + return i + 1; + } + else if( expected_architectures[i].endianness == -1 ) + { + if( !TestArchitectureString_NoEndianness( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].is64, + expected_architectures[i].string )) + { + abort(); + return i + 1; + } + } + else + { + if( Q_strcmp( Q_ArchitectureStringByID( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].endianness, + expected_architectures[i].is64 + ), expected_architectures[i].string )) + return i + 1; + } + } + + return 0; +} + +int main( void ) +{ + int res; + + res = TestPlatformString(); + if( res != 0 ) + return res; + + res = TestArchitectureString(); + if( res != 0 ) + return res + 100; + + if( Q_buildnum_compat() != 4529 ) + return 200; + + if( Q_buildnum_iso( "2015-04-02 21:19:10 +0300" ) != 1 ) + return 202; + + if( Q_buildnum_iso( "2023-04-17 21:19:10 +0300" ) != 2938 ) + return 204; + + return EXIT_SUCCESS; +} diff --git a/public/tests/test_efp.c b/public/tests/test_efp.c new file mode 100644 index 00000000..182104f4 --- /dev/null +++ b/public/tests/test_efp.c @@ -0,0 +1,40 @@ +#include +#include "crtlib.h" +#include + +static int Test_ExtractFilePath( void ) +{ + char dst[64]; + const char *strings[] = + { + "dir/file", "dir", + "bark\\meow", "bark", + "nopath", "", + "knee/deep/in/paths", "knee/deep/in", + // yes, it removes the behavior/ even if it might be technically a directory + "keep/the/original/func/behavior/", "keep/the/original/func", + "backslashes\\are\\annoying\\af", "backslashes\\are\\annoying", + "", "" + }; + size_t i; + + for( i = 0; i < sizeof( strings ) / sizeof( strings[0] ); i += 2 ) + { + COM_ExtractFilePath( strings[i], dst ); + if( Q_strcmp( dst, strings[i+1] )) + { + printf( "%s %s %s\n", strings[i], strings[i+1], dst ); + return (i >> 1) + 1; + } + } + + return 0; +} + +int main( void ) +{ + if( Test_ExtractFilePath( )) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/public/tests/test_filebase.c b/public/tests/test_filebase.c new file mode 100644 index 00000000..89fee044 --- /dev/null +++ b/public/tests/test_filebase.c @@ -0,0 +1,112 @@ +#include +#include +#include "xash3d_mathlib.h" +#include "crtlib.h" + +static void COM_FileBase_old( const char *in, char *out, size_t size ) +{ + int len, start, end; + + len = Q_strlen( in ); + if( !len || !size ) return; + + // scan backward for '.' + end = len - 1; + + while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if( in[end] != '.' ) + end = len-1; // no '.', copy to end + else end--; // found ',', copy to left of '.' + + // scan backward for '/' + start = len - 1; + + while( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) + start = 0; + else start++; + + // length of new sting + len = end - start + 1; + + if( size > len ) // patched to somewhat support size limit + { + // Copy partial string + Q_strncpy( out, &in[start], len + 1 ); + out[len] = 0; + } + else + { + Q_strncpy( out, &in[start], size ); + } +} + +#define COM_FILEBASE_CHECK( in, out, size, expected ) \ + COM_FileBase( in, out, size ); \ + if( Q_strcmp( out, expected )) \ + { \ + printf( "%d: fail with libpublic impl, got: %s, expected: %s\n", __LINE__, out, expected ); \ + return 1; \ + } \ + COM_FileBase_old( in, out, size ); \ + if( Q_strcmp( out, expected )) \ + { \ + printf( "%d: fail with old impl, got: %s, expected: %s\n", __LINE__, out, expected ); \ + return 2; \ + } + +static int Test_FileBase( void ) +{ + string s; + + // test slashless case + COM_FILEBASE_CHECK( "asdf", s, sizeof( s ), "asdf" ); + + // test slashless case with extension + COM_FILEBASE_CHECK( "sdf.wad", s, sizeof( s ), "sdf" ); + + // test case with one slash + COM_FILEBASE_CHECK( "zxcv/asdfqwer", s, sizeof( s ), "asdfqwer" ); + + // test case with multiple slashes + COM_FILEBASE_CHECK( "zxc/asd/qwert", s, sizeof( s ), "qwert" ); + + // test case with full path + COM_FILEBASE_CHECK( "zxc/asd/qvert.lkjefgkljh", s, sizeof( s ), "qvert" ); + + // test case where dot placed before last slash + COM_FILEBASE_CHECK( "pak0.pk3/texture", s, sizeof( s ), "texture" ); + + // test case of directory path + COM_FILEBASE_CHECK( "pak0.pk3/", s, sizeof( s ), "" ); + + // test case of file with no name + COM_FILEBASE_CHECK( "blep/.nomedia", s, sizeof( s ), "" ); + + // test idiot cases + COM_FILEBASE_CHECK( NULL, s, sizeof( s ), "" ); + COM_FILEBASE_CHECK( "", s, sizeof( s ), "" ); + COM_FILEBASE_CHECK( "jhnwrgkujihrgwfikouj", s, 0, "" ); + + // test length limit + COM_FILEBASE_CHECK( "qwertyuiop", s, 3, "qw" ); + COM_FILEBASE_CHECK( "qwertyuiop", s, 1, "" ); + + return 0; +} + +int main( void ) +{ + int ret; + + ret = Test_FileBase(); + + if( ret ) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/public/tests/test_fileext.c b/public/tests/test_fileext.c new file mode 100644 index 00000000..cac0021b --- /dev/null +++ b/public/tests/test_fileext.c @@ -0,0 +1,25 @@ +#include "crtlib.h" + +int main( int argc, char **argv ) +{ + int i; + const char *strings[] = + { + "test.txt", "txt", + "path/to/file.wad", "wad", + "noext", "", + "dir/", "", + "dir.pk3dir/", "", + "https.proto://is_this_an_url?", "", + "inside.wad/AAATRIGGER", "", + "c:/games/gamedir/liblist.cum", "cum", + }; + + for( i = 0; i < sizeof( strings ) / sizeof( strings[0] ); i += 2 ) + { + if( Q_strcmp( COM_FileExtension( strings[i] ), strings[i + 1] )) + return i; + } + + return 0; +} diff --git a/public/tests/test_parsefile.c b/public/tests/test_parsefile.c new file mode 100644 index 00000000..a1201743 --- /dev/null +++ b/public/tests/test_parsefile.c @@ -0,0 +1,52 @@ +#include "crtlib.h" + +static const char *test_file = +"q asdf \"qwerty\" \"f \\\"f\" meowmeow\n" +"// comment \"stuff ignored\"\n" +"bark // ignore\n" +"bashlikecomment #notignored test\n" +"#ignore comment\n" +"thisshall #be ignored\n" +"test_sentinel\n"; + +int main( void ) +{ + int i; + char *file = (char *)test_file; + struct test + { + int bufsize; + const char *expected; + int expected_len; + int flags; + } testdata[] = + { + { 5, "q", 1 }, + { 5, "asdf", 4 }, + { 5, "qwer", -1 }, + { 5, "f \"f", 4 }, + { 5, "meow", -1 }, + { 5, "bark", 4 }, + { 32, "bashlikecomment", 15 }, + { 32, "#notignored", 11 }, + { 32, "test", 4, PFILE_IGNOREHASHCMT }, + { 32, "thisshall", 9, PFILE_IGNOREHASHCMT }, + { 32, "test_sentinel", 13, PFILE_IGNOREHASHCMT }, + }; + + for( i = 0; i < sizeof( testdata ) / sizeof( testdata[0] ); i++ ) + { + string buf; + int len; + + file = COM_ParseFileSafe( file, buf, testdata[i].bufsize, testdata[i].flags, &len, NULL ); + + if( file == NULL ) + return i; + + if( !( !Q_strcmp( buf, testdata[i].expected ) && len == testdata[i].expected_len )) + return i; + } + + return 0; +} diff --git a/public/tests/test_strings.c b/public/tests/test_strings.c new file mode 100644 index 00000000..1d99b1e5 --- /dev/null +++ b/public/tests/test_strings.c @@ -0,0 +1,91 @@ +#include "crtlib.h" + +static int Test_Strcpycatcmp( void ) +{ + char buf[] = "meowmeowmeow", buf2[] = "barkbark"; + char dst[64]; + + if( Q_strncpy( dst, buf, sizeof( dst )) != sizeof( buf ) - 1 ) + return 1; + + if( Q_strcmp( dst, buf )) + return 2; + + if( Q_strcmp( dst, buf )) + return 4; + + if( !Q_strcmp( dst, buf2 )) + return 5; + + if( Q_strncat( dst, buf2, sizeof( dst )) != sizeof( buf ) + sizeof( buf2 ) - 2 ) + return 6; + + if( Q_strcmp( dst, "meowmeowmeowbarkbark" )) + return 7; + + if( Q_strncmp( dst, buf, sizeof( buf ) - 1 )) + return 8; + + return 0; +} + +static int Test_Strnlwr( void ) +{ + string s; + + Q_strnlwr( "ASDFGKJ", s, sizeof( s )); + + if( Q_strcmp( s, "asdfgkj" )) + return 1; + + Q_strnlwr( "qwertyuiop", s, sizeof( s )); + + if( Q_strcmp( s, "qwertyuiop" )) + return 2; + + return 0; +} + +static int Test_FixSlashes( void ) +{ + string s = "path\\with\\back\\slashes"; + string s2 = "path/with/fwd/slashes"; + string s3 = "path\\with/mixed\\slashes"; + + COM_FixSlashes( s ); + + if( Q_strcmp( s, "path/with/back/slashes" )) + return 1; + + COM_FixSlashes( s2 ); + + if( Q_strcmp( s2, "path/with/fwd/slashes" )) + return 2; + + COM_FixSlashes( s3 ); + + if( Q_strcmp( s3, "path/with/mixed/slashes" )) + return 3; + + return 0; +} + +int main( void ) +{ + int ret = Test_Strcpycatcmp(); + + if( ret > 0 ) + return ret; + + ret = Test_Strnlwr(); + + if( ret > 0 ) + return ret + 16; + + ret = Test_FixSlashes(); + + if( ret > 0 ) + return ret + 48; + + return 0; +} diff --git a/public/tests/test_validate_target.c b/public/tests/test_validate_target.c new file mode 100644 index 00000000..e3a645be --- /dev/null +++ b/public/tests/test_validate_target.c @@ -0,0 +1,21 @@ +#include +#include "build.h" +#include "buildenums.h" +#include "crtlib.h" + +#ifndef VALIDATE_TARGET +#error +#endif + +int main( void ) +{ + string buf; + + if( Q_snprintf( buf, sizeof( buf ), "%s-%s", Q_buildos( ), Q_buildarch( )) < 0 ) + return 1; + + if( Q_strcmp( buf, VALIDATE_TARGET ) != 0 ) + return 2; + + return EXIT_SUCCESS; +} diff --git a/public/utflib.c b/public/utflib.c new file mode 100644 index 00000000..74652b70 --- /dev/null +++ b/public/utflib.c @@ -0,0 +1,242 @@ +/* +utflib.c - small unicode conversion library +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#include "utflib.h" +#include "xash3d_types.h" + +uint32_t Q_DecodeUTF8( utfstate_t *s, uint32_t in ) +{ + // get character length + if( s->len == 0 ) + { + // init state + s->uc = 0; + + // expect ASCII symbols by default + if( likely( in <= 0x7fu )) + return in; + + // invalid sequence + if( unlikely( in >= 0xf8u )) + return 0; + + s->k = 0; + + if( in >= 0xf0u ) + { + s->uc = in & 0x07u; + s->len = 3; + } + else if( in >= 0xe0u ) + { + s->uc = in & 0x0fu; + s->len = 2; + } + else if( in >= 0xc0u ) + { + s->uc = in & 0x1fu; + s->len = 1; + } + + return 0; + } + + // invalid sequence, reset + if( unlikely( in > 0xbfu )) + { + s->len = 0; + return 0; + } + + s->uc <<= 6; + s->uc += in & 0x3fu; + s->k++; + + // sequence complete, reset and return code point + if( likely( s->k == s->len )) + { + s->len = 0; + return s->uc; + } + + // feed more characters + return 0; +} + +uint32_t Q_DecodeUTF16( utfstate_t *s, uint32_t in ) +{ + // get character length + if( s->len == 0 ) + { + // init state + s->uc = 0; + + // expect simple case, after all decoding UTF-16 must be easy + if( likely( in < 0xd800u || in > 0xdfffu )) + return in; + + s->uc = (( in - 0xd800u ) << 10 ) + 0x10000u; + s->len = 1; + s->k = 0; + + return 0; + } + + // invalid sequence, reset + if( unlikely( in < 0xdc00u || in > 0xdfffu )) + { + s->len = 0; + return 0; + } + + s->uc |= in - 0xdc00u; + s->k++; + + // sequence complete, reset and return code point + if( likely( s->k == s->len )) + { + s->len = 0; + return s->uc; + } + + // feed more characters (should never happen with UTF-16) + return 0; +} + +static size_t Q_CodepointLength( uint32_t ch ) +{ + if( ch <= 0x7fu ) + return 1; + else if( ch <= 0x7ffu ) + return 2; + else if( ch <= 0xffffu ) + return 3; + + return 4; +} + +size_t Q_EncodeUTF8( char dst[4], uint32_t ch ) +{ + switch( Q_CodepointLength( ch )) + { + case 1: + dst[0] = ch; + return 1; + case 2: + dst[0] = 0xc0u | (( ch >> 6 ) & 0x1fu ); + dst[1] = 0x80u | (( ch ) & 0x3fu ); + return 2; + case 3: + dst[0] = 0xe0u | (( ch >> 12 ) & 0x0fu ); + dst[1] = 0x80u | (( ch >> 6 ) & 0x3fu ); + dst[2] = 0x80u | (( ch ) & 0x3fu ); + return 3; + } + dst[0] = 0xf0u | (( ch >> 18 ) & 0x07u ); + dst[1] = 0x80u | (( ch >> 12 ) & 0x3fu ); + dst[2] = 0x80u | (( ch >> 6 ) & 0x3fu ); + dst[3] = 0x80u | (( ch ) & 0x3fu ); + return 4; +} + +size_t Q_UTF8Length( const char *s ) +{ + size_t len = 0; + utfstate_t state = { 0 }; + + if( !s ) + return 0; + + for( ; *s; s++ ) + { + uint32_t ch = Q_DecodeUTF8( &state, (uint32_t)*s ); + + if( ch == 0 ) + continue; + + len++; + } + + return len; +} + +size_t Q_UTF16ToUTF8( char *dst, size_t dstsize, const uint16_t *src, size_t srcsize ) +{ + utfstate_t state = { 0 }; + size_t dsti = 0, srci; + + if( !dst || !src || !dstsize || !srcsize ) + return 0; + + for( srci = 0; srci < srcsize && src[srci]; srci++ ) + { + uint32_t ch; + size_t len; + + ch = Q_DecodeUTF16( &state, src[srci] ); + + if( ch == 0 ) + continue; + + len = Q_CodepointLength( ch ); + + if( dsti + len + 1 > dstsize ) + break; + + dsti += Q_EncodeUTF8( &dst[dsti], ch ); + } + + dst[dsti] = 0; + + return dsti; +} + +static uint16_t table_cp1251[64] = { + 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, + 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, + 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x007F, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, + 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, + 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, + 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, + 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457 +}; + +uint32_t Q_UnicodeToCP1251( uint32_t uc ) +{ + size_t i; + + if( uc < 0x80 ) + return uc; + + if( uc >= 0x0410 && uc <= 0x042F ) + return uc - 0x410 + 0xC0; + + if( uc >= 0x0430 && uc <= 0x044F ) + return uc - 0x430 + 0xE0; + + for( i = 0; i < sizeof( table_cp1251 ) / sizeof( table_cp1251[0] ); i++ ) + { + if( uc == (uint32_t)table_cp1251[i] ) + return i + 0x80; + } + + return '?'; +} + +uint32_t Q_UnicodeToCP1252( uint32_t uc ) +{ + // this is NOT valid way to convert Unicode codepoint back to CP1252!!! + return uc < 0xFF ? uc : '?'; +} diff --git a/public/utflib.h b/public/utflib.h new file mode 100644 index 00000000..41c60137 --- /dev/null +++ b/public/utflib.h @@ -0,0 +1,44 @@ +/* +utflib.h - small unicode conversion library +Copyright (C) 2024 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef UTFLIB_H +#define UTFLIB_H + +#include STDINT_H +#include + +typedef struct utfstate_s +{ + uint32_t uc; + uint8_t len; + uint8_t k; +} utfstate_t; + +// feed utf8 characters one by one +// if it returns 0, feed more +// utfstate_t must be zero initialized +uint32_t Q_DecodeUTF8( utfstate_t *s, uint32_t ch ); +uint32_t Q_DecodeUTF16( utfstate_t *s, uint32_t ch ); +size_t Q_EncodeUTF8( char dst[4], uint32_t ch ); + +size_t Q_UTF8Length( const char *s ); + +// srcsize in byte pairs +size_t Q_UTF16ToUTF8( char *dst, size_t dstsize, const uint16_t *src, size_t srcsize ); + +// function to convert Unicode codepoints into CP1251 or CP1252 +uint32_t Q_UnicodeToCP1251( uint32_t uc ); +uint32_t Q_UnicodeToCP1252( uint32_t uc ); + +#endif // UTFLIB_H diff --git a/public/wscript b/public/wscript index cc20f0c5..b401e162 100644 --- a/public/wscript +++ b/public/wscript @@ -2,23 +2,178 @@ # encoding: utf-8 # mittorn, 2018 -from waflib import Logs +from waflib import Logs, Configure +from waflib.extras import gitversion import os top = '.' +TGMATH_H_TEST = '''#include +const float val = 2, val2 = 3; +int main(void){ return (int)(-asin(val) + cos(val2)); }''' + +STRNCASECMP_TEST = '''#include +int main(int argc, char **argv) { return strncasecmp(argv[1], argv[2], 10); }''' + +STRCASECMP_TEST = '''#include +int main(int argc, char **argv) { return strcasecmp(argv[1], argv[2]); }''' + +STRCASESTR_TEST = '''#include +int main(int argc, char **argv) { argv[0] = strcasestr(argv[1], argv[2]); return 0; }''' + +STRCHRNUL_TEST = '''#include +int main(int argc, char **argv) { argv[2] = strchrnul(argv[1], 'x'); return 0; }''' + +STRLCPY_TEST = '''#include +int main(int argc, char **argv) { return strlcpy(argv[1], argv[2], 10); }''' + +STRLCAT_TEST = '''#include +int main(int argc, char **argv) { return strlcat(argv[1], argv[2], 10); }''' + +STRNLEN_TEST = '''#include +int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); } +''' + +ALLOCA_TEST = '''#include <%s> +int main(void) { alloca(1); return 0; }''' + +HEADER_TEST = '''#include <%s> +int main(void) { return 0; } +''' + def options(opt): # stub return +def header_frag(header_name): + return '#include <%s>' % header_name + EMPTY_PROGRAM + +@Configure.conf +def export_define(conf, define, value=1): + if not value: + return + if value is True: + value = 1 # so python won't define it as string True + + conf.env.EXPORT_DEFINES_LIST += ['%s=%s' % (define, value)] + +@Configure.conf +def simple_check(conf, fragment, msg, mandatory=False, **kw): + return conf.check_cc(fragment=fragment, msg='Checking for %s' % msg, mandatory=mandatory, **kw) + +@Configure.conf +def get_git_commit_date(conf): + node = conf.srcnode.find_node('.git') + + if not node: + Logs.debug('can\'t find .git in conf.srcnode') + return None + + return gitversion.run_git(conf, ['log', '-1', '--format=%ci']) + +def options(opt): + opt.add_option('--validate-target', action='store', dest='VALIDATE_TARGET', default=None, + help='development option, needs --enable-tests flag') + def configure(conf): - # stub - return + # private to libpublic + conf.load('gitversion') + + conf.start_msg('Git commit date') + conf.env.GIT_COMMIT_DATE = conf.get_git_commit_date() + conf.end_msg(conf.env.GIT_COMMIT_DATE) + + conf.env.VALIDATE_TARGET = conf.options.VALIDATE_TARGET + + # need to expose it for everyone using libpublic headers + conf.env.EXPORT_DEFINES_LIST = [] + conf.export_define('_CRT_SILENCE_NONCONFORMING_TGMATH_H', conf.env.COMPILER_CC == 'msvc') + conf.export_define('_GNU_SOURCE', conf.env.DEST_OS != 'win32') + + # create temporary uselib that just enables extensions + conf.env.DEFINES_export = list(conf.env.EXPORT_DEFINES_LIST) + + # check platform-specific header name for alloca(3) + if conf.env.DEST_OS == 'win32': + # don't waste time on Win32, it's documented to be always in malloc.h. + # (plus test can fail because alloca is underscore-prefixed, e.g. _alloca) + conf.export_define('ALLOCA_H', '') + elif conf.simple_check(ALLOCA_TEST % 'alloca.h', msg='alloca in alloca.h'): + conf.export_define('ALLOCA_H', '') + elif conf.simple_check(ALLOCA_TEST % 'malloc.h', msg='alloca in malloc.h'): + conf.export_define('ALLOCA_H', '') + elif conf.simple_check(ALLOCA_TEST % 'stdlib.h', msg='alloca in stdlib.h'): + conf.export_define('ALLOCA_H', '') + + conf.export_define('STDINT_H', '' if conf.simple_check(HEADER_TEST % 'stdint.h', 'stdint.h') else '') + + # save some time on Windows, msvc is too slow + # these calls must be available with both msvc and mingw + if conf.env.DEST_OS == 'win32': + conf.export_define('HAVE_TGMATH_H', conf.simple_check(TGMATH_H_TEST, 'tgmath.h', use='M werror export')) + conf.export_define('HAVE_STRNICMP') + conf.export_define('HAVE_STRICMP') + else: + exports_list = [] + + def check_libc_extension(frag, msg, define): + exports_list.append(define) + return {'fragment': frag, 'msg': 'Checking for ' + msg, 'define_name': define, 'mandatory': False, 'use': 'M werror export', 'compiler': 'c'} + + conf.multicheck( + check_libc_extension(TGMATH_H_TEST, 'tgmath.h', 'HAVE_TGMATH_H'), + check_libc_extension(STRNCASECMP_TEST, 'strncasecmp', 'HAVE_STRNCASECMP'), + check_libc_extension(STRCASECMP_TEST, 'strcasecmp', 'HAVE_STRCASECMP'), + check_libc_extension(STRCASESTR_TEST, 'strcasestr', 'HAVE_STRCASESTR'), + check_libc_extension(STRCHRNUL_TEST, 'strchrnul', 'HAVE_STRCHRNUL'), + check_libc_extension(STRLCPY_TEST, 'strlcpy', 'HAVE_STRLCPY'), + check_libc_extension(STRLCAT_TEST, 'strlcat', 'HAVE_STRLCAT'), + check_libc_extension(STRNLEN_TEST, 'strnlen', 'HAVE_STRNLEN'), + ) + + for export in exports_list: + if conf.env[export]: + conf.export_define(export) + + # kill temporary uselib + del conf.env.DEFINES_export def build(bld): - bld(name = 'sdk_includes', export_includes = '. ../common ../pm_shared ../engine') - bld.stlib(source = bld.path.ant_glob('*.c'), - target = 'public', - features = 'c', - use = 'sdk_includes', - subsystem = bld.env.MSVC_SUBSYSTEM) + bld(name = 'sdk_includes', + export_includes = '. ../common ../pm_shared ../engine', + export_defines = bld.env.EXPORT_DEFINES_LIST) + + # build it separately to slightly improve rebuild times + bld.stlib(source = 'build_vcs.c', + target = 'build_vcs', + defines = ['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]) + + bld.stlib(source = bld.path.ant_glob('*.c', excl='build_vcs.c'), + target = 'public', + use = 'sdk_includes werror build_vcs') + + if bld.env.TESTS: + if bld.env.VALIDATE_TARGET: + bld.program(features = 'test', + source = 'tests/test_validate_target.c', + target = 'validate_target', + use = 'public', + defines = 'VALIDATE_TARGET="%s"' % bld.env.VALIDATE_TARGET, + install_path = None) + + tests = { + 'strings': 'tests/test_strings.c', + 'build': 'tests/test_build.c', + 'filebase': 'tests/test_filebase.c', + 'fileext': 'tests/test_fileext.c', + 'efp': 'tests/test_efp.c', + 'atoi': 'tests/test_atoi.c', + 'parsefile': 'tests/test_parsefile.c', + } + + for i in tests: + bld.program(features = 'test', + source = tests[i], + target = 'test_%s' % i, + use = 'public', + install_path = None) diff --git a/public/xash3d_mathlib.c b/public/xash3d_mathlib.c index 2fd8f705..e1610580 100644 --- a/public/xash3d_mathlib.c +++ b/public/xash3d_mathlib.c @@ -18,15 +18,14 @@ GNU General Public License for more details. #include "com_model.h" #include "xash3d_mathlib.h" #include "eiface.h" +#include "studio.h" #define NUM_HULL_ROUNDS ARRAYSIZE( hull_table ) #define HULL_PRECISION 4 -vec3_t vec3_origin = { 0, 0, 0 }; +static 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 }; -static 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 }; - -int boxpnt[6][4] = +const int boxpnt[6][4] = { { 0, 4, 6, 2 }, // +X { 0, 1, 5, 4 }, // +Y @@ -42,38 +41,9 @@ const float m_bytenormals[NUMVERTEXNORMALS][3] = #include "anorms.h" }; -/* -================= -anglemod -================= -*/ -float anglemod( float a ) +uint16_t FloatToHalf( float v ) { - a = (360.0f / 65536) * ((int)(a*(65536/360.0f)) & 65535); - return a; -} - -/* -================= -SimpleSpline - -NOTE: ripped from hl2 source -hermite basis function for smooth interpolation -Similar to Gain() above, but very cheap to call -value should be between 0 & 1 inclusive -================= -*/ -float SimpleSpline( float value ) -{ - float valueSquared = value * value; - - // nice little ease-in, ease-out spline-like curve - return (3.0f * valueSquared - 2.0f * valueSquared * value); -} - -word FloatToHalf( float v ) -{ - unsigned int i = *((unsigned int *)&v); + unsigned int i = FloatAsUint( v ); unsigned int e = (i >> 23) & 0x00ff; unsigned int m = i & 0x007fffff; unsigned short h; @@ -87,7 +57,7 @@ word FloatToHalf( float v ) return h; } -float HalfToFloat( word h ) +float HalfToFloat( uint16_t h ) { unsigned int f = (h << 16) & 0x80000000; unsigned int em = h & 0x7fff; @@ -115,7 +85,7 @@ float HalfToFloat( word h ) } } - return *((float *)&f); + return UintAsFloat( f ); } /* @@ -169,125 +139,12 @@ void RoundUpHullSize( vec3_t size ) } } -/* -================= -SignbitsForPlane - -fast box on planeside test -================= -*/ -int SignbitsForPlane( const vec3_t normal ) -{ - int bits, i; - - for( bits = i = 0; i < 3; i++ ) - if( normal[i] < 0.0f ) bits |= 1<normal, n1 ); - VectorNormalize2( plane2->normal, n2 ); - VectorNormalize2( plane3->normal, n3 ); - - CrossProduct( n1, n2, n1n2 ); - CrossProduct( n2, n3, n2n3 ); - CrossProduct( n3, n1, n3n1 ); - - denom = DotProduct( n1, n2n3 ); - VectorClear( out ); - - // check if the denominator is zero (which would mean that no intersection is to be found - if( denom == 0.0f ) - { - // no intersection could be found, return <0,0,0> - return false; - } - - // compute intersection point -#if 0 - VectorMAMAM( plane1->dist, n2n3, plane2->dist, n3n1, plane3->dist, n1n2, out ); -#else - VectorMA( out, plane1->dist, n2n3, out ); - VectorMA( out, plane2->dist, n3n1, out ); - VectorMA( out, plane3->dist, n1n2, out ); -#endif - VectorScale( out, ( 1.0f / denom ), out ); - - return true; -} - -/* -================= -NearestPOW -================= -*/ -int NearestPOW( int value, qboolean roundDown ) -{ - int n = 1; - - if( value <= 0 ) return 1; - while( n < value ) n <<= 1; - - if( roundDown ) - { - if( n > value ) n >>= 1; - } - return n; -} - -// remap a value in the range [A,B] to [C,D]. -float RemapVal( float val, float A, float B, float C, float D ) -{ - return C + (D - C) * (val - A) / (B - A); -} - -float ApproachVal( float target, float value, float speed ) -{ - float delta = target - value; - - if( delta > speed ) - value += speed; - else if( delta < -speed ) - value -= speed; - else value = target; - - return value; -} - /* ================= rsqrt ================= */ -float rsqrt( float number ) +float Q_rsqrt( float number ) { int i; float x, y; @@ -296,76 +153,14 @@ float rsqrt( float number ) return 0.0f; x = number * 0.5f; - i = *(int *)&number; // evil floating point bit level hacking + i = FloatAsInt( number ); // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? - y = *(float *)&i; + y = IntAsFloat( i ); y = y * (1.5f - (x * y * y)); // first iteration return y; } -/* -================= -SinCos -================= -*/ -void SinCos( float radians, float *sine, float *cosine ) -{ -#if _MSC_VER == 1200 - _asm - { - fld dword ptr [radians] - fsincos - - mov edx, dword ptr [cosine] - mov eax, dword ptr [sine] - - fstp dword ptr [edx] - fstp dword ptr [eax] - } -#else - *sine = sin(radians); - *cosine = cos(radians); -#endif -} - -/* -============== -VectorCompareEpsilon - -============== -*/ -qboolean VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon ) -{ - vec_t ax, ay, az; - - ax = fabs( vec1[0] - vec2[0] ); - ay = fabs( vec1[1] - vec2[1] ); - az = fabs( vec1[2] - vec2[2] ); - - if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon )) - return true; - return false; -} - -float VectorNormalizeLength2( const vec3_t v, vec3_t out ) -{ - float length, ilength; - - length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; - length = sqrt( length ); - - if( length ) - { - ilength = 1.0f / length; - out[0] = v[0] * ilength; - out[1] = v[1] * ilength; - out[2] = v[2] * ilength; - } - - return length; -} - void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ) { float d; @@ -381,42 +176,6 @@ void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ) VectorNormalize( up ); } -/* -================= -AngleVectors - -================= -*/ -void GAME_EXPORT AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ) -{ - float sr, sp, sy, cr, cp, cy; - - SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); - SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); - SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); - - if( forward ) - { - forward[0] = cp * cy; - forward[1] = cp * sy; - forward[2] = -sp; - } - - if( right ) - { - right[0] = (-1.0f * sr * sp * cy + -1.0f * cr * -sy ); - right[1] = (-1.0f * sr * sp * sy + -1.0f * cr * cy ); - right[2] = (-1.0f * sr * cp); - } - - if( up ) - { - up[0] = (cr * sp * cy + -sr * -sy ); - up[1] = (cr * sp * sy + -sr * cy ); - up[2] = (cr * cp); - } -} - /* ================= VectorAngles @@ -489,79 +248,6 @@ void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, v // // bounds operations // -/* -================= -ClearBounds -================= -*/ -void ClearBounds( vec3_t mins, vec3_t maxs ) -{ - // make bogus range - mins[0] = mins[1] = mins[2] = 999999.0f; - maxs[0] = maxs[1] = maxs[2] = -999999.0f; -} - -/* -================= -AddPointToBounds -================= -*/ -void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) -{ - float val; - int i; - - for( i = 0; i < 3; i++ ) - { - val = v[i]; - if( val < mins[i] ) mins[i] = val; - if( val > maxs[i] ) maxs[i] = val; - } -} - -/* -================= -ExpandBounds -================= -*/ -void ExpandBounds( vec3_t mins, vec3_t maxs, float offset ) -{ - mins[0] -= offset; - mins[1] -= offset; - mins[2] -= offset; - maxs[0] += offset; - maxs[1] += offset; - maxs[2] += offset; -} - -/* -================= -BoundsIntersect -================= -*/ -qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 ) -{ - if( mins1[0] > maxs2[0] || mins1[1] > maxs2[1] || mins1[2] > maxs2[2] ) - return false; - if( maxs1[0] < mins2[0] || maxs1[1] < mins2[1] || maxs1[2] < mins2[2] ) - return false; - return true; -} - -/* -================= -BoundsAndSphereIntersect -================= -*/ -qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius ) -{ - if( mins[0] > origin[0] + radius || mins[1] > origin[1] + radius || mins[2] > origin[2] + radius ) - return false; - if( maxs[0] < origin[0] - radius || maxs[1] < origin[1] - radius || maxs[2] < origin[2] - radius ) - return false; - return true; -} - /* ================= SphereIntersect @@ -602,67 +288,9 @@ void PlaneIntersect( const mplane_t *plane, const vec3_t p0, const vec3_t p1, ve VectorMA( p0, sect, p1, out ); } -/* -================= -RadiusFromBounds -================= -*/ -float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) -{ - vec3_t corner; - int i; - - for( i = 0; i < 3; i++ ) - { - corner[i] = fabs( mins[i] ) > fabs( maxs[i] ) ? fabs( mins[i] ) : fabs( maxs[i] ); - } - return VectorLength( corner ); -} - // // studio utils // -/* -==================== -AngleQuaternion - -==================== -*/ -void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ) -{ - float sr, sp, sy, cr, cp, cy; - - if( studio ) - { - SinCos( angles[ROLL] * 0.5f, &sy, &cy ); - SinCos( angles[YAW] * 0.5f, &sp, &cp ); - SinCos( angles[PITCH] * 0.5f, &sr, &cr ); - } - else - { - SinCos( DEG2RAD( angles[YAW] ) * 0.5f, &sy, &cy ); - SinCos( DEG2RAD( angles[PITCH] ) * 0.5f, &sp, &cp ); - SinCos( DEG2RAD( angles[ROLL] ) * 0.5f, &sr, &cr ); - } - - q[0] = sr * cp * cy - cr * sp * sy; // X - q[1] = cr * sp * cy + sr * cp * sy; // Y - q[2] = cr * cp * sy - sr * sp * cy; // Z - q[3] = cr * cp * cy + sr * sp * sy; // W -} - -/* -==================== -QuaternionAngle - -==================== -*/ -void QuaternionAngle( const vec4_t q, vec3_t angles ) -{ - matrix3x4 mat; - Matrix3x4_FromOriginQuat( mat, q, vec3_origin ); - Matrix3x4_AnglesFromMatrix( mat, angles ); -} /* ==================== @@ -672,7 +300,7 @@ make sure quaternions are within 180 degrees of one another, if not, reverse q ==================== */ -void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt ) +static void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt ) { // decide if one of the quaternions is backwards float a = 0.0f; @@ -702,7 +330,7 @@ void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt ) QuaternionSlerpNoAlign ==================== */ -void QuaternionSlerpNoAlign( const vec4_t p, const vec4_t q, float t, vec4_t qt ) +static void QuaternionSlerpNoAlign( const vec4_t p, const vec4_t q, float t, vec4_t qt ) { float omega, cosom, sinom, sclp, sclq; int i; @@ -764,53 +392,6 @@ void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ) QuaternionSlerpNoAlign( p, q2, t, qt ); } -/* -==================== -V_CalcFov -==================== -*/ -float V_CalcFov( float *fov_x, float width, float height ) -{ - float x, half_fov_y; - - if( *fov_x < 1.0f || *fov_x > 179.0f ) - *fov_x = 90.0f; // default value - - x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); - half_fov_y = atan( height / x ); - - return RAD2DEG( half_fov_y ) * 2; -} - -/* -==================== -V_AdjustFov -==================== -*/ -void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ) -{ - float x, y; - - if( width * 3 == 4 * height || width * 4 == height * 5 ) - { - // 4:3 or 5:4 ratio - return; - } - - if( lock_x ) - { - *fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI_F / 360.0f * 0.5f )) * 360 / M_PI_F; - return; - } - - y = V_CalcFov( fov_x, 640, 480 ); - x = *fov_x; - - *fov_x = V_CalcFov( &y, height, width ); - if( *fov_x < x ) *fov_x = x; - else *fov_y = y; -} - /* ================== BoxOnPlaneSide @@ -872,3 +453,83 @@ int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ) return sides; } +void R_StudioCalcBones( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const float *adj, vec3_t pos, vec4_t q ) +{ + float v1[6], v2[6]; + int i, max; + + max = q != NULL ? 6 : 3; + + for( i = 0; i < max; i++ ) + { + mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[i] ); + int j = frame; + float fadj = 0.0f; + + if( pbone->bonecontroller[i] >= 0 && adj != NULL ) + fadj = adj[pbone->bonecontroller[i]]; + + if( panim->offset[i] == 0 ) + { + v1[i] = v2[i] = pbone->value[i] + fadj; + continue; + } + + if( panimvalue->num.total < panimvalue->num.valid ) + j = 0; + + while( panimvalue->num.total <= j ) + { + j -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + if( panimvalue->num.total < panimvalue->num.valid ) + j = 0; + } + + if( panimvalue->num.valid > j ) + { + v1[i] = panimvalue[j + 1].value; + + if( panimvalue->num.valid > j + 1 ) + v2[i] = panimvalue[j + 2].value; + else if( panimvalue->num.total > j + 1 ) + v2[i] = v1[i]; + else + v2[i] = panimvalue[panimvalue->num.valid + 2].value; + } + else + { + v1[i] = panimvalue[panimvalue->num.valid].value; + + if( panimvalue->num.total > j + 1 ) + v2[i] = v1[i]; + else + v2[i] = panimvalue[panimvalue->num.valid + 2].value; + } + + v1[i] = pbone->value[i] + v1[i] * pbone->scale[i] + fadj; + v2[i] = pbone->value[i] + v2[i] * pbone->scale[i] + fadj; + } + + if( !VectorCompare( v1, v2 )) + VectorLerp( v1, s, v2, pos ); + else + VectorCopy( v1, pos ); + + if( q != NULL ) + { + if( !VectorCompare( &v1[3], &v2[3] )) + { + vec4_t q1, q2; + + AngleQuaternion( &v1[3], q1, true ); + AngleQuaternion( &v2[3], q2, true ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( &v1[3], q, true ); + } + } +} diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 363090d2..c9413699 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -20,78 +20,72 @@ GNU General Public License for more details. #if HAVE_TGMATH_H #include #endif +#include #include "build.h" -#include "com_model.h" +#include "xash3d_types.h" -#ifdef XASH_MSVC -#pragma warning(disable : 4201) // nonstandard extension used -#endif +/* +=========================== + +CONSTANTS AND HELPER MACROS + +=========================== +*/ // euler angle order -#define PITCH 0 -#define YAW 1 -#define ROLL 2 +#define PITCH 0 +#define YAW 1 +#define ROLL 2 #ifndef M_PI -#define M_PI (double)3.14159265358979323846 +#define M_PI (double)3.14159265358979323846 #endif -#ifndef M_PI2 -#define M_PI2 ((double)(M_PI * 2)) -#endif +#define M_PI2 ((double)(M_PI * 2)) +#define M_PI_F ((float)(M_PI)) +#define M_PI2_F ((float)(M_PI2)) -#define M_PI_F ((float)(M_PI)) -#define M_PI2_F ((float)(M_PI2)) +#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI)) +#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0)) -#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI)) -#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0)) +#define NUMVERTEXNORMALS 162 -#define NUMVERTEXNORMALS 162 +#define BOGUS_RANGE ((vec_t)114032.64) // world.size * 1.74 -#define BOGUS_RANGE ((vec_t)114032.64) // world.size * 1.74 +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS -2 -#define SIDE_FRONT 0 -#define SIDE_BACK 1 -#define SIDE_ON 2 -#define SIDE_CROSS -2 +#define PLANE_X 0 // 0 - 2 are axial planes +#define PLANE_Y 1 // 3 needs alternate calc +#define PLANE_Z 2 +#define PLANE_NONAXIAL 3 -#define PLANE_X 0 // 0 - 2 are axial planes -#define PLANE_Y 1 // 3 needs alternate calc -#define PLANE_Z 2 -#define PLANE_NONAXIAL 3 +#define EQUAL_EPSILON 0.001f +#define STOP_EPSILON 0.1f +#define ON_EPSILON 0.1f -#define EQUAL_EPSILON 0.001f -#define STOP_EPSILON 0.1f -#define ON_EPSILON 0.1f +#define RAD_TO_STUDIO (32768.0 / M_PI) +#define STUDIO_TO_RAD (M_PI / 32768.0) -#define RAD_TO_STUDIO (32768.0 / M_PI) -#define STUDIO_TO_RAD (M_PI / 32768.0) +#define INV127F ( 1.0f / 127.0f ) +#define INV255F ( 1.0f / 255.0f ) +#define MAKE_SIGNED( x ) ((( x ) * INV127F ) - 1.0f ) -#define INV127F ( 1.0f / 127.0f ) -#define INV255F ( 1.0f / 255.0f ) -#define MAKE_SIGNED( x ) ((( x ) * INV127F ) - 1.0f ) - -#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) -#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) -#define Q_equal( a, b ) (((a) > ((b) - EQUAL_EPSILON)) && ((a) < ((b) + EQUAL_EPSILON))) -#define Q_recip( a ) ((float)(1.0f / (float)(a))) -#define Q_floor( a ) ((float)(int)(a)) -#define Q_ceil( a ) ((float)(int)((a) + 1)) -#define Q_round( x, y ) (floor( x / y + 0.5f ) * y ) -#define Q_rint(x) ((x) < 0.0f ? ((int)((x)-0.5f)) : ((int)((x)+0.5f))) - -#ifdef isnan // check for C99 isnan -#define IS_NAN isnan -#else -#define IS_NAN(x) (((*(int *)&x) & (255<<23)) == (255<<23)) -#endif - -#define ALIGN( x, a ) ((( x ) + (( size_t )( a ) - 1 )) & ~(( size_t )( a ) - 1 )) +#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) +#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) +#define Q_equal_e( a, b, e ) (((a) >= ((b) - (e))) && ((a) <= ((b) + (e)))) +#define Q_equal( a, b ) Q_equal_e( a, b, EQUAL_EPSILON ) +#define Q_floor( a ) ((float)(int)(a)) +#define Q_ceil( a ) ((float)(int)((a) + 1)) +#define Q_round( x, y ) (floor( x / y + 0.5f ) * y ) +#define Q_rint(x) ((x) < 0.0f ? ((int)((x)-0.5f)) : ((int)((x)+0.5f))) +#define ALIGN( x, a ) ((( x ) + (( size_t )( a ) - 1 )) & ~(( size_t )( a ) - 1 )) #define VectorIsNAN(v) (IS_NAN(v[0]) || IS_NAN(v[1]) || IS_NAN(v[2])) #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) -#define DotProductAbs(x,y) (abs((x)[0]*(y)[0])+abs((x)[1]*(y)[1])+abs((x)[2]*(y)[2])) #define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2])) #define DotProductPrecise(x,y) ((double)(x)[0]*(double)(y)[0]+(double)(x)[1]*(double)(y)[1]+(double)(x)[2]*(double)(y)[2]) #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]) @@ -115,14 +109,17 @@ GNU General Public License for more details. #define Vector2Average(a,b,o) ((o)[0]=((a)[0]+(b)[0])*0.5f,(o)[1]=((a)[1]+(b)[1])*0.5f) #define VectorAverage(a,b,o) ((o)[0]=((a)[0]+(b)[0])*0.5f,(o)[1]=((a)[1]+(b)[1])*0.5f,(o)[2]=((a)[2]+(b)[2])*0.5f) #define Vector2Set(v, x, y) ((v)[0]=(x),(v)[1]=(y)) +#define Vector2Unpack(v, x, y) ((x)=(v)[0],(y)=(v)[1]) #define VectorSet(v, x, y, z) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z)) -#define Vector4Set(v, a, b, c, d) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c),(v)[3] = (d)) +#define VectorUnpack(v, x, y, z) ((x)=(v)[0],(y)=(v)[1],(z)=(v)[2]) +#define Vector4Set(v, a, b, c, d) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c),(v)[3]=(d)) +#define Vector4Unpack(v, a, b, c, d) ((a)=(v)[0],(b)=(v)[1],(c)=(v)[2],(d)=(v)[3]) #define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0) #define Vector2Lerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1])) #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])) #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; } #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; } -#define VectorNormalizeFast( v ) {float ilength = (float)rsqrt(DotProduct(v,v)); v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } +#define VectorNormalizeFast( v ) {float ilength = (float)Q_rsqrt(DotProduct(v,v)); v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } #define VectorNormalizeLength( v ) VectorNormalizeLength2((v), (v)) #define VectorNegate(x, y) ((y)[0] = -(x)[0], (y)[1] = -(x)[1], (y)[2] = -(x)[2]) #define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2]) @@ -135,45 +132,63 @@ GNU General Public License for more details. #define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) #define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) -float rsqrt( float number ); -float anglemod( float a ); -word FloatToHalf( float v ); -float HalfToFloat( word h ); -float SimpleSpline( float value ); +/* +=========================== + +CONSTANTS GLOBALS + +=========================== +*/ +// a1ba: we never return pointers to these globals +// so help compiler optimize constants away +#define vec3_origin ((vec3_t){ 0.0f, 0.0f, 0.0f }) + +extern const int boxpnt[6][4]; +extern const float m_bytenormals[NUMVERTEXNORMALS][3]; + + +/* +=========================== + +MATH FUNCTIONS + +=========================== +*/ +typedef struct mplane_s mplane_t; +typedef struct mstudiobone_s mstudiobone_t; +typedef struct mstudioanim_s mstudioanim_t; + +float Q_rsqrt( float number ); +uint16_t FloatToHalf( float v ); +float HalfToFloat( uint16_t h ); void RoundUpHullSize( vec3_t size ); -int SignbitsForPlane( const vec3_t normal ); -int PlaneTypeForNormal( const vec3_t normal ); -int NearestPOW( int value, qboolean roundDown ); -void SinCos( float radians, float *sine, float *cosine ); -float VectorNormalizeLength2( const vec3_t v, vec3_t out ); -qboolean VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon ); void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ); void VectorAngles( const float *forward, float *angles ); -void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ); void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles ); -qboolean PlanesGetIntersectionPoint( const struct mplane_s *plane1, const struct mplane_s *plane2, const struct mplane_s *plane3, vec3_t out ); -void PlaneIntersect( const struct mplane_s *plane, const vec3_t p0, const vec3_t p1, vec3_t out ); - -void ClearBounds( vec3_t mins, vec3_t maxs ); -void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); -qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 ); -qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius ); +void PlaneIntersect( const mplane_t *plane, const vec3_t p0, const vec3_t p1, vec3_t out ); qboolean SphereIntersect( const vec3_t vSphereCenter, float fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir ); -float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); -void ExpandBounds( vec3_t mins, vec3_t maxs, float offset ); - -void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ); -void QuaternionAngle( const vec4_t q, vec3_t angles ); void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ); -float RemapVal( float val, float A, float B, float C, float D ); -float ApproachVal( float target, float value, float speed ); + +void R_StudioCalcBones( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const float *adj, vec3_t pos, vec4_t q ); +int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ); +#define BOX_ON_PLANE_SIDE( emins, emaxs, p ) \ + ((( p )->type < 3 ) ? \ + ( \ + ((p)->dist <= (emins)[(p)->type]) ? 1 : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type] ) ? 2 : 3 \ + ) \ + ) : BoxOnPlaneSide(( emins ), ( emaxs ), ( p ))) // // matrixlib.c // -#define Matrix3x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, matrix3x4_identity ) +static inline void Matrix3x4_LoadIdentity( matrix3x4 m ) +{ + memset( m, 0, sizeof( matrix3x4 )); + m[0][0] = m[1][1] = m[2][2] = 1.0f; +} #define Matrix3x4_Copy( out, in ) memcpy( out, in, sizeof( matrix3x4 )) - void Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] ); void Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] ); void Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] ); @@ -181,58 +196,291 @@ void Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ); void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ); void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale ); -void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); void Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const vec3_t maxs, vec3_t absmin, vec3_t absmax ); -void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z ); -void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ); -void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out ); void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ); -void Matrix3x4_Transpose( matrix3x4 out, const matrix3x4 in1 ); -#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, matrix4x4_identity ) +static inline void Matrix4x4_LoadIdentity( matrix4x4 m ) +{ + memset( m, 0, sizeof( matrix4x4 )); + m[0][0] = m[1][1] = m[2][2] = m[3][3] = 1.0f; +} #define Matrix4x4_Copy( out, in ) memcpy( out, in, sizeof( matrix4x4 )) - void Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); -void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ); void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ); void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); -void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin ); -void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ); void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ); -void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ); -void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ); qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ); -float V_CalcFov( float *fov_x, float width, float height ); -void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ); +// horrible cast but helps not breaking strict aliasing in mathlib +// as union type punning should be fine in C but not in C++ +// so don't carry over this to C++ code +#ifndef __cplusplus +typedef union +{ + float fl; + uint32_t u; + int32_t i; +} float_bits_t; -int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ); -#define BOX_ON_PLANE_SIDE( emins, emaxs, p ) \ - ((( p )->type < 3 ) ? \ - ( \ - ((p)->dist <= (emins)[(p)->type]) ? \ - 1 \ - : \ - ( \ - ((p)->dist >= (emaxs)[(p)->type]) ? \ - 2 \ - : \ - 3 \ - ) \ - ) \ - : \ - BoxOnPlaneSide(( emins ), ( emaxs ), ( p ))) +static inline uint32_t FloatAsUint( float v ) +{ + float_bits_t bits = { v }; + return bits.u; +} -extern vec3_t vec3_origin; -extern int boxpnt[6][4]; -extern const matrix3x4 matrix3x4_identity; -extern const matrix4x4 matrix4x4_identity; -extern const float m_bytenormals[NUMVERTEXNORMALS][3]; +static inline int32_t FloatAsInt( float v ) +{ + float_bits_t bits = { v }; + return bits.i; +} + +static inline float IntAsFloat( int32_t i ) +{ + float_bits_t bits; + bits.i = i; + return bits.fl; +} + +static inline float UintAsFloat( uint32_t u ) +{ + float_bits_t bits; + bits.u = u; + return bits.fl; +} +#endif // __cplusplus + +// isnan implementation is broken on IRIX as reported in https://github.com/FWGS/xash3d-fwgs/pull/1211 +#if defined( XASH_IRIX ) || !defined( isnan ) +static inline int IS_NAN( float x ) +{ + int32_t i = FloatAsInt( x ); // only C + return i & ( 255 << 23 ) == ( 255 << 23 ); +} +#else +#define IS_NAN isnan +#endif + +static inline float anglemod( float a ) +{ + a = (360.0f / 65536) * ((int)(a*(65536/360.0f)) & 65535); + return a; +} + +static inline void SinCos( float radians, float *sine, float *cosine ) +{ + *sine = sin( radians ); + *cosine = cos( radians ); +} + +static inline int NearestPOW( int value, qboolean roundDown ) +{ + int n = 1; + + if( value <= 0 ) return 1; + while( n < value ) n <<= 1; + + if( roundDown ) + { + if( n > value ) n >>= 1; + } + return n; +} + +static inline qboolean VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon ) +{ + vec_t ax = fabs( vec1[0] - vec2[0] ); + vec_t ay = fabs( vec1[1] - vec2[1] ); + vec_t az = fabs( vec1[2] - vec2[2] ); + + return ( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon ) ? true : false; +} + +static inline float VectorNormalizeLength2( const vec3_t v, vec3_t out ) +{ + float length = VectorLength( v ); + + if( length ) + { + float ilength = 1.0f / length; + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; + } + + return length; +} + +static inline void GAME_EXPORT AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); + + if( forward ) + { + forward[0] = cp * cy; + forward[1] = cp * sy; + forward[2] = -sp; + } + + if( right ) + { + right[0] = (-1.0f * sr * sp * cy + -1.0f * cr * -sy ); + right[1] = (-1.0f * sr * sp * sy + -1.0f * cr * cy ); + right[2] = (-1.0f * sr * cp); + } + + if( up ) + { + up[0] = (cr * sp * cy + -sr * -sy ); + up[1] = (cr * sp * sy + -sr * cy ); + up[2] = (cr * cp); + } +} + +static inline void ClearBounds( vec3_t mins, vec3_t maxs ) +{ + // make bogus range + mins[0] = mins[1] = mins[2] = 999999.0f; + maxs[0] = maxs[1] = maxs[2] = -999999.0f; +} + +static inline qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 ) +{ + if( mins1[0] > maxs2[0] || mins1[1] > maxs2[1] || mins1[2] > maxs2[2] ) + return false; + if( maxs1[0] < mins2[0] || maxs1[1] < mins2[1] || maxs1[2] < mins2[2] ) + return false; + return true; +} + +static inline qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius ) +{ + if( mins[0] > origin[0] + radius || mins[1] > origin[1] + radius || mins[2] > origin[2] + radius ) + return false; + if( maxs[0] < origin[0] - radius || maxs[1] < origin[1] - radius || maxs[2] < origin[2] - radius ) + return false; + return true; +} + +static inline float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) +{ + vec3_t corner; + int i; + + for( i = 0; i < 3; i++ ) + { + float a = fabs( mins[i] ); + float b = fabs( maxs[i] ); + corner[i] = Q_max( a, b ); + } + + return VectorLength( corner ); +} + +static inline void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + float val = v[i]; + if( val < mins[i] ) mins[i] = val; + if( val > maxs[i] ) maxs[i] = val; + } +} + +static inline void ExpandBounds( vec3_t mins, vec3_t maxs, float offset ) +{ + mins[0] -= offset; + mins[1] -= offset; + mins[2] -= offset; + maxs[0] += offset; + maxs[1] += offset; + maxs[2] += offset; +} + +static inline int SignbitsForPlane( const vec3_t normal ) +{ + int bits, i; + + for( bits = i = 0; i < 3; i++ ) + if( normal[i] < 0.0f ) bits |= 1<= 130 || VER == 100\n" +"precision mediump float;\n" +"#endif\n" +"#if VER == 100\n" +"#define PREC mediump\n" +"#else\n" +"#define PREC\n" +"#endif\n" +"#if ATTR_TEXCOORD0\n" +"uniform sampler2D uTex0;\n" +"#endif\n" +"#if ATTR_TEXCOORD1\n" +"uniform sampler2D uTex1;\n" +"#endif\n" +"#if FEAT_ALPHA_TEST\n" +"uniform float uAlphaTest;\n" +"#endif\n" +"#if FEAT_FOG\n" +"uniform PREC vec4 uFog;\n" +"#endif\n" +"uniform PREC vec4 uColor;\n" +"#if ATTR_COLOR\n" +"in PREC vec4 vColor;\n" +"#endif\n" +"#if ATTR_TEXCOORD0\n" +"in PREC vec2 vTexCoord0;\n" +"#endif\n" +"#if ATTR_TEXCOORD1\n" +"in PREC vec2 vTexCoord1;\n" +"#endif\n" +"#if ATTR_NORMAL\n" +"in PREC vec2 vNormal;\n" +"#endif\n" +"#if VER >= 300\n" +"out vec4 oFragColor;\n" +"#else\n" +"#define oFragColor gl_FragColor\n" +"#endif\n" +"void main()\n" +"{\n" +"#if ATTR_COLOR\n" +"PREC vec4 c = vColor;\n" +"#else\n" +"PREC vec4 c = uColor;\n" +"#endif\n" +"#if ATTR_TEXCOORD0\n" +"c = c * texture(uTex0, vTexCoord0);\n" +"#endif\n" +"#if ATTR_TEXCOORD1\n" +"c = c * texture(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 = gl_FragCoord.z / gl_FragCoord.w;\n" +"float fogRate = clamp(exp(-uFog.w * fogDist), 0.0, 1.0);\n" +"c.rgb = mix(uFog.rgb, c.rgb, fogRate);\n" +"#endif\n" +"oFragColor = c;\n" +"}\n" diff --git a/ref/gl/gl2_shim/gl2_shim.c b/ref/gl/gl2_shim/gl2_shim.c new file mode 100644 index 00000000..988b8154 --- /dev/null +++ b/ref/gl/gl2_shim/gl2_shim.c @@ -0,0 +1,1820 @@ +/* +gl2_shim.c - GL CORE/ES2+ FFP emulation +Copyright (C) 2023 mittorn, fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +/* +based on vglshim, as it has almost all needed for using glbegins on gles +BufStorage mode should work similar to vglshim with vitagl's DRAW_SPEEDHACK +as it uses gpu-mapped in similar way + +gl2shim will not give much performance gain. It does not batch small drawcalls, just do little copy optimization +It just allows to draw similar way as in native legacy context, but in es/core contexts + +Limitations: +1. Matrix support is very limited, only combined MVP +2. No TexEnv support, multitexture always works in MODULATE mode +3. DrawElements with client pointers will not work on CORE/WEBGL contexts, DrawRangeElements will +4. No quads in arrays drawing (simple DrawArrays(GL_QUADS) may be implemented, but DrawElements not) +5. Textures are enabled with texcoord attribs, not glEnable (can be changed, but who cares?) +6. 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 +7. Textures internalformat ignored except of removing alpha from RGB textures +*/ + +#include "gl_local.h" +#if !XASH_GL_STATIC +#include "gl2_shim.h" + +#define MAX_SHADERLEN 4096 +// increase this when adding more attributes +#define MAX_PROGS 32 +// must be LESS GL2_MAX_VERTS +#define MAX_BEGINEND_VERTS 8192 + +enum gl2wrap_attrib_e +{ + GL2_ATTR_POS = 0, // 1 + GL2_ATTR_COLOR, // 2 + GL2_ATTR_TEXCOORD0, // 4 + GL2_ATTR_TEXCOORD1, // 8 + GL2_ATTR_MAX +}; + +// continuation of previous enum +enum gl2wrap_flag_e +{ + GL2_FLAG_ALPHA_TEST = GL2_ATTR_MAX, // 16 + GL2_FLAG_FOG, // 32 + GL2_FLAG_NORMAL, // 64 + GL2_FLAG_MAX +}; + +typedef struct +{ + GLuint flags; + GLint attridx[GL2_ATTR_MAX]; + GLuint glprog; + GLint ucolor; + GLint ualpha; + GLint utex0; + GLint utex1; + GLint ufog; + GLint uMVP; + GLuint *vao_begin; +} gl2wrap_prog_t; + +static const char *gl2wrap_vert_src = +#include "vertex.glsl.inc" +; + +static const char *gl2wrap_frag_src = +#include "fragment.glsl.inc" +; + +static int gl2wrap_init = 0; + +static struct +{ + GLfloat *attrbuf[GL2_ATTR_MAX]; + GLuint *attrbufobj[GL2_ATTR_MAX]; + void **mappings[GL2_ATTR_MAX]; + //GLuint attrbufpers[GL2_ATTR_MAX]; + GLuint attrbufcycle; + GLuint cur_flags; + GLint begin; + GLint end; + GLenum prim; + GLfloat color[4]; + GLfloat fog[4]; // color + density + GLfloat alpharef; + gl2wrap_prog_t progs[MAX_PROGS]; + gl2wrap_prog_t *cur_prog; + GLboolean uchanged; + GLuint triquads_ibo[4]; +} gl2wrap; + +static struct +{ + qboolean buf_storage; // buffer storage is enabled, buffers mapped persistently (zero-copy glBegins) + qboolean incremental; // incremental buffer streaming + qboolean supports_mapbuffer; // set to false on systems with mapbuffer issues + qboolean vao_mandatory; // even if incremental streaming unavailiable (it is very slow without mapbuffers) force VAO+VBO (WebGL-like or broken glcore) + qboolean coherent; // enable MAP_COHERENT_BIT on persist mappings + qboolean async; // enable MAP_UNSYNCHRONIZED_BIT on temporary mappings + qboolean force_flush; // enable MAP_FLUSH_EXPLICIT_BIT and FlushMappedBufferRange calls + uint32_t cycle_buffers; // cycle N buffers during draw to reduce locking in non-incremental mode + uint32_t version; // glsl version to use +} gl2wrap_config; + +static struct +{ + float mvp[16], mv[16], pr[16], dummy[16]; + GLenum mode; + float *current; + uint64_t update; +} gl2wrap_matrix; + +static struct +{ + qboolean alpha_test; + qboolean fog; + GLuint vbo; + GLuint tmu; +} gl2wrap_state; + +//#define QUAD_BATCH + +#ifdef QUAD_BATCH +static struct +{ + unsigned int texture; + unsigned int flags; + GLboolean active; +} gl2wrap_quad; +#endif + +static const int gl2wrap_attr_size[GL2_ATTR_MAX] = { 3, 4, 2, 2 }; + +static const char *gl2wrap_flag_name[GL2_FLAG_MAX] = +{ + "ATTR_POSITION", + "ATTR_COLOR", + "ATTR_TEXCOORD0", + "ATTR_TEXCOORD1", + "FEAT_ALPHA_TEST", + "FEAT_FOG", + "ATTR_NORMAL", +}; + +static const char *gl2wrap_attr_name[GL2_ATTR_MAX] = +{ + "inPosition", + "inColor", + "inTexCoord0", + "inTexCoord1", +}; + +#define MB( x, y ) (( x ) ? GL_MAP_##y##_BIT : 0 ) + +static void (APIENTRY *rpglEnable)( GLenum e ); +static void (APIENTRY *rpglDisable)( GLenum e ); +static void (APIENTRY *rpglDrawElements )( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ); +static void (APIENTRY *rpglDrawArrays )( GLenum mode, GLint first, GLsizei count ); +static void (APIENTRY *rpglDrawRangeElements )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); +static void (APIENTRY *rpglBindBufferARB)( GLenum buf, GLuint obj ); + +static void GL2_FreeArrays( void ); + +#ifdef QUAD_BATCH +static void GL2_FlushPrims( void ); +static void (APIENTRY *rpglBindTexture)( GLenum tex, GLuint obj ); +static void APIENTRY GL2_BindTexture( GLenum tex, GLuint obj ) +{ + if( gl2wrap_quad.texture != obj ) + { + GL2_FlushPrims(); + gl2wrap_quad.texture = obj; + } + rpglBindTexture( tex, obj ); +} +#endif + +static char *GL_PrintInfoLog( GLhandleARB object, qboolean program ) +{ + static char msg[8192]; + GLuint maxLength = 0; + + if( program && pglGetProgramiv ) + pglGetProgramiv( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength ); + else + pglGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength ); + + if( maxLength >= sizeof( msg )) + { + //ALERT( at_warning, "GL_PrintInfoLog: message exceeds %i symbols\n", sizeof( msg )); + maxLength = sizeof( msg ) - 1; + } + + if( program && pglGetProgramInfoLog ) + pglGetProgramInfoLog( object, maxLength, &maxLength, msg ); + else + pglGetInfoLogARB( object, maxLength, &maxLength, msg ); + + return msg; +} + + +static GLuint GL2_GenerateShader( gl2wrap_prog_t *prog, GLenum type ) +{ + char *shader, shader_buf[MAX_SHADERLEN + 1]; + char tmp[256]; + int i; + GLint status, len; + GLuint id, loc; + int version = gl2wrap_config.version; + + shader = shader_buf; + //shader[0] = '\n'; + shader[0] = 0; + + Q_snprintf( shader, MAX_SHADERLEN, "#version %d%s\n", version, version >= 300 && version < 330 ? " es" : "" ); + + Q_snprintf( tmp, sizeof( tmp ), "#define VER %d\n", version ); + Q_strncat( shader, tmp, MAX_SHADERLEN ); + + for( i = 0; i < GL2_FLAG_MAX; ++i ) + { + Q_snprintf( tmp, sizeof( tmp ), "#define %s %d\n", gl2wrap_flag_name[i], FBitSet( prog->flags, BIT( i ))); + Q_strncat( shader, tmp, MAX_SHADERLEN ); + } + + if( version >= 310 ) + { + loc = 0; + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + if( FBitSet( prog->flags, BIT( i ))) + { + Q_snprintf( tmp, sizeof( tmp ), "#define LOC_%s %d\n", gl2wrap_flag_name[i], loc++ ); + Q_strncat( shader, tmp, MAX_SHADERLEN ); + prog->attridx[i] = loc; + + } + else + { + prog->attridx[i] = -1; + } + } + } + + if( type == GL_FRAGMENT_SHADER_ARB ) + Q_strncat( shader, gl2wrap_frag_src, MAX_SHADERLEN ); + else + Q_strncat( shader, gl2wrap_vert_src, MAX_SHADERLEN ); + + id = pglCreateShaderObjectARB( type ); + len = Q_strlen( shader ); + pglShaderSourceARB( id, 1, (void *)&shader, &len ); + pglCompileShaderARB( id ); + pglGetObjectParameterivARB( id, GL_OBJECT_COMPILE_STATUS_ARB, &status ); + + if( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "%s( 0x%04x, 0x%x ): compile failed: %s\n", __func__, prog->flags, type, GL_PrintInfoLog( id, false )); + + gEngfuncs.Con_DPrintf( "Shader text:\n%s\n\n", shader ); + pglDeleteObjectARB( id ); + return 0; + } + + return id; +} + +static gl2wrap_prog_t *GL2_GetProg( const GLuint flags ) +{ + int i, loc; + GLuint status = 0, vp, fp, glprog; + gl2wrap_prog_t *prog; + + // try to find existing prog matching this feature set + + if( gl2wrap.cur_prog && gl2wrap.cur_prog->flags == flags ) + return gl2wrap.cur_prog; + + for( i = 0; i < MAX_PROGS; ++i ) + { + if( gl2wrap.progs[i].flags == flags ) + return &gl2wrap.progs[i]; + else if( gl2wrap.progs[i].flags == 0 ) + break; + } + + if( i == MAX_PROGS ) + { + gEngfuncs.Host_Error( "%s: Ran out of program slots for 0x%04x\n", __func__, flags ); + return NULL; + } + + // new prog; generate shaders + + gEngfuncs.Con_DPrintf( S_NOTE "%s: Generating progs for 0x%04x\n", __func__, flags ); + prog = &gl2wrap.progs[i]; + prog->flags = flags; + + vp = GL2_GenerateShader( prog, GL_VERTEX_SHADER_ARB ); + fp = GL2_GenerateShader( prog, GL_FRAGMENT_SHADER_ARB ); + if( !vp || !fp ) + { + prog->flags = 0; + return NULL; + } + + glprog = pglCreateProgramObjectARB(); + pglAttachObjectARB( glprog, vp ); + pglAttachObjectARB( glprog, fp ); + + loc = 0; + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + if( FBitSet( flags, BIT( i ))) + { + prog->attridx[i] = loc; + if( gl2wrap_config.version <= 300 ) + pglBindAttribLocationARB( glprog, loc++, gl2wrap_attr_name[i] ); + else + loc++; + } + else + { + prog->attridx[i] = -1; + } + } + + pglLinkProgramARB( glprog ); + pglDetachObjectARB( glprog, vp ); + pglDetachObjectARB( glprog, fp ); + pglDeleteObjectARB( vp ); + pglDeleteObjectARB( fp ); + +/// TODO: detect arb/core shaders in engine + + if( pglGetProgramiv ) + pglGetProgramiv( glprog, GL_OBJECT_LINK_STATUS_ARB, &status ); + else + pglGetObjectParameterivARB( glprog, GL_OBJECT_LINK_STATUS_ARB, &status ); + + if( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "%s: Failed linking progs for 0x%04x!\n%s\n", __func__, prog->flags, GL_PrintInfoLog( glprog, true )); + prog->flags = 0; + if( pglDeleteProgram ) + pglDeleteProgram( glprog ); + else + pglDeleteObjectARB( glprog ); + return NULL; + } + + prog->ucolor = pglGetUniformLocationARB( glprog, "uColor" ); + prog->ualpha = pglGetUniformLocationARB( glprog, "uAlphaTest" ); + prog->utex0 = pglGetUniformLocationARB( glprog, "uTex0" ); + prog->utex1 = pglGetUniformLocationARB( glprog, "uTex1" ); + prog->ufog = pglGetUniformLocationARB( glprog, "uFog" ); + prog->uMVP = pglGetUniformLocationARB( glprog, "uMVP" ); + + if( gl2wrap_config.vao_mandatory ) + { + prog->vao_begin = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( GLuint )); + pglGenVertexArrays( gl2wrap_config.cycle_buffers, prog->vao_begin ); + } + pglUseProgramObjectARB( glprog ); + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + if( prog->attridx[i] >= 0 ) + { + if( gl2wrap_config.vao_mandatory || gl2wrap_config.incremental ) + { + int j; + + for( j = 0; j < gl2wrap_config.cycle_buffers; j++ ) + { + pglBindVertexArray( prog->vao_begin[j] ); + pglEnableVertexAttribArrayARB( prog->attridx[i] ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] ); + pglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 ); + } + } + } + } + if( gl2wrap_config.vao_mandatory ) + pglBindVertexArray( 0 ); + + // these never change + if( FBitSet( prog->flags, BIT( GL2_ATTR_TEXCOORD0 )) && prog->utex0 >= 0 ) + pglUniform1iARB( prog->utex0, 0 ); + if( FBitSet( prog->flags, BIT( GL2_ATTR_TEXCOORD1 )) && prog->utex1 >= 0 ) + pglUniform1iARB( prog->utex1, 1 ); + if( gl2wrap.cur_prog ) + pglUseProgramObjectARB( gl2wrap.cur_prog->glprog ); + prog->glprog = glprog; + + gEngfuncs.Con_DPrintf( S_NOTE "%s: Generated progs for 0x%04x\n", __func__, flags ); + + return prog; +} + +static void GL2_UpdateMVP( gl2wrap_prog_t *prog ); +static gl2wrap_prog_t *GL2_SetProg( const GLuint flags ) +{ + gl2wrap_prog_t *prog = NULL; + + if( flags && ( prog = GL2_GetProg( flags ))) + { + if( prog != gl2wrap.cur_prog ) + { + pglUseProgramObjectARB( prog->glprog ); + gl2wrap.uchanged = GL_TRUE; + } + if( gl2wrap.uchanged ) + { + if( prog->ualpha >= 0 ) + pglUniform1fARB( prog->ualpha, gl2wrap.alpharef ); + if( prog->ucolor >= 0 ) + pglUniform4fvARB( prog->ucolor, 1, gl2wrap.color ); + if( prog->ufog >= 0 ) + pglUniform4fvARB( prog->ufog, 1, gl2wrap.fog ); + gl2wrap.uchanged = GL_FALSE; + } + GL2_UpdateMVP( prog ); + } + else + { + pglUseProgramObjectARB( 0 ); + } + + gl2wrap.cur_prog = prog; + return prog; +} + +#define TRIQUADS_SIZE GL2_MAX_VERTS / 4 * 6 + +static void GL2_InitTriQuads( void ) +{ + int i; + for( i = 0; i < ( !!pglDrawRangeElementsBaseVertex ? 1 : 4 ); i++ ) + { + int j; + GLushort triquads_array[TRIQUADS_SIZE]; + + for( j = 0; j < TRIQUADS_SIZE / 6; j++ ) + { + triquads_array[j * 6] = j * 4 + i; + triquads_array[j * 6 + 1] = j * 4 + 1 + i; + triquads_array[j * 6 + 2] = j * 4 + 2 + i; + triquads_array[j * 6 + 3] = j * 4 + i; + triquads_array[j * 6 + 4] = j * 4 + 2 + i; + triquads_array[j * 6 + 5] = j * 4 + 3 + i; + } + pglGenBuffersARB( 1, &gl2wrap.triquads_ibo[i] ); + rpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[i] ); + pglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, sizeof( triquads_array ), triquads_array, GL_STATIC_DRAW_ARB ); + } + + rpglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); +} + +static void GL2_InitIncrementalBuffer( int i, GLuint size ) +{ + int j; + + gl2wrap.attrbufobj[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( GLuint )); + if( gl2wrap_config.buf_storage ) + gl2wrap.mappings[i] = Mem_Calloc( r_temppool, gl2wrap_config.cycle_buffers * sizeof( void * )); + pglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] ); + + for( j = 0; j < gl2wrap_config.cycle_buffers; j++ ) + { + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] ); + if( gl2wrap_config.buf_storage ) + { + GLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) | + GL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT ); + pglBufferStorage( GL_ARRAY_BUFFER_ARB, size, NULL, GL_MAP_WRITE_BIT | MB( gl2wrap_config.coherent, COHERENT ) | GL_MAP_PERSISTENT_BIT ); + gl2wrap.mappings[i][j] = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, flags ); + } + else + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, size, NULL, GL_STREAM_DRAW_ARB ); + } + if( gl2wrap_config.buf_storage ) + gl2wrap.attrbuf[i] = gl2wrap.mappings[i][0]; +} + + +static qboolean GL2_InitProgs( void ) +{ + static const GLuint precache_progs[] = { + BIT( GL2_ATTR_POS ), // out = ucolor + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ), // out = tex0 * ucolor + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_ATTR_COLOR ), // out = tex0 * vcolor + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_ALPHA_TEST ), // out = tex0 * ucolor + FEAT_ALPHA_TEST + BIT( GL2_ATTR_POS ) | BIT( GL2_FLAG_FOG ), // out = ucolor + FEAT_FOG + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_FOG ), // out = tex0 * ucolor + FEAT_FOG + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_ATTR_COLOR ) | BIT( GL2_FLAG_FOG ), // out = tex0 * vcolor + FEAT_FOG + BIT( GL2_ATTR_POS ) | BIT( GL2_ATTR_TEXCOORD0 ) | BIT( GL2_FLAG_ALPHA_TEST ) | BIT( GL2_FLAG_FOG ), // out = tex0 * ucolor + FEAT_ALPHA_TEST + FEAT_FOG + }; + const size_t precache_progs_count = sizeof( precache_progs ) / sizeof( precache_progs[0] ); + int i; + + gEngfuncs.Con_DPrintf( S_NOTE "GL2_InitProgs: Pre-generating %u progs, version %d...\n", (uint)( precache_progs_count ), gl2wrap_config.version ); + for( i = 0; i < (int)( precache_progs_count ); ++i ) + if( !GL2_GetProg( precache_progs[i] )) + return false; + return true; +} + + +int GL2_ShimInit( void ) +{ + int i; + GLuint total; + + if( gl2wrap_init ) + return 0; + + if( !pglBindBufferARB ) + { + gEngfuncs.Con_Printf( S_ERROR "GL2_ShimInit: missing VBO, disabling\n" ); + return 1; + } + + if( !pglCompileShaderARB ) + { + gEngfuncs.Con_Printf( S_ERROR "GL2_ShimInit: missing shaders, disabling\n" ); + return 1; + } + + gl2wrap_config.vao_mandatory = gEngfuncs.Sys_CheckParm( "-vao" ) || glConfig.context == CONTEXT_TYPE_GL_CORE; + gl2wrap_config.incremental = true; + gl2wrap_config.async = true; + gl2wrap_config.force_flush = false; + gl2wrap_config.buf_storage = true; + gl2wrap_config.coherent = true; + gl2wrap_config.supports_mapbuffer = true; + gl2wrap_config.cycle_buffers = 4096; + + if( !pglBufferStorage ) + { + gl2wrap_config.buf_storage = false; + gEngfuncs.Con_Printf( S_NOTE "GL2_ShimInit: missing BufferStorage\n" ); + } + + if( !pglMapBufferRange ) + { + gl2wrap_config.incremental = false; + gl2wrap_config.supports_mapbuffer = false; + gEngfuncs.Con_Printf( S_NOTE "GL2_ShimInit: missing MapBufferRange, disabling incremental rendering\n" ); + } + + if( gEngfuncs.Sys_CheckParm( "-nocoherent" )) + gl2wrap_config.coherent = false; + if( gEngfuncs.Sys_CheckParm( "-nobufstor" )) + gl2wrap_config.buf_storage = false; + if( gEngfuncs.Sys_CheckParm( "-noasync" )) + gl2wrap_config.async = false; + if( gEngfuncs.Sys_CheckParm( "-forceflush" )) + gl2wrap_config.force_flush = true; + if( gEngfuncs.Sys_CheckParm( "-nomapbuffer" )) + gl2wrap_config.supports_mapbuffer = false; + if( gEngfuncs.Sys_CheckParm( "-noincremental" )) + gl2wrap_config.incremental = gl2wrap_config.buf_storage = false; + + gl2wrap_config.version = 310; + if( gEngfuncs.Sys_CheckParm( "-minshaders" )) + gl2wrap_config.version = 100; + if( gl2wrap_config.buf_storage ) + gl2wrap_config.incremental = gl2wrap_config.vao_mandatory = true; + if( !pglBindVertexArray || !gl2wrap_config.vao_mandatory ) + gl2wrap_config.incremental = gl2wrap_config.buf_storage = gl2wrap_config.vao_mandatory = false; + if( gl2wrap_config.incremental && !gl2wrap_config.buf_storage ) + gl2wrap_config.async = true; + if( gl2wrap_config.incremental ) + gl2wrap_config.cycle_buffers = 4; + if( !gl2wrap_config.vao_mandatory ) + gl2wrap_config.cycle_buffers = 1; + gEngfuncs.Con_Printf( S_NOTE "GL2_ShimInit: config: %s%s%s%s%s%s%sCYCLE=%d VER=%d\n", + gl2wrap_config.buf_storage ? "BUF_STOR " : "", + gl2wrap_config.buf_storage&&gl2wrap_config.coherent ? "COHERENT " : "", + gl2wrap_config.async ? "ASYNC " : "", + gl2wrap_config.incremental ? "INC " : "", + gl2wrap_config.force_flush ? "FLUSH " : "", + gl2wrap_config.vao_mandatory ? "VAO " : "", + gl2wrap_config.supports_mapbuffer ? "MAP " : "", + gl2wrap_config.cycle_buffers, gl2wrap_config.version ); + + memset( &gl2wrap, 0, sizeof( gl2wrap )); + GL2_ShimInstall(); + GL2_InitTriQuads(); + + gl2wrap.color[0] = 1.f; + gl2wrap.color[1] = 1.f; + gl2wrap.color[2] = 1.f; + gl2wrap.color[3] = 1.f; + gl2wrap.uchanged = GL_TRUE; + + total = 0; + + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + GLuint size = GL2_MAX_VERTS * gl2wrap_attr_size[i] * sizeof( GLfloat ); + if( !gl2wrap_config.buf_storage ) + { + gl2wrap.attrbuf[i] = Mem_Calloc( r_temppool, size ); + } + + if( gl2wrap_config.incremental ) + { + GL2_InitIncrementalBuffer( i, size ); + } + else + { + if( !gl2wrap_config.incremental && gl2wrap_config.vao_mandatory ) + { + gl2wrap.attrbufobj[i] = malloc( gl2wrap_config.cycle_buffers * 4 ); + pglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] ); + if( gl2wrap_config.supports_mapbuffer ) + { + int j; + + for( j = 0; j < gl2wrap_config.cycle_buffers; j++ ) + { + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, MAX_BEGINEND_VERTS, NULL, GL_STREAM_DRAW_ARB ); + } + } + } + } + + total += size; + } + if( gl2wrap_config.vao_mandatory ) + pglBindVertexArray( 0 ); + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + + gEngfuncs.Con_DPrintf( S_NOTE "%s: %u bytes allocated for vertex buffer\n", __func__, total ); + + if( !GL2_InitProgs( )) + { + gl2wrap_config.version = 300; + if( !GL2_InitProgs( )) + { + gl2wrap_config.version = 110; + if( !GL2_InitProgs( )) + { + gl2wrap_config.version = 100; + if( !GL2_InitProgs( )) + gEngfuncs.Host_Error( "%s: Failed to compile shaders!\n", __func__ ); + } + } + } + + gl2wrap_init = 1; + return 0; +} + +void GL2_ShimShutdown( void ) +{ + int i; + + if( !gl2wrap_init ) + return; + + pglFinish(); + pglUseProgramObjectARB( 0 ); + GL2_FreeArrays(); + pglDeleteBuffersARB(( !!pglDrawRangeElementsBaseVertex ? 1 : 4 ), gl2wrap.triquads_ibo ); + + for( i = 0; i < MAX_PROGS; ++i ) + { + if( gl2wrap.progs[i].flags ) + { + pglDeleteProgram( gl2wrap.progs[i].glprog ); + if( gl2wrap.progs[i].vao_begin ) + { + pglDeleteVertexArrays( gl2wrap_config.cycle_buffers, gl2wrap.progs[i].vao_begin ); + Mem_Free( gl2wrap.progs[i].vao_begin ); + } + } + + } + + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + int j; + if( gl2wrap_config.buf_storage ) + { + for( j = 0; j < gl2wrap_config.cycle_buffers; j++ ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][j] ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + } + if( gl2wrap.attrbufobj[i] ) + { + pglDeleteBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap.attrbufobj[i] ); + Mem_Free( gl2wrap.attrbufobj[i] ); + } + if( gl2wrap.mappings[i] ) + Mem_Free( gl2wrap.mappings[i] ); + + if( !gl2wrap_config.buf_storage ) + Mem_Free( gl2wrap.attrbuf[i] ); + } + + memset( &gl2wrap, 0, sizeof( gl2wrap )); + + gl2wrap_init = 0; +} + +static void GL2_ResetPersistentBuffer( void ) +{ + int i; + +#ifdef QUAD_BATCH + GL2_FlushPrims(); +#endif + gl2wrap.end = gl2wrap.begin = 0; + + if( gl2wrap_config.incremental ) + { + gl2wrap.attrbufcycle = ( gl2wrap.attrbufcycle + 1 ) % gl2wrap_config.cycle_buffers; + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + int size = GL2_MAX_VERTS * gl2wrap_attr_size[i] * sizeof( GLfloat ); + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] ); + if( gl2wrap_config.buf_storage ) + { + GLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) | + GL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + gl2wrap.mappings[i][gl2wrap.attrbufcycle] = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, flags ); + gl2wrap.attrbuf[i] = gl2wrap.mappings[i][gl2wrap.attrbufcycle]; + } + else + { + void *mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT ); + (void)mem; + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + + } + } +} + + +void GL2_ShimEndFrame( void ) +{ +#ifdef QUAD_BATCH + GL2_FlushPrims(); +#endif +} + +static void APIENTRY GL2_Begin( GLenum prim ) +{ + int i; + if( gl2wrap.begin + MAX_BEGINEND_VERTS > GL2_MAX_VERTS ) + GL2_ResetPersistentBuffer(); + +#ifdef QUAD_BATCH + if( gl2wrap.prim == GL_QUADS && gl2wrap_quad.active ) + { + GLuint flags = gl2wrap.cur_flags; + GLuint flags2 = gl2wrap.cur_flags; + + if( gl2wrap_quad.flags != flags || prim != GL_QUADS ) + GL2_FlushPrims(); + else if( gl2wrap_quad.flags == flags && prim == GL_QUADS ) + return; + } + gl2wrap_quad.active = false; +#endif + gl2wrap.prim = prim; + gl2wrap.begin = gl2wrap.end; + // pos always enabled + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_POS )); +} + +/* +============================== +UpdateIncrementalBuffer + +glBufferStorage allows to write directly without mapping buffer every time before draw +This allows keep VAO unchanged and fastly build needed pipeline in driver for every program variant +When buffer storage not supported, we still may use cached VAO, but map/unmap it every time writing new data +============================== +*/ +static void GL2_UpdateIncrementalBuffer( gl2wrap_prog_t *prog, int count ) +{ + int i; + if( !gl2wrap_config.buf_storage ) + { + for( i = 0; i < GL2_ATTR_MAX; i++ ) + { + if( prog->attridx[i] >= 0 ) + { + void *mem; + GLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | + MB( gl2wrap_config.async, UNSYNCHRONIZED ) | + MB( gl2wrap_config.force_flush, FLUSH_EXPLICIT ); + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] ); + mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count, flags ); + memcpy( mem, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count ); + if( gl2wrap_config.force_flush ) + pglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + } + } + else if( !gl2wrap_config.coherent ) + { + // non-coherent buffers anyway require unmapping or flushing after write + for( i = 0; i < GL2_ATTR_MAX; i++ ) + { + if( prog->attridx[i] >= 0 ) + { + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] ); + pglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count ); + } + } + } +} + +static void GL2_FlushPrims( void ) +{ + int i; + int startindex = 0; + GLuint flags = gl2wrap.cur_flags; + GLint count = gl2wrap.end - gl2wrap.begin; + gl2wrap_prog_t *prog; + + if( !gl2wrap.prim || !count ) + goto leave_label; // end without begin + + // enable alpha test and fog if needed + if( gl2wrap_state.alpha_test ) + SetBits( flags, BIT( GL2_FLAG_ALPHA_TEST )); + if( gl2wrap_state.fog ) + SetBits( flags, BIT( GL2_FLAG_FOG )); + + // disable all vertex attrib pointers + if( !gl2wrap_config.vao_mandatory ) + { + for( i = 0; i < GL2_ATTR_MAX; ++i ) + pglDisableVertexAttribArrayARB( i ); + } + + prog = GL2_SetProg( flags ); + if( !prog ) + { + gEngfuncs.Host_Error( "%s: Could not find program for flags 0x%04x!\n", __func__, flags ); + goto leave_label; + } + + if( gl2wrap_config.incremental ) + { + GL2_UpdateIncrementalBuffer( prog, count ); + pglBindVertexArray( prog->vao_begin[gl2wrap.attrbufcycle] ); + startindex = gl2wrap.begin; + } + else + { + if( gl2wrap_config.vao_mandatory ) + pglBindVertexArray( prog->vao_begin[gl2wrap.attrbufcycle] ); + for( i = 0; i < GL2_ATTR_MAX; ++i ) + { + if( prog->attridx[i] >= 0 ) + { + if(( gl2wrap_config.vao_mandatory && !gl2wrap_config.supports_mapbuffer ) || !gl2wrap_config.vao_mandatory ) + pglEnableVertexAttribArrayARB( prog->attridx[i] ); + if( gl2wrap_config.vao_mandatory ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap.attrbufobj[i][gl2wrap.attrbufcycle] ); + if( gl2wrap_config.supports_mapbuffer ) + { + if( gl2wrap_attr_size[i] * 4 * count > MAX_BEGINEND_VERTS ) + { + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * count, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, GL_STREAM_DRAW_ARB ); + pglEnableVertexAttribArrayARB( prog->attridx[i] ); + pglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 ); + } + else + { + GLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | + MB( gl2wrap_config.async, UNSYNCHRONIZED ) | MB( gl2wrap_config.force_flush, FLUSH_EXPLICIT ); + void *mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, gl2wrap_attr_size[i] * 4 * count, flags ); + memcpy( mem, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, gl2wrap_attr_size[i] * 4 * count ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + } + else + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, gl2wrap_attr_size[i] * 4 * count, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin, GL_STREAM_DRAW_ARB ); + + if( gl2wrap_config.vao_mandatory && !gl2wrap_config.supports_mapbuffer ) + pglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, 0 ); + + } + else // if vao is not mandatory, try use client pointers here + pglVertexAttribPointerARB( prog->attridx[i], gl2wrap_attr_size[i], GL_FLOAT, GL_FALSE, 0, gl2wrap.attrbuf[i] + gl2wrap_attr_size[i] * gl2wrap.begin ); + } + } + gl2wrap.attrbufcycle = ( gl2wrap.attrbufcycle + 1 ) % gl2wrap_config.cycle_buffers; + } + + if( gl2wrap.prim == GL_QUADS ) + { + // simple case, one quad may draw like polygon(4) + if( count == 4 ) + rpglDrawArrays( GL_TRIANGLE_FAN, startindex, count ); + else if( pglDrawRangeElementsBaseVertex ) + { + /* + * OpenGL deprecated QUADS, but made some workarounds availiable + * idea: bound static index array that will repeat 0 1 2 0 2 3 4 5 6 4 6 7... + * sequence and draw source arrays. But our array may have different offset + * When DrawRangeElementsBaseVertex unavailiable, we need build 4 different index arrays (as sequence have period 4) + * or just put 0-4 offset when it's availiable + * */ + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[0] ); + pglDrawRangeElementsBaseVertex( GL_TRIANGLES, startindex, startindex + count, + Q_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT, + (void *)(size_t)( startindex / 4 * 6 * 2 ), startindex % 4 ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + else if( rpglDrawRangeElements ) + { + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[startindex % 4] ); + rpglDrawRangeElements( GL_TRIANGLES, startindex, startindex + count, + Q_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT, + (void *)(size_t)( startindex / 4 * 6 * 2 )); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + else + { + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, gl2wrap.triquads_ibo[startindex % 4] ); + rpglDrawElements( GL_TRIANGLES, + Q_min( count / 4 * 6, TRIQUADS_SIZE * 6 - startindex ), GL_UNSIGNED_SHORT, + (void *)(size_t)( startindex / 4 * 6 * 2 )); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + } + } + else if( gl2wrap.prim == GL_POLYGON ) // does it have any difference with TRIFAN? + rpglDrawArrays( GL_TRIANGLE_FAN, startindex, count ); + else // TRIANGLES, LINES, TRISTRIP, TRIFAN supported anyway + rpglDrawArrays( gl2wrap.prim, startindex, count ); + +leave_label: + if( gl2wrap_config.vao_mandatory ) + { + pglBindVertexArray( 0 ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + } + + gl2wrap.prim = GL_NONE; + gl2wrap.begin = gl2wrap.end; + gl2wrap.cur_flags = 0; +#ifdef QUAD_BATCH + gl2wrap_quad.active = 0; +#endif +} + + +static void APIENTRY GL2_End( void ) +{ + int i; +#ifdef QUAD_BATCH + if( gl2wrap.prim == GL_QUADS ) + { + GLuint flags = gl2wrap.cur_flags; + // enable alpha test and fog if needed + /*if( alpha_test_state ) + SetBits( flags, BIT( GL2_FLAG_ALPHA_TEST )); + if( fogging ) + SetBits( flags, BIT( GL2_FLAG_FOG ));*/ + gl2wrap_quad.flags = flags; + gl2wrap_quad.active = 1; + return; + } +#endif + GL2_FlushPrims(); +} + +static void (APIENTRY *rpglTexImage2D)( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); +static void APIENTRY GL2_TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) +{ + void *data = (void *)pixels; + if( pixels && format == GL_RGBA && ( + internalformat == GL_RGB || + internalformat == GL_RGB8 || + internalformat == GL_RGB5 || + internalformat == GL_LUMINANCE || + internalformat == GL_LUMINANCE8 || + internalformat == GL_LUMINANCE4 )) // strip alpha from texture + { + unsigned char *in = data, *out; + int i = 0, size = width * height * 4; + + data = out = (unsigned char *)malloc( size ); + + for( i = 0; i < size; i += 4, in += 4, out += 4 ) + { + memcpy( out, in, 3 ); + out[3] = 255; + } + internalformat = format; + } + if( internalformat == GL_LUMINANCE8_ALPHA8 || internalformat == GL_RGB || internalformat == GL_RGB8 || internalformat == GL_RGB5 ) + internalformat = GL_RGBA; + rpglTexImage2D( target, level, internalformat, width, height, border, format, type, data ); + if( data != pixels ) + free( data ); +} + +static void (APIENTRY *rpglTexParameteri)( GLenum target, GLenum pname, GLint param ); +static void APIENTRY GL2_TexParameteri( GLenum target, GLenum pname, GLint param ) +{ + if( pname == GL_TEXTURE_BORDER_COLOR ) + { + return; // not supported by opengl es + } + if(( pname == GL_TEXTURE_WRAP_S || + pname == GL_TEXTURE_WRAP_T ) && + param == GL_CLAMP ) + { + param = GL_CLAMP_TO_EDGE; + } + + rpglTexParameteri( target, pname, param ); +} + + +GLboolean (APIENTRY *rpglIsEnabled)( GLenum e ); +static GLboolean APIENTRY GL2_IsEnabled( GLenum e ) +{ + if( e == GL_FOG ) + return gl2wrap_state.fog; + return rpglIsEnabled( e ); +} + +static void APIENTRY GL2_Vertex3f( GLfloat x, GLfloat y, GLfloat z ) +{ + GLfloat *p = gl2wrap.attrbuf[GL2_ATTR_POS] + gl2wrap.end * 3; + *p++ = x; + *p++ = y; + *p++ = z; + + if( FBitSet( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR ))) + { + GLfloat *p = gl2wrap.attrbuf[GL2_ATTR_COLOR] + gl2wrap.end * 4; + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR )); + *p++ = gl2wrap.color[0]; + *p++ = gl2wrap.color[1]; + *p++ = gl2wrap.color[2]; + *p++ = gl2wrap.color[3]; + } + ++gl2wrap.end; + + if( gl2wrap.prim == GL_QUADS ) + { + if( !( ( gl2wrap.end - gl2wrap.begin ) % 4 ) && gl2wrap.end > ( GL2_MAX_VERTS - 4 ) ) + { + GL2_FlushPrims(); + GL2_Begin( GL_QUADS ); + } + } + else if( gl2wrap.end - gl2wrap.begin >= MAX_BEGINEND_VERTS ) + { + GLenum prim = gl2wrap.prim; + gEngfuncs.Con_DPrintf( S_ERROR "GL2_Vertex3f: Vertex buffer overflow!\n" ); + GL2_FlushPrims(); + GL2_Begin( prim ); + } +} + +static void APIENTRY GL2_Vertex2f( GLfloat x, GLfloat y ) +{ + GL2_Vertex3f( x, y, 0.f ); +} + +static void APIENTRY GL2_Vertex3fv( const GLfloat *v ) +{ + GL2_Vertex3f( v[0], v[1], v[2] ); +} + +static void APIENTRY GL2_Color4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a ) +{ +#ifdef QUAD_BATCH + if( gl2wrap_quad.active ) + { + if( !( gl2wrap.color[0] == r && gl2wrap.color[1] == g && gl2wrap.color[2] == b && gl2wrap.color[3] == a )) + GL2_FlushPrims(); + } +#endif + gl2wrap.color[0] = r; + gl2wrap.color[1] = g; + gl2wrap.color[2] = b; + gl2wrap.color[3] = a; + gl2wrap.uchanged = GL_TRUE; +#ifdef QUAD_BATCH + if( gl2wrap_quad.active ) + return; +#endif + if( gl2wrap.prim ) + { + // HACK: enable color attribute if we're using color inside a Begin-End pair + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_COLOR )); + } +} + +static void APIENTRY GL2_Color3f( GLfloat r, GLfloat g, GLfloat b ) +{ + GL2_Color4f( r, g, b, 1.f ); +} + +static void APIENTRY GL2_Color4ub( GLubyte r, GLubyte g, GLubyte b, GLubyte a ) +{ + GL2_Color4f((GLfloat)r / 255.f, (GLfloat)g / 255.f, (GLfloat)b / 255.f, (GLfloat)a / 255.f ); +} + +static void APIENTRY GL2_Color4ubv( const GLubyte *v ) +{ + GL2_Color4ub( v[0], v[1], v[2], v[3] ); +} + +static void APIENTRY GL2_TexCoord2f( GLfloat u, GLfloat v ) +{ + // by spec glTexCoord always updates texunit 0 + GLfloat *p = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD0] + gl2wrap.end * 2; + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD0 )); + *p++ = u; + *p++ = v; +} + +static void APIENTRY GL2_MultiTexCoord2f( GLenum tex, GLfloat u, GLfloat v ) +{ + GLfloat *p; + + // assume there can only be two + if( tex == GL_TEXTURE0_ARB ) + { + p = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD0] + gl2wrap.end * 2; + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD0 )); + } + else + { + p = gl2wrap.attrbuf[GL2_ATTR_TEXCOORD1] + gl2wrap.end * 2; + SetBits( gl2wrap.cur_flags, BIT( GL2_ATTR_TEXCOORD1 )); + } + *p++ = u; + *p++ = v; +} + + +static void APIENTRY GL2_AlphaFunc( GLenum mode, GLfloat ref ) +{ + gl2wrap.alpharef = ref; + gl2wrap.uchanged = GL_TRUE; + // mode is always GL_GREATER +} + +static void APIENTRY GL2_Fogf( GLenum param, GLfloat val ) +{ + if( param == GL_FOG_DENSITY ) + { + gl2wrap.fog[3] = val; + gl2wrap.uchanged = GL_TRUE; + } +} + +static void APIENTRY GL2_Fogfv( GLenum param, const GLfloat *val ) +{ + if( param == GL_FOG_COLOR ) + { + gl2wrap.fog[0] = val[0]; + gl2wrap.fog[1] = val[1]; + gl2wrap.fog[2] = val[2]; + gl2wrap.uchanged = GL_TRUE; + } +} + +static qboolean GL2_SkipEnable( GLenum e ) +{ + return e == GL_TEXTURE_2D || e == GL_TEXTURE_1D; +} + +static qboolean GL2_CatchEnable( GLenum e, qboolean enable ) +{ + if( e == GL_FOG ) + gl2wrap_state.fog = enable; + else if( e == GL_ALPHA_TEST ) + gl2wrap_state.alpha_test = enable; + else + return false; + return true; +} + +static void APIENTRY GL2_Enable( GLenum e ) +{ + if( !GL2_SkipEnable( e ) && !GL2_CatchEnable( e, true )) + rpglEnable( e ); +} + + +static void APIENTRY GL2_Disable( GLenum e ) +{ + if( !GL2_SkipEnable( e ) && !GL2_CatchEnable( e, false )) + rpglDisable( e ); +} + +/* +=========================== + +Limited matrix emulation + +=========================== +*/ + +static void APIENTRY GL2_MatrixMode( GLenum m ) +{ +// if( gl2wrap_matrix.mode == m ) +// return; +#ifdef QUAD_BATCH + GL2_FlushPrims(); +#endif + gl2wrap_matrix.mode = m; + switch( m ) + { + case GL_MODELVIEW: + gl2wrap_matrix.current = gl2wrap_matrix.mv; + break; + case GL_PROJECTION: + gl2wrap_matrix.current = gl2wrap_matrix.pr; + break; + default: + gl2wrap_matrix.current = gl2wrap_matrix.dummy; + break; + } +} + +static void APIENTRY GL2_LoadIdentity( void ) +{ + float *m = (float *)gl2wrap_matrix.current; + m[1] = m[2] = m[3] = m[4] = 0.0f; + m[6] = m[7] = m[8] = m[9] = 0.0f; + m[11] = m[12] = m[13] = m[14] = 0.0f; + m[0] = m[5] = m[10] = m[15] = 1.0f; + gl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF; +} + +static void APIENTRY GL2_Ortho( double l, double r, double b, double t, double n, double f ) +{ + GLfloat m0 = 2 / ( r - l ); + GLfloat m5 = 2 / ( t - b ); + GLfloat m10 = - 2 / ( f - n ); + GLfloat m12 = - ( r + l ) / ( r - l ); + GLfloat m13 = - ( t + b ) / ( t - b ); + GLfloat m14 = - ( f + n ) / ( f - n ); + float *m = gl2wrap_matrix.current; + + m[12] += m12 * m[0] + m13 * m[4] + m14 * m[8]; + m[13] += m12 * m[1] + m13 * m[5] + m14 * m[9]; + m[14] += m12 * m[2] + m13 * m[6] + m14 * m[10]; + m[15] += m12 * m[3] + m13 * m[7] + m14 * m[11]; + m[0] *= m0; + m[1] *= m0; + m[2] *= m0; + m[3] *= m0; + m[4] *= m5; + m[5] *= m5; + m[6] *= m5; + m[7] *= m5; + m[8] *= m10; + m[9] *= m10; + m[10] *= m10; + m[11] *= m10; + gl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF; +} + +static void GL2_Mul4x4( const GLfloat *in0, const GLfloat *in1, GLfloat *out ) +{ + out[0] = in0[0] * in1[0] + in0[1] * in1[4] + in0[2] * in1[8] + in0[3] * in1[12]; + out[1] = in0[0] * in1[1] + in0[1] * in1[5] + in0[2] * in1[9] + in0[3] * in1[13]; + out[2] = in0[0] * in1[2] + in0[1] * in1[6] + in0[2] * in1[10] + in0[3] * in1[14]; + out[3] = in0[0] * in1[3] + in0[1] * in1[7] + in0[2] * in1[11] + in0[3] * in1[15]; + out[4] = in0[4] * in1[0] + in0[5] * in1[4] + in0[6] * in1[8] + in0[7] * in1[12]; + out[5] = in0[4] * in1[1] + in0[5] * in1[5] + in0[6] * in1[9] + in0[7] * in1[13]; + out[6] = in0[4] * in1[2] + in0[5] * in1[6] + in0[6] * in1[10] + in0[7] * in1[14]; + out[7] = in0[4] * in1[3] + in0[5] * in1[7] + in0[6] * in1[11] + in0[7] * in1[15]; + out[8] = in0[8] * in1[0] + in0[9] * in1[4] + in0[10] * in1[8] + in0[11] * in1[12]; + out[9] = in0[8] * in1[1] + in0[9] * in1[5] + in0[10] * in1[9] + in0[11] * in1[13]; + out[10] = in0[8] * in1[2] + in0[9] * in1[6] + in0[10] * in1[10] + in0[11] * in1[14]; + out[11] = in0[8] * in1[3] + in0[9] * in1[7] + in0[10] * in1[11] + in0[11] * in1[15]; + out[12] = in0[12] * in1[0] + in0[13] * in1[4] + in0[14] * in1[8] + in0[15] * in1[12]; + out[13] = in0[12] * in1[1] + in0[13] * in1[5] + in0[14] * in1[9] + in0[15] * in1[13]; + out[14] = in0[12] * in1[2] + in0[13] * in1[6] + in0[14] * in1[10] + in0[15] * in1[14]; + out[15] = in0[12] * in1[3] + in0[13] * in1[7] + in0[14] * in1[11] + in0[15] * in1[15]; +} + +static void GL2_UpdateMVP( gl2wrap_prog_t *prog ) +{ + // use bitset to determine if need update matrix for this prog + if( FBitSet( gl2wrap_matrix.update, BIT64( prog->flags ))) + { + ClearBits( gl2wrap_matrix.update, BIT64( prog->flags )); + GL2_Mul4x4( gl2wrap_matrix.mv, gl2wrap_matrix.pr, gl2wrap_matrix.mvp ); + pglUniformMatrix4fvARB( prog->uMVP, 1, false, (void *)gl2wrap_matrix.mvp ); + } +} + +static void APIENTRY GL2_LoadMatrixf( const GLfloat *m ) +{ + memcpy( gl2wrap_matrix.current, m, 16 * sizeof( float )); + gl2wrap_matrix.update = 0xFFFFFFFFFFFFFFFF; +} + +#if XASH_GLES +static void ( APIENTRY *_pglDepthRangef)( GLfloat zFar, GLfloat zNear ); +static void APIENTRY GL2_DepthRange( GLdouble zFar, GLdouble zNear ) +{ + _pglDepthRangef( zFar, zNear ); +} +#endif + +/* +===================== + +Array drawing + +===================== +*/ +typedef struct gl2wrap_arraypointer_s +{ + const void *userptr; + GLint size; + GLenum type; + GLsizei stride; + GLuint vbo, *vbo_fb, vbo_cycle; +} gl2wrap_arraypointer_t; + +static struct +{ + gl2wrap_arraypointer_t ptr[GL2_ATTR_MAX]; + unsigned int flags; + //unsigned int vbo_flags; + GLuint stream_buffer; + void *stream_pointer; + size_t stream_counter; + GLuint vao_dynamic; +} gl2wrap_arrays; + +static void GL2_SetPointer( int idx, GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + gl2wrap_arrays.ptr[idx].size = size; + gl2wrap_arrays.ptr[idx].type = type; + gl2wrap_arrays.ptr[idx].stride = stride; + gl2wrap_arrays.ptr[idx].userptr = pointer; + gl2wrap_arrays.ptr[idx].vbo = gl2wrap_state.vbo; +// if( vbo ) +// SetBits( gl2wrap_arrays.vbo_flags, BIT( idx ); +// else +// ClearBits( gl2wrap_arrays.vbo_flags, BIT( idx ); +} + +static void APIENTRY GL2_VertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + GL2_SetPointer( GL2_ATTR_POS, size, type, stride, pointer ); +} + +static void APIENTRY GL2_ColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + GL2_SetPointer( GL2_ATTR_COLOR, size, type, stride, pointer ); +} + +static void APIENTRY GL2_TexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) +{ + GL2_SetPointer( GL2_ATTR_TEXCOORD0 + gl2wrap_state.tmu, size, type, stride, pointer ); +} + +static unsigned int GL2_GetArrIdx( GLenum array ) +{ + switch( array ) + { + case GL_VERTEX_ARRAY: + return GL2_ATTR_POS; + case GL_COLOR_ARRAY: + return GL2_ATTR_COLOR; + case GL_TEXTURE_COORD_ARRAY: + Assert( gl2wrap_state.tmu < 2 ); + return GL2_ATTR_TEXCOORD0 + gl2wrap_state.tmu; + } + return 0; +} + +static void APIENTRY GL2_EnableClientState( GLenum array ) +{ + unsigned int idx = GL2_GetArrIdx( array ); + SetBits( gl2wrap_arrays.flags, BIT( idx )); +} + +static void APIENTRY GL2_DisableClientState( GLenum array ) +{ + unsigned int idx = GL2_GetArrIdx( array ); + ClearBits( gl2wrap_arrays.flags, BIT( idx )); +} + + +/* +=========================== +UploadBufferData + +Dumb buffer upload +Used when uploading very large buffers or when persistent/incremental buffers disabled (MapBuffer unavailiable?) +=========================== + */ +static void GL2_UploadBufferData( gl2wrap_prog_t *prog, int size, GLuint start, GLuint end, int stride, int attr ) +{ + if( !gl2wrap_arrays.ptr[attr].vbo_fb ) + { + gl2wrap_arrays.ptr[attr].vbo_fb = malloc( 4 * gl2wrap_config.cycle_buffers ); + pglGenBuffersARB( gl2wrap_config.cycle_buffers, gl2wrap_arrays.ptr[attr].vbo_fb ); + } + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.ptr[attr].vbo_fb[gl2wrap_arrays.ptr[attr].vbo_cycle] ); + gl2wrap_arrays.ptr[attr].vbo_cycle = ( gl2wrap_arrays.ptr[attr].vbo_cycle + 1 ) % gl2wrap_config.cycle_buffers; + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, end * stride, gl2wrap_arrays.ptr[attr].userptr, GL_STREAM_DRAW_ARB ); + pglVertexAttribPointerARB( prog->attridx[attr], gl2wrap_arrays.ptr[attr].size, gl2wrap_arrays.ptr[attr].type, attr == GL2_ATTR_COLOR, gl2wrap_arrays.ptr[attr].stride, 0 ); +} +/* +=========================== +UpdatePersistentArrayBuffer + +Persistent array always mapped to stream_pointer with BufferStorage +just memcopy it into and flush when overflowed +=========================== + */ +static void GL2_UpdatePersistentArrayBuffer( gl2wrap_prog_t *prog, int size, int offset, GLuint start, GLuint end, int stride, int attr ) +{ + if( gl2wrap_arrays.stream_counter + size > GL2_MAX_VERTS * 64 ) + { + GLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) | + GL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + gl2wrap_arrays.stream_counter = 0; + gl2wrap_arrays.stream_pointer = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GL2_MAX_VERTS * 64, flags ); + //i = -1; + //continue; + size = end * stride, offset = 0; + } + + memcpy(((char *)gl2wrap_arrays.stream_pointer ) + gl2wrap_arrays.stream_counter, ((char *)gl2wrap_arrays.ptr[attr].userptr ) + offset, size ); + if( !gl2wrap_config.coherent ) + pglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_counter, size ); + pglVertexAttribPointerARB( 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 )); + gl2wrap_arrays.stream_counter += size; +} + +/* +=========================== +UpdateIncrementalArrayBuffer + +Like persistent buffer, but map every time when copying data when BufferStorage unavailiable +=========================== + */ +static void GL2_UpdateIncrementalArrayBuffer( gl2wrap_prog_t *prog, int size, int offset, GLuint start, GLuint end, int stride, int attr ) +{ + void *mem; + qboolean inv = false; + GLuint flags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | MB( inv,INVALIDATE_BUFFER ) | + MB( gl2wrap_config.async, UNSYNCHRONIZED ) | MB( gl2wrap_config.force_flush, FLUSH_EXPLICIT ); + + if( gl2wrap_arrays.stream_counter + size > GL2_MAX_VERTS * 64 ) + { + size = end * stride; + offset = 0; + gl2wrap_arrays.stream_counter = 0; + inv = true; + } + mem = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_counter, size, flags ); + memcpy( mem, ((char *)gl2wrap_arrays.ptr[attr].userptr ) + offset, size ); + if( gl2wrap_config.force_flush ) + pglFlushMappedBufferRange( GL_ARRAY_BUFFER_ARB, 0, size ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + pglVertexAttribPointerARB( 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 )); + gl2wrap_arrays.stream_counter += size; +} + +/* +=========================== +AllocArrayPersistenStorage + +Prepare BufferStorage +=========================== + */ +static void GL2_AllocArrayPersistenStorage( void ) +{ + GLuint flags = GL_MAP_WRITE_BIT | MB( !gl2wrap_config.coherent, FLUSH_EXPLICIT ) | + GL_MAP_PERSISTENT_BIT | MB( gl2wrap_config.coherent, COHERENT ); + pglGenBuffersARB( 1, &gl2wrap_arrays.stream_buffer ); + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer ); + pglBufferStorage( GL_ARRAY_BUFFER_ARB, GL2_MAX_VERTS * 64, NULL, GL_MAP_WRITE_BIT | MB( gl2wrap_config.coherent, COHERENT ) | GL_MAP_PERSISTENT_BIT ); + gl2wrap_arrays.stream_pointer = pglMapBufferRange( GL_ARRAY_BUFFER_ARB, 0, GL2_MAX_VERTS * 64, flags ); +} + +static void GL2_AllocArrays( void ) +{ + if( gl2wrap_config.buf_storage && !gl2wrap_arrays.stream_pointer ) + GL2_AllocArrayPersistenStorage(); + else if( !gl2wrap_config.buf_storage && gl2wrap_config.incremental && !gl2wrap_arrays.stream_buffer ) + { + // prepare incremental buffer + pglGenBuffersARB( 1, &gl2wrap_arrays.stream_buffer ); + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, GL2_MAX_VERTS * 64, NULL, GL_STREAM_DRAW_ARB ); + } +} + +static void GL2_FreeArrays( void ) +{ + if( gl2wrap_arrays.vao_dynamic ) + pglDeleteVertexArrays( 1, &gl2wrap_arrays.vao_dynamic ); + if( gl2wrap_arrays.stream_pointer ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer ); + pglUnmapBufferARB( GL_ARRAY_BUFFER_ARB ); + } + pglDeleteBuffersARB( 1, &gl2wrap_arrays.stream_buffer ); + memset( &gl2wrap_arrays, 0, sizeof( gl2wrap_arrays )); +} + + +/* +====================== +SetupArrays + +If vao usage mandatory, use persistent/incremental buffers when possible +else just set client pointers to default VAO +Usage of client pointers is forbidden with non-default VAO and unavailiable in Core +====================== +*/ +static void GL2_SetupArrays( GLuint start, GLuint end ) +{ + gl2wrap_prog_t *prog; + unsigned int flags = gl2wrap_arrays.flags; + int i; + + if( !flags ) + return; // Legacy pointers not used + +#ifdef QUAD_BATCH + GL2_FlushPrims(); +#endif + + if( gl2wrap_state.alpha_test ) + SetBits( flags, BIT( GL2_FLAG_ALPHA_TEST )); + if( gl2wrap_state.fog ) + SetBits( flags, BIT( GL2_FLAG_FOG )); + prog = GL2_SetProg( flags );// | GL2_ATTR_TEXCOORD0 ); + if( !prog ) + return; + + if( gl2wrap_config.vao_mandatory ) + { + if( !gl2wrap_arrays.vao_dynamic ) + pglGenVertexArrays( 1, &gl2wrap_arrays.vao_dynamic ); + pglBindVertexArray( gl2wrap_arrays.vao_dynamic ); + } + + for( i = 0; i < GL2_ATTR_MAX; i++ ) + { + if( prog->attridx[i] < 0 ) + continue; + if( FBitSet( flags, BIT( i ))) // attribute is enabled + { + pglEnableVertexAttribArrayARB( prog->attridx[i] ); + // sometimes usage of client pointers may be faster, sometimes not + // anyway gl core disallows that, so try use streaming + if( gl2wrap_config.vao_mandatory && !gl2wrap_arrays.ptr[i].vbo ) + { + // detect stride by type + int stride = gl2wrap_arrays.ptr[i].stride, size, offset; + + if( stride == 0 ) + { + if( gl2wrap_arrays.ptr[i].type == GL_UNSIGNED_BYTE ) + stride = gl2wrap_arrays.ptr[i].size; + else + stride = gl2wrap_arrays.ptr[i].size * 4; + } + + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.stream_buffer ); + if( !end ) + { + // we cannot handle this case in VAO without known buffer length + // only workaround is scanning index array to determine buffer limits, but it is slow, + // so just do not use DrawElements when DrawRangeElements availiable + pglDisableVertexAttribArrayARB( prog->attridx[i] ); + gEngfuncs.Con_Printf( S_ERROR "NON-vbo array for DrawElements call, SKIPPING!\n" ); + continue; + } + size = ( end - start ) * stride; + offset = start * stride; + + // Logical buffer start can lie before real buffer start + // but attrib pointer cannot have negative buffer offset + if( gl2wrap_arrays.stream_counter < offset ) + size = end * stride, offset = 0; + + if(( !gl2wrap_config.buf_storage && !gl2wrap_config.incremental ) || size > GL2_MAX_VERTS * 32 ) + { + GL2_UploadBufferData( prog, size, start, end, stride, i ); + continue; + } + if( !gl2wrap_config.buf_storage && gl2wrap_config.incremental ) + { + GL2_UpdateIncrementalArrayBuffer( prog, size, offset, start, end, stride, i ); + continue; + } + GL2_UpdatePersistentArrayBuffer( prog, size, offset, start, end, stride, i ); + } + else + { + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_arrays.ptr[i].vbo ); + pglVertexAttribPointerARB( 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 ); + } + /* + if( i == GL2_ATTR_TEXCOORD0 ) + pglUniform1iARB( prog->utex0, 0 ); + if( i == GL2_ATTR_TEXCOORD1 ) + pglUniform1iARB( prog->utex1, 1 ); + */ + } + else + { + pglDisableVertexAttribArrayARB( prog->attridx[i] ); + } + } + // restore state + rpglBindBufferARB( GL_ARRAY_BUFFER_ARB, gl2wrap_state.vbo ); +} + +static void APIENTRY GL2_DrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) +{ + GL2_SetupArrays( 0, 0 ); + rpglDrawElements( mode, count, type, indices ); +} + +static void APIENTRY GL2_DrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ) +{ + GL2_SetupArrays( start, end ); + if( rpglDrawRangeElements ) + rpglDrawRangeElements( mode, start, end, count, type, indices ); + else + rpglDrawElements( mode, count, type, indices ); +} + +static void APIENTRY GL2_DrawArrays( GLenum mode, GLint first, GLsizei count ) +{ + GL2_SetupArrays( 0, count ); + rpglDrawArrays( mode, first, count ); +} + +static void APIENTRY GL2_BindBufferARB( GLenum buf, GLuint obj ) +{ + if( buf == GL_ARRAY_BUFFER_ARB ) + gl2wrap_state.vbo = obj; + rpglBindBufferARB( buf, obj ); +} + +static void APIENTRY GL2_ActiveTextureARB( GLenum tex ) +{ + //gl2wrap_arrays.texture = GL_TEXTURE0_ARB - tex; +} + +static void APIENTRY GL2_ClientActiveTextureARB( GLenum tex ) +{ + gl2wrap_state.tmu = tex - GL_TEXTURE0_ARB; + + //pglActiveTextureARB( tex ); +} + +#define GL2_OVERRIDE_PTR( name ) \ +{ \ + pgl ## name = GL2_ ## name; \ +} + +#define GL2_OVERRIDE_PTR_B( name ) \ +{ \ + rpgl ## name = pgl ## name; \ + pgl ## name = GL2_ ## name; \ +} + +static void APIENTRY GL2_Normal3fv(const GLfloat *v) +{ +} + +static void APIENTRY GL2_Hint(GLenum target, GLenum mode) +{ +} + +static void APIENTRY GL2_Scalef(GLfloat x, GLfloat y, GLfloat z) +{ +} + +static void APIENTRY GL2_Translatef(GLfloat x, GLfloat y, GLfloat z) +{ +} + +static void APIENTRY GL2_TexEnvi(GLenum target, GLenum pname, GLint param) +{ +} + +static void APIENTRY GL2_TexEnvf(GLenum target, GLenum pname, GLfloat param) +{ +} + +static void APIENTRY GL2_Fogi(GLenum pname, GLint param) +{ +} + +static void APIENTRY GL2_ShadeModel(GLenum mode) +{ +} + +static void APIENTRY GL2_PolygonMode(GLenum face, GLenum mode) +{ +} + +static void APIENTRY GL2_PointSize(GLfloat size) +{ +} + +static void APIENTRY GL2_DrawBuffer(GLenum mode) +{ +} + +#if XASH_EMSCRIPTEN +static void GL2_PolygonOffset( GLfloat factor, GLfloat units ) +{ +} +#endif // XASH_EMSCRIPTEN + +void GL2_ShimInstall( void ) +{ + GL2_OVERRIDE_PTR( Vertex2f ) + GL2_OVERRIDE_PTR( Vertex3f ) + GL2_OVERRIDE_PTR( Vertex3fv ) + GL2_OVERRIDE_PTR( Color3f ) + GL2_OVERRIDE_PTR( Color4f ) + GL2_OVERRIDE_PTR( Color4ub ) + GL2_OVERRIDE_PTR( Color4ubv ) + GL2_OVERRIDE_PTR( Normal3fv ) + GL2_OVERRIDE_PTR( TexCoord2f ) + GL2_OVERRIDE_PTR( MultiTexCoord2f ) + GL2_OVERRIDE_PTR( AlphaFunc ) + GL2_OVERRIDE_PTR( Fogf ) + GL2_OVERRIDE_PTR( Fogfv ) + GL2_OVERRIDE_PTR( Hint ) // fog + GL2_OVERRIDE_PTR( Begin ) + GL2_OVERRIDE_PTR( End ) + GL2_OVERRIDE_PTR_B( Enable ) + GL2_OVERRIDE_PTR_B( Disable ) + GL2_OVERRIDE_PTR( MatrixMode ) + GL2_OVERRIDE_PTR( LoadIdentity ) + GL2_OVERRIDE_PTR( Ortho ) + GL2_OVERRIDE_PTR( LoadMatrixf ) + GL2_OVERRIDE_PTR( Scalef ) + GL2_OVERRIDE_PTR( Translatef ) + GL2_OVERRIDE_PTR( TexEnvi ) + GL2_OVERRIDE_PTR( TexEnvf ) + GL2_OVERRIDE_PTR( ClientActiveTextureARB ) + //GL2_OVERRIDE_PTR( ActiveTextureARB ) + GL2_OVERRIDE_PTR( Fogi ) + GL2_OVERRIDE_PTR( ShadeModel ) +#ifdef XASH_GLES + _pglDepthRangef = gEngfuncs.GL_GetProcAddress( "glDepthRangef" ); + GL2_OVERRIDE_PTR( PolygonMode ) + GL2_OVERRIDE_PTR( PointSize ) + GL2_OVERRIDE_PTR( DepthRange ) + GL2_OVERRIDE_PTR( DrawBuffer ) +#endif + if( glConfig.context != CONTEXT_TYPE_GL ) + { + GL2_OVERRIDE_PTR_B( TexImage2D ) + GL2_OVERRIDE_PTR_B( TexParameteri ) + } +#if XASH_EMSCRIPTEN + GL2_OVERRIDE_PTR( PolygonOffset ) +#endif // XASH_EMSCRIPTEN + GL2_OVERRIDE_PTR_B( IsEnabled ) + GL2_OVERRIDE_PTR_B( DrawRangeElements ) + GL2_OVERRIDE_PTR_B( DrawElements ) + GL2_OVERRIDE_PTR_B( DrawArrays ) + GL2_OVERRIDE_PTR_B( BindBufferARB ) + GL2_OVERRIDE_PTR( EnableClientState ) + GL2_OVERRIDE_PTR( DisableClientState ) + GL2_OVERRIDE_PTR( VertexPointer ) + GL2_OVERRIDE_PTR( ColorPointer ) + GL2_OVERRIDE_PTR( TexCoordPointer ) + +#ifdef QUAD_BATCH + GL2_OVERRIDE_PTR_B( BindTexture ) +#endif + GL2_AllocArrays(); +} +#endif diff --git a/ref/gl/gl2_shim/gl2_shim.h b/ref/gl/gl2_shim/gl2_shim.h new file mode 100644 index 00000000..cfdc9e54 --- /dev/null +++ b/ref/gl/gl2_shim/gl2_shim.h @@ -0,0 +1,24 @@ +/* +vgl_shim.h - vitaGL custom immediate mode shim +Copyright (C) 2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once + +// max verts in a single frame +#define GL2_MAX_VERTS 32768 + +int GL2_ShimInit( void ); +void GL2_ShimInstall( void ); +void GL2_ShimShutdown( void ); +void GL2_ShimEndFrame( void ); diff --git a/ref/gl/gl2_shim/vertex.glsl.inc b/ref/gl/gl2_shim/vertex.glsl.inc new file mode 100644 index 00000000..70986c06 --- /dev/null +++ b/ref/gl/gl2_shim/vertex.glsl.inc @@ -0,0 +1,53 @@ +"#if VER <= 300\n" +"#define layout(x)\n" +"#endif\n" +"#if VER < 300\n" +"#define in attribute\n" +"#define out varying\n" +"#endif\n" +"\n" +"layout(location = LOC_ATTR_POSITION) in vec3 inPosition;\n" +"#if ATTR_COLOR\n" +"layout(location = LOC_ATTR_COLOR) in vec4 inColor;\n" +"#endif\n" +"#if ATTR_TEXCOORD0\n" +"layout(location = LOC_ATTR_TEXCOORD0) in vec2 inTexCoord0;\n" +"#endif\n" +"#if ATTR_TEXCOORD1\n" +"layout(location = LOC_ATTR_TEXCOORD1) in vec2 inTexCoord1;\n" +"#endif\n" +"\n" +"#if ATTR_NORMAL\n" +"in vec3 inNormal;\n" +"#endif\n" +"#if ATTR_COLOR\n" +"out vec4 vColor;\n" +"#endif\n" +"#if ATTR_TEXCOORD0\n" +"out vec2 vTexCoord0;\n" +"#endif\n" +"#if ATTR_TEXCOORD1\n" +"out vec2 vTexCoord1;\n" +"#endif\n" +"#if ATTR_NORMAL\n" +"out vec3 vNormal;\n" +"#endif\n" +"\n" +"uniform mat4 uMVP;\n" +"\n" +"void main()\n" +"{\n" +"gl_Position = uMVP * vec4(inPosition,1.0);\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" diff --git a/ref/gl/gl_alias.c b/ref/gl/gl_alias.c index d9766755..5efc40f7 100644 --- a/ref/gl/gl_alias.c +++ b/ref/gl/gl_alias.c @@ -21,8 +21,6 @@ GNU General Public License for more details. #include "pm_local.h" #include "pmtrace.h" -extern cvar_t r_shadows; - typedef struct { double time; @@ -52,15 +50,10 @@ ALIAS MODEL DISPLAY LIST GENERATION */ static qboolean m_fDoRemap; static aliashdr_t *m_pAliasHeader; -static trivertex_t *g_poseverts[MAXALIASFRAMES]; static dtriangle_t g_triangles[MAXALIASTRIS]; static stvert_t g_stverts[MAXALIASVERTS]; static int g_used[8192]; -// a pose is a single set of vertexes. a frame may be -// an animating sequence of poses -int g_posenum; - // the command list holds counts and s/t values that are valid for // every frame static int g_commands[8192]; @@ -225,7 +218,7 @@ Generate a list of trifans or strips for the model, which holds for all frames ================ */ -void BuildTris( void ) +static void BuildTris( void ) { int len, bestlen, besttype = 0; int bestverts[1024]; @@ -292,8 +285,8 @@ void BuildTris( void ) t = (t + 0.5f) / m_pAliasHeader->skinheight; // Carmack use floats and Valve use shorts here... - *(float *)&g_commands[g_numcommands++] = s; - *(float *)&g_commands[g_numcommands++] = t; + g_commands[g_numcommands++] = FloatAsInt( s ); + g_commands[g_numcommands++] = FloatAsInt( t ); } } @@ -305,7 +298,7 @@ void BuildTris( void ) GL_MakeAliasModelDisplayLists ================ */ -void GL_MakeAliasModelDisplayLists( model_t *m ) +static void GL_MakeAliasModelDisplayLists( model_t *m ) { trivertex_t *verts; int i, j; @@ -324,7 +317,7 @@ void GL_MakeAliasModelDisplayLists( model_t *m ) for( i = 0; i < m_pAliasHeader->numposes; i++ ) { for( j = 0; j < g_numorder; j++ ) - *verts++ = g_poseverts[i][g_vertexorder[j]]; + *verts++ = m_pAliasHeader->pposeverts[i][g_vertexorder[j]]; } } @@ -335,90 +328,18 @@ ALIAS MODELS ============================================================================== */ -/* -================= -Mod_LoadAliasFrame -================= -*/ -void *Mod_LoadAliasFrame( void *pin, maliasframedesc_t *frame ) -{ - daliasframe_t *pdaliasframe; - trivertex_t *pinframe; - int i; - pdaliasframe = (daliasframe_t *)pin; - - Q_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name )); - frame->firstpose = g_posenum; - frame->numposes = 1; - - for( i = 0; i < 3; i++ ) - { - frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; - frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; - } - - pinframe = (trivertex_t *)(pdaliasframe + 1); - - g_poseverts[g_posenum] = (trivertex_t *)pinframe; - pinframe += m_pAliasHeader->numverts; - g_posenum++; - - return (void *)pinframe; -} - -/* -================= -Mod_LoadAliasGroup -================= -*/ -void *Mod_LoadAliasGroup( void *pin, maliasframedesc_t *frame ) -{ - daliasgroup_t *pingroup; - int i, numframes; - daliasinterval_t *pin_intervals; - void *ptemp; - - pingroup = (daliasgroup_t *)pin; - numframes = pingroup->numframes; - - frame->firstpose = g_posenum; - frame->numposes = numframes; - - for( i = 0; i < 3; i++ ) - { - frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; - frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; - } - - pin_intervals = (daliasinterval_t *)(pingroup + 1); - - // all the intervals are always equal 0.1 so we don't care about them - frame->interval = pin_intervals->interval; - pin_intervals += numframes; - ptemp = (void *)pin_intervals; - - for( i = 0; i < numframes; i++ ) - { - g_poseverts[g_posenum] = (trivertex_t *)((daliasframe_t *)ptemp + 1); - ptemp = (trivertex_t *)((daliasframe_t *)ptemp + 1) + m_pAliasHeader->numverts; - g_posenum++; - } - - return ptemp; -} /* =============== Mod_CreateSkinData =============== */ -rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) +static rgbdata_t *Mod_CreateSkinData( model_t *mod, const byte *data, int width, int height ) { static rgbdata_t skin; char name[MAX_QPATH]; int i; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); skin.width = width; skin.height = height; @@ -427,8 +348,8 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) skin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL; skin.encode = DXT_ENCODE_DEFAULT; skin.numMips = 1; - skin.buffer = data; - skin.palette = (byte *)gEngfuncs.CL_GetPaletteColor( 0 ); + skin.buffer = (byte *)data; + skin.palette = (byte *)tr.palette; skin.size = width * height; if( !gEngfuncs.Image_CustomPalette() ) @@ -443,7 +364,7 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) } } - COM_FileBase( loadmodel->name, name ); + COM_FileBase( mod->name, name, sizeof( name )); // for alias models only player can have remap textures if( mod != NULL && !Q_stricmp( name, "player" )) @@ -476,69 +397,68 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) return gEngfuncs.FS_CopyImage( &skin ); } -void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) +static const void *Mod_LoadSingleSkin( model_t *loadmodel, const daliasskintype_t *pskintype, int skinnum, int size ) { - string name, lumaname; - string checkname; - rgbdata_t *pic; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + const byte *ptexture = (const byte *)&pskintype[1]; + rgbdata_t *pic; + string name, lumaname, checkname; Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); - Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); if( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) - pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + pic = Mod_CreateSkinData( loadmodel, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = - m_pAliasHeader->gl_texturenum[skinnum][1] = - m_pAliasHeader->gl_texturenum[skinnum][2] = - m_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0 ); + m_pAliasHeader->gl_texturenum[skinnum][1] = + m_pAliasHeader->gl_texturenum[skinnum][2] = + m_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0 ); gEngfuncs.FS_FreeImage( pic ); - if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags & TF_HAS_LUMA ) + if( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags, TF_HAS_LUMA )) { - pic = Mod_CreateSkinData( NULL, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); + + pic = Mod_CreateSkinData( NULL, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->fb_texturenum[skinnum][0] = - m_pAliasHeader->fb_texturenum[skinnum][1] = - m_pAliasHeader->fb_texturenum[skinnum][2] = - m_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); + m_pAliasHeader->fb_texturenum[skinnum][1] = + m_pAliasHeader->fb_texturenum[skinnum][2] = + m_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); gEngfuncs.FS_FreeImage( pic ); } - return ((byte *)(pskintype + 1) + size); + return ptexture + size; } -void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) +static const void *Mod_LoadGroupSkin( model_t *loadmodel, const daliasskintype_t *pskintype, int skinnum, int size ) { - daliasskininterval_t *pinskinintervals; - daliasskingroup_t *pinskingroup; - string name, lumaname; - rgbdata_t *pic; - int i, j; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + const daliasskininterval_t *pinskinintervals; + const daliasskingroup_t *pinskingroup; + const byte *ptexture; + rgbdata_t *pic; + string name, lumaname; + int i, j; // animating skin group. yuck. - pskintype++; - pinskingroup = (daliasskingroup_t *)pskintype; - pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); - pskintype = (void *)(pinskinintervals + pinskingroup->numskins); + pinskingroup = (const daliasskingroup_t *)&pskintype[1]; + pinskinintervals = (const daliasskininterval_t *)(&pinskingroup[1]); + ptexture = (const byte *)&pinskinintervals[pinskingroup->numskins]; for( i = 0; i < pinskingroup->numskins; i++ ) { Q_snprintf( name, sizeof( name ), "%s_%i_%i", loadmodel->name, skinnum, i ); - pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + pic = Mod_CreateSkinData( loadmodel, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( name, pic, 0 ); gEngfuncs.FS_FreeImage( pic ); - if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags & TF_HAS_LUMA ) + if( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags, TF_HAS_LUMA )) { Q_snprintf( lumaname, sizeof( lumaname ), "%s_%i_%i_luma", loadmodel->name, skinnum, i ); - pic = Mod_CreateSkinData( NULL, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + pic = Mod_CreateSkinData( NULL, ptexture, m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->fb_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); gEngfuncs.FS_FreeImage( pic ); } - pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + ptexture += size; } for( j = i; i < 4; i++ ) @@ -547,7 +467,7 @@ void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) m_pAliasHeader->fb_texturenum[skinnum][i & 3] = m_pAliasHeader->fb_texturenum[skinnum][i - j]; } - return pskintype; + return ptexture; } /* @@ -555,64 +475,25 @@ void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) Mod_LoadAllSkins =============== */ -void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) +static const void *Mod_LoadAllSkins( model_t *mod, int numskins, const daliasskintype_t *pskintype ) { int i, size; - if( numskins < 1 || numskins > MAX_SKINS ) - gEngfuncs.Host_Error( "Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins ); - size = m_pAliasHeader->skinwidth * m_pAliasHeader->skinheight; + // TODO: texture replacement support here for( i = 0; i < numskins; i++ ) { if( pskintype->type == ALIAS_SKIN_SINGLE ) - { - pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( pskintype, i, size ); - } + pskintype = Mod_LoadSingleSkin( mod, pskintype, i, size ); else - { - pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( pskintype, i, size ); - } + pskintype = Mod_LoadGroupSkin( mod, pskintype, i, size ); } - return (void *)pskintype; + return pskintype; } //========================================================================= -/* -================= -Mod_CalcAliasBounds -================= -*/ -void Mod_CalcAliasBounds( model_t *mod ) -{ - int i, j, k; - float radius; - float dist; - vec3_t v; - - ClearBounds( mod->mins, mod->maxs ); - radius = 0.0f; - - // process verts - for( i = 0; i < m_pAliasHeader->numposes; i++ ) - { - for( j = 0; j < m_pAliasHeader->numverts; j++ ) - { - for( k = 0; k < 3; k++ ) - v[k] = g_poseverts[i][j].v[k] * m_pAliasHeader->scale[k] + m_pAliasHeader->scale_origin[k]; - - AddPointToBounds( v, mod->mins, mod->maxs ); - dist = DotProduct( v, v ); - - if( radius < dist ) - radius = dist; - } - } - - mod->radius = sqrt( radius ); -} /* ================= @@ -621,109 +502,34 @@ Mod_LoadAliasModel */ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) { - daliashdr_t *pinmodel; - stvert_t *pinstverts; - dtriangle_t *pintriangles; - daliasframetype_t *pframetype; - daliasskintype_t *pskintype; - int i, j, size; + const daliasskintype_t *pskintype; + const dtriangle_t *pintriangles; + const daliashdr_t *pinmodel; + const stvert_t *pinstverts; if( loaded ) *loaded = false; - pinmodel = (daliashdr_t *)buffer; - i = pinmodel->version; - - if( i != ALIAS_VERSION ) - { - gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, ALIAS_VERSION ); - return; - } - - if( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 ) - return; // how to possible is make that? - - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); - - // allocate space for a working header, plus all the data except the frames, - // skin and group info - size = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t ); - - m_pAliasHeader = Mem_Calloc( mod->mempool, size ); - mod->flags = pinmodel->flags; // share effects flags - - // endian-adjust and copy the data, starting with the alias model header - m_pAliasHeader->boundingradius = pinmodel->boundingradius; - m_pAliasHeader->numskins = pinmodel->numskins; - m_pAliasHeader->skinwidth = pinmodel->skinwidth; - m_pAliasHeader->skinheight = pinmodel->skinheight; - m_pAliasHeader->numverts = pinmodel->numverts; - m_pAliasHeader->numtris = pinmodel->numtris; - m_pAliasHeader->numframes = pinmodel->numframes; - - if( m_pAliasHeader->numverts > MAXALIASVERTS ) - { - gEngfuncs.Con_DPrintf( S_ERROR "model %s has too many vertices\n", mod->name ); - return; - } - - m_pAliasHeader->size = pinmodel->size; -// mod->synctype = pinmodel->synctype; - mod->numframes = m_pAliasHeader->numframes; - - for( i = 0; i < 3; i++ ) - { - m_pAliasHeader->scale[i] = pinmodel->scale[i]; - m_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i]; - m_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i]; - } + pinmodel = (const daliashdr_t *)buffer; + m_pAliasHeader = mod->cache.data; + if( !m_pAliasHeader ) return; // load the skins - pskintype = (daliasskintype_t *)&pinmodel[1]; - pskintype = Mod_LoadAllSkins( m_pAliasHeader->numskins, pskintype ); + pskintype = (const daliasskintype_t *)&pinmodel[1]; + pskintype = Mod_LoadAllSkins( mod, m_pAliasHeader->numskins, pskintype ); // load base s and t vertices - pinstverts = (stvert_t *)pskintype; - - for( i = 0; i < m_pAliasHeader->numverts; i++ ) - { - g_stverts[i].onseam = pinstverts[i].onseam; - g_stverts[i].s = pinstverts[i].s; - g_stverts[i].t = pinstverts[i].t; - } + pinstverts = (const stvert_t *)pskintype; + memset( g_stverts, 0, sizeof( g_stverts )); + memcpy( g_stverts, pinstverts, sizeof( g_stverts[0] ) * m_pAliasHeader->numverts ); // load triangle lists - pintriangles = (dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; - - for( i = 0; i < m_pAliasHeader->numtris; i++ ) - { - g_triangles[i].facesfront = pintriangles[i].facesfront; - - for( j = 0; j < 3; j++ ) - g_triangles[i].vertindex[j] = pintriangles[i].vertindex[j]; - } - - // load the frames - pframetype = (daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris]; - g_posenum = 0; - - for( i = 0; i < m_pAliasHeader->numframes; i++ ) - { - aliasframetype_t frametype = pframetype->type; - - if( frametype == ALIAS_SINGLE ) - pframetype = (daliasframetype_t *)Mod_LoadAliasFrame( pframetype + 1, &m_pAliasHeader->frames[i] ); - else pframetype = (daliasframetype_t *)Mod_LoadAliasGroup( pframetype + 1, &m_pAliasHeader->frames[i] ); - } - - m_pAliasHeader->numposes = g_posenum; - - Mod_CalcAliasBounds( mod ); - mod->type = mod_alias; + pintriangles = (const dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; + memset( g_triangles, 0, sizeof( g_triangles )); + memcpy( g_triangles, pintriangles, sizeof( g_triangles[0] ) * m_pAliasHeader->numtris ); // build the draw lists GL_MakeAliasModelDisplayLists( mod ); - // move the complete, relocatable alias model to the cache - gEngfuncs.Mod_GetCurrentLoadingModel()->cache.data = m_pAliasHeader; + m_pAliasHeader = NULL; if( loaded ) *loaded = true; // done } @@ -764,15 +570,14 @@ R_AliasDynamicLight similar to R_StudioDynamicLight =============== */ -void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) +static void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) { - movevars_t *mv = gEngfuncs.pfnGetMoveVars(); + movevars_t *mv = tr.movevars; vec3_t lightDir, vecSrc, vecEnd; vec3_t origin, dist, finalLight; float add, radius, total; colorVec light; uint lnum; - dlight_t *dl; if( !plight || !ent ) return; @@ -802,7 +607,7 @@ void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) msurface_t *psurf = NULL; pmtrace_t trace; - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) + if( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD )) { vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; @@ -823,9 +628,9 @@ void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) { VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); - light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); - light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); - light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + light.r = mv->skycolor_r; + light.g = mv->skycolor_g; + light.b = mv->skycolor_b; } } @@ -888,7 +693,7 @@ void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { - dl = gEngfuncs.GetDynamicLight( lnum ); + const dlight_t *dl = &tr.dlights[lnum]; if( dl->die < g_alias.time || !r_dynamic->value ) continue; @@ -908,9 +713,9 @@ void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) VectorAdd( lightDir, dist, lightDir ); - finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; - finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; - finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; + finalLight[0] += dl->color.r * ( add / 256.0f ); + finalLight[1] += dl->color.g * ( add / 256.0f ); + finalLight[2] += dl->color.b * ( add / 256.0f ); } } @@ -944,7 +749,7 @@ R_AliasSetupLighting =============== */ -void R_AliasSetupLighting( alight_t *plight ) +static void R_AliasSetupLighting( alight_t *plight ) { if( !m_pAliasHeader || !plight ) return; @@ -965,7 +770,7 @@ R_AliasLighting =============== */ -void R_AliasLighting( float *lv, const vec3_t normal ) +static void R_AliasLighting( float *lv, const vec3_t normal ) { float illum = g_alias.ambientlight; float r, lightcos; @@ -992,9 +797,9 @@ void R_AliasLighting( float *lv, const vec3_t normal ) illum -= g_alias.shadelight * lightcos; } - illum = Q_max( illum, 0.0f ); - illum = Q_min( illum, 255.0f ); - *lv = illum * (1.0f / 255.0f); + illum = bound( 0.0f, illum, 255.0f ); + + *lv = LightToTexGamma( illum * 4 ) / 1023.0f; } /* @@ -1003,15 +808,10 @@ R_AliasSetRemapColors =============== */ -void R_AliasSetRemapColors( int newTop, int newBottom ) +static void R_AliasSetRemapColors( int newTop, int newBottom ) { - gEngfuncs.CL_AllocRemapInfo( RI.currententity, RI.currentmodel, newTop, newBottom ); - - if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) - { - gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); + if( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom )) m_fDoRemap = true; - } } /* @@ -1019,7 +819,7 @@ void R_AliasSetRemapColors( int newTop, int newBottom ) GL_DrawAliasFrame ============= */ -void GL_DrawAliasFrame( aliashdr_t *paliashdr ) +static void GL_DrawAliasFrame( aliashdr_t *paliashdr ) { float lv_tmp; trivertex_t *verts0; @@ -1081,7 +881,7 @@ void GL_DrawAliasFrame( aliashdr_t *paliashdr ) GL_DrawAliasShadow ============= */ -void GL_DrawAliasShadow( aliashdr_t *paliashdr ) +static void GL_DrawAliasShadow( aliashdr_t *paliashdr ) { trivertex_t *verts0; trivertex_t *verts1; @@ -1159,7 +959,7 @@ R_AliasLerpMovement ==================== */ -void R_AliasLerpMovement( cl_entity_t *e ) +static void R_AliasLerpMovement( cl_entity_t *e ) { float f = 1.0f; @@ -1202,7 +1002,7 @@ R_SetupAliasFrame ================= */ -void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) +static void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) { int newpose, oldpose; int newframe, oldframe; @@ -1219,7 +1019,7 @@ void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) else if( newframe >= paliashdr->numframes ) { if( newframe > paliashdr->numframes ) - gEngfuncs.Con_Reportf( S_WARN "R_GetAliasFrame: no such frame %d (%s)\n", newframe, e->model->name ); + gEngfuncs.Con_Reportf( S_WARN "%s: no such frame %d (%s)\n", __func__, newframe, e->model->name ); newframe = paliashdr->numframes - 1; } @@ -1262,7 +1062,7 @@ static void R_AliasDrawAbsBBox( cl_entity_t *e, const vec3_t absmin, const vec3_ int i; // looks ugly, skip - if( r_drawentities->value != 5 || e == gEngfuncs.GetViewModel() ) + if( r_drawentities->value != 5 || e == tr.viewent ) return; // compute a full bounding box @@ -1338,12 +1138,12 @@ static void R_AliasSetupTimings( void ) if( RI.drawWorld ) { // synchronize with server time - g_alias.time = gpGlobals->time; + g_alias.time = gp_cl->time; } else { // menu stuff - g_alias.time = gpGlobals->realtime; + g_alias.time = gp_host->realtime; } m_fDoRemap = false; @@ -1387,7 +1187,7 @@ void R_DrawAliasModel( cl_entity_t *e ) R_AliasLerpMovement( e ); - if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) + if( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG )) e->angles[PITCH] = -e->angles[PITCH]; // stupid quake bug // don't rotate clients, only aim diff --git a/ref/gl/gl_backend.c b/ref/gl/gl_backend.c index da530405..e3982c38 100644 --- a/ref/gl/gl_backend.c +++ b/ref/gl/gl_backend.c @@ -17,8 +17,8 @@ GNU General Public License for more details. #include "gl_local.h" #include "xash3d_mathlib.h" -char r_speeds_msg[MAX_SYSPATH]; -ref_speeds_t r_stats; // r_speeds counters +static char r_speeds_msg[MAX_SYSPATH]; +ref_speeds_t r_stats; // r_speeds counters /* =============== @@ -42,25 +42,6 @@ qboolean R_SpeedsMessage( char *out, size_t size ) return true; } -/* -============== -R_Speeds_Printf - -helper to print into r_speeds message -============== -*/ -void R_Speeds_Printf( const char *msg, ... ) -{ - va_list argptr; - char text[2048]; - - va_start( argptr, msg ); - Q_vsprintf( text, msg, argptr ); - va_end( argptr ); - - Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg )); -} - /* ============== GL_BackendStartFrame @@ -87,47 +68,36 @@ void GL_BackendEndFrame( void ) curleaf = WORLDMODEL->leafs; else curleaf = RI.viewleaf; - R_Speeds_Printf( "Renderer: ^1Engine^7\n\n" ); - switch( (int)r_speeds->value ) { case 1: Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i wpoly, %3i apoly\n%3i epoly, %3i spoly", - r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys ); + r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys ); break; case 2: - R_Speeds_Printf( "visible leafs:\n%3i leafs\ncurrent leaf %3i\n", r_stats.c_world_leafs, curleaf - WORLDMODEL->leafs ); - R_Speeds_Printf( "ReciusiveWorldNode: %3lf secs\nDrawTextureChains %lf\n", r_stats.t_world_node, r_stats.t_world_draw ); + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), + "Renderer: ^1Engine^7\n\n" + "visible leafs:\n%3i leafs\ncurrent leaf %3i\n" + "ReciusiveWorldNode: %3lf secs\nDrawTextureChains %lf", + r_stats.c_world_leafs, (int)( curleaf - WORLDMODEL->leafs ), r_stats.t_world_node, r_stats.t_world_draw ); break; case 3: Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i alias models drawn\n%3i studio models drawn\n%3i sprites drawn", - r_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn ); + r_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn ); break; case 4: Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i static entities\n%3i normal entities\n%3i server entities", - r_numStatics, r_numEntities - r_numStatics, ENGINE_GET_PARM( PARM_NUMENTITIES )); + r_numStatics, r_numEntities - r_numStatics, (int)ENGINE_GET_PARM( PARM_NUMENTITIES )); break; case 5: Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i tempents\n%3i viewbeams\n%3i particles", - r_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count ); + r_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count ); break; } memset( &r_stats, 0, sizeof( r_stats )); } -/* -================= -GL_LoadTexMatrix -================= -*/ -void GL_LoadTexMatrix( const matrix4x4 m ) -{ - pglMatrixMode( GL_TEXTURE ); - GL_LoadMatrix( m ); - glState.texIdentityMatrix[glState.activeTMU] = false; -} - /* ================= GL_LoadTexMatrixExt @@ -174,17 +144,18 @@ void GL_LoadIdentityTexMatrix( void ) GL_SelectTexture ================= */ -void GL_SelectTexture( GLint tmu ) +void GL_SelectTexture( int tmu ) { if( !GL_Support( GL_ARB_MULTITEXTURE )) return; // don't allow negative texture units - if( tmu < 0 ) return; + if( tmu < 0 ) + return; if( tmu >= GL_MaxTextureUnits( )) { - gEngfuncs.Con_Reportf( S_ERROR "GL_SelectTexture: bad tmu state %i\n", tmu ); + gEngfuncs.Con_Reportf( S_ERROR "%s: bad tmu state %i\n", __func__, tmu ); return; } @@ -202,6 +173,49 @@ void GL_SelectTexture( GLint tmu ) } } +/* +================= +GL_Bind +================= +*/ +void GL_Bind( int tmu, unsigned int texnum ) +{ + const gl_texture_t *texture; + GLuint glTarget; + + // missed or invalid texture? + if( texnum <= 0 || texnum >= MAX_TEXTURES ) + { + if( texnum != 0 ) + gEngfuncs.Con_DPrintf( S_ERROR "%s: invalid texturenum %d\n", __func__, texnum ); + texnum = tr.defaultTexture; + } + + if( tmu != GL_KEEP_UNIT ) + GL_SelectTexture( tmu ); + else tmu = glState.activeTMU; + + texture = R_GetTexture( texnum ); + glTarget = texture->target; + + if( glTarget == GL_TEXTURE_2D_ARRAY_EXT ) + glTarget = GL_TEXTURE_2D; + + if( glState.currentTextureTargets[tmu] != glTarget ) + { + GL_EnableTextureUnit( tmu, false ); + glState.currentTextureTargets[tmu] = glTarget; + GL_EnableTextureUnit( tmu, true ); + } + + if( glState.currentTextures[tmu] == texture->texnum ) + return; + + pglBindTexture( texture->target, texture->texnum ); + glState.currentTextures[tmu] = texture->texnum; + glState.currentTexturesIndex[tmu] = texnum; +} + /* ============== GL_DisableAllTexGens @@ -232,6 +246,7 @@ void GL_CleanUpTextureUnits( int last ) pglDisable( glState.currentTextureTargets[i] ); glState.currentTextureTargets[i] = GL_NONE; glState.currentTextures[i] = -1; // unbind texture + glState.currentTexturesIndex[i] = 0; } GL_SetTexCoordArrayMode( GL_NONE ); @@ -259,7 +274,7 @@ void GL_CleanupAllTextureUnits( void ) GL_MultiTexCoord2f ================= */ -void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ) +void GL_MultiTexCoord2f( int tmu, GLfloat s, GLfloat t ) { if( !GL_Support( GL_ARB_MULTITEXTURE )) return; @@ -267,7 +282,28 @@ void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ) #ifndef XASH_GL_STATIC if( pglMultiTexCoord2f != NULL ) #endif - pglMultiTexCoord2f( texture + GL_TEXTURE0_ARB, s, t ); + pglMultiTexCoord2f( tmu + GL_TEXTURE0_ARB, s, t ); +} + +/* +==================== +GL_EnableTextureUnit +==================== +*/ +void GL_EnableTextureUnit( int tmu, qboolean enable ) +{ + // only enable fixed-function pipeline units + if( tmu < glConfig.max_texture_units ) + { + if( enable ) + { + pglEnable( glState.currentTextureTargets[tmu] ); + } + else if( glState.currentTextureTargets[tmu] != GL_NONE ) + { + pglDisable( glState.currentTextureTargets[tmu] ); + } + } } /* @@ -279,17 +315,16 @@ void GL_TextureTarget( uint target ) { if( glState.activeTMU < 0 || glState.activeTMU >= GL_MaxTextureUnits( )) { - gEngfuncs.Con_Reportf( S_ERROR "GL_TextureTarget: bad tmu state %i\n", glState.activeTMU ); + gEngfuncs.Con_Reportf( S_ERROR "%s: bad tmu state %i\n", __func__, glState.activeTMU ); return; } if( glState.currentTextureTargets[glState.activeTMU] != target ) { - if( glState.currentTextureTargets[glState.activeTMU] != GL_NONE ) - pglDisable( glState.currentTextureTargets[glState.activeTMU] ); + GL_EnableTextureUnit( glState.activeTMU, false ); glState.currentTextureTargets[glState.activeTMU] = target; if( target != GL_NONE ) - pglEnable( glState.currentTextureTargets[glState.activeTMU] ); + GL_EnableTextureUnit( glState.activeTMU, true ); } } @@ -326,19 +361,19 @@ void GL_TexGen( GLenum coord, GLenum mode ) if( mode ) { - if( !( glState.genSTEnabled[tmu] & bit )) + if( !FBitSet( glState.genSTEnabled[tmu], bit )) { pglEnable( gen ); - glState.genSTEnabled[tmu] |= bit; + SetBits( glState.genSTEnabled[tmu], bit ); } pglTexGeni( coord, GL_TEXTURE_GEN_MODE, mode ); } else { - if( glState.genSTEnabled[tmu] & bit ) + if( FBitSet( glState.genSTEnabled[tmu], bit )) { pglDisable( gen ); - glState.genSTEnabled[tmu] &= ~bit; + ClearBits( glState.genSTEnabled[tmu], bit ); } } } @@ -357,15 +392,20 @@ void GL_SetTexCoordArrayMode( GLenum mode ) bit = 1; else if( mode == GL_TEXTURE_CUBE_MAP_ARB ) bit = 2; - else bit = 0; + else + bit = 0; if( cmode != bit ) { - if( cmode == 1 ) pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); - else if( cmode == 2 ) pglDisable( GL_TEXTURE_CUBE_MAP_ARB ); + if( cmode == 1 ) + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + else if( cmode == 2 ) + pglDisable( GL_TEXTURE_CUBE_MAP_ARB ); - if( bit == 1 ) pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - else if( bit == 2 ) pglEnable( GL_TEXTURE_CUBE_MAP_ARB ); + if( bit == 1 ) + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + else if( bit == 2 ) + pglEnable( GL_TEXTURE_CUBE_MAP_ARB ); glState.texCoordArrayMode[tmu] = bit; } @@ -417,6 +457,10 @@ void GL_SetRenderMode( int mode ) pglDisable( GL_ALPHA_TEST ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); break; + case kRenderScreenFadeModulate: + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); } } @@ -434,7 +478,7 @@ typedef struct envmap_s int flags; } envmap_t; -const envmap_t r_skyBoxInfo[6] = +static const envmap_t r_skyBoxInfo[6] = { {{ 0, 270, 180}, IMAGE_FLIP_X }, {{ 0, 90, 180}, IMAGE_FLIP_X }, @@ -444,7 +488,7 @@ const envmap_t r_skyBoxInfo[6] = {{ 0, 180, 180}, IMAGE_FLIP_X }, }; -const envmap_t r_envMapInfo[6] = +static const envmap_t r_envMapInfo[6] = { {{ 0, 0, 90}, 0 }, {{ 0, 180, -90}, 0 }, @@ -481,22 +525,10 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) gEngfuncs.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: - flags |= IMAGE_RESAMPLE; - if( gpGlobals->wideScreen ) - { - height = 480; - width = 800; - } - else - { - height = 480; - width = 640; - } - break; case VID_MINISHOT: flags |= IMAGE_RESAMPLE; - height = 200; - width = 320; + height = shot_type == VID_MINISHOT ? 200 : 480; + width = Q_rint( height * ((double)r_shot->width / r_shot->height )); break; case VID_MAPSHOT: flags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format @@ -547,8 +579,6 @@ qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qbo // use client vieworg if( !vieworg ) vieworg = RI.vieworg; - R_CheckGamma(); - for( i = 0; i < 6; i++ ) { // go into 3d mode @@ -586,9 +616,8 @@ qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qbo r_shot->buffer = buffer; // make sure what we have right extension - Q_strncpy( basename, base, MAX_STRING ); - COM_StripExtension( basename ); - COM_DefaultExtension( basename, ".tga" ); + Q_strncpy( basename, base, sizeof( basename )); + COM_ReplaceExtension( basename, ".tga", sizeof( basename )); // write image as 6 sides result = gEngfuncs.FS_SaveImage( basename, r_shot ); @@ -610,16 +639,20 @@ was there. This is used to test for texture thrashing. */ void R_ShowTextures( void ) { - gl_texture_t *image; - float x, y, w, h; - int total, start, end; - int i, j, k, base_w, base_h; - rgba_t color = { 192, 192, 192, 255 }; - int charHeight, numTries = 0; + float w, h; + int start, k; + int base_w, base_h; + rgba_t color = { 255, 255, 255, 255 }; + int charHeight; static qboolean showHelp = true; - string shortname; + float time; //nc add + float time_cubemap; //nc add + float cbm_cos, cbm_sin; //nc add + int per_page; //nc add + qboolean empty_page; + int skipped_empty_pages; - if( !CVAR_TO_BOOL( gl_showtextures )) + if( !r_showtextures->value ) return; if( showHelp ) @@ -628,85 +661,156 @@ void R_ShowTextures( void ) showHelp = false; } - GL_SetRenderMode( kRenderNormal ); pglClear( GL_COLOR_BUFFER_BIT ); - pglFinish(); - base_w = 8; // textures view by horizontal - base_h = 6; // textures view by vertical + w = 200; + h = 200; -rebuild_page: - total = base_w * base_h; - start = total * (gl_showtextures->value - 1); - end = total * gl_showtextures->value; - if( end > MAX_TEXTURES ) end = MAX_TEXTURES; - - w = gpGlobals->width / base_w; - h = gpGlobals->height / base_h; + time = gp_cl->time * 0.5f; + time -= floor( time ); + time_cubemap = gp_cl->time * 0.25f; + time_cubemap -= floor( time_cubemap ); + time_cubemap *= 6.2831853f; + SinCos( time_cubemap, &cbm_sin, &cbm_cos ); gEngfuncs.Con_DrawStringLen( NULL, NULL, &charHeight ); - for( i = j = 0; i < MAX_TEXTURES; i++ ) + base_w = gpGlobals->width / w; + base_h = gpGlobals->height / ( h + charHeight * 2 ); + per_page = base_w * base_h; + start = per_page * ( r_showtextures->value - 1 ) + 1; // skip empty null texture + + GL_SetRenderMode( kRenderTransTexture ); // nc changed from normal to trans, Con_DrawString does this anyway + + empty_page = true; + skipped_empty_pages = 0; + while( empty_page ) { - image = R_GetTexture( i ); - if( j == start ) break; // found start - if( pglIsTexture( image->texnum )) j++; + for( k = 0; k < per_page; k++ ) + { + const gl_texture_t *image; + int i; + + i = k + start; + if( i >= MAX_TEXTURES ) + { + empty_page = false; + break; + } + + image = R_GetTexture( i ); + if( pglIsTexture( image->texnum )) + { + empty_page = false; + break; + } + } + + if( empty_page ) + { + start += per_page; + skipped_empty_pages++; + } } - if( i == MAX_TEXTURES && gl_showtextures->value != 1 ) + if( skipped_empty_pages > 0 ) { - // bad case, rewind to one and try again - gEngfuncs.Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); - if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop + char text[MAX_VA_STRING]; + Q_snprintf( text, sizeof( text ), "%s: skipped %d empty texture pages", __func__, skipped_empty_pages ); + gEngfuncs.CL_CenterPrint( text, 0.25f ); } - for( k = 0; i < MAX_TEXTURES; i++ ) + for( k = 0; k < per_page; k++ ) { - if( j == end ) break; // page is full + const gl_texture_t *image; + int textlen, i; + char text[MAX_VA_STRING]; + string shortname; + float x, y; + + i = k + start; + if ( i >= MAX_TEXTURES ) + break; image = R_GetTexture( i ); if( !pglIsTexture( image->texnum )) continue; - x = k % base_w * w; - y = k / base_w * h; + x = k % base_w * gpGlobals->width / base_w; + y = k / base_w * gpGlobals->height / base_h; pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); - GL_Bind( XASH_TEXTURE0, i ); // NOTE: don't use image->texnum here, because skybox has a 'wrong' indexes + GL_Bind( XASH_TEXTURE0, i ); if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE ); pglBegin( GL_QUADS ); - pglTexCoord2f( 0, 0 ); - pglVertex2f( x, y ); - if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + +#if XASH_NANOGL +#undef pglTexCoord3f +#define pglTexCoord3f( s, t, u ) pglTexCoord2f( s, t ) // not really correct but it requires nanogl rework +#endif // XASH_GLES + + if( image->target == GL_TEXTURE_CUBE_MAP_ARB ) + { + pglTexCoord3f( 0.75 * cbm_cos - cbm_sin, 0.75 * cbm_sin + cbm_cos, 1.0 ); + pglVertex2f( x, y ); + pglTexCoord3f( 0.75 * cbm_cos + cbm_sin, 0.75 * cbm_sin - cbm_cos, 1.0 ); + pglVertex2f( x + w, y ); + pglTexCoord3f( 0.75 * cbm_cos + cbm_sin, 0.75 * cbm_sin - cbm_cos, -1.0 ); + pglVertex2f( x + w, y + h ); + pglTexCoord3f( 0.75 * cbm_cos - cbm_sin, 0.75 * cbm_sin + cbm_cos, -1.0 ); + pglVertex2f( x, y + h ); + } + else if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + { + pglTexCoord2f( 0, 0 ); + pglVertex2f( x, y ); pglTexCoord2f( image->width, 0 ); - else pglTexCoord2f( 1, 0 ); - pglVertex2f( x + w, y ); - if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglVertex2f( x + w, y ); pglTexCoord2f( image->width, image->height ); - else pglTexCoord2f( 1, 1 ); - pglVertex2f( x + w, y + h ); - if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglVertex2f( x + w, y + h ); pglTexCoord2f( 0, image->height ); - else pglTexCoord2f( 0, 1 ); - pglVertex2f( x, y + h ); + pglVertex2f( x, y + h ); + } + else + { + pglTexCoord3f( 0, 0, time ); + pglVertex2f( x, y ); + pglTexCoord3f( 1, 0, time ); + pglVertex2f( x + w, y ); + pglTexCoord3f( 1, 1, time ); + pglVertex2f( x + w, y + h ); + pglTexCoord3f( 0, 1, time); + pglVertex2f( x, y + h ); + } pglEnd(); if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); - COM_FileBase( image->name, shortname ); - if( Q_strlen( shortname ) > 18 ) + COM_FileBase( image->name, shortname, sizeof( shortname )); + gEngfuncs.Con_DrawStringLen( shortname, &textlen, NULL ); + + if( textlen > w ) { // cutoff too long names, it looks ugly shortname[16] = '.'; shortname[17] = '.'; shortname[18] = '\0'; } - gEngfuncs.Con_DrawString( x + 1, y + h - charHeight, shortname, color ); - j++, k++; + + gEngfuncs.Con_DrawString( x + 1, y + h, shortname, color ); + if( image->target == GL_TEXTURE_3D || image->target == GL_TEXTURE_2D_ARRAY_EXT ) + Q_snprintf( text, sizeof( text ), "%ix%ix%i %s", image->width, image->height, image->depth, GL_TargetToString( image->target )); + else + Q_snprintf( text, sizeof( text ), "%ix%i %s", image->width, image->height, GL_TargetToString( image->target )); + gEngfuncs.Con_DrawString( x + 1, y + h + charHeight, text, color ); + + Q_strncpy( text, Q_memprint( image->size ), sizeof( text )); + gEngfuncs.Con_DrawStringLen( text, &textlen, NULL ); + gEngfuncs.Con_DrawString(( x + w ) - textlen - 1, y + h + charHeight, text, color ); } gEngfuncs.CL_DrawCenterPrint (); diff --git a/ref/gl/gl_beams.c b/ref/gl/gl_beams.c index 4e436d17..14b9820a 100644 --- a/ref/gl/gl_beams.c +++ b/ref/gl/gl_beams.c @@ -370,7 +370,7 @@ R_DrawTorus Draw beamtours ================ */ -void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { int i, noiseIndex, noiseStep; float div, length, fraction, factor, vLast, vStep; @@ -459,7 +459,7 @@ R_DrawDisk Draw beamdisk ================ */ -void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { float div, length, fraction; float w, vLast, vStep; @@ -517,7 +517,7 @@ R_DrawCylinder Draw beam cylinder ================ */ -void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { float div, length, fraction; float vLast, vStep; @@ -574,7 +574,7 @@ R_DrawBeamFollow drawi followed beam ============== */ -void R_DrawBeamFollow( BEAM *pbeam, float frametime ) +static void R_DrawBeamFollow( BEAM *pbeam, float frametime ) { particle_t *pnew, *particles; float fraction, div, vLast, vStep; @@ -608,7 +608,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) if( pnew ) { VectorCopy( pbeam->source, pnew->org ); - pnew->die = gpGlobals->time + pbeam->amplitude; + pnew->die = gp_cl->time + pbeam->amplitude; VectorClear( pnew->vel ); pnew->next = particles; @@ -655,7 +655,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) VectorMA( delta, -pbeam->width, normal, last2 ); div = 1.0f / pbeam->amplitude; - fraction = ( pbeam->die - gpGlobals->time ) * div; + fraction = ( pbeam->die - gp_cl->time ) * div; vLast = 0.0f; vStep = 1.0f; @@ -688,7 +688,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) if( particles->next != NULL ) { - fraction = (particles->die - gpGlobals->time) * div; + fraction = (particles->die - gp_cl->time) * div; } else { @@ -724,7 +724,7 @@ R_DrawRing Draw beamring ================ */ -void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) +static void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) { int i, j, noiseIndex, noiseStep; float div, length, fraction, factor, vLast, vStep; @@ -859,7 +859,7 @@ static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) if( !ent ) { - gEngfuncs.Con_DPrintf( S_ERROR "R_BeamComputePoint: invalid entity %i\n", BEAMENT_ENTITY( beamEnt )); + gEngfuncs.Con_DPrintf( S_ERROR "%s: invalid entity %i\n", __func__, BEAMENT_ENTITY( beamEnt )); VectorClear( pt ); return false; } @@ -867,12 +867,8 @@ static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) // get attachment if( attach > 0 ) VectorCopy( ent->attachment[attach - 1], pt ); - else if( ent->index == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) - { - vec3_t simorg; - gEngfuncs.GetPredictedOrigin( simorg ); - VectorCopy( simorg, pt ); - } + else if( ent->index == ( gp_cl->playernum + 1 )) + VectorCopy( gp_cl->simorg, pt ); else VectorCopy( ent->origin, pt ); return true; @@ -885,7 +881,7 @@ R_BeamRecomputeEndpoints Recomputes beam endpoints.. ============== */ -qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) +static qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) { if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) { @@ -916,7 +912,7 @@ qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) { ClearBits( pbeam->flags, FBEAM_ENDENTITY ); - pbeam->die = gpGlobals->time; + pbeam->die = gp_cl->time; return false; } else @@ -938,18 +934,18 @@ R_BeamDraw Update beam vars and draw it ============== */ -void R_BeamDraw( BEAM *pbeam, float frametime ) +static void R_BeamDraw( BEAM *pbeam, float frametime ) { model_t *model; vec3_t delta; - model = gEngfuncs.pfnGetModelByIndex( pbeam->modelIndex ); + model = CL_ModelHandle( pbeam->modelIndex ); SetBits( pbeam->flags, FBEAM_ISACTIVE ); if( !model || model->type != mod_sprite ) { pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore - pbeam->die = gpGlobals->time; + pbeam->die = gp_cl->time; return; } @@ -1007,7 +1003,7 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) if( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT )) { // update life cycle - pbeam->t = pbeam->freq + ( pbeam->die - gpGlobals->time ); + pbeam->t = pbeam->freq + ( pbeam->die - gp_cl->time ); if( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t; } @@ -1055,7 +1051,7 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) TriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd ); - if( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gpGlobals->time) % pbeam->frameCount )) + if( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gp_cl->time) % pbeam->frameCount )) { ClearBits( pbeam->flags, FBEAM_ISACTIVE ); return; @@ -1146,7 +1142,7 @@ passed through this */ static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) { - model_t *sprite = gEngfuncs.pfnGetModelByIndex( modelIndex ); + model_t *sprite = CL_ModelHandle( modelIndex ); if( !sprite ) return; @@ -1160,8 +1156,8 @@ static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, VectorCopy( end, pbeam->target ); VectorSubtract( end, start, pbeam->delta ); - pbeam->freq = speed * gpGlobals->time; - pbeam->die = life + gpGlobals->time; + pbeam->freq = speed * gp_cl->time; + pbeam->die = life + gp_cl->time; pbeam->amplitude = amplitude; pbeam->brightness = brightness; pbeam->width = width; @@ -1184,7 +1180,7 @@ R_BeamDrawCustomEntity initialize beam from server entity ============== */ -void R_BeamDrawCustomEntity( cl_entity_t *ent ) +static void R_BeamDrawCustomEntity( cl_entity_t *ent ) { BEAM beam; float amp = ent->curstate.body / 100.0f; @@ -1196,7 +1192,7 @@ void R_BeamDrawCustomEntity( cl_entity_t *ent ) g = ent->curstate.rendercolor.g / 255.0f; b = ent->curstate.rendercolor.b / 255.0f; - R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); beam.pFollowModel = NULL; @@ -1291,7 +1287,7 @@ void CL_DrawBeams( int fTrans, BEAM *active_beams ) if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID )) continue; - R_BeamDraw( pBeam, gpGlobals->time - gpGlobals->oldtime ); + R_BeamDraw( pBeam, gp_cl->time - gp_cl->oldtime ); } pglShadeModel( GL_FLAT ); diff --git a/ref/gl/gl_context.c b/ref/gl/gl_context.c index c95c0155..db572995 100644 --- a/ref/gl/gl_context.c +++ b/ref/gl/gl_context.c @@ -18,12 +18,24 @@ GNU General Public License for more details. #include "gl_local.h" #include "gl_export.h" -#ifdef XASH_GL4ES +#if XASH_GL4ES #include "gl4es/include/gl4esinit.h" #endif ref_api_t gEngfuncs; ref_globals_t *gpGlobals; +ref_client_t *gp_cl; +ref_host_t *gp_host; + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + gEngfuncs._Mem_Free( data, filename, fileline ); +} + +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return gEngfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline ); +} static void R_ClearScreen( void ) { @@ -47,13 +59,16 @@ CL_FillRGBA ============= */ -static void CL_FillRGBA( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) +static void CL_FillRGBA( int rendermode, float _x, float _y, float _w, float _h, byte r, byte g, byte b, byte a ) { pglDisable( GL_TEXTURE_2D ); pglEnable( GL_BLEND ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f ); + if( rendermode == kRenderTransAdd ) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + else + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4ub( r, g, b, a ); pglBegin( GL_QUADS ); pglVertex2f( _x, _y ); @@ -62,53 +77,46 @@ static void CL_FillRGBA( float _x, float _y, float _w, float _h, int r, int g, i pglVertex2f( _x, _y + _h ); pglEnd (); - pglColor3f( 1.0f, 1.0f, 1.0f ); pglEnable( GL_TEXTURE_2D ); pglDisable( GL_BLEND ); } -/* -============= -pfnFillRGBABlend - -============= -*/ -static void GAME_EXPORT CL_FillRGBABlend( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) +static qboolean Mod_LooksLikeWaterTexture( const char *name ) { - pglDisable( GL_TEXTURE_2D ); - pglEnable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f ); + if(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' ) + return true; - pglBegin( GL_QUADS ); - pglVertex2f( _x, _y ); - pglVertex2f( _x + _w, _y ); - pglVertex2f( _x + _w, _y + _h ); - pglVertex2f( _x, _y + _h ); - pglEnd (); + if( !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) + { + if( !Q_strncmp( name, "water", 5 ) || !Q_strnicmp( name, "laser", 5 )) + return true; + } - pglColor3f( 1.0f, 1.0f, 1.0f ); - pglEnable( GL_TEXTURE_2D ); - pglDisable( GL_BLEND ); + return false; } -void Mod_BrushUnloadTextures( model_t *mod ) +static void Mod_BrushUnloadTextures( model_t *mod ) { int i; for( i = 0; i < mod->numtextures; i++ ) { texture_t *tx = mod->textures[i]; - if( !tx || tx->gl_texturenum == tr.defaultTexture ) + if( !tx ) continue; // free slot - GL_FreeTexture( tx->gl_texturenum ); // main texture - GL_FreeTexture( tx->fb_texturenum ); // luma texture + if( tx->gl_texturenum != tr.defaultTexture ) + GL_FreeTexture( tx->gl_texturenum ); // main texture + + if( !Mod_LooksLikeWaterTexture( tx->name )) + { + GL_FreeTexture( tx->fb_texturenum ); // luma texture + GL_FreeTexture( tx->dt_texturenum ); // detail texture + } } } -void Mod_UnloadTextures( model_t *mod ) +static void Mod_UnloadTextures( model_t *mod ) { Assert( mod != NULL ); @@ -127,40 +135,42 @@ void Mod_UnloadTextures( model_t *mod ) Mod_SpriteUnloadTextures( mod->cache.data ); break; default: - ASSERT( 0 ); + Assert( 0 ); break; } } -qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf ) +static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf ) { - qboolean loaded = true; - - if( create ) - { - switch( mod->type ) - { - case mod_studio: - // Mod_LoadStudioModel( mod, buf, loaded ); - break; - case mod_sprite: - Mod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo ); - break; - case mod_alias: - Mod_LoadAliasModel( mod, buf, &loaded ); - break; - case mod_brush: - // Mod_LoadBrushModel( mod, buf, loaded ); - break; - default: gEngfuncs.Host_Error( "Mod_LoadModel: unsupported type %d\n", mod->type ); - } - } - - if( loaded && gEngfuncs.drawFuncs->Mod_ProcessUserData ) - gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, create, buf ); + qboolean loaded = false; if( !create ) + { + if( gEngfuncs.drawFuncs->Mod_ProcessUserData ) + gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, false, buf ); Mod_UnloadTextures( mod ); + return true; + } + + switch( mod->type ) + { + case mod_studio: + case mod_brush: + loaded = true; + break; + case mod_sprite: + Mod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo ); + break; + case mod_alias: + Mod_LoadAliasModel( mod, buf, &loaded ); + break; + default: + gEngfuncs.Host_Error( "%s: unsupported type %d\n", __func__, mod->type ); + return false; + } + + if( gEngfuncs.drawFuncs->Mod_ProcessUserData ) + gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, true, buf ); return loaded; } @@ -203,14 +213,6 @@ static int GL_RefGetParm( int parm, int arg ) case PARM_TEX_LIGHTMAP: arg = bound( 0, arg, MAX_LIGHTMAPS - 1 ); return tr.lightmapTextures[arg]; - case PARM_WIDESCREEN: - return gpGlobals->wideScreen; - case PARM_FULLSCREEN: - return gpGlobals->fullScreen; - case PARM_SCREEN_WIDTH: - return gpGlobals->width; - case PARM_SCREEN_HEIGHT: - return gpGlobals->height; case PARM_TEX_TARGET: glt = R_GetTexture( arg ); return glt->target; @@ -220,6 +222,8 @@ static int GL_RefGetParm( int parm, int arg ) case PARM_TEX_FLAGS: glt = R_GetTexture( arg ); return glt->flags; + case PARM_TEX_MEMORY: + return GL_TexMemory(); case PARM_ACTIVE_TMU: return glState.activeTMU; case PARM_LIGHTSTYLEVALUE: @@ -229,18 +233,17 @@ static int GL_RefGetParm( int parm, int arg ) return GL_MaxTextureUnits(); case PARM_REBUILD_GAMMA: return glConfig.softwareGammaUpdate; - case PARM_SURF_SAMPLESIZE: - if( arg >= 0 && arg < WORLDMODEL->numsurfaces ) - return gEngfuncs.Mod_SampleSizeForFace( &WORLDMODEL->surfaces[arg] ); - return LM_SAMPLE_SIZE; case PARM_GL_CONTEXT_TYPE: return glConfig.context; case PARM_GLES_WRAPPER: return glConfig.wrapper; case PARM_STENCIL_ACTIVE: return glState.stencilEnabled; - case PARM_SKY_SPHERE: - return ENGINE_GET_PARM_( parm, arg ) && !tr.fCustomSkybox; + case PARM_TEX_FILTERING: + if( arg < 0 ) + return gl_texture_nearest.value == 0.0f; + + return GL_TextureFilteringEnabled( R_GetTexture( arg )); default: return ENGINE_GET_PARM_( parm, arg ); } @@ -292,7 +295,7 @@ static const char *GL_TextureName( unsigned int texnum ) return R_GetTexture( texnum )->name; } -const byte *GL_TextureData( unsigned int texnum ) +static const byte *GL_TextureData( unsigned int texnum ) { rgbdata_t *pic = R_GetTexture( texnum )->original; @@ -301,20 +304,65 @@ const byte *GL_TextureData( unsigned int texnum ) return NULL; } -void R_ProcessEntData( qboolean allocate ) +static void R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities ) { if( !allocate ) { tr.draw_list->num_solid_entities = 0; tr.draw_list->num_trans_entities = 0; tr.draw_list->num_beam_entities = 0; + + tr.max_entities = 0; + tr.entities = NULL; + } + else + { + tr.max_entities = max_entities; + tr.entities = entities; } if( gEngfuncs.drawFuncs->R_ProcessEntData ) gEngfuncs.drawFuncs->R_ProcessEntData( allocate ); } -qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y ) +static void GAME_EXPORT R_Flush( unsigned int flags ) +{ + // stub +} + +/* +============= +R_SetSkyCloudsTextures + +Quake sky cloud texture was processed by the engine, +remember them for easier access during rendering +============== +*/ +static void GAME_EXPORT R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture ) +{ + tr.solidskyTexture = solidskyTexture; + tr.alphaskyTexture = alphaskyTexture; +} + +/* +=============== +R_SetupSky +=============== +*/ +static void GAME_EXPORT R_SetupSky( int *skyboxTextures ) +{ + int i; + + R_UnloadSkybox(); + + if( !skyboxTextures ) + return; + + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) + tr.skyboxTextures[i] = skyboxTextures[i]; +} + +static qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y ) { qboolean ret = true; if( rotate > 0 ) @@ -338,9 +386,39 @@ qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int return ret; } +static void GAME_EXPORT VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) +{ + pglTexSubImage2D( GL_TEXTURE_2D, 0, drawX, drawY, blockWidth, blockHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba ); +} + +static void GAME_EXPORT VGUI_SetupDrawing( qboolean rect ) +{ + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + if( rect ) + { + pglDisable( GL_ALPHA_TEST ); + } + else + { + pglEnable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + } +} + +static void GAME_EXPORT R_OverrideTextureSourceSize( unsigned int texnum, uint srcWidth, uint srcHeight ) +{ + gl_texture_t *tx = R_GetTexture( texnum ); + + tx->srcWidth = srcWidth; + tx->srcHeight = srcHeight; +} + static void* GAME_EXPORT R_GetProcAddress( const char *name ) { -#ifdef XASH_GL4ES +#if XASH_GL4ES return gl4es_GetProcAddress( name ); #else // TODO: other wrappers return gEngfuncs.GL_GetProcAddress( name ); @@ -352,7 +430,7 @@ static const char *R_GetConfigName( void ) return "opengl"; } -ref_interface_t gReffuncs = +static const ref_interface_t gReffuncs = { R_Init, R_Shutdown, @@ -363,6 +441,7 @@ ref_interface_t gReffuncs = GL_InitExtensions, GL_ClearExtensions, + R_GammaChanged, R_BeginFrame, R_RenderScene, R_EndFrame, @@ -378,6 +457,7 @@ ref_interface_t gReffuncs = R_AddEntity, CL_AddCustomBeam, R_ProcessEntData, + R_Flush, R_ShowTextures, @@ -389,9 +469,7 @@ ref_interface_t gReffuncs = R_Set2DMode, R_DrawStretchRaw, R_DrawStretchPic, - R_DrawTileClear, CL_FillRGBA, - CL_FillRGBABlend, R_WorldToScreen, VID_ScreenShot, @@ -408,14 +486,13 @@ ref_interface_t gReffuncs = R_StudioLerpMovement, CL_InitStudioAPI, - R_InitSkyClouds, + R_SetSkyCloudsTextures, GL_SubdivideSurface, CL_RunLightStyles, R_GetSpriteParms, R_GetSpriteTexture, - Mod_LoadMapSprite, Mod_ProcessRenderData, Mod_StudioLoadTextures, @@ -440,6 +517,7 @@ ref_interface_t gReffuncs = GL_LoadTextureArray, GL_CreateTextureArray, GL_FreeTexture, + R_OverrideTextureSourceSize, DrawSingleDecal, R_DecalSetupVerts, @@ -485,43 +563,23 @@ ref_interface_t gReffuncs = TriFogParams, TriCullFace, - VGUI_DrawInit, - VGUI_DrawShutdown, - VGUI_SetupDrawingText, - VGUI_SetupDrawingRect, - VGUI_SetupDrawingImage, - VGUI_BindTexture, - VGUI_EnableTexture, - VGUI_CreateTexture, - VGUI_UploadTexture, + VGUI_SetupDrawing, VGUI_UploadTextureBlock, - VGUI_DrawQuad, - VGUI_GetTextureSizes, - VGUI_GenerateTexture, }; +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ); int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ) { if( version != REF_API_VERSION ) return 0; // fill in our callbacks - memcpy( funcs, &gReffuncs, sizeof( ref_interface_t )); - memcpy( &gEngfuncs, engfuncs, sizeof( ref_api_t )); + *funcs = gReffuncs; + gEngfuncs = *engfuncs; gpGlobals = globals; + gp_cl = (ref_client_t *)ENGINE_GET_PARM( PARM_GET_CLIENT_PTR ); + gp_host = (ref_host_t *)ENGINE_GET_PARM( PARM_GET_HOST_PTR ); + return REF_API_VERSION; } - -void EXPORT GetRefHumanReadableName( char *out, size_t size ) -{ -#if defined XASH_NANOGL - Q_strncpy( out, "GLES1(NanoGL)", size ); -#elif defined XASH_WES - Q_strncpy( out, "GLES2(gl-wes-v2)", size ); -#elif defined XASH_GL4ES - Q_strncpy( out, "GLES2(gl4es)", size ); -#else - Q_strncpy( out, "OpenGL", size ); -#endif -} diff --git a/ref/gl/gl_cull.c b/ref/gl/gl_cull.c index 3cd3af1d..d432b073 100644 --- a/ref/gl/gl_cull.c +++ b/ref/gl/gl_cull.c @@ -35,18 +35,6 @@ qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ) return GL_FrustumCullBox( &RI.frustum, mins, maxs, 0 ); } -/* -================= -R_CullSphere - -Returns true if the sphere is completely outside the frustum -================= -*/ -qboolean R_CullSphere( const vec3_t centre, const float radius ) -{ - return GL_FrustumCullSphere( &RI.frustum, centre, radius, 0 ); -} - /* ============= R_CullModel @@ -54,7 +42,7 @@ R_CullModel */ int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) { - if( e == gEngfuncs.GetViewModel() ) + if( e == tr.viewent ) { if( ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) return 1; @@ -65,10 +53,6 @@ int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) return 1; } - // local client can't view himself if camera or thirdperson is not active - if( RP_LOCALCLIENT( e ) && !ENGINE_GET_PARM( PARM_THIRDPERSON ) && CL_IsViewEntityLocalPlayer()) - return 1; - if( R_CullBox( absmin, absmax )) return 1; @@ -89,11 +73,11 @@ int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ) if( !surf || !surf->texinfo || !surf->texinfo->texture ) return CULL_OTHER; - if( r_nocull->value ) + if( r_nocull.value ) return CULL_VISIBLE; // world surfaces can be culled by vis frame too - if( RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) && surf->visframe != tr.framecount ) + if( RI.currententity == CL_GetEntityByIndex( 0 ) && surf->visframe != tr.framecount ) return CULL_VISFRAME; // only static ents can be culled by frustum @@ -108,7 +92,7 @@ int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ) { vec3_t orthonormal; - if( e == gEngfuncs.GetEntityByIndex( 0 ) ) orthonormal[2] = surf->plane->normal[2]; + if( e == CL_GetEntityByIndex( 0 )) orthonormal[2] = surf->plane->normal[2]; else Matrix4x4_VectorRotate( RI.objectMatrix, surf->plane->normal, orthonormal ); dist = orthonormal[2]; } diff --git a/ref/gl/gl_dbghulls.c b/ref/gl/gl_dbghulls.c deleted file mode 100644 index d21cfae5..00000000 --- a/ref/gl/gl_dbghulls.c +++ /dev/null @@ -1,90 +0,0 @@ -/* -gl_dbghulls.c - loading & handling world and brushmodels -Copyright (C) 2016 Uncle Mike - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "gl_local.h" -#include "mod_local.h" - -#define list_entry( ptr, type, member ) \ - ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member))) - -// iterate over each entry in the list -#define list_for_each_entry( pos, head, member ) \ - for( pos = list_entry( (head)->next, winding_t, member ); \ - &pos->member != (head); \ - pos = list_entry( pos->member.next, winding_t, member )) - -// REFTODO: rewrite in triapi -void R_DrawWorldHull( void ) -{ - hull_model_t *hull = &WORLD->hull_models[0]; - winding_t *poly; - int i; - - if( FBitSet( r_showhull->flags, FCVAR_CHANGED )) - { - int val = bound( 0, (int)r_showhull->value, 3 ); - if( val ) gEngfuncs.Mod_CreatePolygonsForHull( val ); - ClearBits( r_showhull->flags, FCVAR_CHANGED ); - } - - if( !CVAR_TO_BOOL( r_showhull )) - return; - pglDisable( GL_TEXTURE_2D ); - - list_for_each_entry( poly, &hull->polys, chain ) - { - srand((unsigned int)poly); - pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 ); - pglBegin( GL_POLYGON ); - for( i = 0; i < poly->numpoints; i++ ) - pglVertex3fv( poly->p[i] ); - pglEnd(); - } - pglEnable( GL_TEXTURE_2D ); -} - -void R_DrawModelHull( void ) -{ - hull_model_t *hull; - winding_t *poly; - int i; - - if( !CVAR_TO_BOOL( r_showhull )) - return; - - if( !RI.currentmodel || RI.currentmodel->name[0] != '*' ) - return; - - i = atoi( RI.currentmodel->name + 1 ); - if( i < 1 || i >= WORLD->num_hull_models ) - return; - - hull = &WORLD->hull_models[i]; - - pglPolygonOffset( 1.0f, 2.0 ); - pglEnable( GL_POLYGON_OFFSET_FILL ); - pglDisable( GL_TEXTURE_2D ); - list_for_each_entry( poly, &hull->polys, chain ) - { - srand((unsigned int)poly); - pglColor3f( rand() % 256 / 255.0, rand() % 256 / 255.0, rand() % 256 / 255.0 ); - pglBegin( GL_POLYGON ); - for( i = 0; i < poly->numpoints; i++ ) - pglVertex3fv( poly->p[i] ); - pglEnd(); - } - pglEnable( GL_TEXTURE_2D ); - pglDisable( GL_POLYGON_OFFSET_FILL ); -} diff --git a/ref/gl/gl_decals.c b/ref/gl/gl_decals.c index 2b3f82b8..deaae786 100644 --- a/ref/gl/gl_decals.c +++ b/ref/gl/gl_decals.c @@ -74,7 +74,7 @@ static void R_DecalUnlink( decal_t *pdecal ) else { tmp = pdecal->psurface->pdecals; - if( !tmp ) gEngfuncs.Host_Error( "R_DecalUnlink: bad decal list\n" ); + if( !tmp ) gEngfuncs.Host_Error( "%s: bad decal list\n", __func__ ); while( tmp->pnext ) { @@ -143,7 +143,7 @@ static void R_GetDecalDimensions( int texture, int *width, int *height ) //----------------------------------------------------------------------------- // compute the decal basis based on surface normal //----------------------------------------------------------------------------- -void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ) +static void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ) { vec3_t surfaceNormal; @@ -179,7 +179,7 @@ void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[ VectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] ); } -void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +static void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) { int width, height; @@ -199,7 +199,7 @@ void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int textu } // Build the initial list of vertices from the surface verts into the global array, 'verts'. -void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) +static void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) { float *v; int i; @@ -214,7 +214,7 @@ void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t tex } // Figure out where the decal maps onto the surface. -void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +static void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) { R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); @@ -229,7 +229,7 @@ void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t te // Clip polygon to decal in texture space // JAY: This code is lame, change it later. It does way too much work per frame // It can be made to recursively call the clipping code and only copy the vertex list once -int R_ClipInside( float *vert, int edge ) +static int R_ClipInside( float *vert, int edge ) { switch( edge ) { @@ -253,7 +253,7 @@ int R_ClipInside( float *vert, int edge ) return 0; } -void R_ClipIntersect( float *one, float *two, float *out, int edge ) +static void R_ClipIntersect( float *one, float *two, float *out, int edge ) { float t; @@ -354,7 +354,7 @@ static int SHClip( float *vert, int vertCount, float *out, int edge ) return outCount; } -float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) +static float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) { float *pOutVerts = g_DecalClipVerts[0]; int outCount; @@ -374,7 +374,7 @@ float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int * //----------------------------------------------------------------------------- // Generate clipped vertex list for decal pdecal projected onto polygon psurf //----------------------------------------------------------------------------- -float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) +static float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) { float decalWorldScale[2]; vec3_t textureSpaceBasis[3]; @@ -391,30 +391,15 @@ float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pV // Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) { - float s, t; - mtexinfo_t *tex; - mextrasurf_t *info = surf->info; float sample_size; int j; sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - tex = surf->texinfo; for( j = 0; j < vertCount; j++, v += VERTEXSIZE ) { // lightmap texture coordinates - s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; - s += surf->light_s * sample_size; - s += sample_size * 0.5f; - s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; - - t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; - t += surf->light_t * sample_size; - t += sample_size * 0.5f; - t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; - - v[5] = s; - v[6] = t; + R_LightmapCoord( v, surf, sample_size, &v[5] ); } } @@ -507,10 +492,10 @@ R_DecalCreatePoly creates mesh for decal on first rendering ==================== */ -glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) +static glpoly2_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) { int lnumverts; - glpoly_t *poly; + glpoly2_t *poly; float *v; int i; @@ -522,7 +507,7 @@ glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t // allocate glpoly // REFTODO: com_studiocache pool! - poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); + poly = Mem_Calloc( r_temppool, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float )); poly->next = pdecal->polys; poly->flags = surf->flags; pdecal->polys = poly; @@ -610,7 +595,7 @@ static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, fl R_AddDecalToSurface( pdecal, surf, decalinfo ); } -void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) +static void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) { // get the texture associated with this surface mtexinfo_t *tex = surf->texinfo; @@ -685,10 +670,14 @@ static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *dec // iterate over all surfaces in the node msurface_t *surf; int i; + int firstsurface, numsurfaces; - surf = model->surfaces + node->firstsurface; + firstsurface = node_firstsurface( node, model ); + numsurfaces = node_numsurfaces( node, model ); - for( i = 0; i < node->numsurfaces; i++, surf++ ) + surf = model->surfaces + firstsurface; + + for( i = 0; i < numsurfaces; i++, surf++ ) { // never apply decals on the water or sky surfaces if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR)) @@ -710,6 +699,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) { mplane_t *splitplane; float dist; + mnode_t *children[2]; Assert( node != NULL ); @@ -721,6 +711,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) splitplane = node->plane; dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist; + node_children( children, node, model ); // This is arbitrarily set to 10 right now. In an ideal world we'd have the // exact surface but we don't so, this tells me which planes are "sort of @@ -732,19 +723,19 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) // have a surface normal if( dist > decalinfo->m_Size ) { - R_DecalNode( model, node->children[0], decalinfo ); + R_DecalNode( model, children[0], decalinfo ); } else if( dist < -decalinfo->m_Size ) { - R_DecalNode( model, node->children[1], decalinfo ); + R_DecalNode( model, children[1], decalinfo ); } else { if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) R_DecalNodeSurfaces( model, node, decalinfo ); - R_DecalNode( model, node->children[0], decalinfo ); - R_DecalNode( model, node->children[1], decalinfo ); + R_DecalNode( model, children[0], decalinfo ); + R_DecalNode( model, children[1], decalinfo ); } } @@ -765,15 +756,15 @@ void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos if( entityIndex > 0 ) { - ent = gEngfuncs.GetEntityByIndex( entityIndex ); + ent = CL_GetEntityByIndex( entityIndex ); - if( modelIndex > 0 ) model = gEngfuncs.pfnGetModelByIndex( modelIndex ); - else if( ent != NULL ) model = gEngfuncs.pfnGetModelByIndex( ent->curstate.modelindex ); + if( modelIndex > 0 ) model = CL_ModelHandle( modelIndex ); + else if( ent != NULL ) model = CL_ModelHandle( ent->curstate.modelindex ); else return; } else if( modelIndex > 0 ) - model = gEngfuncs.pfnGetModelByIndex( modelIndex ); - else model = WORLDMODEL; + model = CL_ModelHandle( modelIndex ); + else model = CL_ModelHandle( 1 ); if( !model ) return; @@ -844,7 +835,7 @@ void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos // triangles the same way. float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ) { - glpoly_t *p = pDecal->polys; + glpoly2_t *p = pDecal->polys; int i, count; float *v, *v2; @@ -927,10 +918,10 @@ void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ) if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) GL_Cull( GL_NONE ); - if( gl_polyoffset->value ) + if( gl_polyoffset.value ) { pglEnable( GL_POLYGON_OFFSET_FILL ); - pglPolygonOffset( -1.0f, -gl_polyoffset->value ); + pglPolygonOffset( -1.0f, -gl_polyoffset.value ); } } @@ -1020,7 +1011,7 @@ void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ) pglEnable( GL_ALPHA_TEST ); } - if( gl_polyoffset->value ) + if( gl_polyoffset.value ) pglDisable( GL_POLYGON_OFFSET_FILL ); if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) @@ -1058,10 +1049,10 @@ void DrawDecalsBatch( void ) if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) GL_Cull( GL_NONE ); - if( gl_polyoffset->value ) + if( gl_polyoffset.value ) { pglEnable( GL_POLYGON_OFFSET_FILL ); - pglPolygonOffset( -1.0f, -gl_polyoffset->value ); + pglPolygonOffset( -1.0f, -gl_polyoffset.value ); } for( i = 0; i < tr.num_draw_decals; i++ ) @@ -1076,7 +1067,7 @@ void DrawDecalsBatch( void ) pglDisable( GL_ALPHA_TEST ); } - if( gl_polyoffset->value ) + if( gl_polyoffset.value ) pglDisable( GL_POLYGON_OFFSET_FILL ); if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) @@ -1188,7 +1179,7 @@ int R_CreateDecalList( decallist_t *pList ) pList[total].scale = decal->scale; R_DecalUnProject( decal, &pList[total] ); - COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name )); // check to see if the decal should be added total = DecalListAdd( pList, total ); diff --git a/ref/gl/gl_draw.c b/ref/gl/gl_draw.c index ad383f35..71d5fdc7 100644 --- a/ref/gl/gl_draw.c +++ b/ref/gl/gl_draw.c @@ -81,39 +81,6 @@ void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, f pglEnd(); } -/* -============= -Draw_TileClear - -This repeats a 64*64 tile graphic to fill the screen around a sized down -refresh window. -============= -*/ -void R_DrawTileClear( int texnum, int x, int y, int w, int h ) -{ - float tw, th; - gl_texture_t *glt; - - GL_SetRenderMode( kRenderNormal ); - pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); - GL_Bind( XASH_TEXTURE0, texnum ); - - glt = R_GetTexture( texnum ); - tw = glt->srcWidth; - th = glt->srcHeight; - - pglBegin( GL_QUADS ); - pglTexCoord2f( x / tw, y / th ); - pglVertex2f( x, y ); - pglTexCoord2f((x + w) / tw, y / th ); - pglVertex2f( x + w, y ); - pglTexCoord2f((x + w) / tw, (y + h) / th ); - pglVertex2f( x + w, y + h ); - pglTexCoord2f( x / tw, (y + h) / th ); - pglVertex2f( x, y + h ); - pglEnd (); -} - /* ============= R_DrawStretchRaw @@ -145,9 +112,9 @@ void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, c } if( cols > glConfig.max_2d_texture_size ) - gEngfuncs.Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", cols ); + gEngfuncs.Host_Error( "%s: size %i exceeds hardware limits\n", __func__, cols ); if( rows > glConfig.max_2d_texture_size ) - gEngfuncs.Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", rows ); + gEngfuncs.Host_Error( "%s: size %i exceeds hardware limits\n", __func__, rows ); pglDisable( GL_BLEND ); pglDisable( GL_ALPHA_TEST ); @@ -220,9 +187,9 @@ void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, } if( cols > glConfig.max_2d_texture_size ) - gEngfuncs.Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", cols ); + gEngfuncs.Host_Error( "%s: size %i exceeds hardware limits\n", __func__, cols ); if( rows > glConfig.max_2d_texture_size ) - gEngfuncs.Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", rows ); + gEngfuncs.Host_Error( "%s: size %i exceeds hardware limits\n", __func__, rows ); tex = R_GetTexture( texture ); GL_Bind( GL_KEEP_UNIT, texture ); @@ -260,6 +227,9 @@ void R_Set2DMode( qboolean enable ) pglEnable( GL_ALPHA_TEST ); pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + if( glConfig.max_multisamples > 1 && gl_msaa.value ) + pglDisable( GL_MULTISAMPLE_ARB ); + glState.in2DMode = true; RI.currententity = NULL; RI.currentmodel = NULL; @@ -276,6 +246,13 @@ void R_Set2DMode( qboolean enable ) pglMatrixMode( GL_MODELVIEW ); GL_LoadMatrix( RI.worldviewMatrix ); + if( glConfig.max_multisamples > 1 ) + { + if( gl_msaa.value ) + pglEnable( GL_MULTISAMPLE_ARB ); + else pglDisable( GL_MULTISAMPLE_ARB ); + } + GL_Cull( GL_FRONT ); } } diff --git a/ref/gl/gl_export.h b/ref/gl/gl_export.h index 13b7b4d1..0b167513 100644 --- a/ref/gl/gl_export.h +++ b/ref/gl/gl_export.h @@ -20,14 +20,20 @@ GNU General Public License for more details. #endif #ifndef APIENTRY_LINKAGE -#define APIENTRY_LINKAGE extern + #define APIENTRY_LINKAGE extern #endif -#if defined XASH_NANOGL || defined XASH_WES || defined XASH_REGAL -#define XASH_GLES -#define XASH_GL_STATIC -#define REF_GL_KEEP_MANGLED_FUNCTIONS -#endif +#if XASH_NANOGL || XASH_WES || XASH_REGAL + #define XASH_GLES 1 + #define XASH_GL_STATIC 1 + #define REF_GL_KEEP_MANGLED_FUNCTIONS 1 +#elif XASH_GLES3COMPAT + #ifdef SOFTFP_LINK + #undef APIENTRY + #define APIENTRY __attribute__((pcs("aapcs"))) + #endif // SOFTFP_LINK + #define XASH_GLES 1 +#endif // XASH_GLES3COMPAT typedef uint GLenum; typedef byte GLboolean; @@ -811,6 +817,19 @@ typedef float GLmatrix[16]; #define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 #define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 +// GL Core additions +#define GL_NUM_EXTENSIONS 0x821D +#define GL_MAP_WRITE_BIT 0x0002 +#define GL_MAP_COHERENT_BIT 0x0080 +#define GL_MAP_PERSISTENT_BIT 0x0040 +#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 +#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 + +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C + #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 #define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 @@ -877,17 +896,23 @@ typedef float GLmatrix[16]; #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_SAMPLES_ARB 0x2042 -#if defined( XASH_GL_STATIC ) && !defined( REF_GL_KEEP_MANGLED_FUNCTIONS ) -#define GL_FUNCTION( name ) name -#elif defined( XASH_GL_STATIC ) && defined( REF_GL_KEEP_MANGLED_FUNCTIONS ) -#define GL_FUNCTION( name ) APIENTRY p##name +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#if XASH_GL_STATIC && !REF_GL_KEEP_MANGLED_FUNCTIONS + #define GL_FUNCTION( name ) APIENTRY name +#elif XASH_GL_STATIC && REF_GL_KEEP_MANGLED_FUNCTIONS + #define GL_FUNCTION( name ) APIENTRY p##name #else -#define GL_FUNCTION( name ) (APIENTRY *p##name) + #define GL_FUNCTION( name ) (APIENTRY *p##name) #endif // helper opengl functions APIENTRY_LINKAGE GLenum GL_FUNCTION( glGetError )(void); APIENTRY_LINKAGE const GLubyte * GL_FUNCTION( glGetString )(GLenum name); +APIENTRY_LINKAGE const GLubyte * GL_FUNCTION( glGetStringi )(GLenum name, GLint i); // base gl functions APIENTRY_LINKAGE void GL_FUNCTION( glAccum )(GLenum op, GLfloat value); @@ -1229,12 +1254,6 @@ APIENTRY_LINKAGE void GL_FUNCTION( glClientActiveTextureARB )( GLenum ); APIENTRY_LINKAGE void GL_FUNCTION( glGetCompressedTexImage )( GLenum target, GLint lod, const GLvoid* data ); APIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElements )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); APIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElementsEXT )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); -APIENTRY_LINKAGE void GL_FUNCTION( glDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); -APIENTRY_LINKAGE void GL_FUNCTION( glVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); -APIENTRY_LINKAGE void GL_FUNCTION( glNormalPointer )(GLenum type, GLsizei stride, const GLvoid *ptr); -APIENTRY_LINKAGE void GL_FUNCTION( glColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); -APIENTRY_LINKAGE void GL_FUNCTION( glTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); -APIENTRY_LINKAGE void GL_FUNCTION( glArrayElement )(GLint i); APIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord1f) (GLenum, GLfloat); APIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord2f) (GLenum, GLfloat, GLfloat); APIENTRY_LINKAGE void GL_FUNCTION( glMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); @@ -1292,7 +1311,6 @@ APIENTRY_LINKAGE void GL_FUNCTION( glGetActiveUniformARB )(GLhandleARB programOb APIENTRY_LINKAGE void GL_FUNCTION( glGetUniformfvARB )(GLhandleARB programObj, GLint location, GLfloat *params); APIENTRY_LINKAGE void GL_FUNCTION( glGetUniformivARB )(GLhandleARB programObj, GLint location, GLint *params); APIENTRY_LINKAGE void GL_FUNCTION( glGetShaderSourceARB )(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); -APIENTRY_LINKAGE void GL_FUNCTION( glPolygonStipple )(const GLubyte *mask); APIENTRY_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 ); APIENTRY_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 ); APIENTRY_LINKAGE void GL_FUNCTION( glCopyTexSubImage3D )( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height ); @@ -1358,11 +1376,22 @@ APIENTRY_LINKAGE void GL_FUNCTION( glGenVertexArrays )( GLsizei n, const GLuint APIENTRY_LINKAGE GLboolean GL_FUNCTION( glIsVertexArray )( GLuint array ); APIENTRY_LINKAGE void GL_FUNCTION( glSwapInterval ) ( int interval ); -#if !defined( XASH_GLES ) && !defined( XASH_GL4ES ) +// arb shaders change in core +APIENTRY_LINKAGE void GL_FUNCTION( glDeleteProgram )(GLuint program); +APIENTRY_LINKAGE void GL_FUNCTION( glGetProgramiv )(GLuint program, GLenum e, GLuint *v); +APIENTRY_LINKAGE void GL_FUNCTION( glGetProgramInfoLog )(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); + +// gl2shim deps +APIENTRY_LINKAGE void GL_FUNCTION( glBufferStorage )( GLenum target, GLsizei size, const GLvoid * data, GLbitfield flags); +APIENTRY_LINKAGE void GL_FUNCTION( glFlushMappedBufferRange )(GLenum target, GLsizei offset, GLsizei length); +APIENTRY_LINKAGE void *GL_FUNCTION( glMapBufferRange )(GLenum target, GLsizei offset, GLsizei length, GLbitfield access); +APIENTRY_LINKAGE void GL_FUNCTION( glDrawRangeElementsBaseVertex )( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices, GLuint vertex ); + +#if !XASH_GL_STATIC || ( !XASH_GLES && !XASH_GL4ES ) APIENTRY_LINKAGE void GL_FUNCTION( glTexImage2DMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations); #endif /* !XASH_GLES && !XASH_GL4ES */ -#if defined( XASH_GL_STATIC ) && !defined( REF_GL_KEEP_MANGLED_FUNCTIONS ) +#if XASH_GL_STATIC && !REF_GL_KEEP_MANGLED_FUNCTIONS #define pglGetError glGetError #define pglGetString glGetString #define pglAccum glAccum @@ -1705,12 +1734,6 @@ APIENTRY_LINKAGE void GL_FUNCTION( glTexImage2DMultisample )(GLenum target, GLsi #define pglGetCompressedTexImage glGetCompressedTexImage #define pglDrawRangeElements glDrawRangeElements #define pglDrawRangeElementsEXT glDrawRangeElementsEXT -#define pglDrawElements glDrawElements -#define pglVertexPointer glVertexPointer -#define pglNormalPointer glNormalPointer -#define pglColorPointer glColorPointer -#define pglTexCoordPointer glTexCoordPointer -#define pglArrayElement glArrayElement #define pglMultiTexCoord1f glMultiTexCoord1f #define pglMultiTexCoord2f glMultiTexCoord2f #define pglMultiTexCoord3f glMultiTexCoord3f @@ -1768,7 +1791,6 @@ APIENTRY_LINKAGE void GL_FUNCTION( glTexImage2DMultisample )(GLenum target, GLsi #define pglGetUniformfvARB glGetUniformfvARB #define pglGetUniformivARB glGetUniformivARB #define pglGetShaderSourceARB glGetShaderSourceARB -#define pglPolygonStipple glPolygonStipple #define pglTexImage3D glTexImage3D #define pglTexSubImage3D glTexSubImage3D #define pglCopyTexSubImage3D glCopyTexSubImage3D @@ -1834,4 +1856,8 @@ APIENTRY_LINKAGE void GL_FUNCTION( glTexImage2DMultisample )(GLenum target, GLsi #define pglSwapInterval glSwapInterval #endif +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif + #endif//GL_EXPORT_H diff --git a/ref/gl/gl_frustum.c b/ref/gl/gl_frustum.c index 92e995f0..0e4b2103 100644 --- a/ref/gl/gl_frustum.c +++ b/ref/gl/gl_frustum.c @@ -16,22 +16,7 @@ GNU General Public License for more details. #include "gl_local.h" #include "xash3d_mathlib.h" -void GL_FrustumEnablePlane( gl_frustum_t *out, int side ) -{ - Assert( side >= 0 && side < FRUSTUM_PLANES ); - - // make sure what plane is ready - if( !VectorIsNull( out->planes[side].normal )) - SetBits( out->clipFlags, BIT( side )); -} - -void GL_FrustumDisablePlane( gl_frustum_t *out, int side ) -{ - Assert( side >= 0 && side < FRUSTUM_PLANES ); - ClearBits( out->clipFlags, BIT( side )); -} - -void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ) +static void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ) { Assert( side >= 0 && side < FRUSTUM_PLANES ); @@ -43,30 +28,6 @@ void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, fl SetBits( out->clipFlags, BIT( side )); } -void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ) -{ - float length; - - Assert( side >= 0 && side < FRUSTUM_PLANES ); - - // normalize - length = VectorLength( out->planes[side].normal ); - - if( length ) - { - float ilength = (1.0f / length); - out->planes[side].normal[0] *= ilength; - out->planes[side].normal[1] *= ilength; - out->planes[side].normal[2] *= ilength; - out->planes[side].dist *= ilength; - } - - out->planes[side].type = PlaneTypeForNormal( out->planes[side].normal ); - out->planes[side].signbits = SignbitsForPlane( out->planes[side].normal ); - - SetBits( out->clipFlags, BIT( side )); -} - void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ) { float xs, xc; @@ -135,143 +96,13 @@ void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yT GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, iup, -yBottom - orgOffset ); } -void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ) -{ - vec3_t normal; - int i; - - for( i = 0; i < FRUSTUM_PLANES; i++ ) - { - // setup normal for each direction - VectorClear( normal ); - normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f; - GL_FrustumSetPlane( out, i, normal, DotProduct( org, normal ) - radius ); - } -} - -void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ) -{ - int i; - - // left - out->planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0]; - out->planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0]; - out->planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0]; - out->planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]); - - // right - out->planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0]; - out->planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0]; - out->planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0]; - out->planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]); - - // bottom - out->planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1]; - out->planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1]; - out->planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1]; - out->planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]); - - // top - out->planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1]; - out->planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1]; - out->planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1]; - out->planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]); - - // near - out->planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2]; - out->planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2]; - out->planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2]; - out->planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]); - - // far - out->planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2]; - out->planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2]; - out->planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2]; - out->planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]); - - for( i = 0; i < FRUSTUM_PLANES; i++ ) - { - GL_FrustumNormalizePlane( out, i ); - } -} - -void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t corners[8] ) -{ - memset( corners, 0, sizeof( vec3_t ) * 8 ); - - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[0] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[1] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[2] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[3] ); - - if( FBitSet( out->clipFlags, BIT( FRUSTUM_NEAR ))) - { - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[4] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[5] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[6] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[7] ); - } - else - { - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], corners[4] ); - VectorCopy( corners[4], corners[5] ); - VectorCopy( corners[4], corners[6] ); - VectorCopy( corners[4], corners[7] ); - } -} - -void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ) -{ - vec3_t corners[8]; - int i; - - GL_FrustumComputeCorners( out, corners ); - - ClearBounds( mins, maxs ); - - for( i = 0; i < 8; i++ ) - AddPointToBounds( corners[i], mins, maxs ); -} - -void GL_FrustumDrawDebug( gl_frustum_t *out ) -{ - vec3_t bbox[8]; - int i; - - GL_FrustumComputeCorners( out, bbox ); - - // g-cont. frustum must be yellow :-) - pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f ); - pglDisable( GL_TEXTURE_2D ); - pglBegin( GL_LINES ); - - for( i = 0; i < 2; i += 1 ) - { - pglVertex3fv( bbox[i+0] ); - pglVertex3fv( bbox[i+2] ); - pglVertex3fv( bbox[i+4] ); - pglVertex3fv( bbox[i+6] ); - pglVertex3fv( bbox[i+0] ); - pglVertex3fv( bbox[i+4] ); - pglVertex3fv( bbox[i+2] ); - pglVertex3fv( bbox[i+6] ); - pglVertex3fv( bbox[i*2+0] ); - pglVertex3fv( bbox[i*2+1] ); - pglVertex3fv( bbox[i*2+4] ); - pglVertex3fv( bbox[i*2+5] ); - } - - pglEnd(); - pglEnable( GL_TEXTURE_2D ); -} - // cull methods -qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ) +qboolean GL_FrustumCullBox( const gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ) { int iClipFlags; int i, bit; - if( r_nocull->value ) + if( r_nocull.value ) return false; if( userClipFlags != 0 ) @@ -327,12 +158,12 @@ qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t m return false; } -qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t center, float radius, int userClipFlags ) +qboolean GL_FrustumCullSphere( const gl_frustum_t *out, const vec3_t center, float radius, int userClipFlags ) { int iClipFlags; int i, bit; - if( r_nocull->value ) + if( r_nocull.value ) return false; if( userClipFlags != 0 ) diff --git a/ref/gl/gl_frustum.h b/ref/gl/gl_frustum.h index 0b310a80..d54ee49b 100644 --- a/ref/gl/gl_frustum.h +++ b/ref/gl/gl_frustum.h @@ -33,20 +33,9 @@ typedef struct gl_frustum_s void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ); void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ); -void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ); // used for pointlights -void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ); -void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ); -void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ); -void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ); -void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t bbox[8] ); -void GL_FrustumDrawDebug( gl_frustum_t *out ); // cull methods -qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ); -qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags ); - -// plane manipulating -void GL_FrustumEnablePlane( gl_frustum_t *out, int side ); -void GL_FrustumDisablePlane( gl_frustum_t *out, int side ); +qboolean GL_FrustumCullBox( const gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ); +qboolean GL_FrustumCullSphere( const gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags ); #endif//GL_FRUSTUM_H diff --git a/ref/gl/gl_image.c b/ref/gl/gl_image.c index 658b0240..54e91d08 100644 --- a/ref/gl/gl_image.c +++ b/ref/gl/gl_image.c @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include #include "gl_local.h" #include "crclib.h" @@ -43,9 +44,9 @@ R_GetTexture acess to array elem ================= */ -gl_texture_t *R_GetTexture( GLenum texnum ) +gl_texture_t *R_GetTexture( unsigned int texnum ) { - ASSERT( texnum >= 0 && texnum < MAX_TEXTURES ); + Assert( texnum < MAX_TEXTURES ); return &gl_textures[texnum]; } @@ -54,7 +55,7 @@ gl_texture_t *R_GetTexture( GLenum texnum ) GL_TargetToString ================= */ -static const char *GL_TargetToString( GLenum target ) +const char *GL_TargetToString( GLenum target ) { switch( target ) { @@ -76,46 +77,26 @@ static const char *GL_TargetToString( GLenum target ) return "??"; } -/* -================= -GL_Bind -================= -*/ -void GL_Bind( GLint tmu, GLenum texnum ) +qboolean GL_TextureFilteringEnabled( const gl_texture_t *tex ) { - gl_texture_t *texture; - GLuint glTarget; + if( FBitSet( tex->flags, TF_NEAREST )) + return false; - // missed or invalid texture? - if( texnum <= 0 || texnum >= MAX_TEXTURES ) + if( FBitSet( tex->flags, TF_DEPTHMAP )) + return true; + + if( FBitSet( tex->flags, TF_NOMIPMAP ) || tex->numMips <= 1 ) { - if( texnum != 0 ) - gEngfuncs.Con_DPrintf( S_ERROR "GL_Bind: invalid texturenum %d\n", texnum ); - texnum = tr.defaultTexture; - } - if( tmu != GL_KEEP_UNIT ) - GL_SelectTexture( tmu ); - else tmu = glState.activeTMU; + if( FBitSet( tex->flags, TF_ATLAS_PAGE )) + return gl_lightmap_nearest.value == 0.0f; - texture = &gl_textures[texnum]; - glTarget = texture->target; + if( FBitSet( tex->flags, TF_ALLOW_NEAREST )) + return gl_texture_nearest.value == 0.0f; - if( glTarget == GL_TEXTURE_2D_ARRAY_EXT ) - glTarget = GL_TEXTURE_2D; - - if( glState.currentTextureTargets[tmu] != glTarget ) - { - if( glState.currentTextureTargets[tmu] != GL_NONE ) - pglDisable( glState.currentTextureTargets[tmu] ); - glState.currentTextureTargets[tmu] = glTarget; - pglEnable( glState.currentTextureTargets[tmu] ); + return true; } - if( glState.currentTextures[tmu] == texture->texnum ) - return; - - pglBindTexture( texture->target, texture->texnum ); - glState.currentTextures[tmu] = texture->texnum; + return gl_texture_nearest.value == 0.0f; } /* @@ -126,6 +107,7 @@ GL_ApplyTextureParams void GL_ApplyTextureParams( gl_texture_t *tex ) { vec4_t border = { 0.0f, 0.0f, 0.0f, 1.0f }; + qboolean nomipmap; if( !glw_state.initialized ) return; @@ -137,6 +119,18 @@ void GL_ApplyTextureParams( gl_texture_t *tex ) return; // set texture filter + nomipmap = tex->numMips <= 1 || FBitSet( tex->flags, TF_NOMIPMAP|TF_DEPTHMAP ); + if( !GL_TextureFilteringEnabled( tex )) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_NEAREST : GL_NEAREST_MIPMAP_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + if( FBitSet( tex->flags, TF_DEPTHMAP )) { if( !FBitSet( tex->flags, TF_NOCOMPARE )) @@ -149,54 +143,19 @@ void GL_ApplyTextureParams( gl_texture_t *tex ) pglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE ); else pglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY ); - if( FBitSet( tex->flags, TF_NEAREST )) - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - } - else - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - } - // allow max anisotropy as 1.0f on depth textures if( GL_Support( GL_ANISOTROPY_EXT )) pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f ); } - else if( FBitSet( tex->flags, TF_NOMIPMAP ) || tex->numMips <= 1 ) + else if( !FBitSet( tex->flags, TF_NOMIPMAP ) && tex->numMips > 1 ) { - if( FBitSet( tex->flags, TF_NEAREST ) || ( IsLightMap( tex ) && gl_lightmap_nearest->value )) - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - } - else - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - } - } - else - { - if( FBitSet( tex->flags, TF_NEAREST ) || gl_texture_nearest->value ) - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - } - else - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - } - // set texture anisotropy if available if( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_ALPHACONTRAST )) - pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy->value ); + pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value ); // set texture LOD bias if available if( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 )) - pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias->value ); + pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias.value ); } // check if border is not supported @@ -266,6 +225,7 @@ GL_UpdateTextureParams static void GL_UpdateTextureParams( int iTexture ) { gl_texture_t *tex = &gl_textures[iTexture]; + qboolean nomipmap; Assert( tex != NULL ); @@ -275,36 +235,22 @@ static void GL_UpdateTextureParams( int iTexture ) // set texture anisotropy if available if( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP|TF_ALPHACONTRAST )) - pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy->value ); + pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy.value ); // set texture LOD bias if available if( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP )) - pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias->value ); + pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias.value ); - if( IsLightMap( tex )) + nomipmap = tex->numMips <= 1 || FBitSet( tex->flags, TF_NOMIPMAP|TF_DEPTHMAP ); + + if( !GL_TextureFilteringEnabled( tex )) { - if( gl_lightmap_nearest->value ) - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); - } - else - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - } - } - - if( tex->numMips <= 1 ) return; - - if( FBitSet( tex->flags, TF_NEAREST ) || gl_texture_nearest->value ) - { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_NEAREST : GL_NEAREST_MIPMAP_NEAREST ); pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); } else { - pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, nomipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR ); pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } } @@ -320,24 +266,24 @@ void R_SetTextureParameters( void ) if( GL_Support( GL_ANISOTROPY_EXT )) { - if( gl_texture_anisotropy->value > glConfig.max_texture_anisotropy ) + if( gl_texture_anisotropy.value > glConfig.max_texture_anisotropy ) gEngfuncs.Cvar_SetValue( "gl_anisotropy", glConfig.max_texture_anisotropy ); - else if( gl_texture_anisotropy->value < 1.0f ) + else if( gl_texture_anisotropy.value < 1.0f ) gEngfuncs.Cvar_SetValue( "gl_anisotropy", 1.0f ); } if( GL_Support( GL_TEXTURE_LOD_BIAS )) { - if( gl_texture_lodbias->value < -glConfig.max_texture_lod_bias ) + if( gl_texture_lodbias.value < -glConfig.max_texture_lod_bias ) gEngfuncs.Cvar_SetValue( "gl_texture_lodbias", -glConfig.max_texture_lod_bias ); - else if( gl_texture_lodbias->value > glConfig.max_texture_lod_bias ) + else if( gl_texture_lodbias.value > glConfig.max_texture_lod_bias ) gEngfuncs.Cvar_SetValue( "gl_texture_lodbias", glConfig.max_texture_lod_bias ); } - ClearBits( gl_texture_anisotropy->flags, FCVAR_CHANGED ); - ClearBits( gl_texture_lodbias->flags, FCVAR_CHANGED ); - ClearBits( gl_texture_nearest->flags, FCVAR_CHANGED ); - ClearBits( gl_lightmap_nearest->flags, FCVAR_CHANGED ); + ClearBits( gl_texture_anisotropy.flags, FCVAR_CHANGED ); + ClearBits( gl_texture_lodbias.flags, FCVAR_CHANGED ); + ClearBits( gl_texture_nearest.flags, FCVAR_CHANGED ); + ClearBits( gl_lightmap_nearest.flags, FCVAR_CHANGED ); // change all the existing mipmapped texture objects for( i = 0; i < gl_numTextures; i++ ) @@ -388,7 +334,8 @@ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int d case PF_DXT5: case PF_BC6H_SIGNED: case PF_BC6H_UNSIGNED: - case PF_BC7: + case PF_BC7_UNORM: + case PF_BC7_SRGB: case PF_ATI2: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; @@ -500,7 +447,7 @@ static size_t GL_CalcTextureSize( GLenum format, int width, int height, int dept size = width * height * depth * 4; break; default: - gEngfuncs.Host_Error( "GL_CalcTextureSize: bad texture internal format (%u)\n", format ); + gEngfuncs.Host_Error( "%s: bad texture internal format (%u)\n", __func__, format ); break; } @@ -576,7 +523,7 @@ static void GL_SetTextureDimensions( gl_texture_t *tex, int width, int height, i if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) { - int step = (int)gl_round_down->value; + int step = (int)gl_round_down.value; int scaled_width, scaled_height; for( scaled_width = 1; scaled_width < width; scaled_width <<= 1 ); @@ -643,9 +590,12 @@ static void GL_SetTextureTarget( gl_texture_t *tex, rgbdata_t *pic ) pic->numMips = Q_max( 1, pic->numMips ); // trying to determine texture type +#if !XASH_GLES if( pic->width > 1 && pic->height <= 1 ) tex->target = GL_TEXTURE_1D; - else if( FBitSet( pic->flags, IMAGE_CUBEMAP )) + else +#endif // just skip first condition + if( FBitSet( pic->flags, IMAGE_CUBEMAP )) tex->target = GL_TEXTURE_CUBE_MAP_ARB; else if( FBitSet( pic->flags, IMAGE_MULTILAYER ) && pic->depth >= 1 ) tex->target = GL_TEXTURE_2D_ARRAY_EXT; @@ -694,7 +644,7 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan Assert( tex != NULL ); - if( ImageDXT( format )) + if( ImageCompressed( format )) { switch( format ) { @@ -703,7 +653,8 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; case PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break; case PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break; - case PF_BC7: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break; + case PF_BC7_SRGB: + case PF_BC7_UNORM: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break; case PF_ATI2: if( glConfig.hardware_type == GLHW_RADEON ) tex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; @@ -879,7 +830,7 @@ GL_BoxFilter3x3 box filter 3x3 ================= */ -void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) +static void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) { int r = 0, g = 0, b = 0, a = 0; int count = 0, acount = 0; @@ -926,7 +877,7 @@ GL_ApplyFilter Apply box-filter to 1-bit alpha ================= */ -byte *GL_ApplyFilter( const byte *source, int width, int height ) +static byte *GL_ApplyFilter( const byte *source, int width, int height ) { byte *in = (byte *)source; byte *out = (byte *)source; @@ -1038,8 +989,8 @@ static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, static void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, GLint type, const void *data ) { GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; - qboolean subImage = FBitSet( tex->flags, TF_IMG_UPLOADED ); - GLenum inFormat = gEngfuncs.Image_GetPFDesc(type)->glFormat; + qboolean subImage = FBitSet( tex->flags, TF_IMG_UPLOADED ) && data != NULL; + GLenum inFormat = gEngfuncs.Image_GetPFDesc( type )->glFormat; GLint dataType = GL_UNSIGNED_BYTE; GLsizei samplesCount = 0; @@ -1070,8 +1021,8 @@ static void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLin } else if( tex->target == GL_TEXTURE_2D_MULTISAMPLE ) { -#if !defined( XASH_GLES ) && !defined( XASH_GL4ES ) - samplesCount = (GLsizei)gEngfuncs.pfnGetCvarFloat("gl_msaa_samples"); +#if !defined( XASH_GL_STATIC ) || (!defined( XASH_GLES ) && !defined( XASH_GL4ES )) + samplesCount = (GLsizei)gEngfuncs.pfnGetCvarFloat( "gl_msaa_samples" ); switch (samplesCount) { case 2: @@ -1083,9 +1034,9 @@ static void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLin samplesCount = 1; } pglTexImage2DMultisample( tex->target, samplesCount, tex->format, width, height, GL_TRUE ); -#else /* !XASH_GLES && !XASH_GL4ES */ +#else /* !XASH_GL_STATIC !XASH_GLES && !XASH_GL4ES */ gEngfuncs.Con_Printf( S_ERROR "GLES renderer don't support GL_TEXTURE_2D_MULTISAMPLE!\n" ); -#endif /* !XASH_GLES && !XASH_GL4ES */ +#endif /* !XASH_GL_STATIC !XASH_GLES && !XASH_GL4ES */ } else // 2D or RECT { @@ -1094,14 +1045,14 @@ static void GL_TextureImageRAW( gl_texture_t *tex, GLint side, GLint level, GLin } } -static void GL_TextureImageDXT( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data ) +static void GL_TextureImageCompressed( gl_texture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data ) { GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; qboolean subImage = FBitSet( tex->flags, TF_IMG_UPLOADED ); Assert( tex != NULL ); -#ifndef XASH_GLES +#if !XASH_GLES if( tex->target == GL_TEXTURE_1D ) { if( subImage ) pglCompressedTexSubImage1DARB( tex->target, level, 0, width, tex->format, size, data ); @@ -1139,7 +1090,7 @@ static void GL_CheckTexImageError( gl_texture_t *tex ) Assert( tex != NULL ); // catch possible errors - if( CVAR_TO_BOOL( gl_check_errors ) && ( err = pglGetError()) != GL_NO_ERROR ) + if( gl_check_errors.value && ( err = pglGetError()) != GL_NO_ERROR ) gEngfuncs.Con_Printf( S_OPENGL_ERROR "%s while uploading %s [%s]\n", GL_ErrorString( err ), tex->name, GL_TargetToString( tex->target )); } @@ -1172,15 +1123,15 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) // make sure what target is correct if( tex->target == GL_NONE ) { - gEngfuncs.Con_DPrintf( S_ERROR "GL_UploadTexture: %s is not supported by your hardware\n", tex->name ); + gEngfuncs.Con_DPrintf( S_ERROR "%s: %s is not supported by your hardware\n", __func__, tex->name ); return false; } - if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7 ) + if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7_UNORM || pic->type == PF_BC7_SRGB ) { if( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC )) { - gEngfuncs.Con_DPrintf( S_ERROR "GL_UploadTexture: BC6H/BC7 compression formats is not supported by your hardware\n" ); + gEngfuncs.Con_DPrintf( S_ERROR "%s: BC6H/BC7 compression formats is not supported by your hardware\n", __func__ ); return false; } } @@ -1196,7 +1147,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) if(( pic->width * pic->height ) & 3 ) { // will be resampled, just tell me for debug targets - gEngfuncs.Con_Reportf( "GL_UploadTexture: %s s&3 [%d x %d]\n", tex->name, pic->width, pic->height ); + gEngfuncs.Con_Reportf( "%s: %s s&3 [%d x %d]\n", __func__, tex->name, pic->width, pic->height ); } buf = pic->buffer; @@ -1208,15 +1159,16 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) // uploading texture into video memory, change the binding glState.currentTextures[glState.activeTMU] = tex->texnum; + glState.currentTexturesIndex[glState.activeTMU] = tex - gl_textures; pglBindTexture( tex->target, tex->texnum ); for( i = 0; i < numSides; i++ ) { // track the buffer bounds if( buf != NULL && buf >= bufend ) - gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name ); + gEngfuncs.Host_Error( "%s: %s image buffer overflow\n", __func__, tex->name ); - if( ImageDXT( pic->type )) + if( ImageCompressed( pic->type )) { for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) { @@ -1224,7 +1176,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) height = Q_max( 1, ( tex->height >> j )); texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); size = GL_CalcImageSize( pic->type, width, height, tex->depth ); - GL_TextureImageDXT( tex, i, j, width, height, tex->depth, size, buf ); + GL_TextureImageCompressed( tex, i, j, width, height, tex->depth, size, buf ); tex->size += texsize; buf += size; // move pointer tex->numMips++; @@ -1258,7 +1210,7 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); else data = buf; - if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) + if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) data = GL_ApplyFilter( data, tex->width, tex->height ); // mips will be auto-generated if desired @@ -1306,7 +1258,7 @@ static void GL_ProcessImage( gl_texture_t *tex, rgbdata_t *pic ) tex->encode = pic->encode; // share encode method - if( ImageDXT( pic->type )) + if( ImageCompressed( pic->type )) { if( !pic->numMips ) tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request @@ -1350,7 +1302,7 @@ static void GL_ProcessImage( gl_texture_t *tex, rgbdata_t *pic ) GL_CheckTexName ================ */ -qboolean GL_CheckTexName( const char *name ) +static qboolean GL_CheckTexName( const char *name ) { int len; @@ -1398,29 +1350,54 @@ GL_AllocTexture */ static gl_texture_t *GL_AllocTexture( const char *name, texFlags_t flags ) { - gl_texture_t *tex; - uint i; + const qboolean skyboxhack = FBitSet( flags, TF_SKYSIDE ) && glConfig.context == CONTEXT_TYPE_GL; + gl_texture_t *tex = NULL; + GLuint texnum = 1; - // find a free texture_t slot - for( i = 0, tex = gl_textures; i < gl_numTextures; i++, tex++ ) - if( !tex->name[0] ) break; - - if( i == gl_numTextures ) + if( !skyboxhack ) { - if( gl_numTextures == MAX_TEXTURES ) - gEngfuncs.Host_Error( "GL_AllocTexture: MAX_TEXTURES limit exceeds\n" ); - gl_numTextures++; + // keep generating new texture names to avoid collision with predefined skybox objects + do + { + pglGenTextures( 1, &texnum ); + } + while( texnum >= SKYBOX_BASE_NUM && texnum <= SKYBOX_BASE_NUM + SKYBOX_MAX_SIDES ); } + else texnum = tr.skyboxbasenum; - tex = &gl_textures[i]; + // try to match texture slot and texture handle because of buggy games + if( texnum >= MAX_TEXTURES || gl_textures[texnum].texnum != 0 ) + { + // find a free texture_t slot + uint i; + + for( i = 0; i < MAX_TEXTURES; i++ ) + { + if( gl_textures[i].texnum ) + continue; + + tex = &gl_textures[i]; + break; + } + } + else tex = &gl_textures[texnum]; + + if( tex == NULL ) + { + gEngfuncs.Host_Error( "%s: MAX_TEXTURES limit exceeds\n", __func__ ); + return NULL; + } // copy initial params Q_strncpy( tex->name, name, sizeof( tex->name )); - if( FBitSet( flags, TF_SKYSIDE )) - tex->texnum = tr.skyboxbasenum++; - else tex->texnum = i; // texnum is used for fast acess into gl_textures array too + tex->texnum = texnum; tex->flags = flags; + // increase counter + gl_numTextures = Q_max(( tex - gl_textures ) + 1, gl_numTextures ); + if( skyboxhack ) + tr.skyboxbasenum++; + // add to hash table tex->hashValue = COM_HashKey( name, TEXTURES_HASH_SIZE ); tex->nextHash = gl_texturesHashTable[tex->hashValue]; @@ -1439,7 +1416,7 @@ static void GL_DeleteTexture( gl_texture_t *tex ) gl_texture_t **prev; gl_texture_t *cur; - ASSERT( tex != NULL ); + Assert( tex != NULL ); // already freed? if( !tex->texnum ) return; @@ -1447,7 +1424,7 @@ static void GL_DeleteTexture( gl_texture_t *tex ) // debug if( !tex->name[0] ) { - gEngfuncs.Con_Printf( S_ERROR "GL_DeleteTexture: trying to free unnamed texture with texnum %i\n", tex->texnum ); + gEngfuncs.Con_Printf( S_ERROR "%s: trying to free unnamed texture with texnum %i\n", __func__, tex->texnum ); return; } @@ -1467,6 +1444,22 @@ static void GL_DeleteTexture( gl_texture_t *tex ) prev = &cur->nextHash; } + // invalidate texture units state cache + for( int i = 0; i < MAX_TEXTURE_UNITS; i++ ) + { + if( glState.currentTextures[i] == tex->texnum ) + { + if( glState.currentTextureTargets[i] != GL_NONE ) + { + GL_SelectTexture( i ); + pglDisable( glState.currentTextureTargets[i] ); + } + glState.currentTextureTargets[i] = GL_NONE; + glState.currentTextures[i] = -1; + glState.currentTexturesIndex[i] = 0; + } + } + // release source if( tex->original ) gEngfuncs.FS_FreeImage( tex->original ); @@ -1570,6 +1563,8 @@ int GL_LoadTextureArray( const char **names, int flags ) uint picFlags = 0; char name[256]; gl_texture_t *tex; + size_t len = 0; + int ret = 0; uint i, j; if( !names || !names[0] || !glw_state.initialized ) @@ -1583,14 +1578,22 @@ int GL_LoadTextureArray( const char **names, int flags ) if( numLayers <= 0 ) return 0; // create complexname from layer names - for( i = 0; i < numLayers; i++ ) + for( i = 0; i < numLayers - 1; i++ ) { - COM_FileBase( names[i], basename ); - Q_strncat( name, basename, sizeof( name ) ); - if( i != ( numLayers - 1 )) Q_strncat( name, "|", sizeof( name )); + COM_FileBase( names[i], basename, sizeof( basename )); + ret = Q_snprintf( &name[len], sizeof( name ) - len, "%s|", basename ); + + if( ret == -1 ) + return 0; + + len += ret; } - Q_strncat( name, va( "[%i]", numLayers ), sizeof( name )); + COM_FileBase( names[i], basename, sizeof( basename )); + ret = Q_snprintf( &name[len], sizeof( name ) - len, "%s[%i]", basename, numLayers ); + + if( ret == -1 ) + return 0; if( !GL_CheckTexName( name )) return 0; @@ -1612,20 +1615,20 @@ int GL_LoadTextureArray( const char **names, int flags ) // mixed mode: DXT + RGB if( pic->type != src->type ) { - gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch image format for %s and %s\n", names[0], names[i] ); + gEngfuncs.Con_Printf( S_ERROR "%s: mismatch image format for %s and %s\n", __func__, names[0], names[i] ); break; } // different mipcount if( pic->numMips != src->numMips ) { - gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch mip count for %s and %s\n", names[0], names[i] ); + gEngfuncs.Con_Printf( S_ERROR "%s: mismatch mip count for %s and %s\n", __func__, names[0], names[i] ); break; } if( pic->encode != src->encode ) { - gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch custom encoding for %s and %s\n", names[0], names[i] ); + gEngfuncs.Con_Printf( S_ERROR "%s: mismatch custom encoding for %s and %s\n", __func__, names[0], names[i] ); break; } @@ -1635,18 +1638,18 @@ int GL_LoadTextureArray( const char **names, int flags ) if( pic->size != src->size ) { - gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: mismatch image size for %s and %s\n", names[0], names[i] ); + gEngfuncs.Con_Printf( S_ERROR "%s: mismatch image size for %s and %s\n", __func__, names[0], names[i] ); break; } } else { // create new image - pic = Mem_Malloc( gEngfuncs.Image_GetPool(), sizeof( rgbdata_t )); + pic = Mem_Malloc( r_temppool, sizeof( rgbdata_t )); memcpy( pic, src, sizeof( rgbdata_t )); // expand pic buffer for all layers - pic->buffer = Mem_Malloc( gEngfuncs.Image_GetPool(), pic->size * numLayers ); + pic->buffer = Mem_Malloc( r_temppool, pic->size * numLayers ); pic->depth = 0; } @@ -1671,7 +1674,7 @@ int GL_LoadTextureArray( const char **names, int flags ) // there were errors if( !pic || ( pic->depth != numLayers )) { - gEngfuncs.Con_Printf( S_ERROR "GL_LoadTextureArray: not all layers were loaded. Texture array is not created\n" ); + gEngfuncs.Con_Printf( S_ERROR "%s: not all layers were loaded. Texture array is not created\n", __func__ ); if( pic ) gEngfuncs.FS_FreeImage( pic ); return 0; } @@ -1720,7 +1723,7 @@ int GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags if( update ) { if( tex == NULL ) - gEngfuncs.Host_Error( "GL_LoadTextureFromBuffer: couldn't find texture %s for update\n", name ); + gEngfuncs.Host_Error( "%s: couldn't find texture %s for update\n", __func__, name ); SetBits( tex->flags, flags ); } else @@ -1856,10 +1859,11 @@ int GL_FindTexture( const char *name ) GL_FreeTexture ================ */ -void GL_FreeTexture( GLenum texnum ) +void GL_FreeTexture( unsigned int texnum ) { // number 0 it's already freed - if( texnum <= 0 ) return; + if( texnum == 0 || texnum >= MAX_TEXTURES ) + return; GL_DeleteTexture( &gl_textures[texnum] ); } @@ -1890,19 +1894,19 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) } else { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: bad operation for %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: bad operation for %s\n", __func__, image->name ); return; } if( !image->original ) { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: no input data for %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: no input data for %s\n", __func__, image->name ); return; } - if( ImageDXT( image->original->type )) + if( ImageCompressed( image->original->type )) { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: can't process compressed texture %s\n", __func__, image->name ); return; } @@ -1981,19 +1985,20 @@ R_InitDlightTexture */ void R_InitDlightTexture( void ) { - rgbdata_t r_image; + rgbdata_t r_image = + { + .width = BLOCK_SIZE, + .height = BLOCK_SIZE, + .type = PF_RGBA_32, + .flags = IMAGE_HAS_COLOR, + .size = r_image.width * r_image.height * 4 + }; + qboolean update = false; if( tr.dlightTexture != 0 ) - return; // already initialized + update = true; - memset( &r_image, 0, sizeof( r_image )); - r_image.width = BLOCK_SIZE; - r_image.height = BLOCK_SIZE; - r_image.flags = IMAGE_HAS_COLOR; - r_image.type = PF_RGBA_32; - r_image.size = r_image.width * r_image.height * 4; - - tr.dlightTexture = GL_LoadTextureInternal( "*dlight", &r_image, TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE ); + tr.dlightTexture = GL_LoadTextureFromBuffer( "*dlight", &r_image, TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE, update ); } /* @@ -2318,3 +2323,38 @@ void R_ShutdownImages( void ) memset( gl_textures, 0, sizeof( gl_textures )); gl_numTextures = 0; } + +void R_TextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath ) +{ + if( host_allow_materials->value != 2.0f ) + return; + + if( gl_texturenum > 0 ) + gEngfuncs.Con_Printf( "Looking for %s tex replacement..." S_GREEN "OK (%s)\n", modelname, foundpath ); + else if( gl_texturenum < 0 ) + gEngfuncs.Con_Printf( "Looking for %s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, foundpath ); + else + gEngfuncs.Con_Printf( "Looking for %s tex replacement..." S_RED "FAIL (%s)\n", modelname, foundpath ); +} + +qboolean R_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... ) +{ + va_list ap; + int ret; + + va_start( ap, fmt ); + ret = Q_vsnprintf( out, size, fmt, ap ); + va_end( ap ); + + if( ret < 0 ) + { + R_TextureReplacementReport( modelname, -1, "overflow" ); + return false; + } + + if( gEngfuncs.fsapi->FileExists( out, false )) + return true; + + R_TextureReplacementReport( modelname, -1, out ); + return false; +} diff --git a/ref/gl/gl_local.h b/ref/gl/gl_local.h index deac6e94..7b477d81 100644 --- a/ref/gl/gl_local.h +++ b/ref/gl/gl_local.h @@ -31,9 +31,19 @@ GNU General Public License for more details. #include "enginefeatures.h" #include "com_strings.h" #include "pm_movevars.h" -//#include "cvar.h" +#include "cvardef.h" #include "gl_export.h" #include "wadfile.h" +#include "common/mod_local.h" + +#if XASH_PSVITA +int VGL_ShimInit( void ); +void VGL_ShimShutdown( void ); +void VGL_ShimEndFrame( void ); +#endif +#if !defined(XASH_GL_STATIC) +#include "gl2_shim/gl2_shim.h" +#endif #ifndef offsetof #ifdef __GNUC__ @@ -43,19 +53,10 @@ GNU General Public License for more details. #endif #endif -#define ASSERT(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) #define Assert(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) #include -#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) cvar_t cv = { cvname, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } -#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) cvar_t cv = { #cv, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } -#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) - -#define WORLD (gEngfuncs.GetWorld()) -#define WORLDMODEL (gEngfuncs.pfnGetModelByIndex( 1 )) -#define MOVEVARS (gEngfuncs.pfnGetMoveVars()) - // make mod_ref.h? #define LM_SAMPLE_SIZE 16 @@ -74,7 +75,7 @@ extern poolhandle_t r_temppool; #define MAX_DRAW_STACK 2 // normal view and menu view #define SHADEDOT_QUANT 16 // precalculated dot products for quantized angles -#define SHADE_LAMBERT 1.495f +#define SHADE_LAMBERT 1.4953241 #define DEFAULT_ALPHATEST 0.0f // refparams @@ -86,10 +87,10 @@ extern poolhandle_t r_temppool; #define RP_NONVIEWERREF (RP_ENVVIEW) #define R_ModelOpaque( rm ) ( rm == kRenderNormal ) #define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles )) -#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) && e->player ) +#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ( gp_cl->playernum + 1 ) && e->player ) #define RP_NORMALPASS() ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 ) -#define CL_IsViewEntityLocalPlayer() ( ENGINE_GET_PARM( PARM_VIEWENT_INDEX ) == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) +#define CL_IsViewEntityLocalPlayer() ( gp_cl->viewentity == ( gp_cl->playernum + 1 )) #define CULL_VISIBLE 0 // not culled #define CULL_BACKSIDE 1 // backside of transparent wall @@ -99,6 +100,8 @@ extern poolhandle_t r_temppool; #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time +#define SKYBOX_BASE_NUM 5800 // set skybox base (to let some mods load hi-res skyboxes) + typedef struct gltexture_s { char name[256]; // game path, including extension (can be store image programs) @@ -126,7 +129,6 @@ typedef struct gltexture_s float xscale; float yscale; - int servercount; uint hashValue; struct gltexture_s *nextHash; } gl_texture_t; @@ -175,8 +177,8 @@ typedef struct int cached_contents; // in water int cached_waterlevel; // was in water - float skyMins[2][6]; - float skyMaxs[2][6]; + float skyMins[2][SKYBOX_MAX_SIDES]; + float skyMaxs[2][SKYBOX_MAX_SIDES]; matrix4x4 objectMatrix; // currententity matrix matrix4x4 worldviewMatrix; // modelview for world @@ -211,7 +213,7 @@ typedef struct int alphaskyTexture; // quake1 alpha-sky layer int lightmapTextures[MAX_LIGHTMAPS]; int dlightTexture; // custom dlight texture - int skyboxTextures[6]; // skybox sides + int skyboxTextures[SKYBOX_MAX_SIDES]; // skybox sides int cinTexture; // cinematic texture int skytexturenum; // this not a gl_texturenum! @@ -233,7 +235,6 @@ typedef struct int realframecount; // not including viewpasses int framecount; - qboolean ignore_lightgamma; qboolean fCustomRendering; qboolean fResetVis; qboolean fFlipViewModel; @@ -248,7 +249,20 @@ typedef struct // cull info vec3_t modelorg; // relative to viewpoint - qboolean fCustomSkybox; + // get from engine + world_static_t *world; + cl_entity_t *entities; + movevars_t *movevars; + color24 *palette; + cl_entity_t *viewent; + dlight_t *dlights; + dlight_t *elights; + byte *texgammatable; + uint *lightgammatable; + uint *lineargammatable; + uint *screengammatable; + + uint max_entities; } gl_globals_t; typedef struct @@ -278,6 +292,7 @@ extern gl_globals_t tr; extern float gldepthmin, gldepthmax; #define r_numEntities (tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities) #define r_numStatics (r_stats.c_client_ents) +#define Mod_AllowMaterials() (host_allow_materials->value && !FBitSet( gp_host->features, ENGINE_DISABLE_HDTEXTURES )) // // gl_backend.c @@ -285,18 +300,18 @@ extern float gldepthmin, gldepthmax; void GL_BackendStartFrame( void ); void GL_BackendEndFrame( void ); void GL_CleanUpTextureUnits( int last ); -void GL_Bind( GLint tmu, GLenum texnum ); -void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ); +void GL_Bind( int tmu, unsigned int texnum ); +void GL_MultiTexCoord2f( int tmu, GLfloat s, GLfloat t ); void GL_SetTexCoordArrayMode( GLenum mode ); -void GL_LoadTexMatrix( const matrix4x4 m ); void GL_LoadTexMatrixExt( const float *glmatrix ); void GL_LoadMatrix( const matrix4x4 source ); void GL_TexGen( GLenum coord, GLenum mode ); -void GL_SelectTexture( GLint texture ); +void GL_SelectTexture( int tmu ); void GL_CleanupAllTextureUnits( void ); void GL_LoadIdentityTexMatrix( void ); void GL_DisableAllTexGens( void ); void GL_SetRenderMode( int mode ); +void GL_EnableTextureUnit( int tmu, qboolean enable ); void GL_TextureTarget( uint target ); void GL_Cull( GLenum cull ); void R_ShowTextures( void ); @@ -313,7 +328,6 @@ qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ); // int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ); qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); -qboolean R_CullSphere( const vec3_t centre, const float radius ); int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ); // @@ -330,7 +344,6 @@ void R_ClearDecals( void ); // gl_draw.c // void R_Set2DMode( qboolean enable ); -void R_DrawTileClear( int texnum, int x, int y, int w, int h ); void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ); // @@ -343,7 +356,8 @@ void R_DrawModelHull( void ); // gl_image.c // void R_SetTextureParameters( void ); -gl_texture_t *R_GetTexture( GLenum texnum ); +gl_texture_t *R_GetTexture( unsigned int texnum ); +const char *GL_TargetToString( GLenum target ); #define GL_LoadTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, false ) #define GL_UpdateTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, true ) int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); @@ -354,28 +368,27 @@ int GL_CreateTexture( const char *name, int width, int height, const void *buffe int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ); void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ); void GL_UpdateTexSize( int texnum, int width, int height, int depth ); +qboolean GL_TextureFilteringEnabled( const gl_texture_t *tex ); void GL_ApplyTextureParams( gl_texture_t *tex ); int GL_FindTexture( const char *name ); -void GL_FreeTexture( GLenum texnum ); -const char *GL_Target( GLenum target ); +void GL_FreeTexture( unsigned int texnum ); void R_InitDlightTexture( void ); void R_TextureList_f( void ); void R_InitImages( void ); void R_ShutdownImages( void ); int GL_TexMemory( void ); +qboolean R_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... ) FORMAT_CHECK( 4 ); +void R_TextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath ); // // gl_rlight.c // -void CL_RunLightStyles( void ); +void CL_RunLightStyles( lightstyle_t *ls ); void R_PushDlights( void ); -void R_AnimateLight( void ); void R_GetLightSpot( vec3_t lightspot ); -void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); +void R_MarkLights( const dlight_t *light, int bit, const mnode_t *node ); colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec ); -int R_CountSurfaceDlights( msurface_t *surf ); colorVec R_LightPoint( const vec3_t p0 ); -int R_CountDlights( void ); // // gl_rmain.c @@ -389,9 +402,9 @@ void R_TranslateForEntity( cl_entity_t *e ); void R_RotateForEntity( cl_entity_t *e ); void R_SetupGL( qboolean set_gl_state ); void R_AllowFog( qboolean allowed ); +qboolean R_OpaqueEntity( cl_entity_t *ent ); void R_SetupFrustum( void ); void R_FindViewLeaf( void ); -void R_CheckGamma( void ); void R_PushScene( void ); void R_PopScene( void ); void R_DrawFog( void ); @@ -401,16 +414,9 @@ int CL_FxBlend( cl_entity_t *e ); // gl_rmath.c // void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ); -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ); void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_ConcatScale( matrix4x4 out, float x ); -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); -void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); -void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_CreateScale( matrix4x4 out, float x ); -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); void Matrix4x4_CreateModelview( matrix4x4 out ); @@ -427,10 +433,7 @@ void R_MarkLeaves( void ); void R_DrawWorld( void ); void R_DrawWaterSurfaces( void ); void R_DrawBrushModel( cl_entity_t *e ); -void GL_SubdivideSurface( msurface_t *fa ); -void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ); -void DrawGLPoly( glpoly_t *p, float xScale, float yScale ); -texture_t *R_TextureAnimation( msurface_t *s ); +void GL_SubdivideSurface( model_t *mod, msurface_t *fa ); void GL_SetupFogColorForSurfaces( void ); void R_DrawAlphaTextureChains( void ); void GL_RebuildLightmaps( void ); @@ -440,6 +443,10 @@ void GL_ResetFogColor( void ); void R_GenerateVBO( void ); void R_ClearVBO( void ); void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf ); +void R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords ); +qboolean R_HasGeneratedVBO( void ); +void R_EnableVBO( qboolean enable ); +qboolean R_HasEnabledVBO( void ); // // gl_rpart.c @@ -461,16 +468,13 @@ void R_DrawSpriteModel( cl_entity_t *e ); // gl_studio.c // void R_StudioInit( void ); -void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); int R_GetEntityRenderMode( cl_entity_t *ent ); void R_DrawStudioModel( cl_entity_t *e ); player_info_t *pfnPlayerInfo( int index ); void R_GatherPlayerLight( void ); -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void R_StudioResetPlayerModels( void ); void CL_InitStudioAPI( void ); @@ -487,29 +491,15 @@ void R_AliasInit( void ); // // gl_warp.c // -void R_InitSkyClouds( mip_t *mt, struct texture_s *tx, qboolean custom_palette ); void R_AddSkyBoxSurface( msurface_t *fa ); void R_ClearSkyBox( void ); void R_DrawSkyBox( void ); void R_DrawClouds( void ); -void EmitWaterPolys( msurface_t *warp, qboolean reverse ); - -// -// gl_vgui.c -// -void VGUI_DrawInit( void ); -void VGUI_DrawShutdown( void ); -void VGUI_SetupDrawingText( int *pColor ); -void VGUI_SetupDrawingRect( int *pColor ); -void VGUI_SetupDrawingImage( int *pColor ); -void VGUI_BindTexture( int id ); -void VGUI_EnableTexture( qboolean enable ); -void VGUI_CreateTexture( int id, int width, int height ); -void VGUI_UploadTexture( int id, const char *buffer, int width, int height ); -void VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); -void VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ); -void VGUI_GetTextureSizes( int *width, int *height ); -int VGUI_GenerateTexture( void ); +void R_UnloadSkybox( void ); +void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples ); +void R_ResetRipples( void ); +void R_AnimateRipples( void ); +qboolean R_UploadRipples( texture_t *image ); //#include "vid_common.h" @@ -522,11 +512,11 @@ void GL_SetupAttributes( int safegl ); void GL_OnContextCreated( void ); void GL_InitExtensions( void ); void GL_ClearExtensions( void ); -void VID_CheckChanges( void ); int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); void GL_FreeImage( const char *name ); qboolean VID_ScreenShot( const char *filename, int shot_type ); qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ); +void R_GammaChanged( qboolean do_reset_gamma ); void R_BeginFrame( qboolean clearScene ); void R_RenderFrame( const struct ref_viewpass_s *vp ); void R_EndFrame( void ); @@ -536,12 +526,10 @@ void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int cu void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ); void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ); qboolean R_SpeedsMessage( char *out, size_t size ); -void R_SetupSky( const char *skyboxname ); qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); int R_WorldToScreen( const vec3_t point, vec3_t screen ); void R_ScreenToWorld( const vec3_t screen, vec3_t point ); qboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType ); -void Mod_LoadMapSprite( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded ); void Mod_SpriteUnloadTextures( void *data ); void Mod_UnloadAliasModel( struct model_s *mod ); void Mod_AliasUnloadTextures( void *data ); @@ -550,8 +538,6 @@ void R_RunViewmodelEvents( void ); void R_DrawViewModel( void ); int R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame ); void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ); -void R_RemoveEfrags( struct cl_entity_s *ent ); -void R_AddEfrags( struct cl_entity_s *ent ); void R_DecalRemoveAll( int texture ); int R_CreateDecalList( decallist_t *pList ); void R_ClearAllDecals( void ); @@ -566,10 +552,6 @@ void CL_AddCustomBeam( cl_entity_t *pEnvBeam ); #define GL_CheckForErrors() GL_CheckForErrors_( __FILE__, __LINE__ ) void GL_CheckForErrors_( const char *filename, const int fileline ); const char *GL_ErrorString( int err ); -qboolean GL_Support( int r_ext ); -int GL_MaxTextureUnits( void ); -qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char *cvarname, int r_ext ); -void GL_SetExtension( int r_ext, int enable ); // // gl_triapi.c @@ -624,6 +606,11 @@ enum GL_DRAW_RANGEELEMENTS_EXT, GL_TEXTURE_MULTISAMPLE, GL_ARB_TEXTURE_COMPRESSION_BPTC, + GL_SHADER_OBJECTS_EXT, + GL_ARB_VERTEX_ARRAY_OBJECT_EXT, + GL_BUFFER_STORAGE_EXT, + GL_MAP_BUFFER_RANGE_EXT, + GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, GL_EXTCOUNT, // must be last }; @@ -669,6 +656,8 @@ typedef struct int depth_bits; int stencil_bits; int msaasamples; + int version_major; + int version_minor; gl_context_type_t context; gles_wrapper_t wrapper; @@ -681,10 +670,9 @@ typedef struct typedef struct { - - int width, height; int activeTMU; GLint currentTextures[MAX_TEXTURE_UNITS]; + GLint currentTexturesIndex[MAX_TEXTURE_UNITS]; GLuint currentTextureTargets[MAX_TEXTURE_UNITS]; GLboolean texIdentityMatrix[MAX_TEXTURE_UNITS]; GLint genSTEnabled[MAX_TEXTURE_UNITS]; // 0 - disabled, OR 1 - S, OR 2 - T, OR 4 - R @@ -697,7 +685,6 @@ typedef struct qboolean in2DMode; } glstate_t; - typedef struct { qboolean initialized; // OpenGL subsystem started @@ -710,45 +697,120 @@ extern glstate_t glState; extern glwstate_t glw_state; extern ref_api_t gEngfuncs; extern ref_globals_t *gpGlobals; +extern ref_client_t *gp_cl; +extern ref_host_t *gp_host; #define ENGINE_GET_PARM_ (*gEngfuncs.EngineGetParm) #define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 ) +// +// helper funcs +// +static inline cl_entity_t *CL_GetEntityByIndex( int index ) +{ + if( unlikely( index < 0 || index >= tr.max_entities || !tr.entities )) + return NULL; + + return &tr.entities[index]; +} + +static inline model_t *CL_ModelHandle( int index ) +{ + if( unlikely( index < 0 || index >= gp_cl->nummodels )) + return NULL; + + return gp_cl->models[index]; +} + +static inline byte TextureToGamma( byte b ) +{ + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.texgammatable[b] : b; +} + +static inline uint LightToTexGamma( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lightgammatable[b] : b; +} + +static inline uint ScreenGammaTable( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.screengammatable[b] : b; +} + +static inline uint LinearGammaTable( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lineargammatable[b] : b; +} + +static inline qboolean GL_Support( int r_ext ) +{ + if( r_ext >= 0 && r_ext < GL_EXTCOUNT ) + return glConfig.extension[r_ext] ? true : false; + gEngfuncs.Con_Printf( S_ERROR "%s: invalid extension %d\n", __func__, r_ext ); + + return false; +} + +static inline int GL_MaxTextureUnits( void ) +{ + if( GL_Support( GL_SHADER_GLSL100_EXT )) + return Q_min( Q_max( glConfig.max_texture_coords, glConfig.max_teximage_units ), MAX_TEXTURE_UNITS ); + return glConfig.max_texture_units; +} + +#define WORLDMODEL (gp_cl->models[1]) + // // renderer cvars // -extern cvar_t *gl_texture_anisotropy; -extern cvar_t *gl_extensions; -extern cvar_t *gl_check_errors; -extern cvar_t *gl_texture_lodbias; -extern cvar_t *gl_texture_nearest; -extern cvar_t *gl_lightmap_nearest; -extern cvar_t *gl_keeptjunctions; -extern cvar_t *gl_round_down; -extern cvar_t *gl_wireframe; -extern cvar_t *gl_polyoffset; -extern cvar_t *gl_finish; -extern cvar_t *gl_nosort; -extern cvar_t *gl_clear; -extern cvar_t *gl_test; // cvar to testify new effects -extern cvar_t *gl_msaa; -extern cvar_t *gl_stencilbits; - -extern cvar_t *r_lighting_extended; -extern cvar_t *r_lighting_modulate; -extern cvar_t *r_lighting_ambient; -extern cvar_t *r_studio_lambert; -extern cvar_t *r_detailtextures; -extern cvar_t *r_drawentities; -extern cvar_t *r_decals; -extern cvar_t *r_novis; -extern cvar_t *r_nocull; -extern cvar_t *r_lockpvs; -extern cvar_t *r_lockfrustum; -extern cvar_t *r_traceglow; -extern cvar_t *r_vbo; -extern cvar_t *r_vbo_dlightmode; +extern convar_t gl_texture_anisotropy; +extern convar_t gl_extensions; +extern convar_t gl_check_errors; +extern convar_t gl_texture_lodbias; +extern convar_t gl_texture_nearest; +extern convar_t gl_lightmap_nearest; +extern convar_t gl_keeptjunctions; +extern convar_t gl_round_down; +extern convar_t gl_wireframe; +extern convar_t gl_polyoffset; +extern convar_t gl_finish; +extern convar_t gl_nosort; +extern convar_t gl_test; // cvar to testify new effects +extern convar_t gl_msaa; +extern convar_t gl_stencilbits; +extern convar_t gl_overbright; +extern convar_t gl_fog; +extern convar_t r_lighting_extended; +extern convar_t r_lighting_ambient; +extern convar_t r_studio_lambert; +extern convar_t r_detailtextures; +extern convar_t r_novis; +extern convar_t r_nocull; +extern convar_t r_lockpvs; +extern convar_t r_lockfrustum; +extern convar_t r_traceglow; +extern convar_t r_vbo; +extern convar_t r_vbo_dlightmode; +extern convar_t r_vbo_detail; +extern convar_t r_vbo_overbrightmode; +extern convar_t r_studio_sort_textures; +extern convar_t r_studio_drawelements; +extern convar_t r_shadows; +extern convar_t r_ripple; +extern convar_t r_ripple_updatetime; +extern convar_t r_ripple_spawntime; +extern convar_t r_large_lightmaps; +extern convar_t r_dlight_virtual_radius; // // engine shared convars @@ -760,10 +822,14 @@ DECLARE_ENGINE_SHARED_CVAR_LIST() // #include "crtlib.h" -#define Mem_Malloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) -#define Mem_Calloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +void _Mem_Free( void *data, const char *filename, int fileline ); +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) + ALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; + +#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) #define Mem_Realloc( pool, ptr, size ) gEngfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) -#define Mem_Free( mem ) gEngfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ ) #define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) #define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) #define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ ) diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index f0a33d9c..f3708b45 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -5,42 +5,40 @@ #include "gl4es/include/gl4eshint.h" #endif // XASH_GL4ES -cvar_t *gl_extensions; -cvar_t *gl_texture_anisotropy; -cvar_t *gl_texture_lodbias; -cvar_t *gl_texture_nearest; -cvar_t *gl_lightmap_nearest; -cvar_t *gl_keeptjunctions; -cvar_t *gl_check_errors; -cvar_t *gl_polyoffset; -cvar_t *gl_wireframe; -cvar_t *gl_finish; -cvar_t *gl_nosort; -cvar_t *gl_vsync; -cvar_t *gl_clear; -cvar_t *gl_test; -cvar_t *gl_msaa; -cvar_t *gl_stencilbits; -cvar_t *r_lighting_extended; -cvar_t *r_lighting_modulate; -cvar_t *r_lighting_ambient; -cvar_t *r_detailtextures; -cvar_t *r_adjust_fov; -cvar_t *r_decals; -cvar_t *r_novis; -cvar_t *r_nocull; -cvar_t *r_lockpvs; -cvar_t *r_lockfrustum; -cvar_t *r_traceglow; -cvar_t *r_dynamic; -cvar_t *gl_round_down; -cvar_t *r_vbo; -cvar_t *r_vbo_dlightmode; - -cvar_t *tracerred; -cvar_t *tracergreen; -cvar_t *tracerblue; -cvar_t *traceralpha; +CVAR_DEFINE( gl_extensions, "gl_allow_extensions", "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "allow gl_extensions" ); +CVAR_DEFINE( gl_texture_anisotropy, "gl_anisotropy", "8", FCVAR_GLCONFIG, "textures anisotropic filter" ); +CVAR_DEFINE_AUTO( gl_texture_lodbias, "0.0", FCVAR_GLCONFIG, "LOD bias for mipmapped textures (perfomance|quality)" ); +CVAR_DEFINE_AUTO( gl_texture_nearest, "0", FCVAR_GLCONFIG, "disable texture filter" ); +CVAR_DEFINE_AUTO( gl_lightmap_nearest, "0", FCVAR_GLCONFIG, "disable lightmap filter" ); +CVAR_DEFINE_AUTO( gl_keeptjunctions, "1", FCVAR_GLCONFIG, "removing tjuncs causes blinking pixels" ); +CVAR_DEFINE_AUTO( gl_check_errors, "1", FCVAR_GLCONFIG, "ignore video engine errors" ); +CVAR_DEFINE_AUTO( gl_polyoffset, "2.0", FCVAR_GLCONFIG, "polygon offset for decals" ); +CVAR_DEFINE_AUTO( gl_wireframe, "0", FCVAR_GLCONFIG|FCVAR_SPONLY, "show wireframe overlay" ); +CVAR_DEFINE_AUTO( gl_finish, "0", FCVAR_GLCONFIG, "use glFinish instead of glFlush" ); +CVAR_DEFINE_AUTO( gl_nosort, "0", FCVAR_GLCONFIG, "disable sorting of translucent surfaces" ); +CVAR_DEFINE_AUTO( gl_test, "0", 0, "engine developer cvar for quick testing new features" ); +CVAR_DEFINE_AUTO( gl_msaa, "1", FCVAR_GLCONFIG, "enable or disable multisample anti-aliasing" ); +CVAR_DEFINE_AUTO( gl_stencilbits, "8", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "pixelformat stencil bits (0 - auto)" ); +CVAR_DEFINE_AUTO( gl_overbright, "1", FCVAR_GLCONFIG, "overbrights" ); +CVAR_DEFINE_AUTO( gl_fog, "1", FCVAR_GLCONFIG, "allow for rendering fog using built-in OpenGL fog implementation" ); +CVAR_DEFINE_AUTO( r_lighting_extended, "1", FCVAR_GLCONFIG, "allow to get lighting from world and bmodels" ); +CVAR_DEFINE_AUTO( r_lighting_ambient, "0.3", FCVAR_GLCONFIG, "map ambient lighting scale" ); +CVAR_DEFINE_AUTO( r_detailtextures, "1", FCVAR_GLCONFIG, "enable detail textures support" ); +CVAR_DEFINE_AUTO( r_novis, "0", 0, "ignore vis information (perfomance test)" ); +CVAR_DEFINE_AUTO( r_nocull, "0", 0, "ignore frustrum culling (perfomance test)" ); +CVAR_DEFINE_AUTO( r_lockpvs, "0", FCVAR_CHEAT, "lockpvs area at current point (pvs test)" ); +CVAR_DEFINE_AUTO( r_lockfrustum, "0", FCVAR_CHEAT, "lock frustrum area at current point (cull test)" ); +CVAR_DEFINE_AUTO( r_traceglow, "0", FCVAR_GLCONFIG, "cull flares behind models" ); +CVAR_DEFINE_AUTO( gl_round_down, "2", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "round texture sizes to nearest POT value" ); +CVAR_DEFINE( r_vbo, "gl_vbo", "0", FCVAR_GLCONFIG, "draw world using VBO (known to be glitchy)" ); +CVAR_DEFINE( r_vbo_detail, "gl_vbo_detail", "0", FCVAR_GLCONFIG, "detail vbo mode (0: disable, 1: multipass, 2: singlepass, broken decal dlights)" ); +CVAR_DEFINE( r_vbo_dlightmode, "gl_vbo_dlightmode", "1", FCVAR_GLCONFIG, "vbo dlight rendering mode (0-1)" ); +CVAR_DEFINE( r_vbo_overbrightmode, "gl_vbo_overbrightmode", "0", FCVAR_GLCONFIG, "vbo overbright rendering mode (0-1)" ); +CVAR_DEFINE_AUTO( r_ripple, "0", FCVAR_GLCONFIG, "enable software-like water texture ripple simulation" ); +CVAR_DEFINE_AUTO( r_ripple_updatetime, "0.05", FCVAR_GLCONFIG, "how fast ripple simulation is" ); +CVAR_DEFINE_AUTO( r_ripple_spawntime, "0.1", FCVAR_GLCONFIG, "how fast new ripples spawn" ); +CVAR_DEFINE_AUTO( r_large_lightmaps, "0", FCVAR_GLCONFIG|FCVAR_LATCH, "enable larger lightmap atlas textures (might break custom renderer mods)" ); +CVAR_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" ); DEFINE_ENGINE_SHARED_CVAR_LIST() @@ -51,213 +49,382 @@ glconfig_t glConfig; glstate_t glState; glwstate_t glw_state; -#ifdef XASH_GL_STATIC -#define GL_CALL( x ) #x, NULL +#if XASH_GL_STATIC + #define GL_CALL( x ) #x, NULL #else -#define GL_CALL( x ) #x, (void**)&p##x + #define GL_CALL( x ) #x, (void**)&p##x #endif -static dllfunc_t opengl_110funcs[] = + +static const dllfunc_t opengl_110funcs[] = { - { GL_CALL( glClearColor ) }, - { GL_CALL( glClear ) }, - { GL_CALL( glAlphaFunc ) }, - { GL_CALL( glBlendFunc ) }, - { GL_CALL( glCullFace ) }, - { GL_CALL( glDrawBuffer ) }, - { GL_CALL( glReadBuffer ) }, - { GL_CALL( glAccum ) }, - { GL_CALL( glEnable ) }, - { GL_CALL( glDisable ) }, - { GL_CALL( glEnableClientState ) }, - { GL_CALL( glDisableClientState ) }, - { GL_CALL( glGetBooleanv ) }, - { GL_CALL( glGetDoublev ) }, - { GL_CALL( glGetFloatv ) }, - { GL_CALL( glGetIntegerv ) }, - { GL_CALL( glGetError ) }, - { GL_CALL( glGetString ) }, - { GL_CALL( glFinish ) }, - { GL_CALL( glFlush ) }, - { GL_CALL( glClearDepth ) }, - { GL_CALL( glDepthFunc ) }, - { GL_CALL( glDepthMask ) }, - { GL_CALL( glDepthRange ) }, - { GL_CALL( glFrontFace ) }, - { GL_CALL( glDrawElements ) }, - { GL_CALL( glDrawArrays ) }, - { GL_CALL( glColorMask ) }, - { GL_CALL( glIndexPointer ) }, - { GL_CALL( glVertexPointer ) }, - { GL_CALL( glNormalPointer ) }, - { GL_CALL( glColorPointer ) }, - { GL_CALL( glTexCoordPointer ) }, - { GL_CALL( glArrayElement ) }, - { GL_CALL( glColor3f ) }, - { GL_CALL( glColor3fv ) }, - { GL_CALL( glColor4f ) }, - { GL_CALL( glColor4fv ) }, - { GL_CALL( glColor3ub ) }, - { GL_CALL( glColor4ub ) }, - { GL_CALL( glColor4ubv ) }, - { GL_CALL( glTexCoord1f ) }, - { GL_CALL( glTexCoord2f ) }, - { GL_CALL( glTexCoord3f ) }, - { GL_CALL( glTexCoord4f ) }, - { GL_CALL( glTexCoord1fv ) }, - { GL_CALL( glTexCoord2fv ) }, - { GL_CALL( glTexCoord3fv ) }, - { GL_CALL( glTexCoord4fv ) }, - { GL_CALL( glTexGenf ) }, - { GL_CALL( glTexGenfv ) }, - { GL_CALL( glTexGeni ) }, - { GL_CALL( glVertex2f ) }, - { GL_CALL( glVertex3f ) }, - { GL_CALL( glVertex3fv ) }, - { GL_CALL( glNormal3f ) }, - { GL_CALL( glNormal3fv ) }, - { GL_CALL( glBegin ) }, - { GL_CALL( glEnd ) }, - { GL_CALL( glLineWidth ) }, - { GL_CALL( glPointSize ) }, - { GL_CALL( glMatrixMode ) }, - { GL_CALL( glOrtho ) }, - { GL_CALL( glRasterPos2f ) }, - { GL_CALL( glFrustum ) }, - { GL_CALL( glViewport ) }, - { GL_CALL( glPushMatrix ) }, - { GL_CALL( glPopMatrix ) }, - { GL_CALL( glPushAttrib ) }, - { GL_CALL( glPopAttrib ) }, - { GL_CALL( glLoadIdentity ) }, - { GL_CALL( glLoadMatrixd ) }, - { GL_CALL( glLoadMatrixf ) }, - { GL_CALL( glMultMatrixd ) }, - { GL_CALL( glMultMatrixf ) }, - { GL_CALL( glRotated ) }, - { GL_CALL( glRotatef ) }, - { GL_CALL( glScaled ) }, - { GL_CALL( glScalef ) }, - { GL_CALL( glTranslated ) }, - { GL_CALL( glTranslatef ) }, - { GL_CALL( glReadPixels ) }, - { GL_CALL( glDrawPixels ) }, - { GL_CALL( glStencilFunc ) }, - { GL_CALL( glStencilMask ) }, - { GL_CALL( glStencilOp ) }, - { GL_CALL( glClearStencil ) }, - { GL_CALL( glIsEnabled ) }, - { GL_CALL( glIsList ) }, - { GL_CALL( glIsTexture ) }, - { GL_CALL( glTexEnvf ) }, - { GL_CALL( glTexEnvfv ) }, - { GL_CALL( glTexEnvi ) }, - { GL_CALL( glTexParameterf ) }, - { GL_CALL( glTexParameterfv ) }, - { GL_CALL( glTexParameteri ) }, - { GL_CALL( glHint ) }, - { GL_CALL( glPixelStoref ) }, - { GL_CALL( glPixelStorei ) }, - { GL_CALL( glGenTextures ) }, - { GL_CALL( glDeleteTextures ) }, - { GL_CALL( glBindTexture ) }, - { GL_CALL( glTexImage1D ) }, - { GL_CALL( glTexImage2D ) }, - { GL_CALL( glTexSubImage1D ) }, - { GL_CALL( glTexSubImage2D ) }, - { GL_CALL( glCopyTexImage1D ) }, - { GL_CALL( glCopyTexImage2D ) }, - { GL_CALL( glCopyTexSubImage1D ) }, - { GL_CALL( glCopyTexSubImage2D ) }, - { GL_CALL( glScissor ) }, - { GL_CALL( glGetTexImage ) }, - { GL_CALL( glGetTexEnviv ) }, - { GL_CALL( glPolygonOffset ) }, - { GL_CALL( glPolygonMode ) }, - { GL_CALL( glPolygonStipple ) }, - { GL_CALL( glClipPlane ) }, - { GL_CALL( glGetClipPlane ) }, - { GL_CALL( glShadeModel ) }, - { GL_CALL( glGetTexLevelParameteriv ) }, - { GL_CALL( glGetTexLevelParameterfv ) }, - { GL_CALL( glFogfv ) }, - { GL_CALL( glFogf ) }, - { GL_CALL( glFogi ) }, - { NULL , NULL } +{ GL_CALL( glClearColor ) }, +{ GL_CALL( glClear ) }, +{ GL_CALL( glAlphaFunc ) }, +{ GL_CALL( glBlendFunc ) }, +{ GL_CALL( glCullFace ) }, +{ GL_CALL( glDrawBuffer ) }, +{ GL_CALL( glReadBuffer ) }, +{ GL_CALL( glAccum ) }, +{ GL_CALL( glEnable ) }, +{ GL_CALL( glDisable ) }, +{ GL_CALL( glEnableClientState ) }, +{ GL_CALL( glDisableClientState ) }, +{ GL_CALL( glGetBooleanv ) }, +{ GL_CALL( glGetDoublev ) }, +{ GL_CALL( glGetFloatv ) }, +{ GL_CALL( glGetIntegerv ) }, +{ GL_CALL( glGetError ) }, +{ GL_CALL( glGetString ) }, +{ GL_CALL( glFinish ) }, +{ GL_CALL( glFlush ) }, +{ GL_CALL( glClearDepth ) }, +{ GL_CALL( glDepthFunc ) }, +{ GL_CALL( glDepthMask ) }, +{ GL_CALL( glDepthRange ) }, +{ GL_CALL( glFrontFace ) }, +{ GL_CALL( glDrawElements ) }, +{ GL_CALL( glDrawArrays ) }, +{ GL_CALL( glColorMask ) }, +{ GL_CALL( glIndexPointer ) }, +{ GL_CALL( glVertexPointer ) }, +{ GL_CALL( glNormalPointer ) }, +{ GL_CALL( glColorPointer ) }, +{ GL_CALL( glTexCoordPointer ) }, +{ GL_CALL( glArrayElement ) }, +{ GL_CALL( glColor3f ) }, +{ GL_CALL( glColor3fv ) }, +{ GL_CALL( glColor4f ) }, +{ GL_CALL( glColor4fv ) }, +{ GL_CALL( glColor3ub ) }, +{ GL_CALL( glColor4ub ) }, +{ GL_CALL( glColor4ubv ) }, +{ GL_CALL( glTexCoord1f ) }, +{ GL_CALL( glTexCoord2f ) }, +{ GL_CALL( glTexCoord3f ) }, +{ GL_CALL( glTexCoord4f ) }, +{ GL_CALL( glTexCoord1fv ) }, +{ GL_CALL( glTexCoord2fv ) }, +{ GL_CALL( glTexCoord3fv ) }, +{ GL_CALL( glTexCoord4fv ) }, +{ GL_CALL( glTexGenf ) }, +{ GL_CALL( glTexGenfv ) }, +{ GL_CALL( glTexGeni ) }, +{ GL_CALL( glVertex2f ) }, +{ GL_CALL( glVertex3f ) }, +{ GL_CALL( glVertex3fv ) }, +{ GL_CALL( glNormal3f ) }, +{ GL_CALL( glNormal3fv ) }, +{ GL_CALL( glBegin ) }, +{ GL_CALL( glEnd ) }, +{ GL_CALL( glLineWidth ) }, +{ GL_CALL( glPointSize ) }, +{ GL_CALL( glMatrixMode ) }, +{ GL_CALL( glOrtho ) }, +{ GL_CALL( glRasterPos2f ) }, +{ GL_CALL( glFrustum ) }, +{ GL_CALL( glViewport ) }, +{ GL_CALL( glPushMatrix ) }, +{ GL_CALL( glPopMatrix ) }, +{ GL_CALL( glPushAttrib ) }, +{ GL_CALL( glPopAttrib ) }, +{ GL_CALL( glLoadIdentity ) }, +{ GL_CALL( glLoadMatrixd ) }, +{ GL_CALL( glLoadMatrixf ) }, +{ GL_CALL( glMultMatrixd ) }, +{ GL_CALL( glMultMatrixf ) }, +{ GL_CALL( glRotated ) }, +{ GL_CALL( glRotatef ) }, +{ GL_CALL( glScaled ) }, +{ GL_CALL( glScalef ) }, +{ GL_CALL( glTranslated ) }, +{ GL_CALL( glTranslatef ) }, +{ GL_CALL( glReadPixels ) }, +{ GL_CALL( glDrawPixels ) }, +{ GL_CALL( glStencilFunc ) }, +{ GL_CALL( glStencilMask ) }, +{ GL_CALL( glStencilOp ) }, +{ GL_CALL( glClearStencil ) }, +{ GL_CALL( glIsEnabled ) }, +{ GL_CALL( glIsList ) }, +{ GL_CALL( glIsTexture ) }, +{ GL_CALL( glTexEnvf ) }, +{ GL_CALL( glTexEnvfv ) }, +{ GL_CALL( glTexEnvi ) }, +{ GL_CALL( glTexParameterf ) }, +{ GL_CALL( glTexParameterfv ) }, +{ GL_CALL( glTexParameteri ) }, +{ GL_CALL( glHint ) }, +{ GL_CALL( glPixelStoref ) }, +{ GL_CALL( glPixelStorei ) }, +{ GL_CALL( glGenTextures ) }, +{ GL_CALL( glDeleteTextures ) }, +{ GL_CALL( glBindTexture ) }, +{ GL_CALL( glTexImage1D ) }, +{ GL_CALL( glTexImage2D ) }, +{ GL_CALL( glTexSubImage1D ) }, +{ GL_CALL( glTexSubImage2D ) }, +{ GL_CALL( glCopyTexImage1D ) }, +{ GL_CALL( glCopyTexImage2D ) }, +{ GL_CALL( glCopyTexSubImage1D ) }, +{ GL_CALL( glCopyTexSubImage2D ) }, +{ GL_CALL( glScissor ) }, +{ GL_CALL( glGetTexImage ) }, +{ GL_CALL( glGetTexEnviv ) }, +{ GL_CALL( glPolygonOffset ) }, +{ GL_CALL( glPolygonMode ) }, +{ GL_CALL( glPolygonStipple ) }, +{ GL_CALL( glClipPlane ) }, +{ GL_CALL( glGetClipPlane ) }, +{ GL_CALL( glShadeModel ) }, +{ GL_CALL( glGetTexLevelParameteriv ) }, +{ GL_CALL( glGetTexLevelParameterfv ) }, +{ GL_CALL( glFogfv ) }, +{ GL_CALL( glFogf ) }, +{ GL_CALL( glFogi ) }, }; -static dllfunc_t debugoutputfuncs[] = +static const dllfunc_t debugoutputfuncs[] = { - { GL_CALL( glDebugMessageControlARB ) }, - { GL_CALL( glDebugMessageInsertARB ) }, - { GL_CALL( glDebugMessageCallbackARB ) }, - { GL_CALL( glGetDebugMessageLogARB ) }, - { NULL , NULL } +{ GL_CALL( glDebugMessageControlARB ) }, +{ GL_CALL( glDebugMessageInsertARB ) }, +{ GL_CALL( glDebugMessageCallbackARB ) }, +{ GL_CALL( glGetDebugMessageLogARB ) }, }; -static dllfunc_t multitexturefuncs[] = +static const dllfunc_t multitexturefuncs[] = { - { GL_CALL( glMultiTexCoord1f ) }, - { GL_CALL( glMultiTexCoord2f ) }, - { GL_CALL( glMultiTexCoord3f ) }, - { GL_CALL( glMultiTexCoord4f ) }, - { GL_CALL( glActiveTexture ) }, - { GL_CALL( glActiveTextureARB ) }, - { GL_CALL( glClientActiveTexture ) }, - { GL_CALL( glClientActiveTextureARB ) }, - { NULL , NULL } +{ GL_CALL( glMultiTexCoord1f ) }, +{ GL_CALL( glMultiTexCoord2f ) }, +{ GL_CALL( glMultiTexCoord3f ) }, +{ GL_CALL( glMultiTexCoord4f ) }, +{ GL_CALL( glActiveTexture ) }, +{ GL_CALL( glActiveTextureARB ) }, +{ GL_CALL( glClientActiveTexture ) }, +{ GL_CALL( glClientActiveTextureARB ) }, }; -static dllfunc_t texture3dextfuncs[] = +static const dllfunc_t texture3dextfuncs[] = { - { GL_CALL( glTexImage3D ) }, - { GL_CALL( glTexSubImage3D ) }, - { GL_CALL( glCopyTexSubImage3D ) }, - { NULL , NULL } +{ GL_CALL( glTexImage3D ) }, +{ GL_CALL( glTexSubImage3D ) }, +{ GL_CALL( glCopyTexSubImage3D ) }, }; -static dllfunc_t texturecompressionfuncs[] = +static const dllfunc_t texturecompressionfuncs[] = { - { GL_CALL( glCompressedTexImage3DARB ) }, - { GL_CALL( glCompressedTexImage2DARB ) }, - { GL_CALL( glCompressedTexImage1DARB ) }, - { GL_CALL( glCompressedTexSubImage3DARB ) }, - { GL_CALL( glCompressedTexSubImage2DARB ) }, - { GL_CALL( glCompressedTexSubImage1DARB ) }, - { GL_CALL( glGetCompressedTexImage ) }, - { NULL , NULL } +{ GL_CALL( glCompressedTexImage3DARB ) }, +{ GL_CALL( glCompressedTexImage2DARB ) }, +{ GL_CALL( glCompressedTexImage1DARB ) }, +{ GL_CALL( glCompressedTexSubImage3DARB ) }, +{ GL_CALL( glCompressedTexSubImage2DARB ) }, +{ GL_CALL( glCompressedTexSubImage1DARB ) }, +{ GL_CALL( glGetCompressedTexImage ) }, }; -static dllfunc_t vbofuncs[] = +static const dllfunc_t vbofuncs[] = { - { GL_CALL( glBindBufferARB ) }, - { GL_CALL( glDeleteBuffersARB ) }, - { GL_CALL( glGenBuffersARB ) }, - { GL_CALL( glIsBufferARB ) }, - { GL_CALL( glMapBufferARB ) }, - { GL_CALL( glUnmapBufferARB ) }, // , - { GL_CALL( glBufferDataARB ) }, - { GL_CALL( glBufferSubDataARB ) }, - { NULL, NULL } +{ GL_CALL( glBindBufferARB ) }, +{ GL_CALL( glDeleteBuffersARB ) }, +{ GL_CALL( glGenBuffersARB ) }, +{ GL_CALL( glIsBufferARB ) }, +#if !XASH_GLES +{ GL_CALL( glMapBufferARB ) }, +{ GL_CALL( glUnmapBufferARB ) }, +#endif +{ GL_CALL( glBufferDataARB ) }, +{ GL_CALL( glBufferSubDataARB ) }, }; -static dllfunc_t multisampletexfuncs[] = +static const dllfunc_t multisampletexfuncs[] = { - { GL_CALL(glTexImage2DMultisample) }, - { NULL, NULL } +{ GL_CALL(glTexImage2DMultisample) }, }; -static dllfunc_t drawrangeelementsfuncs[] = +static const dllfunc_t drawrangeelementsfuncs[] = { { GL_CALL( glDrawRangeElements ) }, -{ NULL, NULL } }; -static dllfunc_t drawrangeelementsextfuncs[] = +static const dllfunc_t drawrangeelementsextfuncs[] = { { GL_CALL( glDrawRangeElementsEXT ) }, -{ NULL, NULL } }; + +// mangling in gl2shim??? +// still need resolve some ext dynamicly, and mangling beginend wrappers will help only with LTO +// anyway this will not work with gl-wes/nanogl, we do not link to libGLESv2, so skip this now +#if !XASH_GL_STATIC +static const dllfunc_t mapbufferrangefuncs[] = +{ +{ GL_CALL( glMapBufferRange ) }, +{ GL_CALL( glFlushMappedBufferRange ) }, +#if XASH_GLES +{ GL_CALL( glUnmapBufferARB ) }, +#endif +}; + +static const dllfunc_t drawrangeelementsbasevertexfuncs[] = +{ +{ GL_CALL( glDrawRangeElementsBaseVertex ) }, +}; + +static const dllfunc_t bufferstoragefuncs[] = +{ +{ GL_CALL( glBufferStorage ) }, +}; + +static const dllfunc_t shaderobjectsfuncs[] = +{ +{ GL_CALL( glDeleteObjectARB ) }, +{ GL_CALL( glGetHandleARB ) }, +{ GL_CALL( glDetachObjectARB ) }, +{ GL_CALL( glCreateShaderObjectARB ) }, +{ GL_CALL( glShaderSourceARB ) }, +{ GL_CALL( glCompileShaderARB ) }, +{ GL_CALL( glCreateProgramObjectARB ) }, +{ GL_CALL( glAttachObjectARB ) }, +{ GL_CALL( glLinkProgramARB ) }, +{ GL_CALL( glUseProgramObjectARB ) }, +{ GL_CALL( glValidateProgramARB ) }, +{ GL_CALL( glUniform1fARB ) }, +{ GL_CALL( glUniform2fARB ) }, +{ GL_CALL( glUniform3fARB ) }, +{ GL_CALL( glUniform4fARB ) }, +{ GL_CALL( glUniform1iARB ) }, +{ GL_CALL( glUniform2iARB ) }, +{ GL_CALL( glUniform3iARB ) }, +{ GL_CALL( glUniform4iARB ) }, +{ GL_CALL( glUniform1fvARB ) }, +{ GL_CALL( glUniform2fvARB ) }, +{ GL_CALL( glUniform3fvARB ) }, +{ GL_CALL( glUniform4fvARB ) }, +{ GL_CALL( glUniform1ivARB ) }, +{ GL_CALL( glUniform2ivARB ) }, +{ GL_CALL( glUniform3ivARB ) }, +{ GL_CALL( glUniform4ivARB ) }, +{ GL_CALL( glUniformMatrix2fvARB ) }, +{ GL_CALL( glUniformMatrix3fvARB ) }, +{ GL_CALL( glUniformMatrix4fvARB ) }, +{ GL_CALL( glGetObjectParameterfvARB ) }, +{ GL_CALL( glGetObjectParameterivARB ) }, +{ GL_CALL( glGetInfoLogARB ) }, +{ GL_CALL( glGetAttachedObjectsARB ) }, +{ GL_CALL( glGetUniformLocationARB ) }, +{ GL_CALL( glGetActiveUniformARB ) }, +{ GL_CALL( glGetUniformfvARB ) }, +{ GL_CALL( glGetUniformivARB ) }, +{ GL_CALL( glGetShaderSourceARB ) }, +{ GL_CALL( glVertexAttribPointerARB ) }, +{ GL_CALL( glEnableVertexAttribArrayARB ) }, +{ GL_CALL( glDisableVertexAttribArrayARB ) }, +{ GL_CALL( glBindAttribLocationARB ) }, +{ GL_CALL( glGetActiveAttribARB ) }, +{ GL_CALL( glGetAttribLocationARB ) }, +{ GL_CALL( glVertexAttrib2fARB ) }, +{ GL_CALL( glVertexAttrib2fvARB ) }, +//{ GL_CALL( glVertexAttrib3fv ) }, +//{ GL_CALL( glVertexAttrib4f ) }, +//{ GL_CALL( glVertexAttrib4fv ) }, +//{ GL_CALL( glVertexAttrib4ubv ) }, +}; + +/* +================== +Even if *ARB functions may work in GL driver in Core context, +renderdoc completely ignores this calls, so we cannot workaround this +by removing ARB suffix after failed function resolve +I desided not to remove ARB suffix from function declarations because +it historicaly related to ARB_shader_object extension, not GL2+ functions +and all shader code from XashXT/ancient xash3d uses it too +Commented out lines left there intentionally to prevent usage on core/gles +================== +*/ + +static const dllfunc_t shaderobjectsfuncs_gles[] = +{ +{ "glDeleteShader" , (void **)&pglDeleteObjectARB }, +//{ "glGetHandleARB" , (void **)&pglGetHandleARB }, +{ "glDetachShader" , (void **)&pglDetachObjectARB }, +{ "glCreateShader" , (void **)&pglCreateShaderObjectARB }, +{ "glShaderSource" , (void **)&pglShaderSourceARB }, +{ "glCompileShader" , (void **)&pglCompileShaderARB }, +{ "glCreateProgram" , (void **)&pglCreateProgramObjectARB }, +{ "glAttachShader" , (void **)&pglAttachObjectARB }, +{ "glLinkProgram" , (void **)&pglLinkProgramARB }, +{ "glUseProgram" , (void **)&pglUseProgramObjectARB }, +{ "glValidateProgram" , (void **)&pglValidateProgramARB }, +{ "glUniform1f" , (void **)&pglUniform1fARB }, +{ "glUniform2f" , (void **)&pglUniform2fARB }, +{ "glUniform3f" , (void **)&pglUniform3fARB }, +{ "glUniform4f" , (void **)&pglUniform4fARB }, +{ "glUniform1i" , (void **)&pglUniform1iARB }, +{ "glUniform2i" , (void **)&pglUniform2iARB }, +{ "glUniform3i" , (void **)&pglUniform3iARB }, +{ "glUniform4i" , (void **)&pglUniform4iARB }, +{ "glUniform1f" , (void **)&pglUniform1fvARB }, +{ "glUniform2fv" , (void **)&pglUniform2fvARB }, +{ "glUniform3fv" , (void **)&pglUniform3fvARB }, +{ "glUniform4fv" , (void **)&pglUniform4fvARB }, +{ "glUniform1iv" , (void **)&pglUniform1ivARB }, +{ "glUniform2iv" , (void **)&pglUniform2ivARB }, +{ "glUniform3iv" , (void **)&pglUniform3ivARB }, +{ "glUniform4iv" , (void **)&pglUniform4ivARB }, +{ "glUniformMatrix2fv" , (void **)&pglUniformMatrix2fvARB }, +{ "glUniformMatrix3fv" , (void **)&pglUniformMatrix3fvARB }, +{ "glUniformMatrix4fv" , (void **)&pglUniformMatrix4fvARB }, +//{ "glGetShaderfv" , (void **)&pglGetObjectParameterfvARB }, // missing in ES2? +{ "glGetShaderiv" , (void **)&pglGetObjectParameterivARB }, +{ "glGetShaderInfoLog" , (void **)&pglGetInfoLogARB }, +//{ "glGetAttachedObjects" , (void **)&pglGetAttachedObjectsARB }, // missing in ES2? +{ "glGetUniformLocation" , (void **)&pglGetUniformLocationARB }, +{ "glGetActiveUniform" , (void **)&pglGetActiveUniformARB }, +{ "glGetUniformfv" , (void **)&pglGetUniformfvARB }, +{ "glGetUniformiv" , (void **)&pglGetUniformivARB }, +{ "glGetShaderSource" , (void **)&pglGetShaderSourceARB }, +{ "glVertexAttribPointer" , (void **)&pglVertexAttribPointerARB }, +{ "glEnableVertexAttribArray" , (void **)&pglEnableVertexAttribArrayARB }, +{ "glDisableVertexAttribArray" , (void **)&pglDisableVertexAttribArrayARB }, +{ "glBindAttribLocation" , (void **)&pglBindAttribLocationARB }, +{ "glGetActiveAttrib" , (void **)&pglGetActiveAttribARB }, +{ "glGetAttribLocation" , (void **)&pglGetAttribLocationARB }, +{ "glVertexAttrib2f" , (void **)&pglVertexAttrib2fARB }, +{ "glVertexAttrib2fv" , (void **)&pglVertexAttrib2fvARB }, +{ "glVertexAttrib3fv" , (void **)&pglVertexAttrib3fvARB }, + +// Core/GLES only +{ GL_CALL( glGetProgramiv ) }, +{ GL_CALL( glDeleteProgram ) }, +{ GL_CALL( glGetProgramInfoLog ) }, +//{ "glVertexAttrib4f" , (void **)&pglVertexAttrib4fARB }, +//{ "glVertexAttrib4fv" , (void **)&pglVertexAttrib4fvARB }, +//{ "glVertexAttrib4ubv" , (void **)&pglVertexAttrib4ubvARB }, +}; + +static const dllfunc_t vaofuncs[] = +{ +{ GL_CALL( glBindVertexArray ) }, +{ GL_CALL( glDeleteVertexArrays ) }, +{ GL_CALL( glGenVertexArrays ) }, +{ GL_CALL( glIsVertexArray ) }, +}; + +static const dllfunc_t multitexturefuncs_es[] = +{ +{ GL_CALL( glActiveTexture ) }, +{ GL_CALL( glActiveTextureARB ) }, +{ GL_CALL( glClientActiveTexture ) }, +{ GL_CALL( glClientActiveTextureARB ) }, +}; + +static const dllfunc_t multitexturefuncs_es2[] = +{ +{ GL_CALL( glActiveTexture ) }, +{ GL_CALL( glActiveTextureARB ) }, +}; + +#endif // !XASH_GL_STATIC + /* ======================== DebugCallback @@ -292,37 +459,11 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui GL_SetExtension ================= */ -void GL_SetExtension( int r_ext, int enable ) +static void GL_SetExtension( int r_ext, int enable ) { if( r_ext >= 0 && r_ext < GL_EXTCOUNT ) glConfig.extension[r_ext] = enable ? GL_TRUE : GL_FALSE; - else gEngfuncs.Con_Printf( S_ERROR "GL_SetExtension: invalid extension %d\n", r_ext ); -} - -/* -================= -GL_Support -================= -*/ -qboolean GL_Support( int r_ext ) -{ - if( r_ext >= 0 && r_ext < GL_EXTCOUNT ) - return glConfig.extension[r_ext] ? true : false; - gEngfuncs.Con_Printf( S_ERROR "GL_Support: invalid extension %d\n", r_ext ); - - return false; -} - -/* -================= -GL_MaxTextureUnits -================= -*/ -int GL_MaxTextureUnits( void ) -{ - if( GL_Support( GL_SHADER_GLSL100_EXT )) - return Q_min( Q_max( glConfig.max_texture_coords, glConfig.max_teximage_units ), MAX_TEXTURE_UNITS ); - return glConfig.max_texture_units; + else gEngfuncs.Con_Printf( S_ERROR "%s: invalid extension %d\n", __func__, r_ext ); } /* @@ -330,22 +471,25 @@ int GL_MaxTextureUnits( void ) GL_CheckExtension ================= */ -qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char *cvarname, int r_ext ) +static qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, size_t num_funcs, const char *cvarname, int r_ext, float minver ) { - const dllfunc_t *func; - cvar_t *parm = NULL; - const char *extensions_string; + size_t i; + cvar_t *parm = NULL; + const char *extensions_string; + char desc[MAX_VA_STRING]; + float glver = (float)glConfig.version_major + glConfig.version_minor / 10.0f; - gEngfuncs.Con_Reportf( "GL_CheckExtension: %s ", name ); + gEngfuncs.Con_Reportf( "%s: %s ", __func__, name ); GL_SetExtension( r_ext, true ); if( cvarname ) { // system config disable extensions - parm = gEngfuncs.Cvar_Get( cvarname, "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, va( CVAR_GLCONFIG_DESCRIPTION, name )); + Q_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, name ); + parm = gEngfuncs.Cvar_Get( cvarname, "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, desc ); } - if(( parm && !CVAR_TO_BOOL( parm )) || ( !CVAR_TO_BOOL( gl_extensions ) && r_ext != GL_OPENGL_110 )) + if(( parm && !parm->value ) || ( !gl_extensions.value && r_ext != GL_OPENGL_110 )) { gEngfuncs.Con_Reportf( "- disabled\n" ); GL_SetExtension( r_ext, false ); @@ -354,23 +498,69 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char extensions_string = glConfig.extensions_string; - if(( name[2] == '_' || name[3] == '_' ) && !Q_strstr( extensions_string, name )) + if(( name[2] == '_' || name[3] == '_' ) && !Q_strstr( extensions_string, name ) && ( glver < minver || !minver || !glver ) ) { GL_SetExtension( r_ext, false ); // update render info gEngfuncs.Con_Reportf( "- ^1failed\n" ); return false; } -#ifndef XASH_GL_STATIC +#if !XASH_GL_STATIC // clear exports - for( func = funcs; func && func->name; func++ ) - *func->func = NULL; + ClearExports( funcs, num_funcs ); - for( func = funcs; func && func->name; func++ ) + for( i = 0; i < num_funcs; i++ ) { // functions are cleared before all the extensions are evaluated - if((*func->func = (void *)gEngfuncs.GL_GetProcAddress( func->name )) == NULL ) - GL_SetExtension( r_ext, false ); // one or more functions are invalid, extension will be disabled + if(( *(funcs[i].func) = (void *)gEngfuncs.GL_GetProcAddress( funcs[i].name )) == NULL ) + { + string name; + char *end; + size_t j = 0; +#if XASH_GLES + const char *suffixes[] = { "", "EXT", "OES" }; +#else + const char *suffixes[] = { "", "EXT" }; +#endif + + // HACK: fix ARB names + Q_strncpy( name, funcs[i].name, sizeof( name )); + if(( end = Q_strstr( name, "ARB" ))) + { + *end = '\0'; + } + else // I need Q_strstrnul + { + end = name + Q_strlen( name ); + j++; // skip empty suffix + } + + for( ; j < sizeof( suffixes ) / sizeof( suffixes[0] ); j++ ) + { + void *f; + + Q_strncat( name, suffixes[j], sizeof( name )); + + if(( f = gEngfuncs.GL_GetProcAddress( name ))) + { + // GL_GetProcAddress prints errors about missing functions, so tell user that we found it with different name + gEngfuncs.Con_Printf( S_NOTE "found %s\n", name ); + + *(funcs[i].func) = f; + break; + } + else + { + *end = '\0'; // cut suffix, try next + } + } + + // not found... + if( j == sizeof( suffixes ) / sizeof( suffixes[0] )) + { + GL_SetExtension( r_ext, false ); + } + } } #endif @@ -391,6 +581,7 @@ GL_GetProcAddress defined just for nanogl/glwes, so it don't link to SDL2 directly, nor use dlsym ============== */ +void GAME_EXPORT *GL_GetProcAddress( const char *name ); // keep defined for nanogl/wes void GAME_EXPORT *GL_GetProcAddress( const char *name ) { return gEngfuncs.GL_GetProcAddress( name ); @@ -482,7 +673,7 @@ static void GL_SetDefaults( void ) R_RenderInfo_f ================= */ -void R_RenderInfo_f( void ) +static void R_RenderInfo_f( void ) { gEngfuncs.Con_Printf( "\n" ); gEngfuncs.Con_Printf( "GL_VENDOR: %s\n", glConfig.vendor_string ); @@ -507,10 +698,8 @@ void R_RenderInfo_f( void ) gEngfuncs.Con_Printf( "GL4ES_VERSION: %s\n", version ); if( extensions ) gEngfuncs.Con_Reportf( "GL4ES_EXTENSIONS: %s\n", extensions ); - } - gEngfuncs.Con_Printf( "GL_MAX_TEXTURE_SIZE: %i\n", glConfig.max_2d_texture_size ); if( GL_Support( GL_ARB_MULTITEXTURE )) @@ -539,20 +728,23 @@ void R_RenderInfo_f( void ) glConfig.alpha_bits, glConfig.depth_bits, glConfig.stencil_bits ); } -#ifdef XASH_GLES -void GL_InitExtensionsGLES( void ) +#if XASH_GLES +static void GL_InitExtensionsGLES( void ) { int extid; // intialize wrapper type -#ifdef XASH_NANOGL +#if XASH_NANOGL glConfig.context = CONTEXT_TYPE_GLES_1_X; glConfig.wrapper = GLES_WRAPPER_NANOGL; -#elif defined( XASH_WES ) +#elif XASH_WES glConfig.context = CONTEXT_TYPE_GLES_2_X; glConfig.wrapper = GLES_WRAPPER_WES; +#elif XASH_GLES3COMPAT + glConfig.context = CONTEXT_TYPE_GLES_2_X; + glConfig.wrapper = GLES_WRAPPER_NONE; #else -#error "unknown gles wrapper" + #error "unknown gles wrapper" #endif glConfig.hardware_type = GLHW_GENERIC; @@ -562,12 +754,22 @@ void GL_InitExtensionsGLES( void ) switch( extid ) { case GL_ARB_VERTEX_BUFFER_OBJECT_EXT: - GL_SetExtension( extid, true ); + GL_CheckExtension( "vertex_buffer_object", vbofuncs, ARRAYSIZE( vbofuncs ), "gl_vertex_buffer_object", extid, 1.0 ); break; case GL_ARB_MULTITEXTURE: + if( !GL_CheckExtension( "multitexture", multitexturefuncs, ARRAYSIZE( multitexturefuncs ), "gl_arb_multitexture", GL_ARB_MULTITEXTURE, 1.0 ) && glConfig.wrapper == GLES_WRAPPER_NONE ) + { +#if !XASH_GL_STATIC + if( !GL_CheckExtension( "multitexture_es1", multitexturefuncs_es, ARRAYSIZE( multitexturefuncs_es ), "gl_arb_multitexture", GL_ARB_MULTITEXTURE, 1.0 ) + && !GL_CheckExtension( "multitexture_es2", multitexturefuncs_es2, ARRAYSIZE( multitexturefuncs_es2 ), "gl_arb_multitexture", GL_ARB_MULTITEXTURE, 2.0 )) + break; +#endif + } GL_SetExtension( extid, true ); // required to be supported by wrapper pglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units ); + if( glConfig.max_texture_units <= 1 ) + pglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_texture_units ); if( glConfig.max_texture_units <= 1 ) { GL_SetExtension( extid, false ); @@ -577,24 +779,50 @@ void GL_InitExtensionsGLES( void ) glConfig.max_texture_coords = glConfig.max_teximage_units = glConfig.max_texture_units; break; case GL_TEXTURE_CUBEMAP_EXT: - if( GL_CheckExtension( "GL_OES_texture_cube_map", NULL, "gl_texture_cubemap", extid )) + if( GL_CheckExtension( "GL_OES_texture_cube_map", NULL, 0, "gl_texture_cubemap", extid, 0 )) pglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size ); break; case GL_ANISOTROPY_EXT: glConfig.max_texture_anisotropy = 0.0f; - if( GL_CheckExtension( "GL_EXT_texture_filter_anisotropic", NULL, "gl_ext_anisotropic_filter", extid )) + if( GL_CheckExtension( "GL_EXT_texture_filter_anisotropic", NULL, 0, "gl_ext_anisotropic_filter", extid, 0 )) pglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.max_texture_anisotropy ); break; case GL_TEXTURE_LOD_BIAS: - if( GL_CheckExtension( "GL_EXT_texture_lod_bias", NULL, "gl_texture_mipmap_biasing", extid )) + if( GL_CheckExtension( "GL_EXT_texture_lod_bias", NULL, 0, "gl_texture_mipmap_biasing", extid, 0 )) pglGetFloatv( GL_MAX_TEXTURE_LOD_BIAS_EXT, &glConfig.max_texture_lod_bias ); break; case GL_ARB_TEXTURE_NPOT_EXT: - GL_CheckExtension( "GL_OES_texture_npot", NULL, "gl_texture_npot", extid ); + GL_CheckExtension( "GL_OES_texture_npot", NULL, 0, "gl_texture_npot", extid, 0 ); break; +#if !XASH_GL_STATIC + case GL_SHADER_OBJECTS_EXT: + GL_CheckExtension( "ES2 Shaders", shaderobjectsfuncs_gles, ARRAYSIZE( shaderobjectsfuncs_gles ), "gl_shaderobjects", extid, 2.0 ); + break; + case GL_ARB_VERTEX_ARRAY_OBJECT_EXT: + if( !GL_CheckExtension( "GL_OES_vertex_array_object", vaofuncs, ARRAYSIZE( vaofuncs ), "gl_vertex_array_object", extid, 3.0 )) + GL_CheckExtension( "GL_EXT_vertex_array_object", vaofuncs, ARRAYSIZE( vaofuncs ), "gl_vertex_array_object", extid, 3.0 ); + break; + case GL_DRAW_RANGEELEMENTS_EXT: + if( !GL_CheckExtension( "GL_EXT_draw_range_elements", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), "gl_drawrangeelements", extid, 3.0 )) + GL_CheckExtension( "GL_OES_draw_range_elements", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), "gl_drawrangeelements", extid, 3.0 ); + break; + case GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT: + if( !GL_CheckExtension( "GL_OES_draw_elements_base_vertex", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), "gl_drawrangeelementsbasevertex", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 0 )) + GL_CheckExtension( "GL_EXT_draw_elements_base_vertex", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), "gl_drawrangeelementsbasevertex", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 3.2 ); + break; + case GL_MAP_BUFFER_RANGE_EXT: + GL_CheckExtension( "GL_EXT_map_buffer_range", mapbufferrangefuncs, ARRAYSIZE( mapbufferrangefuncs ), "gl_map_buffer_range", GL_MAP_BUFFER_RANGE_EXT , 3.0); + break; + case GL_BUFFER_STORAGE_EXT: + GL_CheckExtension( "GL_EXT_buffer_storage", bufferstoragefuncs, ARRAYSIZE( bufferstoragefuncs ), "gl_buffer_storage", GL_BUFFER_STORAGE_EXT, 0); + break; + +#endif case GL_DEBUG_OUTPUT: if( glw_state.extended ) - GL_CheckExtension( "GL_KHR_debug", NULL, NULL, extid ); + GL_CheckExtension( "GL_KHR_debug", debugoutputfuncs, ARRAYSIZE( debugoutputfuncs ), "gl_debug_output", extid, 0 ); + else + GL_SetExtension( extid, false ); break; // case GL_TEXTURE_COMPRESSION_EXT: NOPE // case GL_SHADER_GLSL100_EXT: NOPE @@ -613,12 +841,15 @@ void GL_InitExtensionsGLES( void ) GL_SetExtension( extid, false ); } } +#if !XASH_GL_STATIC + GL2_ShimInit(); +#endif } #else -void GL_InitExtensionsBigGL( void ) +static void GL_InitExtensionsBigGL( void ) { // intialize wrapper type - glConfig.context = CONTEXT_TYPE_GL; + glConfig.context = gEngfuncs.Sys_CheckParm( "-glcore" )? CONTEXT_TYPE_GL_CORE : CONTEXT_TYPE_GL; glConfig.wrapper = GLES_WRAPPER_NONE; if( Q_stristr( glConfig.renderer_string, "geforce" )) @@ -639,17 +870,11 @@ void GL_InitExtensionsBigGL( void ) // gl4es may be used system-wide if( Q_stristr( glConfig.renderer_string, "gl4es" )) - { - const char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 ); - const char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 ); - const char *version = (const char *)pglGetString( GL_VERSION | 0x10000 ); - const char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 ); glConfig.wrapper = GLES_WRAPPER_GL4ES; - } // multitexture glConfig.max_texture_units = glConfig.max_texture_coords = glConfig.max_teximage_units = 1; - if( GL_CheckExtension( "GL_ARB_multitexture", multitexturefuncs, "gl_arb_multitexture", GL_ARB_MULTITEXTURE )) + if( GL_CheckExtension( "GL_ARB_multitexture", multitexturefuncs, ARRAYSIZE( multitexturefuncs ), "gl_arb_multitexture", GL_ARB_MULTITEXTURE, 1.3f )) { pglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units ); } @@ -658,7 +883,7 @@ void GL_InitExtensionsBigGL( void ) GL_SetExtension( GL_ARB_MULTITEXTURE, false ); // 3d texture support - if( GL_CheckExtension( "GL_EXT_texture3D", texture3dextfuncs, "gl_texture_3d", GL_TEXTURE_3D_EXT )) + if( GL_CheckExtension( "GL_EXT_texture3D", texture3dextfuncs, ARRAYSIZE( texture3dextfuncs ), "gl_texture_3d", GL_TEXTURE_3D_EXT, 2.0f )) { pglGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size ); @@ -670,25 +895,25 @@ void GL_InitExtensionsBigGL( void ) } // 2d texture array support - if( GL_CheckExtension( "GL_EXT_texture_array", texture3dextfuncs, "gl_texture_2d_array", GL_TEXTURE_ARRAY_EXT )) + if( GL_CheckExtension( "GL_EXT_texture_array", texture3dextfuncs, ARRAYSIZE( texture3dextfuncs ), "gl_texture_2d_array", GL_TEXTURE_ARRAY_EXT, 0 )) pglGetIntegerv( GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &glConfig.max_2d_texture_layers ); // cubemaps support - if( GL_CheckExtension( "GL_ARB_texture_cube_map", NULL, "gl_texture_cubemap", GL_TEXTURE_CUBEMAP_EXT )) + if( GL_CheckExtension( "GL_ARB_texture_cube_map", NULL, 0, "gl_texture_cubemap", GL_TEXTURE_CUBEMAP_EXT, 0 )) { pglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size ); // check for seamless cubemaps too - GL_CheckExtension( "GL_ARB_seamless_cube_map", NULL, "gl_texture_cubemap_seamless", GL_ARB_SEAMLESS_CUBEMAP ); + GL_CheckExtension( "GL_ARB_seamless_cube_map", NULL, 0, "gl_texture_cubemap_seamless", GL_ARB_SEAMLESS_CUBEMAP, 0 ); } - GL_CheckExtension( "GL_ARB_texture_non_power_of_two", NULL, "gl_texture_npot", GL_ARB_TEXTURE_NPOT_EXT ); - GL_CheckExtension( "GL_ARB_texture_compression", texturecompressionfuncs, "gl_texture_dxt_compression", GL_TEXTURE_COMPRESSION_EXT ); - if( !GL_CheckExtension( "GL_EXT_texture_edge_clamp", NULL, "gl_clamp_to_edge", GL_CLAMPTOEDGE_EXT )) - GL_CheckExtension( "GL_SGIS_texture_edge_clamp", NULL, "gl_clamp_to_edge", GL_CLAMPTOEDGE_EXT ); + GL_CheckExtension( "GL_ARB_texture_non_power_of_two", NULL, 0, "gl_texture_npot", GL_ARB_TEXTURE_NPOT_EXT, 0 ); + GL_CheckExtension( "GL_ARB_texture_compression", texturecompressionfuncs, ARRAYSIZE( texturecompressionfuncs ), "gl_texture_dxt_compression", GL_TEXTURE_COMPRESSION_EXT, 0 ); + if( !GL_CheckExtension( "GL_EXT_texture_edge_clamp", NULL, 0, "gl_clamp_to_edge", GL_CLAMPTOEDGE_EXT, 2.0 )) // present in ES2 + GL_CheckExtension( "GL_SGIS_texture_edge_clamp", NULL, 0, "gl_clamp_to_edge", GL_CLAMPTOEDGE_EXT, 0 ); glConfig.max_texture_anisotropy = 0.0f; - if( GL_CheckExtension( "GL_EXT_texture_filter_anisotropic", NULL, "gl_texture_anisotropic_filter", GL_ANISOTROPY_EXT )) + if( GL_CheckExtension( "GL_EXT_texture_filter_anisotropic", NULL, 0, "gl_texture_anisotropic_filter", GL_ANISOTROPY_EXT, 0 )) pglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.max_texture_anisotropy ); #if XASH_WIN32 // Win32 only drivers? @@ -696,20 +921,30 @@ void GL_InitExtensionsBigGL( void ) if( glConfig.hardware_type != GLHW_INTEL ) #endif { - if( GL_CheckExtension( "GL_EXT_texture_lod_bias", NULL, "gl_texture_mipmap_biasing", GL_TEXTURE_LOD_BIAS )) + if( GL_CheckExtension( "GL_EXT_texture_lod_bias", NULL, 0, "gl_texture_mipmap_biasing", GL_TEXTURE_LOD_BIAS, 1.4 )) pglGetFloatv( GL_MAX_TEXTURE_LOD_BIAS_EXT, &glConfig.max_texture_lod_bias ); } - GL_CheckExtension( "GL_ARB_texture_border_clamp", NULL, NULL, GL_CLAMP_TEXBORDER_EXT ); + GL_CheckExtension( "GL_ARB_texture_border_clamp", NULL, 0, NULL, GL_CLAMP_TEXBORDER_EXT, 2.0 ); // present in ES2 - GL_CheckExtension( "GL_ARB_depth_texture", NULL, NULL, GL_DEPTH_TEXTURE ); - GL_CheckExtension( "GL_ARB_texture_float", NULL, "gl_texture_float", GL_ARB_TEXTURE_FLOAT_EXT ); - GL_CheckExtension( "GL_ARB_depth_buffer_float", NULL, "gl_texture_depth_float", GL_ARB_DEPTH_FLOAT_EXT ); - GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, NULL, GL_EXT_GPU_SHADER4 ); // don't confuse users - GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, "gl_vertex_buffer_object", GL_ARB_VERTEX_BUFFER_OBJECT_EXT ); - GL_CheckExtension( "GL_ARB_texture_multisample", multisampletexfuncs, "gl_texture_multisample", GL_TEXTURE_MULTISAMPLE ); - GL_CheckExtension( "GL_ARB_texture_compression_bptc", NULL, "gl_texture_bptc_compression", GL_ARB_TEXTURE_COMPRESSION_BPTC ); - if( GL_CheckExtension( "GL_ARB_shading_language_100", NULL, NULL, GL_SHADER_GLSL100_EXT )) + GL_CheckExtension( "GL_ARB_depth_texture", NULL, 0, NULL, GL_DEPTH_TEXTURE, 1.4 ); // missing in gles, check GL_OES_depth_texture + GL_CheckExtension( "GL_ARB_texture_float", NULL, 0, "gl_texture_float", GL_ARB_TEXTURE_FLOAT_EXT, 0 ); + GL_CheckExtension( "GL_ARB_depth_buffer_float", NULL, 0, "gl_texture_depth_float", GL_ARB_DEPTH_FLOAT_EXT, 0 ); + GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, 0, NULL, GL_EXT_GPU_SHADER4, 0 ); // don't confuse users + GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, ARRAYSIZE( vbofuncs ), "gl_vertex_buffer_object", GL_ARB_VERTEX_BUFFER_OBJECT_EXT, 2.0 ); + GL_CheckExtension( "GL_ARB_texture_multisample", multisampletexfuncs, ARRAYSIZE( multisampletexfuncs ), "gl_texture_multisample", GL_TEXTURE_MULTISAMPLE, 0 ); + GL_CheckExtension( "GL_ARB_texture_compression_bptc", NULL, 0, "gl_texture_bptc_compression", GL_ARB_TEXTURE_COMPRESSION_BPTC, 0 ); +#if !XASH_GL_STATIC + if( glConfig.context == CONTEXT_TYPE_GL_CORE ) + GL_CheckExtension( "shader_objects", shaderobjectsfuncs_gles, ARRAYSIZE( shaderobjectsfuncs_gles ), "gl_shaderobjects", GL_SHADER_OBJECTS_EXT, 2.0 ); + else + GL_CheckExtension( "GL_ARB_shader_objects", shaderobjectsfuncs, ARRAYSIZE( shaderobjectsfuncs ), "gl_shaderobjects", GL_SHADER_OBJECTS_EXT, 2.0 ); + GL_CheckExtension( "GL_ARB_vertex_array_object", vaofuncs, ARRAYSIZE( vaofuncs ), "gl_vertex_array_object", GL_ARB_VERTEX_ARRAY_OBJECT_EXT, 3.0 ); + GL_CheckExtension( "GL_ARB_buffer_storage", bufferstoragefuncs, ARRAYSIZE( bufferstoragefuncs ), "gl_buffer_storage", GL_BUFFER_STORAGE_EXT, 4.4); + GL_CheckExtension( "GL_ARB_map_buffer_range", mapbufferrangefuncs, ARRAYSIZE( mapbufferrangefuncs ), "gl_map_buffer_range", GL_MAP_BUFFER_RANGE_EXT , 3.0); + GL_CheckExtension( "GL_ARB_draw_elements_base_vertex", drawrangeelementsbasevertexfuncs, ARRAYSIZE( drawrangeelementsbasevertexfuncs ), "gl_drawrangeelementsbasevertex", GL_DRAW_RANGE_ELEMENTS_BASE_VERTEX_EXT, 3.2 ); +#endif + if( GL_CheckExtension( "GL_ARB_shading_language_100", NULL, 0, NULL, GL_SHADER_GLSL100_EXT, 2.0 )) { pglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords ); pglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_teximage_units ); @@ -730,14 +965,14 @@ void GL_InitExtensionsBigGL( void ) } // rectangle textures support - GL_CheckExtension( "GL_ARB_texture_rectangle", NULL, "gl_texture_rectangle", GL_TEXTURE_2D_RECT_EXT ); + GL_CheckExtension( "GL_ARB_texture_rectangle", NULL, 0, "gl_texture_rectangle", GL_TEXTURE_2D_RECT_EXT, 0 ); - if( !GL_CheckExtension( "glDrawRangeElements", drawrangeelementsfuncs, "gl_drawrangeelements", GL_DRAW_RANGEELEMENTS_EXT ) ) + if( !GL_CheckExtension( "glDrawRangeElements", drawrangeelementsfuncs, ARRAYSIZE( drawrangeelementsfuncs ), "gl_drawrangeelements", GL_DRAW_RANGEELEMENTS_EXT, 0 ) ) { - if( GL_CheckExtension( "glDrawRangeElementsEXT", drawrangeelementsextfuncs, - "gl_drawrangelements", GL_DRAW_RANGEELEMENTS_EXT ) ) + if( GL_CheckExtension( "glDrawRangeElementsEXT", drawrangeelementsextfuncs, ARRAYSIZE( drawrangeelementsextfuncs ), + "gl_drawrangelements", GL_DRAW_RANGEELEMENTS_EXT, 0 )) { -#ifndef XASH_GL_STATIC +#if !XASH_GL_STATIC pglDrawRangeElements = pglDrawRangeElementsEXT; #endif } @@ -745,25 +980,88 @@ void GL_InitExtensionsBigGL( void ) // this won't work without extended context if( glw_state.extended ) - GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, "gl_debug_output", GL_DEBUG_OUTPUT ); + GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, ARRAYSIZE( debugoutputfuncs ), "gl_debug_output", GL_DEBUG_OUTPUT, 0 ); + +#if XASH_PSVITA + // not all GL1.1 functions are implemented in vitaGL, but there's enough + GL_SetExtension( GL_OPENGL_110, true ); + // init our immediate mode override + VGL_ShimInit(); +#endif +#if !XASH_GLES && !XASH_GL_STATIC + if( gEngfuncs.Sys_CheckParm( "-gl2shim" )) + GL2_ShimInit(); +#endif } #endif void GL_InitExtensions( void ) { + char value[MAX_VA_STRING]; + GLint major = 0, minor = 0; + GL_OnContextCreated(); // initialize gl extensions - GL_CheckExtension( "OpenGL 1.1.0", opengl_110funcs, NULL, GL_OPENGL_110 ); + GL_CheckExtension( "OpenGL 1.1.0", opengl_110funcs, ARRAYSIZE( opengl_110funcs ), NULL, GL_OPENGL_110, 1.0 ); // get our various GL strings glConfig.vendor_string = (const char *)pglGetString( GL_VENDOR ); glConfig.renderer_string = (const char *)pglGetString( GL_RENDERER ); glConfig.version_string = (const char *)pglGetString( GL_VERSION ); glConfig.extensions_string = (const char *)pglGetString( GL_EXTENSIONS ); + + pglGetIntegerv( GL_MAJOR_VERSION, &major ); + pglGetIntegerv( GL_MINOR_VERSION, &minor ); + if( !major && glConfig.version_string ) + { + const char *str = glConfig.version_string; + float ver; + + while( *str && ( *str < '0' || *str > '9' )) str++; + ver = Q_atof(str); + if( ver ) + { + glConfig.version_major = ver; + glConfig.version_minor = (int)(ver * 10) % 10; + } + } + else + { + glConfig.version_major = major; + glConfig.version_minor = minor; + } +#if !XASH_GL_STATIC + if( !glConfig.extensions_string ) + { + int n = 0; + pglGetStringi = gEngfuncs.GL_GetProcAddress( "glGetStringi" ); + + pglGetIntegerv( GL_NUM_EXTENSIONS, &n ); + if( n && pglGetStringi ) + { + int i, len = 1; + char *str; + + for( i = 0; i < n; i++ ) + len += Q_strlen((const char *)pglGetStringi( GL_EXTENSIONS, i )) + 1; + + str = (char*)Mem_Calloc( r_temppool, len ); + glConfig.extensions_string = str; + + for( i = 0; i < n; i++ ) + { + int l = Q_strncpy( str, pglGetStringi( GL_EXTENSIONS, i ), len ); + str += l; + *str++ = ' '; + len -= l + 1; + } + } + } +#endif gEngfuncs.Con_Reportf( "^3Video^7: %s\n", glConfig.renderer_string ); -#ifdef XASH_GLES +#if XASH_GLES GL_InitExtensionsGLES(); #else GL_InitExtensionsBigGL(); @@ -771,7 +1069,7 @@ void GL_InitExtensions( void ) pglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.max_2d_texture_size ); if( glConfig.max_2d_texture_size <= 0 ) glConfig.max_2d_texture_size = 256; -#ifndef XASH_GL4ES +#if !XASH_GL4ES // enable gldebug if allowed if( GL_Support( GL_DEBUG_OUTPUT )) { @@ -782,6 +1080,7 @@ void GL_InitExtensions( void ) // force everything to happen in the main thread instead of in a separate driver thread pglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + } // enable all the low priority messages @@ -791,8 +1090,9 @@ void GL_InitExtensions( void ) if( GL_Support( GL_TEXTURE_2D_RECT_EXT )) pglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &glConfig.max_2d_rectangle_size ); - gEngfuncs.Cvar_Get( "gl_max_size", va( "%i", glConfig.max_2d_texture_size ), 0, "opengl texture max dims" ); - gEngfuncs.Cvar_Set( "gl_anisotropy", va( "%f", bound( 0, gl_texture_anisotropy->value, glConfig.max_texture_anisotropy ))); + Q_snprintf( value, sizeof( value ), "%i", glConfig.max_2d_texture_size ); + gEngfuncs.Cvar_Get( "gl_max_size", value, 0, "opengl texture max dims" ); + gEngfuncs.Cvar_SetValue( "gl_anisotropy", bound( 0, gl_texture_anisotropy.value, glConfig.max_texture_anisotropy )); if( GL_Support( GL_TEXTURE_COMPRESSION_EXT )) gEngfuncs.Image_AddCmdFlags( IL_DDS_HARDWARE ); @@ -814,6 +1114,10 @@ void GL_ClearExtensions( void ) // now all extensions are disabled memset( glConfig.extension, 0, sizeof( glConfig.extension )); glw_state.initialized = false; +#if XASH_PSVITA + // deinit our immediate mode override + VGL_ShimShutdown(); +#endif } //======================================================================= @@ -823,37 +1127,50 @@ void GL_ClearExtensions( void ) GL_InitCommands ================= */ -void GL_InitCommands( void ) +static void GL_InitCommands( void ) { RETRIEVE_ENGINE_SHARED_CVAR_LIST(); - r_lighting_extended = gEngfuncs.Cvar_Get( "r_lighting_extended", "1", FCVAR_GLCONFIG, "allow to get lighting from world and bmodels" ); - r_lighting_modulate = gEngfuncs.Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_GLCONFIG, "lightstyles modulate scale" ); - r_lighting_ambient = gEngfuncs.Cvar_Get( "r_lighting_ambient", "0.3", FCVAR_GLCONFIG, "map ambient lighting scale" ); - r_novis = gEngfuncs.Cvar_Get( "r_novis", "0", 0, "ignore vis information (perfomance test)" ); - r_nocull = gEngfuncs.Cvar_Get( "r_nocull", "0", 0, "ignore frustrum culling (perfomance test)" ); - r_detailtextures = gEngfuncs.Cvar_Get( "r_detailtextures", "1", FCVAR_ARCHIVE, "enable detail textures support" ); - r_lockpvs = gEngfuncs.Cvar_Get( "r_lockpvs", "0", FCVAR_CHEAT, "lockpvs area at current point (pvs test)" ); - r_lockfrustum = gEngfuncs.Cvar_Get( "r_lockfrustum", "0", FCVAR_CHEAT, "lock frustrum area at current point (cull test)" ); - r_traceglow = gEngfuncs.Cvar_Get( "r_traceglow", "1", FCVAR_GLCONFIG, "cull flares behind models" ); + gEngfuncs.Cvar_RegisterVariable( &r_lighting_extended ); + gEngfuncs.Cvar_RegisterVariable( &r_lighting_ambient ); + gEngfuncs.Cvar_RegisterVariable( &r_novis ); + gEngfuncs.Cvar_RegisterVariable( &r_nocull ); + gEngfuncs.Cvar_RegisterVariable( &r_detailtextures ); + gEngfuncs.Cvar_RegisterVariable( &r_lockpvs ); + gEngfuncs.Cvar_RegisterVariable( &r_lockfrustum ); + gEngfuncs.Cvar_RegisterVariable( &r_traceglow ); + gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); + gEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime ); + gEngfuncs.Cvar_RegisterVariable( &r_shadows ); + gEngfuncs.Cvar_RegisterVariable( &r_vbo ); + gEngfuncs.Cvar_RegisterVariable( &r_vbo_dlightmode ); + gEngfuncs.Cvar_RegisterVariable( &r_vbo_overbrightmode ); + gEngfuncs.Cvar_RegisterVariable( &r_vbo_detail ); + gEngfuncs.Cvar_RegisterVariable( &r_large_lightmaps ); + gEngfuncs.Cvar_RegisterVariable( &r_dlight_virtual_radius ); - gl_extensions = gEngfuncs.Cvar_Get( "gl_allow_extensions", "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "allow gl_extensions" ); - gl_texture_nearest = gEngfuncs.Cvar_Get( "gl_texture_nearest", "0", FCVAR_GLCONFIG, "disable texture filter" ); - gl_lightmap_nearest = gEngfuncs.Cvar_Get( "gl_lightmap_nearest", "0", FCVAR_GLCONFIG, "disable lightmap filter" ); - gl_check_errors = gEngfuncs.Cvar_Get( "gl_check_errors", "1", FCVAR_GLCONFIG, "ignore video engine errors" ); - gl_texture_anisotropy = gEngfuncs.Cvar_Get( "gl_anisotropy", "8", FCVAR_GLCONFIG, "textures anisotropic filter" ); - gl_texture_lodbias = gEngfuncs.Cvar_Get( "gl_texture_lodbias", "0.0", FCVAR_GLCONFIG, "LOD bias for mipmapped textures (perfomance|quality)" ); - gl_keeptjunctions = gEngfuncs.Cvar_Get( "gl_keeptjunctions", "1", FCVAR_GLCONFIG, "removing tjuncs causes blinking pixels" ); - gl_finish = gEngfuncs.Cvar_Get( "gl_finish", "0", FCVAR_GLCONFIG, "use glFinish instead of glFlush" ); - gl_nosort = gEngfuncs.Cvar_Get( "gl_nosort", "0", FCVAR_GLCONFIG, "disable sorting of translucent surfaces" ); - gl_test = gEngfuncs.Cvar_Get( "gl_test", "0", 0, "engine developer cvar for quick testing new features" ); - gl_wireframe = gEngfuncs.Cvar_Get( "gl_wireframe", "0", FCVAR_GLCONFIG|FCVAR_SPONLY, "show wireframe overlay" ); - gl_msaa = gEngfuncs.Cvar_Get( "gl_msaa", "1", FCVAR_GLCONFIG, "enable or disable multisample anti-aliasing" ); - gl_stencilbits = gEngfuncs.Cvar_Get( "gl_stencilbits", "8", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "pixelformat stencil bits (0 - auto)" ); - gl_round_down = gEngfuncs.Cvar_Get( "gl_round_down", "2", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "round texture sizes to nearest POT value" ); + gEngfuncs.Cvar_RegisterVariable( &gl_extensions ); + gEngfuncs.Cvar_RegisterVariable( &gl_texture_nearest ); + gEngfuncs.Cvar_RegisterVariable( &gl_lightmap_nearest ); + gEngfuncs.Cvar_RegisterVariable( &gl_check_errors ); + gEngfuncs.Cvar_RegisterVariable( &gl_texture_anisotropy ); + gEngfuncs.Cvar_RegisterVariable( &gl_texture_lodbias ); + gEngfuncs.Cvar_RegisterVariable( &gl_keeptjunctions ); + gEngfuncs.Cvar_RegisterVariable( &gl_finish ); + gEngfuncs.Cvar_RegisterVariable( &gl_nosort ); + gEngfuncs.Cvar_RegisterVariable( &gl_test ); + gEngfuncs.Cvar_RegisterVariable( &gl_wireframe ); + gEngfuncs.Cvar_RegisterVariable( &gl_msaa ); + gEngfuncs.Cvar_RegisterVariable( &gl_stencilbits ); + gEngfuncs.Cvar_RegisterVariable( &gl_round_down ); + gEngfuncs.Cvar_RegisterVariable( &gl_overbright ); + gEngfuncs.Cvar_RegisterVariable( &gl_fog ); // these cvar not used by engine but some mods requires this - gl_polyoffset = gEngfuncs.Cvar_Get( "gl_polyoffset", "2.0", FCVAR_GLCONFIG, "polygon offset for decals" ); + gEngfuncs.Cvar_RegisterVariable( &gl_polyoffset ); // make sure gl_vsync is checked after vid_restart SetBits( gl_vsync->flags, FCVAR_CHANGED ); @@ -871,35 +1188,32 @@ register VBO cvars and get default value */ static void R_CheckVBO( void ) { - const char *def = "0"; - const char *dlightmode = "1"; - int flags = FCVAR_ARCHIVE; qboolean disable = false; + int flags = 0; // some bad GLES1 implementations breaks dlights completely if( glConfig.max_texture_units < 3 ) disable = true; -#ifdef XASH_MOBILE_PLATFORM +#if XASH_MOBILE_PLATFORM // VideoCore4 drivers have a problem with mixing VBO and client arrays // Disable it, as there is no suitable workaround here if( Q_stristr( glConfig.renderer_string, "VideoCore IV" ) || Q_stristr( glConfig.renderer_string, "vc4" ) ) disable = true; - - // dlightmode 1 is not too much tested on android - // so better to left it off - dlightmode = "0"; #endif + // we do not want to write vbo code that does not use multitexture + if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 ) + { + flags = FCVAR_READ_ONLY; + disable = true; + } + if( disable ) { - // do not keep in config unless dev > 3 and enabled - flags = 0; - def = "0"; + gEngfuncs.Cvar_FullSet( r_vbo.name, "0", flags ); + gEngfuncs.Cvar_FullSet( r_vbo_dlightmode.name, "0", flags ); } - - r_vbo = gEngfuncs.Cvar_Get( "gl_vbo", def, flags, "draw world using VBO (known to be glitchy)" ); - r_vbo_dlightmode = gEngfuncs.Cvar_Get( "gl_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); } /* @@ -907,9 +1221,10 @@ static void R_CheckVBO( void ) GL_RemoveCommands ================= */ -void GL_RemoveCommands( void ) +static void GL_RemoveCommands( void ) { gEngfuncs.Cmd_RemoveCommand( "r_info" ); + gEngfuncs.Cmd_RemoveCommand( "timerefresh" ); } /* @@ -927,6 +1242,8 @@ qboolean R_Init( void ) GL_SetDefaultState(); + r_temppool = Mem_AllocPool( "Render Zone" ); + // create the window and set up the context if( !gEngfuncs.R_Init_Video( REF_GL )) // request GL context { @@ -934,10 +1251,21 @@ qboolean R_Init( void ) gEngfuncs.R_Free_Video(); // Why? Host_Error again??? // gEngfuncs.Host_Error( "Can't initialize video subsystem\nProbably driver was not installed" ); + Mem_FreePool( &r_temppool ); return false; } - r_temppool = Mem_AllocPool( "Render Zone" ); + // see R_ProcessEntData for tr.entities initialization + tr.world = (struct world_static_s *)ENGINE_GET_PARM( PARM_GET_WORLD_PTR ); + tr.movevars = (movevars_t *)ENGINE_GET_PARM( PARM_GET_MOVEVARS_PTR ); + tr.palette = (color24 *)ENGINE_GET_PARM( PARM_GET_PALETTE_PTR ); + tr.viewent = (cl_entity_t *)ENGINE_GET_PARM( PARM_GET_VIEWENT_PTR ); + tr.texgammatable = (byte *)ENGINE_GET_PARM( PARM_GET_TEXGAMMATABLE_PTR ); + tr.lightgammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LIGHTGAMMATABLE_PTR ); + tr.screengammatable = (uint *)ENGINE_GET_PARM( PARM_GET_SCREENGAMMATABLE_PTR ); + tr.lineargammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LINEARGAMMATABLE_PTR ); + tr.dlights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_DLIGHTS_PTR ); + tr.elights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_ELIGHTS_PTR ); GL_SetDefaults(); R_CheckVBO(); @@ -963,10 +1291,13 @@ void R_Shutdown( void ) GL_RemoveCommands(); R_ShutdownImages(); +#if !XASH_GLES && !XASH_GL_STATIC + GL2_ShimShutdown(); +#endif Mem_FreePool( &r_temppool ); -#ifdef XASH_GL4ES +#if XASH_GL4ES close_gl4es(); #endif // XASH_GL4ES @@ -1011,7 +1342,7 @@ void GL_CheckForErrors_( const char *filename, const int fileline ) { int err; - if( !CVAR_TO_BOOL( gl_check_errors )) + if( !gl_check_errors.value ) return; if(( err = pglGetError( )) == GL_NO_ERROR ) @@ -1025,17 +1356,18 @@ void GL_SetupAttributes( int safegl ) int context_flags = 0; // REFTODO!!!!! int samples = 0; -#ifdef XASH_GLES +#if XASH_GLES gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_ES ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_EGL, 1 ); -#ifdef XASH_NANOGL +#if XASH_NANOGL gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 ); -#elif defined( XASH_WES ) || defined( XASH_REGAL ) +#else // !XASH_NANOGL gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 2 ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 0 ); #endif -#elif defined XASH_GL4ES + +#elif XASH_GL4ES gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_ES ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_EGL, 1 ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 2 ); @@ -1046,14 +1378,22 @@ void GL_SetupAttributes( int safegl ) SetBits( context_flags, FCONTEXT_CORE_PROFILE ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_CORE ); + gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 3 ); + gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 3 ); } else { - gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_COMPATIBILITY ); + if( !safegl ) + gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_PROFILE_MASK, REF_GL_CONTEXT_PROFILE_COMPATIBILITY ); + else + { + gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 ); + gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 ); + } } #endif // XASH_GLES - if( gEngfuncs.Sys_CheckParm( "-gldebug" ) ) + if( gEngfuncs.Sys_CheckParm( "-gldebug" )) { gEngfuncs.Con_Reportf( "Creating an extended GL context for debug...\n" ); SetBits( context_flags, FCONTEXT_DEBUG_ARB ); @@ -1080,7 +1420,7 @@ void GL_SetupAttributes( int safegl ) gEngfuncs.Con_Printf( "bpp %d\n", gpGlobals->desktopBitsPixel ); if( safegl < SAFE_NOSTENCIL ) - gEngfuncs.GL_SetAttribute( REF_GL_STENCIL_SIZE, gl_stencilbits->value ); + gEngfuncs.GL_SetAttribute( REF_GL_STENCIL_SIZE, gl_stencilbits.value ); if( safegl < SAFE_NOALPHA ) gEngfuncs.GL_SetAttribute( REF_GL_ALPHA_SIZE, 8 ); @@ -1149,25 +1489,26 @@ void GL_SetupAttributes( int safegl ) void wes_init( const char *gles2 ); int nanoGL_Init( void ); -#ifdef XASH_GL4ES -void GL4ES_GetMainFBSize( int *width, int *height ) +#if XASH_GL4ES +static void APIENTRY GL4ES_GetMainFBSize( int *width, int *height ) { *width = gpGlobals->width; *height = gpGlobals->height; } -void *GL4ES_GetProcAddress( const char *name ) + +static void * APIENTRY GL4ES_GetProcAddress( const char *name ) { if( !Q_strcmp(name, "glShadeModel") ) // combined gles/gles2/gl implementation exports this, but it is invalid return NULL; return gEngfuncs.GL_GetProcAddress( name ); } -#endif +#endif // XASH_GL4ES void GL_OnContextCreated( void ) { int colorBits[3]; -#ifdef XASH_NANOGL +#if XASH_NANOGL nanoGL_Init(); #endif @@ -1182,11 +1523,14 @@ void GL_OnContextCreated( void ) glState.stencilEnabled = glConfig.stencil_bits ? true : false; gEngfuncs.GL_GetAttribute( REF_GL_MULTISAMPLESAMPLES, &glConfig.msaasamples ); + gEngfuncs.GL_GetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, &glConfig.version_major ); + gEngfuncs.GL_GetAttribute( REF_GL_CONTEXT_MINOR_VERSION, &glConfig.version_minor ); -#ifdef XASH_WES +#if XASH_WES wes_init( "" ); -#endif -#ifdef XASH_GL4ES +#endif // XASH_WES + +#if XASH_GL4ES set_getprocaddress( GL4ES_GetProcAddress ); set_getmainfbsize( GL4ES_GetMainFBSize ); initialize_gl4es(); @@ -1195,5 +1539,5 @@ void GL_OnContextCreated( void ) pglHint( GL_BEGINEND_HINT_GL4ES, 1 ); // dxt unpacked to 16-bit looks ugly pglHint( GL_AVOID16BITS_HINT_GL4ES, 1 ); -#endif +#endif // XASH_GL4ES } diff --git a/ref/gl/gl_rlight.c b/ref/gl/gl_rlight.c index 89679989..d776f783 100644 --- a/ref/gl/gl_rlight.c +++ b/ref/gl/gl_rlight.c @@ -32,64 +32,65 @@ CL_RunLightStyles ================== */ -void CL_RunLightStyles( void ) +void CL_RunLightStyles( lightstyle_t *ls ) { - int i, k, flight, clight; - float l, lerpfrac, backlerp; - float frametime = (gpGlobals->time - gpGlobals->oldtime); - float scale; - lightstyle_t *ls; + int i; + float frametime = (gp_cl->time - gp_cl->oldtime); - if( !WORLDMODEL ) return; + if( !WORLDMODEL ) + return; - scale = r_lighting_modulate->value; + if( !WORLDMODEL->lightdata ) + { + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + tr.lightstylevalue[i] = 256 * 256; + return; + } // light animations // 'm' is normal light, 'a' is no light, 'z' is double bright for( i = 0; i < MAX_LIGHTSTYLES; i++ ) { - ls = gEngfuncs.GetLightStyle( i ); - if( !WORLDMODEL->lightdata ) + int k, flight, clight; + float l, lerpfrac, backlerp; + + if( !gp_cl->paused && frametime <= 0.1f ) + ls[i].time += frametime; // evaluate local time + + if( !ls[i].length ) { - tr.lightstylevalue[i] = 256 * 256; + tr.lightstylevalue[i] = 256; continue; } - - if( !ENGINE_GET_PARM( PARAM_GAMEPAUSED ) && frametime <= 0.1f ) - ls->time += frametime; // evaluate local time - - flight = (int)Q_floor( ls->time * 10 ); - clight = (int)Q_ceil( ls->time * 10 ); - lerpfrac = ( ls->time * 10 ) - flight; - backlerp = 1.0f - lerpfrac; - - if( !ls->length ) - { - tr.lightstylevalue[i] = 256 * scale; - continue; - } - else if( ls->length == 1 ) + else if( ls[i].length == 1 ) { // single length style so don't bother interpolating - tr.lightstylevalue[i] = ls->map[0] * 22 * scale; + tr.lightstylevalue[i] = ( ls[i].map[0] ) * 22; continue; } - else if( !ls->interp || !CVAR_TO_BOOL( cl_lightstyle_lerping )) + + flight = (int)Q_floor( ls[i].time * 10 ); + + if( !ls[i].interp || !cl_lightstyle_lerping->value ) { - tr.lightstylevalue[i] = ls->map[flight%ls->length] * 22 * scale; + tr.lightstylevalue[i] = ls[i].map[flight%ls[i].length] * 22; continue; } + clight = (int)Q_ceil( ls[i].time * 10 ); + lerpfrac = ( ls[i].time * 10 ) - flight; + backlerp = 1.0f - lerpfrac; + // interpolate animating light // frame just gone - k = ls->map[flight % ls->length]; + k = ls[i].map[flight % ls[i].length]; l = (float)( k * 22.0f ) * backlerp; // upcoming frame - k = ls->map[clight % ls->length]; + k = ls[i].map[clight % ls[i].length]; l += (float)( k * 22.0f ) * lerpfrac; - tr.lightstylevalue[i] = (int)l * scale; + tr.lightstylevalue[i] = (int)l; } } @@ -98,46 +99,80 @@ void CL_RunLightStyles( void ) R_MarkLights ============= */ -void R_MarkLights( dlight_t *light, int bit, mnode_t *node ) +void R_MarkLights( const dlight_t *light, int bit, const mnode_t *node ) { - float dist; - msurface_t *surf; - int i; + const float virtual_radius = light->radius * Q_max( 1.0f, r_dlight_virtual_radius.value ); + const float maxdist = light->radius * light->radius; + float dist; + int i; + mnode_t *children[2]; + int firstsurface, numsurfaces; + +start: if( !node || node->contents < 0 ) return; dist = PlaneDiff( light->origin, node->plane ); - if( dist > light->radius ) + node_children( children, node, RI.currentmodel ); + + if( dist > virtual_radius ) { - R_MarkLights( light, bit, node->children[0] ); - return; + node = children[0]; + goto start; } - if( dist < -light->radius ) + + if( dist < -virtual_radius ) { - R_MarkLights( light, bit, node->children[1] ); - return; + node = children[1]; + goto start; } // mark the polygons - surf = RI.currentmodel->surfaces + node->firstsurface; + firstsurface = node_firstsurface( node, RI.currentmodel ); + numsurfaces = node_numsurfaces( node, RI.currentmodel ); - for( i = 0; i < node->numsurfaces; i++, surf++ ) + for( i = 0; i < numsurfaces; i++ ) { - if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius )) - continue; // no intersection + vec3_t impact; + float s, t, l; + msurface_t *surf = &RI.currentmodel->surfaces[firstsurface + i]; + const mextrasurf_t *info = surf->info; + + if( surf->plane->type < 3 ) + { + VectorCopy( light->origin, impact ); + impact[surf->plane->type] -= dist; + } + else VectorMA( light->origin, -dist, surf->plane->normal, impact ); + + // a1ba: the fix was taken from JoeQuake, which traces back to FitzQuake, + // which attributes it to LadyHavoc (Darkplaces author) + // clamp center of light to corner and check brightness + l = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + s = l + 0.5; + s = bound( 0, s, info->lightextents[0] ); + s = l - s; + + l = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + t = l + 0.5; + t = bound( 0, t, info->lightextents[1] ); + t = l - t; + + if( s * s + t * t + dist * dist >= maxdist ) + continue; if( surf->dlightframe != tr.dlightframecount ) { - surf->dlightbits = 0; + surf->dlightbits = bit; surf->dlightframe = tr.dlightframecount; } - surf->dlightbits |= bit; + else surf->dlightbits |= bit; } - R_MarkLights( light, bit, node->children[0] ); - R_MarkLights( light, bit, node->children[1] ); + R_MarkLights( light, bit, children[0] ); + R_MarkLights( light, bit, children[1] ); } /* @@ -147,73 +182,32 @@ R_PushDlights */ void R_PushDlights( void ) { - dlight_t *l; int i; tr.dlightframecount = tr.framecount; - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - if( RI.currententity ) - RI.currentmodel = RI.currententity->model; + RI.currententity = CL_GetEntityByIndex( 0 ); - for( i = 0; i < MAX_DLIGHTS; i++, l++ ) + // no world -- no dlights + if( !RI.currententity ) + return; + + RI.currentmodel = RI.currententity->model; + + for( i = 0; i < MAX_DLIGHTS; i++ ) { - l = gEngfuncs.GetDynamicLight( i ); + dlight_t *l = &tr.dlights[i]; - if( l->die < gpGlobals->time || !l->radius ) + if( l->die < gp_cl->time || !l->radius ) continue; if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) continue; - if( RI.currententity ) - R_MarkLights( l, 1<nodes ); + R_MarkLights( l, 1<nodes ); } } -/* -============= -R_CountDlights -============= -*/ -int R_CountDlights( void ) -{ - dlight_t *l; - int i, numDlights = 0; - - for( i = 0; i < MAX_DLIGHTS; i++ ) - { - l = gEngfuncs.GetDynamicLight( i ); - - if( l->die < gpGlobals->time || !l->radius ) - continue; - - numDlights++; - } - - return numDlights; -} - -/* -============= -R_CountSurfaceDlights -============= -*/ -int R_CountSurfaceDlights( msurface_t *surf ) -{ - int i, numDlights = 0; - - for( i = 0; i < MAX_DLIGHTS; i++ ) - { - if(!( surf->dlightbits & BIT( i ))) - continue; // not lit by this light - - numDlights++; - } - - return numDlights; -} - /* ======================================================================= @@ -242,6 +236,8 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, mtexinfo_t *tex; matrix3x4 tbn; vec3_t mid; + mnode_t *children[2]; + int firstsurface, numsurfaces; // didn't hit anything if( !node || node->contents < 0 ) @@ -250,13 +246,17 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, return false; } + node_children( children, node, model ); + firstsurface = node_firstsurface( node, model ); + numsurfaces = node_numsurfaces( node, model ); + // calculate mid point front = PlaneDiff( start, node->plane ); back = PlaneDiff( end, node->plane ); side = front < 0; if(( back < 0 ) == side ) - return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end ); + return R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end ); frac = front / ( front - back ); @@ -264,7 +264,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, midf = p1f + ( p2f - p1f ) * frac; // co down front side - if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid )) + if( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid )) return true; // hit something if(( back < 0 ) == side ) @@ -274,10 +274,10 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, } // check for impact on this node - surf = model->surfaces + node->firstsurface; + surf = model->surfaces + firstsurface; VectorCopy( mid, g_trace_lightspot ); - for( i = 0; i < node->numsurfaces; i++, surf++ ) + for( i = 0; i < numsurfaces; i++, surf++ ) { int smax, tmax; @@ -343,18 +343,10 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, { uint scale = tr.lightstylevalue[surf->styles[map]]; - if( tr.ignore_lightgamma ) - { - cv->r += lm->r * scale; - cv->g += lm->g * scale; - cv->b += lm->b * scale; - } - else - { - cv->r += gEngfuncs.LightToTexGamma( lm->r ) * scale; - cv->g += gEngfuncs.LightToTexGamma( lm->g ) * scale; - cv->b += gEngfuncs.LightToTexGamma( lm->b ) * scale; - } + cv->r += lm->r * scale; + cv->g += lm->g * scale; + cv->b += lm->b * scale; + lm += size; // skip to next lightmap if( dm != NULL ) @@ -374,7 +366,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, } // go down back side - return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end ); + return R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end ); } /* @@ -384,7 +376,7 @@ R_LightVec check bspmodels to get light from ================= */ -colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) +static colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) { float last_fraction; int i, maxEnts = 1; @@ -399,7 +391,7 @@ colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, last_fraction = 1.0f; // get light from bmodels too - if( CVAR_TO_BOOL( r_lighting_extended )) + if( r_lighting_extended.value ) maxEnts = MAX_PHYSENTS; // check all the bsp-models @@ -441,9 +433,10 @@ colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, { if( lspot ) VectorCopy( g_trace_lightspot, lspot ); if( lvec ) VectorNormalize2( g_trace_lightvec, lvec ); - light.r = Q_min(( cv.r >> 7 ), 255 ); - light.g = Q_min(( cv.g >> 7 ), 255 ); - light.b = Q_min(( cv.b >> 7 ), 255 ); + + light.r = Q_min(( cv.r >> 8 ), 255 ); + light.g = Q_min(( cv.g >> 8 ), 255 ); + light.b = Q_min(( cv.b >> 8 ), 255 ); last_fraction = g_trace_fraction; if(( light.r + light.g + light.b ) != 0 ) @@ -471,7 +464,7 @@ colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t { colorVec light = R_LightVecInternal( start, end, lspot, lvec ); - if( CVAR_TO_BOOL( r_lighting_extended ) && lspot != NULL && lvec != NULL ) + if( r_lighting_extended.value && lspot != NULL && lvec != NULL ) { // trying to get light from ceiling (but ignore gradient analyze) if(( light.r + light.g + light.b ) == 0 ) diff --git a/ref/gl/gl_rmain.c b/ref/gl/gl_rmain.c index e8f93616..ec584130 100644 --- a/ref/gl/gl_rmain.c +++ b/ref/gl/gl_rmain.c @@ -20,6 +20,7 @@ GNU General Public License for more details. #include "particledef.h" #include "entity_types.h" + #define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) float gldepthmin, gldepthmax; @@ -43,7 +44,7 @@ void R_AllowFog( qboolean allowed ) { if( allowed ) { - if( glState.isFogEnabled ) + if( glState.isFogEnabled && gl_fog.value ) pglEnable( GL_FOG ); } else @@ -60,10 +61,19 @@ R_OpaqueEntity Opaque entity can be brush or studio model but sprite =============== */ -static qboolean R_OpaqueEntity( cl_entity_t *ent ) +qboolean R_OpaqueEntity( cl_entity_t *ent ) { if( R_GetEntityRenderMode( ent ) == kRenderNormal ) - return true; + { + switch( ent->curstate.renderfx ) + { + case kRenderFxNone: + case kRenderFxDeadPlayer: + case kRenderFxLightMultiplier: + case kRenderFxExplode: + return true; + } + } return false; } @@ -323,7 +333,7 @@ R_GetFarClip static float R_GetFarClip( void ) { if( WORLDMODEL && RI.drawWorld ) - return MOVEVARS->zmax * 1.73f; + return tr.movevars->zmax * 1.73f; return 2048.0f; } @@ -336,16 +346,16 @@ void R_SetupFrustum( void ) { const ref_overview_t *ov = gEngfuncs.GetOverviewParms(); - if( RP_NORMALPASS() && ( ENGINE_GET_PARM( PARM_WATER_LEVEL ) >= 3 )) + if( RP_NORMALPASS() && ( ENGINE_GET_PARM( PARM_WATER_LEVEL ) >= 3 ) && ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) { - RI.fov_x = atan( tan( DEG2RAD( RI.fov_x ) / 2 ) * ( 0.97f + sin( gpGlobals->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f); - RI.fov_y = atan( tan( DEG2RAD( RI.fov_y ) / 2 ) * ( 1.03f - sin( gpGlobals->time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f); + RI.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); + RI.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); } // build the transformation matrix for the given view angles AngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup ); - if( !r_lockfrustum->value ) + if( !r_lockfrustum.value ) { VectorCopy( RI.vieworg, RI.cullorigin ); VectorCopy( RI.vforward, RI.cull_vforward ); @@ -428,7 +438,7 @@ void R_RotateForEntity( cl_entity_t *e ) { float scale = 1.0f; - if( e == gEngfuncs.GetEntityByIndex( 0 ) ) + if( e == CL_GetEntityByIndex( 0 )) { R_LoadIdentity(); return; @@ -454,7 +464,7 @@ void R_TranslateForEntity( cl_entity_t *e ) { float scale = 1.0f; - if( e == gEngfuncs.GetEntityByIndex( 0 ) ) + if( e == CL_GetEntityByIndex( 0 )) { R_LoadIdentity(); return; @@ -495,7 +505,7 @@ static void R_SetupFrame( void ) // NOTE: this request is the fps-killer on some NVidia drivers glState.isFogEnabled = pglIsEnabled( GL_FOG ); - if( !gl_nosort->value ) + if( !gl_nosort.value ) { // sort translucents entities by rendermode and distance qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare ); @@ -590,6 +600,7 @@ watertexture to grab fog values from it static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down ) { gl_texture_t *tex = NULL; + mnode_t *children[2]; // assure the initial node is not null // we could check it here, but we would rather check it @@ -627,15 +638,17 @@ static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mno // this is a regular node // traverse children - if( node->children[0] && ( node->children[0] != ignore )) + node_children( children, node, WORLDMODEL ); + + if( children[0] && ( children[0] != ignore )) { - tex = R_RecursiveFindWaterTexture( node->children[0], node, true ); + tex = R_RecursiveFindWaterTexture( children[0], node, true ); if( tex ) return tex; } - if( node->children[1] && ( node->children[1] != ignore )) + if( children[1] && ( children[1] != ignore )) { - tex = R_RecursiveFindWaterTexture( node->children[1], node, true ); + tex = R_RecursiveFindWaterTexture( children[1], node, true ); if( tex ) return tex; } @@ -668,7 +681,7 @@ static void R_CheckFog( void ) // quake global fog if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) { - if( !MOVEVARS->fog_settings ) + if( !tr.movevars->fog_settings ) { if( pglIsEnabled( GL_FOG )) pglDisable( GL_FOG ); @@ -677,10 +690,10 @@ static void R_CheckFog( void ) } // quake-style global fog - RI.fogColor[0] = ((MOVEVARS->fog_settings & 0xFF000000) >> 24) / 255.0f; - RI.fogColor[1] = ((MOVEVARS->fog_settings & 0xFF0000) >> 16) / 255.0f; - RI.fogColor[2] = ((MOVEVARS->fog_settings & 0xFF00) >> 8) / 255.0f; - RI.fogDensity = ((MOVEVARS->fog_settings & 0xFF) / 255.0f) * 0.01f; + RI.fogColor[0] = ((tr.movevars->fog_settings & 0xFF000000) >> 24) / 255.0f; + RI.fogColor[1] = ((tr.movevars->fog_settings & 0xFF0000) >> 16) / 255.0f; + RI.fogColor[2] = ((tr.movevars->fog_settings & 0xFF00) >> 8) / 255.0f; + RI.fogDensity = ((tr.movevars->fog_settings & 0xFF) / 255.0f) * 0.01f; RI.fogStart = RI.fogEnd = 0.0f; RI.fogColor[3] = 1.0f; RI.fogCustom = false; @@ -790,7 +803,8 @@ R_DrawFog */ void R_DrawFog( void ) { - if( !RI.fogEnabled ) return; + if( !RI.fogEnabled || !gl_fog.value ) + return; pglEnable( GL_FOG ); if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) @@ -806,7 +820,7 @@ void R_DrawFog( void ) R_DrawEntitiesOnList ============= */ -void R_DrawEntitiesOnList( void ) +static void R_DrawEntitiesOnList( void ) { int i; @@ -949,11 +963,11 @@ R_SetupRefParams must be called right before void R_RenderScene( void ) { if( !WORLDMODEL && RI.drawWorld ) - gEngfuncs.Host_Error( "R_RenderView: NULL worldmodel\n" ); + gEngfuncs.Host_Error( "%s: NULL worldmodel\n", __func__ ); // frametime is valid only for normal pass if( RP_NORMALPASS( )) - tr.frametime = gpGlobals->time - gpGlobals->oldtime; + tr.frametime = gp_cl->time - gp_cl->oldtime; else tr.frametime = 0.0; // begin a new frame @@ -968,6 +982,8 @@ void R_RenderScene( void ) R_MarkLeaves(); R_DrawFog (); + if( RI.drawWorld ) + R_AnimateRipples(); R_CheckGLFog(); R_DrawWorld(); @@ -982,31 +998,54 @@ void R_RenderScene( void ) R_EndGL(); } -/* -=============== -R_CheckGamma -=============== -*/ -void R_CheckGamma( void ) +void R_GammaChanged( qboolean do_reset_gamma ) { - if( gEngfuncs.R_DoResetGamma( )) + if( do_reset_gamma ) { - // paranoia cubemaps uses this - gEngfuncs.BuildGammaTable( 1.8f, 0.0f ); - // paranoia cubemap rendering if( gEngfuncs.drawFuncs->GL_BuildLightmaps ) gEngfuncs.drawFuncs->GL_BuildLightmaps( ); } - else if( FBitSet( vid_gamma->flags, FCVAR_CHANGED ) || FBitSet( vid_brightness->flags, FCVAR_CHANGED )) + else { - gEngfuncs.BuildGammaTable( vid_gamma->value, vid_brightness->value ); glConfig.softwareGammaUpdate = true; GL_RebuildLightmaps(); glConfig.softwareGammaUpdate = false; } } +static void R_CheckCvars( void ) +{ + qboolean rebuild = false; + + if( FBitSet( gl_overbright.flags, FCVAR_CHANGED )) + { + ClearBits( gl_overbright.flags, FCVAR_CHANGED ); + rebuild = true; + } + + if( FBitSet( r_vbo.flags, FCVAR_CHANGED )) + { + ClearBits( r_vbo.flags, FCVAR_CHANGED ); + + R_EnableVBO( r_vbo.value ? true : false ); + if( R_HasEnabledVBO( )) + R_GenerateVBO(); + + if( gl_overbright.value ) + rebuild = true; + } + + if( FBitSet( r_vbo_overbrightmode.flags, FCVAR_CHANGED ) && gl_overbright.value ) + { + ClearBits( r_vbo_overbrightmode.flags, FCVAR_CHANGED ); + rebuild = true; + } + + if( rebuild ) + R_GammaChanged( false ); +} + /* =============== R_BeginFrame @@ -1022,7 +1061,7 @@ void R_BeginFrame( qboolean clearScene ) pglClear( GL_COLOR_BUFFER_BIT ); } - R_CheckGamma(); + R_CheckCvars(); R_Set2DMode( true ); @@ -1030,7 +1069,7 @@ void R_BeginFrame( qboolean clearScene ) pglDrawBuffer( GL_BACK ); // update texture parameters - if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED )) + if( FBitSet( gl_texture_nearest.flags|gl_lightmap_nearest.flags|gl_texture_anisotropy.flags|gl_texture_lodbias.flags, FCVAR_CHANGED )) R_SetTextureParameters(); gEngfuncs.CL_ExtraUpdate (); @@ -1082,17 +1121,9 @@ void R_RenderFrame( const ref_viewpass_t *rvp ) // setup the initial render params R_SetupRefParams( rvp ); - if( gl_finish->value && RI.drawWorld ) + if( gl_finish.value && RI.drawWorld ) pglFinish(); - if( glConfig.max_multisamples > 1 && FBitSet( gl_msaa->flags, FCVAR_CHANGED )) - { - if( CVAR_TO_BOOL( gl_msaa )) - pglEnable( GL_MULTISAMPLE_ARB ); - else pglDisable( GL_MULTISAMPLE_ARB ); - ClearBits( gl_msaa->flags, FCVAR_CHANGED ); - } - // completely override rendering if( gEngfuncs.drawFuncs->GL_RenderFrame != NULL ) { @@ -1124,6 +1155,12 @@ R_EndFrame */ void R_EndFrame( void ) { +#if XASH_PSVITA + VGL_ShimEndFrame(); +#endif +#if !defined( XASH_GL_STATIC ) + GL2_ShimEndFrame(); +#endif // flush any remaining 2D bits R_Set2DMode( false ); gEngfuncs.GL_SwapBuffers(); @@ -1172,16 +1209,16 @@ int CL_FxBlend( cl_entity_t *e ) switch( e->curstate.renderfx ) { case kRenderFxPulseSlowWide: - blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 2 + offset ); + blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset ); break; case kRenderFxPulseFastWide: - blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 8 + offset ); + blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset ); break; case kRenderFxPulseSlow: - blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 2 + offset ); + blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset ); break; case kRenderFxPulseFast: - blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 8 + offset ); + blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset ); break; case kRenderFxFadeSlow: if( RP_NORMALPASS( )) @@ -1220,27 +1257,27 @@ int CL_FxBlend( cl_entity_t *e ) blend = e->curstate.renderamt; break; case kRenderFxStrobeSlow: - blend = 20 * sin( gpGlobals->time * 4 + offset ); + blend = 20 * sin( gp_cl->time * 4 + offset ); if( blend < 0 ) blend = 0; else blend = e->curstate.renderamt; break; case kRenderFxStrobeFast: - blend = 20 * sin( gpGlobals->time * 16 + offset ); + blend = 20 * sin( gp_cl->time * 16 + offset ); if( blend < 0 ) blend = 0; else blend = e->curstate.renderamt; break; case kRenderFxStrobeFaster: - blend = 20 * sin( gpGlobals->time * 36 + offset ); + blend = 20 * sin( gp_cl->time * 36 + offset ); if( blend < 0 ) blend = 0; else blend = e->curstate.renderamt; break; case kRenderFxFlickerSlow: - blend = 20 * (sin( gpGlobals->time * 2 ) + sin( gpGlobals->time * 17 + offset )); + blend = 20 * (sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset )); if( blend < 0 ) blend = 0; else blend = e->curstate.renderamt; break; case kRenderFxFlickerFast: - blend = 20 * (sin( gpGlobals->time * 16 ) + sin( gpGlobals->time * 23 + offset )); + blend = 20 * (sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset )); if( blend < 0 ) blend = 0; else blend = e->curstate.renderamt; break; diff --git a/ref/gl/gl_rmath.c b/ref/gl/gl_rmath.c index 69fb4eb2..1a18fc36 100644 --- a/ref/gl/gl_rmath.c +++ b/ref/gl/gl_rmath.c @@ -119,27 +119,7 @@ void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ) out[15] = in[3][3]; } -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ) -{ - out[0][0] = in[0]; - out[1][0] = in[1]; - out[2][0] = in[2]; - out[3][0] = in[3]; - out[0][1] = in[4]; - out[1][1] = in[5]; - out[2][1] = in[6]; - out[3][1] = in[7]; - out[0][2] = in[8]; - out[1][2] = in[9]; - out[2][2] = in[10]; - out[3][2] = in[11]; - out[0][3] = in[12]; - out[1][3] = in[13]; - out[2][3] = in[14]; - out[3][3] = in[15]; -} - -void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) +static void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) { out[0][0] = 1.0f; out[0][1] = 0.0f; @@ -159,7 +139,7 @@ void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) out[3][3] = 1.0f; } -void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ) +static void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ) { float len, c, s; @@ -190,46 +170,6 @@ void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float out[3][3]=1.0f; } -void Matrix4x4_CreateScale( matrix4x4 out, float x ) -{ - out[0][0] = x; - out[0][1] = 0.0f; - out[0][2] = 0.0f; - out[0][3] = 0.0f; - out[1][0] = 0.0f; - out[1][1] = x; - out[1][2] = 0.0f; - out[1][3] = 0.0f; - out[2][0] = 0.0f; - out[2][1] = 0.0f; - out[2][2] = x; - out[2][3] = 0.0f; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) -{ - out[0][0] = x; - out[0][1] = 0.0f; - out[0][2] = 0.0f; - out[0][3] = 0.0f; - out[1][0] = 0.0f; - out[1][1] = y; - out[1][2] = 0.0f; - out[1][3] = 0.0f; - out[2][0] = 0.0f; - out[2][1] = 0.0f; - out[2][2] = z; - out[2][3] = 0.0f; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ) { matrix4x4 base, temp; @@ -247,21 +187,3 @@ void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float Matrix4x4_CreateRotate( temp, angle, x, y, z ); Matrix4x4_Concat( out, base, temp ); } - -void Matrix4x4_ConcatScale( matrix4x4 out, float x ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale( temp, x ); - Matrix4x4_Concat( out, base, temp ); -} - -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale3( temp, x, y, z ); - Matrix4x4_Concat( out, base, temp ); -} diff --git a/ref/gl/gl_rmisc.c b/ref/gl/gl_rmisc.c index 230e66b9..c5985c0d 100644 --- a/ref/gl/gl_rmisc.c +++ b/ref/gl/gl_rmisc.c @@ -46,14 +46,13 @@ static void R_ParseDetailTextures( const char *filename ) // NOTE: COM_ParseFile handled some symbols seperately // this code will be fix it pfile = COM_ParseFile( pfile, token, sizeof( token )); - Q_strncat( texname, "{", sizeof( texname )); - Q_strncat( texname, token, sizeof( texname )); + Q_snprintf( texname, sizeof( texname ), "{%s", token ); } else Q_strncpy( texname, token, sizeof( texname )); // read detailtexture name pfile = COM_ParseFile( pfile, token, sizeof( token )); - Q_strncat( detail_texname, token, sizeof( detail_texname )); + Q_strncpy( detail_texname, token, sizeof( detail_texname )); // trying the scales or '{' pfile = COM_ParseFile( pfile, token, sizeof( token )); @@ -86,7 +85,7 @@ static void R_ParseDetailTextures( const char *filename ) if( Q_stricmp( tex->name, texname )) continue; - tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR ); + tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR|TF_NOFLIP_TGA ); // texture is loaded if( tex->dt_texturenum ) @@ -114,45 +113,17 @@ void R_NewMap( void ) R_StudioResetPlayerModels(); // upload detailtextures - if( CVAR_TO_BOOL( r_detailtextures )) + if( r_detailtextures.value ) { string mapname, filepath; Q_strncpy( mapname, WORLDMODEL->name, sizeof( mapname )); COM_StripExtension( mapname ); - Q_sprintf( filepath, "%s_detail.txt", mapname ); + Q_snprintf( filepath, sizeof( filepath ), "%s_detail.txt", mapname ); R_ParseDetailTextures( filepath ); } - if( gEngfuncs.pfnGetCvarFloat( "v_dark" )) - { - screenfade_t *sf = gEngfuncs.GetScreenFade(); - float fadetime = 5.0f; - client_textmessage_t *title; - - title = gEngfuncs.pfnTextMessageGet( "GAMETITLE" ); - if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) - fadetime = 1.0f; - - if( title ) - { - // get settings from titles.txt - sf->fadeEnd = title->holdtime + title->fadeout; - sf->fadeReset = title->fadeout; - } - else sf->fadeEnd = sf->fadeReset = fadetime; - - sf->fadeFlags = FFADE_IN; - sf->fader = sf->fadeg = sf->fadeb = 0; - sf->fadealpha = 255; - sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; - sf->fadeReset += gpGlobals->time; - sf->fadeEnd += sf->fadeReset; - - gEngfuncs.Cvar_SetValue( "v_dark", 0.0f ); - } - // clear out efrags in case the level hasn't been reloaded for( i = 0; i < WORLDMODEL->numleafs; i++ ) WORLDMODEL->leafs[i+1].efrags = NULL; @@ -175,10 +146,12 @@ void R_NewMap( void ) tx->texturechain = NULL; } - R_SetupSky( MOVEVARS->skyName ); - GL_BuildLightmaps (); - R_GenerateVBO(); + + R_ClearVBO(); + if( R_HasEnabledVBO( )) + R_GenerateVBO(); + R_ResetRipples(); if( gEngfuncs.drawFuncs->R_NewMap != NULL ) gEngfuncs.drawFuncs->R_NewMap(); diff --git a/ref/gl/gl_rpart.c b/ref/gl/gl_rpart.c index 00954af1..9264170d 100644 --- a/ref/gl/gl_rpart.c +++ b/ref/gl/gl_rpart.c @@ -49,7 +49,7 @@ void CL_DrawParticles( double frametime, particle_t *cl_active_particles, float { particle_t *p; vec3_t right, up; - color24 *pColor; + color24 color; int alpha; float size; @@ -85,15 +85,13 @@ void CL_DrawParticles( double frametime, particle_t *cl_active_particles, float VectorScale( RI.cull_vup, size, up ); p->color = bound( 0, p->color, 255 ); - pColor = gEngfuncs.CL_GetPaletteColor( p->color ); + color = tr.palette[p->color]; - alpha = 255 * (p->die - gpGlobals->time) * 16.0f; + alpha = 255 * (p->die - gp_cl->time) * 16.0f; if( alpha > 255 || p->type == pt_static ) alpha = 255; - pglColor4ub( gEngfuncs.LightToTexGamma( pColor->r ), - gEngfuncs.LightToTexGamma( pColor->g ), - gEngfuncs.LightToTexGamma( pColor->b ), alpha ); + pglColor4ub( color.r, color.g, color.b, alpha ); pglTexCoord2f( 0.0f, 1.0f ); pglVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] ); @@ -188,13 +186,15 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) pglDisable( GL_ALPHA_TEST ); pglDepthMask( GL_FALSE ); - gravity = frametime * MOVEVARS->gravity; + gravity = frametime * tr.movevars->gravity; scale = 1.0 - (frametime * 0.9); if( scale < 0.0f ) scale = 0.0f; + pglBegin( GL_QUADS ); + for( p = cl_active_tracers; p; p = p->next ) { - atten = (p->die - gpGlobals->time); + atten = (p->die - gp_cl->time); if( atten > 0.1f ) atten = 0.1f; VectorScale( p->vel, ( p->ramp * atten ), delta ); @@ -205,7 +205,7 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) { vec3_t verts[4], tmp2; vec3_t tmp, normal; - color24 *pColor; + color24 color; // Transform point into screen space TriWorldToScreen( start, screen ); @@ -229,16 +229,14 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) VectorAdd( verts[0], delta, verts[2] ); VectorAdd( verts[1], delta, verts[3] ); - if( p->color > sizeof( gTracerColors ) / sizeof( color24 ) ) + if( p->color < 0 || p->color >= sizeof( gTracerColors ) / sizeof( gTracerColors[0] )) { - gEngfuncs.Con_Printf( S_ERROR "UserTracer with color > %d\n", sizeof( gTracerColors ) / sizeof( color24 )); - p->color = 0; + p->color = TRACER_COLORINDEX_DEFAULT; } - pColor = &gTracerColors[p->color]; - pglColor4ub( pColor->r, pColor->g, pColor->b, p->packedColor ); + color = gTracerColors[p->color]; + pglColor4ub( color.r, color.g, color.b, p->packedColor ); - pglBegin( GL_QUADS ); pglTexCoord2f( 0.0f, 0.8f ); pglVertex3fv( verts[2] ); pglTexCoord2f( 1.0f, 0.8f ); @@ -247,7 +245,6 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) pglVertex3fv( verts[1] ); pglTexCoord2f( 0.0f, 0.0f ); pglVertex3fv( verts[0] ); - pglEnd(); } // evaluate position @@ -259,7 +256,7 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) p->vel[1] *= scale; p->vel[2] -= gravity; - p->packedColor = 255 * (p->die - gpGlobals->time) * 2; + p->packedColor = 255 * (p->die - gp_cl->time) * 2; if( p->packedColor > 255 ) p->packedColor = 255; } else if( p->type == pt_slowgrav ) @@ -267,6 +264,7 @@ void CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) p->vel[2] = gravity * 0.05f; } } + pglEnd(); pglDepthMask( GL_TRUE ); } @@ -282,7 +280,6 @@ void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, f { ref_instance_t oldRI = RI; - memcpy( &oldRI, &RI, sizeof( ref_instance_t )); R_SetupRefParams( rvp ); R_SetupFrustum(); R_SetupGL( false ); // don't touch GL-states @@ -291,5 +288,5 @@ void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, f gEngfuncs.CL_DrawEFX( frametime, trans_pass ); // restore internal state - memcpy( &RI, &oldRI, sizeof( ref_instance_t )); + RI = oldRI; } diff --git a/ref/gl/gl_rsurf.c b/ref/gl/gl_rsurf.c index c4a53edd..7f3991c7 100644 --- a/ref/gl/gl_rsurf.c +++ b/ref/gl/gl_rsurf.c @@ -26,16 +26,22 @@ typedef struct byte lightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4]; } gllightmapstate_t; -static int nColinElim; // stats static vec2_t world_orthocenter; static vec2_t world_orthohalf; static uint r_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3]; static mextrasurf_t *fullbright_surfaces[MAX_TEXTURES]; static mextrasurf_t *detail_surfaces[MAX_TEXTURES]; static int rtable[MOD_FRAMES][MOD_FRAMES]; -static qboolean draw_alpha_surfaces = false; -static qboolean draw_fullbrights = false; -static qboolean draw_details = false; + +typedef struct +{ + int first, last; +} separate_pass_t; + +static separate_pass_t draw_wateralpha = { 0, -1 }; +static separate_pass_t draw_alpha_surfaces = { 0, -1 }; +static separate_pass_t draw_fullbrights = { 0, -1 }; +static separate_pass_t draw_details = { 0, -1 }; static msurface_t *skychain = NULL; static gllightmapstate_t gl_lms; @@ -43,6 +49,25 @@ static void LM_UploadBlock( qboolean dynamic ); static qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmaps ); static void R_DrawVBO( qboolean drawlightmaps, qboolean drawtextures ); +static inline void R_AddToSeparatePass( separate_pass_t *sp, int num ) +{ + if( sp->first > num ) + sp->first = num; + + if( sp->last < num ) + sp->last = num; +} + +static inline void R_ResetSeparatePass( separate_pass_t *sp ) +{ + sp->last = -1; +} + +static inline qboolean R_SeparatePassActive( const separate_pass_t *sp ) +{ + return sp->last >= 0 ? true : false; +} + byte *Mod_GetCurrentVis( void ) { if( gEngfuncs.drawFuncs->Mod_GetCurrentVis && tr.fCustomRendering ) @@ -61,6 +86,65 @@ void Mod_SetOrthoBounds( const float *mins, const float *maxs ) Vector2Subtract( maxs, world_orthocenter, world_orthohalf ); } +void R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords ) +{ + const mextrasurf_t *info = surf->info; + float s, t; + + s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + s += surf->light_s * sample_size; + s += sample_size * 0.5f; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + t += surf->light_t * sample_size; + t += sample_size * 0.5f; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + Vector2Set( coords, s, t ); +} + +static void R_TextureCoord( const vec3_t v, const msurface_t *surf, vec2_t coords ) +{ + const mtexinfo_t *info = surf->texinfo; + float s, t; + + s = DotProduct( v, info->vecs[0] ); + t = DotProduct( v, info->vecs[1] ); + + if( !FBitSet( surf->flags, SURF_DRAWTURB )) + { + s = ( s + info->vecs[0][3] ) / info->texture->width; + t = ( t + info->vecs[1][3] ) / info->texture->height; + } + + Vector2Set( coords, s, t ); +} + +static void R_GetEdgePosition( const model_t *mod, const msurface_t *fa, int i, vec3_t vec ) +{ + const int lindex = mod->surfedges[fa->firstedge + i]; + + if( FBitSet( mod->flags, MODEL_QBSP2 )) + { + const medge32_t *pedges = mod->edges32; + + if( lindex > 0 ) + VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec ); + else + VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec ); + } + else + { + const medge16_t *pedges = mod->edges16; + + if( lindex > 0 ) + VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec ); + else + VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec ); + } +} + static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) { int i, j; @@ -78,20 +162,18 @@ static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) } } -static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ) +static void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts ) { vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; - mextrasurf_t *warpinfo = warpface->info; float dist[SUBDIVIDE_SIZE]; - float m, frac, s, t, *v; + float m, frac, *v; int i, j, k, f, b; float sample_size; vec3_t mins, maxs; - glpoly_t *poly; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + glpoly2_t *poly; if( numverts > ( SUBDIVIDE_SIZE - 4 )) - gEngfuncs.Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts ); + gEngfuncs.Host_Error( "%s: too many vertexes on face ( %i )\n", __func__, numverts ); sample_size = gEngfuncs.Mod_SampleSizeForFace( warpface ); BoundPoly( numverts, verts, mins, maxs ); @@ -143,8 +225,8 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts } } - SubdividePolygon_r( warpface, f, front[0] ); - SubdividePolygon_r( warpface, b, back[0] ); + SubdividePolygon_r( loadmodel, warpface, f, front[0] ); + SubdividePolygon_r( loadmodel, warpface, b, back[0] ); return; } @@ -152,7 +234,7 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ClearBits( warpface->flags, SURF_DRAWTURB_QUADS ); // add a point in the center to help keep warp valid - poly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly_t ) + (numverts - 4) * VERTEXSIZE * sizeof( float )); + poly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly2_t ) + numverts * VERTEXSIZE * sizeof( float )); poly->next = warpface->polys; poly->flags = warpface->flags; warpface->polys = poly; @@ -161,65 +243,59 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts for( i = 0; i < numverts; i++, verts += 3 ) { VectorCopy( verts, poly->verts[i] ); - - if( FBitSet( warpface->flags, SURF_DRAWTURB )) - { - s = DotProduct( verts, warpface->texinfo->vecs[0] ); - t = DotProduct( verts, warpface->texinfo->vecs[1] ); - } - else - { - s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; - t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; - s /= warpface->texinfo->texture->width; - t /= warpface->texinfo->texture->height; - } - - poly->verts[i][3] = s; - poly->verts[i][4] = t; + R_TextureCoord( verts, warpface, &poly->verts[i][3] ); // for speed reasons if( !FBitSet( warpface->flags, SURF_DRAWTURB )) { // lightmap texture coordinates - s = DotProduct( verts, warpinfo->lmvecs[0] ) + warpinfo->lmvecs[0][3]; - s -= warpinfo->lightmapmins[0]; - s += warpface->light_s * sample_size; - s += sample_size * 0.5f; - s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; - - t = DotProduct( verts, warpinfo->lmvecs[1] ) + warpinfo->lmvecs[1][3]; - t -= warpinfo->lightmapmins[1]; - t += warpface->light_t * sample_size; - t += sample_size * 0.5f; - t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; - - poly->verts[i][5] = s; - poly->verts[i][6] = t; + R_LightmapCoord( verts, warpface, sample_size, &poly->verts[i][5] ); } } } -void GL_SetupFogColorForSurfaces( void ) +/* +=============================== +GL_SetupFogColorForSurfaces + +every render pass applies new fog layer, resulting in wrong fog color +recalculate fog color for current pass count +=============================== +*/ +static void GL_SetupFogColorForSurfacesEx( int passes, float density, qboolean blend_lightmaps ) { - vec3_t fogColor; + vec4_t fogColor; float factor, div; - if( !glState.isFogEnabled) + if( !glState.isFogEnabled ) return; - if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ) + if(( passes < 2 ) || (RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture )) { pglFogfv( GL_FOG_COLOR, RI.fogColor ); return; } - div = (r_detailtextures->value) ? 2.0f : 1.0f; - factor = (r_detailtextures->value) ? 3.0f : 2.0f; + div = passes - 1; + factor = passes; fogColor[0] = pow( RI.fogColor[0] / div, ( 1.0f / factor )); fogColor[1] = pow( RI.fogColor[1] / div, ( 1.0f / factor )); fogColor[2] = pow( RI.fogColor[2] / div, ( 1.0f / factor )); + fogColor[3] = 1.0f; // ignored but GL_FOG_COLOR requires vec4_t + + // because of enabled blending in R_BlendLightmaps, need to scale down fog color + // but only during lightmap blending & without VBO (it takes another route) + if( blend_lightmaps && gl_overbright.value ) + VectorScale( fogColor, 0.5f, fogColor ); + pglFogfv( GL_FOG_COLOR, fogColor ); + pglFogf( GL_FOG_DENSITY, RI.fogDensity * density ); +} + + +void GL_SetupFogColorForSurfaces( void ) +{ + GL_SetupFogColorForSurfacesEx( r_detailtextures.value ? 3 : 2, 1.0f, false ); } void GL_ResetFogColor( void ) @@ -238,30 +314,19 @@ boundaries so that turbulent and sky warps can be done reasonably. ================ */ -void GL_SubdivideSurface( msurface_t *fa ) +void GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa ) { vec3_t verts[SUBDIVIDE_SIZE]; - int numverts; - int i, lindex; - float *vec; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); + int i; // convert edges back to a normal polygon - numverts = 0; for( i = 0; i < fa->numedges; i++ ) - { - lindex = loadmodel->surfedges[fa->firstedge + i]; - - if( lindex > 0 ) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position; - else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; - VectorCopy( vec, verts[numverts] ); - numverts++; - } + R_GetEdgePosition( loadmodel, fa, i, verts[i] ); SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state // do subdivide - SubdividePolygon_r( fa, numverts, verts[0] ); + SubdividePolygon_r( loadmodel, fa, fa->numedges, verts[0] ); } /* @@ -269,20 +334,16 @@ void GL_SubdivideSurface( msurface_t *fa ) GL_BuildPolygonFromSurface ================ */ -void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) +static int GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) { - int i, lindex, lnumverts; - medge_t *pedges, *r_pedge; - mextrasurf_t *info = fa->info; + int i, lnumverts, nColinElim = 0; float sample_size; texture_t *tex; gl_texture_t *glt; - float *vec; - float s, t; - glpoly_t *poly; + glpoly2_t *poly; if( !mod || !fa->texinfo || !fa->texinfo->texture ) - return; // bad polygon ? + return nColinElim; // bad polygon ? if( FBitSet( fa->flags, SURF_CONVEYOR ) && fa->texinfo->texture->gl_texturenum != 0 ) { @@ -298,7 +359,6 @@ void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) sample_size = gEngfuncs.Mod_SampleSizeForFace( fa ); // reconstruct the polygon - pedges = mod->edges; lnumverts = fa->numedges; // detach if already created, reconstruct again @@ -306,7 +366,7 @@ void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) fa->polys = NULL; // quake simple models (healthkits etc) need to be reconstructed their polys because LM coords has changed after the map change - poly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); + poly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float )); poly->next = fa->polys; poly->flags = fa->flags; fa->polys = poly; @@ -314,48 +374,13 @@ void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) for( i = 0; i < lnumverts; i++ ) { - lindex = mod->surfedges[fa->firstedge + i]; - - if( lindex > 0 ) - { - r_pedge = &pedges[lindex]; - vec = mod->vertexes[r_pedge->v[0]].position; - } - else - { - r_pedge = &pedges[-lindex]; - vec = mod->vertexes[r_pedge->v[1]].position; - } - - s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3]; - s /= fa->texinfo->texture->width; - - t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3]; - t /= fa->texinfo->texture->height; - - VectorCopy( vec, poly->verts[i] ); - poly->verts[i][3] = s; - poly->verts[i][4] = t; - - // lightmap texture coordinates - s = DotProduct( vec, info->lmvecs[0] ) + info->lmvecs[0][3]; - s -= info->lightmapmins[0]; - s += fa->light_s * sample_size; - s += sample_size * 0.5f; - s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; - - t = DotProduct( vec, info->lmvecs[1] ) + info->lmvecs[1][3]; - t -= info->lightmapmins[1]; - t += fa->light_t * sample_size; - t += sample_size * 0.5f; - t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; - - poly->verts[i][5] = s; - poly->verts[i][6] = t; + R_GetEdgePosition( mod, fa, i, poly->verts[i] ); + R_TextureCoord( poly->verts[i], fa, &poly->verts[i][3] ); + R_LightmapCoord( poly->verts[i], fa, sample_size, &poly->verts[i][5] ); } // remove co-linear points - Ed - if( !CVAR_TO_BOOL( gl_keeptjunctions ) && !FBitSet( fa->flags, SURF_UNDERWATER )) + if( !gl_keeptjunctions.value && !FBitSet( fa->flags, SURF_UNDERWATER )) { for( i = 0; i < lnumverts; i++ ) { @@ -391,6 +416,7 @@ void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) } poly->numverts = lnumverts; + return nColinElim; } @@ -401,7 +427,7 @@ R_TextureAnim Returns the proper texture for a given time and base texture, do not process random tiling =============== */ -texture_t *R_TextureAnim( texture_t *b ) +static texture_t *R_TextureAnim( texture_t *b ) { texture_t *base = b; int count, reletive; @@ -414,6 +440,7 @@ texture_t *R_TextureAnim( texture_t *b ) if( !base->anim_total ) return base; + if( base->name[0] == '-' ) { return b; // already tiled @@ -427,7 +454,7 @@ texture_t *R_TextureAnim( texture_t *b ) speed = 10; else speed = 20; - reletive = (int)(gpGlobals->time * speed) % base->anim_total; + reletive = (int)(gp_cl->time * speed) % base->anim_total; } @@ -451,7 +478,7 @@ R_TextureAnimation Returns the proper texture for a given time and surface =============== */ -texture_t *R_TextureAnimation( msurface_t *s ) +static texture_t *R_TextureAnimation( msurface_t *s ) { texture_t *base = s->texinfo->texture; int count, reletive; @@ -481,7 +508,7 @@ texture_t *R_TextureAnimation( msurface_t *s ) speed = 10; else speed = 20; - reletive = (int)(gpGlobals->time * speed) % base->anim_total; + reletive = (int)(gp_cl->time * speed) % base->anim_total; } count = 0; @@ -502,21 +529,17 @@ texture_t *R_TextureAnimation( msurface_t *s ) R_AddDynamicLights =============== */ -void R_AddDynamicLights( msurface_t *surf ) +static void R_AddDynamicLights( const msurface_t *surf ) { - float dist, rad, minlight; - int lnum, s, t, sd, td, smax, tmax; - float sl, tl, sacc, tacc; - vec3_t impact, origin_l; - mextrasurf_t *info = surf->info; - int sample_frac = 1.0; - float sample_size; - mtexinfo_t *tex; - dlight_t *dl; - uint *bl; + const mextrasurf_t *info = surf->info; + int lnum, smax, tmax; + int sample_frac = 1.0; + float sample_size; + mtexinfo_t *tex; // no dlighted surfaces here - if( !R_CountSurfaceDlights( surf )) return; + if( !surf->dlightbits ) + return; sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); smax = (info->lightextents[0] / sample_size) + 1; @@ -534,10 +557,16 @@ void R_AddDynamicLights( msurface_t *surf ) for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { + dlight_t *dl; + vec3_t impact, origin_l; + float dist, rad, minlight; + float sl, tl; + int t; + if( !FBitSet( surf->dlightbits, BIT( lnum ))) continue; // not lit by this light - dl = gEngfuncs.GetDynamicLight( lnum ); + dl = &tr.dlights[lnum]; // transform light origin to local bmodel space if( !tr.modelviewIdentity ) @@ -564,26 +593,35 @@ void R_AddDynamicLights( msurface_t *surf ) sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; - bl = r_blocklights; - for( t = 0, tacc = 0; t < tmax; t++, tacc += sample_size ) + for( t = 0; t < tmax; t++ ) { - td = (tl - tacc) * sample_frac; - if( td < 0 ) td = -td; + int td = (tl - sample_size * t) * sample_frac; + int s; - for( s = 0, sacc = 0; s < smax; s++, sacc += sample_size, bl += 3 ) + if( td < 0 ) + td = -td; + + for( s = 0; s < smax; s++ ) { - sd = (sl - sacc) * sample_frac; - if( sd < 0 ) sd = -sd; + int sd = (sl - sample_size * s) * sample_frac; + float dist; - if( sd > td ) dist = sd + (td >> 1); - else dist = td + (sd >> 1); + if( sd < 0 ) + sd = -sd; + + if( sd > td ) + dist = sd + (td >> 1); + else + dist = td + (sd >> 1); if( dist < minlight ) { - bl[0] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.r )) / 256; - bl[1] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.g )) / 256; - bl[2] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.b )) / 256; + uint *bl = &r_blocklights[(s + (t * smax)) * 3]; + + bl[0] += ((int)((rad - dist) * 256) * dl->color.r ) / 256; + bl[1] += ((int)((rad - dist) * 256) * dl->color.g ) / 256; + bl[2] += ((int)((rad - dist) * 256) * dl->color.b ) / 256; } } } @@ -595,7 +633,7 @@ void R_AddDynamicLights( msurface_t *surf ) R_SetCacheState ================ */ -void R_SetCacheState( msurface_t *surf ) +static void R_SetCacheState( msurface_t *surf ) { int maps; @@ -668,27 +706,16 @@ static void LM_UploadDynamicBlock( void ) static void LM_UploadBlock( qboolean dynamic ) { - int i; - if( dynamic ) { - int height = 0; - - for( i = 0; i < BLOCK_SIZE; i++ ) - { - if( gl_lms.allocated[i] > height ) - height = gl_lms.allocated[i]; - } - GL_Bind( XASH_TEXTURE0, tr.dlightTexture ); - pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); + LM_UploadDynamicBlock(); } else { rgbdata_t r_lightmap; char lmName[16]; - - i = gl_lms.current_lightmap_texture; + int i = gl_lms.current_lightmap_texture; // upload static lightmaps only during loading memset( &r_lightmap, 0, sizeof( r_lightmap )); @@ -703,7 +730,7 @@ static void LM_UploadBlock( qboolean dynamic ) tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_NOMIPMAP|TF_ATLAS_PAGE ); if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) - gEngfuncs.Host_Error( "AllocBlock: full\n" ); + gEngfuncs.Host_Error( "%s: full\n", __func__ ); } } @@ -715,34 +742,40 @@ Combine and scale multiple lightmaps into the floating format in r_blocklights ================= */ -static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean dynamic ) +static void R_BuildLightMap( const msurface_t *surf, byte *dest, int stride, qboolean dynamic ) { - int smax, tmax; - uint *bl, scale; - int i, map, size, s, t; - int sample_size; - mextrasurf_t *info = surf->info; - color24 *lm; + int map, t; + const mextrasurf_t *info = surf->info; + int lightscale; - sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - smax = ( info->lightextents[0] / sample_size ) + 1; - tmax = ( info->lightextents[1] / sample_size ) + 1; - size = smax * tmax; + const int sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + const int smax = ( info->lightextents[0] / sample_size ) + 1; + const int tmax = ( info->lightextents[1] / sample_size ) + 1; + const int size = smax * tmax; - lm = surf->samples; + if( gl_overbright.value ) + lightscale = ( R_HasEnabledVBO() && !r_vbo_overbrightmode.value) ? 171 : 256; + else lightscale = ( pow( 2.0f, 1.0f / v_lightgamma->value ) * 256 ) + 0.5; memset( r_blocklights, 0, sizeof( uint ) * size * 3 ); // add all the lightmaps - for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ ) + for( map = 0; map < MAXLIGHTMAPS && surf->samples; map++ ) { + const color24 *lm = &surf->samples[map * size]; + uint scale; + int i; + + if( surf->styles[map] >= 255 ) + break; + scale = tr.lightstylevalue[surf->styles[map]]; - for( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ ) + for( i = 0; i < size; i++ ) { - bl[0] += gEngfuncs.LightToTexGamma( lm->r ) * scale; - bl[1] += gEngfuncs.LightToTexGamma( lm->g ) * scale; - bl[2] += gEngfuncs.LightToTexGamma( lm->b ) * scale; + r_blocklights[i * 3 + 0] += lm[i].r * scale; + r_blocklights[i * 3 + 1] += lm[i].g * scale; + r_blocklights[i * 3 + 2] += lm[i].b * scale; } } @@ -750,21 +783,26 @@ static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean if( surf->dlightframe == tr.framecount && dynamic ) R_AddDynamicLights( surf ); - // Put into texture format - stride -= (smax << 2); - bl = r_blocklights; - - for( t = 0; t < tmax; t++, dest += stride ) + for( t = 0; t < tmax; t++ ) { + int s; + for( s = 0; s < smax; s++ ) { - dest[0] = Q_min((bl[0] >> 7), 255 ); - dest[1] = Q_min((bl[1] >> 7), 255 ); - dest[2] = Q_min((bl[2] >> 7), 255 ); - dest[3] = 255; + const uint *bl = &r_blocklights[(s + (t * smax)) * 3]; + byte *dst = &dest[(t * stride) + (s * 4)]; + int i; - bl += 3; - dest += 4; + for( i = 0; i < 3; i++ ) + { + int t = bl[i] * lightscale >> 14; + + if( t > 1023 ) + t = 1023; + + dst[i] = LightToTexGamma( t ) >> 2; + } + dst[3] = 255; } } } @@ -774,7 +812,7 @@ static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean DrawGLPoly ================ */ -void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) +static void DrawGLPoly( glpoly2_t *p, float xScale, float yScale ) { float *v; float sOffset, sy; @@ -793,7 +831,7 @@ void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) float flRate, flAngle; gl_texture_t *texture; - if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) ) + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == CL_GetEntityByIndex( 0 )) { // same as doom speed flConveyorSpeed = -35.0f; @@ -803,14 +841,14 @@ void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f; if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed; } - texture = R_GetTexture( glState.currentTextures[glState.activeTMU] ); + texture = R_GetTexture( glState.currentTexturesIndex[glState.activeTMU] ); flRate = fabs( flConveyorSpeed ) / (float)texture->srcWidth; flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0; SinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy ); - sOffset = gpGlobals->time * cy * flRate; - tOffset = gpGlobals->time * sy * flRate; + sOffset = gp_cl->time * cy * flRate; + tOffset = gp_cl->time * sy * flRate; // make sure that we are positive if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset; @@ -852,7 +890,7 @@ DrawGLPolyChain Render lightmaps ================ */ -void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) +static void DrawGLPolyChain( glpoly2_t *p, float soffset, float toffset ) { qboolean dynamic = true; @@ -877,9 +915,9 @@ void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) } } -_inline qboolean R_HasLightmap( void ) +static qboolean R_HasLightmap( void ) { - if( CVAR_TO_BOOL( r_fullbright ) || !WORLDMODEL->lightdata ) + if( r_fullbright->value || !WORLDMODEL->lightdata ) return false; if( RI.currententity ) @@ -906,7 +944,7 @@ _inline qboolean R_HasLightmap( void ) R_BlendLightmaps ================ */ -void R_BlendLightmaps( void ) +static void R_BlendLightmaps( void ) { msurface_t *surf, *newsurf = NULL; int i; @@ -914,9 +952,9 @@ void R_BlendLightmaps( void ) if( !R_HasLightmap() ) return; - GL_SetupFogColorForSurfaces (); + GL_SetupFogColorForSurfacesEx( r_detailtextures.value ? 3 : 2, 1.0f, true ); - if( !CVAR_TO_BOOL( r_lightmap )) + if( !r_lightmap->value ) pglEnable( GL_BLEND ); else pglDisable( GL_BLEND ); @@ -925,7 +963,16 @@ void R_BlendLightmaps( void ) pglDepthFunc( GL_EQUAL ); pglDisable( GL_ALPHA_TEST ); - pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + if( gl_overbright.value ) + { + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + if(!( R_HasEnabledVBO() && !r_vbo_overbrightmode.value )) + pglColor4f( 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f ); + } + else + { + pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + } pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); // render static lightmaps first @@ -943,7 +990,7 @@ void R_BlendLightmaps( void ) } // render dynamic lightmaps - if( CVAR_TO_BOOL( r_dynamic )) + if( r_dynamic->value ) { LM_InitBlock(); @@ -1031,12 +1078,12 @@ void R_BlendLightmaps( void ) R_RenderFullbrights ================ */ -void R_RenderFullbrights( void ) +static void R_RenderFullbrights( void ) { mextrasurf_t *es, *p; int i; - if( !draw_fullbrights ) + if( !R_SeparatePassActive( &draw_fullbrights )) return; R_AllowFog( false ); @@ -1046,10 +1093,19 @@ void R_RenderFullbrights( void ) pglBlendFunc( GL_ONE, GL_ONE ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - for( i = 1; i < MAX_TEXTURES; i++ ) + // if fullbright textures are drawn in separate pass from VBO they + // cause z-fighting, this is noticeable on `slide_waifufu.bsp` map + if( R_HasEnabledVBO() && gl_polyoffset.value ) + { + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1.0f, -gl_polyoffset.value ); + } + + for( i = draw_fullbrights.first; i <= draw_fullbrights.last; i++ ) { es = fullbright_surfaces[i]; - if( !es ) continue; + if( !es ) + continue; GL_Bind( XASH_TEXTURE0, i ); @@ -1060,12 +1116,15 @@ void R_RenderFullbrights( void ) es->lumachain = NULL; } + if( R_HasEnabledVBO() && gl_polyoffset.value ) + pglDisable( GL_POLYGON_OFFSET_FILL ); + pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); pglDisable( GL_ALPHA_TEST ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - draw_fullbrights = false; + R_ResetSeparatePass( &draw_fullbrights ); R_AllowFog( true ); } @@ -1074,27 +1133,35 @@ void R_RenderFullbrights( void ) R_RenderDetails ================ */ -void R_RenderDetails( void ) +static void R_RenderDetails( int passes ) { gl_texture_t *glt; mextrasurf_t *es, *p; msurface_t *fa; int i; - if( !draw_details ) + if( !R_SeparatePassActive( &draw_details )) return; - GL_SetupFogColorForSurfaces(); + GL_SetupFogColorForSurfacesEx( passes, passes == 2 ? 0.5f : 1.0f, false ); pglEnable( GL_BLEND ); pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); - pglDepthFunc( GL_EQUAL ); + if(passes == 3) + pglDepthFunc( GL_EQUAL ); + else + { + pglDepthFunc( GL_LEQUAL ); + //pglDepthMask( GL_FALSE ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + } - for( i = 1; i < MAX_TEXTURES; i++ ) + for( i = draw_details.first; i <= draw_details.last; i++ ) { es = detail_surfaces[i]; - if( !es ) continue; + if( !es ) + continue; GL_Bind( XASH_TEXTURE0, i ); @@ -1112,78 +1179,52 @@ void R_RenderDetails( void ) pglDisable( GL_BLEND ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); pglDepthFunc( GL_LEQUAL ); + pglDisable( GL_POLYGON_OFFSET_FILL ); - draw_details = false; + R_ResetSeparatePass( &draw_details ); // restore fog here GL_ResetFogColor(); } -/* -================ -R_RenderBrushPoly -================ -*/ -void R_RenderBrushPoly( msurface_t *fa, int cull_type ) +static void R_RenderFullbrightForSurface( msurface_t *fa, texture_t *t ) { - qboolean is_dynamic = false; - int maps; - texture_t *t; - - r_stats.c_world_polys++; - - if( fa->flags & SURF_DRAWSKY ) - return; // already handled - - t = R_TextureAnimation( fa ); - - GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); - - if( FBitSet( fa->flags, SURF_DRAWTURB )) - { - // warp texture, no lightmaps - EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); + if( !t->fb_texturenum ) return; - } - if( t->fb_texturenum ) - { - fa->info->lumachain = fullbright_surfaces[t->fb_texturenum]; - fullbright_surfaces[t->fb_texturenum] = fa->info; - draw_fullbrights = true; - } + fa->info->lumachain = fullbright_surfaces[t->fb_texturenum]; + fullbright_surfaces[t->fb_texturenum] = fa->info; + R_AddToSeparatePass( &draw_fullbrights, t->fb_texturenum ); +} - if( CVAR_TO_BOOL( r_detailtextures )) +static void R_RenderDetailsForSurface( msurface_t *fa, texture_t *t ) +{ + if( !r_detailtextures.value ) + return; + + if( glState.isFogEnabled ) { - if( glState.isFogEnabled ) + // don't apply detail textures for windows in the fog + if( RI.currententity->curstate.rendermode != kRenderTransTexture ) { - // don't apply detail textures for windows in the fog - if( RI.currententity->curstate.rendermode != kRenderTransTexture ) - { - if( t->dt_texturenum ) - { - fa->info->detailchain = detail_surfaces[t->dt_texturenum]; - detail_surfaces[t->dt_texturenum] = fa->info; - } - else - { - // draw stub detail texture for underwater surfaces - fa->info->detailchain = detail_surfaces[tr.grayTexture]; - detail_surfaces[tr.grayTexture] = fa->info; - } - draw_details = true; - } - } - else if( t->dt_texturenum ) - { - fa->info->detailchain = detail_surfaces[t->dt_texturenum]; - detail_surfaces[t->dt_texturenum] = fa->info; - draw_details = true; + // draw stub detail texture for underwater surfaces + int texturenum = t->dt_texturenum ? t->dt_texturenum : tr.grayTexture; + + fa->info->detailchain = detail_surfaces[texturenum]; + detail_surfaces[texturenum] = fa->info; + R_AddToSeparatePass( &draw_details, texturenum ); } } + else if( t->dt_texturenum ) + { + fa->info->detailchain = detail_surfaces[t->dt_texturenum]; + detail_surfaces[t->dt_texturenum] = fa->info; + R_AddToSeparatePass( &draw_details, t->dt_texturenum ); + } +} - DrawGLPoly( fa->polys, 0.0f, 0.0f ); - +static void R_RenderDecalsForSurface( msurface_t *fa, int cull_type ) +{ if( RI.currententity->curstate.rendermode == kRenderNormal ) { // batch decals to draw later @@ -1195,9 +1236,12 @@ void R_RenderBrushPoly( msurface_t *fa, int cull_type ) // if rendermode != kRenderNormal draw decals sequentially DrawSurfaceDecals( fa, true, (cull_type == CULL_BACKSIDE)); } +} - if( FBitSet( fa->flags, SURF_DRAWTILED )) - return; // no lightmaps anyway +static qboolean R_CheckLightMap( msurface_t *fa ) +{ + qboolean is_dynamic = false; + int maps; // check for lightmap modification for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) @@ -1211,12 +1255,15 @@ void R_RenderBrushPoly( msurface_t *fa, int cull_type ) { dynamic: // NOTE: at this point we have only valid textures - if( r_dynamic->value ) is_dynamic = true; + if( r_dynamic->value ) + is_dynamic = true; } if( is_dynamic ) { - if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 || fa->styles[maps] == 20 ) && ( fa->dlightframe != tr.framecount )) + const int style = fa->styles[maps]; + + if( maps < MAXLIGHTMAPS && ( style >= 32 || style == 0 || style == 20 ) && fa->dlightframe != tr.framecount ) { byte temp[132*132*4]; mextrasurf_t *info = fa->info; @@ -1227,22 +1274,47 @@ dynamic: smax = ( info->lightextents[0] / sample_size ) + 1; tmax = ( info->lightextents[1] / sample_size ) + 1; - R_BuildLightMap( fa, temp, smax * 4, true ); + if( smax < 132 && tmax < 132 ) + R_BuildLightMap( fa, temp, smax * 4, true ); + else + { + smax = Q_min( smax, 132 ); + tmax = Q_min( tmax, 132 ); + memset( temp, 255, sizeof( temp )); + //Host_MapDesignError( "%s: bad surface extents: %d %d", __func__, fa->extents[0], fa->extents[1] ); + } + R_SetCacheState( fa ); +#if XASH_WES + GL_Bind( XASH_TEXTURE1, tr.lightmapTextures[fa->lightmaptexturenum] ); + pglTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); +#else GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] ); +#endif - pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, - GL_RGBA, GL_UNSIGNED_BYTE, temp ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, temp ); - fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; - gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; +#if XASH_WES + GL_SelectTexture( XASH_TEXTURE0 ); +#endif } else - { - fa->info->lightmapchain = gl_lms.dynamic_surfaces; - gl_lms.dynamic_surfaces = fa; - } + return true; // add to dynamic chain + } + + return false; // updated +} + +static void R_RenderLightmapForSurface( msurface_t *fa ) +{ + if( !fa->polys || FBitSet( fa->flags, SURF_DRAWTILED )) + return; + + if( R_CheckLightMap( fa )) + { + fa->info->lightmapchain = gl_lms.dynamic_surfaces; + gl_lms.dynamic_surfaces = fa; } else { @@ -1251,12 +1323,43 @@ dynamic: } } +/* +================ +R_RenderBrushPoly +================ +*/ +static void R_RenderBrushPoly( msurface_t *fa, int cull_type ) +{ + texture_t *t; + + r_stats.c_world_polys++; + + if( fa->flags & SURF_DRAWSKY ) + return; // already handled + + t = R_TextureAnimation( fa ); + + if( FBitSet( fa->flags, SURF_DRAWTURB )) + { + // warp texture, no lightmaps + EmitWaterPolys( fa, cull_type == CULL_BACKSIDE, R_UploadRipples( t )); + return; + } + else GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); + + R_RenderFullbrightForSurface( fa, t ); + R_RenderDetailsForSurface( fa, t ); + DrawGLPoly( fa->polys, 0.0f, 0.0f ); + R_RenderDecalsForSurface( fa, cull_type ); + R_RenderLightmapForSurface( fa ); +} + /* ================ R_DrawTextureChains ================ */ -void R_DrawTextureChains( void ) +static void R_DrawTextureChains( void ) { int i; msurface_t *s; @@ -1269,10 +1372,10 @@ void R_DrawTextureChains( void ) GL_SetupFogColorForSurfaces(); // restore worldmodel - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currententity = CL_GetEntityByIndex( 0 ); RI.currentmodel = RI.currententity->model; - if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) ) + if( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX )) { pglDisable( GL_TEXTURE_2D ); pglColor3f( 1.0f, 1.0f, 1.0f ); @@ -1282,7 +1385,7 @@ void R_DrawTextureChains( void ) for( s = skychain; s != NULL; s = s->texturechain ) R_AddSkyBoxSurface( s ); - if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) ) + if( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX )) { pglEnable( GL_TEXTURE_2D ); if( skychain ) @@ -1290,6 +1393,8 @@ void R_DrawTextureChains( void ) skychain = NULL; } + R_DrawVBO( !r_fullbright->value && !!WORLDMODEL->lightdata, true ); + for( i = 0; i < WORLDMODEL->numtextures; i++ ) { t = WORLDMODEL->textures[i]; @@ -1300,12 +1405,15 @@ void R_DrawTextureChains( void ) if( !s || ( i == tr.skytexturenum )) continue; - if(( s->flags & SURF_DRAWTURB ) && MOVEVARS->wateralpha < 1.0f ) + if(( s->flags & SURF_DRAWTURB ) && tr.movevars->wateralpha < 1.0f ) + { + R_AddToSeparatePass( &draw_wateralpha, i ); continue; // draw translucent water later + } if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT )) { - draw_alpha_surfaces = true; + R_AddToSeparatePass( &draw_alpha_surfaces, i ); continue; // draw transparent surfaces later } @@ -1326,7 +1434,7 @@ void R_DrawAlphaTextureChains( void ) msurface_t *s; texture_t *t; - if( !draw_alpha_surfaces ) + if( !R_SeparatePassActive( &draw_alpha_surfaces )) return; memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); @@ -1343,15 +1451,15 @@ void R_DrawAlphaTextureChains( void ) GL_SetupFogColorForSurfaces(); // restore worldmodel - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currententity = CL_GetEntityByIndex( 0 ); RI.currentmodel = RI.currententity->model; RI.currententity->curstate.rendermode = kRenderTransAlpha; - draw_alpha_surfaces = false; - for( i = 0; i < WORLDMODEL->numtextures; i++ ) + for( i = draw_alpha_surfaces.first; i <= draw_alpha_surfaces.last; i++ ) { t = WORLDMODEL->textures[i]; - if( !t ) continue; + if( !t ) + continue; s = t->texturechain; @@ -1363,6 +1471,8 @@ void R_DrawAlphaTextureChains( void ) t->texturechain = NULL; } + R_ResetSeparatePass( &draw_alpha_surfaces ); + GL_ResetFogColor(); R_BlendLightmaps(); RI.currententity->curstate.rendermode = kRenderNormal; // restore world rendermode @@ -1384,11 +1494,11 @@ void R_DrawWaterSurfaces( void ) return; // non-transparent water is already drawed - if( MOVEVARS->wateralpha >= 1.0f ) + if( !R_SeparatePassActive( &draw_wateralpha )) return; // restore worldmodel - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); + RI.currententity = CL_GetEntityByIndex( 0 ); RI.currentmodel = RI.currententity->model; // go back to the world matrix @@ -1399,9 +1509,9 @@ void R_DrawWaterSurfaces( void ) pglDisable( GL_ALPHA_TEST ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglColor4f( 1.0f, 1.0f, 1.0f, MOVEVARS->wateralpha ); + pglColor4f( 1.0f, 1.0f, 1.0f, tr.movevars->wateralpha ); - for( i = 0; i < WORLDMODEL->numtextures; i++ ) + for( i = draw_wateralpha.first; i <= draw_wateralpha.last; i++ ) { t = WORLDMODEL->textures[i]; if( !t ) continue; @@ -1412,15 +1522,14 @@ void R_DrawWaterSurfaces( void ) if( !FBitSet( s->flags, SURF_DRAWTURB )) continue; - // set modulate mode explicitly - GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); - for( ; s; s = s->texturechain ) - EmitWaterPolys( s, false ); + EmitWaterPolys( s, false, R_UploadRipples( t )); t->texturechain = NULL; } + R_ResetSeparatePass( &draw_wateralpha ); + pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); pglDisable( GL_ALPHA_TEST ); @@ -1459,7 +1568,7 @@ static int R_SurfaceCompare( const void *a, const void *b ) return 0; } -void R_SetRenderMode( cl_entity_t *e ) +static void R_SetRenderMode( cl_entity_t *e ) { switch( e->curstate.rendermode ) { @@ -1522,7 +1631,7 @@ void R_DrawBrushModel( cl_entity_t *e ) model_t *clmodel; qboolean rotated; dlight_t *l; - qboolean allow_vbo = CVAR_TO_BOOL( r_vbo ); + qboolean allow_vbo = R_HasEnabledVBO(); if( !RI.drawWorld ) return; @@ -1569,9 +1678,9 @@ void R_DrawBrushModel( cl_entity_t *e ) // calculate dynamic lighting for bmodel for( k = 0; k < MAX_DLIGHTS; k++ ) { - l = gEngfuncs.GetDynamicLight( k ); + l = &tr.dlights[k]; - if( l->die < gpGlobals->time || !l->radius ) + if( l->die < gp_cl->time || !l->radius ) continue; VectorCopy( l->origin, oldorigin ); // save lightorigin @@ -1583,8 +1692,6 @@ void R_DrawBrushModel( cl_entity_t *e ) // setup the rendermode R_SetRenderMode( e ); - GL_SetupFogColorForSurfaces (); - if( e->curstate.rendermode == kRenderTransAdd ) { R_AllowFog( false ); @@ -1594,6 +1701,9 @@ void R_DrawBrushModel( cl_entity_t *e ) if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) allow_vbo = false; + if( !allow_vbo ) + GL_SetupFogColorForSurfaces (); + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; num_sorted = 0; @@ -1627,7 +1737,7 @@ void R_DrawBrushModel( cl_entity_t *e ) } // sort faces if needs - if( !FBitSet( clmodel->flags, MODEL_LIQUID ) && e->curstate.rendermode == kRenderTransTexture && !CVAR_TO_BOOL( gl_nosort )) + if( !FBitSet( clmodel->flags, MODEL_LIQUID ) && e->curstate.rendermode == kRenderTransTexture && !gl_nosort.value ) qsort( gpGlobals->draw_surfaces, num_sorted, sizeof( sortedface_t ), R_SurfaceCompare ); // draw sorted translucent surfaces @@ -1643,7 +1753,7 @@ void R_DrawBrushModel( cl_entity_t *e ) GL_ResetFogColor(); R_BlendLightmaps(); R_RenderFullbrights(); - R_RenderDetails(); + R_RenderDetails( allow_vbo? 2: 3 ); // restore fog here if( e->curstate.rendermode == kRenderTransAdd ) @@ -1654,7 +1764,22 @@ void R_DrawBrushModel( cl_entity_t *e ) pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); - R_DrawModelHull(); // draw before restore + + if( r_showhull->value > 0.0f ) + { + GLfloat factor, units; + + pglGetFloatv( GL_POLYGON_OFFSET_FACTOR, &factor ); + pglGetFloatv( GL_POLYGON_OFFSET_UNITS, &units ); + + pglPolygonOffset( 1.0f, 2.0f ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + gEngfuncs.R_DrawModelHull( clmodel ); // draw before restore + pglDisable( GL_POLYGON_OFFSET_FILL ); + + pglPolygonOffset( factor, units ); + } + R_LoadIdentity(); // restore worldmatrix } @@ -1683,10 +1808,20 @@ typedef struct vbovertex_s #endif } vbovertex_t; +#ifndef UINT_INDEX +typedef unsigned short vboindex_t; +#define VBOINDEX_MAX USHRT_MAX +#define GL_VBOINDEX_TYPE GL_UNSIGNED_SHORT +#else +typedef unsigned int vboindex_t; +#define VBOINDEX_MAX UINT_MAX +#define GL_VBOINDEX_TYPE GL_UNSIGNED_INT +#endif + // store indexes for each texture typedef struct vbotexture_s { - unsigned short *indexarray; // index array (generated instead of texture chains) + vboindex_t *indexarray; // index array (generated instead of texture chains) uint curindex; // counter for index array uint len; // maximum index array length struct vbotexture_s *next; // if cannot fit into one array, allocate new one, as every array has own index space @@ -1731,7 +1866,7 @@ typedef struct vbodecaldata_s // gl_decals.c extern decal_t gDecalPool[MAX_RENDER_DECALS]; -struct vbo_static_s +static struct vbo_static_s { // quickly free all allocations on map change poolhandle_t mempool; @@ -1743,7 +1878,7 @@ struct vbo_static_s vboarray_t *arraylist; // linked list // separate areay for dlights (build during draw) - unsigned short *dlight_index; // array + vboindex_t *dlight_index; // array vec2_t *dlight_tc; // array unsigned int dlight_vbo; vbovertex_t decal_dlight[MAX_RENDER_DECALS * DECAL_VERTS_MAX]; @@ -1761,9 +1896,13 @@ struct vbo_static_s int maxarraysplit_tex; int minarraysplit_lm; int maxarraysplit_lm; + + // cvar state potentially might be changed during frame + // so only enable VBO at the beginning of frame + qboolean enabled; } vbos; -struct multitexturestate_s +static struct multitexturestate_s { int tmu_gl; // texture tmu int tmu_dt; // detail tmu @@ -1774,6 +1913,53 @@ struct multitexturestate_s gl_texture_t *glt; // details scale } mtst; +enum array_state_e +{ + VBO_ARRAY_NONE, + VBO_ARRAY_STATIC, + VBO_ARRAY_DECAL, + VBO_ARRAY_DLIGHT, + VBO_ARRAY_DECAL_DLIGHT +}; + +enum texture_state_e +{ + VBO_TEXTURE_NONE, + VBO_TEXTURE_MAIN, + VBO_TEXTURE_DECAL +}; + +enum lightmap_state_e +{ + VBO_LIGHTMAP_NONE, + VBO_LIGHTMAP_STATIC, + VBO_LIGHTMAP_DYNAMIC +}; + +static struct arraystate_s +{ + enum array_state_e astate; + enum texture_state_e tstate; + enum lightmap_state_e lstate; + int itexture; + qboolean decal_mode; +} vboarray; + +qboolean R_HasGeneratedVBO( void ) +{ + return vbos.mempool != 0; +} + +void R_EnableVBO( qboolean enable ) +{ + vbos.enabled = enable; +} + +qboolean R_HasEnabledVBO( void ) +{ + return vbos.enabled; +} + /* =================== R_GenerateVBO @@ -1783,24 +1969,24 @@ Allocate memory for arrays, fill it with vertex attribs and upload to GPU */ void R_GenerateVBO( void ) { - int numtextures = WORLDMODEL->numtextures; - int numlightmaps = gl_lms.current_lightmap_texture; + model_t *world = WORLDMODEL; + msurface_t *surfaces; + int numsurfaces; + int numtextures; + const int numlightmaps = gl_lms.current_lightmap_texture; int k, len = 0; vboarray_t *vbo; uint maxindex = 0; + double t1, t2, t3; - R_ClearVBO(); - - // we do not want to write vbo code that does not use multitexture - if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 ) - { - gEngfuncs.Cvar_FullSet( "gl_vbo", "0", FCVAR_READ_ONLY ); + if( R_HasGeneratedVBO() || !world || !world->surfaces ) return; - } - // save in config if enabled manually - if( CVAR_TO_BOOL( r_vbo ) ) - r_vbo->flags |= FCVAR_ARCHIVE; + t1 = gEngfuncs.pfnTime(); + + surfaces = world->surfaces; + numsurfaces = world->numsurfaces; + numtextures = world->numtextures; vbos.mempool = Mem_AllocPool("Render VBO Zone"); @@ -1829,24 +2015,25 @@ void R_GenerateVBO( void ) int i; vbotexture_t *vbotex = &vbos.textures[k * numtextures + j]; - for( i = 0; i < WORLDMODEL->numsurfaces; i++ ) + for( i = 0; i < numsurfaces; i++ ) { - msurface_t *surf = &WORLDMODEL->surfaces[i]; - - if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) - continue; + msurface_t *surf = &surfaces[i]; if( surf->lightmaptexturenum != k ) continue; - if( R_TextureAnimation( surf ) != WORLDMODEL->textures[j] ) + if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) continue; - if( vbo->array_len + surf->polys->numverts > USHRT_MAX ) + if( R_TextureAnimation( surf ) != world->textures[j] ) + continue; + + if( vbo->array_len + surf->polys->numverts > VBOINDEX_MAX ) { + vbotex->vboarray = vbo; // generate new array and new vbotexture node vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len ); - gEngfuncs.Con_Printf( "R_GenerateVBOs: allocated array of %d verts, texture %d\n", vbo->array_len, j ); + gEngfuncs.Con_Printf( S_NOTE "%s: allocated array of %d verts, texture %d, lm %d\n", __func__, vbo->array_len, j, k ); vbo->next = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) ); vbo = vbo->next; vbotex->next = Mem_Calloc( vbos.mempool, sizeof( vbotexture_t ) ); @@ -1874,7 +2061,9 @@ void R_GenerateVBO( void ) // allocate last array vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len ); - gEngfuncs.Con_Printf( "R_GenerateVBOs: allocated array of %d verts\n", vbo->array_len ); + + t2 = gEngfuncs.pfnTime(); + gEngfuncs.Con_Printf( S_NOTE "%s: allocated array of %d verts in %.3g seconds\n", __func__, vbo->array_len, t2 - t1 ); // switch to list begin vbo = vbos.arraylist; @@ -1890,39 +2079,39 @@ void R_GenerateVBO( void ) vbotexture_t *vbotex = &vbos.textures[k * numtextures + j]; // preallocate index arrays - vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len ); + vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( *vbotex->indexarray ) * 6 * vbotex->len ); vbotex->lightmaptexturenum = k; if( maxindex < vbotex->len ) maxindex = vbotex->len; - for( i = 0; i < WORLDMODEL->numsurfaces; i++ ) + for( i = 0; i < numsurfaces; i++ ) { - msurface_t *surf = &WORLDMODEL->surfaces[i]; + msurface_t *surf = &surfaces[i]; int l; - if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) - continue; - if( surf->lightmaptexturenum != k ) continue; - if( R_TextureAnimation( surf ) != WORLDMODEL->textures[j] ) + if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) + continue; + + if( R_TextureAnimation( surf ) != world->textures[j] ) continue; // switch to next array - if( len + surf->polys->numverts > USHRT_MAX ) + if( len + surf->polys->numverts > VBOINDEX_MAX ) { // upload last generated array pglGenBuffersARB( 1, &vbo->glindex ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); pglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB ); - ASSERT( len == vbo->array_len ); + Assert( len == vbo->array_len ); vbo = vbo->next; vbotex = vbotex->next; - vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len ); + vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( *vbotex->indexarray ) * 6 * vbotex->len ); vbotex->lightmaptexturenum = k; // calculate limits for dlights @@ -1943,9 +2132,9 @@ void R_GenerateVBO( void ) vbo->array[len + l].lm_tc[0] = v[5]; vbo->array[len + l].lm_tc[1] = v[6]; #ifdef NO_TEXTURE_MATRIX - if( WORLDMODEL->textures[j]->dt_texturenum ) + if( world->textures[j]->dt_texturenum ) { - gl_texture_t *glt = R_GetTexture( WORLDMODEL->textures[j]->gl_texturenum ); + gl_texture_t *glt = R_GetTexture( world->textures[j]->gl_texturenum ); vbo->array[len + l].dt_tc[0] = v[3] * glt->xscale; vbo->array[len + l].dt_tc[1] = v[4] * glt->yscale; } @@ -1957,7 +2146,7 @@ void R_GenerateVBO( void ) } } } - ASSERT( len == vbo->array_len ); + Assert( len == vbo->array_len ); // upload last array pglGenBuffersARB( 1, &vbo->glindex ); @@ -1970,25 +2159,29 @@ void R_GenerateVBO( void ) pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_CUT * MAX_RENDER_DECALS, vbos.decaldata->decalarray, GL_DYNAMIC_DRAW_ARB ); // preallocate dlight arrays - vbos.dlight_index = Mem_Calloc( vbos.mempool, maxindex * sizeof( unsigned short ) * 6 ); + vbos.dlight_index = Mem_Calloc( vbos.mempool, maxindex * sizeof( *vbos.dlight_index ) * 6 ); // select maximum possible length for dlight - vbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) ); + vbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)( vbos.arraylist->next ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 )); - if( CVAR_TO_BOOL(r_vbo_dlightmode) ) + if( r_vbo_dlightmode.value ) { pglGenBuffersARB( 1, &vbos.dlight_vbo ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); - pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) , vbos.dlight_tc, GL_STREAM_DRAW_ARB ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (int)( vbos.arraylist->next ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 ), NULL, GL_STREAM_DRAW_ARB ); pglGenBuffersARB( 1, &vbos.decal_dlight_vbo ); pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); - pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), vbos.decal_dlight, GL_STREAM_DRAW_ARB ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB ); } // reset state pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); mtst.tmu_gl = XASH_TEXTURE0; + + t3 = gEngfuncs.pfnTime(); + + gEngfuncs.Con_Reportf( S_NOTE "%s: uploaded VBOs in %.3g seconds, %.3g seconds total\n", __func__, t3 - t2, t3 - t1 ); } /* @@ -2071,6 +2264,7 @@ static void R_DisableDetail( void ) GL_SelectTexture( mtst.tmu_dt ); pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); pglDisable( GL_TEXTURE_2D ); + pglMatrixMode( GL_TEXTURE ); pglLoadIdentity(); } } @@ -2119,17 +2313,37 @@ static void R_SetLightmap( void ) if( mtst.skiptexture ) return; - /*if( gl_overbright->integer ) + if( gl_overbright.value ) { + if( r_vbo_overbrightmode.value == 1 ) + { + GLfloat color[4] = { 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f }; + int tmu = glState.activeTMU; + GL_SelectTexture( tmu - 1 ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT_ARB ); + pglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + // doesn't work here for some reason + pglTexEnvfv( GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color ); + GL_SelectTexture( tmu ); + } + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB ); pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE ); pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB ); pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB ); pglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 ); - } - else*/ + else + { pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + } pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); } @@ -2138,11 +2352,14 @@ static void R_SetLightmap( void ) R_SetDecalMode When drawing decal, disable or restore bump and details -set tmu to lightmap when enabled ============== */ static void R_SetDecalMode( qboolean enable ) { + if( vboarray.decal_mode == enable ) + return; + vboarray.decal_mode = enable; + // order is important to correctly rearrange TMUs if( enable ) { @@ -2172,7 +2389,7 @@ static texture_t *R_SetupVBOTexture( texture_t *tex, int number ) if( !tex ) tex = R_TextureAnim( WORLDMODEL->textures[number] ); - if( CVAR_TO_BOOL( r_detailtextures ) && tex->dt_texturenum && mtst.tmu_dt != -1 ) + if( r_detailtextures.value && tex->dt_texturenum && mtst.tmu_dt != -1 ) { mtst.details_enabled = true; GL_Bind( mtst.tmu_dt, tex->dt_texturenum ); @@ -2181,11 +2398,130 @@ static texture_t *R_SetupVBOTexture( texture_t *tex, int number ) } else R_DisableDetail(); - GL_Bind( mtst.tmu_gl, CVAR_TO_BOOL( r_lightmap )?tr.whiteTexture:tex->gl_texturenum ); + GL_Bind( mtst.tmu_gl, r_lightmap->value ?tr.whiteTexture:tex->gl_texturenum ); + if(number) + vboarray.itexture = number; return tex; } + +static void R_SetupVBOArrayStatic( vboarray_t *vbo, qboolean drawlightmap, qboolean drawtextures ) +{ + if( vboarray.astate != VBO_ARRAY_STATIC ) + { + // bind array + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + // dlights use same vertex array + if( vboarray.astate != VBO_ARRAY_DLIGHT ) + { + + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + } + + + // setup multitexture + if( drawtextures && vboarray.tstate != VBO_TEXTURE_MAIN ) + { + GL_SelectTexture( mtst.tmu_gl = XASH_TEXTURE0 ); + pglEnable( GL_TEXTURE_2D ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + vboarray.tstate = VBO_TEXTURE_MAIN; + } + + if( drawlightmap && (vboarray.lstate != VBO_LIGHTMAP_STATIC || vboarray.astate == VBO_ARRAY_DECAL ) ) + { + // set lightmap texenv + if( mtst.lm ) + GL_Bind( mtst.tmu_lm = XASH_TEXTURE1, mtst.lm ); + else + GL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 ); + pglEnable( GL_TEXTURE_2D ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_SetLightmap(); + vboarray.lstate = VBO_LIGHTMAP_STATIC; + } + vboarray.astate = VBO_ARRAY_STATIC; + R_SetDecalMode( false ); + } +} + + + +static void R_SetupVBOArrayDlight( vboarray_t *vbo, texture_t *texture ) +{ + + if( vboarray.astate != VBO_ARRAY_DLIGHT ) + { + if( vboarray.astate == VBO_ARRAY_DECAL_DLIGHT ) + { + // bind array + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); + } + if( vboarray.tstate != VBO_TEXTURE_MAIN || vboarray.astate == VBO_ARRAY_DECAL_DLIGHT ) + { + R_SetupVBOTexture( texture, vboarray.itexture ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + vboarray.tstate = VBO_TEXTURE_MAIN; + } + GL_Bind( mtst.tmu_lm, tr.dlightTexture ); + vboarray.lstate = VBO_LIGHTMAP_DYNAMIC; + + // replace lightmap texcoord array by dlight array + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + if( vbos.dlight_vbo ) + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 ); + else + pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc ); + + vboarray.astate = VBO_ARRAY_DLIGHT; + } +} + +#define SPARSE_DECALS_UPLOAD 0 + +static void R_SetupVBOArrayDecalDlight( int decalcount ) +{ + if( vbos.decal_dlight_vbo ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); +#if !SPARSE_DECALS_UPLOAD + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_MAX * decalcount, vbos.decal_dlight, GL_STREAM_DRAW_ARB ); +#endif + } + + R_SetDecalMode( true ); + // hack: fix decal dlights on gl_vbo_details == 2 (wrong state??) + /*if( mtst.details_enabled && mtst.tmu_dt != -1 ) + { + GL_Bind( mtst.tmu_dt, tr.whiteTexture ); + }*/ + GL_SelectTexture( mtst.tmu_lm ); + if( vbos.decal_dlight_vbo ) + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) ); + } + else + { + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos); + } + vboarray.astate = VBO_ARRAY_DECAL_DLIGHT; + vboarray.tstate = VBO_TEXTURE_DECAL; + vboarray.lstate = VBO_LIGHTMAP_DYNAMIC; +} + + /* =================== R_AdditionalPasses @@ -2193,10 +2529,13 @@ R_AdditionalPasses draw details when not enough tmus =================== */ -static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo ) +static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo, size_t offset ) { + if( !indexlen ) + return; + // draw details in additional pass - if( r_detailtextures->value && mtst.tmu_dt == -1 && tex->dt_texturenum ) + if( r_detailtextures.value && r_vbo_detail.value == 1 && mtst.tmu_dt == -1 && tex->dt_texturenum ) { gl_texture_t *glt = R_GetTexture( tex->gl_texturenum ); @@ -2213,7 +2552,7 @@ static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, if( resetvbo ) pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(offset + offsetof( vbovertex_t, gl_tc ))); // apply scale pglMatrixMode( GL_TEXTURE ); @@ -2223,93 +2562,238 @@ static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, // draw #if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes if( pglDrawRangeElements ) - pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, indexlen, GL_UNSIGNED_SHORT, indexarray ); + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, indexlen, GL_VBOINDEX_TYPE, indexarray ); else #endif - pglDrawElements( GL_TRIANGLES, indexlen, GL_UNSIGNED_SHORT, indexarray ); + pglDrawElements( GL_TRIANGLES, indexlen, GL_VBOINDEX_TYPE, indexarray ); // restore state pglLoadIdentity(); + /* pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); pglDisable( GL_BLEND ); - GL_Bind( XASH_TEXTURE1, mtst.lm ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) ); + //GL_Bind( XASH_TEXTURE1, mtst.lm ); + //pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );;*/ GL_SelectTexture( XASH_TEXTURE1 ); pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_BLEND ); + vboarray.astate = VBO_ARRAY_NONE; + vboarray.tstate = VBO_TEXTURE_NONE; + vboarray.lstate = VBO_LIGHTMAP_NONE; if( resetvbo ) - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + R_SetupVBOArrayDlight( vbo, tex ); + else + R_SetupVBOArrayStatic( vbo, true, true ); } } -/* -===================== -R_DrawLightmappedVBO -Draw array for given vbotexture_t. build and draw dynamic lightmaps if present -===================== -*/ -static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap, qboolean skiplighting ) +#define MINIMIZE_UPLOAD +#define DISCARD_DLIGHTS + + +static void R_DrawDlightedDecals( vboarray_t *vbo, msurface_t *newsurf, msurface_t *surf, int decalcount, texture_t *texture ) { + msurface_t *decalsurf; + decal_t *pdecal; + int decali = 0; + + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + + R_SetupVBOArrayDecalDlight( decalcount ); + + for( decalsurf = newsurf; ( decali < decalcount ) && (!surf ||( decalsurf != surf )); decalsurf = decalsurf->info->lightmapchain ) + { + for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext ) + { + gl_texture_t *glt; + + if( !pdecal->texture ) + continue; + + glt = R_GetTexture( pdecal->texture ); + + GL_Bind( mtst.tmu_gl, pdecal->texture ); + + // normal HL decal with alpha-channel + if( glt->flags & TF_HAS_ALPHA ) + { + // draw transparent decals with GL_MODULATE + if( glt->fogParams[3] > 230 ) + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + // color decal like detail texture. Base color is 127 127 127 + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + + pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] ); + decali++; + } + newsurf = surf; + + } + +#if SPARSE_DECALS_UPLOAD + if( vbos.decal_dlight_vbo ) + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB ); +#endif + + // restore states pointers for next dynamic lightmap + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); + R_SetDecalMode( false ); +} + +static void R_FlushDlights( vboarray_t *vbo, int min_index, int max_index, int dlightindex, vboindex_t *dlightarray ) +{ + if( max_index == 0 ) + return; + if( vbos.dlight_vbo ) + { +#ifndef MINIMIZE_UPLOAD + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), vbos.dlight_tc + min_index, GL_STREAM_DRAW_ARB ); +#endif + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index* sizeof( vbovertex_t ) + offsetof(vbovertex_t,pos)) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) ); + if( mtst.details_enabled && mtst.tmu_dt != -1 ) + { + GL_SelectTexture( mtst.tmu_dt ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) ); + } + + } + //GL_SelectTexture( mtst.tmu_lm ); + GL_Bind( mtst.tmu_lm, tr.dlightTexture ); +#ifdef DISCARD_DLIGHTS + pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, BLOCK_SIZE, BLOCK_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 ); +#endif + LM_UploadDynamicBlock(); #if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes if( pglDrawRangeElements ) - pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + pglDrawRangeElements( GL_TRIANGLES, min_index, max_index, dlightindex, GL_VBOINDEX_TYPE, dlightarray ); else #endif - pglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); + pglDrawElements( GL_TRIANGLES, dlightindex, GL_VBOINDEX_TYPE, dlightarray ); +} - R_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false ); - - // draw debug lines - if( CVAR_TO_BOOL(gl_wireframe) && !skiplighting ) +static void R_AddSurfaceDecalsDlight( msurface_t *surf, int *pdecalcount ) +{ + decal_t *pdecal; + int decalcount = *pdecalcount; +#if SPARSE_DECALS_UPLOAD + if( decalcount == 0 ) { - R_SetDecalMode( true ); - pglDisable( GL_TEXTURE_2D ); - GL_SelectTexture( XASH_TEXTURE0 ); - pglDisable( GL_TEXTURE_2D ); - pglDisable( GL_DEPTH_TEST ); -#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes - if( pglDrawRangeElements ) - pglDrawRangeElements( GL_LINES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); - else + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB ); + } #endif - pglDrawElements( GL_LINES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray ); - pglEnable( GL_DEPTH_TEST ); - pglEnable( GL_TEXTURE_2D ); - GL_SelectTexture( XASH_TEXTURE1 ); - pglEnable( GL_TEXTURE_2D ); - R_SetDecalMode( false ); - } - //Msg( "%d %d %d\n", vbo->array_len, vbotex->len, lightmap ); - if( skiplighting ) + for( pdecal = surf->pdecals; pdecal; pdecal = pdecal->pnext ) { - vbotex->curindex = 0; - vbotex->dlightchain = NULL; - return; - } + int decalindex = pdecal - &gDecalPool[0]; + int numVerts = vbos.decaldata->decals[decalindex].numVerts; + int i; + if( numVerts == -1 ) + { + // build decal array + float *v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts ); + + for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) + { + VectorCopy( v, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = v[3]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = v[4]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = v[5] - ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); + } + } + else + { + // copy from vbo + for( i = 0; i < numVerts; i++ ) + { + VectorCopy( vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].pos, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[0]; + vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[1]; + vbos.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 ); + vbos.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 ); + } + } +#if SPARSE_DECALS_UPLOAD + if( vbos.dlight_vbo ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); + pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * decalcount * DECAL_VERTS_MAX, sizeof( vbovertex_t )* numVerts, vbos.decal_dlight + decalcount * DECAL_VERTS_MAX ); + } +#endif + + vbos.decal_numverts[decalcount] = numVerts; + decalcount++; + } + *pdecalcount = decalcount; +} + + +static void R_DrawVBODlights( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap ) +{ // draw dlights and dlighted decals if( vbotex->dlightchain ) { - unsigned short *dlightarray = vbos.dlight_index; // preallocated array + vboindex_t *dlightarray = vbos.dlight_index; // preallocated array unsigned int dlightindex = 0; msurface_t *surf, *newsurf; int decalcount = 0; + int min_index = 65536; + int max_index = 0; - - GL_Bind( mtst.tmu_lm, tr.dlightTexture ); - - // replace lightmap texcoord array by dlight array - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); - if( vbos.dlight_vbo ) - pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 ); - else - pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc ); + R_SetupVBOArrayDlight( vbo, texture ); // clear the block LM_InitBlock(); + + if( vbos.dlight_vbo ) + { + // calculate minimum indexbase + for( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain ) + { + uint indexbase = vbos.surfdata[((char*)surf - (char*)WORLDMODEL->surfaces) / sizeof( *surf )].startindex; + if(min_index > indexbase) + min_index = indexbase; +#ifdef MINIMIZE_UPLOAD + if( max_index < indexbase + surf->polys->numverts ) + max_index = indexbase + surf->polys->numverts; +#endif + } +#ifdef MINIMIZE_UPLOAD + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB ); +#endif + } + else + { + min_index = 0; + max_index = vbo->array_len; + } + // accumulate indexes for every dlighted surface until dlight block full for( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain ) { @@ -2337,102 +2821,30 @@ static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture } else { + + // out of free block space. Draw all generated index array and clear it // upload already generated block - LM_UploadDynamicBlock(); -#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes - if( pglDrawRangeElements ) - pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); - else + R_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray ); + + R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) ); +#ifdef MINIMIZE_UPLOAD + // invalidate buffer to prevent blocking on SubData + if( vbos.dlight_vbo ) + { + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB ); + } +#else + if( vbos.dlight_vbo ) + max_index = 0; #endif - pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); // draw decals that lighted with this lightmap if( decalcount ) - { - msurface_t *decalsurf; - int decali = 0; - - pglDepthMask( GL_FALSE ); - pglEnable( GL_BLEND ); - pglEnable( GL_POLYGON_OFFSET_FILL ); - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglDisable( GL_ALPHA_TEST ); - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); - R_SetDecalMode( true ); - if( vbos.decal_dlight_vbo ) - { - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) ); - } - else - { - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos); - } - - for( decalsurf = newsurf; ( decali < decalcount ) && ( decalsurf != surf ); decalsurf = decalsurf->info->lightmapchain ) - { - for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext ) - { - gl_texture_t *glt; - - if( !pdecal->texture ) - continue; - - glt = R_GetTexture( pdecal->texture ); - - GL_Bind( mtst.tmu_gl, pdecal->texture ); - - // normal HL decal with alpha-channel - if( glt->flags & TF_HAS_ALPHA ) - { - // draw transparent decals with GL_MODULATE - if( glt->fogParams[3] > 230 ) - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - } - else - { - // color decal like detail texture. Base color is 127 127 127 - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); - } - - pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] ); - decali++; - } - newsurf = surf; - - } - - // restore states pointers for next dynamic lightmap - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglDepthMask( GL_TRUE ); - pglDisable( GL_BLEND ); - pglDisable( GL_POLYGON_OFFSET_FILL ); - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglEnable( GL_ALPHA_TEST ); - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); - R_SetDecalMode( false ); - GL_SelectTexture( mtst.tmu_lm ); - if( vbos.dlight_vbo ) - pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 ); - else - pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc ); - - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); - R_SetupVBOTexture( texture, 0 ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); - - decalcount = 0; - } + R_DrawDlightedDecals( vbo, newsurf, surf, decalcount, texture ); + decalcount = 0; + R_SetupVBOArrayDlight( vbo, texture ); // clear the block LM_InitBlock(); @@ -2440,7 +2852,7 @@ static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture // try upload the block now if( !LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t )) - gEngfuncs.Host_Error( "AllocBlock: full\n" ); + gEngfuncs.Host_Error( "%s: full\n", __func__ ); base = gl_lms.lightmap_buffer; base += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4; @@ -2456,172 +2868,265 @@ static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ ) { - dlightarray[dlightindex++] = indexbase; - dlightarray[dlightindex++] = index - 1; - dlightarray[dlightindex++] = index; + dlightarray[dlightindex++] = indexbase - min_index; + dlightarray[dlightindex++] = index - 1 - min_index; + dlightarray[dlightindex++] = index - min_index; vbos.dlight_tc[index][0] = surf->polys->verts[index - indexbase][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); vbos.dlight_tc[index][1] = surf->polys->verts[index - indexbase][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); } - +#ifndef MINIMIZE_UPLOAD + if( max_index < indexbase + surf->polys->numverts ) + max_index = indexbase + surf->polys->numverts; +#else if( vbos.dlight_vbo ) { pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); - pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * indexbase, sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase ); + pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (indexbase - min_index), sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase ); } +#endif // if surface has decals, build decal array - for( pdecal = surf->pdecals; pdecal; pdecal = pdecal->pnext ) - { - int decalindex = pdecal - &gDecalPool[0]; - int numVerts = vbos.decaldata->decals[decalindex].numVerts; - int i; - - if( numVerts == -1 ) - { - // build decal array - float *v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts ); - - for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) - { - VectorCopy( v, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = v[3]; - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = v[4]; - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = v[5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); - } - } - else - { - // copy from vbo - for( i = 0; i < numVerts; i++ ) - { - VectorCopy( vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].pos, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos ); - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[0]; - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[1]; - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[0] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); - vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[1] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); - } - } - if( vbos.dlight_vbo ) - { - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); - pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * decalcount * DECAL_VERTS_MAX, sizeof( vbovertex_t )* numVerts, vbos.decal_dlight + decalcount * DECAL_VERTS_MAX ); - } - - vbos.decal_numverts[decalcount] = numVerts; - decalcount++; - } + R_AddSurfaceDecalsDlight( surf, &decalcount ); //info->dlight_s = info->dlight_t = 0; } if( dlightindex ) { - // update block - LM_UploadDynamicBlock(); - - // draw remaining array -#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes - if( pglDrawRangeElements ) - pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); - else -#endif - pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray ); - - R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true ); + R_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray ); + R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) ); // draw remaining decals if( decalcount ) { - msurface_t *decalsurf; - int decali = 0; - - pglDepthMask( GL_FALSE ); - pglEnable( GL_BLEND ); - pglEnable( GL_POLYGON_OFFSET_FILL ); - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglDisable( GL_ALPHA_TEST ); - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); - R_SetDecalMode( true ); - if( vbos.decal_dlight_vbo ) - { - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) ); - } - else - { - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos); - } - for( decalsurf = newsurf; decali < decalcount && decalsurf; decalsurf = decalsurf->info->lightmapchain ) - { - decal_t *pdecal; - - for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext ) - { - gl_texture_t *glt; - - if( !pdecal->texture ) - continue; - - glt = R_GetTexture( pdecal->texture ); - - GL_Bind( mtst.tmu_gl, pdecal->texture ); - - // normal HL decal with alpha-channel - if( glt->flags & TF_HAS_ALPHA ) - { - // draw transparent decals with GL_MODULATE - if( glt->fogParams[3] > 230 ) - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - } - else - { - // color decal like detail texture. Base color is 127 127 127 - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); - } - - pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] ); - - decali++; - } - - } - - // reset states - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglDepthMask( GL_TRUE ); - pglDisable( GL_BLEND ); - pglDisable( GL_POLYGON_OFFSET_FILL ); - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglEnable( GL_ALPHA_TEST ); - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - R_SetDecalMode( false ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); - R_SetupVBOTexture( texture, 0 ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + R_DrawDlightedDecals( vbo, newsurf, NULL, decalcount, texture ); } } - // restore static lightmap - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - GL_Bind( mtst.tmu_lm, tr.lightmapTextures[lightmap] ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + if( vbos.dlight_vbo ) + { + // invalidate state to reset vertex array offset + vboarray.astate = VBO_ARRAY_NONE; + vboarray.tstate = VBO_TEXTURE_NONE; + vboarray.lstate = VBO_LIGHTMAP_NONE; + } + R_SetupVBOArrayStatic( vbo, true, true ); + R_SetupVBOTexture( texture, vboarray.itexture ); // prepare to next frame vbotex->dlightchain = NULL; } +} + + +/* +===================== +R_DrawLightmappedVBO + +Draw array for given vbotexture_t. build and draw dynamic lightmaps if present +===================== +*/ +static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap, qboolean skiplighting ) +{ + if( vbotex->curindex ) + { +#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray ); + else +#endif + pglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray ); + + // draw debug lines + if( gl_wireframe.value && !skiplighting ) + { + R_SetDecalMode( true ); + pglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( XASH_TEXTURE0 ); + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); +#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes + if( pglDrawRangeElements ) + pglDrawRangeElements( GL_LINES, 0, vbo->array_len, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray ); + else +#endif + pglDrawElements( GL_LINES, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray ); + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); + GL_SelectTexture( XASH_TEXTURE1 ); + pglEnable( GL_TEXTURE_2D ); + R_SetDecalMode( false ); + } + } + + //Msg( "%d %d %d\n", vbo->array_len, vbotex->len, lightmap ); + if( skiplighting ) + { + vbotex->curindex = 0; + vbotex->dlightchain = NULL; + return; + } + + R_DrawVBODlights( vbo, vbotex, texture, lightmap ); + + R_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false, 0 ); // prepare to next frame vbotex->curindex = 0; } +static void R_SetupVBOArrayDecal( qboolean drawlightmap ) +{ + // prepare for decal draw + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); + // Set pointers to vbodecaldata->decalvbo + if( drawlightmap ) + { + GL_SelectTexture( mtst.tmu_lm ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); + GL_SelectTexture( mtst.tmu_gl ); + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) ); + /*if( mtst.details_enabled && mtst.tmu_dt != -1 ) + { + GL_Bind( mtst.tmu_dt, tr.whiteTexture ); + }*/ + } + R_SetDecalMode( true ); + vboarray.astate = VBO_ARRAY_DECAL; + vboarray.tstate = VBO_TEXTURE_DECAL; + +} + +static void R_SetupVBOArrayDecalDyn( qboolean drawlightmap, float *v ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + pglVertexPointer( 3, GL_FLOAT, VERTEXSIZE * 4, v ); + pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 3 ); + if( drawlightmap ) + { + GL_SelectTexture( mtst.tmu_lm ); + pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 5 ); + } + R_SetDecalMode( true ); + vboarray.astate = VBO_ARRAY_DECAL; + vboarray.tstate = VBO_TEXTURE_DECAL; + +} + +static void R_DrawStaticDecals( vboarray_t *vbo, qboolean drawlightmap, int ilightmap ) +{ + int k = ilightmap; + msurface_t *lightmapchain; + + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + + R_SetupVBOArrayDecal( drawlightmap ); + + // all surfaces having decals and this lightmap + for( lightmapchain = vbos.decaldata->lm[k]; lightmapchain; lightmapchain = lightmapchain->info->lightmapchain ) + { + decal_t *pdecal; + + // all decals of surface + for( pdecal = lightmapchain->pdecals; pdecal; pdecal = pdecal->pnext ) + { + gl_texture_t *glt; + int decalindex = pdecal - &gDecalPool[0]; + + if( !pdecal->texture ) + continue; + + glt = R_GetTexture( pdecal->texture ); + + GL_Bind( mtst.tmu_gl, pdecal->texture ); + + // normal HL decal with alpha-channel + if( glt->flags & TF_HAS_ALPHA ) + { + // draw transparent decals with GL_MODULATE + if( glt->fogParams[3] > 230 ) + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + // color decal like detail texture. Base color is 127 127 127 + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + + if( vbos.decaldata->decals[decalindex].numVerts == -1 ) + { + int numVerts; + float *v; + + v = R_DecalSetupVerts( pdecal, lightmapchain, pdecal->texture, &numVerts ); + + // to many verts to keep in sparse array, so build it now + R_SetupVBOArrayDecalDyn( drawlightmap, v ); + + pglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts ); + + R_SetupVBOArrayDecal( drawlightmap ); + } + else // just draw VBO + pglDrawArrays( GL_TRIANGLE_FAN, decalindex * DECAL_VERTS_CUT, vbos.decaldata->decals[decalindex].numVerts ); + } + } + + // prepare for next frame + vbos.decaldata->lm[k] = NULL; + + // prepare for next texture + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + + R_SetupVBOArrayStatic( vbo, drawlightmap, true ); + + if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); +} + +static void R_ClearVBOState( qboolean drawlightmap, qboolean drawtextures ) +{ + // restore states + R_DisableDetail(); + + if( drawlightmap ) + { + // reset states + GL_Bind( XASH_TEXTURE1, tr.defaultTexture ); // force reset tmu in case we have only one lightmap + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisable( GL_TEXTURE_2D ); + if( drawtextures ) + { + GL_SelectTexture( XASH_TEXTURE0 ); + pglEnable( GL_TEXTURE_2D ); + } + } + + if( drawtextures ) + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + + pglDisableClientState( GL_VERTEX_ARRAY ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + + vboarray.astate = VBO_ARRAY_NONE; + vboarray.tstate = VBO_TEXTURE_NONE; + vboarray.lstate = VBO_LIGHTMAP_NONE; + vboarray.itexture = 0; + mtst.lm = 0; +} + + /* ===================== R_DrawVBO @@ -2636,35 +3141,14 @@ void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) int k; vboarray_t *vbo = vbos.arraylist; - if( !CVAR_TO_BOOL( r_vbo ) ) + if( !R_HasGeneratedVBO() || !R_HasEnabledVBO() ) return; - // bind array - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - pglEnableClientState( GL_VERTEX_ARRAY ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); - - // setup multitexture - if( drawtextures ) - { - GL_SelectTexture( mtst.tmu_gl = XASH_TEXTURE0 ); - pglEnable( GL_TEXTURE_2D ); - pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); - } - - if( drawlightmap ) - { - // set lightmap texenv - GL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 ); - pglEnable( GL_TEXTURE_2D ); - pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - R_SetLightmap(); - } + GL_SetupFogColorForSurfacesEx( 1, 0.5f, false ); + R_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures ); mtst.skiptexture = !drawtextures; - mtst.tmu_dt = glConfig.max_texture_units > 2? XASH_TEXTURE2:-1; + mtst.tmu_dt = glConfig.max_texture_units > 2 && r_vbo_detail.value == 2 ? XASH_TEXTURE2 : -1; // setup limits if( vbos.minlightmap > vbos.minarraysplit_lm ) @@ -2683,7 +3167,6 @@ void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) for( k = vbos.minlightmap; k < vbos.maxlightmap; k++ ) { int j; - msurface_t *lightmapchain; if( drawlightmap ) { @@ -2697,7 +3180,7 @@ void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) if( !vbotex->vboarray ) continue; - // ASSERT( vbotex->vboarray == vbo ); + // Assert( vbotex->vboarray == vbo ); if( vbotex->vboarray != vbo ) continue; @@ -2716,167 +3199,40 @@ void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) vbotex = vbotex->next; vbo = vbo->next; + vboarray.astate = VBO_ARRAY_NONE; // invalidate + vboarray.tstate = VBO_TEXTURE_NONE; + vboarray.lstate = VBO_LIGHTMAP_NONE; - // bind new vertex and index arrays - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); - - // update texcoord pointers if( drawtextures ) { - tex = R_SetupVBOTexture( tex, 0 ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); + tex = R_SetupVBOTexture( tex, j ); } + // update texcoord pointers + R_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures ); + if( drawlightmap ) { GL_Bind( mtst.tmu_lm, tr.lightmapTextures[k] ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); } // draw new array if( (vbotex->curindex || vbotex->dlightchain) ) R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap ); + vboarray.astate = VBO_ARRAY_NONE; // invalidate + vboarray.tstate = VBO_TEXTURE_NONE; } } if( drawtextures && drawlightmap && vbos.decaldata->lm[k] ) { - // prepare for decal draw - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); - pglDepthMask( GL_FALSE ); - pglEnable( GL_BLEND ); - pglEnable( GL_POLYGON_OFFSET_FILL ); - - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglDisable( GL_ALPHA_TEST ); - - R_SetDecalMode( true ); - - // Set pointers to vbodecaldata->decalvbo - if( drawlightmap ) - { - GL_SelectTexture( mtst.tmu_lm ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) ); - } - - // all surfaces having decals and this lightmap - for( lightmapchain = vbos.decaldata->lm[k]; lightmapchain; lightmapchain = lightmapchain->info->lightmapchain ) - { - decal_t *pdecal; - - // all decals of surface - for( pdecal = lightmapchain->pdecals; pdecal; pdecal = pdecal->pnext ) - { - gl_texture_t *glt; - int decalindex = pdecal - &gDecalPool[0]; - - if( !pdecal->texture ) - continue; - - glt = R_GetTexture( pdecal->texture ); - - GL_Bind( mtst.tmu_gl, pdecal->texture ); - - // normal HL decal with alpha-channel - if( glt->flags & TF_HAS_ALPHA ) - { - // draw transparent decals with GL_MODULATE - if( glt->fogParams[3] > 230 ) - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - } - else - { - // color decal like detail texture. Base color is 127 127 127 - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); - } - - if( vbos.decaldata->decals[decalindex].numVerts == -1 ) - { - int numVerts; - float *v; - - v = R_DecalSetupVerts( pdecal, lightmapchain, pdecal->texture, &numVerts ); - - // to many verts to keep in sparse array, so build it now - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); - pglVertexPointer( 3, GL_FLOAT, VERTEXSIZE * 4, v ); - pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 3 ); - if( drawlightmap ) - { - GL_SelectTexture( mtst.tmu_lm ); - pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 5 ); - } - - pglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts ); - - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); - GL_SelectTexture( mtst.tmu_gl ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) ); - } - else // just draw VBO - pglDrawArrays( GL_TRIANGLE_FAN, decalindex * DECAL_VERTS_CUT, vbos.decaldata->decals[decalindex].numVerts ); - } - } - - // prepare for next frame - vbos.decaldata->lm[k] = NULL; - - // prepare for next texture - pglDepthMask( GL_TRUE ); - pglDisable( GL_BLEND ); - pglDisable( GL_POLYGON_OFFSET_FILL ); - - // restore vbo - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); - pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); - - // restore bump if needed - R_SetDecalMode( false ); - - // restore texture - GL_SelectTexture( mtst.tmu_gl ); - pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); - - // restore lightmap - GL_SelectTexture( mtst.tmu_lm ); - pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) ); - - if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - pglEnable( GL_ALPHA_TEST ); + R_DrawStaticDecals( vbo, drawlightmap, k ); } if( !drawtextures || !drawlightmap ) vbos.decaldata->lm[k] = NULL; } - // ASSERT( !vbo->next ); - - // restore states - R_DisableDetail(); - - if( drawlightmap ) - { - // reset states - GL_SelectTexture( XASH_TEXTURE1 ); - pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); - pglDisable( GL_TEXTURE_2D ); - if( drawtextures ) - { - GL_SelectTexture( XASH_TEXTURE0 ); - pglEnable( GL_TEXTURE_2D ); - } - } - - if( drawtextures ) - pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + // Assert( !vbo->next ); + R_ClearVBOState( drawlightmap, drawtextures ); mtst.details_enabled = false; @@ -2884,137 +3240,79 @@ void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures ) vbos.maxlightmap = 0; vbos.mintexture = INT_MAX; vbos.maxtexture = 0; - - pglDisableClientState( GL_VERTEX_ARRAY ); - pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); -} - -/* -================ -R_CheckLightMap - -update surface's lightmap if needed and return true if it is dynamic -================ -*/ -static qboolean R_CheckLightMap( msurface_t *fa ) -{ - int maps; - qboolean is_dynamic = false; - - // check for lightmap modification - for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) - { - if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] ) - { - is_dynamic = true; - break; - } - } - - // already up to date - if( !is_dynamic && ( fa->dlightframe != tr.framecount || maps == MAXLIGHTMAPS ) ) - return false; - - // build lightmap - if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 ) && ( fa->dlightframe != tr.framecount )) - { - byte temp[132*132*4]; - int smax, tmax; - int sample_size; - mextrasurf_t *info; - - info = fa->info; - sample_size = gEngfuncs.Mod_SampleSizeForFace( fa ); - smax = ( info->lightextents[0] / sample_size ) + 1; - tmax = ( info->lightextents[1] / sample_size ) + 1; - - if( smax < 132 && tmax < 132 ) - { - R_BuildLightMap( fa, temp, smax * 4, true ); - } - else - { - smax = Q_min( smax, 132 ); - tmax = Q_min( tmax, 132 ); - //Host_MapDesignError( "R_RenderBrushPoly: bad surface extents: %d %d", fa->extents[0], fa->extents[1] ); - memset( temp, 255, sizeof( temp ) ); - } - - R_SetCacheState( fa ); -#ifdef XASH_WES - GL_Bind( XASH_TEXTURE1, tr.lightmapTextures[fa->lightmaptexturenum] ); - - pglTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); -#else - GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] ); -#endif - - pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, - GL_RGBA, GL_UNSIGNED_BYTE, temp ); -#ifdef XASH_WES - GL_SelectTexture( XASH_TEXTURE0 ); -#endif - } - // add to dynamic chain - else - return true; - - // updated - return false; } qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmap ) { - if( CVAR_TO_BOOL(r_vbo) && vbos.surfdata[surf - WORLDMODEL->surfaces].vbotexture ) - { - // find vbotexture_t assotiated with this surface - int idx = surf - WORLDMODEL->surfaces; - vbotexture_t *vbotex = vbos.surfdata[idx].vbotexture; - int texturenum = vbos.surfdata[idx].texturenum; + const int idx = surf - WORLDMODEL->surfaces; + vbotexture_t *vbotex; + int texturenum; - if( !surf->polys ) - return true; + if( !R_HasGeneratedVBO() || !R_HasEnabledVBO( )) + return false; - if( vbos.maxlightmap < surf->lightmaptexturenum + 1 ) - vbos.maxlightmap = surf->lightmaptexturenum + 1; - if( vbos.minlightmap > surf->lightmaptexturenum ) - vbos.minlightmap = surf->lightmaptexturenum; - if( vbos.maxtexture < texturenum + 1 ) - vbos.maxtexture = texturenum + 1; - if( vbos.mintexture > texturenum ) - vbos.mintexture = texturenum; + // find vbotexture_t assotiated with this surface + vbotex = vbos.surfdata[idx].vbotexture; + texturenum = vbos.surfdata[idx].texturenum; - buildlightmap &= !CVAR_TO_BOOL( r_fullbright ) && !!WORLDMODEL->lightdata; + if( !vbotex ) + return false; - if( buildlightmap && R_CheckLightMap( surf ) ) - { - // every vbotex has own lightmap chain (as we sorted if by textures to use multitexture) - surf->info->lightmapchain = vbotex->dlightchain; - vbotex->dlightchain = surf; - } - else - { - uint indexbase = vbos.surfdata[idx].startindex; - uint index; - - // GL_TRIANGLE_FAN: 0 1 2 0 2 3 0 3 4 ... - for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ ) - { - vbotex->indexarray[vbotex->curindex++] = indexbase; - vbotex->indexarray[vbotex->curindex++] = index - 1; - vbotex->indexarray[vbotex->curindex++] = index; - } - - // if surface has decals, add it to decal lightmapchain - if( surf->pdecals ) - { - surf->info->lightmapchain = vbos.decaldata->lm[vbotex->lightmaptexturenum]; - vbos.decaldata->lm[vbotex->lightmaptexturenum] = surf; - } - } + if( !surf->polys ) return true; + + if( vbos.maxlightmap < surf->lightmaptexturenum + 1 ) + vbos.maxlightmap = surf->lightmaptexturenum + 1; + if( vbos.minlightmap > surf->lightmaptexturenum ) + vbos.minlightmap = surf->lightmaptexturenum; + if( vbos.maxtexture < texturenum + 1 ) + vbos.maxtexture = texturenum + 1; + if( vbos.mintexture > texturenum ) + vbos.mintexture = texturenum; + + buildlightmap &= !r_fullbright->value && !!WORLDMODEL->lightdata; + + if( surf->texinfo != NULL ) + { + // fullbright textures are rare, no sense to build VBO for them + R_RenderFullbrightForSurface( surf, surf->texinfo->texture ); + + // draw details in regular way + if( r_vbo_detail.value ) + R_RenderDetailsForSurface( surf, surf->texinfo->texture ); } - return false; + + if( buildlightmap && R_CheckLightMap( surf )) + { + // every vbotex has own lightmap chain (as we sorted if by textures to use multitexture) + surf->info->lightmapchain = vbotex->dlightchain; + vbotex->dlightchain = surf; + } + else + { + uint indexbase = vbos.surfdata[idx].startindex; + uint index; + + // GL_TRIANGLE_FAN: 0 1 2 0 2 3 0 3 4 ... + for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ ) + { + vbotex->indexarray[vbotex->curindex++] = indexbase; + vbotex->indexarray[vbotex->curindex++] = index - 1; + vbotex->indexarray[vbotex->curindex++] = index; + } + + // if surface has decals, add it to decal lightmapchain + if( surf->pdecals ) + { + surf->info->lightmapchain = vbos.decaldata->lm[vbotex->lightmaptexturenum]; + vbos.decaldata->lm[vbotex->lightmaptexturenum] = surf; + } + } + + // now this path does not draw wapred surfaces, so count it as one poly + r_stats.c_world_polys++; + + return true; } /* @@ -3029,13 +3327,16 @@ qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmap ) R_RecursiveWorldNode ================ */ -void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) +static void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) { int i, clipped; msurface_t *surf, **mark; mleaf_t *pleaf; int c, side; float dot; + mnode_t *children[2]; + int numsurfaces, firstsurface; + loc0: if( node->contents == CONTENTS_SOLID ) return; // hit a solid leaf @@ -3043,7 +3344,7 @@ loc0: if( node->visframe != tr.visframecount ) return; - if( clipflags && !CVAR_TO_BOOL( r_nocull )) + if( clipflags && !r_nocull.value ) { for( i = 0; i < 6; i++ ) { @@ -3090,10 +3391,14 @@ loc0: side = (dot >= 0.0f) ? 0 : 1; // recurse down the children, front side first - R_RecursiveWorldNode( node->children[side], clipflags ); + node_children( children, node, WORLDMODEL ); + R_RecursiveWorldNode( children[side], clipflags ); + + firstsurface = node_firstsurface( node, WORLDMODEL ); + numsurfaces = node_numsurfaces( node, WORLDMODEL ); // draw stuff - for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ ) + for( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ ) { if( R_CullSurface( surf, &RI.frustum, clipflags )) continue; @@ -3112,7 +3417,7 @@ loc0: } // recurse down the back side - node = node->children[!side]; + node = children[!side]; goto loc0; } @@ -3123,7 +3428,7 @@ R_CullNodeTopView cull node by user rectangle (simple scissor) ================ */ -qboolean R_CullNodeTopView( mnode_t *node ) +static qboolean R_CullNodeTopView( mnode_t *node ) { vec2_t delta, size; vec3_t center, half; @@ -3181,20 +3486,23 @@ static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) R_DrawWorldTopView ================ */ -void R_DrawWorldTopView( mnode_t *node, uint clipflags ) +static void R_DrawWorldTopView( mnode_t *node, uint clipflags ) { int i, c, clipped; msurface_t *surf; do { + mnode_t *children[2]; + int numsurfaces, firstsurface; + if( node->contents == CONTENTS_SOLID ) return; // hit a solid leaf if( node->visframe != tr.visframecount ) return; - if( clipflags && !r_nocull->value ) + if( clipflags && !r_nocull.value ) { for( i = 0; i < 6; i++ ) { @@ -3221,7 +3529,10 @@ void R_DrawWorldTopView( mnode_t *node, uint clipflags ) } // draw stuff - for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ ) + numsurfaces = node_numsurfaces( node, WORLDMODEL ); + firstsurface = node_firstsurface( node, WORLDMODEL ); + + for( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ ) { // don't process the same surface twice if( surf->visframe == tr.framecount ) @@ -3240,9 +3551,9 @@ void R_DrawWorldTopView( mnode_t *node, uint clipflags ) } // recurse down both children, we don't care the order... - R_DrawWorldTopView( node->children[0], clipflags ); - node = node->children[1]; - + node_children( children, node, WORLDMODEL ); + R_DrawWorldTopView( children[0], clipflags ); + node = children[1]; } while( node ); } @@ -3251,14 +3562,14 @@ void R_DrawWorldTopView( mnode_t *node, uint clipflags ) R_DrawTriangleOutlines ============= */ -void R_DrawTriangleOutlines( void ) +static void R_DrawTriangleOutlines( void ) { int i, j; msurface_t *surf; - glpoly_t *p; + glpoly2_t *p; float *v; - if( !gl_wireframe->value ) + if( !gl_wireframe.value ) return; pglDisable( GL_TEXTURE_2D ); @@ -3314,9 +3625,11 @@ void R_DrawWorld( void ) // paranoia issues: when gl_renderer is "0" we need have something valid for currententity // to prevent crashing until HeadShield drawing. - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - RI.currentmodel = RI.currententity->model; + RI.currententity = CL_GetEntityByIndex( 0 ); + if( !RI.currententity ) + return; + RI.currentmodel = RI.currententity->model; if( !RI.drawWorld || RI.onlyClientDraw ) return; @@ -3341,7 +3654,6 @@ void R_DrawWorld( void ) r_stats.t_world_node = end - start; start = gEngfuncs.pfnTime(); - R_DrawVBO( !CVAR_TO_BOOL(r_fullbright) && !!WORLDMODEL->lightdata, true ); R_DrawTextureChains(); @@ -3351,7 +3663,7 @@ void R_DrawWorld( void ) GL_ResetFogColor(); R_BlendLightmaps(); R_RenderFullbrights(); - R_RenderDetails(); + R_RenderDetails( R_HasEnabledVBO() ? 2 : 3 ); if( skychain ) R_DrawSkyBox(); @@ -3365,7 +3677,7 @@ void R_DrawWorld( void ) R_DrawTriangleOutlines (); - R_DrawWorldHull(); + gEngfuncs.R_DrawWorldHull(); } /* @@ -3386,10 +3698,10 @@ void R_MarkLeaves( void ) if( !RI.drawWorld ) return; - if( FBitSet( r_novis->flags, FCVAR_CHANGED ) || tr.fResetVis ) + if( FBitSet( r_novis.flags, FCVAR_CHANGED ) || tr.fResetVis ) { // force recalc viewleaf - ClearBits( r_novis->flags, FCVAR_CHANGED ); + ClearBits( r_novis.flags, FCVAR_CHANGED ); tr.fResetVis = false; RI.viewleaf = NULL; } @@ -3419,12 +3731,12 @@ void R_MarkLeaves( void ) // development aid to let you run around // and see exactly where the pvs ends - if( r_lockpvs->value ) return; + if( r_lockpvs.value ) return; RI.oldviewleaf = RI.viewleaf; tr.visframecount++; - if( r_novis->value || RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata ) + if( r_novis.value || RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata ) novis = true; gEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis ); @@ -3451,7 +3763,7 @@ void R_MarkLeaves( void ) GL_CreateSurfaceLightmap ======================== */ -void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel ) +static void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel ) { int smax, tmax; int sample_size; @@ -3474,7 +3786,7 @@ void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel ) LM_InitBlock(); if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t )) - gEngfuncs.Host_Error( "AllocBlock: full\n" ); + gEngfuncs.Host_Error( "%s: full\n", __func__ ); } surf->lightmaptexturenum = gl_lms.current_lightmap_texture; @@ -3502,9 +3814,6 @@ void GL_RebuildLightmaps( void ) if( !ENGINE_GET_PARM( PARM_CLIENT_ACTIVE ) ) return; // wait for worldmodel - ClearBits( vid_brightness->flags, FCVAR_CHANGED ); - ClearBits( vid_gamma->flags, FCVAR_CHANGED ); - // release old lightmaps for( i = 0; i < MAX_LIGHTMAPS; i++ ) { @@ -3516,13 +3825,13 @@ void GL_RebuildLightmaps( void ) gl_lms.current_lightmap_texture = 0; // setup all the lightstyles - CL_RunLightStyles(); + CL_RunLightStyles((lightstyle_t *)ENGINE_GET_PARM( PARM_GET_LIGHTSTYLES_PTR )); LM_InitBlock(); - for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ ) + for( i = 0; i < gp_cl->nummodels; i++ ) { - if(( m = gEngfuncs.pfnGetModelByIndex( i + 1 )) == NULL ) + if(( m = CL_ModelHandle( i + 1 )) == NULL ) continue; if( m->name[0] == '*' || m->type != mod_brush ) @@ -3550,7 +3859,7 @@ with all the surfaces from all brush models */ void GL_BuildLightmaps( void ) { - int i, j; + int i, j, nColinElim = 0; model_t *m; // release old lightmaps @@ -3564,7 +3873,7 @@ void GL_BuildLightmaps( void ) memset( &RI, 0, sizeof( RI )); // update the lightmap blocksize - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_LARGE_LIGHTMAPS )) + if( FBitSet( gp_host->features, ENGINE_LARGE_LIGHTMAPS ) || tr.world->version == QBSP2_VERSION || r_large_lightmaps.value ) tr.block_size = BLOCK_SIZE_MAX; else tr.block_size = BLOCK_SIZE_DEFAULT; @@ -3574,19 +3883,18 @@ void GL_BuildLightmaps( void ) gl_lms.current_lightmap_texture = 0; tr.modelviewIdentity = false; tr.realframecount = 1; - nColinElim = 0; // setup the texture for dlights R_InitDlightTexture(); // setup all the lightstyles - CL_RunLightStyles(); + CL_RunLightStyles((lightstyle_t *)ENGINE_GET_PARM( PARM_GET_LIGHTSTYLES_PTR )); LM_InitBlock(); - for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ ) + for( i = 0; i < gp_cl->nummodels; i++ ) { - if(( m = gEngfuncs.pfnGetModelByIndex( i + 1 )) == NULL ) + if(( m = CL_ModelHandle( i + 1 )) == NULL ) continue; if( m->name[0] == '*' || m->type != mod_brush ) @@ -3603,7 +3911,7 @@ void GL_BuildLightmaps( void ) if( m->surfaces[j].flags & SURF_DRAWTURB ) continue; - GL_BuildPolygonFromSurface( m, m->surfaces + j ); + nColinElim += GL_BuildPolygonFromSurface( m, m->surfaces + j ); } // clearing visframe @@ -3620,10 +3928,6 @@ void GL_BuildLightmaps( void ) // build lightmaps on the client-side gEngfuncs.drawFuncs->GL_BuildLightmaps( ); } - - // now gamma and brightness are valid - ClearBits( vid_brightness->flags, FCVAR_CHANGED ); - ClearBits( vid_gamma->flags, FCVAR_CHANGED ); } void GL_InitRandomTable( void ) diff --git a/ref/gl/gl_sprite.c b/ref/gl/gl_sprite.c index 006fe135..12040ad3 100644 --- a/ref/gl/gl_sprite.c +++ b/ref/gl/gl_sprite.c @@ -19,8 +19,6 @@ GNU General Public License for more details. #include "studio.h" #include "entity_types.h" -// it's a Valve default value for LoadMapSprite (probably must be power of two) -#define MAPSPRITE_SIZE 128 #define GLARE_FALLOFF 19000.0f char sprite_name[MAX_QPATH]; @@ -46,7 +44,7 @@ R_SpriteLoadFrame upload a single frame ==================== */ -static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num ) +static const byte *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num ) { dspriteframe_t pinframe; mspriteframe_t *pspriteframe; @@ -54,7 +52,7 @@ static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, msp char texname[128]; int bytes = 1; - memcpy( &pinframe, pin, sizeof(dspriteframe_t)); + memcpy( &pinframe, pin, sizeof( dspriteframe_t )); if( sprite_version == SPRITE_VERSION_32 ) bytes = 4; @@ -67,8 +65,21 @@ static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, msp } else { - Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 ); - gl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags ); + // partial HD-textures support + if( Mod_AllowMaterials( )) + { + if( R_SearchForTextureReplacement( texname, sizeof( texname ), sprite_name, "materials/%s/%s%i%i.tga", sprite_name, group_suffix, num / 10, num % 10 )) + { + gl_texturenum = GL_LoadTexture( texname, NULL, 0, r_texFlags ); + R_TextureReplacementReport( sprite_name, gl_texturenum, texname ); + } + } + + if( gl_texturenum == 0 ) + { + Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 ); + gl_texturenum = GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags ); + } } // setup frame description @@ -82,7 +93,7 @@ static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, msp pspriteframe->gl_texturenum = gl_texturenum; *ppframe = pspriteframe; - return ( const dframetype_t* )(( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes ); + return (( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes ); } /* @@ -92,7 +103,7 @@ R_SpriteLoadGroup upload a group frames ==================== */ -static const dframetype_t *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) +static const byte *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) { const dspritegroup_t *pingroup; mspritegroup_t *pspritegroup; @@ -128,9 +139,10 @@ static const dframetype_t *R_SpriteLoadGroup( model_t *mod, const void *pin, msp ptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); } - return (const dframetype_t *)ptemp; + return ptemp; } + /* ==================== Mod_LoadSpriteModel @@ -140,11 +152,11 @@ load sprite model */ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ) { - const dsprite_t *pin; - const short *numi = NULL; - const dframetype_t *pframetype; - msprite_t *psprite; - int i; + const dsprite_t *pin; + const short *numi = NULL; + const byte *pframetype; + msprite_t *psprite; + int i; pin = buffer; psprite = mod->cache.data; @@ -152,7 +164,7 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 ) numi = NULL; else if( pin->version == SPRITE_VERSION_HL ) - numi = (const short *)(void *)((const byte*)buffer + sizeof( dsprite_hl_t )); + numi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t )); r_texFlags = texFlags; sprite_version = pin->version; @@ -164,34 +176,35 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui rgbdata_t *pal; pal = gEngfuncs.FS_LoadImage( "#id.pal", (byte *)&i, 768 ); - pframetype = (const dframetype_t *)(void *)((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1 + pframetype = ((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1 gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data } - else if( *numi == 256 ) + else if( *numi <= 256 ) { const byte *src = (const byte *)(numi+1); rgbdata_t *pal; + size_t pal_bytes = *numi * 3; // install palette switch( psprite->texFormat ) { case SPR_INDEXALPHA: - pal = gEngfuncs.FS_LoadImage( "#gradient.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#gradient.pal", src, pal_bytes ); break; case SPR_ALPHTEST: - pal = gEngfuncs.FS_LoadImage( "#masked.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#masked.pal", src, pal_bytes ); break; default: - pal = gEngfuncs.FS_LoadImage( "#normal.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#texgamma.pal", src, pal_bytes ); break; } - pframetype = (const dframetype_t *)(void *)(src + 768); + pframetype = (const byte *)(src + pal_bytes); gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data } else { - gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be 256)\n", mod->name, *numi ); + gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be less or equal than 256)\n", mod->name, *numi ); return; } @@ -200,22 +213,26 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui for( i = 0; i < mod->numframes; i++ ) { - frametype_t frametype = pframetype->type; + frametype_t frametype; + dframetype_t dframetype; + + memcpy( &dframetype, pframetype, sizeof( dframetype )); + frametype = dframetype.type; psprite->frames[i].type = (spriteframetype_t)frametype; switch( frametype ) { case FRAME_SINGLE: Q_strncpy( group_suffix, "frame", sizeof( group_suffix )); - pframetype = R_SpriteLoadFrame( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; case FRAME_GROUP: Q_strncpy( group_suffix, "group", sizeof( group_suffix )); - pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; case FRAME_ANGLED: Q_strncpy( group_suffix, "angle", sizeof( group_suffix )); - pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; } if( pframetype == NULL ) break; // technically an error @@ -224,127 +241,6 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui if( loaded ) *loaded = true; // done } -/* -==================== -Mod_LoadMapSprite - -Loading a bitmap image as sprite with multiple frames -as pieces of input image -==================== -*/ -void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) -{ - byte *src, *dst; - rgbdata_t *pix, temp; - char texname[128]; - int i, j, x, y, w, h; - int xl, yl, xh, yh; - int linedelta, numframes; - mspriteframe_t *pspriteframe; - msprite_t *psprite; - - if( loaded ) *loaded = false; - Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); - gEngfuncs.Image_SetForceFlags( IL_OVERVIEW ); - pix = gEngfuncs.FS_LoadImage( texname, buffer, size ); - gEngfuncs.Image_ClearForceFlags(); - if( !pix ) return; // bad image or something else - - mod->type = mod_sprite; - r_texFlags = 0; // no custom flags for map sprites - - if( pix->width % MAPSPRITE_SIZE ) - w = pix->width - ( pix->width % MAPSPRITE_SIZE ); - else w = pix->width; - - if( pix->height % MAPSPRITE_SIZE ) - h = pix->height - ( pix->height % MAPSPRITE_SIZE ); - else h = pix->height; - - if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; - if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; - - // resample image if needed - gEngfuncs.Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f ); - - w = h = MAPSPRITE_SIZE; - - // check range - if( w > pix->width ) w = pix->width; - if( h > pix->height ) h = pix->height; - - // determine how many frames we needs - numframes = (pix->width * pix->height) / (w * h); - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); - psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); - mod->cache.data = psprite; // make link to extradata - - psprite->type = SPR_FWD_PARALLEL_ORIENTED; - psprite->texFormat = SPR_ALPHTEST; - psprite->numframes = mod->numframes = numframes; - psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); - - mod->mins[0] = mod->mins[1] = -w / 2; - mod->maxs[0] = mod->maxs[1] = w / 2; - mod->mins[2] = -h / 2; - mod->maxs[2] = h / 2; - - // create a temporary pic - memset( &temp, 0, sizeof( temp )); - temp.width = w; - temp.height = h; - temp.type = pix->type; - temp.flags = pix->flags; - temp.size = w * h * gEngfuncs.Image_GetPFDesc(temp.type)->bpp; - temp.buffer = Mem_Malloc( r_temppool, temp.size ); - temp.palette = NULL; - - // chop the image and upload into video memory - for( i = xl = yl = 0; i < numframes; i++ ) - { - xh = xl + w; - yh = yl + h; - - src = pix->buffer + ( yl * pix->width + xl ) * 4; - linedelta = ( pix->width - w ) * 4; - dst = temp.buffer; - - // cut block from source - for( y = yl; y < yh; y++ ) - { - for( x = xl; x < xh; x++ ) - for( j = 0; j < 4; j++ ) - *dst++ = *src++; - src += linedelta; - } - - // build uinque frame name - Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); - - psprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t )); - pspriteframe = psprite->frames[i].frameptr; - pspriteframe->width = w; - pspriteframe->height = h; - pspriteframe->up = ( h >> 1 ); - pspriteframe->left = -( w >> 1 ); - pspriteframe->down = ( h >> 1 ) - h; - pspriteframe->right = w + -( w >> 1 ); - pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE ); - - xl += w; - if( xl >= pix->width ) - { - xl = 0; - yl += h; - } - } - - gEngfuncs.FS_FreeImage( pix ); - Mem_Free( temp.buffer ); - - if( loaded ) *loaded = true; -} - /* ==================== Mod_UnloadSpriteModel @@ -354,32 +250,31 @@ release sprite model and frames */ void Mod_SpriteUnloadTextures( void *data ) { - msprite_t *psprite; - mspritegroup_t *pspritegroup; - mspriteframe_t *pspriteframe; - int i, j; + msprite_t *psprite = data; + int i; - psprite = data; + if( !data ) + return; - if( psprite ) + // release all textures + for( i = 0; i < psprite->numframes; i++ ) { - // release all textures - for( i = 0; i < psprite->numframes; i++ ) - { - if( psprite->frames[i].type == SPR_SINGLE ) - { - pspriteframe = psprite->frames[i].frameptr; - GL_FreeTexture( pspriteframe->gl_texturenum ); - } - else - { - pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + if( !psprite->frames[i].frameptr ) + continue; - for( j = 0; j < pspritegroup->numframes; j++ ) - { - pspriteframe = pspritegroup->frames[i]; - GL_FreeTexture( pspriteframe->gl_texturenum ); - } + if( psprite->frames[i].type == SPR_SINGLE ) + { + GL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum ); + } + else + { + mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + int j; + + for( j = 0; j < pspritegroup->numframes; j++ ) + { + if( pspritegroup->frames[j] ) + GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum ); } } } @@ -411,7 +306,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) else if( frame >= psprite->numframes ) { if( frame > psprite->numframes ) - gEngfuncs.Con_Printf( S_WARN "R_GetSpriteFrame: no such frame %d (%s)\n", frame, pModel->name ); + gEngfuncs.Con_Printf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, pModel->name ); frame = psprite->numframes - 1; } @@ -428,7 +323,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // are positive, so we don't have to worry about division by zero - targettime = gpGlobals->time - ((int)( gpGlobals->time / fullinterval )) * fullinterval; + targettime = gp_cl->time - ((int)( gp_cl->time / fullinterval )) * fullinterval; for( i = 0; i < (numframes - 1); i++ ) { @@ -457,7 +352,7 @@ NOTE: we using prevblending[0] and [1] for holds interval between frames where are we lerping ================ */ -float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) +static float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) { msprite_t *psprite; mspritegroup_t *pspritegroup; @@ -479,7 +374,7 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, } else if( frame >= psprite->numframes ) { - gEngfuncs.Con_Reportf( S_WARN "R_GetSpriteFrameInterpolant: no such frame %d (%s)\n", frame, ent->model->name ); + gEngfuncs.Con_Reportf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, ent->model->name ); frame = psprite->numframes - 1; } @@ -492,25 +387,25 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 1.0f; } - if( ent->latched.sequencetime < gpGlobals->time ) + if( ent->latched.sequencetime < gp_cl->time ) { if( frame != ent->latched.prevblending[1] ) { ent->latched.prevblending[0] = ent->latched.prevblending[1]; ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } - else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * 11.0f; + else lerpFrac = (gp_cl->time - ent->latched.sequencetime) * 11.0f; } else { ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } } @@ -524,7 +419,7 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, { // reset interpolation on change model ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } @@ -539,7 +434,7 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, numframes = pspritegroup->numframes; fullinterval = pintervals[numframes-1]; jinterval = pintervals[1] - pintervals[0]; - time = gpGlobals->time; + time = gp_cl->time; jtime = 0.0f; // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values @@ -578,25 +473,25 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 1.0f; } - if( ent->latched.sequencetime < gpGlobals->time ) + if( ent->latched.sequencetime < gp_cl->time ) { if( frame != ent->latched.prevblending[1] ) { ent->latched.prevblending[0] = ent->latched.prevblending[1]; ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } - else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * ent->curstate.framerate; + else lerpFrac = (gp_cl->time - ent->latched.sequencetime) * ent->curstate.framerate; } else { ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } } @@ -623,7 +518,7 @@ R_CullSpriteModel Cull sprite model by bbox ================ */ -qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) +static qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) { vec3_t sprite_mins, sprite_maxs; float scale = 1.0f; @@ -664,7 +559,7 @@ static float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, flo if( RP_NORMALPASS( )) { - tr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow->value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE)); + tr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow.value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE)); if(( 1.0f - tr->fraction ) * dist > 8.0f ) return 0.0f; @@ -687,7 +582,7 @@ R_SpriteOccluded Do occlusion test for glow-sprites ================ */ -qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) +static qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) { if( e->curstate.rendermode == kRenderGlow ) { @@ -755,7 +650,7 @@ static qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat ) if( texFormat != SPR_ALPHTEST ) return false; - if( e->curstate.effects & EF_FULLBRIGHT ) + if( FBitSet( e->curstate.effects, EF_FULLBRIGHT )) return false; if( e->curstate.renderamt <= 127 ) @@ -803,7 +698,7 @@ R_DrawSpriteModel */ void R_DrawSpriteModel( cl_entity_t *e ) { - mspriteframe_t *frame, *oldframe; + mspriteframe_t *frame = NULL, *oldframe = NULL; msprite_t *psprite; model_t *model; int i, type; @@ -824,7 +719,7 @@ void R_DrawSpriteModel( cl_entity_t *e ) { cl_entity_t *parent; - parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); + parent = CL_GetEntityByIndex( e->curstate.aiment ); if( parent && parent->model ) { @@ -899,7 +794,8 @@ void R_DrawSpriteModel( cl_entity_t *e ) color2[1] = (float)lightColor.g * ( 1.0f / 255.0f ); color2[2] = (float)lightColor.b * ( 1.0f / 255.0f ); // NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0 - pglAlphaFunc( GL_GREATER, 0.5f ); + // NOTE: make them easier to see with 0.3333, was 0.5 in original + pglAlphaFunc( GL_GREATER, 1.0f / 3.0f ); } if( R_SpriteAllowLerping( e, psprite )) @@ -995,6 +891,7 @@ void R_DrawSpriteModel( cl_entity_t *e ) R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); pglDepthFunc( GL_LEQUAL ); + pglDisable( GL_BLEND ); } if( psprite->facecull == SPR_CULL_NONE ) diff --git a/ref/gl/gl_studio.c b/ref/gl/gl_studio.c index 910dd07b..8b1fbca8 100644 --- a/ref/gl/gl_studio.c +++ b/ref/gl/gl_studio.c @@ -33,9 +33,9 @@ typedef struct } player_model_t; // never gonna change, just shut up const warning -cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; +CVAR_DEFINE_AUTO( r_shadows, "0", 0, "draw ugly shadows" ); -static vec3_t hullcolor[8] = +static const vec3_t hullcolor[8] = { { 1.0f, 1.0f, 1.0f }, { 1.0f, 0.5f, 0.5f }, @@ -103,7 +103,7 @@ typedef struct int numlocallights; int lightage[MAXSTUDIOBONES]; dlight_t *locallight[MAX_LOCALLIGHTS]; - color24 locallightcolor[MAX_LOCALLIGHTS]; + int locallightcolor[MAX_LOCALLIGHTS][3]; vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; float locallightR2[MAX_LOCALLIGHTS]; @@ -121,9 +121,9 @@ typedef struct } studio_draw_state_t; // studio-related cvars -static cvar_t *r_studio_sort_textures; +CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); +CVAR_DEFINE_AUTO( r_studio_drawelements, "1", FCVAR_GLCONFIG, "use glDrawElements for studiomodels" ); static cvar_t *cl_righthand = NULL; -static cvar_t *r_studio_drawelements; static r_studio_interface_t *pStudioDraw; static studio_draw_state_t g_studio; // global studio state @@ -135,7 +135,6 @@ mstudiobodyparts_t *m_pBodyPart; player_info_t *m_pPlayerInfo; studiohdr_t *m_pStudioHeader; float m_flGaitMovement; -int g_iBackFaceCull; int g_nTopColor, g_nBottomColor; // remap colors int g_nFaceFlags, g_nForceFaceFlags; @@ -147,14 +146,14 @@ R_StudioInit */ void R_StudioInit( void ) { - r_studio_sort_textures = gEngfuncs.Cvar_Get( "r_studio_sort_textures", "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); - r_studio_drawelements = gEngfuncs.Cvar_Get( "r_studio_drawelements", "1", FCVAR_GLCONFIG, "use glDrawElements for studiomodels" ); + +#if XASH_PSVITA + // don't do the same array-building work twice since that's what our FFP shim does anyway + gEngfuncs.Cvar_FullSet( "r_studio_drawelements", "0", FCVAR_READ_ONLY ); +#endif Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); - // g-cont. cvar disabled by Valve -// gEngfuncs.Cvar_RegisterVariable( &r_shadows ); - g_studio.interpolate = true; g_studio.framecount = 0; m_fDoRemap = false; @@ -172,14 +171,14 @@ static void R_StudioSetupTimings( void ) if( RI.drawWorld ) { // synchronize with server time - g_studio.time = gpGlobals->time; - g_studio.frametime = gpGlobals->time - gpGlobals->oldtime; + g_studio.time = gp_cl->time; + g_studio.frametime = gp_cl->time - gp_cl->oldtime; } else { // menu stuff - g_studio.time = gpGlobals->realtime; - g_studio.frametime = gpGlobals->frametime; + g_studio.time = gp_host->realtime; + g_studio.frametime = gp_host->frametime; } } @@ -194,7 +193,7 @@ static qboolean R_AllowFlipViewModel( cl_entity_t *e ) { if( cl_righthand && cl_righthand->value > 0 ) { - if( e == gEngfuncs.GetViewModel() ) + if( e == tr.viewent ) return true; } @@ -259,7 +258,7 @@ static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) return true; // visible } -void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) +static void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) { float flWeight0, flWeight1, flWeight2, flWeight3; int i, numbones = 0; @@ -394,7 +393,7 @@ pfnGetPlayerState =============== */ -entity_state_t *R_StudioGetPlayerState( int index ) +static entity_state_t *R_StudioGetPlayerState( int index ) { if( !RI.drawWorld ) return &RI.currententity->curstate; @@ -410,7 +409,7 @@ pfnGetViewEntity */ static cl_entity_t *pfnGetViewEntity( void ) { - return gEngfuncs.GetViewModel(); + return tr.viewent; } /* @@ -422,8 +421,8 @@ pfnGetEngineTimes static void pfnGetEngineTimes( int *framecount, double *current, double *old ) { if( framecount ) *framecount = tr.realframecount; - if( current ) *current = gpGlobals->time; - if( old ) *old = gpGlobals->oldtime; + if( current ) *current = gp_cl->time; + if( old ) *old = gp_cl->oldtime; } /* @@ -525,7 +524,7 @@ StudioPlayerBlend ==================== */ -void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +static void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing *pBlend = (*pPitch * 3.0f); @@ -586,7 +585,7 @@ StudioSetUpTransform ==================== */ -void R_StudioSetUpTransform( cl_entity_t *e ) +static void R_StudioSetUpTransform( cl_entity_t *e ) { vec3_t origin, angles; @@ -594,12 +593,12 @@ void R_StudioSetUpTransform( cl_entity_t *e ) VectorCopy( e->angles, angles ); // interpolate monsters position (moved into UpdateEntityFields by user request) - if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPUTE_STUDIO_LERP )) + if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gp_host->features, ENGINE_COMPUTE_STUDIO_LERP )) { R_StudioLerpMovement( e, g_studio.time, origin, angles ); } - if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) + if( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG )) angles[PITCH] = -angles[PITCH]; // stupid quake bug // don't rotate clients, only aim @@ -621,14 +620,14 @@ StudioEstimateFrame ==================== */ -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ) { double dfdt, f; if( g_studio.interpolate ) { - if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; - else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + if( time < e->curstate.animtime ) dfdt = 0.0; + else dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; } else dfdt = 0; @@ -658,7 +657,7 @@ StudioEstimateInterpolant ==================== */ -float R_StudioEstimateInterpolant( cl_entity_t *e ) +static float R_StudioEstimateInterpolant( cl_entity_t *e ) { float dadt = 1.0f; @@ -671,42 +670,13 @@ float R_StudioEstimateInterpolant( cl_entity_t *e ) return dadt; } -/* -==================== -CL_GetSequenceDuration - -==================== -*/ -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) -{ - studiohdr_t *pstudiohdr; - mstudioseqdesc_t *pseqdesc; - - if( ent->model != NULL && ent->model->type == mod_studio ) - { - pstudiohdr = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, ent->model ); - - if( pstudiohdr ) - { - sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); - pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - - if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) - return (float)pseqdesc->numframes / (float)pseqdesc->fps; - } - } - - return 0.1f; -} - - /* ==================== StudioFxTransform ==================== */ -void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) +static void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) { switch( ent->curstate.renderfx ) { @@ -750,7 +720,7 @@ StudioCalcBoneAdj ==================== */ -void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +static void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) { mstudiobonecontroller_t *pbonecontroller; float value = 0.0f; @@ -815,7 +785,7 @@ StudioCalcRotations ==================== */ -void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +static void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) { int i, frame; float adj[MAXSTUDIOCONTROLLERS]; @@ -846,10 +816,7 @@ void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudiose R_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen ); for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ ) - { - gEngfuncs.R_StudioCalcBoneQuaternion( frame, s, pbone, panim, adj, q[i] ); - gEngfuncs.R_StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); - } + R_StudioCalcBones( frame, s, pbone, panim, adj, pos[i], q[i] ); if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f; @@ -862,7 +829,7 @@ StudioMergeBones ==================== */ -void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) +static void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) { int i, j; mstudiobone_t *pbones; @@ -878,7 +845,7 @@ void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -922,7 +889,7 @@ StudioSetupBones ==================== */ -void R_StudioSetupBones( cl_entity_t *e ) +static void R_StudioSetupBones( cl_entity_t *e ) { float f; mstudiobone_t *pbones; @@ -944,7 +911,7 @@ void R_StudioSetupBones( cl_entity_t *e ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -960,7 +927,7 @@ void R_StudioSetupBones( cl_entity_t *e ) dadt = R_StudioEstimateInterpolant( e ); s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { @@ -971,10 +938,10 @@ void R_StudioSetupBones( cl_entity_t *e ) R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f ); s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); } } @@ -997,7 +964,7 @@ void R_StudioSetupBones( cl_entity_t *e ) R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe ); s = (e->latched.prevseqblending[0]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { @@ -1008,15 +975,15 @@ void R_StudioSetupBones( cl_entity_t *e ) R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe ); s = (e->latched.prevseqblending[0]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); s = (e->latched.prevseqblending[1]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); } } s = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); } else { @@ -1102,7 +1069,7 @@ StudioBuildNormalTable NOTE: m_pSubModel must be set ==================== */ -void R_StudioBuildNormalTable( void ) +static void R_StudioBuildNormalTable( void ) { cl_entity_t *e = RI.currententity; mstudiomesh_t *pmesh; @@ -1153,7 +1120,7 @@ NOTE: m_pSubModel must be set g_studio.verts must be computed ==================== */ -void R_StudioGenerateNormals( void ) +static void R_StudioGenerateNormals( void ) { int v0, v1, v2; vec3_t e0, e1, norm; @@ -1248,7 +1215,7 @@ StudioSetupChrome ==================== */ -void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) +static void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) { float n; @@ -1260,18 +1227,18 @@ void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) vec3_t tmp; // vector pointing at bone in world reference frame VectorNegate( g_studio.chrome_origin, tmp ); - tmp[0] += g_studio.bonestransform[bone][0][3]; - tmp[1] += g_studio.bonestransform[bone][1][3]; - tmp[2] += g_studio.bonestransform[bone][2][3]; + tmp[0] += g_studio.lighttransform[bone][0][3]; + tmp[1] += g_studio.lighttransform[bone][1][3]; + tmp[2] += g_studio.lighttransform[bone][2][3]; VectorNormalize( tmp ); CrossProduct( tmp, RI.vright, chromeupvec ); VectorNormalize( chromeupvec ); - CrossProduct( tmp, chromeupvec, chromerightvec ); + CrossProduct( chromeupvec, tmp, chromerightvec ); VectorNormalize( chromerightvec ); - Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] ); - Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] ); + Matrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromeupvec, g_studio.chromeup[bone] ); + Matrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromerightvec, g_studio.chromeright[bone] ); g_studio.chromeage[bone] = g_studio.framecount; } @@ -1349,9 +1316,9 @@ R_StudioDynamicLight =============== */ -void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) +static void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) { - movevars_t *mv = gEngfuncs.pfnGetMoveVars(); + movevars_t *mv = tr.movevars; vec3_t lightDir, vecSrc, vecEnd; vec3_t origin, dist, finalLight; float add, radius, total; @@ -1387,7 +1354,7 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) msurface_t *psurf = NULL; pmtrace_t trace; - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) + if( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD )) { vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; @@ -1408,13 +1375,13 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) { VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); - light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); - light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); - light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + light.r = mv->skycolor_r; + light.g = mv->skycolor_g; + light.b = mv->skycolor_b; } } - if(( light.r + light.g + light.b ) < 16 ) // TESTTEST + if(( light.r + light.g + light.b ) == 0 ) { colorVec gcolor; float grad[4]; @@ -1462,6 +1429,13 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) } } + if( ent->curstate.renderfx == kRenderFxLightMultiplier && ent->curstate.iuser4 != 10 ) + { + light.r *= ent->curstate.iuser4 / 10.0f; + light.g *= ent->curstate.iuser4 / 10.0f; + light.b *= ent->curstate.iuser4 / 10.0f; + } + VectorSet( finalLight, light.r, light.g, light.b ); ent->cvFloorColor = light; @@ -1473,7 +1447,7 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { - dl = gEngfuncs.GetDynamicLight( lnum ); + dl = &tr.dlights[lnum]; if( dl->die < g_studio.time || !r_dynamic->value ) continue; @@ -1493,15 +1467,15 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) VectorAdd( lightDir, dist, lightDir ); - finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; - finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; - finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; + finalLight[0] += dl->color.r * ( add / 256.0f ); + finalLight[1] += dl->color.g * ( add / 256.0f ); + finalLight[2] += dl->color.b * ( add / 256.0f ); } } if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) add = 0.6f; - else add = 0.9f; + else add = bound( 0.75f, v_direct->value, 1.0f ); VectorScale( lightDir, add, lightDir ); @@ -1533,14 +1507,13 @@ pfnStudioEntityLight =============== */ -void R_StudioEntityLight( alight_t *lightinfo ) +static void R_StudioEntityLight( alight_t *lightinfo ) { int lnum, i, j, k; float minstrength, dist2, f, r2; float lstrength[MAX_LOCALLIGHTS]; cl_entity_t *ent = RI.currententity; vec3_t mid, origin, pos; - dlight_t *el; g_studio.numlocallights = 0; @@ -1556,7 +1529,7 @@ void R_StudioEntityLight( alight_t *lightinfo ) for( lnum = 0; lnum < MAX_ELIGHTS; lnum++ ) { - el = gEngfuncs.GetEntityLight( lnum ); + dlight_t *el = &tr.elights[lnum]; if( el->die < g_studio.time || el->radius <= 0.0f ) continue; @@ -1595,9 +1568,9 @@ void R_StudioEntityLight( alight_t *lightinfo ) if( k != -1 ) { - g_studio.locallightcolor[k].r = gEngfuncs.LightToTexGamma( el->color.r ); - g_studio.locallightcolor[k].g = gEngfuncs.LightToTexGamma( el->color.g ); - g_studio.locallightcolor[k].b = gEngfuncs.LightToTexGamma( el->color.b ); + g_studio.locallightcolor[k][0] = LinearGammaTable( el->color.r << 2 ); + g_studio.locallightcolor[k][1] = LinearGammaTable( el->color.g << 2 ); + g_studio.locallightcolor[k][2] = LinearGammaTable( el->color.b << 2 ); g_studio.locallightR2[k] = r2; g_studio.locallight[k] = el; lstrength[k] = minstrength; @@ -1615,7 +1588,7 @@ R_StudioSetupLighting =============== */ -void R_StudioSetupLighting( alight_t *plight ) +static void R_StudioSetupLighting( alight_t *plight ) { float scale = 1.0f; int i; @@ -1645,7 +1618,7 @@ R_StudioLighting =============== */ -void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) +static void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) { float illum; @@ -1692,7 +1665,8 @@ void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) } illum = Q_min( illum, 255.0f ); - *lv = illum * (1.0f / 255.0f); + + *lv = LightToTexGamma( illum * 4 ) / 1023.0f; } /* @@ -1701,91 +1675,80 @@ R_LightLambert ==================== */ -void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) +static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out ) { vec3_t finalLight; - vec3_t localLight; int i; - VectorCopy( color, finalLight ); + if( !g_studio.numlocallights ) + { + VectorScale( color, 255.0f, out ); + return; + } + + VectorSet( finalLight, 0, 0, 0 ); for( i = 0; i < g_studio.numlocallights; i++ ) { - float r, r2; + float r; - if( tr.fFlipViewModel ) - r = DotProduct( normal, light[i] ); - else r = -DotProduct( normal, light[i] ); + r = DotProduct( normal, light[i] ); + if( likely( !tr.fFlipViewModel )) + r = -r; if( r > 0.0f ) { + vec3_t localLight; + float temp; + if( light[i][3] == 0.0f ) { - r2 = DotProduct( light[i], light[i] ); + float r2 = DotProduct( light[i], light[i] ); if( r2 > 0.0f ) light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); else light[i][3] = 0.0001f; } - localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); - localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); - localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); - VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + temp = r * light[i][3]; - finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); - finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); - finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + VectorAddScalar( g_studio.locallightcolor[i], temp, localLight ); + VectorAdd( finalLight, localLight, finalLight ); } } - out[0] = finalLight[0] * 255; - out[1] = finalLight[1] * 255; - out[2] = finalLight[2] * 255; -} - -static void R_StudioSetColorBegin(short *ptricmds, vec3_t *pstudionorms ) -{ - float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; - rgba_t color; - - if( g_studio.numlocallights ) + if( !VectorIsNull( finalLight )) { - color[3] = tr.blend * 255; - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); - pglColor4ubv( color ); + for( i = 0; i < 3; i++ ) + { + float c = finalLight[i] + LinearGammaTable( color[i] * 1023.0f ); + + if( c > 1023.0f ) + out[i] = 255; + else + out[i] = ScreenGammaTable( c ) >> 2; + } } else { - if( RI.currententity->curstate.rendermode == kRenderTransColor ) - { - color[3] = tr.blend * 255; - VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); - pglColor4ubv( color ); - } - else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + VectorScale( color, 255.0f, out ); } } -static void R_StudioSetColorArray(short *ptricmds, vec3_t *pstudionorms, byte *color ) +static void R_StudioSetColorArray( short *ptricmds, vec3_t *pstudionorms, byte *color ) { float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; color[3] = tr.blend * 255; + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); +} - if( g_studio.numlocallights ) - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); - else - { - if( RI.currententity->curstate.rendermode == kRenderTransColor ) - VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); - else - { - color[0] = lv[0] * 255; - color[1] = lv[1] * 255; - color[2] = lv[2] * 255; - } - } +static void R_StudioSetColorBegin( short *ptricmds, vec3_t *pstudionorms ) +{ + rgba_t color; + + R_StudioSetColorArray( ptricmds, pstudionorms, color ); + pglColor4ubv( color ); } /* @@ -1794,7 +1757,7 @@ R_LightStrength ==================== */ -void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) +static void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) { int i; @@ -1865,7 +1828,7 @@ mstudiotexture_t *R_StudioGetTexture( cl_entity_t *e ) return ptexture; } -void R_StudioSetRenderamt( int iRenderamt ) +static void R_StudioSetRenderamt( int iRenderamt ) { if( !RI.currententity ) return; @@ -1880,9 +1843,9 @@ R_StudioSetCullState sets true for enable backculling (for left-hand viewmodel) =============== */ -void R_StudioSetCullState( int iCull ) +static void R_StudioSetCullState( int iCull ) { - g_iBackFaceCull = iCull; + // This function intentionally does nothing } /* @@ -1892,12 +1855,12 @@ R_StudioRenderShadow just a prefab for render shadow =============== */ -void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) +static void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) { if( !p1 || !p2 || !p3 || !p4 ) return; - if( TriSpriteTexture( gEngfuncs.pfnGetModelByIndex( iSprite ), 0 )) + if( TriSpriteTexture( CL_ModelHandle( iSprite ), 0 )) { TriRenderMode( kRenderTransAlpha ); TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); @@ -1942,9 +1905,8 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { - float *lv; int i; while(( i = *( ptricmds++ ))) @@ -1975,9 +1937,8 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { - float *lv; int i; while(( i = *( ptricmds++ ))) @@ -2007,7 +1968,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { float *lv, *av; int i, idx; @@ -2052,7 +2013,7 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa } -_inline int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) +static int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) { // build in indices if( vertexState++ < 3 ) @@ -2095,7 +2056,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { float *lv; int i; @@ -2138,7 +2099,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { float *lv; int i; @@ -2181,7 +2142,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { float *lv, *av; int i, idx; @@ -2238,7 +2199,7 @@ _inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms } } -_inline void R_StudioDrawArrays( uint startverts, uint startelems ) +static void R_StudioDrawArrays( uint startverts, uint startelems ) { pglEnableClientState( GL_VERTEX_ARRAY ); pglVertexPointer( 3, GL_FLOAT, 12, g_studio.arrayverts ); @@ -2290,8 +2251,7 @@ static void R_StudioDrawPoints( void ) g_studio.numverts = g_studio.numelems = 0; - // safety bounding the skinnum - m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); + m_skinnum = RI.currententity->curstate.skin; ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex); @@ -2301,7 +2261,8 @@ static void R_StudioDrawPoints( void ) pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); - if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); + if( m_skinnum > 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) + pskinref += (m_skinnum * m_pStudioHeader->numskinref); if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) { @@ -2375,7 +2336,7 @@ static void R_StudioDrawPoints( void ) } } - if( r_studio_sort_textures->value && need_sort ) + if( r_studio_sort_textures.value && need_sort ) { // resort opaque and translucent meshes draw order qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare ); @@ -2384,6 +2345,18 @@ static void R_StudioDrawPoints( void ) // NOTE: rewind normals at start pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + // backface culling for left-handed weapons + if( R_AllowFlipViewModel( RI.currententity )) + { + tr.fFlipViewModel = true; + GL_Cull( GL_NONE ); + } + else + { + tr.fFlipViewModel = false; + GL_Cull( GL_FRONT ); + } + for( j = 0; j < m_pSubModel->nummesh; j++ ) { float oldblend = tr.blend; @@ -2422,7 +2395,7 @@ static void R_StudioDrawPoints( void ) R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); - if( CVAR_TO_BOOL(r_studio_drawelements) ) + if( r_studio_drawelements.value ) { if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) R_StudioBuildArrayChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); @@ -2524,7 +2497,7 @@ static void R_StudioDrawAbsBBox( void ) int i; // looks ugly, skip - if( RI.currententity == gEngfuncs.GetViewModel() ) + if( RI.currententity == tr.viewent ) return; if( !R_StudioComputeBBox( p )) @@ -2660,13 +2633,8 @@ R_StudioSetRemapColors */ static void R_StudioSetRemapColors( int newTop, int newBottom ) { - gEngfuncs.CL_AllocRemapInfo( RI.currententity, RI.currentmodel, newTop, newBottom ); - - if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) - { - gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); + if( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom )) m_fDoRemap = true; - } } void R_StudioResetPlayerModels( void ) @@ -2682,8 +2650,11 @@ R_StudioSetupPlayerModel */ static model_t *R_StudioSetupPlayerModel( int index ) { - player_info_t *info = gEngfuncs.pfnPlayerInfo( index ); - player_model_t *state; + player_info_t *info = gEngfuncs.pfnPlayerInfo( index ); + player_model_t *state; + + if( index < 0 || index >= gp_cl->maxclients ) + return NULL; state = &g_studio.player_models[index]; @@ -2699,7 +2670,8 @@ static model_t *R_StudioSetupPlayerModel( int index ) if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); - else state->model = NULL; + else + state->model = NULL; if( !state->model ) state->model = RI.currententity->model; @@ -2724,18 +2696,20 @@ check for texture flags */ int R_GetEntityRenderMode( cl_entity_t *ent ) { - int i, opaque, trans; - mstudiotexture_t *ptexture; - cl_entity_t *oldent; - model_t *model; - studiohdr_t *phdr; + int i, opaque, trans; + mstudiotexture_t *ptexture; + cl_entity_t *oldent; + model_t *model = NULL; + studiohdr_t *phdr; oldent = RI.currententity; RI.currententity = ent; if( ent->player ) // check it for real playermodel model = R_StudioSetupPlayerModel( ent->curstate.number - 1 ); - else model = ent->model; + + if( !model ) + model = ent->model; RI.currententity = oldent; @@ -2797,7 +2771,7 @@ static void R_StudioClientEvents( void ) ClearBits( e->curstate.effects, EF_MUZZLEFLASH ); VectorCopy( e->attachment[0], el->origin ); - el->die = gpGlobals->time + 0.05f; + el->die = gp_cl->time + 0.05f; el->color.r = 255; el->color.g = 192; el->color.b = 64; @@ -2812,8 +2786,8 @@ static void R_StudioClientEvents( void ) if( pseqdesc->numevents == 0 ) return; - end = R_StudioEstimateFrame( e, pseqdesc ); - start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps; + end = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); + start = end - e->curstate.framerate * gp_host->frametime * pseqdesc->fps; pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); if( e->latched.sequencetime == e->curstate.animtime ) @@ -2839,7 +2813,7 @@ R_StudioGetForceFaceFlags =============== */ -int R_StudioGetForceFaceFlags( void ) +static int R_StudioGetForceFaceFlags( void ) { return g_nForceFaceFlags; } @@ -2850,7 +2824,7 @@ R_StudioSetForceFaceFlags =============== */ -void R_StudioSetForceFaceFlags( int flags ) +static void R_StudioSetForceFaceFlags( int flags ) { g_nForceFaceFlags = flags; } @@ -2861,7 +2835,7 @@ pfnStudioSetHeader =============== */ -void R_StudioSetHeader( studiohdr_t *pheader ) +static void R_StudioSetHeader( studiohdr_t *pheader ) { m_pStudioHeader = pheader; m_fDoRemap = false; @@ -2873,7 +2847,7 @@ R_StudioSetRenderModel =============== */ -void R_StudioSetRenderModel( model_t *model ) +static void R_StudioSetRenderModel( model_t *model ) { RI.currentmodel = model; } @@ -2929,7 +2903,7 @@ R_StudioSetChromeOrigin =============== */ -void R_StudioSetChromeOrigin( void ) +static void R_StudioSetChromeOrigin( void ) { VectorCopy( RI.vieworg, g_studio.chrome_origin ); } @@ -3017,7 +2991,7 @@ GL_StudioSetRenderMode set rendermode for studiomodel =============== */ -void GL_StudioSetRenderMode( int rendermode ) +static void GL_StudioSetRenderMode( int rendermode ) { switch( rendermode ) { @@ -3084,7 +3058,7 @@ StudioRenderFinal ==================== */ -void R_StudioRenderFinal( void ) +static void R_StudioRenderFinal( void ) { int i, rendermode; @@ -3170,7 +3144,7 @@ StudioRenderModel ==================== */ -void R_StudioRenderModel( void ) +static void R_StudioRenderModel( void ) { R_StudioSetChromeOrigin(); R_StudioSetForceFaceFlags( 0 ); @@ -3199,7 +3173,7 @@ StudioEstimateGait ==================== */ -void R_StudioEstimateGait( entity_state_t *pplayer ) +static void R_StudioEstimateGait( entity_state_t *pplayer ) { vec3_t est_velocity; float dt; @@ -3255,7 +3229,7 @@ StudioProcessGait ==================== */ -void R_StudioProcessGait( entity_state_t *pplayer ) +static void R_StudioProcessGait( entity_state_t *pplayer ) { mstudioseqdesc_t *pseqdesc; int iBlend; @@ -3338,7 +3312,7 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) m_nPlayerIndex = pplayer->number - 1; - if( m_nPlayerIndex < 0 || m_nPlayerIndex >= ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + if( m_nPlayerIndex < 0 || m_nPlayerIndex >= gp_cl->maxclients ) return 0; RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex ); @@ -3407,20 +3381,21 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) // copy attachments into global entity array if( RI.currententity->index > 0 ) { - cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); } } if( flags & STUDIO_RENDER ) { - if( cl_himodels->value && RI.currentmodel != RI.currententity->model ) + // change body if it's a menu entity + if( cl_himodels->value && ( RI.currentmodel != RI.currententity->model || !RI.drawWorld )) { // show highest resolution multiplayer model RI.currententity->curstate.body = 255; } - if( !( !gpGlobals->developer && ENGINE_GET_PARM( PARM_MAX_CLIENTS ) == 1 ) && ( RI.currentmodel == RI.currententity->model )) + if( !( !gpGlobals->developer && gp_cl->maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model )) RI.currententity->curstate.body = 1; // force helmet lighting.plightvec = dir; @@ -3450,7 +3425,7 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) if( pplayer->weaponmodel ) { cl_entity_t saveent = *RI.currententity; - model_t *pweaponmodel = gEngfuncs.pfnGetModelByIndex( pplayer->weaponmodel ); + model_t *pweaponmodel = CL_ModelHandle( pplayer->weaponmodel ); m_pStudioHeader = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, pweaponmodel ); @@ -3483,7 +3458,7 @@ static int R_StudioDrawModel( int flags ) int result; if( RI.currententity->curstate.renderamt <= 0 || - RI.currententity->curstate.renderamt > ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + RI.currententity->curstate.renderamt > gp_cl->maxclients ) return 0; // get copy of player @@ -3536,7 +3511,7 @@ static int R_StudioDrawModel( int flags ) // copy attachments into global entity array if( RI.currententity->index > 0 ) { - cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); } } @@ -3568,7 +3543,7 @@ static int R_StudioDrawModel( int flags ) R_StudioDrawModelInternal ================= */ -void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) +static void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) { if( !RI.drawWorld ) { @@ -3585,6 +3560,19 @@ void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) } } +static cl_entity_t *R_FindParentEntity( cl_entity_t *e, cl_entity_t **entities, uint num_entities ) +{ + uint i; + + for( i = 0; i < num_entities; i++ ) + { + if( entities[i]->index == e->curstate.aiment ) + return entities[i]; + } + + return NULL; +} + /* ================= R_DrawStudioModel @@ -3601,22 +3589,31 @@ void R_DrawStudioModel( cl_entity_t *e ) { R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); } + else if( e->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *parent = CL_GetEntityByIndex( e->curstate.aiment ); + + if( !parent || !parent->model || parent->model->type != mod_studio ) + return; + + parent = R_FindParentEntity( e, tr.draw_list->solid_entities, tr.draw_list->num_solid_entities ); + + if( !parent ) + parent = R_FindParentEntity( e, tr.draw_list->trans_entities, tr.draw_list->num_trans_entities ); + + if( parent ) + { + RI.currententity = parent; + R_StudioDrawModelInternal( RI.currententity, 0 ); + VectorCopy( RI.currententity->curstate.origin, e->curstate.origin ); + VectorCopy( RI.currententity->origin, e->origin ); + RI.currententity = e; + + R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + } + } else { - if( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 ) - { - cl_entity_t *parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); - - if( parent && parent->model && parent->model->type == mod_studio ) - { - RI.currententity = parent; - R_StudioDrawModelInternal( RI.currententity, 0 ); - VectorCopy( parent->curstate.origin, e->curstate.origin ); - VectorCopy( parent->origin, e->origin ); - RI.currententity = e; - } - } - R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); } } @@ -3641,14 +3638,14 @@ void R_RunViewmodelEvents( void ) if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer()) return; - RI.currententity = gEngfuncs.GetViewModel(); + RI.currententity = tr.viewent; if( !RI.currententity->model || RI.currententity->model->type != mod_studio ) return; R_StudioSetupTimings(); - gEngfuncs.GetPredictedOrigin( simorg ); + VectorCopy( gp_cl->simorg, simorg ); for( i = 0; i < 4; i++ ) VectorCopy( simorg, RI.currententity->attachment[i] ); RI.currentmodel = RI.currententity->model; @@ -3663,12 +3660,10 @@ R_GatherPlayerLight */ void R_GatherPlayerLight( void ) { - cl_entity_t *view = gEngfuncs.GetViewModel(); + cl_entity_t *view = tr.viewent; colorVec c; - tr.ignore_lightgamma = true; c = R_LightPoint( view->origin ); - tr.ignore_lightgamma = false; gEngfuncs.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 ); } @@ -3679,7 +3674,7 @@ R_DrawViewModel */ void R_DrawViewModel( void ) { - cl_entity_t *view = gEngfuncs.GetViewModel(); + cl_entity_t *view = tr.viewent; R_GatherPlayerLight(); @@ -3706,13 +3701,6 @@ void R_DrawViewModel( void ) pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); RI.currentmodel = RI.currententity->model; - // backface culling for left-handed weapons - if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) - { - tr.fFlipViewModel = true; - pglFrontFace( GL_CW ); - } - switch( RI.currententity->model->type ) { case mod_alias: @@ -3726,13 +3714,6 @@ void R_DrawViewModel( void ) // restore depth range pglDepthRange( gldepthmin, gldepthmax ); - - // backface culling for left-handed weapons - if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) - { - tr.fFlipViewModel = false; - pglFrontFace( GL_CCW ); - } } /* @@ -3748,6 +3729,7 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture int flags = 0; char texname[128], name[128], mdlname[128]; texture_t *tx = NULL; + qboolean load_external = false; if( ptexture->flags & STUDIO_NF_NORMALMAP ) flags |= (TF_NORMALMAP); @@ -3800,23 +3782,37 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture } Q_strncpy( mdlname, mod->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) SetBits( flags, TF_NOMIPMAP ); - // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it - //ptexture->index = (int)((byte *)phdr) + ptexture->index; - gEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index); - size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; - - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + if( FBitSet( gp_host->features, ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) flags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing - // build the texname - Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); - ptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags ); + // NOTE: colormaps must have the palette for properly work. Ignore them + if( Mod_AllowMaterials( ) && !FBitSet( ptexture->flags, STUDIO_NF_COLORMAP )) + { + if( R_SearchForTextureReplacement( texname, sizeof( texname ), mdlname, "materials/%s/%s.tga", mdlname, name )) + { + int gl_texturenum = GL_LoadTexture( texname, NULL, 0, flags ); + R_TextureReplacementReport( mdlname, gl_texturenum, texname ); + if(( load_external = gl_texturenum != 0 )) + ptexture->index = gl_texturenum; + } + } + + if( !load_external ) + { + // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it + gEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index); + size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; + + // build the texname + Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); + ptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags ); + } if( !ptexture->index ) { @@ -3878,7 +3874,9 @@ void Mod_StudioUnloadTextures( void *data ) static model_t *pfnModelHandle( int modelindex ) { - return gEngfuncs.pfnGetModelByIndex( modelindex ); + if( modelindex < 0 || modelindex >= MAX_MODELS ) + return NULL; + return CL_ModelHandle( modelindex ); } static void *pfnMod_CacheCheck( struct cache_user_s *c ) @@ -3977,9 +3975,6 @@ void CL_InitStudioAPI( void ) // trying to grab them from client.dll cl_righthand = gEngfuncs.pfnGetCvarPointer( "cl_righthand", 0 ); - if( cl_righthand == NULL ) - cl_righthand = gEngfuncs.Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" ); - // Xash will be used internal StudioModelRenderer if( gEngfuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI )) return; diff --git a/ref/gl/gl_triapi.c b/ref/gl/gl_triapi.c index 63536898..81cb06bf 100644 --- a/ref/gl/gl_triapi.c +++ b/ref/gl/gl_triapi.c @@ -40,16 +40,16 @@ set rendermode void TriRenderMode( int mode ) { ds.renderMode = mode; + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + switch( mode ) { case kRenderNormal: - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); break; case kRenderTransAlpha: pglEnable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglDepthMask( GL_FALSE ); break; @@ -244,7 +244,7 @@ int TriSpriteTexture( model_t *pSpriteModel, int frame ) if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 ) return 0; - if( gl_texturenum <= 0 || gl_texturenum > MAX_TEXTURES ) + if( gl_texturenum <= 0 || gl_texturenum >= MAX_TEXTURES ) gl_texturenum = tr.defaultTexture; GL_Bind( XASH_TEXTURE0, gl_texturenum ); @@ -262,7 +262,7 @@ enables global fog on the level void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) { // overrided by internal fog - if( RI.fogEnabled ) return; + if( RI.fogEnabled || !gl_fog.value ) return; RI.fogCustom = bOn; // check for invalid parms @@ -281,13 +281,22 @@ void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) RI.fogColor[0] = flFogColor[0] / 255.0f; RI.fogColor[1] = flFogColor[1] / 255.0f; RI.fogColor[2] = flFogColor[2] / 255.0f; - RI.fogStart = flStart; RI.fogColor[3] = 1.0f; - RI.fogDensity = 0.0f; - RI.fogSkybox = true; + + RI.fogStart = flStart; RI.fogEnd = flEnd; - pglFogi( GL_FOG_MODE, GL_LINEAR ); + if( RI.fogDensity > 0.0f ) + { + pglFogi( GL_FOG_MODE, GL_EXP2 ); + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); + } + else + { + pglFogi( GL_FOG_MODE, GL_LINEAR ); + RI.fogSkybox = true; + } + pglFogfv( GL_FOG_COLOR, RI.fogColor ); pglFogf( GL_FOG_START, RI.fogStart ); pglFogf( GL_FOG_END, RI.fogEnd ); diff --git a/ref/gl/gl_vgui.c b/ref/gl/gl_vgui.c deleted file mode 100644 index 029d8499..00000000 --- a/ref/gl/gl_vgui.c +++ /dev/null @@ -1,253 +0,0 @@ -/* -gl_vgui.c - OpenGL vgui draw methods -Copyright (C) 2011 Uncle Mike -Copyright (C) 2019 a1batross - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "gl_local.h" - -#define VGUI_MAX_TEXTURES ( MAX_TEXTURES / 2 ) // a half of total textures count - -static int g_textures[VGUI_MAX_TEXTURES]; -static int g_textureId = 0; -static int g_iBoundTexture; - -/* -================ -VGUI_DrawInit - -Startup VGUI backend -================ -*/ -void GAME_EXPORT VGUI_DrawInit( void ) -{ - memset( g_textures, 0, sizeof( g_textures )); - g_textureId = g_iBoundTexture = 0; -} - -/* -================ -VGUI_DrawShutdown - -Release all textures -================ -*/ -void GAME_EXPORT VGUI_DrawShutdown( void ) -{ - int i; - - for( i = 1; i < g_textureId; i++ ) - { - GL_FreeTexture( g_textures[i] ); - } -} - -/* -================ -VGUI_GenerateTexture - -generate unique texture number -================ -*/ -int GAME_EXPORT VGUI_GenerateTexture( void ) -{ - if( ++g_textureId >= VGUI_MAX_TEXTURES ) - gEngfuncs.Host_Error( "VGUI_GenerateTexture: VGUI_MAX_TEXTURES limit exceeded\n" ); - return g_textureId; -} - -/* -================ -VGUI_UploadTexture - -Upload texture into video memory -================ -*/ -void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height ) -{ - rgbdata_t r_image; - char texName[32]; - - if( id <= 0 || id >= VGUI_MAX_TEXTURES ) - { - gEngfuncs.Con_DPrintf( S_ERROR "VGUI_UploadTexture: bad texture %i. Ignored\n", id ); - return; - } - - Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); - memset( &r_image, 0, sizeof( r_image )); - - r_image.width = width; - r_image.height = height; - r_image.type = PF_RGBA_32; - r_image.size = r_image.width * r_image.height * 4; - r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; - r_image.buffer = (byte *)buffer; - - g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE ); -} - -/* -================ -VGUI_CreateTexture - -Create empty rgba texture and upload them into video memory -================ -*/ -void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height ) -{ - rgbdata_t r_image; - char texName[32]; - - if( id <= 0 || id >= VGUI_MAX_TEXTURES ) - { - gEngfuncs.Con_Reportf( S_ERROR "VGUI_CreateTexture: bad texture %i. Ignored\n", id ); - return; - } - - Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); - memset( &r_image, 0, sizeof( r_image )); - - r_image.width = width; - r_image.height = height; - r_image.type = PF_RGBA_32; - r_image.size = r_image.width * r_image.height * 4; - r_image.flags = IMAGE_HAS_ALPHA; - r_image.buffer = NULL; - - g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE|TF_NEAREST ); - g_iBoundTexture = id; -} - -void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) -{ - if( id <= 0 || id >= VGUI_MAX_TEXTURES || g_textures[id] == 0 || g_textures[id] == tr.whiteTexture ) - { - gEngfuncs.Con_Reportf( S_ERROR "VGUI_UploadTextureBlock: bad texture %i. Ignored\n", id ); - return; - } - - pglTexSubImage2D( GL_TEXTURE_2D, 0, drawX, drawY, blockWidth, blockHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba ); - g_iBoundTexture = id; -} - -void GAME_EXPORT VGUI_SetupDrawingRect( int *pColor ) -{ - pglEnable( GL_BLEND ); - pglDisable( GL_ALPHA_TEST ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); -} - -void GAME_EXPORT VGUI_SetupDrawingText( int *pColor ) -{ - pglEnable( GL_BLEND ); - pglEnable( GL_ALPHA_TEST ); - pglAlphaFunc( GL_GREATER, 0.0f ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); -} - -void GAME_EXPORT VGUI_SetupDrawingImage( int *pColor ) -{ - pglEnable( GL_BLEND ); - pglEnable( GL_ALPHA_TEST ); - pglAlphaFunc( GL_GREATER, 0.0f ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); -} - -void GAME_EXPORT VGUI_BindTexture( int id ) -{ - if( id > 0 && id < VGUI_MAX_TEXTURES && g_textures[id] ) - { - GL_Bind( XASH_TEXTURE0, g_textures[id] ); - g_iBoundTexture = id; - } - else - { - // NOTE: same as bogus index 2700 in GoldSrc - id = g_iBoundTexture = 1; - GL_Bind( XASH_TEXTURE0, g_textures[id] ); - } -} - -/* -================ -VGUI_GetTextureSizes - -returns wide and tall for currently binded texture -================ -*/ -void GAME_EXPORT VGUI_GetTextureSizes( int *width, int *height ) -{ - gl_texture_t *glt; - int texnum; - - if( g_iBoundTexture ) - texnum = g_textures[g_iBoundTexture]; - else texnum = tr.defaultTexture; - - glt = R_GetTexture( texnum ); - if( width ) *width = glt->srcWidth; - if( height ) *height = glt->srcHeight; -} - -/* -================ -VGUI_EnableTexture - -disable texturemode for fill rectangle -================ -*/ -void GAME_EXPORT VGUI_EnableTexture( qboolean enable ) -{ - if( enable ) pglEnable( GL_TEXTURE_2D ); - else pglDisable( GL_TEXTURE_2D ); -} - -/* -================ -VGUI_DrawQuad - -generic method to fill rectangle -================ -*/ -void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ) -{ - int width, height; - float xscale, yscale; - - gEngfuncs.CL_GetScreenInfo( &width, &height ); - - xscale = gpGlobals->width / (float)width; - yscale = gpGlobals->height / (float)height; - - ASSERT( ul != NULL && lr != NULL ); - - pglBegin( GL_QUADS ); - pglTexCoord2f( ul->coord[0], ul->coord[1] ); - pglVertex2f( ul->point[0] * xscale, ul->point[1] * yscale ); - - pglTexCoord2f( lr->coord[0], ul->coord[1] ); - pglVertex2f( lr->point[0] * xscale, ul->point[1] * yscale ); - - pglTexCoord2f( lr->coord[0], lr->coord[1] ); - pglVertex2f( lr->point[0] * xscale, lr->point[1] * yscale ); - - pglTexCoord2f( ul->coord[0], lr->coord[1] ); - pglVertex2f( ul->point[0] * xscale, lr->point[1] * yscale ); - pglEnd(); -} diff --git a/ref/gl/gl_warp.c b/ref/gl/gl_warp.c index 3144324d..34d05a71 100644 --- a/ref/gl/gl_warp.c +++ b/ref/gl/gl_warp.c @@ -16,13 +16,14 @@ GNU General Public License for more details. #include "gl_local.h" #include "wadfile.h" + #define SKYCLOUDS_QUALITY 12 #define MAX_CLIP_VERTS 128 // skybox clip vertices #define TURBSCALE ( 256.0f / ( M_PI2 )) -const char* r_skyBoxSuffix[6] = { "rt", "bk", "lf", "ft", "up", "dn" }; -static const int r_skyTexOrder[6] = { 0, 2, 1, 3, 4, 5 }; -static const vec3_t skyclip[6] = +static const int r_skyTexOrder[SKYBOX_MAX_SIDES] = { 0, 2, 1, 3, 4, 5 }; + +static const vec3_t skyclip[SKYBOX_MAX_SIDES] = { { 1, 1, 0 }, { 1, -1, 0 }, @@ -33,7 +34,7 @@ static const vec3_t skyclip[6] = }; // 1 = s, 2 = t, 3 = 2048 -static const int st_to_vec[6][3] = +static const int st_to_vec[SKYBOX_MAX_SIDES][3] = { { 3, -1, 2 }, { -3, 1, 2 }, @@ -44,7 +45,7 @@ static const int st_to_vec[6][3] = }; // s = [0]/[2], t = [1]/[2] -static const int vec_to_st[6][3] = +static const int vec_to_st[SKYBOX_MAX_SIDES][3] = { { -2, 3, 1 }, { 2, 3, -1 }, @@ -55,57 +56,33 @@ static const int vec_to_st[6][3] = }; // speed up sin calculations -float r_turbsin[] = +static float r_turbsin[] = { - #include "warpsin.h" +#include "warpsin.h" }; -static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) +#define RIPPLES_CACHEWIDTH_BITS 7 +#define RIPPLES_CACHEWIDTH ( 1 << RIPPLES_CACHEWIDTH_BITS ) +#define RIPPLES_CACHEWIDTH_MASK (( RIPPLES_CACHEWIDTH ) - 1 ) +#define RIPPLES_TEXSIZE ( RIPPLES_CACHEWIDTH * RIPPLES_CACHEWIDTH ) +#define RIPPLES_TEXSIZE_MASK ( RIPPLES_TEXSIZE - 1 ) + +STATIC_ASSERT( RIPPLES_TEXSIZE == 0x4000, "fix the algorithm to work with custom resolution" ); + +static struct { - const char *skybox_ext[3] = { "dds", "tga", "bmp" }; - int i, j, num_checked_sides; - const char *sidename; + double time; + double oldtime; - // search for skybox images - for( i = 0; i < 3; i++ ) - { - // check HL-style skyboxes - num_checked_sides = 0; - for( j = 0; j < 6; j++ ) - { - // build side name - sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.fsapi->FileExists( sidename, false )) - { - Q_strncpy( out[j], sidename, sizeof( out[j] )); - num_checked_sides++; - } - } + short *curbuf, *oldbuf; + short buf[2][RIPPLES_TEXSIZE]; + qboolean update; - if( num_checked_sides == 6 ) - return true; // image exists + uint32_t texture[RIPPLES_TEXSIZE]; +} g_ripple; - // check Q1-style skyboxes - num_checked_sides = 0; - for( j = 0; j < 6; j++ ) - { - // build side name - sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.fsapi->FileExists( sidename, false )) - { - Q_strncpy( out[j], sidename, sizeof( out[j] )); - num_checked_sides++; - } - } - if( num_checked_sides == 6 ) - return true; // images exists - } - - return false; -} - -void DrawSkyPolygon( int nump, vec3_t vecs ) +static void DrawSkyPolygon( int nump, vec3_t vecs ) { int i, j, axis; float s, t, dv, *vp; @@ -153,7 +130,7 @@ void DrawSkyPolygon( int nump, vec3_t vecs ) ClipSkyPolygon ============== */ -void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) +static void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) { const float *norm; float *v, d, e; @@ -165,7 +142,7 @@ void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) int i, j; if( nump > MAX_CLIP_VERTS ) - gEngfuncs.Host_Error( "ClipSkyPolygon: MAX_CLIP_VERTS\n" ); + gEngfuncs.Host_Error( "%s: MAX_CLIP_VERTS\n", __func__ ); loc1: if( stage == 6 ) { @@ -248,7 +225,7 @@ loc1: ClipSkyPolygon( newc[1], newv[1][0], stage + 1 ); } -void MakeSkyVec( float s, float t, int axis ) +static void MakeSkyVec( float s, float t, int axis ) { int j, k, farclip; vec3_t v, b; @@ -270,14 +247,16 @@ void MakeSkyVec( float s, float t, int axis ) s = (s + 1.0f) * 0.5f; t = (t + 1.0f) * 0.5f; - if( s < 1.0f / 512.0f ) - s = 1.0f / 512.0f; - else if( s > 511.0f / 512.0f ) - s = 511.0f / 512.0f; - if( t < 1.0f / 512.0f ) - t = 1.0f / 512.0f; - else if( t > 511.0f / 512.0f ) - t = 511.0f / 512.0f; + if( GL_Support( GL_CLAMPTOEDGE_EXT )) + { + s = bound( 0.0f, s, 1.0f ); + t = bound( 0.0f, t, 1.0f ); + } + else + { + s = bound( 1.0f / 512.0f, s, 511.0f / 512.0f ); + t = bound( 1.0f / 512.0f, t, 511.0f / 512.0f ); + } t = 1.0f - t; @@ -294,7 +273,7 @@ void R_ClearSkyBox( void ) { int i; - for( i = 0; i < 6; i++ ) + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) { RI.skyMins[0][i] = RI.skyMins[1][i] = 9999999.0f; RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999.0f; @@ -309,13 +288,13 @@ R_AddSkyBoxSurface void R_AddSkyBoxSurface( msurface_t *fa ) { vec3_t verts[MAX_CLIP_VERTS]; - glpoly_t *p; + glpoly2_t *p; float *v; int i; - if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) && fa->polys && !tr.fCustomSkybox ) + if( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && fa->polys && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX )) { - glpoly_t *p = fa->polys; + glpoly2_t *p = fa->polys; // draw the sky poly pglBegin( GL_POLYGON ); @@ -348,16 +327,16 @@ void R_UnloadSkybox( void ) int i; // release old skybox - for( i = 0; i < 6; i++ ) + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) { if( !tr.skyboxTextures[i] ) continue; GL_FreeTexture( tr.skyboxTextures[i] ); } - tr.skyboxbasenum = 5800; // set skybox base (to let some mods load hi-res skyboxes) + tr.skyboxbasenum = SKYBOX_BASE_NUM; // set skybox base (to let some mods load hi-res skyboxes) memset( tr.skyboxTextures, 0, sizeof( tr.skyboxTextures )); - tr.fCustomSkybox = false; + ClearBits( tr.world->flags, FWORLD_CUSTOM_SKYBOX ); } /* @@ -381,7 +360,7 @@ void R_DrawSkyBox( void ) pglDisable( GL_ALPHA_TEST ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - for( i = 0; i < 6; i++ ) + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) { if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) continue; @@ -407,67 +386,6 @@ void R_DrawSkyBox( void ) R_LoadIdentity(); } -/* -=============== -R_SetupSky -=============== -*/ -void R_SetupSky( const char *skyboxname ) -{ - char loadname[MAX_STRING]; - char sidenames[6][MAX_STRING]; - int i, len; - qboolean result; - - if( !COM_CheckString( skyboxname )) - { - R_UnloadSkybox(); - return; // clear old skybox - } - - Q_snprintf( loadname, sizeof( loadname ), "gfx/env/%s", skyboxname ); - COM_StripExtension( loadname ); - - // kill the underline suffix to find them manually later - len = Q_strlen( loadname ); - - if( loadname[len - 1] == '_' ) - loadname[len - 1] = '\0'; - result = CheckSkybox( loadname, sidenames ); - - // to prevent infinite recursion if default skybox was missed - if( !result && Q_stricmp( loadname, DEFAULT_SKYBOX_PATH )) - { - gEngfuncs.Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", skyboxname ); - R_SetupSky( "desert" ); // force to default - return; - } - - // release old skybox - R_UnloadSkybox(); - gEngfuncs.Con_DPrintf( "SKY: " ); - - for( i = 0; i < 6; i++ ) - { - tr.skyboxTextures[i] = GL_LoadTexture( sidenames[i], NULL, 0, TF_CLAMP|TF_SKY ); - - if( !tr.skyboxTextures[i] ) - break; - - gEngfuncs.Con_DPrintf( "%s%s%s", skyboxname, r_skyBoxSuffix[i], i != 5 ? ", " : ". " ); - } - - if( i == 6 ) - { - tr.fCustomSkybox = true; - gEngfuncs.Con_DPrintf( "done\n" ); - return; // loaded - } - - gEngfuncs.Con_DPrintf( "^2failed\n" ); - R_UnloadSkybox(); -} - //============================================================================== // // RENDER CLOUDS @@ -478,7 +396,7 @@ void R_SetupSky( const char *skyboxname ) R_CloudVertex ============== */ -void R_CloudVertex( float s, float t, int axis, vec3_t v ) +static void R_CloudVertex( float s, float t, int axis, vec3_t v ) { int j, k, farclip; vec3_t b; @@ -502,12 +420,12 @@ void R_CloudVertex( float s, float t, int axis, vec3_t v ) R_CloudTexCoord ============= */ -void R_CloudTexCoord( vec3_t v, float speed, float *s, float *t ) +static void R_CloudTexCoord( const vec3_t v, float speed, float *s, float *t ) { float length, speedscale; vec3_t dir; - speedscale = gpGlobals->time * speed; + speedscale = gp_cl->time * speed; speedscale -= (int)speedscale & ~127; VectorSubtract( v, RI.vieworg, dir ); @@ -525,17 +443,17 @@ void R_CloudTexCoord( vec3_t v, float speed, float *s, float *t ) R_CloudDrawPoly =============== */ -void R_CloudDrawPoly( glpoly_t *p ) +static void R_CloudDrawPoly( const float *verts ) { + const float *v; float s, t; - float *v; int i; GL_SetRenderMode( kRenderNormal ); GL_Bind( XASH_TEXTURE0, tr.solidskyTexture ); pglBegin( GL_QUADS ); - for( i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE ) + for( i = 0, v = verts; i < 4; i++, v += VERTEXSIZE ) { R_CloudTexCoord( v, 8.0f, &s, &t ); pglTexCoord2f( s, t ); @@ -547,7 +465,7 @@ void R_CloudDrawPoly( glpoly_t *p ) GL_Bind( XASH_TEXTURE0, tr.alphaskyTexture ); pglBegin( GL_QUADS ); - for( i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE ) + for( i = 0, v = verts; i < 4; i++, v += VERTEXSIZE ) { R_CloudTexCoord( v, 16.0f, &s, &t ); pglTexCoord2f( s, t ); @@ -563,13 +481,13 @@ void R_CloudDrawPoly( glpoly_t *p ) R_CloudRenderSide ============== */ -void R_CloudRenderSide( int axis ) +static void R_CloudRenderSide( int axis ) { vec3_t verts[4]; + float final_verts[4][VERTEXSIZE]; float di, qi, dj, qj; vec3_t vup, vright; vec3_t temp, temp2; - glpoly_t p[1]; int i, j; R_CloudVertex( -1.0f, -1.0f, axis, verts[0] ); @@ -580,7 +498,6 @@ void R_CloudRenderSide( int axis ) VectorSubtract( verts[2], verts[3], vup ); VectorSubtract( verts[2], verts[1], vright ); - p->numverts = 4; di = SKYCLOUDS_QUALITY; qi = 1.0f / di; dj = (axis < 4) ? di * 2 : di; //subdivide vertically more than horizontally on skybox sides @@ -599,17 +516,17 @@ void R_CloudRenderSide( int axis ) VectorScale( vright, qi * i, temp ); VectorScale( vup, qj * j, temp2 ); VectorAdd( temp, temp2, temp ); - VectorAdd( verts[0], temp, p->verts[0] ); + VectorAdd( verts[0], temp, final_verts[0] ); VectorScale( vup, qj, temp ); - VectorAdd( p->verts[0], temp, p->verts[1] ); + VectorAdd( final_verts[0], temp, final_verts[1] ); VectorScale( vright, qi, temp ); - VectorAdd( p->verts[1], temp, p->verts[2] ); + VectorAdd( final_verts[1], temp, final_verts[2] ); - VectorAdd( p->verts[0], temp, p->verts[3] ); + VectorAdd( final_verts[0], temp, final_verts[3] ); - R_CloudDrawPoly( p ); + R_CloudDrawPoly( final_verts[0] ); } } } @@ -632,7 +549,7 @@ void R_DrawClouds( void ) pglDepthFunc( GL_GEQUAL ); pglDepthMask( GL_FALSE ); - for( i = 0; i < 6; i++ ) + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) { if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) continue; @@ -646,112 +563,6 @@ void R_DrawClouds( void ) pglFogf( GL_FOG_DENSITY, RI.fogDensity ); } -/* -============= -R_InitSkyClouds - -A sky texture is 256*128, with the right side being a masked overlay -============== -*/ -void R_InitSkyClouds( mip_t *mt, texture_t *tx, qboolean custom_palette ) -{ - rgbdata_t r_temp, *r_sky; - uint *trans, *rgba; - uint transpix; - int r, g, b; - int i, j, p; - char texname[32]; - - if( !glw_state.initialized ) - return; - - Q_snprintf( texname, sizeof( texname ), "%s%s.mip", ( mt->offsets[0] > 0 ) ? "#" : "", tx->name ); - - if( mt->offsets[0] > 0 ) - { - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - - if( custom_palette ) size += sizeof( short ) + 768; - r_sky = gEngfuncs.FS_LoadImage( texname, (byte *)mt, size ); - } - else - { - // okay, loading it from wad - r_sky = gEngfuncs.FS_LoadImage( texname, NULL, 0 ); - } - - // make sure what sky image is valid - if( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 ) - { - gEngfuncs.Con_Reportf( S_ERROR "R_InitSky: unable to load sky texture %s\n", tx->name ); - if( r_sky ) gEngfuncs.FS_FreeImage( r_sky ); - return; - } - - // make an average value for the back to avoid - // a fringe on the top level - trans = Mem_Malloc( r_temppool, r_sky->height * r_sky->height * sizeof( *trans )); - r = g = b = 0; - - for( i = 0; i < r_sky->width >> 1; i++ ) - { - for( j = 0; j < r_sky->height; j++ ) - { - p = r_sky->buffer[i * r_sky->width + j + r_sky->height]; - rgba = (uint *)r_sky->palette + p; - trans[(i * r_sky->height) + j] = *rgba; - r += ((byte *)rgba)[0]; - g += ((byte *)rgba)[1]; - b += ((byte *)rgba)[2]; - } - } - - ((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height ); - ((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height ); - ((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height ); - ((byte *)&transpix)[3] = 0; - - // build a temporary image - r_temp = *r_sky; - r_temp.width = r_sky->width >> 1; - r_temp.height = r_sky->height; - r_temp.type = PF_RGBA_32; - r_temp.flags = IMAGE_HAS_COLOR; - r_temp.size = r_temp.width * r_temp.height * 4; - r_temp.buffer = (byte *)trans; - r_temp.palette = NULL; - - // load it in - tr.solidskyTexture = GL_LoadTextureInternal( REF_SOLIDSKY_TEXTURE, &r_temp, TF_NOMIPMAP ); - - for( i = 0; i < r_sky->width >> 1; i++ ) - { - for( j = 0; j < r_sky->height; j++ ) - { - p = r_sky->buffer[i * r_sky->width + j]; - - if( p == 0 ) - { - trans[(i * r_sky->height) + j] = transpix; - } - else - { - rgba = (uint *)r_sky->palette + p; - trans[(i * r_sky->height) + j] = *rgba; - } - } - } - - r_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; - - // load it in - tr.alphaskyTexture = GL_LoadTextureInternal( REF_ALPHASKY_TEXTURE, &r_temp, TF_NOMIPMAP ); - - // clean up - gEngfuncs.FS_FreeImage( r_sky ); - Mem_Free( trans ); -} - /* ============= EmitWaterPolys @@ -759,17 +570,14 @@ EmitWaterPolys Does a water warp on the pre-fragmented glpoly_t chain ============= */ -void EmitWaterPolys( msurface_t *warp, qboolean reverse ) +void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples ) { float *v, nv, waveHeight; float s, t, os, ot; - glpoly_t *p; + glpoly2_t *p; int i; -#ifndef XASH_GLES - const qboolean useQuads = FBitSet( warp->flags, SURF_DRAWTURB_QUADS ); -#else - const qboolean useQuads = false; // TODO: figure out why -#endif + + const qboolean useQuads = FBitSet( warp->flags, SURF_DRAWTURB_QUADS ) && glConfig.context == CONTEXT_TYPE_GL; if( !warp->polys ) return; @@ -797,8 +605,8 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) { if( waveHeight ) { - nv = r_turbsin[(int)(gpGlobals->time * 160.0f + v[1] + v[0]) & 255] + 8.0f; - nv = (r_turbsin[(int)(v[0] * 5.0f + gpGlobals->time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv; + nv = r_turbsin[(int)(gp_cl->time * 160.0f + v[1] + v[0]) & 255] + 8.0f; + nv = (r_turbsin[(int)(v[0] * 5.0f + gp_cl->time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv; nv = nv * waveHeight + v[2]; } else nv = v[2]; @@ -806,10 +614,18 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) os = v[3]; ot = v[4]; - s = os + r_turbsin[(int)((ot * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; - s *= ( 1.0f / SUBDIVIDE_SIZE ); + if( !ripples ) + { + s = os + r_turbsin[(int)((ot * 0.125f + gp_cl->time) * TURBSCALE) & 255]; + t = ot + r_turbsin[(int)((os * 0.125f + gp_cl->time) * TURBSCALE) & 255]; + } + else + { + s = os; + t = ot; + } - t = ot + r_turbsin[(int)((os * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + s *= ( 1.0f / SUBDIVIDE_SIZE ); t *= ( 1.0f / SUBDIVIDE_SIZE ); pglTexCoord2f( s, t ); @@ -829,3 +645,194 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) GL_SetupFogColorForSurfaces(); } + +/* +============================================================ + + HALF-LIFE SOFTWARE WATER + +============================================================ +*/ +void R_ResetRipples( void ) +{ + g_ripple.curbuf = g_ripple.buf[0]; + g_ripple.oldbuf = g_ripple.buf[1]; + g_ripple.time = g_ripple.oldtime = gp_cl->time - 0.1; + memset( g_ripple.buf, 0, sizeof( g_ripple.buf )); +} + +static void R_SwapBufs( void ) +{ + short *tempbufp = g_ripple.curbuf; + g_ripple.curbuf = g_ripple.oldbuf; + g_ripple.oldbuf = tempbufp; +} + +static void R_SpawnNewRipple( int x, int y, short val ) +{ +#define PIXEL( x, y ) ((( x ) & RIPPLES_CACHEWIDTH_MASK ) + ((( y ) & RIPPLES_CACHEWIDTH_MASK) << 7 )) + g_ripple.oldbuf[PIXEL( x, y )] += val; + + val >>= 2; + g_ripple.oldbuf[PIXEL( x + 1, y )] += val; + g_ripple.oldbuf[PIXEL( x - 1, y )] += val; + g_ripple.oldbuf[PIXEL( x, y + 1 )] += val; + g_ripple.oldbuf[PIXEL( x, y - 1 )] += val; +#undef PIXEL +} + +static void R_RunRipplesAnimation( const short *oldbuf, short *pbuf ) +{ + size_t i = 0; + const int w = RIPPLES_CACHEWIDTH; + const int m = RIPPLES_TEXSIZE_MASK; + + for( i = w; i < m + w; i++, pbuf++ ) + { + *pbuf = ( + ( (int)oldbuf[( i - ( w * 2 )) & m] + + (int)oldbuf[( i - ( w + 1 )) & m] + + (int)oldbuf[( i - ( w - 1 )) & m] + + (int)oldbuf[( i ) & m]) >> 1 ) - (int)*pbuf; + + *pbuf -= ( *pbuf >> 6 ); + } +} + +void R_AnimateRipples( void ) +{ + double frametime = gp_cl->time - g_ripple.time; + + g_ripple.update = r_ripple.value && frametime >= r_ripple_updatetime.value; + + if( !g_ripple.update ) + return; + + g_ripple.time = gp_cl->time; + + R_SwapBufs(); + + if( g_ripple.time - g_ripple.oldtime > r_ripple_spawntime.value ) + { + int x, y, val; + + g_ripple.oldtime = g_ripple.time; + + x = rand() & 0x7fff; + y = rand() & 0x7fff; + val = rand() & 0x3ff; + + R_SpawnNewRipple( x, y, val ); + } + + R_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf ); +} + +static void R_GetRippleTextureSize( const texture_t *image, int *width, int *height ) +{ + // try to preserve aspect ratio + if( image->width > image->height ) + { + *width = RIPPLES_CACHEWIDTH; + *height = (float)image->height / image->width * RIPPLES_CACHEWIDTH; + } + else if( image->width < image->height ) + { + *width = (float)image->width / image->height * RIPPLES_CACHEWIDTH; + *height = RIPPLES_CACHEWIDTH; + } + else + { + *width = *height = RIPPLES_CACHEWIDTH; + } +} + +qboolean R_UploadRipples( texture_t *image ) +{ + const gl_texture_t *glt; + const uint32_t *pixels; + int y; + int width, height, size; + qboolean update = g_ripple.update; + + if( !r_ripple.value ) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return false; + } + + // discard unuseful textures + glt = R_GetTexture( image->gl_texturenum ); + if( !glt || !glt->original || !glt->original->buffer ) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return false; + } + + if( !image->fb_texturenum ) + { + rgbdata_t pic = { 0 }; + string name; + + Q_snprintf( name, sizeof( name ), "*rippletex_%s", image->name ); + R_GetRippleTextureSize( image, &width, &height ); + + pic.width = width; + pic.height = height; + pic.depth = 1; + pic.flags = IMAGE_HAS_COLOR; + pic.buffer = (byte *)g_ripple.texture; + pic.type = PF_RGBA_32; + pic.size = width * height * 4; + pic.numMips = 1; + memset( pic.buffer, 0, pic.size ); + + image->fb_texturenum = GL_LoadTextureInternal( name, &pic, TF_NOMIPMAP | TF_ALLOW_NEAREST ); + + update = true; + image->dt_texturenum = ( tr.framecount - 1 ) & 0xFFFF; + } + + GL_Bind( XASH_TEXTURE0, image->fb_texturenum ); + + // no updates this frame + if( !update || image->dt_texturenum == ( tr.framecount & 0xFFFF )) + return true; + + // prevent rendering texture multiple times in frame + image->dt_texturenum = tr.framecount & 0xFFFF; + + R_GetRippleTextureSize( image, &width, &height ); + + size = r_ripple.value == 1.0f ? 64 : RIPPLES_CACHEWIDTH; + pixels = (const uint32_t *)glt->original->buffer; + + for( y = 0; y < height; y++ ) + { + int ry = (float)y / height * size; + int x; + + for( x = 0; x < width; x++ ) + { + int rx = (float)x / width * size; + int val = g_ripple.curbuf[ry * RIPPLES_CACHEWIDTH + rx] / 16; + + // transform it to texture space and get nice tiling effect + int rpy = ( y - val ) % height; + int rpx = ( x + val ) % width; + + int py = (float)rpy / height * image->height; + int px = (float)rpx / width * image->width; + + if( py < 0 ) py = image->height + py; + if( px < 0 ) px = image->width + px; + + g_ripple.texture[y * width + x] = pixels[py * image->width + px]; + } + } + + pglTexImage2D( GL_TEXTURE_2D, 0, glt->format, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture ); + + return true; +} diff --git a/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc b/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc new file mode 100644 index 00000000..6aa0a092 --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc @@ -0,0 +1,58 @@ +R""( + +#if ATTR_TEXCOORD0 +uniform sampler2D uTex0 : TEXUNIT0; +#endif +#if ATTR_TEXCOORD1 +uniform sampler2D uTex1 : TEXUNIT1; +#endif +#if FEAT_ALPHA_TEST +uniform float uAlphaTest; +#endif +#if FEAT_FOG +// color + density, mode always GL_EXP +uniform float4 uFog; +#endif + +float4 main( + uniform float4 uColor +#if ATTR_COLOR + , float4 vColor : COLOR +#endif +#if ATTR_TEXCOORD0 + , float2 vTexCoord0 : TEXCOORD0 +#endif +#if ATTR_TEXCOORD1 + , float2 vTexCoord1 : TEXCOORD1 +#endif +#if ATTR_NORMAL + , float3 vNormal : TEXCOORD2 +#endif +#if FEAT_FOG + , float4 vPosition : WPOS +#endif +) { +#if ATTR_COLOR + float4 c = vColor; +#else + float4 c = uColor; +#endif +#if ATTR_TEXCOORD0 + c *= tex2D(uTex0, vTexCoord0); +#endif +#if ATTR_TEXCOORD1 + c *= tex2D(uTex1, vTexCoord1); +#endif +#if FEAT_ALPHA_TEST + if (c.a <= uAlphaTest) + discard; +#endif +#if FEAT_FOG + float fogDist = vPosition.z / vPosition.w; + float fogRate = clamp(exp(-uFog.w * fogDist), 0.f, 1.f); + c.rgb = lerp(uFog.rgb, c.rgb, fogRate); +#endif + return c; +} + +)"" diff --git a/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc b/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc new file mode 100644 index 00000000..75a9919a --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc @@ -0,0 +1,49 @@ +R""( + +// has to be called this for VGL to fill it in automatically +uniform float4x4 gl_ModelViewProjectionMatrix; + +void main( + float3 inPosition +#if ATTR_COLOR + , float4 inColor +#endif +#if ATTR_NORMAL + , float3 inNormal +#endif +#if ATTR_TEXCOORD0 + , float2 inTexCoord0 +#endif +#if ATTR_TEXCOORD1 + , float2 inTexCoord1 +#endif + , float4 out vPosition : POSITION +#if ATTR_COLOR + , float4 out vColor : COLOR +#endif +#if ATTR_TEXCOORD0 + , float2 out vTexCoord0 : TEXCOORD0 +#endif +#if ATTR_TEXCOORD1 + , float2 out vTexCoord1 : TEXCOORD1 +#endif +#if ATTR_NORMAL + , float3 out vNormal : TEXCOORD2 +#endif +) { + vPosition = mul(gl_ModelViewProjectionMatrix, float4(inPosition, 1.f)); +#if ATTR_COLOR + vColor = inColor; +#endif +#if ATTR_NORMAL + vNormal = inNormal; +#endif +#if ATTR_TEXCOORD0 + vTexCoord0 = inTexCoord0; +#endif +#if ATTR_TEXCOORD1 + vTexCoord1 = inTexCoord1; +#endif +} + +)"" diff --git a/ref/gl/vgl_shim/vgl_shim.c b/ref/gl/vgl_shim/vgl_shim.c new file mode 100644 index 00000000..dc544b2d --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shim.c @@ -0,0 +1,573 @@ +/* +vgl_shim.c - vitaGL custom immediate mode shim +Copyright (C) 2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +/* + this is a "replacement" for vitaGL's immediate mode tailored specifically for xash + this will only provide performance gains if vitaGL is built with DRAW_SPEEDHACK=1 + since that makes it assume that all vertex data pointers are GPU-mapped +*/ + +#include +#include +#include +#include +#include + +#include "port.h" +#include "xash3d_types.h" +#include "cvardef.h" +#include "const.h" +#include "com_model.h" +#include "cl_entity.h" +#include "render_api.h" +#include "protocol.h" +#include "dlight.h" +#include "ref_api.h" +#include "com_strings.h" +#include "crtlib.h" +#include "vgl_shim.h" + +#define MAX_SHADERLEN 4096 +// increase this when adding more attributes +#define MAX_PROGS 32 + +extern ref_api_t gEngfuncs; + +enum vgl_attrib_e +{ + VGL_ATTR_POS = 0, // 1 + VGL_ATTR_COLOR = 1, // 2 + VGL_ATTR_TEXCOORD0 = 2, // 4 + VGL_ATTR_TEXCOORD1 = 3, // 8 + VGL_ATTR_MAX +}; + +// continuation of previous enum +enum vgl_flag_e +{ + VGL_FLAG_ALPHA_TEST = VGL_ATTR_MAX, // 16 + VGL_FLAG_FOG, // 32 + VGL_FLAG_MAX +}; + +typedef struct +{ + GLuint flags; + GLint attridx[VGL_ATTR_MAX]; + GLuint glprog; + GLint ucolor; + GLint ualpha; + GLint utex0; + GLint utex1; + GLint ufog; +} vgl_prog_t; + +static const char *vgl_vert_src = +#include "vgl_shaders/vertex.cg.inc" +; + +static const char *vgl_frag_src = +#include "vgl_shaders/fragment.cg.inc" +; + +static int vgl_init = 0; + +static struct +{ + GLfloat *attrbuf[VGL_ATTR_MAX]; + GLuint cur_flags; + GLint begin; + GLint end; + GLenum prim; + GLfloat color[4]; + GLfloat fog[4]; // color + density + GLfloat alpharef; + vgl_prog_t progs[MAX_PROGS]; + vgl_prog_t *cur_prog; + GLboolean uchanged; +} vgl; + +static const int vgl_attr_size[VGL_ATTR_MAX] = { 3, 4, 2, 2 }; + +static const char *vgl_flag_name[VGL_FLAG_MAX] = +{ + "ATTR_POSITION", + "ATTR_COLOR", + "ATTR_TEXCOORD0", + "ATTR_TEXCOORD1", + "FEAT_ALPHA_TEST", + "FEAT_FOG", +}; + +static const char *vgl_attr_name[VGL_ATTR_MAX] = +{ + "inPosition", + "inColor", + "inTexCoord0", + "inTexCoord1", +}; + +// HACK: borrow alpha test and fog flags from internal vitaGL state +extern GLboolean alpha_test_state; +extern GLboolean fogging; + +static GLuint VGL_GenerateShader( const vgl_prog_t *prog, GLenum type ) +{ + char *shader, shader_buf[MAX_SHADERLEN + 1]; + char tmp[256]; + int i; + GLint status, len; + GLuint id; + + shader = shader_buf; + shader[0] = '\n'; + shader[1] = 0; + + for ( i = 0; i < VGL_FLAG_MAX; ++i ) + { + Q_snprintf( tmp, sizeof( tmp ), "#define %s %d\n", vgl_flag_name[i], prog->flags & ( 1 << i ) ); + Q_strncat( shader, tmp, MAX_SHADERLEN ); + } + + if ( type == GL_FRAGMENT_SHADER ) + Q_strncat( shader, vgl_frag_src, MAX_SHADERLEN ); + else + Q_strncat( shader, vgl_vert_src, MAX_SHADERLEN ); + + id = glCreateShader( type ); + len = Q_strlen( shader ); + glShaderSource( id, 1, (const void *)&shader, &len ); + glCompileShader( id ); + glGetShaderiv( id, GL_COMPILE_STATUS, &status ); + if ( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGL_GenerateShader( 0x%04x, 0x%x ): compile failed:\n", prog->flags, type ); + gEngfuncs.Con_DPrintf( "Shader text:\n%s\n\n", shader ); + glDeleteShader( id ); + return 0; + } + + return id; +} + +static vgl_prog_t *VGL_GetProg( const GLuint flags ) +{ + int i, loc, status; + GLuint vp, fp, glprog; + vgl_prog_t *prog; + + // try to find existing prog matching this feature set + + if ( vgl.cur_prog && vgl.cur_prog->flags == flags ) + return vgl.cur_prog; + + for ( i = 0; i < MAX_PROGS; ++i ) + { + if ( vgl.progs[i].flags == flags ) + return &vgl.progs[i]; + else if ( vgl.progs[i].flags == 0 ) + break; + } + + if ( i == MAX_PROGS ) + { + gEngfuncs.Host_Error( "VGL_GetProg(): Ran out of program slots for 0x%04x\n", flags ); + return NULL; + } + + // new prog; generate shaders + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_GetProg(): Generating progs for 0x%04x\n", flags ); + prog = &vgl.progs[i]; + prog->flags = flags; + + vp = VGL_GenerateShader( prog, GL_VERTEX_SHADER ); + fp = VGL_GenerateShader( prog, GL_FRAGMENT_SHADER ); + if ( !vp || !fp ) + { + prog->flags = 0; + return NULL; + } + + glprog = glCreateProgram(); + glAttachShader( glprog, vp ); + glAttachShader( glprog, fp ); + + loc = 0; + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + if ( flags & ( 1 << i ) ) + { + prog->attridx[i] = loc; + glBindAttribLocation( glprog, loc++, vgl_attr_name[i] ); + } + else + { + prog->attridx[i] = -1; + } + } + + glLinkProgram( glprog ); + glDeleteShader( vp ); + glDeleteShader( fp ); + + glGetProgramiv( glprog, GL_LINK_STATUS, &status ); + if ( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGL_GetProg(): Failed linking progs for 0x%04x!\n", prog->flags ); + prog->flags = 0; + glDeleteProgram( glprog ); + return NULL; + } + + prog->ucolor = glGetUniformLocation( glprog, "uColor" ); + prog->ualpha = glGetUniformLocation( glprog, "uAlphaTest" ); + prog->utex0 = glGetUniformLocation( glprog, "uTex0" ); + prog->utex1 = glGetUniformLocation( glprog, "uTex1" ); + prog->ufog = glGetUniformLocation( glprog, "uFog" ); + + // these never change + if ( prog->utex0 >= 0 ) + glUniform1i( prog->utex0, 0 ); + if ( prog->utex1 >= 0 ) + glUniform1i( prog->utex1, 1 ); + + prog->glprog = glprog; + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_GetProg(): Generated progs for 0x%04x\n", flags ); + + return prog; +} + +static vgl_prog_t *VGL_SetProg( const GLuint flags ) +{ + vgl_prog_t *prog = NULL; + + if ( flags && ( prog = VGL_GetProg( flags ) ) ) + { + if ( prog != vgl.cur_prog ) + { + glUseProgram( prog->glprog ); + vgl.uchanged = GL_TRUE; + } + if ( vgl.uchanged ) + { + if ( prog->ualpha >= 0 ) + glUniform1f( prog->ualpha, vgl.alpharef ); + if ( prog->ucolor >= 0 ) + glUniform4fv( prog->ucolor, 1, vgl.color ); + if ( prog->ufog >= 0 ) + glUniform4fv( prog->ufog, 1, vgl.fog ); + vgl.uchanged = GL_FALSE; + } + } + else + { + glUseProgram( 0 ); + } + + vgl.cur_prog = prog; + return prog; +} + +int VGL_ShimInit( void ) +{ + int i; + GLuint total, size; + static const GLuint precache_progs[] = { + 0x0001, // out = ucolor + 0x0005, // out = tex0 * ucolor + 0x0007, // out = tex0 * vcolor + 0x0015, // out = tex0 * ucolor + FEAT_ALPHA_TEST + 0x0021, // out = ucolor + FEAT_FOG + 0x0025, // out = tex0 * ucolor + FEAT_FOG + 0x0027, // out = tex0 * vcolor + FEAT_FOG + 0x0035, // out = tex0 * ucolor + FEAT_ALPHA_TEST + FEAT_FOG + }; + + if ( vgl_init ) + return 0; + + memset( &vgl, 0, sizeof( vgl ) ); + + vgl.color[0] = 1.f; + vgl.color[1] = 1.f; + vgl.color[2] = 1.f; + vgl.color[3] = 1.f; + vgl.uchanged = GL_TRUE; + + total = 0; + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + size = VGL_MAX_VERTS * vgl_attr_size[i] * sizeof( GLfloat ); + vgl.attrbuf[i] = memalign( 0x100, size ); + total += size; + } + + VGL_ShimInstall(); + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_ShimInit(): %u bytes allocated for vertex buffer\n", total ); + gEngfuncs.Con_DPrintf( S_NOTE "VGL_ShimInit(): Pre-generating %u progs...\n", sizeof( precache_progs ) / sizeof( *precache_progs ) ); + for ( i = 0; i < (int)( sizeof( precache_progs ) / sizeof( *precache_progs ) ); ++i ) + VGL_GetProg( precache_progs[i] ); + + vgl_init = 1; + return 0; +} + +void VGL_ShimShutdown( void ) +{ + int i; + + if ( !vgl_init ) + return; + + glFinish(); + glUseProgram( 0 ); + + /* + // FIXME: this sometimes causes the game to block on glDeleteProgram for up to a minute + // but since this is only called on shutdown or game change, it should be fine to skip + for ( i = 0; i < MAX_PROGS; ++i ) + { + if ( vgl.progs[i].flags ) + glDeleteProgram( vgl.progs[i].glprog ); + } + */ + + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + free( vgl.attrbuf[i] ); + + memset( &vgl, 0, sizeof( vgl ) ); + + vgl_init = 0; +} + +void VGL_ShimEndFrame( void ) +{ + vgl.end = vgl.begin = 0; +} + +static void VGL_Begin( GLenum prim ) +{ + int i; + vgl.prim = prim; + vgl.begin = vgl.end; + // pos always enabled + vgl.cur_flags = 1 << VGL_ATTR_POS; + // disable all vertex attrib pointers + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + glDisableVertexAttribArray( i ); +} + +static void VGL_End( void ) +{ + int i; + vgl_prog_t *prog; + GLuint flags = vgl.cur_flags; + GLint count = vgl.end - vgl.begin; + + if ( !vgl.prim || !count ) + goto _leave; // end without begin + + // enable alpha test and fog if needed + if ( alpha_test_state ) + flags |= 1 << VGL_FLAG_ALPHA_TEST; + if ( fogging ) + flags |= 1 << VGL_FLAG_FOG; + + prog = VGL_SetProg( flags ); + if ( !prog ) + { + gEngfuncs.Host_Error( "VGL_End(): Could not find program for flags 0x%04x!\n", flags ); + goto _leave; + } + + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + if ( prog->attridx[i] >= 0 ) + { + glEnableVertexAttribArray( prog->attridx[i] ); + glVertexAttribPointer( prog->attridx[i], vgl_attr_size[i], GL_FLOAT, GL_FALSE, 0, vgl.attrbuf[i] + vgl_attr_size[i] * vgl.begin ); + } + } + + glDrawArrays( vgl.prim, 0, count ); + +_leave: + vgl.prim = GL_NONE; + vgl.begin = vgl.end; + vgl.cur_flags = 0; +} + +static void VGL_Vertex3f( GLfloat x, GLfloat y, GLfloat z ) +{ + GLfloat *p = vgl.attrbuf[VGL_ATTR_POS] + vgl.end * 3; + *p++ = x; + *p++ = y; + *p++ = z; + ++vgl.end; + if ( vgl.end >= VGL_MAX_VERTS ) + { + gEngfuncs.Con_DPrintf( S_ERROR "VGL_Vertex3f(): Vertex buffer overflow!\n" ); + vgl.end = vgl.begin = 0; + } +} + +static void VGL_Vertex2f( GLfloat x, GLfloat y ) +{ + VGL_Vertex3f( x, y, 0.f ); +} + +static void VGL_Vertex3fv( const GLfloat *v ) +{ + VGL_Vertex3f( v[0], v[1], v[2] ); +} + +static void VGL_Color4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a ) +{ + vgl.color[0] = r; + vgl.color[1] = g; + vgl.color[2] = b; + vgl.color[3] = a; + vgl.uchanged = GL_TRUE; + if ( vgl.prim ) + { + // HACK: enable color attribute if we're using color inside a Begin-End pair + GLfloat *p = vgl.attrbuf[VGL_ATTR_COLOR] + vgl.end * 4; + vgl.cur_flags |= 1 << VGL_ATTR_COLOR; + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } +} + +static void VGL_Color3f( GLfloat r, GLfloat g, GLfloat b ) +{ + VGL_Color4f( r, g, b, 1.f ); +} + +static void VGL_Color4ub( GLubyte r, GLubyte g, GLubyte b, GLubyte a ) +{ + VGL_Color4f( (GLfloat)r / 255.f, (GLfloat)g / 255.f, (GLfloat)b / 255.f, (GLfloat)a / 255.f ); +} + +static void VGL_Color4ubv( const GLubyte *v ) +{ + VGL_Color4ub( v[0], v[1], v[2], v[3] ); +} + +static void VGL_TexCoord2f( GLfloat u, GLfloat v ) +{ + // by spec glTexCoord always updates texunit 0 + GLfloat *p = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0; + *p++ = u; + *p++ = v; +} + +static void VGL_MultiTexCoord2f( GLenum tex, GLfloat u, GLfloat v ) +{ + GLfloat *p; + // assume there can only be two + if ( tex == GL_TEXTURE0 ) + { + p = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0; + } + else + { + p = vgl.attrbuf[VGL_ATTR_TEXCOORD1] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD1; + } + *p++ = u; + *p++ = v; +} + +static void VGL_Normal3fv( const GLfloat *v ) +{ + /* this does not seem to be necessary */ +} + +static void VGL_ShadeModel( GLenum unused ) +{ + /* this doesn't do anything in vitaGL except spit errors in debug mode, so stub it out */ +} + +static void VGL_AlphaFunc( GLenum mode, GLfloat ref ) +{ + vgl.alpharef = ref; + vgl.uchanged = GL_TRUE; + // mode is always GL_GREATER +} + +static void VGL_Fogf( GLenum param, GLfloat val ) +{ + if ( param == GL_FOG_DENSITY ) + { + vgl.fog[3] = val; + vgl.uchanged = GL_TRUE; + } +} + +static void VGL_Fogfv( GLenum param, const GLfloat *val ) +{ + if ( param == GL_FOG_COLOR ) + { + vgl.fog[0] = val[0]; + vgl.fog[1] = val[1]; + vgl.fog[2] = val[2]; + vgl.uchanged = GL_TRUE; + } +} + +static void VGL_DrawBuffer( GLenum mode ) +{ + /* unsupported */ +} + +static void VGL_Hint( GLenum hint, GLenum val ) +{ + /* none of the used hints are supported; stub to prevent errors */ +} + +#define VGL_OVERRIDE_PTR( name ) \ +{ \ + extern void *pgl ## name; \ + pgl ## name = VGL_ ## name; \ +} + +void VGL_ShimInstall( void ) +{ + VGL_OVERRIDE_PTR( Vertex2f ) + VGL_OVERRIDE_PTR( Vertex3f ) + VGL_OVERRIDE_PTR( Vertex3fv ) + VGL_OVERRIDE_PTR( Color3f ) + VGL_OVERRIDE_PTR( Color4f ) + VGL_OVERRIDE_PTR( Color4ub ) + VGL_OVERRIDE_PTR( Color4ubv ) + VGL_OVERRIDE_PTR( Normal3fv ) + VGL_OVERRIDE_PTR( TexCoord2f ) + VGL_OVERRIDE_PTR( MultiTexCoord2f ) + VGL_OVERRIDE_PTR( ShadeModel ) + VGL_OVERRIDE_PTR( DrawBuffer ) + VGL_OVERRIDE_PTR( AlphaFunc ) + VGL_OVERRIDE_PTR( Fogf ) + VGL_OVERRIDE_PTR( Fogfv ) + VGL_OVERRIDE_PTR( Hint ) + VGL_OVERRIDE_PTR( Begin ) + VGL_OVERRIDE_PTR( End ) +} diff --git a/ref/gl/vgl_shim/vgl_shim.h b/ref/gl/vgl_shim/vgl_shim.h new file mode 100644 index 00000000..e5107d4e --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shim.h @@ -0,0 +1,24 @@ +/* +vgl_shim.h - vitaGL custom immediate mode shim +Copyright (C) 2023 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once + +// max verts in a single frame +#define VGL_MAX_VERTS 32768 + +int VGL_ShimInit( void ); +void VGL_ShimInstall( void ); +void VGL_ShimShutdown( void ); +void VGL_ShimEndFrame( void ); diff --git a/ref/gl/vgl_shim/wscript b/ref/gl/vgl_shim/wscript new file mode 100644 index 00000000..0a5264a5 --- /dev/null +++ b/ref/gl/vgl_shim/wscript @@ -0,0 +1,18 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import os + +def options(opt): + pass + +def configure(conf): + conf.define('REF_DLL', 1) + +def build(bld): + bld.env.CFLAGS_cstlib.remove('-fno-PIC') + bld.env.CFLAGS += ['-fPIC'] + bld.stlib(source = bld.path.ant_glob('*.c'), + target = 'vgl_shim', + use = 'engine_includes sdk_includes werror', + includes = '.') diff --git a/ref/gl/wscript b/ref/gl/wscript index 1fc5e047..0f7cc81b 100644 --- a/ref/gl/wscript +++ b/ref/gl/wscript @@ -11,52 +11,55 @@ def options(opt): grp = opt.add_option_group('ref_gl options') grp.add_option('--enable-static-gl', action='store_true', dest='GL_STATIC', default=False, - help = 'enable direct linking to opengl [default: %default]') + help = 'enable direct linking to opengl [default: %(default)s]') # stub return def configure(conf): - # check for dedicated server build - if conf.options.DEDICATED: - return - - conf.define_cond('SUPPORT_BSP2_FORMAT', conf.options.SUPPORT_BSP2_FORMAT) - conf.env.GL_STATIC = conf.options.GL_STATIC if conf.env.GL_STATIC: conf.check(lib='GL') - conf.define('REF_DLL', 1) if conf.env.DEST_OS2 == 'android': conf.check_cc(lib='log') def build(bld): - libs = [ 'engine_includes', 'public', 'M' ] + libs = [ 'engine_includes', 'werror' ] + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS == 'psvita': + libs += [ 'sdk_includes', 'vgl_shim' ] + else: + libs += [ 'public', 'M' ] - source = bld.path.ant_glob(['*.c']) + source = bld.path.ant_glob(['*.c', 'gl2_shim/*.c']) includes = '.' targets = { 'ref_gl': { 'enable': bld.env.GL, 'libs': ['GL'] if bld.env.GL_STATIC else [], - 'defines': ['XASH_GL_STATIC'] if bld.env.GL_STATIC else [], + 'defines': ['XASH_GL_STATIC=1'] if bld.env.GL_STATIC else [], }, 'ref_gles1': { 'enable': bld.env.NANOGL, - 'libs': ['DL', 'nanogl'], - 'defines': ['XASH_NANOGL'], + 'libs': ['DL', 'nanogl', 'LOG'], + 'defines': ['XASH_NANOGL=1'], }, 'ref_gles2': { 'enable': bld.env.GLWES, - 'libs': ['DL', 'gl-wes-v2'], - 'defines': ['XASH_WES'], + 'libs': ['DL', 'gl-wes-v2', 'LOG'], + 'defines': ['XASH_WES=1'], }, 'ref_gl4es': { 'enable': bld.env.GL4ES, 'libs': ['DL', 'gl4es', 'LOG'], - 'defines': ['XASH_GL_STATIC', 'XASH_GL4ES'], + 'defines': ['XASH_GL_STATIC=1', 'XASH_GL4ES=1'], + }, + 'ref_gles3compat': { + 'enable': bld.env.GLES3COMPAT, + 'libs': [], + 'defines': ['XASH_GLES3COMPAT=1'], }, } @@ -66,9 +69,7 @@ def build(bld): bld.shlib(source = source, target = k, - features = 'c', includes = includes, use = libs + v['libs'], - defines = v['defines'], - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM) + defines = v['defines'] + ['REF_DLL=1'], + install_path = bld.env.LIBDIR) diff --git a/ref/null/r_context.c b/ref/null/r_context.c new file mode 100644 index 00000000..c7ad177f --- /dev/null +++ b/ref/null/r_context.c @@ -0,0 +1,587 @@ +/* +r_context.c -- null renderer context +Copyright (C) 2023-2024 a1batross + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "xash3d_types.h" +#include "const.h" +#include "cvardef.h" +#include "xash3d_mathlib.h" +#include "ref_api.h" + +/* + * this initially was made to be able to run full client + * in hazardous^Wheadless environments + * but might be a starting point for new renderers as well + */ + +static ref_api_t gEngfuncs; +static ref_globals_t *gpGlobals; + +static void R_SimpleStub( void ) +{ + ; +} + +static void R_SimpleStubInt( int unused ) +{ + ; +} + +static void R_SimpleStubUInt( unsigned int unused ) +{ + ; +} + +static void R_SimpleStubBool( qboolean unused ) +{ + ; +} + +static qboolean R_Init( void ) +{ + gEngfuncs.R_Init_Video( REF_SOFTWARE ); + return true; +} + +static const char *R_GetConfigName( void ) +{ + return NULL; +} + +static qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int x, int y, float scale_x, float scale_y ) +{ + return true; +} + +static void GL_SetupAttributes( int safegl ) +{ + ; +} + +static qboolean R_AddEntity( struct cl_entity_s *clent, int type ) +{ + return true; +} + +static void CL_AddCustomBeam( cl_entity_t *pEnvBeam ) +{ + ; +} + +static void R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities ) +{ + ; +} + +static const byte *R_GetTextureOriginalBuffer( unsigned int idx ) +{ + return NULL; +} + +static int GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) +{ + return 0; +} + +static void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) +{ + ; +} + +static void R_SetupSky( int *skytextures ) +{ + ; +} + +static void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ) +{ + ; +} + +static void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ) +{ + ; +} + +static void FillRGBA( int rendermode, float x, float y, float w, float h, byte r, byte g, byte b, byte a ) +{ + ; +} + +static int WorldToScreen( const vec3_t world, vec3_t screen ) +{ + VectorClear( screen ); + return 0; +} + +static qboolean VID_ScreenShot( const char *filename, int shot_type ) +{ + return false; +} + +static qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ) +{ + return false; +} + +static colorVec R_LightPoint( const float *p ) +{ + colorVec c = { 0 }; + return c; +} + +static void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ) +{ + ; +} + +static int R_CreateDecalList( struct decallist_s *pList ) +{ + return 0; +} + +static float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ) +{ + return 0.0f; +} + +static void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ) +{ + ; +} + +static void R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture ) +{ + ; +} + +static void GL_SubdivideSurface( model_t *mod, msurface_t *fa ) +{ + ; +} + +static void CL_RunLightStyles( lightstyle_t *ls ) +{ + +} + +static void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite ) +{ + if( frameWidth ) + *frameWidth = 0; + + if( frameHeight ) + *frameHeight = 0; + + if( numFrames ) + *numFrames = 0; +} + +static int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) +{ + return 0; +} + +static qboolean Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buffer ) +{ + return true; +} + +static void Mod_StudioLoadTextures( model_t *mod, void *data ) +{ + ; +} + +static void CL_DrawParticles( double frametime, particle_t *particles, float partsize ) +{ + ; +} + +static void CL_DrawTracers( double frametime, particle_t *tracers ) +{ + ; +} + +static void CL_DrawBeams( int fTrans, BEAM *beams ) +{ + ; +} + +static qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ) +{ + return false; +} + +static int RefGetParm( int parm, int arg ) +{ + return 0; +} + +static void GetDetailScaleForTexture( int texture, float *xScale, float *yScale ) +{ + *xScale = *yScale = 1.0f; +} + +static void GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *alpha ) +{ + *red = *green = *blue = *alpha = 0; +} + +static float GetFrameTime( void ) +{ + return 0.0f; +} + +static void R_SetCurrentEntity( struct cl_entity_s *ent ) +{ + ; +} + +static void R_SetCurrentModel( struct model_s *mod ) +{ + ; +} + +static int GL_FindTexture( const char *name ) +{ + return 0; +} + +static const char *GL_TextureName( unsigned int texnum ) +{ + return NULL; +} + +static const byte *GL_TextureData( unsigned int texnum ) +{ + return NULL; +} + +static int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ) +{ + return 0; +} + +static int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ) +{ + return 0; +} + +static int GL_LoadTextureArray( const char **names, int flags ) +{ + return 0; +} + +static int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ) +{ + return 0; +} + +static void R_OverrideTextureSourceSize( unsigned int textnum, unsigned int srcWidth, unsigned int srcHeight ) +{ + +} + +static void DrawSingleDecal( struct decal_s *pDecal, struct msurface_s *fa ) +{ + ; +} + +static float *R_DecalSetupVerts( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount ) +{ + return NULL; +} + +static void R_EntityRemoveDecals( struct model_s *mod ) +{ + ; +} + +static void AVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data ) +{ + ; +} + +static void GL_Bind( int tmu, unsigned int texnum ) +{ + ; +} + +static void GL_LoadTextureMatrix( const float *glmatrix ) +{ + ; +} + +static void GL_TexGen( unsigned int coord, unsigned int mode ) +{ + ; +} + +static void GL_UpdateTexSize( int texnum, int width, int height, int depth ) +{ + ; +} + +static void GL_DrawParticles( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime ) +{ + ; +} + +static colorVec LightVec( const float *start, const float *end, float *lightspot, float *lightvec ) +{ + colorVec c = { 0 }; + return c; +} + +static struct mstudiotex_s *StudioGetTexture( struct cl_entity_s *e ) +{ + return NULL; +} + +static void GL_RenderFrame( const struct ref_viewpass_s *rvp ) +{ + ; +} + +static void GL_OrthoBounds( const float *mins, const float *maxs ) +{ + ; +} + +static qboolean R_SpeedsMessage( char *out, size_t size ) +{ + return false; +} + +static byte *Mod_GetCurrentVis( void ) +{ + return NULL; +} + +static void *R_GetProcAddress( const char *name ) +{ + return NULL; +} + +static void Color4f( float r, float g, float b, float a ) +{ + ; +} + +static void Color4ub( unsigned char r, unsigned char g, unsigned char b, unsigned char a ) +{ + ; +} + +static void TexCoord2f( float u, float v ) +{ + ; +} + +static void Vertex3fv( const float *worldPnt ) +{ + ; +} + +static void Vertex3f( float x, float y, float z ) +{ + ; +} + +static void Fog( float flFogColor[3], float flStart, float flEnd, int bOn ) +{ + ; +} + +static void ScreenToWorld( const float *screen, float *world ) +{ + ; +} + +static void GetMatrix( const int pname, float *matrix ) +{ + ; +} + +static void FogParams( float flDensity, int iFogSkybox ) +{ + ; +} + +static void CullFace( TRICULLSTYLE mode ) +{ + ; +} + +static void VGUI_SetupDrawing( qboolean rect ) +{ + ; +} + +static void VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) +{ + ; +} + +static const ref_interface_t gReffuncs = +{ + .R_Init = R_Init, + .R_Shutdown = R_SimpleStub, + .R_GetConfigName = R_GetConfigName, + .R_SetDisplayTransform = R_SetDisplayTransform, + + .GL_SetupAttributes = GL_SetupAttributes, + .GL_InitExtensions = R_SimpleStub, + .GL_ClearExtensions = R_SimpleStub, + + .R_GammaChanged = R_SimpleStubBool, + .R_BeginFrame = R_SimpleStubBool, + .R_RenderScene = R_SimpleStub, + .R_EndFrame = R_SimpleStub, + .R_PushScene = R_SimpleStub, + .R_PopScene = R_SimpleStub, + .GL_BackendStartFrame = R_SimpleStub, + .GL_BackendEndFrame = R_SimpleStub, + + .R_ClearScreen = R_SimpleStub, + .R_AllowFog = R_SimpleStubBool, + .GL_SetRenderMode = R_SimpleStubInt, + + .R_AddEntity = R_AddEntity, + .CL_AddCustomBeam = CL_AddCustomBeam, + .R_ProcessEntData = R_ProcessEntData, + .R_Flush = R_SimpleStubUInt, + + .R_ShowTextures = R_SimpleStub, + + .R_GetTextureOriginalBuffer = R_GetTextureOriginalBuffer, + .GL_LoadTextureFromBuffer = GL_LoadTextureFromBuffer, + .GL_ProcessTexture = GL_ProcessTexture, + .R_SetupSky = R_SetupSky, + + .R_Set2DMode = R_SimpleStubBool, + .R_DrawStretchRaw = R_DrawStretchRaw, + .R_DrawStretchPic = R_DrawStretchPic, + .FillRGBA = FillRGBA, + .WorldToScreen = WorldToScreen, + + .VID_ScreenShot = VID_ScreenShot, + .VID_CubemapShot = VID_CubemapShot, + + .R_LightPoint = R_LightPoint, + + .R_DecalShoot = R_DecalShoot, + .R_DecalRemoveAll = R_SimpleStubInt, + .R_CreateDecalList = R_CreateDecalList, + .R_ClearAllDecals = R_SimpleStub, + + .R_StudioEstimateFrame = R_StudioEstimateFrame, + .R_StudioLerpMovement = R_StudioLerpMovement, + .CL_InitStudioAPI = R_SimpleStub, + + .R_SetSkyCloudsTextures = R_SetSkyCloudsTextures, + .GL_SubdivideSurface = GL_SubdivideSurface, + .CL_RunLightStyles = CL_RunLightStyles, + + .R_GetSpriteParms = R_GetSpriteParms, + .R_GetSpriteTexture = R_GetSpriteTexture, + + .Mod_ProcessRenderData = Mod_ProcessRenderData, + .Mod_StudioLoadTextures = Mod_StudioLoadTextures, + + .CL_DrawParticles = CL_DrawParticles, + .CL_DrawTracers = CL_DrawTracers, + .CL_DrawBeams = CL_DrawBeams, + .R_BeamCull = R_BeamCull, + + .RefGetParm = RefGetParm, + .GetDetailScaleForTexture = GetDetailScaleForTexture, + .GetExtraParmsForTexture = GetExtraParmsForTexture, + .GetFrameTime = GetFrameTime, + + .R_SetCurrentEntity = R_SetCurrentEntity, + .R_SetCurrentModel = R_SetCurrentModel, + + .GL_FindTexture = GL_FindTexture, + .GL_TextureName = GL_TextureName, + .GL_TextureData = GL_TextureData, + .GL_LoadTexture = GL_LoadTexture, + .GL_CreateTexture = GL_CreateTexture, + .GL_LoadTextureArray = GL_LoadTextureArray, + .GL_CreateTextureArray = GL_CreateTextureArray, + .GL_FreeTexture = R_SimpleStubUInt, + .R_OverrideTextureSourceSize = R_OverrideTextureSourceSize, + + .DrawSingleDecal = DrawSingleDecal, + .R_DecalSetupVerts = R_DecalSetupVerts, + .R_EntityRemoveDecals = R_EntityRemoveDecals, + + .AVI_UploadRawFrame = AVI_UploadRawFrame, + + .GL_Bind = GL_Bind, + .GL_SelectTexture = R_SimpleStubInt, + .GL_LoadTextureMatrix = GL_LoadTextureMatrix, + .GL_TexMatrixIdentity = R_SimpleStub, + .GL_CleanUpTextureUnits = R_SimpleStubInt, + .GL_TexGen = GL_TexGen, + .GL_TextureTarget = R_SimpleStubUInt, + .GL_TexCoordArrayMode = R_SimpleStubUInt, + .GL_UpdateTexSize = GL_UpdateTexSize, + .GL_Reserved0 = NULL, + .GL_Reserved1 = NULL, + + .GL_DrawParticles = GL_DrawParticles, + .LightVec = LightVec, + .StudioGetTexture = StudioGetTexture, + + .GL_RenderFrame = GL_RenderFrame, + .GL_OrthoBounds = GL_OrthoBounds, + .R_SpeedsMessage = R_SpeedsMessage, + .Mod_GetCurrentVis = Mod_GetCurrentVis, + .R_NewMap = R_SimpleStub, + .R_ClearScene = R_SimpleStub, + .R_GetProcAddress = R_GetProcAddress, + + .TriRenderMode = R_SimpleStubInt, + .Begin = R_SimpleStubInt, + .End = R_SimpleStub, + .Color4f = Color4f, + .Color4ub = Color4ub, + .TexCoord2f = TexCoord2f, + .Vertex3fv = Vertex3fv, + .Vertex3f = Vertex3f, + .Fog = Fog, + .ScreenToWorld = ScreenToWorld, + .GetMatrix = GetMatrix, + .FogParams = FogParams, + .CullFace = CullFace, + + .VGUI_SetupDrawing = VGUI_SetupDrawing, + .VGUI_UploadTextureBlock = VGUI_UploadTextureBlock, +}; + +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ); +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ) +{ + if( version != REF_API_VERSION ) + return 0; + + // fill in our callbacks + *funcs = gReffuncs; + gEngfuncs = *engfuncs; + gpGlobals = globals; + + return REF_API_VERSION; +} diff --git a/ref/null/wscript b/ref/null/wscript new file mode 100644 index 00000000..8de44e99 --- /dev/null +++ b/ref/null/wscript @@ -0,0 +1,18 @@ +#! /usr/bin/env python +# encoding: utf-8 +# mittorn, 2018 + +def options(opt): + pass + +def configure(conf): + pass + +def build(bld): + bld.shlib( + source = 'r_context.c', + target = 'ref_null', + defines = 'REF_DLL', + use = 'engine_includes sdk_includes werror', + install_path = bld.env.LIBDIR + ) diff --git a/ref/soft/r_aclip.c b/ref/soft/r_aclip.c index 93e2d8e0..647d34fe 100644 --- a/ref/soft/r_aclip.c +++ b/ref/soft/r_aclip.c @@ -21,17 +21,17 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" -static finalvert_t fv[2][8]; +static finalvert_t fv[2][8]; -void R_AliasProjectAndClipTestFinalVert (finalvert_t *fv); -void R_Alias_clip_top (finalvert_t *pfv0, finalvert_t *pfv1, - finalvert_t *out); -void R_Alias_clip_bottom (finalvert_t *pfv0, finalvert_t *pfv1, - finalvert_t *out); -void R_Alias_clip_left (finalvert_t *pfv0, finalvert_t *pfv1, - finalvert_t *out); -void R_Alias_clip_right (finalvert_t *pfv0, finalvert_t *pfv1, - finalvert_t *out); +void R_AliasProjectAndClipTestFinalVert( finalvert_t *fv ); +void R_Alias_clip_top( finalvert_t *pfv0, finalvert_t *pfv1, + finalvert_t *out ); +void R_Alias_clip_bottom( finalvert_t *pfv0, finalvert_t *pfv1, + finalvert_t *out ); +void R_Alias_clip_left( finalvert_t *pfv0, finalvert_t *pfv1, + finalvert_t *out ); +void R_Alias_clip_right( finalvert_t *pfv0, finalvert_t *pfv1, + finalvert_t *out ); /* @@ -41,178 +41,169 @@ R_Alias_clip_z pfv0 is the unclipped vertex, pfv1 is the z-clipped vertex ================ */ -void R_Alias_clip_z (finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out) +static void R_Alias_clip_z( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out ) { - float scale; + float scale; - scale = (ALIAS_Z_CLIP_PLANE - pfv0->xyz[2]) / - (pfv1->xyz[2] - pfv0->xyz[2]); + scale = ( ALIAS_Z_CLIP_PLANE - pfv0->xyz[2] ) + / ( pfv1->xyz[2] - pfv0->xyz[2] ); - out->xyz[0] = pfv0->xyz[0] + (pfv1->xyz[0] - pfv0->xyz[0]) * scale; - out->xyz[1] = pfv0->xyz[1] + (pfv1->xyz[1] - pfv0->xyz[1]) * scale; + out->xyz[0] = pfv0->xyz[0] + ( pfv1->xyz[0] - pfv0->xyz[0] ) * scale; + out->xyz[1] = pfv0->xyz[1] + ( pfv1->xyz[1] - pfv0->xyz[1] ) * scale; out->xyz[2] = ALIAS_Z_CLIP_PLANE; - out->s = pfv0->s + (pfv1->s - pfv0->s) * scale; - out->t = pfv0->t + (pfv1->t - pfv0->t) * scale; - out->l = pfv0->l + (pfv1->l - pfv0->l) * scale; + out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale; + out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale; + out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale; - R_AliasProjectAndClipTestFinalVert (out); + R_AliasProjectAndClipTestFinalVert( out ); } - -#if !id386 - -void R_Alias_clip_left (finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out) +void R_Alias_clip_left( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out ) { - float scale; + float scale; - if (pfv0->v >= pfv1->v ) + if( pfv0->v >= pfv1->v ) { - scale = (float)(RI.aliasvrect.x - pfv0->u) / - (pfv1->u - pfv0->u); - out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; - out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; - out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; - out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; - out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; - out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrect.x - pfv0->u ) + / ( pfv1->u - pfv0->u ); + out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; + out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; + out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; + out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; + out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; + out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f; } else { - scale = (float)(RI.aliasvrect.x - pfv1->u) / - (pfv0->u - pfv1->u); - out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; - out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; - out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; - out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; - out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; - out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrect.x - pfv1->u ) + / ( pfv0->u - pfv1->u ); + out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; + out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; + out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; + out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; + out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; + out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f; } } - -void R_Alias_clip_right (finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out) +void R_Alias_clip_right( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out ) { - float scale; + float scale; - if ( pfv0->v >= pfv1->v ) + if( pfv0->v >= pfv1->v ) { - scale = (float)(RI.aliasvrectright - pfv0->u ) / - (pfv1->u - pfv0->u ); - out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; - out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; - out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; - out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; - out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; - out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrectright - pfv0->u ) + / ( pfv1->u - pfv0->u ); + out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; + out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; + out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; + out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; + out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; + out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f; } else { - scale = (float)(RI.aliasvrectright - pfv1->u ) / - (pfv0->u - pfv1->u ); - out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; - out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; - out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; - out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; - out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; - out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrectright - pfv1->u ) + / ( pfv0->u - pfv1->u ); + out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; + out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; + out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; + out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; + out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; + out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f; } } - -void R_Alias_clip_top (finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out) +void R_Alias_clip_top( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out ) { - float scale; + float scale; - if (pfv0->v >= pfv1->v) + if( pfv0->v >= pfv1->v ) { - scale = (float)(RI.aliasvrect.y - pfv0->v) / - (pfv1->v - pfv0->v); - out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; - out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; - out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; - out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; - out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; - out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrect.y - pfv0->v ) + / ( pfv1->v - pfv0->v ); + out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; + out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; + out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; + out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; + out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; + out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f; } else { - scale = (float)(RI.aliasvrect.y - pfv1->v) / - (pfv0->v - pfv1->v); - out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; - out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; - out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; - out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; - out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; - out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi) * scale + 0.5f; + scale = (float)( RI.aliasvrect.y - pfv1->v ) + / ( pfv0->v - pfv1->v ); + out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; + out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; + out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; + out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; + out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; + out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f; } } - -void R_Alias_clip_bottom (finalvert_t *pfv0, finalvert_t *pfv1, - finalvert_t *out) +void R_Alias_clip_bottom( finalvert_t *pfv0, finalvert_t *pfv1, + finalvert_t *out ) { - float scale; + float scale; - if (pfv0->v >= pfv1->v) + if( pfv0->v >= pfv1->v ) { - scale = (float)(RI.aliasvrectbottom - pfv0->v) / - (pfv1->v - pfv0->v); + scale = (float)( RI.aliasvrectbottom - pfv0->v ) + / ( pfv1->v - pfv0->v ); - out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; - out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; - out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; - out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; - out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; - out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi) * scale + 0.5f; + out->u = pfv0->u + ( pfv1->u - pfv0->u ) * scale + 0.5f; + out->v = pfv0->v + ( pfv1->v - pfv0->v ) * scale + 0.5f; + out->s = pfv0->s + ( pfv1->s - pfv0->s ) * scale + 0.5f; + out->t = pfv0->t + ( pfv1->t - pfv0->t ) * scale + 0.5f; + out->l = pfv0->l + ( pfv1->l - pfv0->l ) * scale + 0.5f; + out->zi = pfv0->zi + ( pfv1->zi - pfv0->zi ) * scale + 0.5f; } else { - scale = (float)(RI.aliasvrectbottom - pfv1->v) / - (pfv0->v - pfv1->v); + scale = (float)( RI.aliasvrectbottom - pfv1->v ) + / ( pfv0->v - pfv1->v ); - out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; - out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; - out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; - out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; - out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; - out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi) * scale + 0.5f; + out->u = pfv1->u + ( pfv0->u - pfv1->u ) * scale + 0.5f; + out->v = pfv1->v + ( pfv0->v - pfv1->v ) * scale + 0.5f; + out->s = pfv1->s + ( pfv0->s - pfv1->s ) * scale + 0.5f; + out->t = pfv1->t + ( pfv0->t - pfv1->t ) * scale + 0.5f; + out->l = pfv1->l + ( pfv0->l - pfv1->l ) * scale + 0.5f; + out->zi = pfv1->zi + ( pfv0->zi - pfv1->zi ) * scale + 0.5f; } } -#endif - - -int R_AliasClip (finalvert_t *in, finalvert_t *out, int flag, int count, - void(*clip)(finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out) ) +static int R_AliasClip( finalvert_t *in, finalvert_t *out, int flag, int count, + void ( *clip )( finalvert_t *pfv0, finalvert_t *pfv1, finalvert_t *out )) { - int i,j,k; - int flags, oldflags; + int i, j, k; + int flags, oldflags; - j = count-1; + j = count - 1; k = 0; - for (i=0 ; i RI.aliasvrectright) + if( out[k].u > RI.aliasvrectright ) out[k].flags |= ALIAS_RIGHT_CLIP; - if (out[k].v > RI.aliasvrectbottom) + if( out[k].v > RI.aliasvrectbottom ) out[k].flags |= ALIAS_BOTTOM_CLIP; k++; } - if (!flags) + if( !flags ) { out[k] = in[i]; k++; @@ -222,16 +213,15 @@ int R_AliasClip (finalvert_t *in, finalvert_t *out, int flag, int count, return k; } - /* ================ R_AliasClipTriangle ================ */ -void R_AliasClipTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t *index2) +void R_AliasClipTriangle( finalvert_t *index0, finalvert_t *index1, finalvert_t *index2 ) { - int i, k, pingpong; - unsigned clipflags; + int i, k, pingpong; + unsigned clipflags; // copy vertexes and fix seam texture coordinates fv[0][0] = *index0; @@ -241,10 +231,10 @@ void R_AliasClipTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t // clip clipflags = fv[0][0].flags | fv[0][1].flags | fv[0][2].flags; - if (clipflags & ALIAS_Z_CLIP) + if( clipflags & ALIAS_Z_CLIP ) { - k = R_AliasClip (fv[0], fv[1], ALIAS_Z_CLIP, 3, R_Alias_clip_z); - if (k == 0) + k = R_AliasClip( fv[0], fv[1], ALIAS_Z_CLIP, 3, R_Alias_clip_z ); + if( k == 0 ) return; pingpong = 1; @@ -256,67 +246,67 @@ void R_AliasClipTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t k = 3; } - if (clipflags & ALIAS_LEFT_CLIP) + if( clipflags & ALIAS_LEFT_CLIP ) { - k = R_AliasClip (fv[pingpong], fv[pingpong ^ 1], - ALIAS_LEFT_CLIP, k, R_Alias_clip_left); - if (k == 0) + k = R_AliasClip( fv[pingpong], fv[pingpong ^ 1], + ALIAS_LEFT_CLIP, k, R_Alias_clip_left ); + if( k == 0 ) return; pingpong ^= 1; } - if (clipflags & ALIAS_RIGHT_CLIP) + if( clipflags & ALIAS_RIGHT_CLIP ) { - k = R_AliasClip (fv[pingpong], fv[pingpong ^ 1], - ALIAS_RIGHT_CLIP, k, R_Alias_clip_right); - if (k == 0) + k = R_AliasClip( fv[pingpong], fv[pingpong ^ 1], + ALIAS_RIGHT_CLIP, k, R_Alias_clip_right ); + if( k == 0 ) return; pingpong ^= 1; } - if (clipflags & ALIAS_BOTTOM_CLIP) + if( clipflags & ALIAS_BOTTOM_CLIP ) { - k = R_AliasClip (fv[pingpong], fv[pingpong ^ 1], - ALIAS_BOTTOM_CLIP, k, R_Alias_clip_bottom); - if (k == 0) + k = R_AliasClip( fv[pingpong], fv[pingpong ^ 1], + ALIAS_BOTTOM_CLIP, k, R_Alias_clip_bottom ); + if( k == 0 ) return; pingpong ^= 1; } - if (clipflags & ALIAS_TOP_CLIP) + if( clipflags & ALIAS_TOP_CLIP ) { - k = R_AliasClip (fv[pingpong], fv[pingpong ^ 1], - ALIAS_TOP_CLIP, k, R_Alias_clip_top); - if (k == 0) + k = R_AliasClip( fv[pingpong], fv[pingpong ^ 1], + ALIAS_TOP_CLIP, k, R_Alias_clip_top ); + if( k == 0 ) return; pingpong ^= 1; } - for (i=0 ; i RI.aliasvrectright) + else if( fv[pingpong][i].u > RI.aliasvrectright ) fv[pingpong][i].u = RI.aliasvrectright; - if (fv[pingpong][i].v < RI.aliasvrect.y) + if( fv[pingpong][i].v < RI.aliasvrect.y ) fv[pingpong][i].v = RI.aliasvrect.y; - else if (fv[pingpong][i].v > RI.aliasvrectbottom) + else if( fv[pingpong][i].v > RI.aliasvrectbottom ) fv[pingpong][i].v = RI.aliasvrectbottom; fv[pingpong][i].flags = 0; } // draw triangles - for (i=1 ; i> 1; - if( divs < 2 ) return; + if( divs < 2 ) + return; // noise is normalized to +/- scale noise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * gEngfuncs.COM_RandomFloat( -0.125f, 0.125f ); @@ -62,9 +63,9 @@ static void FracNoise( float *noise, int divs ) static void SineNoise( float *noise, int divs ) { - float freq = 0; - float step = M_PI_F / (float)divs; - int i; + float freq = 0; + float step = M_PI_F / (float)divs; + int i; for( i = 0; i < divs; i++ ) { @@ -84,7 +85,7 @@ BEAM MATHLIB static void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp ) { // direction in worldspace of the center of the beam - vec3_t vecBeamCenter; + vec3_t vecBeamCenter; VectorNormalize2( vecBeamDelta, vecBeamCenter ); CrossProduct( RI.vforward, vecBeamCenter, pPerp ); @@ -94,7 +95,7 @@ static void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp static void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal ) { // vTangentY = line vector for beam - vec3_t vTangentY, vDirToBeam; + vec3_t vTangentY, vDirToBeam; VectorSubtract( vStartPos, vNextPos, vTangentY ); @@ -117,8 +118,8 @@ Cull the beam by bbox */ qboolean GAME_EXPORT R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ) { - vec3_t mins, maxs; - int i; + vec3_t mins, maxs; + int i; return false; /* for( i = 0; i < 3; i++ ) @@ -193,14 +194,15 @@ general code for drawing beams */ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags ) { - int noiseIndex, noiseStep; - int i, total_segs, segs_drawn; - float div, length, fraction, factor; - float flMaxWidth, vLast, vStep, brightness; - vec3_t perp1, vLastNormal = { 0.0f }; - beamseg_t curSeg; + int noiseIndex, noiseStep; + int i, total_segs, segs_drawn; + float div, length, fraction, factor; + float flMaxWidth, vLast, vStep, brightness; + vec3_t perp1, vLastNormal = { 0.0f }; + beamseg_t curSeg; - if( segments < 2 ) return; + if( segments < 2 ) + return; length = VectorLength( delta ); flMaxWidth = width * 0.5f; @@ -210,15 +212,16 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f { // here, we have too many segments; we could get overlap... so lets have less segments segments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f; - if( segments < 2 ) segments = 2; + if( segments < 2 ) + segments = 2; } if( segments > NOISE_DIVISIONS ) segments = NOISE_DIVISIONS; - div = 1.0f / (segments - 1); + div = 1.0f / ( segments - 1 ); length *= 0.01f; - vStep = length * div; // Texture length texels per space pixel + vStep = length * div; // Texture length texels per space pixel // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) vLast = fmod( freq * speed, 1 ); @@ -255,8 +258,8 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f // specify all the segments. for( i = 0; i < segments; i++ ) { - beamseg_t nextSeg; - vec3_t vPoint1, vPoint2; + beamseg_t nextSeg; + vec3_t vPoint1, vPoint2; Assert( noiseIndex < ( NOISE_DIVISIONS << 16 )); @@ -267,17 +270,17 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f // distort using noise if( scale != 0 ) { - factor = rgNoise[noiseIndex>>16] * scale; + factor = rgNoise[noiseIndex >> 16] * scale; if( FBitSet( flags, FBEAM_SINENOISE )) { - float s, c; + float s, c; SinCos( fraction * M_PI_F * length + freq, &s, &c ); - VectorMA( nextSeg.pos, (factor * s), RI.vup, nextSeg.pos ); + VectorMA( nextSeg.pos, ( factor * s ), RI.vup, nextSeg.pos ); // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal - VectorMA( nextSeg.pos, (factor * c), RI.vright, nextSeg.pos ); + VectorMA( nextSeg.pos, ( factor * c ), RI.vright, nextSeg.pos ); } else { @@ -289,11 +292,11 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f nextSeg.width = width * 2.0f; nextSeg.texcoord = vLast; - if( segs_drawn > 0 ) + if( segs_drawn > 0 ) { // Get a vector that is perpendicular to us and perpendicular to the beam. // This is used to fatten the beam. - vec3_t vNormal, vAveNormal; + vec3_t vNormal, vAveNormal; R_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal ); @@ -313,16 +316,16 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f // draw regular segment VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 ); - VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 ); + VectorMA( curSeg.pos, ( -curSeg.width * 0.5f ), vAveNormal, vPoint2 ); TriTexCoord2f( 0.0f, curSeg.texcoord ); TriBrightness( brightness ); - //pglNormal3fv( vAveNormal ); + // pglNormal3fv( vAveNormal ); TriVertex3fv( vPoint1 ); TriTexCoord2f( 1.0f, curSeg.texcoord ); TriBrightness( brightness ); - //pflNormal3fv( vAveNormal ); + // pflNormal3fv( vAveNormal ); TriVertex3fv( vPoint2 ); } @@ -331,8 +334,10 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f if( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT )) { - if( fraction < 0.5f ) brightness = fraction; - else brightness = ( 1.0f - fraction ); + if( fraction < 0.5f ) + brightness = fraction; + else + brightness = ( 1.0f - fraction ); } else if( FBitSet( flags, FBEAM_SHADEIN )) { @@ -343,21 +348,21 @@ static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, f brightness = 1.0f - fraction; } - if( segs_drawn == total_segs ) + if( segs_drawn == total_segs ) { // draw the last segment VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 ); - VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 ); + VectorMA( curSeg.pos, ( -curSeg.width * 0.5f ), vLastNormal, vPoint2 ); // specify the points. TriTexCoord2f( 0.0f, curSeg.texcoord ); TriBrightness( brightness ); - //pglNormal3fv( vLastNormal ); + // pglNormal3fv( vLastNormal ); TriVertex3fv( vPoint1 ); TriTexCoord2f( 1.0f, curSeg.texcoord ); TriBrightness( brightness ); - //pglNormal3fv( vLastNormal ); + // pglNormal3fv( vLastNormal ); TriVertex3fv( vPoint2 ); } @@ -373,11 +378,11 @@ R_DrawTorus Draw beamtours ================ */ -void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { - int i, noiseIndex, noiseStep; - float div, length, fraction, factor, vLast, vStep; - vec3_t last1, last2, point, screen, screenLast, tmp, normal; + int i, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast, tmp, normal; if( segments < 2 ) return; @@ -386,9 +391,10 @@ void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float f segments = NOISE_DIVISIONS; length = VectorLength( delta ) * 0.01f; - if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + if( length < 0.5f ) + length = 0.5f; // don't lose all of the noise/texture on short beams - div = 1.0f / (segments - 1); + div = 1.0f / ( segments - 1 ); vStep = length * div; // Texture length texels per space pixel @@ -402,7 +408,7 @@ void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float f for( i = 0; i < segments; i++ ) { - float s, c; + float s, c; fraction = i * div; SinCos( fraction * M_PI2, &s, &c ); @@ -416,11 +422,11 @@ void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float f { if(( noiseIndex >> 16 ) < NOISE_DIVISIONS ) { - factor = rgNoise[noiseIndex>>16] * scale; + factor = rgNoise[noiseIndex >> 16] * scale; VectorMA( point, factor, RI.vup, point ); // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal - factor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI_F * 3 + freq ); + factor = rgNoise[noiseIndex >> 16] * scale * cos( fraction *M_PI_F * 3 + freq ); VectorMA( point, factor, RI.vright, point ); } } @@ -436,7 +442,7 @@ void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float f // we don't need Z, we're in screen space tmp[2] = 0; VectorNormalize( tmp ); - VectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x) VectorMA( normal, tmp[1], RI.vright, normal ); // Make a wide line @@ -462,24 +468,25 @@ R_DrawDisk Draw beamdisk ================ */ -void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { - float div, length, fraction; - float w, vLast, vStep; - vec3_t point; - int i; + float div, length, fraction; + float w, vLast, vStep; + vec3_t point; + int i; if( segments < 2 ) return; - if( segments > NOISE_DIVISIONS ) // UNDONE: Allow more segments? + if( segments > NOISE_DIVISIONS ) // UNDONE: Allow more segments? segments = NOISE_DIVISIONS; length = VectorLength( delta ) * 0.01f; - if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + if( length < 0.5f ) + length = 0.5f; // don't lose all of the noise/texture on short beams - div = 1.0f / (segments - 1); - vStep = length * div; // Texture length texels per space pixel + div = 1.0f / ( segments - 1 ); + vStep = length * div; // Texture length texels per space pixel // scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) vLast = fmod( freq * speed, 1 ); @@ -491,7 +498,7 @@ void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float fr // NOTE: we must force the degenerate triangles to be on the edge for( i = 0; i < segments; i++ ) { - float s, c; + float s, c; fraction = i * div; VectorCopy( source, point ); @@ -509,7 +516,7 @@ void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float fr TriTexCoord2f( 0.0f, vLast ); TriVertex3fv( point ); - vLast += vStep; // advance texture scroll (v axis only) + vLast += vStep; // advance texture scroll (v axis only) } } @@ -520,12 +527,12 @@ R_DrawCylinder Draw beam cylinder ================ */ -void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +static void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) { - float div, length, fraction; - float vLast, vStep; - vec3_t point; - int i; + float div, length, fraction; + float vLast, vStep; + vec3_t point; + int i; if( segments < 2 ) return; @@ -534,18 +541,19 @@ void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, floa segments = NOISE_DIVISIONS; length = VectorLength( delta ) * 0.01f; - if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + if( length < 0.5f ) + length = 0.5f; // don't lose all of the noise/texture on short beams - div = 1.0f / (segments - 1); - vStep = length * div; // texture length texels per space pixel + div = 1.0f / ( segments - 1 ); + vStep = length * div; // texture length texels per space pixel // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) vLast = fmod( freq * speed, 1 ); scale = scale * length; - for ( i = 0; i < segments; i++ ) + for( i = 0; i < segments; i++ ) { - float s, c; + float s, c; fraction = i * div; SinCos( fraction * M_PI2, &s, &c ); @@ -566,7 +574,7 @@ void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, floa TriTexCoord2f( 0, vLast ); TriVertex3fv( point ); - vLast += vStep; // Advance texture scroll (v axis only) + vLast += vStep; // Advance texture scroll (v axis only) } } @@ -577,12 +585,12 @@ R_DrawBeamFollow drawi followed beam ============== */ -void R_DrawBeamFollow( BEAM *pbeam, float frametime ) +static void R_DrawBeamFollow( BEAM *pbeam, float frametime ) { - particle_t *pnew, *particles; - float fraction, div, vLast, vStep, saved_fraction; - vec3_t last1, last2, tmp, screen, saved_last2; - vec3_t delta, screenLast, normal; + particle_t *pnew, *particles; + float fraction, div, vLast, vStep, saved_fraction; + vec3_t last1, last2, tmp, screen, saved_last2; + vec3_t delta, screenLast, normal; gEngfuncs.R_FreeDeadParticles( &pbeam->particles ); @@ -611,7 +619,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) if( pnew ) { VectorCopy( pbeam->source, pnew->org ); - pnew->die = gpGlobals->time + pbeam->amplitude; + pnew->die = gp_cl->time + pbeam->amplitude; VectorClear( pnew->vel ); pnew->next = particles; @@ -620,7 +628,8 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) } // nothing to draw - if( !particles ) return; + if( !particles ) + return; if( !pnew && div != 0 ) { @@ -650,7 +659,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) VectorNormalize( tmp ); // Build point along noraml line (normal is -y, x) - VectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x) VectorMA( normal, tmp[1], RI.vright, normal ); // Make a wide line @@ -658,7 +667,7 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) VectorMA( delta, -pbeam->width, normal, last2 ); div = 1.0f / pbeam->amplitude; - fraction = ( pbeam->die - gpGlobals->time ) * div; + fraction = ( pbeam->die - gp_cl->time ) * div; vLast = 0.0f; vStep = 1.0f; @@ -683,18 +692,18 @@ void R_DrawBeamFollow( BEAM *pbeam, float frametime ) // we don't need Z, we're in screen space tmp[2] = 0; VectorNormalize( tmp ); - VectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x) VectorMA( normal, tmp[1], RI.vright, normal ); // Make a wide line VectorMA( particles->org, pbeam->width, normal, last1 ); VectorMA( particles->org, -pbeam->width, normal, last2 ); - vLast += vStep; // Advance texture scroll (v axis only) + vLast += vStep; // Advance texture scroll (v axis only) if( particles->next != NULL ) { - fraction = (particles->die - gpGlobals->time) * div; + fraction = ( particles->die - gp_cl->time ) * div; } else { @@ -737,13 +746,13 @@ R_DrawRing Draw beamring ================ */ -void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) +static void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) { - int i, j, noiseIndex, noiseStep; - float div, length, fraction, factor, vLast, vStep; - vec3_t last1, last2, point, screen, screenLast; - vec3_t tmp, normal, center, xaxis, yaxis; - float radius, x, y, scale; + int i, j, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast; + vec3_t tmp, normal, center, xaxis, yaxis; + float radius, x, y, scale; if( segments < 2 ) return; @@ -755,11 +764,12 @@ void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, floa segments = NOISE_DIVISIONS * 8; length = VectorLength( delta ) * 0.01f * M_PI_F; - if( length < 0.5f ) length = 0.5f; // Don't lose all of the noise/texture on short beams + if( length < 0.5f ) + length = 0.5f; // Don't lose all of the noise/texture on short beams div = 1.0f / ( segments - 1 ); - vStep = length * div / 8.0f; // texture length texels per space pixel + vStep = length * div / 8.0f; // texture length texels per space pixel // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) vLast = fmod( freq * speed, 1.0f ); @@ -779,14 +789,14 @@ void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, floa // -------------------------------- // Compute box center +/- radius VectorSet( last1, radius, radius, scale ); - VectorAdd( center, last1, tmp ); // maxs - VectorSubtract( center, last1, screen ); // mins + VectorAdd( center, last1, tmp ); // maxs + VectorSubtract( center, last1, screen ); // mins if( !WORLDMODEL ) return; // is that box in PVS && frustum? - if( !gEngfuncs.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) ) //|| R_CullBox( screen, tmp )) + if( !gEngfuncs.Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( ))) // || R_CullBox( screen, tmp )) { return; } @@ -805,11 +815,11 @@ void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, floa VectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point ); // distort using noise - factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + factor = rgNoise[( noiseIndex >> 16 ) & ( NOISE_DIVISIONS - 1 )] * scale; VectorMA( point, factor, RI.vup, point ); // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal - factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + factor = rgNoise[( noiseIndex >> 16 ) & ( NOISE_DIVISIONS - 1 )] * scale; factor *= cos( fraction * M_PI_F * 24 + freq ); VectorMA( point, factor, RI.vright, point ); @@ -833,7 +843,7 @@ void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, floa VectorMA( point, width, normal, last1 ); VectorMA( point, -width, normal, last2 ); - vLast += vStep; // Advance texture scroll (v axis only) + vLast += vStep; // Advance texture scroll (v axis only) TriTexCoord2f( 1.0f, vLast ); TriVertex3fv( last2 ); TriTexCoord2f( 0.0f, vLast ); @@ -861,18 +871,19 @@ compute attachment point for beam */ static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) { - cl_entity_t *ent; - int attach; + cl_entity_t *ent; + int attach; ent = gEngfuncs.R_BeamGetEntity( beamEnt ); if( beamEnt < 0 ) attach = BEAMENT_ATTACHMENT( -beamEnt ); - else attach = BEAMENT_ATTACHMENT( beamEnt ); + else + attach = BEAMENT_ATTACHMENT( beamEnt ); if( !ent ) { - gEngfuncs.Con_DPrintf( S_ERROR "R_BeamComputePoint: invalid entity %i\n", BEAMENT_ENTITY( beamEnt )); + gEngfuncs.Con_DPrintf( S_ERROR "%s: invalid entity %i\n", __func__, BEAMENT_ENTITY( beamEnt )); VectorClear( pt ); return false; } @@ -880,13 +891,10 @@ static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) // get attachment if( attach > 0 ) VectorCopy( ent->attachment[attach - 1], pt ); - else if( ent->index == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) - { - vec3_t simorg; - gEngfuncs.GetPredictedOrigin( simorg ); - VectorCopy( simorg, pt ); - } - else VectorCopy( ent->origin, pt ); + else if( ent->index == ( gp_cl->playernum + 1 )) + VectorCopy( gp_cl->simorg, pt ); + else + VectorCopy( ent->origin, pt ); return true; } @@ -898,7 +906,7 @@ R_BeamRecomputeEndpoints Recomputes beam endpoints.. ============== */ -qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) +static qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) { if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) { @@ -929,7 +937,7 @@ qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) { ClearBits( pbeam->flags, FBEAM_ENDENTITY ); - pbeam->die = gpGlobals->time; + pbeam->die = gp_cl->time; return false; } else @@ -951,18 +959,18 @@ R_BeamDraw Update beam vars and draw it ============== */ -void R_BeamDraw( BEAM *pbeam, float frametime ) +static void R_BeamDraw( BEAM *pbeam, float frametime ) { - model_t *model; - vec3_t delta; + model_t *model; + vec3_t delta; - model = gEngfuncs.pfnGetModelByIndex( pbeam->modelIndex ); + model = CL_ModelHandle( pbeam->modelIndex ); SetBits( pbeam->flags, FBEAM_ISACTIVE ); if( !model || model->type != mod_sprite ) { pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore - pbeam->die = gpGlobals->time; + pbeam->die = gp_cl->time; return; } @@ -980,11 +988,12 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) { if( FBitSet( pbeam->flags, FBEAM_SINENOISE )) SineNoise( rgNoise, NOISE_DIVISIONS ); - else FracNoise( rgNoise, NOISE_DIVISIONS ); + else + FracNoise( rgNoise, NOISE_DIVISIONS ); } // update end points - if( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY )) + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY )) { // makes sure attachment[0] + attachment[1] are valid if( !R_BeamRecomputeEndpoints( pbeam )) @@ -1002,7 +1011,8 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) if( pbeam->amplitude >= 0.50f ) pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels - else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + else + pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels } if( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 )) @@ -1017,16 +1027,17 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) return; } - if( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT )) + if( pbeam->flags & ( FBEAM_FADEIN | FBEAM_FADEOUT )) { // update life cycle - pbeam->t = pbeam->freq + ( pbeam->die - gpGlobals->time ); - if( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t; + pbeam->t = pbeam->freq + ( pbeam->die - gp_cl->time ); + if( pbeam->t != 0.0f ) + pbeam->t = 1.0f - pbeam->freq / pbeam->t; } if( pbeam->type == TE_BEAMHOSE ) { - float flDot; + float flDot; VectorSubtract( pbeam->target, pbeam->source, delta ); VectorNormalize( delta ); @@ -1040,9 +1051,9 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) } else { - float flFade = pow( flDot, 10 ); - vec3_t localDir, vecProjection, tmp; - float flDistance; + float flFade = pow( flDot, 10 ); + vec3_t localDir, vecProjection, tmp; + float flDistance; // fade the beam if the player's not looking at the source VectorSubtract( RI.vieworg, pbeam->source, localDir ); @@ -1054,8 +1065,10 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) if( flDistance > 30 ) { flDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f ); - if( flDistance <= 0 ) flFade = 0; - else flFade *= pow( flDistance, 3 ); + if( flDistance <= 0 ) + flFade = 0; + else + flFade *= pow( flDistance, 3 ); } if( flFade < ( 1.0f / 255.0f )) @@ -1068,7 +1081,7 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) TriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd ); - if( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * gpGlobals->time) % pbeam->frameCount )) + if( !TriSpriteTexture( model, (int)( pbeam->frame + pbeam->frameRate * gp_cl->time ) % pbeam->frameCount )) { ClearBits( pbeam->flags, FBEAM_ISACTIVE ); return; @@ -1076,7 +1089,7 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) if( pbeam->type == TE_BEAMFOLLOW ) { - cl_entity_t *pStart; + cl_entity_t *pStart; // XASH SPECIFIC: get brightness from head entity pStart = gEngfuncs.R_BeamGetEntity( pbeam->startEntity ); @@ -1088,24 +1101,25 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness ); else if( FBitSet( pbeam->flags, FBEAM_FADEOUT )) TriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness ); - else TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness ); + else + TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness ); switch( pbeam->type ) { case TE_BEAMTORUS: - //GL_Cull( GL_NONE ); + // GL_Cull( GL_NONE ); TriBegin( TRI_TRIANGLE_STRIP ); R_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); TriEnd(); break; case TE_BEAMDISK: - //GL_Cull( GL_NONE ); + // GL_Cull( GL_NONE ); TriBegin( TRI_TRIANGLE_STRIP ); R_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); TriEnd(); break; case TE_BEAMCYLINDER: - //GL_Cull( GL_NONE ); + // GL_Cull( GL_NONE ); TriBegin( TRI_TRIANGLE_STRIP ); R_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); TriEnd(); @@ -1122,14 +1136,14 @@ void R_BeamDraw( BEAM *pbeam, float frametime ) TriEnd(); break; case TE_BEAMRING: - //GL_Cull( GL_NONE ); + // GL_Cull( GL_NONE ); TriBegin( TRI_TRIANGLE_STRIP ); R_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); TriEnd(); break; } - //GL_Cull( GL_FRONT ); + // GL_Cull( GL_FRONT ); r_stats.c_view_beams_count++; } @@ -1159,9 +1173,10 @@ passed through this */ static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) { - model_t *sprite = gEngfuncs.pfnGetModelByIndex( modelIndex ); + model_t *sprite = CL_ModelHandle( modelIndex ); - if( !sprite ) return; + if( !sprite ) + return; pbeam->type = BEAM_POINTS; pbeam->modelIndex = modelIndex; @@ -1173,16 +1188,17 @@ static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, VectorCopy( end, pbeam->target ); VectorSubtract( end, start, pbeam->delta ); - pbeam->freq = speed * gpGlobals->time; - pbeam->die = life + gpGlobals->time; + pbeam->freq = speed * gp_cl->time; + pbeam->die = life + gp_cl->time; pbeam->amplitude = amplitude; pbeam->brightness = brightness; pbeam->width = width; pbeam->speed = speed; if( amplitude >= 0.50f ) - pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels - else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels + else + pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels pbeam->pFollowModel = NULL; pbeam->flags = 0; @@ -1197,26 +1213,26 @@ R_BeamDrawCustomEntity initialize beam from server entity ============== */ -void R_BeamDrawCustomEntity( cl_entity_t *ent ) +static void R_BeamDrawCustomEntity( cl_entity_t *ent ) { - BEAM beam; - float amp = ent->curstate.body / 100.0f; - float blend = CL_FxBlend( ent ) / 255.0f; - float r, g, b; - int beamFlags; + BEAM beam; + float amp = ent->curstate.body / 100.0f; + float blend = CL_FxBlend( ent ) / 255.0f; + float r, g, b; + int beamFlags; r = ent->curstate.rendercolor.r / 255.0f; g = ent->curstate.rendercolor.g / 255.0f; b = ent->curstate.rendercolor.b / 255.0f; - R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetup( &beam, ent->origin, ent->curstate.angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); beam.pFollowModel = NULL; switch( ent->curstate.rendermode & 0x0F ) { case BEAM_ENTPOINT: - beam.type = TE_BEAMPOINTS; + beam.type = TE_BEAMPOINTS; if( ent->curstate.sequence ) { SetBits( beam.flags, FBEAM_STARTENTITY ); @@ -1229,13 +1245,13 @@ void R_BeamDrawCustomEntity( cl_entity_t *ent ) } break; case BEAM_ENTS: - beam.type = TE_BEAMPOINTS; + beam.type = TE_BEAMPOINTS; SetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); beam.startEntity = ent->curstate.sequence; beam.endEntity = ent->curstate.skin; break; case BEAM_HOSE: - beam.type = TE_BEAMHOSE; + beam.type = TE_BEAMHOSE; break; case BEAM_POINTS: // already set up @@ -1270,11 +1286,11 @@ draw beam loop */ void GAME_EXPORT CL_DrawBeams( int fTrans, BEAM *active_beams ) { - BEAM *pBeam; - int i, flags; + BEAM *pBeam; + int i, flags; - //pglShadeModel( GL_SMOOTH ); - //pglDepthMask( fTrans ? GL_FALSE : GL_TRUE ); + // pglShadeModel( GL_SMOOTH ); + // pglDepthMask( fTrans ? GL_FALSE : GL_TRUE ); // server beams don't allocate beam chains // all params are stored in cl_entity_t @@ -1304,9 +1320,9 @@ void GAME_EXPORT CL_DrawBeams( int fTrans, BEAM *active_beams ) if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID )) continue; - R_BeamDraw( pBeam, gpGlobals->time - gpGlobals->oldtime ); + R_BeamDraw( pBeam, gp_cl->time - gp_cl->oldtime ); } - //pglShadeModel( GL_FLAT ); - //pglDepthMask( GL_TRUE ); + // pglShadeModel( GL_FLAT ); + // pglDepthMask( GL_TRUE ); } diff --git a/ref/soft/r_bsp.c b/ref/soft/r_bsp.c index 79ad14c8..074f7e19 100644 --- a/ref/soft/r_bsp.c +++ b/ref/soft/r_bsp.c @@ -24,25 +24,25 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // current entity info // -vec3_t r_entorigin; // the currently rendering entity in world - // coordinates +vec3_t r_entorigin; // the currently rendering entity in world +// coordinates -float entity_rotation[3][3]; +float entity_rotation[3][3]; -int r_currentbkey; +int r_currentbkey; typedef enum {touchessolid, drawnode, nodrawnode} solidstate_t; -#define MAX_BMODEL_VERTS 1000 // 12K -#define MAX_BMODEL_EDGES 2000 // 24K +#define MAX_BMODEL_VERTS 1000 // 12K +#define MAX_BMODEL_EDGES 2000 // 24K -static mvertex_t *pbverts; -static bedge_t *pbedges; -static int numbverts, numbedges; +static mvertex_t *pbverts; +static bedge_t *pbedges; +static int numbverts, numbedges; -static mvertex_t *pfrontenter, *pfrontexit; +static mvertex_t *pfrontenter, *pfrontexit; -static qboolean makeclippededge; +static qboolean makeclippededge; @@ -51,44 +51,44 @@ static qboolean makeclippededge; R_ConcatRotations ================ */ -void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) +static void R_ConcatRotations( float in1[3][3], float in2[3][3], float out[3][3] ) { - out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + - in1[0][2] * in2[2][0]; - out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + - in1[0][2] * in2[2][1]; - out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + - in1[0][2] * in2[2][2]; - out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + - in1[1][2] * in2[2][0]; - out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + - in1[1][2] * in2[2][1]; - out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + - in1[1][2] * in2[2][2]; - out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + - in1[2][2] * in2[2][0]; - out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + - in1[2][2] * in2[2][1]; - out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + - in1[2][2] * in2[2][2]; + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; } -//=========================================================================== +// =========================================================================== /* ================ R_EntityRotate ================ */ -void R_EntityRotate (vec3_t vec) +static void R_EntityRotate( vec3_t vec ) { - vec3_t tvec; + vec3_t tvec; - VectorCopy (vec, tvec); - vec[0] = DotProduct (entity_rotation[0], tvec); - vec[1] = DotProduct (entity_rotation[1], tvec); - vec[2] = DotProduct (entity_rotation[2], tvec); + VectorCopy( vec, tvec ); + vec[0] = DotProduct( entity_rotation[0], tvec ); + vec[1] = DotProduct( entity_rotation[1], tvec ); + vec[2] = DotProduct( entity_rotation[2], tvec ); } @@ -97,9 +97,9 @@ void R_EntityRotate (vec3_t vec) R_RotateBmodel ================ */ -void R_RotateBmodel (void) +void R_RotateBmodel( void ) { - float angle, s, c, temp1[3][3], temp2[3][3], temp3[3][3]; + float angle, s, c, temp1[3][3], temp2[3][3], temp3[3][3]; // TODO: should use a look-up table // TODO: should really be stored with the entity instead of being reconstructed @@ -109,8 +109,8 @@ void R_RotateBmodel (void) // yaw angle = RI.currententity->angles[YAW]; angle = angle * M_PI_F * 2 / 360.0f; - s = sin(angle); - c = cos(angle); + s = sin( angle ); + c = cos( angle ); temp1[0][0] = c; temp1[0][1] = s; @@ -126,8 +126,8 @@ void R_RotateBmodel (void) // pitch angle = RI.currententity->angles[PITCH]; angle = angle * M_PI_F * 2 / 360.0f; - s = sin(angle); - c = cos(angle); + s = sin( angle ); + c = cos( angle ); temp2[0][0] = c; temp2[0][1] = 0; @@ -139,13 +139,13 @@ void R_RotateBmodel (void) temp2[2][1] = 0; temp2[2][2] = c; - R_ConcatRotations (temp2, temp1, temp3); + R_ConcatRotations( temp2, temp1, temp3 ); // roll angle = RI.currententity->angles[ROLL]; - angle = angle * M_PI_F*2 / 360.0f; - s = sin(angle); - c = cos(angle); + angle = angle * M_PI_F * 2 / 360.0f; + s = sin( angle ); + c = cos( angle ); temp1[0][0] = 1; temp1[0][1] = 0; @@ -157,36 +157,32 @@ void R_RotateBmodel (void) temp1[2][1] = -s; temp1[2][2] = c; - R_ConcatRotations (temp1, temp3, entity_rotation); + R_ConcatRotations( temp1, temp3, entity_rotation ); // // rotate modelorg and the transformation matrix // - R_EntityRotate (tr.modelorg); - R_EntityRotate (RI.vforward); - R_EntityRotate (RI.vright); - R_EntityRotate (RI.vup); + R_EntityRotate( tr.modelorg ); + R_EntityRotate( RI.vforward ); + R_EntityRotate( RI.vright ); + R_EntityRotate( RI.vup ); - R_TransformFrustum (); + R_TransformFrustum(); } -#if 0 /* ================ R_RecursiveClipBPoly - -Clip a bmodel poly down the world bsp tree ================ */ -void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) +static void R_RecursiveClipBPoly( model_t *mod, bedge_t *pedges, mnode_t *pnode, msurface_t *psurf ) { - bedge_t *psideedges[2], *pnextedge, *ptedge; - int i, side, lastside; - float dist, frac, lastdist; - mplane_t *splitplane, tplane; - mvertex_t *pvert, *plastvert, *ptvert; - mnode_t *pn; - int area; + bedge_t *psideedges[2], *pnextedge, *ptedge; + int i, side, lastside; + float dist, frac, lastdist; + mplane_t *splitplane, tplane; + mvertex_t *pvert, *plastvert, *ptvert; + mnode_t *pn; psideedges[0] = psideedges[1] = NULL; @@ -195,62 +191,62 @@ void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) // transform the BSP plane into model space // FIXME: cache these? splitplane = pnode->plane; - tplane.dist = splitplane->dist - - DotProduct(r_entorigin, splitplane->normal); - tplane.normal[0] = DotProduct (entity_rotation[0], splitplane->normal); - tplane.normal[1] = DotProduct (entity_rotation[1], splitplane->normal); - tplane.normal[2] = DotProduct (entity_rotation[2], splitplane->normal); + tplane.dist = splitplane->dist + - DotProduct( r_entorigin, splitplane->normal ); + tplane.normal[0] = DotProduct( entity_rotation[0], splitplane->normal ); + tplane.normal[1] = DotProduct( entity_rotation[1], splitplane->normal ); + tplane.normal[2] = DotProduct( entity_rotation[2], splitplane->normal ); // clip edges to BSP plane - for ( ; pedges ; pedges = pnextedge) + for( ; pedges; pedges = pnextedge ) { pnextedge = pedges->pnext; - // set the status for the last point as the previous point - // FIXME: cache this stuff somehow? + // set the status for the last point as the previous point + // FIXME: cache this stuff somehow? plastvert = pedges->v[0]; - lastdist = DotProduct (plastvert->position, tplane.normal) - - tplane.dist; + lastdist = DotProduct( plastvert->position, tplane.normal ) + - tplane.dist; - if (lastdist > 0) + if( lastdist > 0 ) lastside = 0; else lastside = 1; pvert = pedges->v[1]; - dist = DotProduct (pvert->position, tplane.normal) - tplane.dist; + dist = DotProduct( pvert->position, tplane.normal ) - tplane.dist; - if (dist > 0) + if( dist > 0 ) side = 0; else side = 1; - if (side != lastside) + if( side != lastside ) { - // clipped - if (numbverts >= MAX_BMODEL_VERTS) + // clipped + if( numbverts >= MAX_BMODEL_VERTS ) return; - // generate the clipped vertex - frac = lastdist / (lastdist - dist); + // generate the clipped vertex + frac = lastdist / ( lastdist - dist ); ptvert = &pbverts[numbverts++]; - ptvert->position[0] = plastvert->position[0] + - frac * (pvert->position[0] - - plastvert->position[0]); - ptvert->position[1] = plastvert->position[1] + - frac * (pvert->position[1] - - plastvert->position[1]); - ptvert->position[2] = plastvert->position[2] + - frac * (pvert->position[2] - - plastvert->position[2]); + ptvert->position[0] = plastvert->position[0] + + frac * ( pvert->position[0] + - plastvert->position[0] ); + ptvert->position[1] = plastvert->position[1] + + frac * ( pvert->position[1] + - plastvert->position[1] ); + ptvert->position[2] = plastvert->position[2] + + frac * ( pvert->position[2] + - plastvert->position[2] ); - // split into two edges, one on each side, and remember entering - // and exiting points - // FIXME: share the clip edge by having a winding direction flag? - if (numbedges >= (MAX_BMODEL_EDGES - 1)) + // split into two edges, one on each side, and remember entering + // and exiting points + // FIXME: share the clip edge by having a winding direction flag? + if( numbedges >= ( MAX_BMODEL_EDGES - 1 )) { - gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); + // gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); return; } @@ -268,9 +264,9 @@ void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) numbedges += 2; - if (side == 0) + if( side == 0 ) { - // entering for front, exiting for back + // entering for front, exiting for back pfrontenter = ptvert; makeclippededge = true; } @@ -282,7 +278,7 @@ void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) } else { - // add the edge to the appropriate side + // add the edge to the appropriate side pedges->pnext = psideedges[side]; psideedges[side] = pedges; } @@ -290,180 +286,11 @@ void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) // if anything was clipped, reconstitute and add the edges along the clip // plane to both sides (but in opposite directions) - if (makeclippededge) + if( makeclippededge ) { - if (numbedges >= (MAX_BMODEL_EDGES - 2)) + if( numbedges >= ( MAX_BMODEL_EDGES - 2 )) { - gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); - return; - } - - ptedge = &pbedges[numbedges]; - ptedge->pnext = psideedges[0]; - psideedges[0] = ptedge; - ptedge->v[0] = pfrontexit; - ptedge->v[1] = pfrontenter; - - ptedge = &pbedges[numbedges + 1]; - ptedge->pnext = psideedges[1]; - psideedges[1] = ptedge; - ptedge->v[0] = pfrontenter; - ptedge->v[1] = pfrontexit; - - numbedges += 2; - } - // draw or recurse further - for (i=0 ; i<2 ; i++) - { - if (psideedges[i]) - { - // draw if we've reached a non-solid leaf, done if all that's left is a - // solid leaf, and continue down the tree if it's not a leaf - pn = pnode->children[i]; - - // we're done with this branch if the node or leaf isn't in the PVS - if (pn->visframe == r_visframecount) - { - if (pn->contents < 0) - { - if (pn->contents != CONTENTS_SOLID) - { - r_currentbkey = ((mleaf_t *)pn)->cluster; - R_RenderBmodelFace (psideedges[i], psurf); - } - } - else - { - R_RecursiveClipBPoly (psideedges[i], pnode->children[i], - psurf); - } - } - } - } - -} -#else -/* -================ -R_RecursiveClipBPoly -================ -*/ -void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) -{ - bedge_t *psideedges[2], *pnextedge, *ptedge; - int i, side, lastside; - float dist, frac, lastdist; - mplane_t *splitplane, tplane; - mvertex_t *pvert, *plastvert, *ptvert; - mnode_t *pn; - - psideedges[0] = psideedges[1] = NULL; - - makeclippededge = false; - -// transform the BSP plane into model space -// FIXME: cache these? - splitplane = pnode->plane; - tplane.dist = splitplane->dist - - DotProduct(r_entorigin, splitplane->normal); - tplane.normal[0] = DotProduct (entity_rotation[0], splitplane->normal); - tplane.normal[1] = DotProduct (entity_rotation[1], splitplane->normal); - tplane.normal[2] = DotProduct (entity_rotation[2], splitplane->normal); - -// clip edges to BSP plane - for ( ; pedges ; pedges = pnextedge) - { - pnextedge = pedges->pnext; - - // set the status for the last point as the previous point - // FIXME: cache this stuff somehow? - plastvert = pedges->v[0]; - lastdist = DotProduct (plastvert->position, tplane.normal) - - tplane.dist; - - if (lastdist > 0) - lastside = 0; - else - lastside = 1; - - pvert = pedges->v[1]; - - dist = DotProduct (pvert->position, tplane.normal) - tplane.dist; - - if (dist > 0) - side = 0; - else - side = 1; - - if (side != lastside) - { - // clipped - if (numbverts >= MAX_BMODEL_VERTS) - return; - - // generate the clipped vertex - frac = lastdist / (lastdist - dist); - ptvert = &pbverts[numbverts++]; - ptvert->position[0] = plastvert->position[0] + - frac * (pvert->position[0] - - plastvert->position[0]); - ptvert->position[1] = plastvert->position[1] + - frac * (pvert->position[1] - - plastvert->position[1]); - ptvert->position[2] = plastvert->position[2] + - frac * (pvert->position[2] - - plastvert->position[2]); - - // split into two edges, one on each side, and remember entering - // and exiting points - // FIXME: share the clip edge by having a winding direction flag? - if (numbedges >= (MAX_BMODEL_EDGES - 1)) - { - //gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); - return; - } - - ptedge = &pbedges[numbedges]; - ptedge->pnext = psideedges[lastside]; - psideedges[lastside] = ptedge; - ptedge->v[0] = plastvert; - ptedge->v[1] = ptvert; - - ptedge = &pbedges[numbedges + 1]; - ptedge->pnext = psideedges[side]; - psideedges[side] = ptedge; - ptedge->v[0] = ptvert; - ptedge->v[1] = pvert; - - numbedges += 2; - - if (side == 0) - { - // entering for front, exiting for back - pfrontenter = ptvert; - makeclippededge = true; - } - else - { - pfrontexit = ptvert; - makeclippededge = true; - } - } - else - { - // add the edge to the appropriate side - pedges->pnext = psideedges[side]; - psideedges[side] = pedges; - } - } - -// if anything was clipped, reconstitute and add the edges along the clip -// plane to both sides (but in opposite directions) - if (makeclippededge) - { - if (numbedges >= (MAX_BMODEL_EDGES - 2)) - { - //gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); + // gEngfuncs.Con_Printf ("Out of edges for bmodel\n"); return; } @@ -483,157 +310,34 @@ void R_RecursiveClipBPoly (bedge_t *pedges, mnode_t *pnode, msurface_t *psurf) } // draw or recurse further - for (i=0 ; i<2 ; i++) + for( i = 0; i < 2; i++ ) { - if (psideedges[i]) + if( psideedges[i] ) { - // draw if we've reached a non-solid leaf, done if all that's left is a - // solid leaf, and continue down the tree if it's not a leaf - pn = pnode->children[i]; + // draw if we've reached a non-solid leaf, done if all that's left is a + // solid leaf, and continue down the tree if it's not a leaf + pn = node_child( pnode, i, mod ); - // we're done with this branch if the node or leaf isn't in the PVS - if (pn->visframe == tr.visframecount) + // we're done with this branch if the node or leaf isn't in the PVS + if( pn->visframe == tr.visframecount ) { - if (pn->contents < 0) + if( pn->contents < 0 ) { - if (pn->contents != CONTENTS_SOLID) + if( pn->contents != CONTENTS_SOLID ) { - //r_currentbkey = ((mleaf_t *)pn)->cluster; - r_currentbkey = LEAF_KEY (((mleaf_t *)pn)); - R_RenderBmodelFace (psideedges[i], psurf); + // r_currentbkey = ((mleaf_t *)pn)->cluster; + r_currentbkey = LEAF_KEY(((mleaf_t *)pn )); + R_RenderBmodelFace( psideedges[i], psurf ); } } else { - R_RecursiveClipBPoly (psideedges[i], pnode->children[i], - psurf); + R_RecursiveClipBPoly( mod, psideedges[i], pn, psurf ); } } } } } -#endif -#if 0 -/* -================ -R_DrawSolidClippedSubmodelPolygons -================ -*/ -void R_DrawSolidClippedSubmodelPolygons (model_t *pmodel) -{ - int i, j, lindex; - vec_t dot; - msurface_t *psurf; - int numsurfaces; - mplane_t *pplane; - mvertex_t bverts[MAX_BMODEL_VERTS]; - bedge_t bedges[MAX_BMODEL_EDGES], *pbedge; - medge_t *pedge, *pedges; - -// FIXME: use bounding-box-based frustum clipping info? - - psurf = &pmodel->surfaces[pmodel->firstmodelsurface]; - numsurfaces = pmodel->nummodelsurfaces; - pedges = pmodel->edges; - - for (i=0 ; iplane; - - dot = DotProduct (modelorg, pplane->normal) - pplane->dist; - - // draw the polygon - if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || - (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) - { - // FIXME: use bounding-box-based frustum clipping info? - - // copy the edges to bedges, flipping if necessary so always - // clockwise winding - // FIXME: if edges and vertices get caches, these assignments must move - // outside the loop, and overflow checking must be done here - pbverts = bverts; - pbedges = bedges; - numbverts = numbedges = 0; - - if (psurf->numedges > 0) - { - pbedge = &bedges[numbedges]; - numbedges += psurf->numedges; - - for (j=0 ; jnumedges ; j++) - { - lindex = pmodel->surfedges[psurf->firstedge+j]; - - if (lindex > 0) - { - pedge = &pedges[lindex]; - pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]]; - pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[1]]; - } - else - { - lindex = -lindex; - pedge = &pedges[lindex]; - pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[1]]; - pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]]; - } - - pbedge[j].pnext = &pbedge[j+1]; - } - - pbedge[j-1].pnext = NULL; // mark end of edges - - R_RecursiveClipBPoly (pbedge, RI.currententity->topnode, psurf); - } - else - { - gEngfuncs.Host_Error ("no edges in bmodel"); - } - } - } -} - - -/* -================ -R_DrawSubmodelPolygons -================ -*/ -void R_DrawSubmodelPolygons (model_t *pmodel, int clipflags) -{ - int i; - vec_t dot; - msurface_t *psurf; - int numsurfaces; - mplane_t *pplane; - -// FIXME: use bounding-box-based frustum clipping info? - - psurf = &pmodel->surfaces[pmodel->firstmodelsurface]; - numsurfaces = pmodel->nummodelsurfaces; - - for (i=0 ; iplane; - - dot = DotProduct (modelorg, pplane->normal) - pplane->dist; - - // draw the polygon - if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || - (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) - { - r_currentkey = ((mleaf_t *)RI.currententity->topnode)->cluster; - - // FIXME: use bounding-box-based frustum clipping info? - R_RenderFace (psurf, clipflags); - } - } -} - -#else /* ================ @@ -642,24 +346,24 @@ R_DrawSolidClippedSubmodelPolygons Bmodel crosses multiple leafs ================ */ -void R_DrawSolidClippedSubmodelPolygons (model_t *pmodel, mnode_t *topnode) +void R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode ) { - int i, j, lindex; - vec_t dot; - msurface_t *psurf; - int numsurfaces; - mplane_t *pplane; - mvertex_t bverts[MAX_BMODEL_VERTS]; - bedge_t bedges[MAX_BMODEL_EDGES], *pbedge; - medge_t *pedge, *pedges; + int i, j, lindex; + vec_t dot; + msurface_t *psurf; + int numsurfaces; + mplane_t *pplane; + mvertex_t bverts[MAX_BMODEL_VERTS]; + bedge_t bedges[MAX_BMODEL_EDGES], *pbedge; + medge16_t *pedge, *pedges; // FIXME: use bounding-box-based frustum clipping info? psurf = &pmodel->surfaces[pmodel->firstmodelsurface]; numsurfaces = pmodel->nummodelsurfaces; - pedges = pmodel->edges; + pedges = pmodel->edges16; - for (i=0 ; iflags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) { @@ -668,33 +372,33 @@ void R_DrawSolidClippedSubmodelPolygons (model_t *pmodel, mnode_t *topnode) if( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist ) continue; } - // find which side of the node we are on + // find which side of the node we are on pplane = psurf->plane; - dot = DotProduct (tr.modelorg, pplane->normal) - pplane->dist; + dot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist; - // draw the polygon - if (( !(psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || - ((psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + // draw the polygon + if(( !( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON )) + || (( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON ))) continue; - // FIXME: use bounding-box-based frustum clipping info? + // FIXME: use bounding-box-based frustum clipping info? - // copy the edges to bedges, flipping if necessary so always - // clockwise winding - // FIXME: if edges and vertices get caches, these assignments must move - // outside the loop, and overflow checking must be done here + // copy the edges to bedges, flipping if necessary so always + // clockwise winding + // FIXME: if edges and vertices get caches, these assignments must move + // outside the loop, and overflow checking must be done here pbverts = bverts; pbedges = bedges; numbverts = numbedges = 0; pbedge = &bedges[numbedges]; numbedges += psurf->numedges; - for (j=0 ; jnumedges ; j++) + for( j = 0; j < psurf->numedges; j++ ) { - lindex = pmodel->surfedges[psurf->firstedge+j]; + lindex = pmodel->surfedges[psurf->firstedge + j]; - if (lindex > 0) + if( lindex > 0 ) { pedge = &pedges[lindex]; pbedge[j].v[0] = &r_pcurrentvertbase[pedge->v[0]]; @@ -708,14 +412,14 @@ void R_DrawSolidClippedSubmodelPolygons (model_t *pmodel, mnode_t *topnode) pbedge[j].v[1] = &r_pcurrentvertbase[pedge->v[0]]; } - pbedge[j].pnext = &pbedge[j+1]; + pbedge[j].pnext = &pbedge[j + 1]; } - pbedge[j-1].pnext = NULL; // mark end of edges + pbedge[j - 1].pnext = NULL; // mark end of edges - //if ( !( psurf->texinfo->flags & ( SURF_TRANS66 | SURF_TRANS33 ) ) ) - R_RecursiveClipBPoly (pbedge, topnode, psurf); - //else + // if ( !( psurf->texinfo->flags & ( SURF_TRANS66 | SURF_TRANS33 ) ) ) + R_RecursiveClipBPoly( pmodel, pbedge, topnode, psurf ); + // else // R_RenderBmodelFace( pbedge, psurf ); } } @@ -728,20 +432,20 @@ R_DrawSubmodelPolygons All in one leaf ================ */ -void R_DrawSubmodelPolygons (model_t *pmodel, int clipflags, mnode_t *topnode) +void R_DrawSubmodelPolygons( model_t *pmodel, int clipflags, mnode_t *topnode ) { - int i; - vec_t dot; - msurface_t *psurf; - int numsurfaces; - mplane_t *pplane; + int i; + vec_t dot; + msurface_t *psurf; + int numsurfaces; + mplane_t *pplane; // FIXME: use bounding-box-based frustum clipping info? psurf = &pmodel->surfaces[pmodel->firstmodelsurface]; numsurfaces = pmodel->nummodelsurfaces; - for (i=0 ; iflags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) { @@ -750,27 +454,23 @@ void R_DrawSubmodelPolygons (model_t *pmodel, int clipflags, mnode_t *topnode) if( r_entorigin[2] + pmodel->mins[2] + 1.0f >= psurf->plane->dist ) continue; } - // find which side of the node we are on + // find which side of the node we are on pplane = psurf->plane; - dot = DotProduct (tr.modelorg, pplane->normal) - pplane->dist; + dot = DotProduct( tr.modelorg, pplane->normal ) - pplane->dist; - // draw the polygon - if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) || - (!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) + // draw the polygon + if((( psurf->flags & SURF_PLANEBACK ) && ( dot < -BACKFACE_EPSILON )) + || ( !( psurf->flags & SURF_PLANEBACK ) && ( dot > BACKFACE_EPSILON ))) { - r_currentkey = LEAF_KEY(((mleaf_t *)topnode)); + r_currentkey = LEAF_KEY(((mleaf_t *)topnode )); - // FIXME: use bounding-box-based frustum clipping info? - R_RenderFace (psurf, clipflags); + // FIXME: use bounding-box-based frustum clipping info? + R_RenderFace( psurf, clipflags ); } } } -#endif - - -int c_drawnode; #if XASH_LOW_MEMORY unsigned short r_leafkeys[MAX_MAP_LEAFS]; #else @@ -781,34 +481,34 @@ int r_leafkeys[MAX_MAP_LEAFS]; R_RecursiveWorldNode ================ */ -void R_RecursiveWorldNode (mnode_t *node, int clipflags) +static void R_RecursiveWorldNode( mnode_t *node, int clipflags ) { - int i, c, side, *pindex; - vec3_t acceptpt, rejectpt; - mplane_t *plane; - msurface_t *surf, **mark; - mleaf_t *pleaf; - double d, dot; + int i, c, side, *pindex; + vec3_t acceptpt, rejectpt; + mplane_t *plane; + msurface_t *surf, **mark; + mleaf_t *pleaf; + double d, dot; - if (node->contents == CONTENTS_SOLID) - return; // solid + if( node->contents == CONTENTS_SOLID ) + return; // solid - if (node->visframe != tr.visframecount) + if( node->visframe != tr.visframecount ) return; // cull the clipping planes if not trivial accept // FIXME: the compiler is doing a lousy job of optimizing here; it could be // twice as fast in ASM - if (clipflags) + if( clipflags ) { - for (i=0 ; i<4 ; i++) + for( i = 0; i < 4; i++ ) { - if (! (clipflags & (1<minmaxs[pindex[1]]; rejectpt[2] = (float)node->minmaxs[pindex[2]]; - d = DotProduct (rejectpt, qfrustum.view_clipplanes[i].normal); + d = DotProduct( rejectpt, qfrustum.view_clipplanes[i].normal ); d -= qfrustum.view_clipplanes[i].dist; - if (d <= 0) + if( d <= 0 ) return; - acceptpt[0] = (float)node->minmaxs[pindex[3+0]]; - acceptpt[1] = (float)node->minmaxs[pindex[3+1]]; - acceptpt[2] = (float)node->minmaxs[pindex[3+2]]; + acceptpt[0] = (float)node->minmaxs[pindex[3 + 0]]; + acceptpt[1] = (float)node->minmaxs[pindex[3 + 1]]; + acceptpt[2] = (float)node->minmaxs[pindex[3 + 2]]; - d = DotProduct (acceptpt, qfrustum.view_clipplanes[i].normal); + d = DotProduct( acceptpt, qfrustum.view_clipplanes[i].normal ); d -= qfrustum.view_clipplanes[i].dist; - if (d >= 0) - clipflags &= ~(1<= 0 ) + clipflags &= ~( 1 << i ); // node is entirely on screen } } // if a leaf node, draw stuff - if (node->contents < 0) + if( node->contents < 0 ) { pleaf = (mleaf_t *)node; mark = pleaf->firstmarksurface; c = pleaf->nummarksurfaces; - if (c) + if( c ) { do { - (*mark)->visframe = tr.framecount; + ( *mark )->visframe = tr.framecount; mark++; - } while (--c); + } + while( --c ); } - // deal with model fragments in this leaf - if (pleaf->efrags) + // deal with model fragments in this leaf + if( pleaf->efrags ) { - gEngfuncs.R_StoreEfrags(&pleaf->efrags,tr.realframecount); + gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount ); } - // pleaf->cluster - LEAF_KEY(pleaf) = r_currentkey; - r_currentkey++; // all bmodels in a leaf share the same key + // pleaf->cluster + LEAF_KEY( pleaf ) = r_currentkey; + r_currentkey++; // all bmodels in a leaf share the same key } else { - // node is just a decision point, so go down the apropriate sides + mnode_t *children[2]; + int firstsurface; - // find which side of the node we are on + // node is just a decision point, so go down the apropriate sides + + // find which side of the node we are on plane = node->plane; - switch (plane->type) + switch( plane->type ) { case PLANE_X: dot = tr.modelorg[0] - plane->dist; @@ -881,58 +585,62 @@ void R_RecursiveWorldNode (mnode_t *node, int clipflags) dot = tr.modelorg[2] - plane->dist; break; default: - dot = DotProduct (tr.modelorg, plane->normal) - plane->dist; + dot = DotProduct( tr.modelorg, plane->normal ) - plane->dist; break; } - if (dot >= 0) + if( dot >= 0 ) side = 0; else side = 1; - // recurse down the children, front side first - R_RecursiveWorldNode (node->children[side], clipflags); + // recurse down the children, front side first + node_children( children, node, WORLDMODEL ); + R_RecursiveWorldNode( children[side], clipflags ); - // draw stuff - c = node->numsurfaces; + // draw stuff + c = node_numsurfaces( node, WORLDMODEL ); + firstsurface = node_firstsurface( node, WORLDMODEL ); - if (c) + if( c ) { - surf = WORLDMODEL->surfaces + node->firstsurface; + surf = WORLDMODEL->surfaces + firstsurface; - if (dot < -BACKFACE_EPSILON) + if( dot < -BACKFACE_EPSILON ) { do { - if ((surf->flags & SURF_PLANEBACK) && - (surf->visframe == tr.framecount)) + if(( surf->flags & SURF_PLANEBACK ) + && ( surf->visframe == tr.framecount )) { - R_RenderFace (surf, clipflags); + R_RenderFace( surf, clipflags ); } surf++; - } while (--c); + } + while( --c ); } - else if (dot > BACKFACE_EPSILON) + else if( dot > BACKFACE_EPSILON ) { do { - if (!(surf->flags & SURF_PLANEBACK) && - (surf->visframe == tr.framecount)) + if( !( surf->flags & SURF_PLANEBACK ) + && ( surf->visframe == tr.framecount )) { - R_RenderFace (surf, clipflags); + R_RenderFace( surf, clipflags ); } surf++; - } while (--c); + } + while( --c ); } - // all surfaces on the same node share the same sequence number + // all surfaces on the same node share the same sequence number r_currentkey++; } - // recurse down the back side - R_RecursiveWorldNode (node->children[!side], clipflags); + // recurse down the back side + R_RecursiveWorldNode( children[!side], clipflags ); } } @@ -941,23 +649,18 @@ void R_RecursiveWorldNode (mnode_t *node, int clipflags) R_RenderWorld ================ */ -void R_RenderWorld (void) +void R_RenderWorld( void ) { - - //if (!r_drawworld->value) - // return; - if ( !RI.drawWorld ) + if( !RI.drawWorld ) return; - c_drawnode=0; - // auto cycle the world frame for texture animation - RI.currententity = gEngfuncs.GetEntityByIndex(0); - //RI.currententity->frame = (int)(gpGlobals->time*2); + RI.currententity = CL_GetEntityByIndex( 0 ); + // RI.currententity->frame = (int)(gp_cl->time*2); - VectorCopy (RI.vieworg, tr.modelorg); + VectorCopy( RI.vieworg, tr.modelorg ); RI.currentmodel = WORLDMODEL; r_pcurrentvertbase = RI.currentmodel->vertexes; - R_RecursiveWorldNode (RI.currentmodel->nodes, 15); + R_RecursiveWorldNode( RI.currentmodel->nodes, 15 ); } diff --git a/ref/soft/r_context.c b/ref/soft/r_context.c index 747e029d..00e02d28 100644 --- a/ref/soft/r_context.c +++ b/ref/soft/r_context.c @@ -15,34 +15,30 @@ GNU General Public License for more details. #include "r_local.h" -ref_api_t gEngfuncs; +ref_api_t gEngfuncs; ref_globals_t *gpGlobals; -gl_globals_t tr; -ref_speeds_t r_stats; -poolhandle_t r_temppool; -viddef_t vid; +ref_client_t *gp_cl; +ref_host_t *gp_host; +gl_globals_t tr; +ref_speeds_t r_stats; +poolhandle_t r_temppool; +viddef_t vid; + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + gEngfuncs._Mem_Free( data, filename, fileline ); +} + +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return gEngfuncs._Mem_Alloc( poolptr, size, clear, filename, fileline ); +} + static void GAME_EXPORT R_ClearScreen( void ) { } -static qboolean GAME_EXPORT IsNormalPass( void ) -{ - return RP_NORMALPASS(); -} - -static void GAME_EXPORT R_IncrementSpeedsCounter( int type ) -{ - switch( type ) - { - case RS_ACTIVE_TENTS: - r_stats.c_active_tents_count++; - break; - default: - gEngfuncs.Host_Error( "R_IncrementSpeedsCounter: unsupported type %d\n", type ); - } -} - static const byte * GAME_EXPORT R_GetTextureOriginalBuffer( unsigned int idx ) { image_t *glt = R_GetTexture( idx ); @@ -59,64 +55,48 @@ CL_FillRGBA ============= */ -static void GAME_EXPORT CL_FillRGBA( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) +static void GAME_EXPORT CL_FillRGBA( int rendermode, float _x, float _y, float _w, float _h, byte r, byte g, byte b, byte a ) { - vid.rendermode = kRenderTransAdd; - _TriColor4ub(r,g,b,a); - Draw_Fill(_x,_y,_w,_h); + vid.rendermode = rendermode; + _TriColor4ub( r, g, b, a ); + Draw_Fill( _x, _y, _w, _h ); } -/* -============= -pfnFillRGBABlend - -============= -*/ -static void GAME_EXPORT CL_FillRGBABlend( float _x, float _y, float _w, float _h, int r, int g, int b, int a ) -{ - vid.rendermode = kRenderTransAlpha; - _TriColor4ub(r,g,b,a); - Draw_Fill(_x,_y,_w,_h); -} void Mod_UnloadTextures( model_t *mod ); -qboolean GAME_EXPORT Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf ) +static qboolean GAME_EXPORT Mod_ProcessRenderData( model_t *mod, qboolean create, const byte *buf ) { - qboolean loaded = true; - - if( create ) - { - - - switch( mod->type ) - { - case mod_studio: - //Mod_LoadStudioModel( mod, buf, loaded ); - break; - case mod_sprite: - Mod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo ); - break; - case mod_alias: - //Mod_LoadAliasModel( mod, buf, &loaded ); - break; - case mod_brush: - // Mod_LoadBrushModel( mod, buf, loaded ); - break; - - default: gEngfuncs.Host_Error( "Mod_LoadModel: unsupported type %d\n", mod->type ); - } - } - - if( loaded && gEngfuncs.drawFuncs->Mod_ProcessUserData ) - gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, create, buf ); + qboolean loaded = false; if( !create ) + { + if( gEngfuncs.drawFuncs->Mod_ProcessUserData ) + gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, false, buf ); Mod_UnloadTextures( mod ); + return true; + } + + switch( mod->type ) + { + case mod_studio: + case mod_brush: + case mod_alias: + loaded = true; + break; + case mod_sprite: + Mod_LoadSpriteModel( mod, buf, &loaded, mod->numtexinfo ); + break; + default: + gEngfuncs.Host_Error( "%s: unsupported type %d\n", __func__, mod->type ); + return false; + } + + if( gEngfuncs.drawFuncs->Mod_ProcessUserData ) + gEngfuncs.drawFuncs->Mod_ProcessUserData( mod, true, buf ); return loaded; } - static int GL_RefGetParm( int parm, int arg ) { image_t *glt; @@ -137,10 +117,10 @@ static int GL_RefGetParm( int parm, int arg ) return glt->srcHeight; case PARM_TEX_GLFORMAT: glt = R_GetTexture( arg ); - return 0; //glt->format; + return 0; // glt->format; case PARM_TEX_ENCODE: glt = R_GetTexture( arg ); - return 0; //glt->encode; + return 0; // glt->encode; case PARM_TEX_MIPCOUNT: glt = R_GetTexture( arg ); return glt->numMips; @@ -151,48 +131,40 @@ static int GL_RefGetParm( int parm, int arg ) Assert( arg >= 0 && arg < 6 ); return tr.skyboxTextures[arg]; case PARM_TEX_SKYTEXNUM: - return 0; //tr.skytexturenum; + return 0; // tr.skytexturenum; case PARM_TEX_LIGHTMAP: arg = bound( 0, arg, MAX_LIGHTMAPS - 1 ); return tr.lightmapTextures[arg]; - case PARM_WIDESCREEN: - return gpGlobals->wideScreen; - case PARM_FULLSCREEN: - return gpGlobals->fullScreen; - case PARM_SCREEN_WIDTH: - return gpGlobals->width; - case PARM_SCREEN_HEIGHT: - return gpGlobals->height; case PARM_TEX_TARGET: glt = R_GetTexture( arg ); - return 0; //glt->target; + return 0; // glt->target; case PARM_TEX_TEXNUM: glt = R_GetTexture( arg ); - return 0; //glt->texnum; + return 0; // glt->texnum; case PARM_TEX_FLAGS: glt = R_GetTexture( arg ); return glt->flags; + case PARM_TEX_MEMORY: + return R_TexMemory(); case PARM_ACTIVE_TMU: - return 0; //glState.activeTMU; + return 0; // glState.activeTMU; case PARM_LIGHTSTYLEVALUE: arg = bound( 0, arg, MAX_LIGHTSTYLES - 1 ); return tr.lightstylevalue[arg]; case PARM_MAX_IMAGE_UNITS: - return 0; //GL_MaxTextureUnits(); + return 0; // GL_MaxTextureUnits(); case PARM_REBUILD_GAMMA: return 0; - case PARM_SURF_SAMPLESIZE: - if( arg >= 0 && arg < WORLDMODEL->numsurfaces ) - return gEngfuncs.Mod_SampleSizeForFace( &WORLDMODEL->surfaces[arg] ); - return LM_SAMPLE_SIZE; case PARM_GL_CONTEXT_TYPE: - return 0; //glConfig.context; + return 0; // glConfig.context; case PARM_GLES_WRAPPER: - return 0; //glConfig.wrapper; + return 0; // glConfig.wrapper; case PARM_STENCIL_ACTIVE: - return 0; //glState.stencilEnabled; + return 0; // glState.stencilEnabled; case PARM_SKY_SPHERE: - return ENGINE_GET_PARM_( parm, arg ) && !tr.fCustomSkybox; + return 0; // ref_soft doesn't support sky sphere + case PARM_TEX_FILTERING: + return 0; // ref_soft doesn't do filtering in general default: return ENGINE_GET_PARM_( parm, arg ); } @@ -203,18 +175,24 @@ static void GAME_EXPORT R_GetDetailScaleForTexture( int texture, float *xScale, { image_t *glt = R_GetTexture( texture ); - if( xScale ) *xScale = glt->xscale; - if( yScale ) *yScale = glt->yscale; + if( xScale ) + *xScale = glt->xscale; + if( yScale ) + *yScale = glt->yscale; } static void GAME_EXPORT R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density ) { image_t *glt = R_GetTexture( texture ); - if( red ) *red = glt->fogParams[0]; - if( green ) *green = glt->fogParams[1]; - if( blue ) *blue = glt->fogParams[2]; - if( density ) *density = glt->fogParams[3]; + if( red ) + *red = glt->fogParams[0]; + if( green ) + *green = glt->fogParams[1]; + if( blue ) + *blue = glt->fogParams[2]; + if( density ) + *density = glt->fogParams[3]; } @@ -244,7 +222,7 @@ static const char * GAME_EXPORT GL_TextureName( unsigned int texnum ) return R_GetTexture( texnum )->name; } -const byte * GAME_EXPORT GL_TextureData( unsigned int texnum ) +static const byte * GAME_EXPORT GL_TextureData( unsigned int texnum ) { rgbdata_t *pic = R_GetTexture( texnum )->original; @@ -253,38 +231,38 @@ const byte * GAME_EXPORT GL_TextureData( unsigned int texnum ) return NULL; } -void Mod_BrushUnloadTextures( model_t *mod ) +static void Mod_BrushUnloadTextures( model_t *mod ) { int i; - gEngfuncs.Con_Printf("Unloading world\n"); + gEngfuncs.Con_Printf( "Unloading world\n" ); tr.map_unload = true; for( i = 0; i < mod->numtextures; i++ ) { texture_t *tx = mod->textures[i]; if( !tx || tx->gl_texturenum == tr.defaultTexture ) - continue; // free slot + continue; // free slot - GL_FreeTexture( tx->gl_texturenum ); // main texture - GL_FreeTexture( tx->fb_texturenum ); // luma texture + GL_FreeTexture( tx->gl_texturenum ); // main texture + GL_FreeTexture( tx->fb_texturenum ); // luma texture } } void Mod_UnloadTextures( model_t *mod ) { - int i, j; + int i, j; Assert( mod != NULL ); switch( mod->type ) { case mod_studio: - //Mod_StudioUnloadTextures( mod->cache.data ); + // Mod_StudioUnloadTextures( mod->cache.data ); break; case mod_alias: - //Mod_AliasUnloadTextures( mod->cache.data ); + // Mod_AliasUnloadTextures( mod->cache.data ); break; case mod_brush: Mod_BrushUnloadTextures( mod ); @@ -292,122 +270,121 @@ void Mod_UnloadTextures( model_t *mod ) case mod_sprite: Mod_SpriteUnloadTextures( mod->cache.data ); break; - default: gEngfuncs.Host_Error( "Mod_UnloadModel: unsupported type %d\n", mod->type ); + default: gEngfuncs.Host_Error( "%s: unsupported type %d\n", __func__, mod->type ); } } -void GAME_EXPORT R_ProcessEntData( qboolean allocate ) +static void GAME_EXPORT R_ProcessEntData( qboolean allocate, cl_entity_t *entities, unsigned int max_entities ) { + tr.entities = entities; + tr.max_entities = max_entities; +} +static void GAME_EXPORT R_Flush( unsigned int flags ) +{ + // stub } // stubs -void GAME_EXPORT GL_SetTexCoordArrayMode( uint mode ) +static void GAME_EXPORT GL_SetTexCoordArrayMode( uint mode ) { } -void GAME_EXPORT GL_BackendStartFrame( void ) +static void GAME_EXPORT GL_BackendStartFrame( void ) { } -void GAME_EXPORT GL_BackendEndFrame( void ) +static void GAME_EXPORT GL_BackendEndFrame( void ) { } -void GAME_EXPORT GL_SetRenderMode(int mode) +void GAME_EXPORT GL_SetRenderMode( int mode ) { vid.rendermode = mode; /// TODO: table shading/blending??? /// maybe, setup block drawing function pointers here } -void GAME_EXPORT R_ShowTextures( void ) +static void GAME_EXPORT R_ShowTextures( void ) { // textures undone too } -void GAME_EXPORT R_ShowTree( void ) +static void GAME_EXPORT R_SetupSky( int *skyboxTextures ) { - // do we really need this here??? + int i; + + // TODO: R_UnloadSkybox(); + if( !skyboxTextures ) + return; + + for( i = 0; i < SKYBOX_MAX_SIDES; i++ ) + tr.skyboxTextures[i] = skyboxTextures[i]; } -void GAME_EXPORT R_SetupSky(const char *skyboxname) -{ - -} - -qboolean GAME_EXPORT VID_ScreenShot(const char *filename, int shot_type) -{ - return false; -} - -qboolean GAME_EXPORT VID_CubemapShot(const char *base, uint size, const float *vieworg, qboolean skyshot) +qboolean GAME_EXPORT VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ) { // cubemaps? in my softrender??? return false; } -void R_InitSkyClouds(mip_t *mt, texture_t *tx, qboolean custom_palette) +static void GAME_EXPORT R_SetSkyCloudsTextures( int solidskyTexture, int alphaskyTexture ) +{ + tr.solidskyTexture = solidskyTexture; + tr.alphaskyTexture = alphaskyTexture; +} + +static void GAME_EXPORT GL_SubdivideSurface( model_t *mod, msurface_t *fa ) { } -void GAME_EXPORT GL_SubdivideSurface(msurface_t *fa) +static void GAME_EXPORT DrawSingleDecal( decal_t *pDecal, msurface_t *fa ) { } -void GAME_EXPORT DrawSingleDecal(decal_t *pDecal, msurface_t *fa) +static void GAME_EXPORT GL_SelectTexture( int texture ) { } -void GAME_EXPORT GL_SelectTexture(int texture) +static void GAME_EXPORT GL_LoadTexMatrixExt( const float *glmatrix ) { } -void GAME_EXPORT GL_LoadTexMatrixExt(const float *glmatrix) +static void GAME_EXPORT GL_LoadIdentityTexMatrix( void ) { } -void GAME_EXPORT GL_LoadIdentityTexMatrix( void ) +static void GAME_EXPORT GL_CleanUpTextureUnits( int last ) { } -void GAME_EXPORT GL_CleanUpTextureUnits(int last) +static void GAME_EXPORT GL_TexGen( unsigned int coord, unsigned int mode ) { } -void GAME_EXPORT GL_TexGen(unsigned int coord, unsigned int mode) +static void GAME_EXPORT GL_TextureTarget( uint target ) { } -void GAME_EXPORT GL_TextureTarget(uint target) +void GAME_EXPORT Mod_SetOrthoBounds( const float *mins, const float *maxs ) { } -void GAME_EXPORT GL_BuildLightmaps( void ) -{ - CL_RunLightStyles(); -} - -void GAME_EXPORT Mod_SetOrthoBounds(const float *mins, const float *maxs) -{ - -} - -qboolean GAME_EXPORT R_SpeedsMessage(char *out, size_t size) +qboolean GAME_EXPORT R_SpeedsMessage( char *out, size_t size ) { return false; } @@ -417,17 +394,33 @@ byte *GAME_EXPORT Mod_GetCurrentVis( void ) return NULL; } -const char *R_GetConfigName( void ) +static void GAME_EXPORT VGUI_UploadTextureBlock( int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) +{ +} + +static void GAME_EXPORT VGUI_SetupDrawing( qboolean rect ) +{ +} + +static void GAME_EXPORT R_OverrideTextureSourceSize( unsigned int texnum, uint srcWidth, uint srcHeight ) +{ + image_t *tx = R_GetTexture( texnum ); + + tx->srcWidth = srcWidth; + tx->srcHeight = srcHeight; +} + +static const char *R_GetConfigName( void ) { return "ref_soft"; // software specific cvars will go to ref_soft.cfg } -static void* GAME_EXPORT R_GetProcAddress( const char *name ) +static void * GAME_EXPORT R_GetProcAddress( const char *name ) { return gEngfuncs.GL_GetProcAddress( name ); } -ref_interface_t gReffuncs = +static const ref_interface_t gReffuncs = { R_Init, R_Shutdown, @@ -438,6 +431,7 @@ ref_interface_t gReffuncs = GL_InitExtensions, GL_ClearExtensions, + R_GammaChanged, R_BeginFrame, R_RenderScene, R_EndFrame, @@ -453,6 +447,7 @@ ref_interface_t gReffuncs = R_AddEntity, CL_AddCustomBeam, R_ProcessEntData, + R_Flush, R_ShowTextures, @@ -464,9 +459,7 @@ ref_interface_t gReffuncs = R_Set2DMode, R_DrawStretchRaw, R_DrawStretchPic, - R_DrawTileClear, CL_FillRGBA, - CL_FillRGBABlend, R_WorldToScreen, VID_ScreenShot, @@ -483,14 +476,13 @@ ref_interface_t gReffuncs = R_StudioLerpMovement, CL_InitStudioAPI, - R_InitSkyClouds, + R_SetSkyCloudsTextures, GL_SubdivideSurface, CL_RunLightStyles, R_GetSpriteParms, R_GetSpriteTexture, - Mod_LoadMapSprite, Mod_ProcessRenderData, Mod_StudioLoadTextures, @@ -515,6 +507,7 @@ ref_interface_t gReffuncs = GL_LoadTextureArray, GL_CreateTextureArray, GL_FreeTexture, + R_OverrideTextureSourceSize, DrawSingleDecal, R_DecalSetupVerts, @@ -560,35 +553,23 @@ ref_interface_t gReffuncs = TriFogParams, TriCullFace, - VGUI_DrawInit, - VGUI_DrawShutdown, - VGUI_SetupDrawingText, - VGUI_SetupDrawingRect, - VGUI_SetupDrawingImage, - VGUI_BindTexture, - VGUI_EnableTexture, - VGUI_CreateTexture, - VGUI_UploadTexture, + VGUI_SetupDrawing, VGUI_UploadTextureBlock, - VGUI_DrawQuad, - VGUI_GetTextureSizes, - VGUI_GenerateTexture, }; -int EXPORT GAME_EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ) +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ); +int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, ref_globals_t *globals ) { if( version != REF_API_VERSION ) return 0; // fill in our callbacks - memcpy( funcs, &gReffuncs, sizeof( ref_interface_t )); - memcpy( &gEngfuncs, engfuncs, sizeof( ref_api_t )); + *funcs = gReffuncs; + gEngfuncs = *engfuncs; gpGlobals = globals; + gp_cl = (ref_client_t *)ENGINE_GET_PARM( PARM_GET_CLIENT_PTR ); + gp_host = (ref_host_t *)ENGINE_GET_PARM( PARM_GET_HOST_PTR ); + return REF_API_VERSION; } - -void EXPORT GetRefHumanReadableName( char *out, size_t size ) -{ - Q_strncpy( out, "Software", size ); -} diff --git a/ref/soft/r_decals.c b/ref/soft/r_decals.c index e3d4805a..7877a153 100644 --- a/ref/soft/r_decals.c +++ b/ref/soft/r_decals.c @@ -15,44 +15,44 @@ GNU General Public License for more details. #include "r_local.h" -#define DECAL_OVERLAP_DISTANCE 2 -#define DECAL_DISTANCE 4 // too big values produce more clipped polygons -#define MAX_DECALCLIPVERT 32 // produced vertexes of fragmented decal -#define DECAL_CACHEENTRY 256 // MUST BE POWER OF 2 or code below needs to change! -#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE +#define DECAL_OVERLAP_DISTANCE 2 +#define DECAL_DISTANCE 4 // too big values produce more clipped polygons +#define MAX_DECALCLIPVERT 32 // produced vertexes of fragmented decal +#define DECAL_CACHEENTRY 256 // MUST BE POWER OF 2 or code below needs to change! +#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE // empirically determined constants for minimizing overalpping decals -#define MAX_OVERLAP_DECALS 6 -#define DECAL_OVERLAP_DIST 8 -#define MIN_DECAL_SCALE 0.01f -#define MAX_DECAL_SCALE 16.0f +#define MAX_OVERLAP_DECALS 6 +#define DECAL_OVERLAP_DIST 8 +#define MIN_DECAL_SCALE 0.01f +#define MAX_DECAL_SCALE 16.0f // clip edges -#define LEFT_EDGE 0 -#define RIGHT_EDGE 1 -#define TOP_EDGE 2 -#define BOTTOM_EDGE 3 +#define LEFT_EDGE 0 +#define RIGHT_EDGE 1 +#define TOP_EDGE 2 +#define BOTTOM_EDGE 3 // This structure contains the information used to create new decals typedef struct { - vec3_t m_Position; // world coordinates of the decal center - model_t *m_pModel; // the model the decal is going to be applied in - int m_iTexture; // The decal material - int m_Size; // Size of the decal (in world coords) - int m_Flags; - int m_Entity; // Entity the decal is applied to. - float m_scale; - int m_decalWidth; - int m_decalHeight; - vec3_t m_Basis[3]; + vec3_t m_Position; // world coordinates of the decal center + model_t *m_pModel; // the model the decal is going to be applied in + int m_iTexture; // The decal material + int m_Size; // Size of the decal (in world coords) + int m_Flags; + int m_Entity; // Entity the decal is applied to. + float m_scale; + int m_decalWidth; + int m_decalHeight; + vec3_t m_Basis[3]; } decalinfo_t; -static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE]; -static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE]; +static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE]; +static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE]; -decal_t gDecalPool[MAX_RENDER_DECALS]; -static int gDecalCount; +decal_t gDecalPool[MAX_RENDER_DECALS]; +static int gDecalCount; void R_ClearDecals( void ) { @@ -63,7 +63,7 @@ void R_ClearDecals( void ) // unlink pdecal from any surface it's attached to static void R_DecalUnlink( decal_t *pdecal ) { - decal_t *tmp; + decal_t *tmp; if( pdecal->psurface ) { @@ -74,7 +74,8 @@ static void R_DecalUnlink( decal_t *pdecal ) else { tmp = pdecal->psurface->pdecals; - if( !tmp ) gEngfuncs.Host_Error( "D_DecalUnlink: bad decal list\n" ); + if( !tmp ) + gEngfuncs.Host_Error( "%s: bad decal list\n", __func__ ); while( tmp->pnext ) { @@ -100,16 +101,17 @@ static void R_DecalUnlink( decal_t *pdecal ) // as each surface needs it's own. static decal_t *R_DecalAlloc( decal_t *pdecal ) { - int limit = MAX_RENDER_DECALS; + int limit = MAX_RENDER_DECALS; if( r_decals->value < limit ) limit = r_decals->value; - if( !limit ) return NULL; + if( !limit ) + return NULL; if( !pdecal ) { - int count = 0; + int count = 0; // check for the odd possiblity of infinte loop do @@ -120,7 +122,8 @@ static decal_t *R_DecalAlloc( decal_t *pdecal ) pdecal = &gDecalPool[gDecalCount]; // reuse next decal gDecalCount++; count++; - } while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit ); + } + while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit ); } // if decal is already linked to a surface, unlink it. @@ -129,59 +132,40 @@ static decal_t *R_DecalAlloc( decal_t *pdecal ) return pdecal; } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // find decal image and grab size from it -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- static void R_GetDecalDimensions( int texture, int *width, int *height ) { - if( width ) *width = 1; // to avoid divide by zero - if( height ) *height = 1; + if( width ) + *width = 1; // to avoid divide by zero + if( height ) + *height = 1; R_GetTextureParms( width, height, texture ); } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // compute the decal basis based on surface normal -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ) { - vec3_t surfaceNormal; + vec3_t surfaceNormal; // setup normal if( surf->flags & SURF_PLANEBACK ) VectorNegate( surf->plane->normal, surfaceNormal ); - else VectorCopy( surf->plane->normal, surfaceNormal ); + else + VectorCopy( surf->plane->normal, surfaceNormal ); VectorNormalize2( surfaceNormal, textureSpaceBasis[2] ); -#if 0 - if( FBitSet( flags, FDECAL_CUSTOM )) - { - vec3_t pSAxis = { 1, 0, 0 }; - - // T = S cross N - CrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] ); - - // Name sure they aren't parallel or antiparallel - // In that case, fall back to the normal algorithm. - if( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 ) - { - // S = N cross T - CrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] ); - - VectorNormalizeFast( textureSpaceBasis[0] ); - VectorNormalizeFast( textureSpaceBasis[1] ); - return; - } - // Fall through to the standard algorithm for parallel or antiparallel - } -#endif VectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] ); VectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] ); } -void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +static void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) { - int width, height; + int width, height; // Compute the non-scaled decal basis R_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis ); @@ -199,10 +183,10 @@ void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int textu } // Build the initial list of vertices from the surface verts into the global array, 'verts'. -void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) +static void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) { - float *v; - int i; + float *v; + int i; if( !surf->polys ) return; @@ -216,7 +200,7 @@ void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t tex } // Figure out where the decal maps onto the surface. -void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +static void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) { R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); @@ -231,7 +215,7 @@ void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t te // Clip polygon to decal in texture space // JAY: This code is lame, change it later. It does way too much work per frame // It can be made to recursively call the clipping code and only copy the vertex list once -int R_ClipInside( float *vert, int edge ) +static int R_ClipInside( float *vert, int edge ) { switch( edge ) { @@ -255,9 +239,9 @@ int R_ClipInside( float *vert, int edge ) return 0; } -void R_ClipIntersect( float *one, float *two, float *out, int edge ) +static void R_ClipIntersect( float *one, float *two, float *out, int edge ) { - float t; + float t; // t is the parameter of the line between one and two clipped to the edge // or the fraction of the clipped point between one & two @@ -272,36 +256,36 @@ void R_ClipIntersect( float *one, float *two, float *out, int edge ) if( edge == LEFT_EDGE ) { // left - t = ((one[3] - 0.0f) / (one[3] - two[3])); + t = (( one[3] - 0.0f ) / ( one[3] - two[3] )); out[3] = out[5] = 0.0f; } else { // right - t = ((one[3] - 1.0f) / (one[3] - two[3])); + t = (( one[3] - 1.0f ) / ( one[3] - two[3] )); out[3] = out[5] = 1.0f; } - out[4] = one[4] + (two[4] - one[4]) * t; - out[6] = one[6] + (two[6] - one[6]) * t; + out[4] = one[4] + ( two[4] - one[4] ) * t; + out[6] = one[6] + ( two[6] - one[6] ) * t; } else { if( edge == TOP_EDGE ) { // top - t = ((one[4] - 0.0f) / (one[4] - two[4])); + t = (( one[4] - 0.0f ) / ( one[4] - two[4] )); out[4] = out[6] = 0.0f; } else { // bottom - t = ((one[4] - 1.0f) / (one[4] - two[4])); + t = (( one[4] - 1.0f ) / ( one[4] - two[4] )); out[4] = out[6] = 1.0f; } - out[3] = one[3] + (two[3] - one[3]) * t; - out[5] = one[5] + (two[4] - one[5]) * t; + out[3] = one[3] + ( two[3] - one[3] ) * t; + out[5] = one[5] + ( two[4] - one[5] ) * t; } VectorLerp( one, t, two, out ); @@ -309,12 +293,12 @@ void R_ClipIntersect( float *one, float *two, float *out, int edge ) static int SHClip( float *vert, int vertCount, float *out, int edge ) { - int j, outCount; - float *s, *p; + int j, outCount; + float *s, *p; outCount = 0; - s = &vert[(vertCount - 1) * VERTEXSIZE]; + s = &vert[( vertCount - 1 ) * VERTEXSIZE]; for( j = 0; j < vertCount; j++ ) { @@ -356,10 +340,10 @@ static int SHClip( float *vert, int vertCount, float *out, int edge ) return outCount; } -float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) +static float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) { - float *pOutVerts = g_DecalClipVerts[0]; - int outCount; + float *pOutVerts = g_DecalClipVerts[0]; + int outCount; // clip the polygon to the decal texture space outCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE ); @@ -373,13 +357,13 @@ float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int * return pOutVerts; } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Generate clipped vertex list for decal pdecal projected onto polygon psurf -//----------------------------------------------------------------------------- -float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) +// ----------------------------------------------------------------------------- +static float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) { - float decalWorldScale[2]; - vec3_t textureSpaceBasis[3]; + float decalWorldScale[2]; + vec3_t textureSpaceBasis[3]; // figure out where the decal maps onto the surface. R_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); @@ -395,11 +379,11 @@ float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pV // Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) { - float s, t; - mtexinfo_t *tex; - mextrasurf_t *info = surf->info; - float sample_size; - int j; + float s, t; + mtexinfo_t *tex; + mextrasurf_t *info = surf->info; + float sample_size; + int j; sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); tex = surf->texinfo; @@ -410,12 +394,12 @@ static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; s += surf->light_s * sample_size; s += sample_size * 0.5f; - s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + s /= BLOCK_SIZE * sample_size; // fa->texinfo->texture->width; t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; t += surf->light_t * sample_size; t += sample_size * 0.5f; - t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + t /= BLOCK_SIZE * sample_size; // fa->texinfo->texture->height; v[5] = s; v[6] = t; @@ -425,11 +409,11 @@ static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) // Check for intersecting decals on this surface static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount ) { - int texture; - decal_t *plast, *pDecal; - vec3_t decalExtents[2]; - float lastArea = 2; - int mapSize[2]; + int texture; + decal_t *plast, *pDecal; + vec3_t decalExtents[2]; + float lastArea = 2; + int mapSize[2]; plast = NULL; *pcount = 0; @@ -439,8 +423,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // precalculate the extents of decalinfo's decal in world space. R_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] ); - VectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] ); - VectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] ); + VectorScale( decalinfo->m_Basis[0], (( mapSize[0] / decalinfo->m_scale ) * 0.5f ), decalExtents[0] ); + VectorScale( decalinfo->m_Basis[1], (( mapSize[1] / decalinfo->m_scale ) * 0.5f ), decalExtents[1] ); pDecal = surf->pdecals; @@ -452,11 +436,11 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // Don't steal permanent decals if( !FBitSet( pDecal->flags, FDECAL_PERMANENT )) { - vec3_t testBasis[3]; - vec3_t testPosition[2]; - float testWorldScale[2]; - vec2_t vDecalMin, vDecalMax; - vec2_t vUnionMin, vUnionMax; + vec3_t testBasis[3]; + vec3_t testPosition[2]; + float testWorldScale[2]; + vec2_t vDecalMin, vDecalMax; + vec2_t vUnionMin, vUnionMax; R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale ); @@ -467,15 +451,15 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were // clipping a triangle into pDecal's clip space. Vector2Set( vDecalMin, - DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, - DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); VectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] ); VectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] ); Vector2Set( vDecalMax, - DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, - DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); // Now figure out the part of the projection that intersects pDecal's // clip box [0,0,1,1]. @@ -485,7 +469,7 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) { // Figure out how much of this intersects the (0,0) - (1,1) bbox. - float flArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]); + float flArea = ( vUnionMax[0] - vUnionMin[1] ) * ( vUnionMax[1] - vUnionMin[1] ); if( flArea > 0.6f ) { @@ -494,7 +478,7 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int if( !plast || flArea <= lastArea ) { plast = pDecal; - lastArea = flArea; + lastArea = flArea; } } } @@ -511,23 +495,24 @@ R_DecalCreatePoly creates mesh for decal on first rendering ==================== */ -glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) +static glpoly2_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) { - int lnumverts; - glpoly_t *poly; - float *v; - int i; + int lnumverts; + glpoly2_t *poly; + float *v; + int i; return NULL; - if( pdecal->polys ) // already created? + if( pdecal->polys ) // already created? return pdecal->polys; v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts ); - if( !lnumverts ) return NULL; // probably this never happens + if( !lnumverts ) + return NULL; // probably this never happens // allocate glpoly // REFTODO: com_studiocache pool! - poly = Mem_Calloc( r_temppool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); + poly = Mem_Calloc( r_temppool, sizeof( glpoly2_t ) + lnumverts * VERTEXSIZE * sizeof( float )); poly->next = pdecal->polys; poly->flags = surf->flags; pdecal->polys = poly; @@ -548,7 +533,7 @@ glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t // Add the decal to the surface's list of decals. static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo ) { - decal_t *pold; + decal_t *pold; pdecal->pnext = NULL; pold = surf->pdecals; @@ -576,21 +561,24 @@ static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t // alloc clipped poly for decal R_DecalCreatePoly( decalinfo, pdecal, surf ); - //R_AddDecalVBO( pdecal, surf ); + // R_AddDecalVBO( pdecal, surf ); } static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y ) { - decal_t *pdecal, *pold; - int count, vertCount; + decal_t *pdecal, *pold; + int count, vertCount; - if( !surf ) return; // ??? + if( !surf ) + return; // ??? pold = R_DecalIntersect( decalinfo, surf, &count ); - if( count < MAX_OVERLAP_DECALS ) pold = NULL; + if( count < MAX_OVERLAP_DECALS ) + pold = NULL; pdecal = R_DecalAlloc( pold ); - if( !pdecal ) return; // r_decals == 0 ??? + if( !pdecal ) + return; // r_decals == 0 ??? pdecal->flags = decalinfo->m_Flags; @@ -618,13 +606,13 @@ static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, fl R_AddDecalToSurface( pdecal, surf, decalinfo ); } -void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) +static void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) { // get the texture associated with this surface - mtexinfo_t *tex = surf->texinfo; - decal_t *decal = surf->pdecals; - vec4_t textureU, textureV; - float s, t, w, h; + mtexinfo_t *tex = surf->texinfo; + decal_t *decal = surf->pdecals; + vec4_t textureU, textureV; + float s, t, w, h; connstate_t state = ENGINE_GET_PARM( PARM_CONNSTATE ); // we in restore mode @@ -632,7 +620,7 @@ void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) { // NOTE: we may have the decal on this surface that come from another level. // check duplicate with same position and texture - while( decal != NULL ) + while( decal != NULL && decal != decal->pnext ) { if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture ) return; // decal already exists, don't place it again @@ -665,18 +653,18 @@ void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) // (decalWidth * decalBasis[0], decalHeight * decalBasis[1]) // in texture coordinates: - w = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] )) + - fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] )); + w = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] )); - h = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] )) + - fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] )); + h = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] )); // move s,t to upper left corner s -= ( w * 0.5f ); t -= ( h * 0.5f ); // Is this rect within the surface? -- tex width & height are unsigned - if( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h)) + if( s <= -w || t <= -h || s > ( surf->extents[0] + w ) || t > ( surf->extents[1] + h )) { return; // nope } @@ -685,40 +673,42 @@ void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) R_DecalCreate( decalinfo, surf, s, t ); } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // iterate over all surfaces on a node, looking for surfaces to decal -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) { // iterate over all surfaces in the node msurface_t *surf; int i; + int firstsurface, numsurfaces; - surf = model->surfaces + node->firstsurface; + firstsurface = node_firstsurface( node, model ); + numsurfaces = node_numsurfaces( node, model ); - for( i = 0; i < node->numsurfaces; i++, surf++ ) + surf = model->surfaces + firstsurface; + + for( i = 0; i < numsurfaces; i++, surf++ ) { // never apply decals on the water or sky surfaces if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR)) continue; - // we can implement alpha testing without stencil - //if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled ) - //continue; - R_DecalSurface( surf, decalinfo ); } + } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Recursive routine to find surface to apply a decal to. World coordinates of // the decal are passed in r_recalpos like the rest of the engine. This should // be called through R_DecalShoot() -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) { - mplane_t *splitplane; - float dist; + mplane_t *splitplane; + float dist; + mnode_t *children[2]; Assert( node != NULL ); @@ -730,6 +720,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) splitplane = node->plane; dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist; + node_children( children, node, model ); // This is arbitrarily set to 10 right now. In an ideal world we'd have the // exact surface but we don't so, this tells me which planes are "sort of @@ -741,30 +732,30 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) // have a surface normal if( dist > decalinfo->m_Size ) { - R_DecalNode( model, node->children[0], decalinfo ); + R_DecalNode( model, children[0], decalinfo ); } else if( dist < -decalinfo->m_Size ) { - R_DecalNode( model, node->children[1], decalinfo ); + R_DecalNode( model, children[1], decalinfo ); } else { if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) R_DecalNodeSurfaces( model, node, decalinfo ); - R_DecalNode( model, node->children[0], decalinfo ); - R_DecalNode( model, node->children[1], decalinfo ); + R_DecalNode( model, children[0], decalinfo ); + R_DecalNode( model, children[1], decalinfo ); } } // Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords void GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ) { - decalinfo_t decalInfo; - cl_entity_t *ent = NULL; - model_t *model = NULL; - int width, height; - hull_t *hull; + decalinfo_t decalInfo; + cl_entity_t *ent = NULL; + model_t *model = NULL; + int width, height; + hull_t *hull; if( textureIndex <= 0 || textureIndex >= MAX_TEXTURES ) { @@ -774,17 +765,22 @@ void GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex if( entityIndex > 0 ) { - ent = gEngfuncs.GetEntityByIndex( entityIndex ); + ent = CL_GetEntityByIndex( entityIndex ); - if( modelIndex > 0 ) model = gEngfuncs.pfnGetModelByIndex( modelIndex ); - else if( ent != NULL ) model = gEngfuncs.pfnGetModelByIndex( ent->curstate.modelindex ); - else return; + if( modelIndex > 0 ) + model = CL_ModelHandle( modelIndex ); + else if( ent != NULL ) + model = CL_ModelHandle( ent->curstate.modelindex ); + else + return; } else if( modelIndex > 0 ) - model = gEngfuncs.pfnGetModelByIndex( modelIndex ); - else model = WORLDMODEL; + model = CL_ModelHandle( modelIndex ); + else + model = WORLDMODEL; - if( !model ) return; + if( !model ) + return; if( model->type != mod_brush ) { @@ -793,18 +789,18 @@ void GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex } decalInfo.m_pModel = model; - hull = &model->hulls[0]; // always use #0 hull + hull = &model->hulls[0]; // always use #0 hull // NOTE: all the decals at 'first shoot' placed into local space of parent entity // and won't transform again on a next restore, levelchange etc if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE )) { - vec3_t pos_l; + vec3_t pos_l; // transform decal position in local bmodel space if( !VectorIsNull( ent->angles )) { - matrix4x4 matrix; + matrix4x4 matrix; Matrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f ); Matrix4x4_VectorITransform( matrix, pos, pos_l ); @@ -853,9 +849,9 @@ void GAME_EXPORT R_DecalShoot( int textureIndex, int entityIndex, int modelIndex // triangles the same way. float * GAME_EXPORT R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ) { - glpoly_t *p = pDecal->polys; - int i, count; - float *v, *v2; + glpoly2_t *p = pDecal->polys; + int i, count; + float *v, *v2; if( p ) { @@ -888,213 +884,6 @@ float * GAME_EXPORT R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int te return v; } -#if 0 -void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ) -{ - float *v; - int i, numVerts; - - v = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts ); - if( !numVerts ) return; - - GL_Bind( XASH_TEXTURE0, pDecal->texture ); - - pglBegin( GL_POLYGON ); - - for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) - { - pglTexCoord2f( v[3], v[4] ); - pglVertex3fv( v ); - } - - pglEnd(); -} - -void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ) -{ - decal_t *p; - cl_entity_t *e; - - if( !fa->pdecals ) return; - - e = RI.currententity; - Assert( e != NULL ); - - if( single ) - { - if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) - { - pglDepthMask( GL_FALSE ); - pglEnable( GL_BLEND ); - - if( e->curstate.rendermode == kRenderTransAlpha ) - pglDisable( GL_ALPHA_TEST ); - } - - if( e->curstate.rendermode == kRenderTransColor ) - pglEnable( GL_TEXTURE_2D ); - - if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) - GL_Cull( GL_NONE ); - - if( gl_polyoffset->value ) - { - pglEnable( GL_POLYGON_OFFSET_FILL ); - pglPolygonOffset( -1.0f, -gl_polyoffset->value ); - } - } - - if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) - { - mtexinfo_t *tex = fa->texinfo; - - for( p = fa->pdecals; p; p = p->pnext ) - { - if( p->texture ) - { - float *o, *v; - int i, numVerts; - o = R_DecalSetupVerts( p, fa, p->texture, &numVerts ); - - pglEnable( GL_STENCIL_TEST ); - pglStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFF ); - pglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); - - pglStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE ); - pglBegin( GL_POLYGON ); - - for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE ) - { - v[5] = ( DotProduct( v, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width; - v[6] = ( DotProduct( v, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height; - - pglTexCoord2f( v[5], v[6] ); - pglVertex3fv( v ); - } - - pglEnd(); - pglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); - - pglEnable( GL_ALPHA_TEST ); - pglBegin( GL_POLYGON ); - - for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE ) - { - pglTexCoord2f( v[5], v[6] ); - pglVertex3fv( v ); - } - - pglEnd(); - pglDisable( GL_ALPHA_TEST ); - - pglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); - pglStencilFunc( GL_EQUAL, 0, 0xFFFFFFFF ); - pglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); - } - } - } - - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - - if( reverse && e->curstate.rendermode == kRenderTransTexture ) - { - decal_t *list[1024]; - int i, count; - - for( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext ) - if( p->texture ) list[count++] = p; - - for( i = count - 1; i >= 0; i-- ) - DrawSingleDecal( list[i], fa ); - } - else - { - for( p = fa->pdecals; p; p = p->pnext ) - { - if( !p->texture ) continue; - DrawSingleDecal( p, fa ); - } - } - - if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) - pglDisable( GL_STENCIL_TEST ); - - if( single ) - { - if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) - { - pglDepthMask( GL_TRUE ); - pglDisable( GL_BLEND ); - - if( e->curstate.rendermode == kRenderTransAlpha ) - pglEnable( GL_ALPHA_TEST ); - } - - if( gl_polyoffset->value ) - pglDisable( GL_POLYGON_OFFSET_FILL ); - - if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) - GL_Cull( GL_FRONT ); - - if( e->curstate.rendermode == kRenderTransColor ) - pglDisable( GL_TEXTURE_2D ); - - // restore blendfunc here - if( e->curstate.rendermode == kRenderTransAdd || e->curstate.rendermode == kRenderGlow ) - pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - - pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - } -} - -void DrawDecalsBatch( void ) -{ - cl_entity_t *e; - int i; - - if( !tr.num_draw_decals ) - return; - - e = RI.currententity; - Assert( e != NULL ); - - if( e->curstate.rendermode != kRenderTransTexture ) - { - pglEnable( GL_BLEND ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglDepthMask( GL_FALSE ); - } - - if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) - GL_Cull( GL_NONE ); - - if( gl_polyoffset->value ) - { - pglEnable( GL_POLYGON_OFFSET_FILL ); - pglPolygonOffset( -1.0f, -gl_polyoffset->value ); - } - - for( i = 0; i < tr.num_draw_decals; i++ ) - { - DrawSurfaceDecals( tr.draw_decals[i], false, false ); - } - - if( e->curstate.rendermode != kRenderTransTexture ) - { - pglDepthMask( GL_TRUE ); - pglDisable( GL_BLEND ); - pglDisable( GL_ALPHA_TEST ); - } - - if( gl_polyoffset->value ) - pglDisable( GL_POLYGON_OFFSET_FILL ); - - if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) - GL_Cull( GL_FRONT ); - - tr.num_draw_decals = 0; -} -#endif /* ============================================================= @@ -1113,30 +902,31 @@ static qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry ) // Grab surface plane equation if( pdecal->psurface->flags & SURF_PLANEBACK ) VectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); - else VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); + else + VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); return true; } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Purpose: // Input : *pList - // count - // Output : static int -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- static int DecalListAdd( decallist_t *pList, int count ) { - vec3_t tmp; - decallist_t *pdecal; - int i; + vec3_t tmp; + decallist_t *pdecal; + int i; pdecal = pList + count; for( i = 0; i < count; i++ ) { - if( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex ) + if( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex ) { - VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge + VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge if( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE ) return count; @@ -1149,7 +939,7 @@ static int DecalListAdd( decallist_t *pList, int count ) static int DecalDepthCompare( const void *a, const void *b ) { - const decallist_t *elem1, *elem2; + const decallist_t *elem1, *elem2; elem1 = (const decallist_t *)a; elem2 = (const decallist_t *)b; @@ -1162,15 +952,15 @@ static int DecalDepthCompare( const void *a, const void *b ) return 0; } -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // Purpose: Called by CSaveRestore::SaveClientState // Input : *pList - // Output : int -//----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- int GAME_EXPORT R_CreateDecalList( decallist_t *pList ) { - int total = 0; - int i, depth; + int total = 0; + int i, depth; // return 0; // crash on changelevel. API bug? @@ -1178,12 +968,12 @@ int GAME_EXPORT R_CreateDecalList( decallist_t *pList ) { for( i = 0; i < MAX_RENDER_DECALS; i++ ) { - decal_t *decal = &gDecalPool[i]; - decal_t *pdecals; + decal_t *decal = &gDecalPool[i]; + decal_t *pdecals; // decal is in use and is not a custom decal if( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE )) - continue; + continue; // compute depth depth = 0; @@ -1200,7 +990,7 @@ int GAME_EXPORT R_CreateDecalList( decallist_t *pList ) pList[total].scale = decal->scale; R_DecalUnProject( decal, &pList[total] ); - COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name )); // check to see if the decal should be added total = DecalListAdd( pList, total ); @@ -1227,8 +1017,8 @@ remove all decals with specified texture */ void GAME_EXPORT R_DecalRemoveAll( int textureIndex ) { - decal_t *pdecal; - int i; + decal_t *pdecal; + int i; if( textureIndex < 0 || textureIndex >= MAX_TEXTURES ) return; // out of bounds @@ -1255,9 +1045,9 @@ remove all decals from specified entity */ void GAME_EXPORT R_EntityRemoveDecals( model_t *mod ) { - msurface_t *psurf; - decal_t *p; - int i; + msurface_t *psurf; + decal_t *p; + int i; if( !mod || mod->type != mod_brush ) return; @@ -1280,8 +1070,8 @@ used for full decals restart */ void GAME_EXPORT R_ClearAllDecals( void ) { - decal_t *pdecal; - int i; + decal_t *pdecal; + int i; // because gDecalCount may be zeroed after recach the decal limit for( i = 0; i < MAX_RENDER_DECALS; i++ ) diff --git a/ref/soft/r_draw.c b/ref/soft/r_draw.c index 24ff99f7..c01d4697 100644 --- a/ref/soft/r_draw.c +++ b/ref/soft/r_draw.c @@ -22,11 +22,13 @@ R_GetImageParms */ void R_GetTextureParms( int *w, int *h, int texnum ) { - image_t *glt; + image_t *glt; glt = R_GetTexture( texnum ); - if( w ) *w = glt->srcWidth; - if( h ) *h = glt->srcHeight; + if( w ) + *w = glt->srcWidth; + if( h ) + *h = glt->srcHeight; } /* @@ -39,14 +41,18 @@ for sprite models */ void GAME_EXPORT R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite ) { - mspriteframe_t *pFrame; + mspriteframe_t *pFrame; - if( !pSprite || pSprite->type != mod_sprite ) return; // bad model ? + if( !pSprite || pSprite->type != mod_sprite ) + return; // bad model ? pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f ); - if( frameWidth ) *frameWidth = pFrame->width; - if( frameHeight ) *frameHeight = pFrame->height; - if( numFrames ) *numFrames = pSprite->numframes; + if( frameWidth ) + *frameWidth = pFrame->width; + if( frameHeight ) + *frameHeight = pFrame->height; + if( numFrames ) + *numFrames = pSprite->numframes; } int GAME_EXPORT R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) @@ -63,39 +69,40 @@ int GAME_EXPORT R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) Draw_StretchPicImplementation ============= */ -void R_DrawStretchPicImplementation( int x, int y, int w, int h, int s1, int t1, int s2, int t2, image_t *pic ) +static void R_DrawStretchPicImplementation( int x, int y, int w, int h, int s1, int t1, int s2, int t2, image_t *pic ) { - pixel_t *source, *dest; - unsigned int v, u, sv; - unsigned int height; - unsigned int f, fstep; - int skip; - qboolean transparent = false; - pixel_t *buffer; + unsigned int height; + int skip, v; + qboolean transparent = false; + pixel_t *buffer; if( x < 0 ) { - s1 += (-x)*(s2-s1) / w; + s1 += ( -x ) * ( s2 - s1 ) / w; x = 0; } if( x + w > vid.width ) { - s2 -= (x + w - vid.width) * (s2 - s1)/ w ; + s2 -= ( x + w - vid.width ) * ( s2 - s1 ) / w; w = vid.width - x; } if( y + h > vid.height ) { - t2 -= (y + h - vid.height) * (t2 - t1) / h; + t2 -= ( y + h - vid.height ) * ( t2 - t1 ) / h; h = vid.height - y; } if( !pic->pixels[0] || s1 >= s2 || t1 >= t2 ) return; - //gEngfuncs.Con_Printf ("pixels is %p\n", pic->pixels[0] ); + // gEngfuncs.Con_Printf ("pixels is %p\n", pic->pixels[0] ); height = h; - if (y < 0) + + if( y < -h ) // out of display, out of bounds + return; + + if( y < 0 ) { skip = -y; height += y; @@ -104,8 +111,6 @@ void R_DrawStretchPicImplementation( int x, int y, int w, int h, int s1, int t1, else skip = 0; - dest = vid.buffer + y * vid.rowbytes + x; - if( pic->alpha_pixels ) { buffer = pic->alpha_pixels; @@ -115,69 +120,55 @@ void R_DrawStretchPicImplementation( int x, int y, int w, int h, int s1, int t1, buffer = pic->pixels[0]; - #pragma omp parallel for schedule(static) - for (v=0 ; vwidth + s1; + int alpha1 = vid.alpha; + pixel_t *dest = vid.buffer + ( y + v ) * vid.rowbytes + x; + uint sv = ( skip + v ) * ( t2 - t1 ) / h + t1; + uint u, f, fstep; + pixel_t *source = buffer + sv * pic->width + s1; + f = 0; + fstep = (( s2 - s1 ) << 16 ) / w; + + for( u = 0; u < w; u++ ) { - f = 0; - fstep = ((s2-s1) << 16)/w; + pixel_t src = source[f >> 16]; + int alpha = alpha1; + f += fstep; -#if 0 - for (u=0 ; u>16]; - f += fstep; - dest[u+1] = source[f>>16]; - f += fstep; - dest[u+2] = source[f>>16]; - f += fstep; - dest[u+3] = source[f>>16]; - f += fstep; + alpha &= src >> ( 16 - 3 ); + src = src << 3; } -#else - for (u=0 ; u> 8 )] << 8 | ( src & vid.color & 0xff ) | (( src & 0xff ) >> 3 ); + + if( vid.rendermode == kRenderTransAdd ) { - pixel_t src = source[f>>16]; - int alpha = alpha1; - f += fstep; - - if( transparent ) - { - alpha &= src >> ( 16 - 3 ); - src = src << 3; - } - - if( alpha == 0 ) - continue; - - if( vid.color != COLOR_WHITE ) - src = vid.modmap[(src & 0xff00)|(vid.color>>8)] << 8 | (src & vid.color & 0xff) | ((src & 0xff) >> 3); - - if( vid.rendermode == kRenderTransAdd) - { - pixel_t screen = dest[u]; - dest[u] = vid.addmap[(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) | ((src & 0xff) >> 0); - } - else if( alpha < 7) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) ) - { - pixel_t screen = dest[u]; // | 0xff & screen & src ; - dest[u] = BLEND_ALPHA( alpha, src, screen);//vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3); - - } - else - dest[u] = src; - + pixel_t screen = dest[u]; + dest[u] = vid.addmap[( src & 0xff00 ) | ( screen >> 8 )] << 8 | ( screen & 0xff ) | (( src & 0xff ) >> 0 ); } -#endif + else if( vid.rendermode == kRenderScreenFadeModulate ) + { + pixel_t screen = dest[u]; + dest[u] = BLEND_COLOR( screen, vid.color ); + } + else if( alpha < 7 ) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) ) + { + pixel_t screen = dest[u]; // | 0xff & screen & src ; + dest[u] = BLEND_ALPHA( alpha, src, screen ); // vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3); + } + else + dest[u] = src; + } - dest += vid.rowbytes; } } @@ -189,8 +180,8 @@ R_DrawStretchPic */ void GAME_EXPORT R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ) { - image_t *pic = R_GetTexture(texnum); - int width = pic->width, height = pic->height; + image_t *pic = R_GetTexture( texnum ); + int width = pic->width, height = pic->height; // GL_Bind( XASH_TEXTURE0, texnum ); if( s2 > 1.0f || t2 > 1.0f ) return; @@ -198,17 +189,15 @@ void GAME_EXPORT R_DrawStretchPic( float x, float y, float w, float h, float s1, return; if( w < 1.0f || h < 1.0f ) return; - R_DrawStretchPicImplementation(x,y,w,h, width * s1, height * t1, width * s2, height * t2, pic); + R_DrawStretchPicImplementation( x, y, w, h, width * s1, height * t1, width * s2, height * t2, pic ); } -void Draw_Fill (int x, int y, int w, int h) +void Draw_Fill( int x, int y, int w, int h ) { - pixel_t *dest; - unsigned int v, u; - unsigned int height; - int skip; - pixel_t src = vid.color; - int alpha = vid.alpha; + unsigned int height; + int v; + pixel_t src = vid.color; + int alpha = vid.alpha; if( x < 0 ) x = 0; @@ -230,109 +219,34 @@ void Draw_Fill (int x, int y, int w, int h) { if( h <= -y ) return; - skip = -y; height += y; y = 0; } - else - skip = 0; - dest = vid.buffer + y * vid.rowbytes + x; - - #pragma omp parallel for schedule(static) - for (v=0 ; v>16]; - f += fstep; - dest[u+1] = source[f>>16]; - f += fstep; - dest[u+2] = source[f>>16]; - f += fstep; - dest[u+3] = source[f>>16]; - f += fstep; + pixel_t screen = dest[u]; + dest[u] = vid.addmap[( src & 0xff00 ) | ( screen >> 8 )] << 8 | ( screen & 0xff ) | (( src & 0xff ) >> 0 ); } -#else - for (u=0 ; u>8)] << 8 | (screen & 0xff) | ((src & 0xff) >> 0); - } - else if( alpha < 7) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) ) - { - pixel_t screen = dest[u]; // | 0xff & screen & src ; - dest[u] = BLEND_ALPHA( alpha, src, screen);//vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3); - - } - else - dest[u] = src; - + pixel_t screen = dest[u]; // | 0xff & screen & src ; + dest[u] = BLEND_ALPHA( alpha, src, screen ); // vid.alphamap[( alpha << 16)|(src & 0xff00)|(screen>>8)] << 8 | (screen & 0xff) >> 3 | ((src & 0xff) >> 3); } -#endif + else + dest[u] = src; } - dest += vid.rowbytes; - } -} - -/* -============= -Draw_TileClear - -This repeats a 64*64 tile graphic to fill the screen around a sized down -refresh window. -============= -*/ -void GAME_EXPORT R_DrawTileClear( int texnum, int x, int y, int w, int h ) -{ - int tw, th, x2, i, j; - image_t *pic; - pixel_t *psrc, *pdest; - - GL_SetRenderMode( kRenderNormal ); - _TriColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); - GL_Bind( XASH_TEXTURE0, texnum ); - - pic = R_GetTexture( texnum ); - - tw = pic->width; - th = pic->height; - if (x < 0) - { - w += x; - x = 0; - } - if (y < 0) - { - h += y; - y = 0; - } - if (x + w > vid.width) - w = vid.width - x; - if (y + h > vid.height) - h = vid.height - y; - if (w <= 0 || h <= 0) - return; - - x2 = x + w; - pdest = vid.buffer + y*vid.rowbytes; - for (i=0 ; ipixels[0] + tw * ((i+y)&63); - for (j=x ; jwidth, gpGlobals->height ); - pglMatrixMode( GL_PROJECTION ); - pglLoadIdentity(); - pglOrtho( 0, gpGlobals->width, gpGlobals->height, 0, -99999, 99999 ); - pglMatrixMode( GL_MODELVIEW ); - pglLoadIdentity(); - - GL_Cull( GL_NONE ); - - pglDepthMask( GL_FALSE ); - pglDisable( GL_DEPTH_TEST ); - pglEnable( GL_ALPHA_TEST ); - pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); -#endif - - // glState.in2DMode = true; RI.currententity = NULL; RI.currentmodel = NULL; } - else - { -#if 0 - pglDepthMask( GL_TRUE ); - pglEnable( GL_DEPTH_TEST ); - glState.in2DMode = false; - - pglMatrixMode( GL_PROJECTION ); - GL_LoadMatrix( RI.projectionMatrix ); - - pglMatrixMode( GL_MODELVIEW ); - GL_LoadMatrix( RI.worldviewMatrix ); - - GL_Cull( GL_FRONT ); -#endif - } } diff --git a/ref/soft/r_edge.c b/ref/soft/r_edge.c index e5b8e571..a523ed66 100644 --- a/ref/soft/r_edge.c +++ b/ref/soft/r_edge.c @@ -21,76 +21,52 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" -#ifndef id386 -void R_SurfacePatch (void) -{ -} +edge_t *auxedges; +edge_t *r_edges, *edge_p, *edge_max; -void R_EdgeCodeStart (void) -{ -} - -void R_EdgeCodeEnd (void) -{ -} -#endif - - -#if 0 -the complex cases add new polys on most lines, so dont optimize for keeping them the same -have multiple free span lists to try to get better coherence? -low depth complexity -- 1 to 3 or so - -have a sentinal at both ends? -#endif - - -edge_t *auxedges; -edge_t *r_edges, *edge_p, *edge_max; - -surf_t *surfaces, *surface_p, *surf_max; +surf_t *surfaces, *surface_p, *surf_max; // surfaces are generated in back to front order by the bsp, so if a surf // pointer is greater than another one, it should be drawn in front // surfaces[1] is the background, and is used as the active surface stack -edge_t *newedges[MAXHEIGHT]; -edge_t *removeedges[MAXHEIGHT]; +edge_t *newedges[MAXHEIGHT]; +edge_t *removeedges[MAXHEIGHT]; -espan_t *span_p, *max_span_p; +espan_t *span_p, *max_span_p; -int r_currentkey; +int r_currentkey; -int current_iv; +int current_iv; -int edge_head_u_shift20, edge_tail_u_shift20; +int edge_head_u_shift20, edge_tail_u_shift20; -static void (*pdrawfunc)(void); +static void (*pdrawfunc)( void ); -edge_t edge_head; -edge_t edge_tail; +edge_t edge_head; +edge_t edge_tail; -edge_t edge_aftertail; -edge_t edge_sentinel; +edge_t edge_aftertail; +edge_t edge_sentinel; -static float fv; +static float fv; -static int miplevel; +static int miplevel; -float scale_for_mip; +float scale_for_mip; // FIXME: should go away -extern void R_RotateBmodel (void); -extern void R_TransformFrustum (void); +extern void R_RotateBmodel( void ); +extern void R_TransformFrustum( void ); -void R_GenerateSpans (void); -void R_GenerateSpansBackward (void); +void R_GenerateSpans( void ); +void R_GenerateSpansBackward( void ); -void R_LeadingEdge (edge_t *edge); -void R_LeadingEdgeBackwards (edge_t *edge); -void R_TrailingEdge (surf_t *surf, edge_t *edge); +void R_LeadingEdge( edge_t *edge ); +void R_LeadingEdgeBackwards( edge_t *edge ); +void R_TrailingEdge( surf_t *surf, edge_t *edge ); /* @@ -106,20 +82,20 @@ EDGE SCANNING R_BeginEdgeFrame ============== */ -void R_BeginEdgeFrame (void) +void R_BeginEdgeFrame( void ) { - int v; + int v; edge_p = r_edges; edge_max = &r_edges[r_numallocatededges]; - surface_p = &surfaces[2]; // background is surface 1, - // surface 0 is a dummy - surfaces[1].spans = NULL; // no background spans yet - surfaces[1].flags = 0; // SURF_DRAWBACKGROUND; + surface_p = &surfaces[2]; // background is surface 1, + // surface 0 is a dummy + surfaces[1].spans = NULL; // no background spans yet + surfaces[1].flags = 0; // SURF_DRAWBACKGROUND; // put the background behind everything in the world - if (sw_draworder->value) + if( sw_draworder.value ) { pdrawfunc = R_GenerateSpansBackward; surfaces[1].key = 0; @@ -133,15 +109,12 @@ void R_BeginEdgeFrame (void) } // FIXME: set with memset - for (v=RI.vrect.y ; vu >= edgestoadd->u) + if( edgelist->u >= edgestoadd->u ) goto addedge; - edgelist=edgelist->next; -#if 1 - if (edgelist->u >= edgestoadd->u) + edgelist = edgelist->next; + if( edgelist->u >= edgestoadd->u ) goto addedge; - edgelist=edgelist->next; - if (edgelist->u >= edgestoadd->u) + edgelist = edgelist->next; + if( edgelist->u >= edgestoadd->u ) goto addedge; - edgelist=edgelist->next; - if (edgelist->u >= edgestoadd->u) + edgelist = edgelist->next; + if( edgelist->u >= edgestoadd->u ) goto addedge; - edgelist=edgelist->next; -#endif + edgelist = edgelist->next; goto edgesearch; - // insert edgestoadd before edgelist + // insert edgestoadd before edgelist addedge: edgestoadd->next = edgelist; edgestoadd->prev = edgelist->prev; edgelist->prev->next = edgestoadd; edgelist->prev = edgestoadd; - } while ((edgestoadd = next_edge) != NULL); + } + while(( edgestoadd = next_edge ) != NULL ); } -#endif // !id386 - - -#if !id386 - /* ============== R_RemoveEdges ============== */ -void R_RemoveEdges (edge_t *pedge) +static void R_RemoveEdges( edge_t *pedge ) { do { pedge->next->prev = pedge->prev; pedge->prev->next = pedge->next; - } while ((pedge = pedge->nextremove) != NULL); + } + while(( pedge = pedge->nextremove ) != NULL ); } -#endif // !id386 - - -#if !id386 - /* ============== R_StepActiveU ============== */ -void R_StepActiveU (edge_t *pedge) +static void R_StepActiveU( edge_t *pedge ) { - edge_t *pnext_edge, *pwedge; + edge_t *pnext_edge, *pwedge; - while (1) + while( 1 ) { nextedge: pedge->u += pedge->u_step; - if (pedge->u < pedge->prev->u) + if( pedge->u < pedge->prev->u ) goto pushback; pedge = pedge->next; pedge->u += pedge->u_step; - if (pedge->u < pedge->prev->u) + if( pedge->u < pedge->prev->u ) goto pushback; pedge = pedge->next; pedge->u += pedge->u_step; - if (pedge->u < pedge->prev->u) + if( pedge->u < pedge->prev->u ) goto pushback; pedge = pedge->next; pedge->u += pedge->u_step; - if (pedge->u < pedge->prev->u) + if( pedge->u < pedge->prev->u ) goto pushback; pedge = pedge->next; goto nextedge; pushback: - if (pedge == &edge_aftertail) + if( pedge == &edge_aftertail ) return; - // push it back to keep it sorted + // push it back to keep it sorted pnext_edge = pedge->next; - // pull the edge out of the edge list + // pull the edge out of the edge list pedge->next->prev = pedge->prev; pedge->prev->next = pedge->next; - // find out where the edge goes in the edge list + // find out where the edge goes in the edge list pwedge = pedge->prev->prev; - // if( !pwedge ) + // if( !pwedge ) // return; - while (pwedge->u > pedge->u) + while( pwedge->u > pedge->u ) { pwedge = pwedge->prev; - //if( !pwedge ) - //return; + // if( !pwedge ) + // return; } - // put the edge back into the edge list + // put the edge back into the edge list pedge->next = pwedge->next; pedge->prev = pwedge; pedge->next->prev = pedge; pwedge->next = pedge; pedge = pnext_edge; - if (pedge == &edge_tail) + if( pedge == &edge_tail ) return; } } -#endif // !id386 - - /* ============== R_CleanupSpan ============== */ -void R_CleanupSpan (void) +static void R_CleanupSpan( void ) { - surf_t *surf; - int iu; - espan_t *span; + surf_t *surf; + int iu; + espan_t *span; // now that we've reached the right edge of the screen, we're done with any // unfinished surfaces, so emit a span for whatever's on top surf = surfaces[1].next; iu = edge_tail_u_shift20; - if (iu > surf->last_u) + if( iu > surf->last_u ) { span = span_p++; span->u = surf->last_u; @@ -317,7 +277,8 @@ void R_CleanupSpan (void) { surf->spanstate = 0; surf = surf->next; - } while (surf != &surfaces[1]); + } + while( surf != &surfaces[1] ); } @@ -326,11 +287,11 @@ void R_CleanupSpan (void) R_LeadingEdgeBackwards ============== */ -void R_LeadingEdgeBackwards (edge_t *edge) +void R_LeadingEdgeBackwards( edge_t *edge ) { - espan_t *span; - surf_t *surf, *surf2; - int iu; + espan_t *span; + surf_t *surf, *surf2; + int iu; // it's adding a new surface in, so find the correct place surf = &surfaces[edge->surfs[1]]; @@ -338,19 +299,19 @@ void R_LeadingEdgeBackwards (edge_t *edge) // don't start a span if this is an inverted span, with the end // edge preceding the start edge (that is, we've already seen the // end edge) - if (++surf->spanstate == 1) + if( ++surf->spanstate == 1 ) { surf2 = surfaces[1].next; - if (surf->key > surf2->key) + if( surf->key > surf2->key ) goto newtop; - // if it's two surfaces on the same plane, the one that's already - // active is in front, so keep going unless it's a bmodel - if (surf->insubmodel && (surf->key == surf2->key)) + // if it's two surfaces on the same plane, the one that's already + // active is in front, so keep going unless it's a bmodel + if( surf->insubmodel && ( surf->key == surf2->key )) { - // must be two bmodels in the same leaf; don't care, because they'll - // never be farthest anyway + // must be two bmodels in the same leaf; don't care, because they'll + // never be farthest anyway goto newtop; } @@ -359,26 +320,27 @@ continue_search: do { surf2 = surf2->next; - } while (surf->key < surf2->key); + } + while( surf->key < surf2->key ); - if (surf->key == surf2->key) + if( surf->key == surf2->key ) { - // if it's two surfaces on the same plane, the one that's already - // active is in front, so keep going unless it's a bmodel - if (!surf->insubmodel) + // if it's two surfaces on the same plane, the one that's already + // active is in front, so keep going unless it's a bmodel + if( !surf->insubmodel ) goto continue_search; - // must be two bmodels in the same leaf; don't care which is really - // in front, because they'll never be farthest anyway + // must be two bmodels in the same leaf; don't care which is really + // in front, because they'll never be farthest anyway } goto gotposition; newtop: - // emit a span (obscures current top) + // emit a span (obscures current top) iu = edge->u >> 20; - if (iu > surf2->last_u) + if( iu > surf2->last_u ) { span = span_p++; span->u = surf2->last_u; @@ -392,7 +354,7 @@ newtop: surf->last_u = iu; gotposition: - // insert before surf2 + // insert before surf2 surf->next = surf2; surf->prev = surf2->prev; surf2->prev->next = surf; @@ -406,21 +368,21 @@ gotposition: R_TrailingEdge ============== */ -void R_TrailingEdge (surf_t *surf, edge_t *edge) +void R_TrailingEdge( surf_t *surf, edge_t *edge ) { - espan_t *span; - int iu; + espan_t *span; + int iu; // don't generate a span if this is an inverted span, with the end // edge preceding the start edge (that is, we haven't seen the // start edge yet) - if (--surf->spanstate == 0) + if( --surf->spanstate == 0 ) { - if (surf == surfaces[1].next) + if( surf == surfaces[1].next ) { - // emit a span (current top going away) + // emit a span (current top going away) iu = edge->u >> 20; - if (iu > surf->last_u) + if( iu > surf->last_u ) { span = span_p++; span->u = surf->last_u; @@ -430,7 +392,7 @@ void R_TrailingEdge (surf_t *surf, edge_t *edge) surf->spans = span; } - // set last_u on the surface below + // set last_u on the surface below surf->next->last_u = iu; } @@ -439,58 +401,55 @@ void R_TrailingEdge (surf_t *surf, edge_t *edge) } } - -#if !id386 - /* ============== R_LeadingEdge ============== */ -void R_LeadingEdge (edge_t *edge) +void R_LeadingEdge( edge_t *edge ) { - espan_t *span; - surf_t *surf, *surf2; - int iu; - float fu, newzi, testzi, newzitop, newzibottom; + espan_t *span; + surf_t *surf, *surf2; + int iu; + float fu, newzi, testzi, newzitop, newzibottom; - if (edge->surfs[1]) + if( edge->surfs[1] ) { - // it's adding a new surface in, so find the correct place + // it's adding a new surface in, so find the correct place surf = &surfaces[edge->surfs[1]]; - // don't start a span if this is an inverted span, with the end - // edge preceding the start edge (that is, we've already seen the - // end edge) - if (++surf->spanstate == 1) + // don't start a span if this is an inverted span, with the end + // edge preceding the start edge (that is, we've already seen the + // end edge) + if( ++surf->spanstate == 1 ) { surf2 = surfaces[1].next; - if (surf->key < surf2->key) + if( surf->key < surf2->key ) goto newtop; - // if it's two surfaces on the same plane, the one that's already - // active is in front, so keep going unless it's a bmodel - if (surf->insubmodel && (surf->key == surf2->key)) + // if it's two surfaces on the same plane, the one that's already + // active is in front, so keep going unless it's a bmodel + if( surf->insubmodel && ( surf->key == surf2->key )) { - // must be two bmodels in the same leaf; sort on 1/z - fu = (float)(edge->u - 0xFFFFF) * (1.0f / 0x100000); - newzi = surf->d_ziorigin + fv*surf->d_zistepv + - fu*surf->d_zistepu; + // must be two bmodels in the same leaf; sort on 1/z + fu = (float)( edge->u - 0xFFFFF ) * ( 1.0f / 0x100000 ); + newzi = surf->d_ziorigin + fv * surf->d_zistepv + + fu * surf->d_zistepu; newzibottom = newzi * 0.99f; - testzi = surf2->d_ziorigin + fv*surf2->d_zistepv + - fu*surf2->d_zistepu; + testzi = surf2->d_ziorigin + fv * surf2->d_zistepv + + fu * surf2->d_zistepu; - if (newzibottom >= testzi) + if( newzibottom >= testzi ) { goto newtop; } newzitop = newzi * 1.01f; - if (newzitop >= testzi) + if( newzitop >= testzi ) { - if (surf->d_zistepu >= surf2->d_zistepu) + if( surf->d_zistepu >= surf2->d_zistepu ) { goto newtop; } @@ -502,33 +461,34 @@ continue_search: do { surf2 = surf2->next; - } while (surf->key > surf2->key); + } + while( surf->key > surf2->key ); - if (surf->key == surf2->key) + if( surf->key == surf2->key ) { - // if it's two surfaces on the same plane, the one that's already - // active is in front, so keep going unless it's a bmodel - if (!surf->insubmodel) + // if it's two surfaces on the same plane, the one that's already + // active is in front, so keep going unless it's a bmodel + if( !surf->insubmodel ) goto continue_search; - // must be two bmodels in the same leaf; sort on 1/z - fu = (float)(edge->u - 0xFFFFF) * (1.0f / 0x100000); - newzi = surf->d_ziorigin + fv*surf->d_zistepv + - fu*surf->d_zistepu; + // must be two bmodels in the same leaf; sort on 1/z + fu = (float)( edge->u - 0xFFFFF ) * ( 1.0f / 0x100000 ); + newzi = surf->d_ziorigin + fv * surf->d_zistepv + + fu * surf->d_zistepu; newzibottom = newzi * 0.99f; - testzi = surf2->d_ziorigin + fv*surf2->d_zistepv + - fu*surf2->d_zistepu; + testzi = surf2->d_ziorigin + fv * surf2->d_zistepv + + fu * surf2->d_zistepu; - if (newzibottom >= testzi) + if( newzibottom >= testzi ) { goto gotposition; } newzitop = newzi * 1.01f; - if (newzitop >= testzi) + if( newzitop >= testzi ) { - if (surf->d_zistepu >= surf2->d_zistepu) + if( surf->d_zistepu >= surf2->d_zistepu ) { goto gotposition; } @@ -540,10 +500,10 @@ continue_search: goto gotposition; newtop: - // emit a span (obscures current top) + // emit a span (obscures current top) iu = edge->u >> 20; - if (iu > surf2->last_u) + if( iu > surf2->last_u ) { span = span_p++; span->u = surf2->last_u; @@ -557,7 +517,7 @@ newtop: surf->last_u = iu; gotposition: - // insert before surf2 + // insert before surf2 surf->next = surf2; surf->prev = surf2->prev; surf2->prev->next = surf; @@ -572,62 +532,59 @@ gotposition: R_GenerateSpans ============== */ -void R_GenerateSpans (void) +void R_GenerateSpans( void ) { - edge_t *edge; - surf_t *surf; + edge_t *edge; + surf_t *surf; // clear active surfaces to just the background surface surfaces[1].next = surfaces[1].prev = &surfaces[1]; surfaces[1].last_u = edge_head_u_shift20; // generate spans - for (edge=edge_head.next ; edge != &edge_tail; edge=edge->next) + for( edge = edge_head.next; edge != &edge_tail; edge = edge->next ) { - if (edge->surfs[0]) + if( edge->surfs[0] ) { - // it has a left surface, so a surface is going away for this span + // it has a left surface, so a surface is going away for this span surf = &surfaces[edge->surfs[0]]; - R_TrailingEdge (surf, edge); + R_TrailingEdge( surf, edge ); - if (!edge->surfs[1]) + if( !edge->surfs[1] ) continue; } - R_LeadingEdge (edge); + R_LeadingEdge( edge ); } - R_CleanupSpan (); + R_CleanupSpan(); } -#endif // !id386 - - /* ============== R_GenerateSpansBackward ============== */ -void R_GenerateSpansBackward (void) +void R_GenerateSpansBackward( void ) { - edge_t *edge; + edge_t *edge; // clear active surfaces to just the background surface surfaces[1].next = surfaces[1].prev = &surfaces[1]; surfaces[1].last_u = edge_head_u_shift20; // generate spans - for (edge=edge_head.next ; edge != &edge_tail; edge=edge->next) + for( edge = edge_head.next; edge != &edge_tail; edge = edge->next ) { - if (edge->surfs[0]) - R_TrailingEdge (&surfaces[edge->surfs[0]], edge); + if( edge->surfs[0] ) + R_TrailingEdge( &surfaces[edge->surfs[0]], edge ); - if (edge->surfs[1]) - R_LeadingEdgeBackwards (edge); + if( edge->surfs[1] ) + R_LeadingEdgeBackwards( edge ); } - R_CleanupSpan (); + R_CleanupSpan(); } @@ -643,15 +600,15 @@ Output: Each surface has a linked list of its visible spans ============== */ -void R_ScanEdges (void) +void R_ScanEdges( void ) { - int iv, bottom; - byte basespans[MAXSPANS*sizeof(espan_t)+CACHE_SIZE]; - espan_t *basespan_p; - surf_t *s; + int iv, bottom; + byte basespans[MAXSPANS * sizeof( espan_t ) + CACHE_SIZE]; + espan_t *basespan_p; + surf_t *s; basespan_p = (espan_t *) - ((uintptr_t)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + ((uintptr_t)( basespans + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 )); max_span_p = &basespan_p[MAXSPANS - RI.vrect.width]; span_p = basespan_p; @@ -666,7 +623,7 @@ void R_ScanEdges (void) edge_head.surfs[0] = 0; edge_head.surfs[1] = 1; - edge_tail.u =(RI.vrectright << 20) + 0xFFFFF; // (r_refdef.vrectright << 20) + 0xFFFFF; + edge_tail.u = ( RI.vrectright << 20 ) + 0xFFFFF; // (r_refdef.vrectright << 20) + 0xFFFFF; edge_tail_u_shift20 = edge_tail.u >> 20; edge_tail.u_step = 0; edge_tail.prev = &edge_head; @@ -674,13 +631,13 @@ void R_ScanEdges (void) edge_tail.surfs[0] = 1; edge_tail.surfs[1] = 0; - edge_aftertail.u = -1; // force a move + edge_aftertail.u = -1; // force a move edge_aftertail.u_step = 0; edge_aftertail.next = &edge_sentinel; edge_aftertail.prev = &edge_tail; // FIXME: do we need this now that we clamp x in r_draw.c? - edge_sentinel.u = 2000 << 20; // make sure nothing sorts past this + edge_sentinel.u = 2000 << 20; // make sure nothing sorts past this edge_sentinel.prev = &edge_aftertail; // @@ -688,39 +645,37 @@ void R_ScanEdges (void) // bottom = RI.vrectbottom - 1; - for (iv=0 ; iv max_span_p) + // flush the span list if we can't be sure we have enough spans left for + // the next scan + if( span_p > max_span_p ) { - D_DrawSurfaces (); + D_DrawSurfaces(); - // clear the surface span pointers - for (s = &surfaces[1] ; sspans = NULL; span_p = basespan_p; } -#endif - if (removeedges[iv]) - R_RemoveEdges (removeedges[iv]); + if( removeedges[iv] ) + R_RemoveEdges( removeedges[iv] ); - if (edge_head.next != &edge_tail) - R_StepActiveU (edge_head.next); + if( edge_head.next != &edge_tail ) + R_StepActiveU( edge_head.next ); } // do the last scan (no need to step or sort or remove on the last scan) @@ -731,13 +686,13 @@ void R_ScanEdges (void) // mark that the head (background start) span is pre-included surfaces[1].spanstate = 1; - if (newedges[iv]) - R_InsertNewEdges (newedges[iv], edge_head.next); + if( newedges[iv] ) + R_InsertNewEdges( newedges[iv], edge_head.next ); - (*pdrawfunc) (); + ( *pdrawfunc )(); // draw whatever's left in the span list - D_DrawSurfaces (); + D_DrawSurfaces(); } @@ -749,31 +704,31 @@ SURFACE FILLING ========================================================================= */ -msurface_t *pface; -surfcache_t *pcurrentcache; -vec3_t transformed_modelorg; -vec3_t world_transformed_modelorg; -vec3_t local_modelorg; +msurface_t *pface; +surfcache_t *pcurrentcache; +vec3_t transformed_modelorg; +vec3_t world_transformed_modelorg; +vec3_t local_modelorg; /* ============= D_MipLevelForScale ============= */ -int D_MipLevelForScale (float scale) +static int D_MipLevelForScale( float scale ) { - int lmiplevel; + int lmiplevel; - if (scale >= d_scalemip[0] ) + if( scale >= d_scalemip[0] ) lmiplevel = 0; - else if (scale >= d_scalemip[1] ) + else if( scale >= d_scalemip[1] ) lmiplevel = 1; - else if (scale >= d_scalemip[2] ) + else if( scale >= d_scalemip[2] ) lmiplevel = 2; else lmiplevel = 3; - if (lmiplevel < d_minmip) + if( lmiplevel < d_minmip ) lmiplevel = d_minmip; return lmiplevel; @@ -787,18 +742,18 @@ D_FlatFillSurface Simple single color fill with no texture mapping ============== */ -void D_FlatFillSurface (surf_t *surf, int color) +static void D_FlatFillSurface( surf_t *surf, int color ) { - espan_t *span; - pixel_t *pdest; - int u, u2; + espan_t *span; + pixel_t *pdest; + int u, u2; - for (span=surf->spans ; span ; span=span->pnext) + for( span = surf->spans; span; span = span->pnext ) { - pdest = d_viewbuffer + r_screenwidth*span->v; + pdest = d_viewbuffer + r_screenwidth * span->v; u = span->u; u2 = span->u + span->count - 1; - for ( ; u <= u2 ; u++) + for( ; u <= u2; u++ ) pdest[u] = color; } } @@ -809,28 +764,28 @@ void D_FlatFillSurface (surf_t *surf, int color) D_CalcGradients ============== */ -void D_CalcGradients (msurface_t *pface) +static void D_CalcGradients( msurface_t *pface ) { - mplane_t *pplane; - float mipscale; - vec3_t p_temp1; - vec3_t p_saxis, p_taxis; - float t; + mplane_t *pplane; + float mipscale; + vec3_t p_temp1; + vec3_t p_saxis, p_taxis; + float t; pplane = pface->plane; - mipscale = 1.0f / (float)(1 << miplevel); + mipscale = 1.0f / (float)( 1 << miplevel ); if( pface->texinfo->flags & TEX_WORLD_LUXELS ) { - TransformVector (pface->texinfo->vecs[0], p_saxis); - TransformVector (pface->texinfo->vecs[1], p_taxis); + TransformVector( pface->texinfo->vecs[0], p_saxis ); + TransformVector( pface->texinfo->vecs[1], p_taxis ); } else { - TransformVector (pface->info->lmvecs[0], p_saxis); - TransformVector (pface->info->lmvecs[1], p_taxis); + TransformVector( pface->info->lmvecs[0], p_saxis ); + TransformVector( pface->info->lmvecs[1], p_taxis ); } t = xscaleinv * mipscale; @@ -841,50 +796,50 @@ void D_CalcGradients (msurface_t *pface) d_sdivzstepv = -p_saxis[1] * t; d_tdivzstepv = -p_taxis[1] * t; - d_sdivzorigin = p_saxis[2] * mipscale - xcenter * d_sdivzstepu - - ycenter * d_sdivzstepv; - d_tdivzorigin = p_taxis[2] * mipscale - xcenter * d_tdivzstepu - - ycenter * d_tdivzstepv; + d_sdivzorigin = p_saxis[2] * mipscale - xcenter * d_sdivzstepu + - ycenter * d_sdivzstepv; + d_tdivzorigin = p_taxis[2] * mipscale - xcenter * d_tdivzstepu + - ycenter * d_tdivzstepv; - VectorScale (transformed_modelorg, mipscale, p_temp1); + VectorScale( transformed_modelorg, mipscale, p_temp1 ); - t = 0x10000*mipscale; + t = 0x10000 * mipscale; if( pface->texinfo->flags & TEX_WORLD_LUXELS ) { - sadjust = ((fixed16_t)(DotProduct (p_temp1, p_saxis) * 0x10000 + 0.5f)) - - ((pface->texturemins[0] << 16) >> miplevel) - + pface->texinfo->vecs[0][3]*t; - tadjust = ((fixed16_t)(DotProduct (p_temp1, p_taxis) * 0x10000 + 0.5f)) - - ((pface->texturemins[1] << 16) >> miplevel) - + pface->texinfo->vecs[1][3]*t; + sadjust = ((fixed16_t)( DotProduct( p_temp1, p_saxis ) * 0x10000 + 0.5f )) + - (( pface->texturemins[0] << 16 ) >> miplevel ) + + pface->texinfo->vecs[0][3] * t; + tadjust = ((fixed16_t)( DotProduct( p_temp1, p_taxis ) * 0x10000 + 0.5f )) + - (( pface->texturemins[1] << 16 ) >> miplevel ) + + pface->texinfo->vecs[1][3] * t; } else { - sadjust = ((fixed16_t)(DotProduct (p_temp1, p_saxis) * 0x10000 + 0.5f)) - - ((pface->info->lightmapmins[0] << 16) >> miplevel) - + pface->info->lmvecs[0][3]*t; - tadjust = ((fixed16_t)(DotProduct (p_temp1, p_taxis) * 0x10000 + 0.5f)) - - ((pface->info->lightmapmins[1] << 16) >> miplevel) - + pface->info->lmvecs[1][3]*t; + sadjust = ((fixed16_t)( DotProduct( p_temp1, p_saxis ) * 0x10000 + 0.5f )) + - (( pface->info->lightmapmins[0] << 16 ) >> miplevel ) + + pface->info->lmvecs[0][3] * t; + tadjust = ((fixed16_t)( DotProduct( p_temp1, p_taxis ) * 0x10000 + 0.5f )) + - (( pface->info->lightmapmins[1] << 16 ) >> miplevel ) + + pface->info->lmvecs[1][3] * t; } // PGM - changing flow speed for non-warping textures. - if (pface->flags & SURF_CONVEYOR) + if( pface->flags & SURF_CONVEYOR ) { - if(pface->flags & SURF_DRAWTURB) - sadjust += 0x10000 * (-128 * ( (gpGlobals->time * 0.25f) - (int)(gpGlobals->time * 0.25f) )); + if( pface->flags & SURF_DRAWTURB ) + sadjust += 0x10000 * ( -128 * (( gp_cl->time * 0.25f ) - (int)( gp_cl->time * 0.25f ))); else - sadjust += 0x10000 * (-128 * ( (gpGlobals->time * 0.77f) - (int)(gpGlobals->time * 0.77f) )); - bbextents = ((pface->extents[0] << 16) >> miplevel) - 1; + sadjust += 0x10000 * ( -128 * (( gp_cl->time * 0.77f ) - (int)( gp_cl->time * 0.77f ))); + bbextents = (( pface->extents[0] << 16 ) >> miplevel ) - 1; } else - bbextents = ((pface->info->lightextents[0] << 16) >> miplevel) - 1; - bbextentt = ((pface->info->lightextents[1] << 16) >> miplevel) - 1; + bbextents = (( pface->info->lightextents[0] << 16 ) >> miplevel ) - 1; + bbextentt = (( pface->info->lightextents[1] << 16 ) >> miplevel ) - 1; if( pface->texinfo->flags & TEX_WORLD_LUXELS ) { - bbextents = ((pface->extents[0] << 16) >> miplevel) - 1; - bbextentt = ((pface->extents[1] << 16) >> miplevel) - 1; + bbextents = (( pface->extents[0] << 16 ) >> miplevel ) - 1; + bbextentt = (( pface->extents[1] << 16 ) >> miplevel ) - 1; } } @@ -896,7 +851,7 @@ D_BackgroundSurf The grey background filler seen when there is a hole in the map ============== */ -void D_BackgroundSurf (surf_t *s) +static void D_BackgroundSurf( surf_t *s ) { // set up a gradient for the background surface that places it // effectively at infinity distance from the viewpoint @@ -904,8 +859,8 @@ void D_BackgroundSurf (surf_t *s) d_zistepv = 0; d_ziorigin = -0.9; - D_FlatFillSurface (s, (int)sw_clearcolor->value & 0xFFFF); - D_DrawZSpans (s->spans); + D_FlatFillSurface( s, (int)sw_clearcolor.value & 0xFFFF ); + D_DrawZSpans( s->spans ); } /* @@ -913,7 +868,7 @@ void D_BackgroundSurf (surf_t *s) D_TurbulentSurf ================= */ -void D_TurbulentSurf (surf_t *s) +static void D_TurbulentSurf( surf_t *s ) { d_zistepu = s->d_zistepu; d_zistepv = s->d_zistepv; @@ -921,95 +876,55 @@ void D_TurbulentSurf (surf_t *s) pface = s->msurf; miplevel = 0; - cacheblock = R_GetTexture(pface->texinfo->texture->gl_texturenum)->pixels[0]; + cacheblock = R_GetTexture( pface->texinfo->texture->gl_texturenum )->pixels[0]; cachewidth = 64; - if (s->insubmodel) + if( s->insubmodel ) { - // FIXME: we don't want to do all this for every polygon! - // TODO: store once at start of frame - RI.currententity = s->entity; //FIXME: make this passed in to - // R_RotateBmodel () - VectorSubtract (RI.vieworg, RI.currententity->origin, - local_modelorg); - TransformVector (local_modelorg, transformed_modelorg); + // FIXME: we don't want to do all this for every polygon! + // TODO: store once at start of frame + RI.currententity = s->entity; // FIXME: make this passed in to + // R_RotateBmodel () + VectorSubtract( RI.vieworg, RI.currententity->origin, + local_modelorg ); + TransformVector( local_modelorg, transformed_modelorg ); - R_RotateBmodel (); // FIXME: don't mess with the frustum, - // make entity passed in + R_RotateBmodel(); // FIXME: don't mess with the frustum, + // make entity passed in } - D_CalcGradients (pface); + D_CalcGradients( pface ); -//============ -//PGM +// ============ +// PGM // textures that aren't warping are just flowing. Use NonTurbulent8 instead -#if 0 - Turbulent8 (s->spans); -#else - if(!(pface->flags & SURF_DRAWTURB)) - NonTurbulent8 (s->spans); + if( !( pface->flags & SURF_DRAWTURB )) + NonTurbulent8( s->spans ); else - Turbulent8 (s->spans); -#endif -//PGM -//============ + Turbulent8( s->spans ); +// PGM +// ============ - D_DrawZSpans (s->spans); + D_DrawZSpans( s->spans ); - if (s->insubmodel) + if( s->insubmodel ) { - // - // restore the old drawing state - // FIXME: we don't want to do this every time! - // TODO: speed up - // - RI.currententity = NULL; // &r_worldentity; - VectorCopy (world_transformed_modelorg, - transformed_modelorg); - VectorCopy (RI.base_vpn, RI.vforward); - VectorCopy (RI.base_vup, RI.vup); - VectorCopy (RI.base_vright, RI.vright); - R_TransformFrustum (); + // + // restore the old drawing state + // FIXME: we don't want to do this every time! + // TODO: speed up + // + RI.currententity = NULL; // &r_worldentity; + VectorCopy( world_transformed_modelorg, + transformed_modelorg ); + VectorCopy( RI.base_vpn, RI.vforward ); + VectorCopy( RI.base_vup, RI.vup ); + VectorCopy( RI.base_vright, RI.vright ); + R_TransformFrustum(); } } -/* -============== -D_SkySurf -============== -*/ -void D_SkySurf (surf_t *s) -{ - pface = s->msurf; - miplevel = 0; - if (!pface->texinfo->texture) - return; - cacheblock = R_GetTexture(pface->texinfo->texture->gl_texturenum)->pixels[0]; - cachewidth = 256; - - d_zistepu = s->d_zistepu; - d_zistepv = s->d_zistepv; - d_ziorigin = s->d_ziorigin; - - D_CalcGradients (pface); - - D_DrawSpans16 (s->spans); - -// set up a gradient for the background surface that places it -// effectively at infinity distance from the viewpoint - d_zistepu = 0; - d_zistepv = 0; - d_ziorigin = -0.9; - - D_DrawZSpans (s->spans); -} qboolean alphaspans; - - -void D_AlphaSpans16 (espan_t *pspan); -void D_AddSpans16 (espan_t *pspan); -void D_BlendSpans16 (espan_t *pspan, int alpha ); -void TurbulentZ8 (espan_t *pspan, int alpha ); /* ============== D_SolidSurf @@ -1017,27 +932,27 @@ D_SolidSurf Normal surface cached, texture mapped surface ============== */ -void D_AlphaSurf (surf_t *s) +static void D_AlphaSurf( surf_t *s ) { int alpha; d_zistepu = s->d_zistepu; d_zistepv = s->d_zistepv; d_ziorigin = s->d_ziorigin; - if(s->flags & SURF_DRAWSKY) + if( s->flags & SURF_DRAWSKY ) return; - if (!s->insubmodel) // wtf? how it is possible? + if( !s->insubmodel )// wtf? how it is possible? return; // FIXME: we don't want to do all this for every polygon! // TODO: store once at start of frame - RI.currententity = s->entity; //FIXME: make this passed in to - // R_RotateBmodel () - VectorSubtract (RI.vieworg, RI.currententity->origin, local_modelorg); - TransformVector (local_modelorg, transformed_modelorg); + RI.currententity = s->entity; // FIXME: make this passed in to + // R_RotateBmodel () + VectorSubtract( RI.vieworg, RI.currententity->origin, local_modelorg ); + TransformVector( local_modelorg, transformed_modelorg ); - R_RotateBmodel (); // FIXME: don't mess with the frustum, - // make entity passed in + R_RotateBmodel(); // FIXME: don't mess with the frustum, + // make entity passed in pface = s->msurf; @@ -1045,75 +960,49 @@ void D_AlphaSurf (surf_t *s) if( !pface ) return; -#if 1 if( pface->flags & SURF_CONVEYOR ) miplevel = 1; else miplevel = 0; -#else - { - float dot; - float normal[3]; - if ( s->insubmodel ) - { - VectorCopy( pface->plane->normal, normal ); -// TransformVector( pface->plane->normal, normal); - dot = DotProduct( normal, vpn ); - } - else - { - VectorCopy( pface->plane->normal, normal ); - dot = DotProduct( normal, vpn ); - } - - if ( pface->flags & SURF_PLANEBACK ) - dot = -dot; - - if ( dot > 0 ) - printf( "blah" ); - - miplevel = D_MipLevelForScale(s->nearzi * scale_for_mip * pface->texinfo->mipadjust); - } -#endif alpha = RI.currententity->curstate.renderamt * 7 / 255; if( alpha <= 0 && RI.currententity->curstate.renderamt > 0 ) alpha = 1; - if (s->flags & SURF_DRAWTURB ) + if( s->flags & SURF_DRAWTURB ) { - cacheblock = R_GetTexture(pface->texinfo->texture->gl_texturenum)->pixels[0]; + cacheblock = R_GetTexture( pface->texinfo->texture->gl_texturenum )->pixels[0]; cachewidth = 64; - D_CalcGradients (pface); - TurbulentZ8( s->spans, alpha); + D_CalcGradients( pface ); + TurbulentZ8( s->spans, alpha ); } else { // FIXME: make this passed in to D_CacheSurface - pcurrentcache = D_CacheSurface (pface, miplevel); + pcurrentcache = D_CacheSurface( pface, miplevel ); cacheblock = (pixel_t *)pcurrentcache->data; cachewidth = pcurrentcache->width; - D_CalcGradients (pface); + D_CalcGradients( pface ); if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) - D_AlphaSpans16(s->spans); + D_AlphaSpans16( s->spans ); else if( RI.currententity->curstate.rendermode == kRenderTransAdd ) - D_AddSpans16(s->spans); + D_AddSpans16( s->spans ); else - D_BlendSpans16(s->spans, alpha ); + D_BlendSpans16( s->spans, alpha ); } - VectorCopy (world_transformed_modelorg, - transformed_modelorg); - VectorCopy (RI.base_vpn, RI.vforward); - VectorCopy (RI.base_vup, RI.vup); - VectorCopy (RI.base_vright, RI.vright); - R_TransformFrustum (); + VectorCopy( world_transformed_modelorg, + transformed_modelorg ); + VectorCopy( RI.base_vpn, RI.vforward ); + VectorCopy( RI.base_vup, RI.vup ); + VectorCopy( RI.base_vright, RI.vright ); + R_TransformFrustum(); } @@ -1124,27 +1013,27 @@ D_SolidSurf Normal surface cached, texture mapped surface ============== */ -void D_SolidSurf (surf_t *s) +static void D_SolidSurf( surf_t *s ) { d_zistepu = s->d_zistepu; d_zistepv = s->d_zistepv; d_ziorigin = s->d_ziorigin; - if(s->flags & SURF_DRAWSKY) + if( s->flags & SURF_DRAWSKY ) return; - if (s->flags & SURF_DRAWTURB ) + if( s->flags & SURF_DRAWTURB ) return; - if (s->insubmodel) + if( s->insubmodel ) { - // FIXME: we don't want to do all this for every polygon! - // TODO: store once at start of frame - RI.currententity = s->entity; //FIXME: make this passed in to - // R_RotateBmodel () - VectorSubtract (RI.vieworg, RI.currententity->origin, local_modelorg); - TransformVector (local_modelorg, transformed_modelorg); + // FIXME: we don't want to do all this for every polygon! + // TODO: store once at start of frame + RI.currententity = s->entity; // FIXME: make this passed in to + // R_RotateBmodel () + VectorSubtract( RI.vieworg, RI.currententity->origin, local_modelorg ); + TransformVector( local_modelorg, transformed_modelorg ); - R_RotateBmodel (); // FIXME: don't mess with the frustum, - // make entity passed in + R_RotateBmodel(); // FIXME: don't mess with the frustum, + // make entity passed in // setup dlight transform if( s->msurf && s->msurf->dlightframe == tr.framecount ) { @@ -1156,7 +1045,7 @@ void D_SolidSurf (surf_t *s) { if( alphaspans ) return; - RI.currententity = gEngfuncs.GetEntityByIndex(0); //r_worldentity; + RI.currententity = CL_GetEntityByIndex( 0 ); // r_worldentity; tr.modelviewIdentity = true; } @@ -1165,66 +1054,40 @@ void D_SolidSurf (surf_t *s) if( !pface ) return; -#if 1 + if( pface->flags & SURF_CONVEYOR ) miplevel = 1; else - miplevel = D_MipLevelForScale(s->nearzi * scale_for_mip ); - while( 1 << miplevel > gEngfuncs.Mod_SampleSizeForFace(pface)) + miplevel = D_MipLevelForScale( s->nearzi * scale_for_mip ); + while( 1 << miplevel > gEngfuncs.Mod_SampleSizeForFace( pface )) miplevel--; -#else - { - float dot; - float normal[3]; - if ( s->insubmodel ) - { - VectorCopy( pface->plane->normal, normal ); -// TransformVector( pface->plane->normal, normal); - dot = DotProduct( normal, vpn ); - } - else - { - VectorCopy( pface->plane->normal, normal ); - dot = DotProduct( normal, vpn ); - } - - if ( pface->flags & SURF_PLANEBACK ) - dot = -dot; - - if ( dot > 0 ) - printf( "blah" ); - - miplevel = D_MipLevelForScale(s->nearzi * scale_for_mip * pface->texinfo->mipadjust); - } -#endif - -// FIXME: make this passed in to D_CacheSurface - pcurrentcache = D_CacheSurface (pface, miplevel); + // FIXME: make this passed in to D_CacheSurface + pcurrentcache = D_CacheSurface( pface, miplevel ); cacheblock = (pixel_t *)pcurrentcache->data; cachewidth = pcurrentcache->width; - D_CalcGradients (pface); + D_CalcGradients( pface ); - D_DrawSpans16 (s->spans); + D_DrawSpans16( s->spans ); - D_DrawZSpans (s->spans); + D_DrawZSpans( s->spans ); - if (s->insubmodel) + if( s->insubmodel ) { - // - // restore the old drawing state - // FIXME: we don't want to do this every time! - // TODO: speed up - // - VectorCopy (world_transformed_modelorg, - transformed_modelorg); - VectorCopy (RI.base_vpn, RI.vforward); - VectorCopy (RI.base_vup, RI.vup); - VectorCopy (RI.base_vright, RI.vright); - R_TransformFrustum (); - RI.currententity = NULL; //&r_worldentity; + // + // restore the old drawing state + // FIXME: we don't want to do this every time! + // TODO: speed up + // + VectorCopy( world_transformed_modelorg, + transformed_modelorg ); + VectorCopy( RI.base_vpn, RI.vforward ); + VectorCopy( RI.base_vup, RI.vup ); + VectorCopy( RI.base_vright, RI.vright ); + R_TransformFrustum(); + RI.currententity = NULL; // &r_worldentity; } } @@ -1235,13 +1098,13 @@ D_DrawflatSurfaces To allow developers to see the polygon carving of the world ============= */ -void D_DrawflatSurfaces (void) +static void D_DrawflatSurfaces( void ) { - surf_t *s; + surf_t *s; - for (s = &surfaces[1] ; sspans) + if( !s->spans ) continue; d_zistepu = s->d_zistepu; @@ -1250,8 +1113,8 @@ void D_DrawflatSurfaces (void) // make a stable color for each surface by taking the low // bits of the msurface pointer - D_FlatFillSurface (s, (uintptr_t)s->msurf & 0xFFFF); - D_DrawZSpans (s->spans); + D_FlatFillSurface( s, (uintptr_t)s->msurf & 0xFFFF ); + D_DrawZSpans( s->spans ); } } @@ -1263,49 +1126,37 @@ Rasterize all the span lists. Guaranteed zero overdraw. May be called more than once a frame if the surf list overflows (higher res) ============== */ -void D_DrawSurfaces (void) +void D_DrawSurfaces( void ) { - surf_t *s; + surf_t *s; // currententity = NULL; //&r_worldentity; - VectorSubtract (RI.vieworg, vec3_origin, tr.modelorg); - TransformVector (tr.modelorg, transformed_modelorg); - VectorCopy (transformed_modelorg, world_transformed_modelorg); + VectorSubtract( RI.vieworg, vec3_origin, tr.modelorg ); + TransformVector( tr.modelorg, transformed_modelorg ); + VectorCopy( transformed_modelorg, world_transformed_modelorg ); - if (!sw_drawflat->value) + if( !sw_drawflat.value ) { - for (s = &surfaces[1] ; sspans) + if( !s->spans ) continue; - //r_drawnpolycount++; -#if 1 if( alphaspans ) - D_AlphaSurf (s); - else if(s->flags & SURF_DRAWSKY) - D_BackgroundSurf (s); - else if (s->flags & SURF_DRAWTURB ) - D_TurbulentSurf (s); + D_AlphaSurf( s ); + else if( s->flags & SURF_DRAWSKY ) + D_BackgroundSurf( s ); + else if( s->flags & SURF_DRAWTURB ) + D_TurbulentSurf( s ); else - D_SolidSurf (s); -#else - if (! (s->flags & (SURF_DRAWSKYBOX|SURF_DRAWBACKGROUND|SURF_DRAWTURB) ) ) - D_SolidSurf (s); - else if (s->flags & SURF_DRAWSKYBOX) - D_SkySurf (s); - else if (s->flags & SURF_DRAWBACKGROUND) - D_BackgroundSurf (s); - else if (s->flags & SURF_DRAWTURB) - D_TurbulentSurf (s); -#endif + D_SolidSurf( s ); } } else - D_DrawflatSurfaces (); + D_DrawflatSurfaces(); - //RI.currententity = NULL; //&r_worldentity; - VectorSubtract (RI.vieworg, vec3_origin, tr.modelorg); - R_TransformFrustum (); + // RI.currententity = NULL; //&r_worldentity; + VectorSubtract( RI.vieworg, vec3_origin, tr.modelorg ); + R_TransformFrustum(); } diff --git a/ref/soft/r_glblit.c b/ref/soft/r_glblit.c index 99d609b1..6aa21909 100644 --- a/ref/soft/r_glblit.c +++ b/ref/soft/r_glblit.c @@ -4,13 +4,13 @@ struct swblit_s { - uint stride; - uint bpp; - uint rmask, gmask, bmask; - void *(*pLockBuffer)( void ); - void (*pUnlockBuffer)( void ); - qboolean(*pCreateBuffer)( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ); - uint rotate; + uint stride; + uint bpp; + uint rmask, gmask, bmask; + void *(*pLockBuffer)( void ); + void (*pUnlockBuffer)( void ); + qboolean (*pCreateBuffer)( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ); + uint rotate; qboolean gl1; } swblit; @@ -20,7 +20,7 @@ qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int qboolean ret = true; if( rotate > 1 ) { - gEngfuncs.Con_Printf("only 0-1 rotation supported\n"); + gEngfuncs.Con_Printf( "only 0-1 rotation supported\n" ); ret = false; } else @@ -29,14 +29,14 @@ qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int if( offset_x || offset_y ) { // it is possible implement for offset > 0 - gEngfuncs.Con_Printf("offset transform not supported\n"); + gEngfuncs.Con_Printf( "offset transform not supported\n" ); ret = false; } if( scale_x != 1.0f || scale_y != 1.0f ) { // maybe implement 2x2? - gEngfuncs.Con_Printf("scale transform not supported\n"); + gEngfuncs.Con_Printf( "scale transform not supported\n" ); ret = false; } @@ -76,8 +76,8 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui static unsigned short *glbuf; static int tex; -#define LOAD(x) p##x = gEngfuncs.GL_GetProcAddress(#x); \ - gEngfuncs.Con_Printf(#x " : %p\n",p##x) +#define LOAD( x ) p ## x = gEngfuncs.GL_GetProcAddress(#x ); \ + gEngfuncs.Con_Printf(#x " : %p\n", p ## x ) void GAME_EXPORT GL_SetupAttributes( int safegl ) @@ -108,69 +108,69 @@ void GAME_EXPORT GL_SetupAttributes( int safegl ) gEngfuncs.GL_SetAttribute( REF_GL_BLUE_SIZE, 5 ); } -void (*pglOrthof)(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar); -void GL_FUNCTION( glBindBuffer)(GLenum target, GLuint buffer); +void (*pglOrthof)( GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar ); +void GL_FUNCTION( glBindBuffer )( GLenum target, GLuint buffer ); -void GL_FUNCTION( glBufferData )(GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); -void GL_FUNCTION( glGenBuffers )(GLsizei n, GLuint *buffers); -void GL_FUNCTION( glDeleteBuffers )(GLsizei n, const GLuint *buffers); -GLvoid* GL_FUNCTION( glMapBufferOES )(GLenum target, GLenum access); -GLboolean GL_FUNCTION( glUnmapBufferOES )(GLenum target); +void GL_FUNCTION( glBufferData )( GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage ); +void GL_FUNCTION( glGenBuffers )( GLsizei n, GLuint *buffers ); +void GL_FUNCTION( glDeleteBuffers )( GLsizei n, const GLuint *buffers ); +GLvoid * GL_FUNCTION( glMapBufferOES )( GLenum target, GLenum access ); +GLboolean GL_FUNCTION( glUnmapBufferOES )( GLenum target ); #define GL_PIXEL_UNPACK_BUFFER 0x88EC -#define GL_FRAMEBUFFER 0x8D40 -#define GL_COLOR_ATTACHMENT0 0x8CE0 -#define GL_READ_FRAMEBUFFER 0x8CA8 -#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#define GL_FRAMEBUFFER 0x8D40 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 void GAME_EXPORT GL_InitExtensions( void ) { - LOAD(glBegin); - LOAD(glEnd); - LOAD(glTexCoord2f); - LOAD(glVertex2f); - LOAD(glEnable); - LOAD(glDisable); - LOAD(glTexImage2D); - LOAD(glOrtho); - LOAD(glOrthof); - LOAD(glMatrixMode); - LOAD(glLoadIdentity); - LOAD(glViewport); - LOAD(glBindTexture); - LOAD(glDebugMessageCallbackARB); - LOAD(glDebugMessageControlARB); - LOAD(glGetError); - LOAD(glGenTextures); - LOAD(glTexParameteri); - LOAD(glEnableClientState); - LOAD(glDisableClientState); - LOAD(glVertexPointer); - LOAD(glTexCoordPointer); - LOAD(glDrawElements); - LOAD(glClear); - LOAD(glClearColor); - LOAD(glGetString); - LOAD(glColor4f); - LOAD(glDrawArrays); - LOAD(glBindBuffer); - LOAD(glBufferData); - LOAD(glGenBuffers); - LOAD(glDeleteBuffers); - LOAD(glMapBufferOES); + LOAD( glBegin ); + LOAD( glEnd ); + LOAD( glTexCoord2f ); + LOAD( glVertex2f ); + LOAD( glEnable ); + LOAD( glDisable ); + LOAD( glTexImage2D ); + LOAD( glOrtho ); + LOAD( glOrthof ); + LOAD( glMatrixMode ); + LOAD( glLoadIdentity ); + LOAD( glViewport ); + LOAD( glBindTexture ); + LOAD( glDebugMessageCallbackARB ); + LOAD( glDebugMessageControlARB ); + LOAD( glGetError ); + LOAD( glGenTextures ); + LOAD( glTexParameteri ); + LOAD( glEnableClientState ); + LOAD( glDisableClientState ); + LOAD( glVertexPointer ); + LOAD( glTexCoordPointer ); + LOAD( glDrawElements ); + LOAD( glClear ); + LOAD( glClearColor ); + LOAD( glGetString ); + LOAD( glColor4f ); + LOAD( glDrawArrays ); + LOAD( glBindBuffer ); + LOAD( glBufferData ); + LOAD( glGenBuffers ); + LOAD( glDeleteBuffers ); + LOAD( glMapBufferOES ); if( !pglMapBufferOES ) - pglMapBufferOES = gEngfuncs.GL_GetProcAddress("glMapBuffer"); - LOAD(glUnmapBufferOES); + pglMapBufferOES = gEngfuncs.GL_GetProcAddress( "glMapBuffer" ); + LOAD( glUnmapBufferOES ); if( !pglUnmapBufferOES ) - pglUnmapBufferOES = gEngfuncs.GL_GetProcAddress("glUnmapBuffer"); - LOAD(glGenFramebuffers); - LOAD(glBindFramebuffer); - LOAD(glFramebufferTexture2D); - LOAD(glBlitFramebuffer); - LOAD(glGenTextures); - gEngfuncs.Con_Printf("version:%s\n",pglGetString(GL_VERSION)); + pglUnmapBufferOES = gEngfuncs.GL_GetProcAddress( "glUnmapBuffer" ); + LOAD( glGenFramebuffers ); + LOAD( glBindFramebuffer ); + LOAD( glFramebufferTexture2D ); + LOAD( glBlitFramebuffer ); + LOAD( glGenTextures ); + gEngfuncs.Con_Printf( "version:%s\n", pglGetString( GL_VERSION )); #if GLDEBUG if( gpGlobals->developer ) { - gEngfuncs.Con_Reportf( "Installing GL_DebugOutput...\n"); + gEngfuncs.Con_Reportf( "Installing GL_DebugOutput...\n" ); pglDebugMessageCallbackARB( GL_DebugOutput, NULL ); // force everything to happen in the main thread instead of in a separate driver thread @@ -196,19 +196,19 @@ static void R_Unlock_GL1( void ) { pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf ); - //gEngfuncs.Con_Printf("%d\n",pglGetError()); + // gEngfuncs.Con_Printf("%d\n",pglGetError()); pglBegin( GL_QUADS ); - pglTexCoord2f( 0, 0 ); - pglVertex2f( 0, 0 ); + pglTexCoord2f( 0, 0 ); + pglVertex2f( 0, 0 ); - pglTexCoord2f( 1, 0 ); - pglVertex2f( 1, 0 ); + pglTexCoord2f( 1, 0 ); + pglVertex2f( 1, 0 ); - pglTexCoord2f( 1, 1 ); - pglVertex2f( 1, 1 ); + pglTexCoord2f( 1, 1 ); + pglVertex2f( 1, 1 ); - pglTexCoord2f( 0, 1 ); - pglVertex2f( 0, 1 ); + pglTexCoord2f( 0, 1 ); + pglVertex2f( 0, 1 ); pglEnd(); gEngfuncs.GL_SwapBuffers(); } @@ -217,7 +217,7 @@ static void R_Unlock_GL1( void ) static void R_Unlock_GLES1( void ) { pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf ); - pglDrawArrays( GL_TRIANGLE_FAN, 0,4 ); + pglDrawArrays( GL_TRIANGLE_FAN, 0, 4 ); gEngfuncs.GL_SwapBuffers(); } @@ -232,19 +232,19 @@ static qboolean R_CreateBuffer_GL1( int width, int height, uint *stride, uint *b pglLoadIdentity(); pglEnable( GL_TEXTURE_2D ); - pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + pglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + pglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); if( glbuf ) - Mem_Free(glbuf); + Mem_Free( glbuf ); - glbuf = Mem_Malloc( r_temppool, width*height*2 ); + glbuf = Mem_Malloc( r_temppool, width * height * 2 ); *stride = width; *bpp = 2; - *r = MASK(5) << (6 + 5); - *g = MASK(6) << 5; - *b = MASK(5); + *r = MASK( 5 ) << ( 6 + 5 ); + *g = MASK( 6 ) << 5; + *b = MASK( 5 ); return true; } @@ -258,7 +258,7 @@ static qboolean R_CreateBuffer_GLES1( int width, int height, uint *stride, uint 1, 1, 0, 1, }; - int vbo; + int vbo; pglViewport( 0, 0, width, height ); pglMatrixMode( GL_PROJECTION ); @@ -272,32 +272,32 @@ static qboolean R_CreateBuffer_GLES1( int width, int height, uint *stride, uint pglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); pglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - //if( vbo ) + // if( vbo ) // pglDeleteBuffers( 1,&vbo ); - pglGenBuffers( 1,&vbo ); + pglGenBuffers( 1, &vbo ); pglBindBuffer( GL_ARRAY_BUFFER_ARB, vbo ); - pglBufferData( GL_ARRAY_BUFFER_ARB, sizeof(data), data, GL_STATIC_DRAW_ARB ); + pglBufferData( GL_ARRAY_BUFFER_ARB, sizeof( data ), data, GL_STATIC_DRAW_ARB ); pglEnableClientState( GL_VERTEX_ARRAY ); pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); pglVertexPointer( 2, GL_FLOAT, 8, 0 ); pglTexCoordPointer( 2, GL_FLOAT, 8, 0 ); - pglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 ) ; + pglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 ); pglColor4f( 1, 1, 1, 1 ); if( glbuf ) Mem_Free( glbuf ); - glbuf = Mem_Malloc( r_temppool, width*height*2 ); + glbuf = Mem_Malloc( r_temppool, width * height * 2 ); *stride = width; *bpp = 2; - *r = MASK(5) << (6 + 5); - *g = MASK(6) << 5; - *b = MASK(5); + *r = MASK( 5 ) << ( 6 + 5 ); + *g = MASK( 6 ) << 5; + *b = MASK( 5 ); return true; } @@ -320,7 +320,7 @@ static void *R_Lock_GLES3( void ) if( pglUnmapBufferOES ) pglUnmapBufferOES( GL_PIXEL_UNPACK_BUFFER ); pglBindBuffer( GL_PIXEL_UNPACK_BUFFER, 0 ); - glbuf = Mem_Malloc( r_temppool, vid.width*vid.height*2 ); + glbuf = Mem_Malloc( r_temppool, vid.width * vid.height * 2 ); pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glbuf ); return glbuf; } @@ -340,13 +340,13 @@ static void R_Unlock_GLES3( void ) pglUnmapBufferOES( GL_PIXEL_UNPACK_BUFFER ); pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, vid.width, vid.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0 ); } - //pglDrawArrays( GL_TRIANGLE_FAN, 0,4 ); + // pglDrawArrays( GL_TRIANGLE_FAN, 0,4 ); pglBlitFramebuffer( 0, vid.height, vid.width, 0, 0, 0, vid.width, vid.height, GL_COLOR_BUFFER_BIT, GL_NEAREST ); } static qboolean R_CreateBuffer_GLES3( int width, int height, uint *stride, uint *bpp, uint *r, uint *g, uint *b ) { - float data[] = { + float data[] = { // quad verts match texcoords 0, 0, 1, 0, @@ -378,35 +378,35 @@ static qboolean R_CreateBuffer_GLES3( int width, int height, uint *stride, uint pglDeleteBuffers( 1,&pbo ); */ - //pglGenBuffers( 1,&vbo ); + // pglGenBuffers( 1,&vbo ); pglGenBuffers( 1, &pbo ); - //pglBindBuffer( GL_ARRAY_BUFFER_ARB, vbo ); - //pglBufferData( GL_ARRAY_BUFFER_ARB, sizeof(data), data, GL_STATIC_DRAW_ARB ); + // pglBindBuffer( GL_ARRAY_BUFFER_ARB, vbo ); + // pglBufferData( GL_ARRAY_BUFFER_ARB, sizeof(data), data, GL_STATIC_DRAW_ARB ); - //pglEnableClientState( GL_VERTEX_ARRAY ); - //pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + // pglEnableClientState( GL_VERTEX_ARRAY ); + // pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); - //pglVertexPointer( 2, GL_FLOAT, 8, 0 ); - //pglTexCoordPointer( 2, GL_FLOAT, 8, 0 ); - //pglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 ); + // pglVertexPointer( 2, GL_FLOAT, 8, 0 ); + // pglTexCoordPointer( 2, GL_FLOAT, 8, 0 ); + // pglBindBuffer( GL_ARRAY_BUFFER_ARB, 0 ); pglBindBuffer( GL_PIXEL_UNPACK_BUFFER, pbo ); pglBufferData( GL_PIXEL_UNPACK_BUFFER, width * height * 2, 0, GL_STREAM_DRAW_ARB ); pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0 ); - pglGenFramebuffers(1, &fbo); - pglBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - pglFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, to, 0); - pglBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + pglGenFramebuffers( 1, &fbo ); + pglBindFramebuffer( GL_READ_FRAMEBUFFER, fbo ); + pglFramebufferTexture2D( GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, to, 0 ); + pglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 ); - //pglColor4f( 1, 1, 1, 1 ); + // pglColor4f( 1, 1, 1, 1 ); *stride = width; *bpp = 2; - *r = MASK(5) << (6 + 5); - *g = MASK(6) << 5; - *b = MASK(5); + *r = MASK( 5 ) << ( 6 + 5 ); + *g = MASK( 6 ) << 5; + *b = MASK( 5 ); return true; } @@ -417,7 +417,8 @@ static int FIRST_BIT( uint mask ) { uint i; - for( i = 0; !(BIT(i) & mask); i++ ); + for( i = 0; !( BIT( i ) & mask ); i++ ) + ; return i; } @@ -432,62 +433,62 @@ static int COUNT_BITS( uint mask ) return i; } -void R_BuildScreenMap( void ) +static void R_BuildScreenMap( void ) { - int i; - uint rshift = FIRST_BIT(swblit.rmask), gshift = FIRST_BIT(swblit.gmask), bshift = FIRST_BIT(swblit.bmask); - uint rbits = COUNT_BITS(swblit.rmask), gbits = COUNT_BITS(swblit.gmask), bbits = COUNT_BITS(swblit.bmask); - uint rmult = BIT(rbits), gmult = BIT(gbits), bmult = BIT(bbits); - uint rdiv = MASK(5), gdiv = MASK(6), bdiv = MASK(5); + int i; + uint rshift = FIRST_BIT( swblit.rmask ), gshift = FIRST_BIT( swblit.gmask ), bshift = FIRST_BIT( swblit.bmask ); + uint rbits = COUNT_BITS( swblit.rmask ), gbits = COUNT_BITS( swblit.gmask ), bbits = COUNT_BITS( swblit.bmask ); + uint rmult = BIT( rbits ), gmult = BIT( gbits ), bmult = BIT( bbits ); + uint rdiv = MASK( 5 ), gdiv = MASK( 6 ), bdiv = MASK( 5 ); - gEngfuncs.Con_Printf("Blit table: %d %d %d %d %d %d\n", rmult, gmult, bmult, rdiv, gdiv, bdiv ); + gEngfuncs.Con_Printf( "Blit table: %d %d %d %d %d %d\n", rmult, gmult, bmult, rdiv, gdiv, bdiv ); #ifdef SEPARATE_BLIT for( i = 0; i < 256; i++ ) { - unsigned int r,g,b; + unsigned int r, g, b; // 332 to 565 - r = ((i >> (8 - 3) )<< 2 ) & MASK(5); - g = ((i >> (8 - 3 - 3)) << 3) & MASK(6); - b = ((i >> (8 - 3 - 3 - 2)) << 3) & MASK(5); - vid.screen_major[i] = r << (6 + 5) | (g << 5) | b; + r = (( i >> ( 8 - 3 )) << 2 ) & MASK( 5 ); + g = (( i >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 ); + b = (( i >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 ); + vid.screen_major[i] = r << ( 6 + 5 ) | ( g << 5 ) | b; // restore minor GBRGBRGB - r = MOVE_BIT(i, 5, 1) | MOVE_BIT(i, 2, 0); - g = MOVE_BIT(i, 7, 2) | MOVE_BIT(i, 4, 1) | MOVE_BIT(i, 1, 0); - b = MOVE_BIT(i, 6, 2) | MOVE_BIT(i, 3, 1) | MOVE_BIT(i, 0, 0); - vid.screen_minor[i] = r << (6 + 5) | (g << 5) | b; + r = MOVE_BIT( i, 5, 1 ) | MOVE_BIT( i, 2, 0 ); + g = MOVE_BIT( i, 7, 2 ) | MOVE_BIT( i, 4, 1 ) | MOVE_BIT( i, 1, 0 ); + b = MOVE_BIT( i, 6, 2 ) | MOVE_BIT( i, 3, 1 ) | MOVE_BIT( i, 0, 0 ); + vid.screen_minor[i] = r << ( 6 + 5 ) | ( g << 5 ) | b; } #else for( i = 0; i < 256; i++ ) { - unsigned int r,g,b , major, j; + unsigned int r, g, b, major, j; // 332 to 565 - r = ((i >> (8 - 3) )<< 2 ) & MASK(5); - g = ((i >> (8 - 3 - 3)) << 3) & MASK(6); - b = ((i >> (8 - 3 - 3 - 2)) << 3) & MASK(5); - //major = r << (6 + 5) | (g << 5) | b; - major = (r * rmult / rdiv) << rshift | (g * gmult / gdiv) << gshift | (b * bmult / bdiv) << bshift; + r = (( i >> ( 8 - 3 )) << 2 ) & MASK( 5 ); + g = (( i >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 ); + b = (( i >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 ); + // major = r << (6 + 5) | (g << 5) | b; + major = ( r * rmult / rdiv ) << rshift | ( g * gmult / gdiv ) << gshift | ( b * bmult / bdiv ) << bshift; for( j = 0; j < 256; j++ ) { uint minor; // restore minor GBRGBRGB - r = MOVE_BIT(j, 5, 1) | MOVE_BIT(j, 2, 0); - g = MOVE_BIT(j, 7, 2) | MOVE_BIT(j, 4, 1) | MOVE_BIT(j, 1, 0); - b = MOVE_BIT(j, 6, 2) | MOVE_BIT(j, 3, 1) | MOVE_BIT(j, 0, 0); - //vid.screen[(i<<8)|j] = r << (6 + 5) | (g << 5) | b | major; - minor = (r * rmult / rdiv) << rshift | (g * gmult / gdiv) << gshift | (b * bmult / bdiv) << bshift; + r = MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 ); + g = MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 ); + b = MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 ); + // vid.screen[(i<<8)|j] = r << (6 + 5) | (g << 5) | b | major; + minor = ( r * rmult / rdiv ) << rshift | ( g * gmult / gdiv ) << gshift | ( b * bmult / bdiv ) << bshift; if( swblit.bpp == 2 ) - vid.screen[(i<<8)|j] = major | minor; + vid.screen[( i << 8 ) | j] = major | minor; else - vid.screen32[(i<<8)|j] = major | minor; + vid.screen32[( i << 8 ) | j] = major | minor; } @@ -495,100 +496,90 @@ void R_BuildScreenMap( void ) #endif } -#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++ ) +#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++ ) -void R_BuildBlendMaps( void ) +static void R_BuildBlendMaps( void ) { unsigned int r1, g1, b1; unsigned int r2, g2, b2; unsigned int i, j; - FOR_EACH_COLOR(1)FOR_EACH_COLOR(2) + FOR_EACH_COLOR( 1 ) FOR_EACH_COLOR( 2 ) { - unsigned int r, g, b; - unsigned short index1 = r1 << (2 + 3) | g1 << 2 | b1; - unsigned short index2 = (r2 << (2 + 3) | g2 << 2 | b2) << 8; - unsigned int a; + unsigned int r, g, b; + unsigned short index1 = r1 << ( 2 + 3 ) | g1 << 2 | b1; + unsigned short index2 = ( r2 << ( 2 + 3 ) | g2 << 2 | b2 ) << 8; + unsigned int a; r = r1 + r2; g = g1 + g2; b = b1 + b2; - if( r > MASK(3) ) - r = MASK(3); - if( g > MASK(3) ) - g = MASK(3); - if( b > MASK(2) ) - b = MASK(2); - ASSERT(!vid.addmap[index2|index1]); + if( r > MASK( 3 )) + r = MASK( 3 ); + if( g > MASK( 3 )) + g = MASK( 3 ); + if( b > MASK( 2 )) + b = MASK( 2 ); + ASSERT( !vid.addmap[index2 | index1] ); - vid.addmap[index2|index1] = r << (2 + 3) | g << 2 | b; - r = r1 * r2 / MASK(3); - g = g1 * g2 / MASK(3); - b = b1 * b2 / MASK(2); + vid.addmap[index2 | index1] = r << ( 2 + 3 ) | g << 2 | b; + r = r1 * r2 / MASK( 3 ); + g = g1 * g2 / MASK( 3 ); + b = b1 * b2 / MASK( 2 ); - vid.modmap[index2|index1] = r << (2 + 3) | g << 2 | b; -#if 0 - for( a = 0; a < 8; a++ ) - { - r = r1 * (7 - a) / 7 + r2 * a / 7; - g = g1 * (7 - a) / 7 + g2 * a / 7; - b = b1 * (7 - a) / 7 + b2 * a / 7; - //if( b == 1 ) b = 0; - vid.alphamap[a << 16|index2|index1] = r << (2 + 3) | g << 2 | b; - } -#endif + vid.modmap[index2 | index1] = r << ( 2 + 3 ) | g << 2 | b; } for( i = 0; i < 8192; i++ ) { - unsigned int r, g, b; - uint color = i << 3; - uint m = color >> 8; - uint j = color & 0xff; + unsigned int r, g, b; + uint color = i << 3; + uint m = color >> 8; + uint j = color & 0xff; unsigned short index1 = i; - r1 = ((m >> (8 - 3) )<< 2 ) & MASK(5); - g1 = ((m >> (8 - 3 - 3)) << 3) & MASK(6); - b1 = ((m >> (8 - 3 - 3 - 2)) << 3) & MASK(5); - r1 |= MOVE_BIT(j, 5, 1) | MOVE_BIT(j, 2, 0); - g1 |= MOVE_BIT(j, 7, 2) | MOVE_BIT(j, 4, 1) | MOVE_BIT(j, 1, 0); - b1 |= MOVE_BIT(j, 6, 2) | MOVE_BIT(j, 3, 1) | MOVE_BIT(j, 0, 0); + r1 = (( m >> ( 8 - 3 )) << 2 ) & MASK( 5 ); + g1 = (( m >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 ); + b1 = (( m >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 ); + r1 |= MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 ); + g1 |= MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 ); + b1 |= MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 ); - for( j = 0; j < 32; j++) + for( j = 0; j < 32; j++ ) { unsigned int index2 = j << 13; unsigned int major, minor; r = r1 * j / 32; g = g1 * j / 32; b = b1 * j / 32; - major = (((r >> 2) & MASK(3)) << 5) |( (( (g >> 3) & MASK(3)) << 2 ) )| (((b >> 3) & MASK(2))); + major = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 ))); // save minor GBRGBRGB - minor = 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); + minor = 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 ); - vid.colormap[index2|index1] = major << 8 | (minor & 0xFF); + vid.colormap[index2 | index1] = major << 8 | ( minor & 0xFF ); } } -#if 1 + for( i = 0; i < 1024; i++ ) { - unsigned int r, g, b; - uint color = i << 6 | BIT(5) | BIT(4) | BIT(3); - uint m = color >> 8; - uint j = color & 0xff; + unsigned int r, g, b; + uint color = i << 6 | BIT( 5 ) | BIT( 4 ) | BIT( 3 ); + uint m = color >> 8; + uint j = color & 0xff; unsigned short index1 = i; - r1 = ((m >> (8 - 3) )<< 2 ) & MASK(5); - g1 = ((m >> (8 - 3 - 3)) << 3) & MASK(6); - b1 = ((m >> (8 - 3 - 3 - 2)) << 3) & MASK(5); - r1 |= MOVE_BIT(j, 5, 1) | MOVE_BIT(j, 2, 0); - g1 |= MOVE_BIT(j, 7, 2) | MOVE_BIT(j, 4, 1) | MOVE_BIT(j, 1, 0); - b1 |= MOVE_BIT(j, 6, 2) | MOVE_BIT(j, 3, 1) | MOVE_BIT(j, 0, 0); + r1 = (( m >> ( 8 - 3 )) << 2 ) & MASK( 5 ); + g1 = (( m >> ( 8 - 3 - 3 )) << 3 ) & MASK( 6 ); + b1 = (( m >> ( 8 - 3 - 3 - 2 )) << 3 ) & MASK( 5 ); + r1 |= MOVE_BIT( j, 5, 1 ) | MOVE_BIT( j, 2, 0 ); + g1 |= MOVE_BIT( j, 7, 2 ) | MOVE_BIT( j, 4, 1 ) | MOVE_BIT( j, 1, 0 ); + b1 |= MOVE_BIT( j, 6, 2 ) | MOVE_BIT( j, 3, 1 ) | MOVE_BIT( j, 0, 0 ); - FOR_EACH_COLOR(2) + FOR_EACH_COLOR( 2 ) { - unsigned int index2 = (r2 << (2 + 3) | g2 << 2 | b2) << 10; + unsigned int index2 = ( r2 << ( 2 + 3 ) | g2 << 2 | b2 ) << 10; unsigned int k; for( k = 0; k < 3; k++ ) { @@ -596,35 +587,33 @@ void R_BuildBlendMaps( void ) unsigned int a = k + 2; - r = r1 * (7 - a) / 7 + (r2 << 2 | BIT(2)) * a / 7; - g = g1 * (7 - a) / 7 + (g2 << 3 | MASK(2)) * a / 7; - b = b1 * (7 - a) / 7 + (b2 << 3 | MASK(2)) * a / 7; - if( r > MASK(5) ) - r = MASK(5); - if( g > MASK(6) ) - g = MASK(6); - if( b > MASK(5) ) - b = MASK(5); + r = r1 * ( 7 - a ) / 7 + ( r2 << 2 | BIT( 2 )) * a / 7; + g = g1 * ( 7 - a ) / 7 + ( g2 << 3 | MASK( 2 )) * a / 7; + b = b1 * ( 7 - a ) / 7 + ( b2 << 3 | MASK( 2 )) * a / 7; + if( r > MASK( 5 )) + r = MASK( 5 ); + if( g > MASK( 6 )) + g = MASK( 6 ); + if( b > MASK( 5 )) + b = MASK( 5 ); ASSERT( b < 32 ); - major = (((r >> 2) & MASK(3)) << 5) |( (( (g >> 3) & MASK(3)) << 2 ) )| (((b >> 3) & MASK(2))); + major = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 ))); // save minor GBRGBRGB - minor = 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); + minor = 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 ); minor = minor & ~0x3f; - - vid.alphamap[k << 18|index2|index1] = major << 8 | (minor & 0xFF); + vid.alphamap[k << 18 | index2 | index1] = major << 8 | ( minor & 0xFF ); } } } -#endif } -void R_AllocScreen( void ); +static qboolean R_AllocScreen( void ); -void R_InitBlit( qboolean glblit ) +qboolean R_InitBlit( qboolean glblit ) { R_BuildBlendMaps(); @@ -646,10 +635,11 @@ void R_InitBlit( qboolean glblit ) swblit.pUnlockBuffer = gEngfuncs.SW_UnlockBuffer; swblit.pCreateBuffer = gEngfuncs.SW_CreateBuffer; } - R_AllocScreen(); + + return R_AllocScreen(); } -void R_AllocScreen( void ) +static qboolean R_AllocScreen( void ) { int w, h; @@ -661,50 +651,60 @@ void R_AllocScreen( void ) R_InitCaches(); if( swblit.rotate ) - w = gpGlobals->height, h = gpGlobals->width; + { + w = gpGlobals->height; + h = gpGlobals->width; + } else - h = gpGlobals->height, w = gpGlobals->width; + { + w = gpGlobals->width; + h = gpGlobals->height; + } + + if( !swblit.pCreateBuffer( w, h, &swblit.stride, &swblit.bpp, + &swblit.rmask, &swblit.gmask, &swblit.bmask )) + return false; - swblit.pCreateBuffer( w, h, &swblit.stride, &swblit.bpp, - &swblit.rmask, &swblit.gmask, &swblit.bmask); R_BuildScreenMap(); vid.width = gpGlobals->width; vid.height = gpGlobals->height; vid.rowbytes = gpGlobals->width; // rowpixels if( d_pzbuffer ) free( d_pzbuffer ); - d_pzbuffer = malloc( vid.width*vid.height*2 + 64 ); + d_pzbuffer = malloc( vid.width * vid.height * 2 + 64 ); + if( vid.buffer ) free( vid.buffer ); + vid.buffer = malloc( vid.width * vid.height * sizeof( pixel_t )); - vid.buffer = malloc( vid.width * vid.height*sizeof( pixel_t ) ); + return true; } void R_BlitScreen( void ) { - int u, v; + int u, v; void *buffer = swblit.pLockBuffer(); // gEngfuncs.Con_Printf("blit begin\n"); - //memset( vid.buffer, 10, vid.width * vid.height ); + // memset( vid.buffer, 10, vid.width * vid.height ); if( !buffer || gpGlobals->width != vid.width || gpGlobals->height != vid.height ) { - gEngfuncs.Con_Printf("pre allocscrn\n"); + gEngfuncs.Con_Printf( "pre allocscrn\n" ); R_AllocScreen(); - gEngfuncs.Con_Printf("post allocscrn\n"); + gEngfuncs.Con_Printf( "post allocscrn\n" ); return; } - //return; - //byte *buf = vid.buffer; + // return; + // byte *buf = vid.buffer; - //#pragma omp parallel for schedule(static) - //gEngfuncs.Con_Printf("swblit %d %d", swblit.bpp, vid.height ); + // #pragma omp parallel for schedule(static) + // gEngfuncs.Con_Printf("swblit %d %d", swblit.bpp, vid.height ); if( swblit.rotate ) { if( swblit.bpp == 2 ) { unsigned short *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint d = swblit.stride - v - 1; @@ -721,7 +721,7 @@ void R_BlitScreen( void ) { unsigned int *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint d = swblit.stride - v - 1; @@ -737,7 +737,7 @@ void R_BlitScreen( void ) else if( swblit.bpp == 3 ) { byte *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint d = swblit.stride - v - 1; @@ -745,11 +745,11 @@ void R_BlitScreen( void ) for( u = 0; u < vid.width; u++ ) { unsigned int s = vid.screen32[vid.buffer[start + u]]; - pbuf[(d)*3] = s; + pbuf[( d ) * 3] = s; s = s >> 8; - pbuf[(d)*3+1] = s; + pbuf[( d ) * 3 + 1] = s; s = s >> 8; - pbuf[(d)*3+2] = s; + pbuf[( d ) * 3 + 2] = s; d += swblit.stride; } } @@ -760,7 +760,7 @@ void R_BlitScreen( void ) if( swblit.bpp == 2 ) { unsigned short *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint dstart = swblit.stride * v; @@ -776,7 +776,7 @@ void R_BlitScreen( void ) { unsigned int *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint dstart = swblit.stride * v; @@ -791,7 +791,7 @@ void R_BlitScreen( void ) else if( swblit.bpp == 3 ) { byte *pbuf = buffer; - for( v = 0; v < vid.height;v++) + for( v = 0; v < vid.height; v++ ) { uint start = vid.rowbytes * v; uint dstart = swblit.stride * v; @@ -799,11 +799,11 @@ void R_BlitScreen( void ) for( u = 0; u < vid.width; u++ ) { unsigned int s = vid.screen32[vid.buffer[start + u]]; - pbuf[(dstart+u)*3] = s; + pbuf[( dstart + u ) * 3] = s; s = s >> 8; - pbuf[(dstart+u)*3+1] = s; + pbuf[( dstart + u ) * 3 + 1] = s; s = s >> 8; - pbuf[(dstart+u)*3+2] = s; + pbuf[( dstart + u ) * 3 + 2] = s; } } } @@ -812,3 +812,108 @@ void R_BlitScreen( void ) swblit.pUnlockBuffer(); // gEngfuncs.Con_Printf("blit end\n"); } + +static uint32_t Get8888PixelAt( int u, int start ) +{ + uint32_t s; + switch( swblit.bpp ) + { + case 2: + { + pixel_t color = vid.screen[vid.buffer[start + u]]; + uint8_t c[3]; + c[0] = (((( color >> 11 ) & 0x1F ) * 527 ) + 23 ) >> 6; + c[1] = (((( color >> 5 ) & 0x3F ) * 259 ) + 33 ) >> 6; + c[2] = (((( color ) & 0x1F ) * 527 ) + 23 ) >> 6; + + s = c[0] << 16 | c[1] << 8 | c[2]; + break; + } + case 3: + case 4: + default: + s = vid.screen32[vid.buffer[start + u]]; + break; + } + return s | 0xFF000000; +} + +qboolean GAME_EXPORT VID_ScreenShot( const char *filename, int shot_type ) +{ + rgbdata_t *r_shot; + uint flags = IMAGE_FLIP_Y; + int width = 0, height = 0, u, v; + qboolean result; + + r_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t )); + r_shot->width = ( vid.width + 3 ) & ~3; + r_shot->height = ( vid.height + 3 ) & ~3; + r_shot->flags = IMAGE_HAS_COLOR; + r_shot->type = PF_BGRA_32; // was RGBA + r_shot->size = r_shot->width * r_shot->height * gEngfuncs.Image_GetPFDesc( r_shot->type )->bpp; + r_shot->palette = NULL; + r_shot->buffer = Mem_Malloc( r_temppool, r_shot->size ); + + // get screen frame + if( swblit.rotate ) + { + uint32_t *pbuf = (uint32_t *)r_shot->buffer; + + for( v = 0; v < vid.height; v++ ) + { + uint start = vid.rowbytes * ( vid.height - v ); + uint d = swblit.stride - v - 1; + + for( u = 0; u < vid.width; u++ ) + { + pbuf[d] = Get8888PixelAt( u, start ); + d += swblit.stride; + } + } + } + else + { + uint32_t *pbuf = (uint32_t *)r_shot->buffer; + + for( v = 0; v < vid.height; v++ ) + { + uint start = vid.rowbytes * ( vid.height - v ); + uint dstart = swblit.stride * v; + + for( u = 0; u < vid.width; u++ ) + { + pbuf[dstart + u] = Get8888PixelAt( u, start ); + } + } + } + + switch( shot_type ) + { + case VID_SCREENSHOT: + break; + case VID_SNAPSHOT: + gEngfuncs.fsapi->AllowDirectPaths( true ); + break; + case VID_LEVELSHOT: + case VID_MINISHOT: + flags |= IMAGE_RESAMPLE; + height = shot_type == VID_MINISHOT ? 200 : 480; + width = Q_rint( height * ((double)r_shot->width / r_shot->height )); + break; + case VID_MAPSHOT: + flags |= IMAGE_RESAMPLE | IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format + height = 768; + width = 1024; + break; + } + + gEngfuncs.Image_Process( &r_shot, width, height, flags, 0.0f ); + + // write image + result = gEngfuncs.FS_SaveImage( filename, r_shot ); + gEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.FS_FreeImage( r_shot ); + + return result; +} + diff --git a/ref/soft/r_image.c b/ref/soft/r_image.c index 247e2e1e..00619936 100644 --- a/ref/soft/r_image.c +++ b/ref/soft/r_image.c @@ -15,13 +15,13 @@ GNU General Public License for more details. #include "r_local.h" -#define TEXTURES_HASH_SIZE (MAX_TEXTURES >> 2) +#define TEXTURES_HASH_SIZE ( MAX_TEXTURES >> 2 ) -static image_t r_images[MAX_TEXTURES]; -static image_t* r_imagesHashTable[TEXTURES_HASH_SIZE]; -static uint r_numImages; +static image_t r_images[MAX_TEXTURES]; +static image_t *r_imagesHashTable[TEXTURES_HASH_SIZE]; +static uint r_numImages; -#define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) +#define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) /* ================= R_GetTexture @@ -42,25 +42,17 @@ GL_Bind */ void GAME_EXPORT GL_Bind( int tmu, unsigned int texnum ) { - image_t *image; - - extern void (*d_pdrawspans)(void *); - extern void R_PolysetFillSpans8 ( void * ); - extern void R_PolysetDrawSpansConstant8_33( void *pspanpackage); - extern void R_PolysetDrawSpansTextureBlended( void *pspanpackage); - extern void R_PolysetDrawSpansBlended( void *pspanpackage); - extern void R_PolysetDrawSpansAdditive( void *pspanpackage); - extern void R_PolysetDrawSpansGlow( void *pspanpackage); + image_t *image; image = &r_images[texnum]; - //vid.rendermode = kRenderNormal; + // vid.rendermode = kRenderNormal; if( vid.rendermode == kRenderNormal ) { r_affinetridesc.pskin = image->pixels[0]; - d_pdrawspans = R_PolysetFillSpans8 ; + d_pdrawspans = R_PolysetFillSpans8; } - else if( vid.rendermode == kRenderTransAdd) + else if( vid.rendermode == kRenderTransAdd ) { r_affinetridesc.pskin = image->pixels[0]; d_pdrawspans = R_PolysetDrawSpansAdditive; @@ -103,11 +95,12 @@ GL_UpdateTextureParams */ static void GL_UpdateTextureParams( int iTexture ) { - image_t *tex = &r_images[iTexture]; + image_t *tex = &r_images[iTexture]; Assert( tex != NULL ); - if( !tex->pixels) return; // free slot + if( !tex->pixels ) + return; // free slot GL_Bind( XASH_TEXTURE0, iTexture ); } @@ -119,7 +112,7 @@ R_SetTextureParameters */ void R_SetTextureParameters( void ) { - int i; + int i; // change all the existing mipmapped texture objects for( i = 0; i < r_numImages; i++ ) @@ -134,7 +127,7 @@ GL_CalcImageSize */ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int depth ) { - size_t size = 0; + size_t size = 0; // check the depth error depth = Q_max( 1, depth ); @@ -150,12 +143,12 @@ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int d size = width * height * depth * 4; break; case PF_DXT1: - size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; + size = ((( width + 3 ) >> 2 ) * (( height + 3 ) >> 2 ) * 8 ) * depth; break; case PF_DXT3: case PF_DXT5: case PF_ATI2: - size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; + size = ((( width + 3 ) >> 2 ) * (( height + 3 ) >> 2 ) * 16 ) * depth; break; } @@ -174,8 +167,8 @@ static size_t GL_CalcTextureSize( int width, int height, int depth ) static int GL_CalcMipmapCount( image_t *tex, qboolean haveBuffer ) { - int width, height; - int mipcount; + int width, height; + int mipcount; Assert( tex != NULL ); @@ -205,8 +198,8 @@ GL_SetTextureDimensions */ static void GL_SetTextureDimensions( image_t *tex, int width, int height, int depth ) { - int maxTextureSize = 1024; - int maxDepthSize = 1; + int maxTextureSize = 1024; + int maxDepthSize = 1; Assert( tex != NULL ); @@ -254,11 +247,11 @@ GL_SetTextureFormat */ static void GL_SetTextureFormat( image_t *tex, pixformat_t format, int channelMask ) { - qboolean haveColor = ( channelMask & IMAGE_HAS_COLOR ); - qboolean haveAlpha = ( channelMask & IMAGE_HAS_ALPHA ); + qboolean haveColor = ( channelMask & IMAGE_HAS_COLOR ); + qboolean haveAlpha = ( channelMask & IMAGE_HAS_ALPHA ); Assert( tex != NULL ); - //tex->transparent = !!( channelMask & IMAGE_HAS_ALPHA ); + // tex->transparent = !!( channelMask & IMAGE_HAS_ALPHA ); } /* @@ -270,16 +263,17 @@ Assume input buffer is RGBA */ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int outWidth, int outHeight, qboolean isNormalMap ) { - uint frac, fracStep; - uint *in = (uint *)source; - uint p1[0x1000], p2[0x1000]; - byte *pix1, *pix2, *pix3, *pix4; - uint *out, *inRow1, *inRow2; - static byte *scaledImage = NULL; // pointer to a scaled image - vec3_t normal; - int i, x, y; + uint frac, fracStep; + uint *in = (uint *)source; + uint p1[0x1000], p2[0x1000]; + byte *pix1, *pix2, *pix3, *pix4; + uint *out, *inRow1, *inRow2; + static byte *scaledImage = NULL; // pointer to a scaled image + vec3_t normal; + int i, x, y; - if( !source ) return NULL; + if( !source ) + return NULL; scaledImage = Mem_Realloc( r_temppool, scaledImage, outWidth * outHeight * 4 ); fracStep = inWidth * 0x10000 / outWidth; @@ -288,14 +282,14 @@ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int out frac = fracStep >> 2; for( i = 0; i < outWidth; i++ ) { - p1[i] = 4 * (frac >> 16); + p1[i] = 4 * ( frac >> 16 ); frac += fracStep; } - frac = (fracStep >> 2) * 3; + frac = ( fracStep >> 2 ) * 3; for( i = 0; i < outWidth; i++ ) { - p2[i] = 4 * (frac >> 16); + p2[i] = 4 * ( frac >> 16 ); frac += fracStep; } @@ -303,8 +297,8 @@ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int out { for( y = 0; y < outHeight; y++, out += outWidth ) { - inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); - inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + inRow1 = in + inWidth * (int)(((float)y + 0.25f ) * inHeight / outHeight ); + inRow2 = in + inWidth * (int)(((float)y + 0.75f ) * inHeight / outHeight ); for( x = 0; x < outWidth; x++ ) { @@ -320,10 +314,10 @@ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int out if( !VectorNormalizeLength( normal )) VectorSet( normal, 0.5f, 0.5f, 1.0f ); - ((byte *)(out+x))[0] = 128 + (byte)(127.0f * normal[0]); - ((byte *)(out+x))[1] = 128 + (byte)(127.0f * normal[1]); - ((byte *)(out+x))[2] = 128 + (byte)(127.0f * normal[2]); - ((byte *)(out+x))[3] = 255; + ((byte *)( out + x ))[0] = 128 + (byte)( 127.0f * normal[0] ); + ((byte *)( out + x ))[1] = 128 + (byte)( 127.0f * normal[1] ); + ((byte *)( out + x ))[2] = 128 + (byte)( 127.0f * normal[2] ); + ((byte *)( out + x ))[3] = 255; } } } @@ -331,8 +325,8 @@ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int out { for( y = 0; y < outHeight; y++, out += outWidth ) { - inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); - inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + inRow1 = in + inWidth * (int)(((float)y + 0.25f ) * inHeight / outHeight ); + inRow2 = in + inWidth * (int)(((float)y + 0.75f ) * inHeight / outHeight ); for( x = 0; x < outWidth; x++ ) { @@ -341,10 +335,10 @@ byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int out pix3 = (byte *)inRow2 + p1[x]; pix4 = (byte *)inRow2 + p2[x]; - ((byte *)(out+x))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2; - ((byte *)(out+x))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2; - ((byte *)(out+x))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2; - ((byte *)(out+x))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2; + ((byte *)( out + x ))[0] = ( pix1[0] + pix2[0] + pix3[0] + pix4[0] ) >> 2; + ((byte *)( out + x ))[1] = ( pix1[1] + pix2[1] + pix3[1] + pix4[1] ) >> 2; + ((byte *)( out + x ))[2] = ( pix1[2] + pix2[2] + pix3[2] + pix4[2] ) >> 2; + ((byte *)( out + x ))[3] = ( pix1[3] + pix2[3] + pix3[3] + pix4[3] ) >> 2; } } } @@ -359,12 +353,12 @@ GL_BoxFilter3x3 box filter 3x3 ================= */ -void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) +static void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) { - int r = 0, g = 0, b = 0, a = 0; - int count = 0, acount = 0; - int i, j, u, v; - const byte *pixel; + int r = 0, g = 0, b = 0, a = 0; + int count = 0, acount = 0; + int i, j, u, v; + const byte *pixel; for( i = 0; i < 3; i++ ) { @@ -390,7 +384,7 @@ void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) } } - if( acount == 0 ) + if( acount == 0 ) acount = 1; out[0] = r / acount; @@ -406,13 +400,13 @@ GL_ApplyFilter Apply box-filter to 1-bit alpha ================= */ -byte *GL_ApplyFilter( const byte *source, int width, int height ) +static byte *GL_ApplyFilter( const byte *source, int width, int height ) { - byte *in = (byte *)source; - byte *out = (byte *)source; - int i; + byte *in = (byte *)source; + byte *out = (byte *)source; + int i; - if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) ) + if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) return in; for( i = 0; source && i < width * height; i++, in += 4 ) @@ -433,13 +427,14 @@ Operates in place, quartering the size of the texture */ static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, int flags ) { - byte *out = in; - int instride = ALIGN( srcWidth * 4, 1 ); - int mipWidth, mipHeight, outpadding; - int row, x, y, z; - vec3_t normal; + byte *out = in; + int instride = ALIGN( srcWidth * 4, 1 ); + int mipWidth, mipHeight, outpadding; + int row, x, y, z; + vec3_t normal; - if( !in ) return; + if( !in ) + return; mipWidth = Q_max( 1, ( srcWidth >> 1 )); mipHeight = Q_max( 1, ( srcHeight >> 1 )); @@ -464,26 +459,26 @@ static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, { if((( x << 1 ) + 1 ) < srcWidth ) { - normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] ) - + MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] ); - normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] ) - + MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] ); - normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] ) - + MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] ); + normal[0] = MAKE_SIGNED( in[row + 0] ) + MAKE_SIGNED( in[row + 4] ) + + MAKE_SIGNED( next[row + 0] ) + MAKE_SIGNED( next[row + 4] ); + normal[1] = MAKE_SIGNED( in[row + 1] ) + MAKE_SIGNED( in[row + 5] ) + + MAKE_SIGNED( next[row + 1] ) + MAKE_SIGNED( next[row + 5] ); + normal[2] = MAKE_SIGNED( in[row + 2] ) + MAKE_SIGNED( in[row + 6] ) + + MAKE_SIGNED( next[row + 2] ) + MAKE_SIGNED( next[row + 6] ); } else { - normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] ); - normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] ); - normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] ); + normal[0] = MAKE_SIGNED( in[row + 0] ) + MAKE_SIGNED( next[row + 0] ); + normal[1] = MAKE_SIGNED( in[row + 1] ) + MAKE_SIGNED( next[row + 1] ); + normal[2] = MAKE_SIGNED( in[row + 2] ) + MAKE_SIGNED( next[row + 2] ); } if( !VectorNormalizeLength( normal )) VectorSet( normal, 0.5f, 0.5f, 1.0f ); - out[0] = 128 + (byte)(127.0f * normal[0]); - out[1] = 128 + (byte)(127.0f * normal[1]); - out[2] = 128 + (byte)(127.0f * normal[2]); + out[0] = 128 + (byte)( 127.0f * normal[0] ); + out[1] = 128 + (byte)( 127.0f * normal[1] ); + out[2] = 128 + (byte)( 127.0f * normal[2] ); out[3] = 255; } } @@ -497,17 +492,17 @@ static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, { if((( x << 1 ) + 1 ) < srcWidth ) { - out[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2; - out[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2; - out[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2; - out[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2; + out[0] = ( in[row + 0] + in[row + 4] + next[row + 0] + next[row + 4] ) >> 2; + out[1] = ( in[row + 1] + in[row + 5] + next[row + 1] + next[row + 5] ) >> 2; + out[2] = ( in[row + 2] + in[row + 6] + next[row + 2] + next[row + 6] ) >> 2; + out[3] = ( in[row + 3] + in[row + 7] + next[row + 3] + next[row + 7] ) >> 2; } else { - out[0] = (in[row+0] + next[row+0]) >> 1; - out[1] = (in[row+1] + next[row+1]) >> 1; - out[2] = (in[row+2] + next[row+2]) >> 1; - out[3] = (in[row+3] + next[row+3]) >> 1; + out[0] = ( in[row + 0] + next[row + 0] ) >> 1; + out[1] = ( in[row + 1] + next[row + 1] ) >> 1; + out[2] = ( in[row + 2] + next[row + 2] ) >> 1; + out[3] = ( in[row + 3] + next[row + 3] ) >> 1; } } } @@ -524,14 +519,14 @@ upload texture into video memory */ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic ) { - byte *buf, *data; - size_t texsize, size; - uint width, height; - uint i, j, numSides; - uint offset = 0; - qboolean normalMap = false; - const byte *bufend; - int mipCount; + byte *buf, *data; + size_t texsize, size; + uint width, height; + uint i, j, numSides; + uint offset = 0; + qboolean normalMap = false; + const byte *bufend; + int mipCount; tex->fogParams[0] = pic->fogParams[0]; tex->fogParams[1] = pic->fogParams[1]; @@ -540,7 +535,7 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic ) GL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth ); GL_SetTextureFormat( tex, pic->type, pic->flags ); - //gEngfuncs.Con_Printf("%s %d %d\n", tex->name, tex->width, tex->height ); + // gEngfuncs.Con_Printf("%s %d %d\n", tex->name, tex->width, tex->height ); Assert( pic != NULL ); Assert( tex != NULL ); @@ -550,64 +545,60 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic ) buf = pic->buffer; - mipCount = 4;//GL_CalcMipmapCount( tex, ( buf != NULL )); + mipCount = 4; // GL_CalcMipmapCount( tex, ( buf != NULL )); // NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides if((( pic->width != tex->width ) || ( pic->height != tex->height ))) data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); - else data = buf; + else + data = buf; - //if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) + // if( !ImageCompressed( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) // data = GL_ApplyFilter( data, tex->width, tex->height ); // mips will be auto-generated if desired for( j = 0; j < mipCount; j++ ) { - int x, y; + int x, y; width = Q_max( 1, ( tex->width >> j )); height = Q_max( 1, ( tex->height >> j )); texsize = GL_CalcTextureSize( width, height, tex->depth ); size = GL_CalcImageSize( pic->type, width, height, tex->depth ); - //GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data ); + // GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data ); // increase size to workaround triangle renderer bugs // it seems to assume memory readable. maybe it was pointed to WAD? - //tex->pixels[j] = (byte*)Mem_Calloc( r_temppool, width * height * sizeof(pixel_t) + 1024 ) + 512; - tex->pixels[j] = (pixel_t*)Mem_Calloc( r_temppool, width * height * sizeof(pixel_t) ); + // tex->pixels[j] = (byte*)Mem_Calloc( r_temppool, width * height * sizeof(pixel_t) + 1024 ) + 512; + tex->pixels[j] = (pixel_t *)Mem_Calloc( r_temppool, width * height * sizeof( pixel_t )); - //memset( (byte*)tex->pixels[j] - 512, 0xFF, 512 ); - //memset( (byte*)tex->pixels[j] + width * height * sizeof(pixel_t), 0xFF, 512 ); + // memset( (byte*)tex->pixels[j] - 512, 0xFF, 512 ); + // memset( (byte*)tex->pixels[j] + width * height * sizeof(pixel_t), 0xFF, 512 ); - if( j == 0 && tex->flags & TF_HAS_ALPHA ) - tex->alpha_pixels = (pixel_t*)Mem_Calloc( r_temppool, width * height * sizeof(pixel_t) ); + if( j == 0 && tex->flags & TF_HAS_ALPHA ) + tex->alpha_pixels = (pixel_t *)Mem_Calloc( r_temppool, width * height * sizeof( pixel_t )); - for(i = 0; i < height * width; i++ ) + for( i = 0; i < height * width; i++ ) { - unsigned int r, g, b, major, minor; - #if 0 - r = data[i * 4 + 0] * MASK(5-1) / 255; - g = data[i * 4 + 1] * MASK(6-1) / 255; - b = data[i * 4 + 2] * MASK(5-1) / 255; - #else - // seems to look better - r = data[i * 4 + 0] * BIT(5) / 256; - g = data[i * 4 + 1] * BIT(6) / 256; - b = data[i * 4 + 2] * BIT(5) / 256; - #endif - // 565 to 332 - major = (((r >> 2) & MASK(3)) << 5) |( (( (g >> 3) & MASK(3)) << 2 ) )| (((b >> 3) & MASK(2))); + unsigned int r, g, b, major, minor; + // seems to look better + r = data[i * 4 + 0] * BIT( 5 ) / 256; + g = data[i * 4 + 1] * BIT( 6 ) / 256; + b = data[i * 4 + 2] * BIT( 5 ) / 256; - // save minor GBRGBRGB - minor = 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); + // 565 to 332 + major = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 ))); - tex->pixels[j][i] = major << 8 | (minor & 0xFF); - if( j == 0 && tex->alpha_pixels ) - { - unsigned int alpha = (data[i * 4 + 3] * 8 / 256) << (16 - 3); - tex->alpha_pixels[i] = (tex->pixels[j][i] >> 3) | alpha; - if( !sw_noalphabrushes->value && data[i * 4 + 3] < 128 && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA ) ) - tex->pixels[j][i] = TRANSPARENT_COLOR; //0000 0011 0100 1001; - } + // save minor GBRGBRGB + minor = 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 ); + + tex->pixels[j][i] = major << 8 | ( minor & 0xFF ); + if( j == 0 && tex->alpha_pixels ) + { + unsigned int alpha = ( data[i * 4 + 3] * 8 / 256 ) << ( 16 - 3 ); + tex->alpha_pixels[i] = ( tex->pixels[j][i] >> 3 ) | alpha; + if( !sw_noalphabrushes.value && data[i * 4 + 3] < 128 && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) + tex->pixels[j][i] = TRANSPARENT_COLOR; // 0000 0011 0100 1001; + } } @@ -617,124 +608,9 @@ static qboolean GL_UploadTexture( image_t *tex, rgbdata_t *pic ) tex->size += texsize; tex->numMips++; - //GL_CheckTexImageError( tex ); + // GL_CheckTexImageError( tex ); } -#if 0 - - - GL_SetTextureTarget( tex, pic ); // must be first - - // make sure what target is correct - if( tex->target == GL_NONE ) - { - gEngfuncs.Con_DPrintf( S_ERROR "GL_UploadTexture: %s is not supported by your hardware\n", tex->name ); - return false; - } - - GL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth ); - GL_SetTextureFormat( tex, pic->type, pic->flags ); - - tex->fogParams[0] = pic->fogParams[0]; - tex->fogParams[1] = pic->fogParams[1]; - tex->fogParams[2] = pic->fogParams[2]; - tex->fogParams[3] = pic->fogParams[3]; - - if(( pic->width * pic->height ) & 3 ) - { - // will be resampled, just tell me for debug targets - gEngfuncs.Con_Reportf( "GL_UploadTexture: %s s&3 [%d x %d]\n", tex->name, pic->width, pic->height ); - } - - buf = pic->buffer; - bufend = pic->buffer + pic->size; // total image size include all the layers, cube sides, mipmaps - offset = GL_CalcImageSize( pic->type, pic->width, pic->height, pic->depth ); - texsize = GL_CalcTextureSize( tex->format, tex->width, tex->height, tex->depth ); - normalMap = FBitSet( tex->flags, TF_NORMALMAP ) ? true : false; - numSides = FBitSet( pic->flags, IMAGE_CUBEMAP ) ? 6 : 1; - - // uploading texture into video memory, change the binding - glState.currentTextures[glState.activeTMU] = tex->texnum; - pglBindTexture( tex->target, tex->texnum ); - - for( i = 0; i < numSides; i++ ) - { - // track the buffer bounds - if( buf != NULL && buf >= bufend ) - gEngfuncs.Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name ); - - if( ImageDXT( pic->type )) - { - for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) - { - width = Q_max( 1, ( tex->width >> j )); - height = Q_max( 1, ( tex->height >> j )); - texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); - size = GL_CalcImageSize( pic->type, width, height, tex->depth ); - GL_TextureImageDXT( tex, i, j, width, height, tex->depth, size, buf ); - tex->size += texsize; - buf += size; // move pointer - tex->numMips++; - - GL_CheckTexImageError( tex ); - } - } - else if( Q_max( 1, pic->numMips ) > 1 ) // not-compressed DDS - { - for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) - { - width = Q_max( 1, ( tex->width >> j )); - height = Q_max( 1, ( tex->height >> j )); - texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); - size = GL_CalcImageSize( pic->type, width, height, tex->depth ); - GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, buf ); - tex->size += texsize; - buf += size; // move pointer - tex->numMips++; - - GL_CheckTexImageError( tex ); - - } - } - else // RGBA32 - { - int mipCount = GL_CalcMipmapCount( tex, ( buf != NULL )); - - // NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides - if(( tex->depth == 1 ) && ( pic->width != tex->width ) || ( pic->height != tex->height )) - data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); - else data = buf; - - if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) - data = GL_ApplyFilter( data, tex->width, tex->height ); - - // mips will be auto-generated if desired - for( j = 0; j < mipCount; j++ ) - { - width = Q_max( 1, ( tex->width >> j )); - height = Q_max( 1, ( tex->height >> j )); - texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); - size = GL_CalcImageSize( pic->type, width, height, tex->depth ); - GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data ); - if( mipCount > 1 ) - GL_BuildMipMap( data, width, height, tex->depth, tex->flags ); - tex->size += texsize; - tex->numMips++; - - GL_CheckTexImageError( tex ); - } - - // move to next side - if( numSides > 1 && ( buf != NULL )) - buf += GL_CalcImageSize( pic->type, pic->width, pic->height, 1 ); - } - } - - SetBits( tex->flags, TF_IMG_UPLOADED ); // done - tex->numMips /= numSides; - - return true; -#endif return true; } @@ -747,13 +623,15 @@ do specified actions on pixels */ static void GL_ProcessImage( image_t *tex, rgbdata_t *pic ) { - uint img_flags = 0; + uint img_flags = 0; // force upload texture as RGB or RGBA (detail textures requires this) - if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; - if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; + if( tex->flags & TF_FORCE_COLOR ) + pic->flags |= IMAGE_HAS_COLOR; + if( pic->flags & IMAGE_HAS_ALPHA ) + tex->flags |= TF_HAS_ALPHA; - if( ImageDXT( pic->type )) + if( ImageCompressed( pic->type )) { if( !pic->numMips ) tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request @@ -794,11 +672,11 @@ static void GL_ProcessImage( image_t *tex, rgbdata_t *pic ) GL_CheckTexName ================ */ -qboolean GL_CheckTexName( const char *name ) +static qboolean GL_CheckTexName( const char *name ) { int len; - if( !COM_CheckString( name ) ) + if( !COM_CheckString( name )) return false; len = Q_strlen( name ); @@ -806,7 +684,7 @@ qboolean GL_CheckTexName( const char *name ) // because multi-layered textures can exceed name string if( len >= sizeof( r_images->name )) { - gEngfuncs.Con_Printf( S_ERROR "LoadTexture: too long name %s (%d)\n", name, len); + gEngfuncs.Con_Printf( S_ERROR "%s: too long name %s (%d)\n", __func__, name, len ); return false; } @@ -820,8 +698,8 @@ GL_TextureForName */ static image_t *GL_TextureForName( const char *name ) { - image_t *tex; - uint hash; + image_t *tex; + uint hash; // find the texture in array hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); @@ -842,17 +720,18 @@ GL_AllocTexture */ static image_t *GL_AllocTexture( const char *name, texFlags_t flags ) { - image_t *tex; - uint i; + image_t *tex; + uint i; // find a free texture_t slot for( i = 0, tex = r_images; i < r_numImages; i++, tex++ ) - if( !tex->name[0] ) break; + if( !tex->name[0] ) + break; if( i == r_numImages ) { if( r_numImages == MAX_TEXTURES ) - gEngfuncs.Host_Error( "GL_AllocTexture: MAX_TEXTURES limit exceeds\n" ); + gEngfuncs.Host_Error( "%s: MAX_TEXTURES limit exceeds\n", __func__ ); r_numImages++; } @@ -861,7 +740,7 @@ static image_t *GL_AllocTexture( const char *name, texFlags_t flags ) // copy initial params Q_strncpy( tex->name, name, sizeof( tex->name )); - //tex->texnum = i; // texnum is used for fast acess into gl_textures array too + // tex->texnum = i; // texnum is used for fast acess into gl_textures array too tex->flags = flags; // add to hash table @@ -879,19 +758,20 @@ GL_DeleteTexture */ static void GL_DeleteTexture( image_t *tex ) { - image_t **prev; - image_t *cur; - int i; + image_t **prev; + image_t *cur; + int i; ASSERT( tex != NULL ); // already freed? - if( !tex->pixels[0]) return; + if( !tex->pixels[0] ) + return; // debug if( !tex->name[0] ) { - gEngfuncs.Con_Printf( S_ERROR "GL_DeleteTexture: trying to free unnamed texture\n"); + gEngfuncs.Con_Printf( S_ERROR "%s: trying to free unnamed texture\n", __func__ ); return; } @@ -901,7 +781,8 @@ static void GL_DeleteTexture( image_t *tex ) while( 1 ) { cur = *prev; - if( !cur ) break; + if( !cur ) + break; if( cur == tex ) { @@ -916,8 +797,10 @@ static void GL_DeleteTexture( image_t *tex ) gEngfuncs.FS_FreeImage( tex->original ); for( i = 0; i < 4; i++ ) - if( tex->pixels[i]) Mem_Free(tex->pixels[i]); - if( tex->alpha_pixels ) Mem_Free(tex->alpha_pixels); + if( tex->pixels[i] ) + Mem_Free( tex->pixels[i] ); + if( tex->alpha_pixels ) + Mem_Free( tex->alpha_pixels ); memset( tex, 0, sizeof( *tex )); } @@ -931,9 +814,9 @@ recalc image room */ void GAME_EXPORT GL_UpdateTexSize( int texnum, int width, int height, int depth ) { - int i, j, texsize; - int numSides; - image_t *tex; + int i, j, texsize; + int numSides; + image_t *tex; if( texnum <= 0 || texnum >= MAX_TEXTURES ) return; @@ -962,16 +845,16 @@ GL_LoadTexture */ int GAME_EXPORT GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ) { - image_t *tex; - rgbdata_t *pic; - uint picFlags = 0; + image_t *tex; + rgbdata_t *pic; + uint picFlags = 0; if( !GL_CheckTexName( name )) return 0; // see if already loaded if(( tex = GL_TextureForName( name ))) - return (tex - r_images); + return( tex - r_images ); if( FBitSet( flags, TF_NOFLIP_TGA )) SetBits( picFlags, IL_DONTFLIP_TGA ); @@ -983,7 +866,8 @@ int GAME_EXPORT GL_LoadTexture( const char *name, const byte *buf, size_t size, gEngfuncs.Image_SetForceFlags( picFlags ); pic = gEngfuncs.FS_LoadImage( name, buf, size ); - if( !pic ) return 0; // couldn't loading image + if( !pic ) + return 0; // couldn't loading image // allocate the new one tex = GL_AllocTexture( name, flags ); @@ -996,7 +880,7 @@ int GAME_EXPORT GL_LoadTexture( const char *name, const byte *buf, size_t size, return 0; } - GL_ApplyTextureParams( tex ); // update texture filter, wrap etc + GL_ApplyTextureParams( tex ); // update texture filter, wrap etc gEngfuncs.FS_FreeImage( pic ); // release source texture // NOTE: always return texnum as index in array or engine will stop work !!! @@ -1020,22 +904,23 @@ GL_LoadTextureFromBuffer */ int GAME_EXPORT GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) { - image_t *tex; + image_t *tex; if( !GL_CheckTexName( name )) return 0; // see if already loaded if(( tex = GL_TextureForName( name )) && !update ) - return (tex - r_images); + return( tex - r_images ); // couldn't loading image - if( !pic ) return 0; + if( !pic ) + return 0; if( update ) { if( tex == NULL ) - gEngfuncs.Host_Error( "GL_LoadTextureFromBuffer: couldn't find texture %s for update\n", name ); + gEngfuncs.Host_Error( "%s: couldn't find texture %s for update\n", __func__, name ); SetBits( tex->flags, flags ); } else @@ -1052,7 +937,7 @@ int GAME_EXPORT GL_LoadTextureFromBuffer( const char *name, rgbdata_t *pic, texF } GL_ApplyTextureParams( tex ); // update texture filter, wrap etc - return (tex - r_images); + return( tex - r_images ); } /* @@ -1064,8 +949,8 @@ creates texture from buffer */ int GAME_EXPORT GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ) { - int datasize = 1; - rgbdata_t r_empty; + int datasize = 1; + rgbdata_t r_empty; if( FBitSet( flags, TF_ARB_16BIT )) datasize = 2; @@ -1116,14 +1001,14 @@ GL_FindTexture */ int GAME_EXPORT GL_FindTexture( const char *name ) { - image_t *tex; + image_t *tex; if( !GL_CheckTexName( name )) return 0; // see if already loaded if(( tex = GL_TextureForName( name ))) - return (tex - r_images); + return( tex - r_images ); return 0; } @@ -1149,9 +1034,9 @@ GL_ProcessTexture */ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) { - image_t *image; - rgbdata_t *pic; - int flags = 0; + image_t *image; + rgbdata_t *pic; + int flags = 0; if( texnum <= 0 || texnum >= MAX_TEXTURES ) return; // missed image @@ -1168,19 +1053,19 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b } else { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: bad operation for %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: bad operation for %s\n", __func__, image->name ); return; } if( !image->original ) { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: no input data for %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: no input data for %s\n", __func__, image->name ); return; } - if( ImageDXT( image->original->type )) + if( ImageCompressed( image->original->type )) { - gEngfuncs.Con_Printf( S_ERROR "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); + gEngfuncs.Con_Printf( S_ERROR "%s: can't process compressed texture %s\n", __func__, image->name ); return; } @@ -1199,6 +1084,23 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b gEngfuncs.FS_FreeImage( pic ); } +/* +================ +R_TexMemory + +return size of all uploaded textures +================ +*/ +int R_TexMemory( void ) +{ + int i, total = 0; + + for( i = 0; i < r_numImages; i++ ) + total += r_images[i].size; + + return total; +} + /* ============================================================================== @@ -1213,8 +1115,8 @@ GL_FakeImage */ static rgbdata_t *GL_FakeImage( int width, int height, int depth, int flags ) { - static byte data2D[1024]; // 16x16x4 - static rgbdata_t r_image; + static byte data2D[1024]; // 16x16x4 + static rgbdata_t r_image; // also use this for bad textures, but without alpha r_image.width = Q_max( 1, width ); @@ -1223,7 +1125,7 @@ static rgbdata_t *GL_FakeImage( int width, int height, int depth, int flags ) r_image.flags = flags; r_image.type = PF_RGBA_32; r_image.size = r_image.width * r_image.height * r_image.depth * 4; - r_image.buffer = (r_image.size > sizeof( data2D )) ? NULL : data2D; + r_image.buffer = ( r_image.size > sizeof( data2D )) ? NULL : data2D; r_image.palette = NULL; r_image.numMips = 1; r_image.encode = 0; @@ -1242,7 +1144,7 @@ R_InitDlightTexture */ void R_InitDlightTexture( void ) { - rgbdata_t r_image; + rgbdata_t r_image; if( tr.dlightTexture != 0 ) return; // already initialized @@ -1254,7 +1156,7 @@ void R_InitDlightTexture( void ) r_image.type = PF_RGBA_32; r_image.size = r_image.width * r_image.height * 4; - tr.dlightTexture = GL_LoadTextureInternal( "*dlight", &r_image, TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE ); + tr.dlightTexture = GL_LoadTextureInternal( "*dlight", &r_image, TF_NOMIPMAP | TF_CLAMP | TF_ATLAS_PAGE ); } /* @@ -1264,9 +1166,9 @@ GL_CreateInternalTextures */ static void GL_CreateInternalTextures( void ) { - int dx2, dy, d; - int x, y; - rgbdata_t *pic; + int dx2, dy, d; + int x, y; + rgbdata_t *pic; // emo-texture from quake1 pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR ); @@ -1276,15 +1178,16 @@ static void GL_CreateInternalTextures( void ) for( x = 0; x < 16; x++ ) { if(( y < 8 ) ^ ( x < 8 )) - ((uint *)pic->buffer)[y*16+x] = 0xFFFF00FF; - else ((uint *)pic->buffer)[y*16+x] = 0xFF000000; + ((uint *)pic->buffer )[y * 16 + x] = 0xFFFF00FF; + else + ((uint *)pic->buffer )[y * 16 + x] = 0xFF000000; } } tr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); // particle texture from quake1 - pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); + pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA ); for( x = 0; x < 16; x++ ) { @@ -1304,24 +1207,24 @@ static void GL_CreateInternalTextures( void ) // white texture pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFFFFFFFF; + ((uint *)pic->buffer )[x] = 0xFFFFFFFF; tr.whiteTexture = GL_LoadTextureInternal( REF_WHITE_TEXTURE, pic, TF_COLORMAP ); // gray texture pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFF7F7F7F; + ((uint *)pic->buffer )[x] = 0xFF7F7F7F; tr.grayTexture = GL_LoadTextureInternal( REF_GRAY_TEXTURE, pic, TF_COLORMAP ); // black texture pic = GL_FakeImage( 4, 4, 1, IMAGE_HAS_COLOR ); for( x = 0; x < 16; x++ ) - ((uint *)pic->buffer)[x] = 0xFF000000; + ((uint *)pic->buffer )[x] = 0xFF000000; tr.blackTexture = GL_LoadTextureInternal( REF_BLACK_TEXTURE, pic, TF_COLORMAP ); // cinematic dummy pic = GL_FakeImage( 640, 100, 1, IMAGE_HAS_COLOR ); - tr.cinTexture = GL_LoadTextureInternal( "*cintexture", pic, TF_NOMIPMAP|TF_CLAMP ); + tr.cinTexture = GL_LoadTextureInternal( "*cintexture", pic, TF_NOMIPMAP | TF_CLAMP ); } /* @@ -1331,15 +1234,16 @@ R_TextureList_f */ void R_TextureList_f( void ) { - image_t *image; - int i, texCount, bytes = 0; + image_t *image; + int i, texCount, bytes = 0; gEngfuncs.Con_Printf( "\n" ); gEngfuncs.Con_Printf( " -id- -w- -h- -size- -fmt- -type- -data- -encode- -wrap- -depth- -name--------\n" ); for( i = texCount = 0, image = r_images; i < r_numImages; i++, image++ ) { - if( !image->pixels ) continue; + if( !image->pixels ) + continue; bytes += image->size; texCount++; @@ -1350,13 +1254,15 @@ void R_TextureList_f( void ) if( image->flags & TF_NORMALMAP ) gEngfuncs.Con_Printf( "normal " ); - else gEngfuncs.Con_Printf( "diffuse " ); + else + gEngfuncs.Con_Printf( "diffuse " ); if( image->flags & TF_CLAMP ) gEngfuncs.Con_Printf( "clamp " ); else if( image->flags & TF_BORDER ) gEngfuncs.Con_Printf( "border " ); - else gEngfuncs.Con_Printf( "repeat " ); + else + gEngfuncs.Con_Printf( "repeat " ); gEngfuncs.Con_Printf( " %d ", image->depth ); gEngfuncs.Con_Printf( " %s\n", image->name ); } @@ -1399,8 +1305,8 @@ R_ShutdownImages */ void R_ShutdownImages( void ) { - image_t *tex; - int i; + image_t *tex; + int i; gEngfuncs.Cmd_RemoveCommand( "texturelist" ); diff --git a/ref/soft/r_light.c b/ref/soft/r_light.c index 82429070..b9eeadaf 100644 --- a/ref/soft/r_light.c +++ b/ref/soft/r_light.c @@ -19,8 +19,8 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "ref_params.h" -//unused, need refactor -unsigned blocklights[10240]; +// unused, need refactor +unsigned blocklights[10240]; /* ============================================================================= @@ -35,64 +35,65 @@ CL_RunLightStyles ================== */ -void GAME_EXPORT CL_RunLightStyles( void ) +void CL_RunLightStyles( lightstyle_t *ls ) { - int i, k, flight, clight; - float l, lerpfrac, backlerp; - float frametime = (gpGlobals->time - gpGlobals->oldtime); - float scale; - lightstyle_t *ls; + int i; + float frametime = ( gp_cl->time - gp_cl->oldtime ); - if( !WORLDMODEL ) return; + if( !WORLDMODEL ) + return; - scale = 1; //r_lighting_modulate->value; + if( r_fullbright->value || !WORLDMODEL->lightdata ) + { + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + tr.lightstylevalue[i] = 256 * 256; + return; + } // light animations // 'm' is normal light, 'a' is no light, 'z' is double bright for( i = 0; i < MAX_LIGHTSTYLES; i++ ) { - ls = gEngfuncs.GetLightStyle( i ); - if( r_fullbright->value || !WORLDMODEL->lightdata ) + int k, flight, clight; + float l, lerpfrac, backlerp; + + if( !gp_cl->paused && frametime <= 0.1f ) + ls[i].time += frametime; // evaluate local time + + if( !ls[i].length ) { - tr.lightstylevalue[i] = 256 * 256; + tr.lightstylevalue[i] = 256; continue; } - - if( !ENGINE_GET_PARM( PARAM_GAMEPAUSED ) && frametime <= 0.1f ) - ls->time += frametime; // evaluate local time - - flight = (int)Q_floor( ls->time * 10 ); - clight = (int)Q_ceil( ls->time * 10 ); - lerpfrac = ( ls->time * 10 ) - flight; - backlerp = 1.0f - lerpfrac; - - if( !ls->length ) - { - tr.lightstylevalue[i] = 256 * scale; - continue; - } - else if( ls->length == 1 ) + else if( ls[i].length == 1 ) { // single length style so don't bother interpolating - tr.lightstylevalue[i] = ls->map[0] * 22 * scale; + tr.lightstylevalue[i] = ( ls[i].map[0] ) * 22; continue; } - else if( !ls->interp ) // || !CVAR_TO_BOOL( cl_lightstyle_lerping )) + + flight = (int)Q_floor( ls[i].time * 10 ); + + if( !ls[i].interp || !cl_lightstyle_lerping->value ) { - tr.lightstylevalue[i] = ls->map[flight%ls->length] * 22 * scale; + tr.lightstylevalue[i] = ls[i].map[flight % ls[i].length] * 22; continue; } + clight = (int)Q_ceil( ls[i].time * 10 ); + lerpfrac = ( ls[i].time * 10 ) - flight; + backlerp = 1.0f - lerpfrac; + // interpolate animating light // frame just gone - k = ls->map[flight % ls->length]; + k = ls[i].map[flight % ls[i].length]; l = (float)( k * 22.0f ) * backlerp; // upcoming frame - k = ls->map[clight % ls->length]; + k = ls[i].map[clight % ls[i].length]; l += (float)( k * 22.0f ) * lerpfrac; - tr.lightstylevalue[i] = (int)l * scale; + tr.lightstylevalue[i] = (int)l; } } @@ -106,41 +107,47 @@ void R_MarkLights( dlight_t *light, int bit, mnode_t *node ) float dist; msurface_t *surf; int i; + mnode_t *children[2]; + int firstsurface, numsurfaces; if( !node || node->contents < 0 ) return; dist = PlaneDiff( light->origin, node->plane ); + node_children( children, node, RI.currentmodel ); + firstsurface = node_firstsurface( node, RI.currentmodel ); + numsurfaces = node_numsurfaces( node, RI.currentmodel ); + if( dist > light->radius ) { - R_MarkLights( light, bit, node->children[0] ); + R_MarkLights( light, bit, children[0] ); return; } if( dist < -light->radius ) { - R_MarkLights( light, bit, node->children[1] ); + R_MarkLights( light, bit, children[1] ); return; } // mark the polygons - surf = RI.currentmodel->surfaces + node->firstsurface; + surf = RI.currentmodel->surfaces + firstsurface; - for( i = 0; i < node->numsurfaces; i++, surf++ ) + for( i = 0; i < numsurfaces; i++, surf++ ) { if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius )) continue; // no intersection - if( surf->dlightframe != tr.framecount )//tr.dlightframecount ) + if( surf->dlightframe != tr.dlightframecount ) { surf->dlightbits = 0; - surf->dlightframe = tr.framecount; //tr.dlightframecount; + surf->dlightframe = tr.dlightframecount; } surf->dlightbits |= bit; } - R_MarkLights( light, bit, node->children[0] ); - R_MarkLights( light, bit, node->children[1] ); + R_MarkLights( light, bit, children[0] ); + R_MarkLights( light, bit, children[1] ); } /* @@ -150,71 +157,30 @@ R_PushDlights */ void R_PushDlights( void ) { - dlight_t *l; - int i; + int i; tr.dlightframecount = tr.framecount; - RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - if( RI.currententity ) - RI.currentmodel = RI.currententity->model; + RI.currententity = CL_GetEntityByIndex( 0 ); - for( i = 0; i < MAX_DLIGHTS; i++, l++ ) - { - l = gEngfuncs.GetDynamicLight( i ); + // no world -- no dlights + if( !RI.currententity ) + return; - if( l->die < gpGlobals->time || !l->radius ) - continue; - - //if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) - //continue; - - if( RI.currententity ) - R_MarkLights( l, 1<nodes ); - } -} - -/* -============= -R_CountDlights -============= -*/ -int R_CountDlights( void ) -{ - dlight_t *l; - int i, numDlights = 0; + RI.currentmodel = RI.currententity->model; for( i = 0; i < MAX_DLIGHTS; i++ ) { - l = gEngfuncs.GetDynamicLight( i ); + dlight_t *l = &tr.dlights[i]; - if( l->die < gpGlobals->time || !l->radius ) + if( l->die < gp_cl->time || !l->radius ) continue; - numDlights++; + // if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) + // continue; + + R_MarkLights( l, 1 << i, RI.currentmodel->nodes ); } - - return numDlights; -} - -/* -============= -R_CountSurfaceDlights -============= -*/ -int R_CountSurfaceDlights( msurface_t *surf ) -{ - int i, numDlights = 0; - - for( i = 0; i < MAX_DLIGHTS; i++ ) - { - if(!( surf->dlightbits & BIT( i ))) - continue; // not lit by this light - - numDlights++; - } - - return numDlights; } /* @@ -224,9 +190,9 @@ int R_CountSurfaceDlights( msurface_t *surf ) ======================================================================= */ -static vec3_t g_trace_lightspot; -static vec3_t g_trace_lightvec; -static float g_trace_fraction; +static vec3_t g_trace_lightspot; +static vec3_t g_trace_lightvec; +static float g_trace_fraction; /* ================= @@ -235,16 +201,18 @@ R_RecursiveLightPoint */ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end ) { - float front, back, frac, midf; - int i, map, side, size; - float ds, dt, s, t; - int sample_size; - color24 *lm, *dm; - mextrasurf_t *info; - msurface_t *surf; - mtexinfo_t *tex; - matrix3x4 tbn; - vec3_t mid; + float front, back, frac, midf; + int i, map, side, size; + float ds, dt, s, t; + int sample_size; + color24 *lm, *dm; + mextrasurf_t *info; + msurface_t *surf; + mtexinfo_t *tex; + matrix3x4 tbn; + vec3_t mid; + mnode_t *children[2]; + int firstsurface, numsurfaces; // didn't hit anything if( !node || node->contents < 0 ) @@ -253,13 +221,17 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, return false; } + node_children( children, node, model ); + firstsurface = node_firstsurface( node, model ); + numsurfaces = node_numsurfaces( node, model ); + // calculate mid point front = PlaneDiff( start, node->plane ); back = PlaneDiff( end, node->plane ); side = front < 0; if(( back < 0 ) == side ) - return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end ); + return R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end ); frac = front / ( front - back ); @@ -267,7 +239,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, midf = p1f + ( p2f - p1f ) * frac; // co down front side - if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid )) + if( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid )) return true; // hit something if(( back < 0 ) == side ) @@ -277,18 +249,18 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, } // check for impact on this node - surf = model->surfaces + node->firstsurface; + surf = model->surfaces + firstsurface; VectorCopy( mid, g_trace_lightspot ); - for( i = 0; i < node->numsurfaces; i++, surf++ ) + for( i = 0; i < numsurfaces; i++, surf++ ) { - int smax, tmax; + int smax, tmax; tex = surf->texinfo; info = surf->info; if( FBitSet( surf->flags, SURF_DRAWTILED )) - continue; // no lightmaps + continue; // no lightmaps s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3]; t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3]; @@ -299,7 +271,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, ds = s - info->lightmapmins[0]; dt = t - info->lightmapmins[1]; - if ( ds > info->lightextents[0] || dt > info->lightextents[1] ) + if( ds > info->lightextents[0] || dt > info->lightextents[1] ) continue; cv->r = cv->g = cv->b = cv->a = 0; @@ -308,8 +280,8 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, return true; sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - smax = (info->lightextents[0] / sample_size) + 1; - tmax = (info->lightextents[1] / sample_size) + 1; + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; ds /= sample_size; dt /= sample_size; @@ -320,11 +292,12 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, if( surf->info->deluxemap ) { - vec3_t faceNormal; + vec3_t faceNormal; if( FBitSet( surf->flags, SURF_PLANEBACK )) VectorNegate( surf->plane->normal, faceNormal ); - else VectorCopy( surf->plane->normal, faceNormal ); + else + VectorCopy( surf->plane->normal, faceNormal ); // compute face TBN #if 1 @@ -344,30 +317,22 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) { - uint scale = tr.lightstylevalue[surf->styles[map]]; + uint scale = tr.lightstylevalue[surf->styles[map]]; + + cv->r += lm->r * scale; + cv->g += lm->g * scale; + cv->b += lm->b * scale; - if( tr.ignore_lightgamma ) - { - cv->r += lm->r * scale * 2.5; // scale; - cv->g += lm->g * scale * 2.5; // scale; - cv->b += lm->b * scale * 2.5; // scale; - } - else - { - cv->r += gEngfuncs.LightToTexGamma( lm->r ) * scale; - cv->g += gEngfuncs.LightToTexGamma( lm->g ) * scale; - cv->b += gEngfuncs.LightToTexGamma( lm->b ) * scale; - } lm += size; // skip to next lightmap if( dm != NULL ) { - vec3_t srcNormal, lightNormal; - float f = (1.0f / 128.0f); + vec3_t srcNormal, lightNormal; + float f = ( 1.0f / 128.0f ); - VectorSet( srcNormal, ((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f ); - Matrix3x4_VectorIRotate( tbn, srcNormal, lightNormal ); // turn to world space - VectorScale( lightNormal, (float)scale * -1.0f, lightNormal ); // turn direction from light + VectorSet( srcNormal, ((float)dm->r - 128.0f ) * f, ((float)dm->g - 128.0f ) * f, ((float)dm->b - 128.0f ) * f ); + Matrix3x4_VectorIRotate( tbn, srcNormal, lightNormal ); // turn to world space + VectorScale( lightNormal, (float)scale * -1.0f, lightNormal ); // turn direction from light VectorAdd( g_trace_lightvec, lightNormal, g_trace_lightvec ); dm += size; // skip to next deluxmap } @@ -377,7 +342,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, } // go down back side - return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end ); + return R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end ); } /* @@ -387,14 +352,16 @@ R_LightVec check bspmodels to get light from ================= */ -colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) +static colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) { - float last_fraction; - int i, maxEnts = 1; - colorVec light, cv; + float last_fraction; + int i, maxEnts = 1; + colorVec light, cv; - if( lspot ) VectorClear( lspot ); - if( lvec ) VectorClear( lvec ); + if( lspot ) + VectorClear( lspot ); + if( lvec ) + VectorClear( lvec ); if( WORLDMODEL && WORLDMODEL->lightdata ) { @@ -402,16 +369,16 @@ colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, last_fraction = 1.0f; // get light from bmodels too - //if( CVAR_TO_BOOL( r_lighting_extended )) - maxEnts = MAX_PHYSENTS; + // if( CVAR_TO_BOOL( r_lighting_extended )) + maxEnts = MAX_PHYSENTS; // check all the bsp-models for( i = 0; i < maxEnts; i++ ) { - physent_t *pe = gEngfuncs.EV_GetPhysent( i ); - vec3_t offset, start_l, end_l; - mnode_t *pnodes; - matrix4x4 matrix; + physent_t *pe = gEngfuncs.EV_GetPhysent( i ); + vec3_t offset, start_l, end_l; + mnode_t *pnodes; + matrix4x4 matrix; if( !pe ) break; @@ -438,15 +405,17 @@ colorVec R_LightVecInternal( const vec3_t start, const vec3_t end, vec3_t lspot, g_trace_fraction = 1.0f; if( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l )) - continue; // didn't hit anything + continue; // didn't hit anything if( g_trace_fraction < last_fraction ) { - if( lspot ) VectorCopy( g_trace_lightspot, lspot ); - if( lvec ) VectorNormalize2( g_trace_lightvec, lvec ); - light.r = Q_min(( cv.r >> 7 ), 255 ); - light.g = Q_min(( cv.g >> 7 ), 255 ); - light.b = Q_min(( cv.b >> 7 ), 255 ); + if( lspot ) + VectorCopy( g_trace_lightspot, lspot ); + if( lvec ) + VectorNormalize2( g_trace_lightvec, lvec ); + light.r = Q_min(( cv.r >> 8 ), 255 ); + light.g = Q_min(( cv.g >> 8 ), 255 ); + light.b = Q_min(( cv.b >> 8 ), 255 ); last_fraction = g_trace_fraction; if(( light.r + light.g + light.b ) != 0 ) @@ -472,9 +441,9 @@ check bspmodels to get light from */ colorVec GAME_EXPORT R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot, vec3_t lvec ) { - colorVec light = R_LightVecInternal( start, end, lspot, lvec ); + colorVec light = R_LightVecInternal( start, end, lspot, lvec ); - //light.r = light.g = light.b = 255; + // light.r = light.g = light.b = 255; if( lspot != NULL && lvec != NULL ) // CVAR_TO_BOOL( r_lighting_extended ) && { @@ -495,7 +464,7 @@ light from floor */ colorVec GAME_EXPORT R_LightPoint( const vec3_t p0 ) { - vec3_t p1; + vec3_t p1; VectorSet( p1, p0[0], p0[1], p0[2] - 2048.0f ); diff --git a/ref/soft/r_local.h b/ref/soft/r_local.h index 3ee559cc..0b76ad97 100644 --- a/ref/soft/r_local.h +++ b/ref/soft/r_local.h @@ -30,78 +30,69 @@ GNU General Public License for more details. #include "enginefeatures.h" #include "com_strings.h" #include "pm_movevars.h" -//#include "cvar.h" +#include "cvardef.h" typedef struct mip_s mip_t; -typedef int fixed8_t; -typedef int fixed16_t; +typedef int fixed8_t; +typedef int fixed16_t; -#define ASSERT(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) -#define Assert(x) if(!( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) +#define ASSERT( x ) if( !( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) +#define Assert( x ) if( !( x )) gEngfuncs.Host_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) #include -#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) cvar_t cv = { cvname, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } -#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) cvar_t cv = { #cv, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } -#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) - -#define WORLD (gEngfuncs.GetWorld()) -#define WORLDMODEL (gEngfuncs.pfnGetModelByIndex( 1 )) -#define MOVEVARS (gEngfuncs.pfnGetMoveVars()) - // make mod_ref.h? -#define LM_SAMPLE_SIZE 16 - +#define LM_SAMPLE_SIZE 16 extern poolhandle_t r_temppool; -#define BLOCK_SIZE tr.block_size // lightmap blocksize -#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility -#define BLOCK_SIZE_MAX 1024 +#define BLOCK_SIZE tr.block_size // lightmap blocksize +#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility +#define BLOCK_SIZE_MAX 1024 -#define MAX_TEXTURES 8192 // a1ba: increased by users request -#define MAX_DECAL_SURFS 4096 +#define MAX_TEXTURES 8192 // a1ba: increased by users request +#define MAX_DECAL_SURFS 4096 #if XASH_LOW_MEMORY #undef MAX_TEXTURES #undef MAX_DECAL_SURFS - #define MAX_TEXTURES 1024 + #define MAX_TEXTURES 1024 #define MAX_DECAL_SURFS 256 #endif -#define MAX_DETAIL_TEXTURES 256 -#define MAX_LIGHTMAPS 256 -#define SUBDIVIDE_SIZE 64 -#define MAX_DRAW_STACK 2 // normal view and menu view +#define MAX_DETAIL_TEXTURES 256 +#define MAX_LIGHTMAPS 256 +#define SUBDIVIDE_SIZE 64 +#define MAX_DRAW_STACK 2 // normal view and menu view -#define SHADEDOT_QUANT 16 // precalculated dot products for quantized angles -#define SHADE_LAMBERT 1.495f -#define DEFAULT_ALPHATEST 0.0f +#define SHADEDOT_QUANT 16 // precalculated dot products for quantized angles +#define SHADE_LAMBERT 1.4953241 +#define DEFAULT_ALPHATEST 0.0f // refparams -#define RP_NONE 0 -#define RP_ENVVIEW BIT( 0 ) // used for cubemapshot -#define RP_OLDVIEWLEAF BIT( 1 ) -#define RP_CLIPPLANE BIT( 2 ) +#define RP_NONE 0 +#define RP_ENVVIEW BIT( 0 ) // used for cubemapshot +#define RP_OLDVIEWLEAF BIT( 1 ) +#define RP_CLIPPLANE BIT( 2 ) -#define RP_NONVIEWERREF (RP_ENVVIEW) -#define R_ModelOpaque( rm ) ( rm == kRenderNormal ) -#define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles )) -#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == gEngfuncs.GetPlayerIndex() && e->player ) -#define RP_NORMALPASS() ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 ) +#define RP_NONVIEWERREF ( RP_ENVVIEW ) +#define R_ModelOpaque( rm ) ( rm == kRenderNormal ) +#define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles )) +#define RP_LOCALCLIENT( e ) (( e ) != NULL && ( e )->index == ( gp_cl->playernum + 1 ) && e->player ) +#define RP_NORMALPASS() ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 ) -#define CL_IsViewEntityLocalPlayer() ( ENGINE_GET_PARM( PARM_VIEWENT_INDEX ) == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) ) +#define CL_IsViewEntityLocalPlayer() ( gp_cl->viewentity == ( gp_cl->playernum + 1 )) -#define CULL_VISIBLE 0 // not culled -#define CULL_BACKSIDE 1 // backside of transparent wall -#define CULL_FRUSTUM 2 // culled by frustum -#define CULL_VISFRAME 3 // culled by PVS -#define CULL_OTHER 4 // culled by other reason +#define CULL_VISIBLE 0 // not culled +#define CULL_BACKSIDE 1 // backside of transparent wall +#define CULL_FRUSTUM 2 // culled by frustum +#define CULL_VISFRAME 3 // culled by PVS +#define CULL_OTHER 4 // culled by other reason // bit operation helpers -#define MASK(x) (BIT(x)-1) -#define GET_BIT(s,b) ((s & (1 << b)) >> b) -#define MOVE_BIT(s, f, t) (GET_BIT(s,f) << t ) +#define MASK( x ) ( BIT( x ) - 1 ) +#define GET_BIT( s, b ) (( s & ( 1 << b )) >> b ) +#define MOVE_BIT( s, f, t ) ( GET_BIT( s, f ) << t ) /* @@ -124,318 +115,288 @@ typedef enum } imagetype_t; -//=================================================================== +// =================================================================== typedef unsigned short pixel_t; typedef struct vrect_s { - int x,y,width,height; - struct vrect_s *pnext; + int x, y, width, height; + struct vrect_s *pnext; } vrect_t; #define COLOR_WHITE 0xFFFF -//#define SEPARATE_BLIT +// #define SEPARATE_BLIT typedef struct { - pixel_t *buffer; // invisible buffer - pixel_t colormap[32*8192]; // 8192 * light levels - //pixel_t *alphamap; // 256 * 256 translucency map + pixel_t *buffer; // invisible buffer + pixel_t colormap[32 * 8192]; // 8192 * light levels + // pixel_t *alphamap; // 256 * 256 translucency map #ifdef SEPARATE_BLIT - pixel_t screen_minor[256]; - pixel_t screen_major[256]; + pixel_t screen_minor[256]; + pixel_t screen_major[256]; #else - pixel_t screen[256*256]; - unsigned int screen32[256*256]; + pixel_t screen[256 * 256]; + unsigned int screen32[256 * 256]; #endif - byte addmap[256*256]; - byte modmap[256*256]; - pixel_t alphamap[3*1024*256]; - pixel_t color; - qboolean is2d; - byte alpha; + byte addmap[256 * 256]; + byte modmap[256 * 256]; + pixel_t alphamap[3 * 1024 * 256]; + pixel_t color; + qboolean is2d; + byte alpha; // maybe compute colormask for minor byte? - int rendermode; - int rowbytes; // may be > width if displayed in a window - // can be negative for stupid dibs - int width; - int height; + int rendermode; + int rowbytes; // may be > width if displayed in a window + // can be negative for stupid dibs + int width; + int height; } viddef_t; extern viddef_t vid; typedef struct { - int params; // rendering parameters + int params; // rendering parameters - qboolean drawWorld; // ignore world for drawing PlayerModel - qboolean isSkyVisible; // sky is visible - qboolean onlyClientDraw; // disabled by client request - qboolean drawOrtho; // draw world as orthogonal projection + qboolean drawWorld; // ignore world for drawing PlayerModel + qboolean isSkyVisible; // sky is visible + qboolean onlyClientDraw; // disabled by client request + qboolean drawOrtho; // draw world as orthogonal projection - float fov_x, fov_y; // current view fov + float fov_x, fov_y; // current view fov - cl_entity_t *currententity; - model_t *currentmodel; - cl_entity_t *currentbeam; // same as above but for beams + cl_entity_t *currententity; + model_t *currentmodel; + cl_entity_t *currentbeam; // same as above but for beams - int viewport[4]; - //gl_frustum_t frustum; + int viewport[4]; + // gl_frustum_t frustum; - mleaf_t *viewleaf; - mleaf_t *oldviewleaf; - vec3_t pvsorigin; - vec3_t vieworg; // locked vieworigin - vec3_t viewangles; - vec3_t vforward; - vec3_t vright; - vec3_t vup; - vec3_t base_vup; - vec3_t base_vpn; - vec3_t base_vright; + mleaf_t *viewleaf; + mleaf_t *oldviewleaf; + vec3_t pvsorigin; + vec3_t vieworg; // locked vieworigin + vec3_t viewangles; + vec3_t vforward; + vec3_t vright; + vec3_t vup; + vec3_t base_vup; + vec3_t base_vpn; + vec3_t base_vright; - vec3_t cullorigin; - vec3_t cull_vforward; - vec3_t cull_vright; - vec3_t cull_vup; + vec3_t cullorigin; + vec3_t cull_vforward; + vec3_t cull_vright; + vec3_t cull_vup; - int cached_contents; // in water - int cached_waterlevel; // was in water - float farClip; + int cached_contents; // in water + int cached_waterlevel; // was in water + float farClip; - float skyMins[2][6]; - float skyMaxs[2][6]; + float skyMins[2][6]; + float skyMaxs[2][6]; - matrix4x4 objectMatrix; // currententity matrix - matrix4x4 worldviewMatrix; // modelview for world - matrix4x4 modelviewMatrix; // worldviewMatrix * objectMatrix + matrix4x4 objectMatrix; // currententity matrix + matrix4x4 worldviewMatrix; // modelview for world + matrix4x4 modelviewMatrix; // worldviewMatrix * objectMatrix - matrix4x4 projectionMatrix; - matrix4x4 worldviewProjectionMatrix; // worldviewMatrix * projectionMatrix - byte visbytes[(MAX_MAP_LEAFS+7)/8];// actual PVS for current frame + matrix4x4 projectionMatrix; + matrix4x4 worldviewProjectionMatrix; // worldviewMatrix * projectionMatrix + byte visbytes[( MAX_MAP_LEAFS + 7 ) / 8]; // actual PVS for current frame - float viewplanedist; + float viewplanedist; // q2 oldrefdef - vrect_t vrect; // subwindow in video for refresh - // FIXME: not need vrect next field here? - vrect_t aliasvrect; // scaled Alias version - int vrectright, vrectbottom; // right & bottom screen coords - int aliasvrectright, aliasvrectbottom; // scaled Alias versions - float vrectrightedge; // rightmost right edge we care about, - // for use in edge list - float fvrectx, fvrecty; // for floating-point compares - float fvrectx_adj, fvrecty_adj; // left and top edges, for clamping - int vrect_x_adj_shift20; // (vrect.x + 0.5 - epsilon) << 20 - int vrectright_adj_shift20; // (vrectright + 0.5 - epsilon) << 20 - float fvrectright_adj, fvrectbottom_adj; - // right and bottom edges, for clamping - float fvrectright; // rightmost edge, for Alias clamping - float fvrectbottom; // bottommost edge, for Alias clampin + vrect_t vrect; // subwindow in video for refresh + // FIXME: not need vrect next field here? + vrect_t aliasvrect; // scaled Alias version + int vrectright, vrectbottom; // right & bottom screen coords + int aliasvrectright, aliasvrectbottom; // scaled Alias versions + float vrectrightedge; // rightmost right edge we care about, + // for use in edge list + float fvrectx, fvrecty; // for floating-point compares + float fvrectx_adj, fvrecty_adj; // left and top edges, for clamping + int vrect_x_adj_shift20; // (vrect.x + 0.5 - epsilon) << 20 + int vrectright_adj_shift20; // (vrectright + 0.5 - epsilon) << 20 + float fvrectright_adj, fvrectbottom_adj; + // right and bottom edges, for clamping + float fvrectright; // rightmost edge, for Alias clamping + float fvrectbottom; // bottommost edge, for Alias clampin } ref_instance_t; typedef struct { - cl_entity_t *edge_entities[MAX_VISIBLE_PACKET]; // brush edge drawing - cl_entity_t *solid_entities[MAX_VISIBLE_PACKET]; // opaque moving or alpha brushes - cl_entity_t *trans_entities[MAX_VISIBLE_PACKET]; // translucent brushes - cl_entity_t *beam_entities[MAX_VISIBLE_PACKET]; - uint num_edge_entities; - uint num_solid_entities; - uint num_trans_entities; - uint num_beam_entities; + cl_entity_t *edge_entities[MAX_VISIBLE_PACKET]; // brush edge drawing + cl_entity_t *solid_entities[MAX_VISIBLE_PACKET]; // opaque moving or alpha brushes + cl_entity_t *trans_entities[MAX_VISIBLE_PACKET]; // translucent brushes + cl_entity_t *beam_entities[MAX_VISIBLE_PACKET]; + uint num_edge_entities; + uint num_solid_entities; + uint num_trans_entities; + uint num_beam_entities; } draw_list_t; typedef struct { - int defaultTexture; // use for bad textures - int particleTexture; - int whiteTexture; - int grayTexture; - int blackTexture; - int solidskyTexture; // quake1 solid-sky layer - int alphaskyTexture; // quake1 alpha-sky layer - int lightmapTextures[MAX_LIGHTMAPS]; - int dlightTexture; // custom dlight texture - int skyboxTextures[6]; // skybox sides - int cinTexture; // cinematic texture + int defaultTexture; // use for bad textures + int particleTexture; + int whiteTexture; + int grayTexture; + int blackTexture; + int solidskyTexture; // quake1 solid-sky layer + int alphaskyTexture; // quake1 alpha-sky layer + int lightmapTextures[MAX_LIGHTMAPS]; + int dlightTexture; // custom dlight texture + int skyboxTextures[6]; // skybox sides + int cinTexture; // cinematic texture // entity lists - draw_list_t draw_stack[MAX_DRAW_STACK]; - int draw_stack_pos; - draw_list_t *draw_list; + draw_list_t draw_stack[MAX_DRAW_STACK]; + int draw_stack_pos; + draw_list_t *draw_list; - msurface_t *draw_decals[MAX_DECAL_SURFS]; - int num_draw_decals; + msurface_t *draw_decals[MAX_DECAL_SURFS]; + int num_draw_decals; // OpenGL matrix states - qboolean modelviewIdentity; + qboolean modelviewIdentity; - int visframecount; // PVS frame - int dlightframecount; // dynamic light frame - int realframecount; // not including viewpasses - int framecount; + int visframecount; // PVS frame + int dlightframecount; // dynamic light frame + int realframecount; // not including viewpasses + int framecount; - qboolean ignore_lightgamma; - qboolean fCustomRendering; - qboolean fResetVis; - qboolean fFlipViewModel; + qboolean fCustomRendering; + qboolean fResetVis; + qboolean fFlipViewModel; // tree visualization stuff - int recursion_level; - int max_recursion; + int recursion_level; + int max_recursion; - byte visbytes[(MAX_MAP_LEAFS+7)/8]; // member custom PVS - int lightstylevalue[MAX_LIGHTSTYLES]; // value 0 - 65536 - int block_size; // lightmap blocksize + byte visbytes[( MAX_MAP_LEAFS + 7 ) / 8]; // member custom PVS + int lightstylevalue[MAX_LIGHTSTYLES]; // value 0 - 65536 + int block_size; // lightmap blocksize - double frametime; // special frametime for multipass rendering (will set to 0 on a nextview) - float blend; // global blend value + double frametime; // special frametime for multipass rendering (will set to 0 on a nextview) + float blend; // global blend value // cull info - vec3_t modelorg; // relative to viewpoint + vec3_t modelorg; // relative to viewpoint - qboolean fCustomSkybox; - int sample_size; - uint sample_bits; - qboolean map_unload; + int sample_size; + uint sample_bits; + qboolean map_unload; + + // get from engine + cl_entity_t *entities; + movevars_t *movevars; + color24 *palette; + cl_entity_t *viewent; + lightstyle_t *lightstyles; + dlight_t *dlights; + dlight_t *elights; + byte *texgammatable; + uint *lightgammatable; + uint *lineargammatable; + uint *screengammatable; + + uint max_entities; } gl_globals_t; typedef struct { - uint c_world_polys; - uint c_studio_polys; - uint c_sprite_polys; - uint c_alias_polys; - uint c_world_leafs; + uint c_world_polys; + uint c_studio_polys; + uint c_sprite_polys; + uint c_alias_polys; + uint c_world_leafs; - uint c_view_beams_count; - uint c_active_tents_count; - uint c_alias_models_drawn; - uint c_studio_models_drawn; - uint c_sprite_models_drawn; - uint c_particle_count; + uint c_view_beams_count; + uint c_active_tents_count; + uint c_alias_models_drawn; + uint c_studio_models_drawn; + uint c_sprite_models_drawn; + uint c_particle_count; - uint c_client_ents; // entities that moved to client - double t_world_node; - double t_world_draw; + uint c_client_ents; // entities that moved to client + double t_world_node; + double t_world_draw; } ref_speeds_t; -extern ref_speeds_t r_stats; -extern ref_instance_t RI; -extern gl_globals_t tr; +extern ref_speeds_t r_stats; +extern ref_instance_t RI; +extern gl_globals_t tr; -#define r_numEntities (tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities) -#define r_numStatics (r_stats.c_client_ents) +#define r_numEntities ( tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities ) +#define r_numStatics ( r_stats.c_client_ents ) typedef struct image_s { - char name[256]; // game path, including extension (can be store image programs) - word srcWidth; // keep unscaled sizes - word srcHeight; - word width; // upload width\height - word height; - word depth; // texture depth or count of layers for 2D_ARRAY - byte numMips; // mipmap count + char name[256]; // game path, including extension (can be store image programs) + word srcWidth; // keep unscaled sizes + word srcHeight; + word width; // upload width\height + word height; + word depth; // texture depth or count of layers for 2D_ARRAY + byte numMips; // mipmap count - texFlags_t flags; + texFlags_t flags; - rgba_t fogParams; // some water textures - // contain info about underwater fog - rgbdata_t *original; // keep original image + rgba_t fogParams; // some water textures + // contain info about underwater fog + rgbdata_t *original; // keep original image // debug info - size_t size; // upload size for debug targets + size_t size; // upload size for debug targets // detail textures stuff - float xscale; - float yscale; + float xscale; + float yscale; - imagetype_t type; - pixel_t *pixels[4]; // mip levels - pixel_t *alpha_pixels; // mip levels + imagetype_t type; + pixel_t *pixels[4]; // mip levels + pixel_t *alpha_pixels; // mip levels - - int servercount; - uint hashValue; - struct image_s *nextHash; + uint hashValue; + struct image_s *nextHash; } image_t; -#if 0 - -// -// gl_backend.c -// -void GL_BackendStartFrame( void ); -void GL_BackendEndFrame( void ); -void GL_CleanUpTextureUnits( int last ); -void GL_Bind( int tmu, unsigned int texnum ); -void GL_LoadTexMatrix( const matrix4x4 m ); -void GL_LoadTexMatrixExt( const float *glmatrix ); -void GL_LoadMatrix( const matrix4x4 source ); -void GL_TexGen( unsigned int coord, unsigned int mode ); -void GL_SelectTexture( int texture ); -void GL_CleanupAllTextureUnits( void ); -void GL_LoadIdentityTexMatrix( void ); -void GL_DisableAllTexGens( void ); -void GL_SetRenderMode( int mode ); -void GL_TextureTarget( uint target ); -void GL_Cull( unsigned int cull ); -void R_ShowTextures( void ); -void R_ShowTree( void ); -void SCR_TimeRefresh_f( void ); -#endif // // gl_beams.c // void CL_DrawBeams( int fTrans, BEAM *active_beams ); qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ); -#if 0 -// -// gl_cull.c -// -int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ); -qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); -qboolean R_CullSphere( const vec3_t centre, const float radius ); -//int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ); -#endif + // // gl_decals.c // void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ); float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ); -//void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ); +// void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ); void R_EntityRemoveDecals( model_t *mod ); -//void DrawDecalsBatch( void ); +// void DrawDecalsBatch( void ); void R_ClearDecals( void ); - -#if 0 - -// -// gl_drawhulls.c -// -void R_DrawWorldHull( void ); -void R_DrawModelHull( void ); -#endif +void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ); void GL_Bind( int tmu, unsigned int texnum ); // -// gl_draw.cM_PI +// gl_draw.c // void R_Set2DMode( qboolean enable ); -void R_DrawTileClear( int texnum, int x, int y, int w, int h ); -void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data );// +void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ); // // gl_image.c // void R_SetTextureParameters( void ); image_t *R_GetTexture( unsigned int texnum ); -#define GL_LoadTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, false ) +#define GL_LoadTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, false ) #define GL_UpdateTextureInternal( name, pic, flags ) GL_LoadTextureFromBuffer( name, pic, flags, true ) int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); int GL_LoadTextureArray( const char **names, int flags ); @@ -453,19 +414,18 @@ void R_InitDlightTexture( void ); void R_TextureList_f( void ); void R_InitImages( void ); void R_ShutdownImages( void ); +int R_TexMemory( void ); + #if 1 // // gl_rlight.c // -void CL_RunLightStyles( void ); +void CL_RunLightStyles( lightstyle_t *ls ); void R_PushDlights( void ); -void R_AnimateLight( void ); void R_GetLightSpot( vec3_t lightspot ); void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec ); -int R_CountSurfaceDlights( msurface_t *surf ); colorVec R_LightPoint( const vec3_t p0 ); -int R_CountDlights( void ); #endif // // gl_rmain.c @@ -478,6 +438,7 @@ void R_SetupRefParams( const struct ref_viewpass_s *rvp ); void R_TranslateForEntity( cl_entity_t *e ); void R_RotateForEntity( cl_entity_t *e ); void R_SetupGL( qboolean set_gl_state ); +qboolean R_OpaqueEntity( cl_entity_t *ent ); void R_AllowFog( qboolean allowed ); void R_SetupFrustum( void ); void R_FindViewLeaf( void ); @@ -488,47 +449,15 @@ void R_DrawFog( void ); // // gl_rmath.c // -void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ); -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ); void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_ConcatScale( matrix4x4 out, float x ); -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_CreateScale( matrix4x4 out, float x ); -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); -void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); -void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); +void Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar ); +void Matrix4x4_CreateOrtho( matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ); void Matrix4x4_CreateModelview( matrix4x4 out ); -#if 0 -// -// gl_rmisc.c -// -void R_ClearStaticEntities( void ); -// -// gl_rsurf.c -// -void R_MarkLeaves( void ); -void R_DrawWorld( void ); -void R_DrawWaterSurfaces( void ); -void R_DrawBrushModel( cl_entity_t *e ); -void GL_SubdivideSurface( msurface_t *fa ); -void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ); -void DrawGLPoly( glpoly_t *p, float xScale, float yScale ); -texture_t *R_TextureAnimation( msurface_t *s ); -void GL_SetupFogColorForSurfaces( void ); -void R_DrawAlphaTextureChains( void ); -void GL_RebuildLightmaps( void ); -void GL_InitRandomTable( void ); -void GL_BuildLightmaps( void ); -void GL_ResetFogColor( void ); -void R_GenerateVBO(); -void R_ClearVBO(); -void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf ); -#endif // // gl_rpart.c // @@ -551,58 +480,41 @@ void R_DrawSpriteModel( cl_entity_t *e ); void R_StudioInit( void ); void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); int R_GetEntityRenderMode( cl_entity_t *ent ); void R_DrawStudioModel( cl_entity_t *e ); player_info_t *pfnPlayerInfo( int index ); void R_GatherPlayerLight( void ); -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void R_StudioResetPlayerModels( void ); void CL_InitStudioAPI( void ); void Mod_StudioLoadTextures( model_t *mod, void *data ); void Mod_StudioUnloadTextures( void *data ); -#if 0 -// -// gl_alias.c -// -void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ); -void R_DrawAliasModel( cl_entity_t *e ); -void R_AliasInit( void ); // -// gl_warp.c +// r_polyse.c // +// !!! if this is changed, it must be changed in asm_draw.h too !!! +typedef struct +{ + void *pdest; + short *pz; + int count; + pixel_t *ptex; + int sfrac, tfrac, light, zi; +} spanpackage_t; -void R_InitSkyClouds( mip_t *mt, struct texture_s *tx, qboolean custom_palette ); -void R_AddSkyBoxSurface( msurface_t *fa ); -void R_ClearSkyBox( void ); -void R_DrawSkyBox( void ); -void R_DrawClouds( void ); -void EmitWaterPolys( msurface_t *warp, qboolean reverse ); -#endif +extern void (*d_pdrawspans)( spanpackage_t * ); +void R_PolysetFillSpans8( spanpackage_t * ); +void R_PolysetDrawSpans8_33( spanpackage_t * ); +void R_PolysetDrawSpansConstant8_33( spanpackage_t *pspanpackage ); +void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage ); +void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage ); +void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage ); +void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage ); -void GAME_EXPORT R_InitSkyClouds( struct mip_s *mt, struct texture_s *tx, qboolean custom_palette ); -// -// gl_vgui.c -// -void VGUI_DrawInit( void ); -void VGUI_DrawShutdown( void ); -void VGUI_SetupDrawingText( int *pColor ); -void VGUI_SetupDrawingRect( int *pColor ); -void VGUI_SetupDrawingImage( int *pColor ); -void VGUI_BindTexture( int id ); -void VGUI_EnableTexture( qboolean enable ); -void VGUI_CreateTexture( int id, int width, int height ); -void VGUI_UploadTexture( int id, const char *buffer, int width, int height ); -void VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ); -void VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ); -void VGUI_GetTextureSizes( int *width, int *height ); -int VGUI_GenerateTexture( void ); - -//#include "vid_common.h" +// #include "vid_common.h" // // renderer exports @@ -617,6 +529,7 @@ int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags ); void GL_FreeImage( const char *name ); qboolean VID_ScreenShot( const char *filename, int shot_type ); qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ); +void R_GammaChanged( qboolean do_reset_gamma ); void R_BeginFrame( qboolean clearScene ); void R_RenderFrame( const struct ref_viewpass_s *vp ); void R_EndFrame( void ); @@ -626,12 +539,10 @@ void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int cu void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ); void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ); qboolean R_SpeedsMessage( char *out, size_t size ); -void R_SetupSky( const char *skyboxname ); qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); int R_WorldToScreen( const vec3_t point, vec3_t screen ); void R_ScreenToWorld( const vec3_t screen, vec3_t point ); qboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType ); -void Mod_LoadMapSprite( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded ); void Mod_SpriteUnloadTextures( void *data ); void Mod_UnloadAliasModel( struct model_s *mod ); void Mod_AliasUnloadTextures( void *data ); @@ -640,8 +551,6 @@ void R_RunViewmodelEvents( void ); void R_DrawViewModel( void ); int R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame ); void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ); -void R_RemoveEfrags( struct cl_entity_s *ent ); -void R_AddEfrags( struct cl_entity_s *ent ); void R_DecalRemoveAll( int texture ); int R_CreateDecalList( decallist_t *pList ); void R_ClearAllDecals( void ); @@ -649,18 +558,7 @@ byte *Mod_GetCurrentVis( void ); void Mod_SetOrthoBounds( const float *mins, const float *maxs ); void R_NewMap( void ); void CL_AddCustomBeam( cl_entity_t *pEnvBeam ); -#if 0 -// -// gl_opengl.c -// -#define GL_CheckForErrors() GL_CheckForErrors_( __FILE__, __LINE__ ) -void GL_CheckForErrors_( const char *filename, const int fileline ); -const char *GL_ErrorString( int err ); -qboolean GL_Support( int r_ext ); -int GL_MaxTextureUnits( void ); -void GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char *cvarname, int r_ext ); -void GL_SetExtension( int r_ext, int enable ); -#endif + // // gl_triapi.c // @@ -682,21 +580,72 @@ void TriFogParams( float flDensity, int iFogSkybox ); void TriCullFace( TRICULLSTYLE mode ); void TriBrightness( float brightness ); -#define ENGINE_GET_PARM_ (*gEngfuncs.EngineGetParm) -#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( (parm), 0 ) +#define ENGINE_GET_PARM_ ( *gEngfuncs.EngineGetParm ) +#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_(( parm ), 0 ) -extern ref_api_t gEngfuncs; +extern ref_api_t gEngfuncs; extern ref_globals_t *gpGlobals; -extern cvar_t *r_dynamic; +extern ref_client_t *gp_cl; +extern ref_host_t *gp_host; DECLARE_ENGINE_SHARED_CVAR_LIST() +// +// helper funcs +// +static inline cl_entity_t *CL_GetEntityByIndex( int index ) +{ + if( unlikely( index < 0 || index >= tr.max_entities || !tr.entities )) + return NULL; + + return &tr.entities[index]; +} + +static inline model_t *CL_ModelHandle( int index ) +{ + if( unlikely( index < 0 || index >= gp_cl->nummodels )) + return NULL; + + return gp_cl->models[index]; +} + +static inline byte TextureToGamma( byte b ) +{ + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.texgammatable[b] : b; +} + +static inline uint LightToTexGamma( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lightgammatable[b] : b; +} + +static inline uint ScreenGammaTable( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.screengammatable[b] : b; +} + +static inline uint LinearGammaTable( uint b ) +{ + if( unlikely( b >= 1024 )) + return 0; + + return !FBitSet( gp_host->features, ENGINE_LINEAR_GAMMA_SPACE ) ? tr.lineargammatable[b] : b; +} + +#define WORLDMODEL ( gp_cl->models[1] ) + // todo: gl_cull.c -#define R_CullModel(...) 0 +#define R_CullModel( ... ) 0 // softrender defs -#define CACHE_SIZE 32 +#define CACHE_SIZE 32 /* ==================================================== @@ -704,86 +653,86 @@ DECLARE_ENGINE_SHARED_CVAR_LIST() ==================================================== */ -#define VID_CBITS 6 -#define VID_GRADES (1 << VID_CBITS) +#define VID_CBITS 6 +#define VID_GRADES ( 1 << VID_CBITS ) // r_shared.h: general refresh-related stuff shared between the refresh and the // driver -#define MAXVERTS 64 // max points in a surface polygon -#define MAXWORKINGVERTS (MAXVERTS+4) // max points in an intermediate - // polygon (while processing) +#define MAXVERTS 64 // max points in a surface polygon +#define MAXWORKINGVERTS ( MAXVERTS + 4 ) // max points in an intermediate +// polygon (while processing) // !!! if this is changed, it must be changed in d_ifacea.h too !!! -#define MAXHEIGHT 1200 -#define MAXWIDTH 1920 +#define MAXHEIGHT 1200 +#define MAXWIDTH 1920 -#define INFINITE_DISTANCE 0x10000 // distance that's always guaranteed to - // be farther away than anything in - // the scene +#define INFINITE_DISTANCE 0x10000 // distance that's always guaranteed to +// be farther away than anything in +// the scene // d_iface.h: interface header file for rasterization driver modules -#define WARP_WIDTH 320 -#define WARP_HEIGHT 240 +#define WARP_WIDTH 320 +#define WARP_HEIGHT 240 -#define MAX_LBM_HEIGHT 480 +#define MAX_LBM_HEIGHT 480 #define PARTICLE_Z_CLIP 8.0 // !!! must be kept the same as in quakeasm.h !!! -#define TRANSPARENT_COLOR 0x0349 //0xFF +#define TRANSPARENT_COLOR 0x0349 // 0xFF // !!! if this is changed, it must be changed in d_ifacea.h too !!! -#define TURB_TEX_SIZE 64 // base turbulent texture size +#define TURB_TEX_SIZE 64 // base turbulent texture size // !!! if this is changed, it must be changed in d_ifacea.h too !!! -#define CYCLE 128 // turbulent cycle size +#define CYCLE 128 // turbulent cycle size -#define SCANBUFFERPAD 0x1000 +#define SCANBUFFERPAD 0x1000 -#define DS_SPAN_LIST_END -128 +#define DS_SPAN_LIST_END -128 -#define NUMSTACKEDGES 4000 -#define MINEDGES NUMSTACKEDGES -#define NUMSTACKSURFACES 2000 -#define MINSURFACES NUMSTACKSURFACES -#define MAXSPANS 6000 +#define NUMSTACKEDGES 4000 +#define MINEDGES NUMSTACKEDGES +#define NUMSTACKSURFACES 2000 +#define MINSURFACES NUMSTACKSURFACES +#define MAXSPANS 6000 // flags in finalvert_t.flags -#define ALIAS_LEFT_CLIP 0x0001 -#define ALIAS_TOP_CLIP 0x0002 -#define ALIAS_RIGHT_CLIP 0x0004 -#define ALIAS_BOTTOM_CLIP 0x0008 -#define ALIAS_Z_CLIP 0x0010 -#define ALIAS_XY_CLIP_MASK 0x000F +#define ALIAS_LEFT_CLIP 0x0001 +#define ALIAS_TOP_CLIP 0x0002 +#define ALIAS_RIGHT_CLIP 0x0004 +#define ALIAS_BOTTOM_CLIP 0x0008 +#define ALIAS_Z_CLIP 0x0010 +#define ALIAS_XY_CLIP_MASK 0x000F -#define SURFCACHE_SIZE_AT_320X240 1024*768 +#define SURFCACHE_SIZE_AT_320X240 1024 * 768 -#define BMODEL_FULLY_CLIPPED 0x10 // value returned by R_BmodelCheckBBox () - // if bbox is trivially rejected +#define BMODEL_FULLY_CLIPPED 0x10 // value returned by R_BmodelCheckBBox () +// if bbox is trivially rejected -#define XCENTERING (1.0f / 2.0f) -#define YCENTERING (1.0f / 2.0f) +#define XCENTERING ( 1.0f / 2.0f ) +#define YCENTERING ( 1.0f / 2.0f ) -#define CLIP_EPSILON 0.001f +#define CLIP_EPSILON 0.001f // !!! if this is changed, it must be changed in asm_draw.h too !!! -#define NEAR_CLIP 0.01f +#define NEAR_CLIP 0.01f -//#define MAXALIASVERTS 2000 // TODO: tune this -#define ALIAS_Z_CLIP_PLANE 4 +// #define MAXALIASVERTS 2000 // TODO: tune this +#define ALIAS_Z_CLIP_PLANE 4 // turbulence stuff -#define AMP 8*0x10000 -#define AMP2 3 -#define SPEED 20 +#define AMP 8 * 0x10000 +#define AMP2 3 +#define SPEED 20 /* @@ -794,9 +743,9 @@ TYPES typedef struct { - float u, v; - float s, t; - float zi; + float u, v; + float s, t; + float zi; } emitpoint_t; /* @@ -807,19 +756,20 @@ typedef struct #if SMALL_FINALVERT -typedef struct finalvert_s { - short u, v, s, t; - int l; - int zi; - int flags; - float xyz[3]; // eye space +typedef struct finalvert_s +{ + short u, v, s, t; + int l; + int zi; + int flags; + float xyz[3]; // eye space } finalvert_t; -#define FINALVERT_V0 0 -#define FINALVERT_V1 2 -#define FINALVERT_V2 4 -#define FINALVERT_V3 6 -#define FINALVERT_V4 8 +#define FINALVERT_V0 0 +#define FINALVERT_V1 2 +#define FINALVERT_V2 4 +#define FINALVERT_V3 6 +#define FINALVERT_V4 8 #define FINALVERT_V5 12 #define FINALVERT_FLAGS 16 #define FINALVERT_X 20 @@ -829,17 +779,18 @@ typedef struct finalvert_s { #else -typedef struct finalvert_s { - int u, v, s, t; - int l; - int zi; - int flags; - float xyz[3]; // eye space +typedef struct finalvert_s +{ + int u, v, s, t; + int l; + int zi; + int flags; + float xyz[3]; // eye space } finalvert_t; -#define FINALVERT_V0 0 -#define FINALVERT_V1 4 -#define FINALVERT_V2 8 +#define FINALVERT_V0 0 +#define FINALVERT_V1 4 +#define FINALVERT_V2 8 #define FINALVERT_V3 12 #define FINALVERT_V4 16 #define FINALVERT_V5 20 @@ -854,20 +805,20 @@ typedef struct finalvert_s { typedef struct { - short s; - short t; + short s; + short t; } dstvert_t; typedef struct { - short index_xyz[3]; - short index_st[3]; + short index_xyz[3]; + short index_st[3]; } dtriangle_t; typedef struct { - byte v[3]; // scaled byte to fit in frame mins/maxs - byte lightnormalindex; + byte v[3]; // scaled byte to fit in frame mins/maxs + byte lightnormalindex; } dtrivertx_t; #define DTRIVERTX_V0 0 @@ -878,84 +829,73 @@ typedef struct typedef struct { - void *pskin; - int pskindesc; - int skinwidth; - int skinheight; - dtriangle_t *ptriangles; - finalvert_t *pfinalverts; - int numtriangles; - int drawtype; - int seamfixupX16; - qboolean do_vis_thresh; - int vis_thresh; + void *pskin; + int pskindesc; + int skinwidth; + int skinheight; + dtriangle_t *ptriangles; + finalvert_t *pfinalverts; + int numtriangles; + int drawtype; + int seamfixupX16; + qboolean do_vis_thresh; + int vis_thresh; } affinetridesc_t; typedef struct { - pixel_t *surfdat; // destination for generated surface - int rowbytes; // destination logical width in bytes - msurface_t *surf; // description for surface to generate - fixed8_t lightadj[MAXLIGHTMAPS]; - // adjust for lightmap levels for dynamic lighting - image_t *image; - int surfmip; // mipmapped ratio of surface texels / world pixels - int surfwidth; // in mipmapped texels - int surfheight; // in mipmapped texels + pixel_t *surfdat; // destination for generated surface + int rowbytes; // destination logical width in bytes + msurface_t *surf; // description for surface to generate + fixed8_t lightadj[MAXLIGHTMAPS]; + // adjust for lightmap levels for dynamic lighting + image_t *image; + int surfmip; // mipmapped ratio of surface texels / world pixels + int surfwidth; // in mipmapped texels + int surfheight; // in mipmapped texels } drawsurf_t; - -#if 0 -typedef struct { - int ambientlight; - int shadelight; - float *plightvec; -} alight_t; - -#endif - // clipped bmodel edges - typedef struct bedge_s { - mvertex_t *v[2]; - struct bedge_s *pnext; + mvertex_t *v[2]; + struct bedge_s *pnext; } bedge_t; // !!! if this is changed, it must be changed in asm_draw.h too !!! typedef struct clipplane_s { - vec3_t normal; - float dist; - struct clipplane_s *next; - byte leftedge; - byte rightedge; - byte reserved[2]; + vec3_t normal; + float dist; + struct clipplane_s *next; + byte leftedge; + byte rightedge; + byte reserved[2]; } clipplane_t; typedef struct surfcache_s { - struct surfcache_s *next; - struct surfcache_s **owner; // NULL is an empty chunk of memory - int lightadj[MAXLIGHTMAPS]; // checked for strobe flush - int dlight; - int size; // including header - unsigned width; - unsigned height; // DEBUG only needed for debug - float mipscale; - image_t *image; - byte data[4]; // width*height elements + struct surfcache_s *next; + struct surfcache_s **owner; // NULL is an empty chunk of memory + int lightadj[MAXLIGHTMAPS]; // checked for strobe flush + int dlight; + int size; // including header + unsigned width; + unsigned height; // DEBUG only needed for debug + float mipscale; + image_t *image; + byte data[4]; // width*height elements } surfcache_t; // !!! if this is changed, it must be changed in asm_draw.h too !!! typedef struct espan_s { - int u, v, count; - struct espan_s *pnext; + int u, v, count; + struct espan_s *pnext; } espan_t; @@ -963,35 +903,35 @@ typedef struct espan_s // insubmodel is only 1, flags is fewer than 32, spanstate could be a byte typedef struct surf_s { - struct surf_s *next; // active surface stack in r_edge.c - struct surf_s *prev; // used in r_edge.c for active surf stack - struct espan_s *spans; // pointer to linked list of spans to draw - int key; // sorting key (BSP order) - int last_u; // set during tracing - int spanstate; // 0 = not in span - // 1 = in span - // -1 = in inverted span (end before - // start) - int flags; // currentface flags - msurface_t *msurf; - cl_entity_t *entity; - float nearzi; // nearest 1/z on surface, for mipmapping - qboolean insubmodel; - float d_ziorigin, d_zistepu, d_zistepv; + struct surf_s *next; // active surface stack in r_edge.c + struct surf_s *prev; // used in r_edge.c for active surf stack + struct espan_s *spans; // pointer to linked list of spans to draw + int key; // sorting key (BSP order) + int last_u; // set during tracing + int spanstate; // 0 = not in span + // 1 = in span + // -1 = in inverted span (end before + // start) + int flags; // currentface flags + msurface_t *msurf; + cl_entity_t *entity; + float nearzi; // nearest 1/z on surface, for mipmapping + qboolean insubmodel; + float d_ziorigin, d_zistepu, d_zistepv; - int pad[2]; // to 64 bytes + int pad[2]; // to 64 bytes } surf_t; // !!! if this is changed, it must be changed in asm_draw.h too !!! typedef struct edge_s { - fixed16_t u; - fixed16_t u_step; - struct edge_s *prev, *next; - unsigned short surfs[2]; - struct edge_s *nextremove; - float nearzi; - medge_t *owner; + fixed16_t u; + fixed16_t u_step; + struct edge_s *prev, *next; + unsigned short surfs[2]; + struct edge_s *nextremove; + float nearzi; + medge16_t *owner; } edge_t; @@ -1001,76 +941,78 @@ VARS ==================================================== */ - // started -extern float r_aliasuvscale; // scale-up factor for screen u and v - // on Alias vertices passed to driver +// started +extern float r_aliasuvscale; // scale-up factor for screen u and v +// on Alias vertices passed to driver -extern affinetridesc_t r_affinetridesc; +extern affinetridesc_t r_affinetridesc; -void D_DrawSurfaces (void); +void D_DrawSurfaces( void ); void R_DrawParticle( void ); -void D_ViewChanged (void); -void D_WarpScreen (void); -void R_PolysetUpdateTables (void); +void D_ViewChanged( void ); -//=======================================================================// +// =======================================================================// // callbacks to Quake -extern drawsurf_t r_drawsurf; +extern drawsurf_t r_drawsurf; -void R_DrawSurface (void); +void R_DrawSurface( void ); -//extern int c_surf; +// extern int c_surf; -extern byte r_warpbuffer[WARP_WIDTH * WARP_HEIGHT]; +extern byte r_warpbuffer[WARP_WIDTH * WARP_HEIGHT]; -extern float scale_for_mip; +extern float scale_for_mip; -extern qboolean d_roverwrapped; -extern surfcache_t *sc_rover; -extern surfcache_t *d_initial_rover; +extern qboolean d_roverwrapped; +extern surfcache_t *sc_rover; +extern surfcache_t *d_initial_rover; -extern float d_sdivzstepu, d_tdivzstepu, d_zistepu; -extern float d_sdivzstepv, d_tdivzstepv, d_zistepv; -extern float d_sdivzorigin, d_tdivzorigin, d_ziorigin; +extern float d_sdivzstepu, d_tdivzstepu, d_zistepu; +extern float d_sdivzstepv, d_tdivzstepv, d_zistepv; +extern float d_sdivzorigin, d_tdivzorigin, d_ziorigin; -extern fixed16_t sadjust, tadjust; -extern fixed16_t bbextents, bbextentt; +extern fixed16_t sadjust, tadjust; +extern fixed16_t bbextents, bbextentt; -void D_DrawSpans16 (espan_t *pspans); -void D_DrawZSpans (espan_t *pspans); -void Turbulent8 (espan_t *pspan); -void NonTurbulent8 (espan_t *pspan); //PGM +void D_DrawSpans16( espan_t *pspans ); +void D_DrawZSpans( espan_t *pspans ); +void Turbulent8( espan_t *pspan ); +void NonTurbulent8( espan_t *pspan ); // PGM +void D_BlendSpans16( espan_t *pspan, int alpha ); +void D_AlphaSpans16( espan_t *pspan ); +void D_AddSpans16( espan_t *pspan ); +void TurbulentZ8( espan_t *pspan, int alpha ); -surfcache_t *D_CacheSurface (msurface_t *surface, int miplevel); +surfcache_t *D_CacheSurface( msurface_t *surface, int miplevel ); -extern pixel_t *d_viewbuffer; -extern short *d_pzbuffer; +extern pixel_t *d_viewbuffer; +extern short *d_pzbuffer; extern unsigned int d_zrowbytes, d_zwidth; -extern short *zspantable[MAXHEIGHT]; -extern int d_scantable[MAXHEIGHT]; +extern short *zspantable[MAXHEIGHT]; +extern int d_scantable[MAXHEIGHT]; -extern int d_minmip; -extern float d_scalemip[3]; +extern int d_minmip; +extern float d_scalemip[3]; -//=================================================================== +// =================================================================== -extern int cachewidth; -extern pixel_t *cacheblock; -extern int r_screenwidth; +extern int cachewidth; +extern pixel_t *cacheblock; +extern int r_screenwidth; -extern int sintable[1280]; -extern int intsintable[1280]; -extern int blanktable[1280]; // PGM +extern int sintable[1280]; +extern int intsintable[1280]; +extern int blanktable[1280]; // PGM -extern surf_t *surfaces, *surface_p, *surf_max; +extern surf_t *surfaces, *surface_p, *surf_max; // surfaces are generated in back to front order by the bsp, so if a surf // pointer is greater than another one, it should be drawn in front @@ -1078,81 +1020,67 @@ extern surf_t *surfaces, *surface_p, *surf_max; // surfaces[0] is a dummy, because index 0 is used to indicate no surface // attached to an edge_t -//=================================================================== +// =================================================================== -//extern vec3_t sxformaxis[4]; // s axis transformed into viewspace -//extern vec3_t txformaxis[4]; // t axis transformed into viewspac +// extern vec3_t sxformaxis[4]; // s axis transformed into viewspace +// extern vec3_t txformaxis[4]; // t axis transformed into viewspac -extern float xcenter, ycenter; -extern float xscale, yscale; -extern float xscaleinv, yscaleinv; -//extern float xscaleshrink, yscaleshrink; +extern float xcenter, ycenter; +extern float xscale, yscale; +extern float xscaleinv, yscaleinv; +// extern float xscaleshrink, yscaleshrink; -extern edge_t *auxedges; -extern int r_numallocatededges; -extern edge_t *r_edges, *edge_p, *edge_max; +extern edge_t *auxedges; +extern int r_numallocatededges; +extern edge_t *r_edges, *edge_p, *edge_max; -extern edge_t *newedges[MAXHEIGHT]; -extern edge_t *removeedges[MAXHEIGHT]; +extern edge_t *newedges[MAXHEIGHT]; +extern edge_t *removeedges[MAXHEIGHT]; -extern int r_viewcluster, r_oldviewcluster; +extern int r_viewcluster, r_oldviewcluster; -extern int r_clipflags; -//extern qboolean r_fov_greater_than_90; +extern int r_clipflags; +// extern qboolean r_fov_greater_than_90; -extern cvar_t *sw_aliasstats; -extern cvar_t *sw_clearcolor; -extern cvar_t *sw_drawflat; -extern cvar_t *sw_draworder; -extern cvar_t *sw_maxedges; -extern cvar_t *sw_maxsurfs; -extern cvar_t *sw_mipcap; -extern cvar_t *sw_mipscale; -extern cvar_t *sw_reportsurfout; -extern cvar_t *sw_reportedgeout; -extern cvar_t *sw_stipplealpha; -extern cvar_t *sw_surfcacheoverride; -extern cvar_t *sw_waterwarp; -extern cvar_t *sw_texfilt; -extern cvar_t *r_decals; -extern cvar_t *r_traceglow; -extern cvar_t *sw_notransbrushes; -extern cvar_t *sw_noalphabrushes; +extern convar_t sw_clearcolor; +extern convar_t sw_drawflat; +extern convar_t sw_draworder; +extern convar_t sw_maxedges; +extern convar_t sw_mipcap; +extern convar_t sw_mipscale; +extern convar_t sw_surfcacheoverride; +extern convar_t sw_texfilt; +extern convar_t r_traceglow; +extern convar_t sw_noalphabrushes; +extern convar_t r_studio_sort_textures; -extern cvar_t *tracerred; -extern cvar_t *tracergreen; -extern cvar_t *tracerblue; -extern cvar_t *traceralpha; - - -extern struct qfrustum_s { - mplane_t screenedge[4]; - clipplane_t view_clipplanes[4]; - int frustum_indexes[4*6]; - int *pfrustum_indexes[4]; +extern struct qfrustum_s +{ + mplane_t screenedge[4]; + clipplane_t view_clipplanes[4]; + int frustum_indexes[4 * 6]; + int *pfrustum_indexes[4]; } qfrustum; -extern cvar_t *r_fullbright; +#define CACHESPOT( surf ) ((surfcache_t **)surf->info->reserved ) +extern int r_currentkey; +extern int r_currentbkey; +extern qboolean insubmodel; -#define CACHESPOT(surf) ((surfcache_t**)surf->info->reserved) -extern int r_currentkey; -extern int r_currentbkey; -extern qboolean insubmodel; - -extern vec3_t r_entorigin; +extern vec3_t r_entorigin; #if XASH_LOW_MEMORY extern unsigned short r_leafkeys[MAX_MAP_LEAFS]; #else -extern int r_leafkeys[MAX_MAP_LEAFS]; +extern int r_leafkeys[MAX_MAP_LEAFS]; #endif -#define LEAF_KEY(pleaf) r_leafkeys[(pleaf - WORLDMODEL->leafs)] +#define LEAF_KEY( pleaf ) r_leafkeys[( pleaf - WORLDMODEL->leafs )] -extern mvertex_t *r_pcurrentvertbase; -//extern int r_maxvalidedgeoffset; +extern mvertex_t *r_pcurrentvertbase; +// extern int r_maxvalidedgeoffset; typedef struct { @@ -1162,82 +1090,84 @@ typedef struct extern aliastriangleparms_t aliastriangleparms; -extern int r_aliasblendcolor; +extern int r_aliasblendcolor; -extern float aliasxscale, aliasyscale, aliasxcenter, aliasycenter; -extern float s_ziscale; +extern float aliasxscale, aliasyscale, aliasxcenter, aliasycenter; +extern float s_ziscale; void R_DrawTriangle( void ); -//void R_DrawTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t *index2); -void R_AliasClipTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t *index2); +// void R_DrawTriangle (finalvert_t *index0, finalvert_t *index1, finalvert_t *index2); +void R_AliasClipTriangle( finalvert_t *index0, finalvert_t *index1, finalvert_t *index2 ); // // r_bsp.c // -void R_RotateBmodel (void); -void R_DrawSolidClippedSubmodelPolygons (model_t *pmodel, mnode_t *topnode); -void R_DrawSubmodelPolygons (model_t *pmodel, int clipflags, mnode_t *topnode); -void R_DrawBrushModel(cl_entity_t *pent); +void R_RotateBmodel( void ); +void R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode ); +void R_DrawSubmodelPolygons( model_t *pmodel, int clipflags, mnode_t *topnode ); +void R_DrawBrushModel( cl_entity_t *pent ); // // r_blitscreen.c // -void R_InitCaches (void); +void R_InitCaches( void ); void R_BlitScreen( void ); -void R_InitBlit( qboolean gl ); +qboolean R_InitBlit( qboolean gl ); qboolean R_SetDisplayTransform( ref_screen_rotation_t rotate, int offset_x, int offset_y, float scale_x, float scale_y ); // // r_edge.c // -void R_SurfacePatch (void); -void R_BeginEdgeFrame (void); -void R_RenderWorld (void); -void R_ScanEdges (void); +static inline void R_SurfacePatch( void ) { +} +void R_BeginEdgeFrame( void ); +void R_RenderWorld( void ); +void R_ScanEdges( void ); // // r_surf.c // +void GL_InitRandomTable( void ); void D_FlushCaches( void ); // // r_draw.c // -void Draw_Fill (int x, int y, int w, int h); +void Draw_Fill( int x, int y, int w, int h ); // // r_misc.c // -void R_SetupFrameQ (void); -void R_TransformFrustum (void); -void TransformVector (vec3_t in, vec3_t out); +void R_SetupFrameQ( void ); +void R_TransformFrustum( void ); +void TransformVector( vec3_t in, vec3_t out ); // // r_rast.c // -void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf); -void R_RenderFace (msurface_t *fa, int clipflags); +void R_RenderBmodelFace( bedge_t *pedges, msurface_t *psurf ); +void R_RenderFace( msurface_t *fa, int clipflags ); // // r_main.c // -void R_RenderTriangle( finalvert_t *fv1 , finalvert_t *fv2, finalvert_t *fv3 ); +void R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 ); void R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, int s, int t ); -void RotatedBBox (vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs); -int R_BmodelCheckBBox (float *minmaxs); +void RotatedBBox( vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs ); +int R_BmodelCheckBBox( float *minmaxs ); int CL_FxBlend( cl_entity_t *e ); -void R_SetUpWorldTransform (void); +void R_SetUpWorldTransform( void ); -#define BLEND_ALPHA_LOW(alpha, src, screen) (vid.alphamap[((alpha) << 18) | (((src) & 0xff00) << 2) | ((screen) >> 6)] | ((screen) & 0x3f)) -#define BLEND_ALPHA(alpha, src, dst) (alpha) > 3?BLEND_ALPHA_LOW(7 - 1 - (alpha), (dst), (src)) : BLEND_ALPHA_LOW((alpha)-1, (src), (dst)) -#define BLEND_ADD(src, screen) vid.addmap[((src)& 0xff00)|((screen)>>8)] << 8 | ((screen) & 0xff) | (((src) & 0xff) >> 0); -#define BLEND_COLOR(src, color) vid.modmap[((src) & 0xff00)|((color)>>8)] << 8 | ((src) & (color) & 0xff) | (((src) & 0xff) >> 3); +#define BLEND_ALPHA_LOW( alpha, src, screen ) ( vid.alphamap[(( alpha ) << 18 ) | ((( src ) & 0xff00 ) << 2 ) | (( screen ) >> 6 )] | (( screen ) & 0x3f )) +#define BLEND_ALPHA( alpha, src, dst ) ( alpha ) > 3 ? BLEND_ALPHA_LOW( 7 - 1 - ( alpha ), ( dst ), ( src )) : BLEND_ALPHA_LOW(( alpha ) - 1, ( src ), ( dst )) +#define BLEND_ADD( src, screen ) vid.addmap[(( src ) & 0xff00 ) | (( screen ) >> 8 )] << 8 | (( screen ) & 0xff ) | ((( src ) & 0xff ) >> 0 ); +#define BLEND_COLOR( src, color ) vid.modmap[(( src ) & 0xff00 ) | (( color ) >> 8 )] << 8 | (( src ) & ( color ) & 0xff ) | ((( src ) & 0xff ) >> 3 ); -#define LM_SAMPLE_SIZE_AUTO(surf) (tr.sample_size == -1?gEngfuncs.Mod_SampleSizeForFace( surf ): tr.sample_size) +#define LM_SAMPLE_SIZE_AUTO( surf ) ( tr.sample_size == -1 ? gEngfuncs.Mod_SampleSizeForFace( surf ) : tr.sample_size ) @@ -1246,21 +1176,16 @@ void R_SetUpWorldTransform (void); // #include "crtlib.h" #include "crclib.h" -#if 1 -#define Mem_Malloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) -#define Mem_Calloc( pool, size ) gEngfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +void _Mem_Free( void *data, const char *filename, int fileline ); +void *_Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +ALLOC_CHECK( 2 ) MALLOC_LIKE( _Mem_Free, 1 ) WARN_UNUSED_RESULT; + +#define Mem_Malloc( pool, size ) _Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) _Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) #define Mem_Realloc( pool, ptr, size ) gEngfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) -#define Mem_Free( mem ) gEngfuncs._Mem_Free( mem, __FILE__, __LINE__ ) -#define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) -#define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) -#define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ ) -#else -#define Mem_Malloc( pool, size ) malloc(size) -#define Mem_Calloc( pool, size ) calloc(1,size) -#define Mem_Realloc( pool, ptr, size ) realloc(ptr, size) -#define Mem_Free( mem ) free(mem) -#define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) -#define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) -#define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ ) -#endif +#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) gEngfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) gEngfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) +#define Mem_EmptyPool( pool ) gEngfuncs._Mem_EmptyPool( pool, __FILE__, __LINE__ ) + #endif // GL_LOCAL_H diff --git a/ref/soft/r_main.c b/ref/soft/r_main.c index 44d2a383..34592885 100644 --- a/ref/soft/r_main.c +++ b/ref/soft/r_main.c @@ -16,14 +16,14 @@ GNU General Public License for more details. #include "r_local.h" #include "xash3d_mathlib.h" #include "library.h" -//#include "beamdef.h" -//#include "particledef.h" +// #include "beamdef.h" +// #include "particledef.h" #include "entity_types.h" #include "mod_local.h" int r_cnumsurfs; -#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) +#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) -ref_instance_t RI; +ref_instance_t RI; // quake defines. will be refactored @@ -34,13 +34,13 @@ ref_instance_t RI; // // screen size info // -float xcenter, ycenter; -float xscale, yscale; -float xscaleinv, yscaleinv; -//float xscaleshrink, yscaleshrink; -float aliasxscale, aliasyscale, aliasxcenter, aliasycenter; +float xcenter, ycenter; +float xscale, yscale; +float xscaleinv, yscaleinv; +// float xscaleshrink, yscaleshrink; +float aliasxscale, aliasyscale, aliasxcenter, aliasycenter; -int r_screenwidth; +int r_screenwidth; @@ -49,102 +49,69 @@ int r_screenwidth; // refresh flags // -//int d_spanpixcount; -//int r_polycount; -//int r_drawnpolycount; -//int r_wholepolycount; +// int d_spanpixcount; +// int r_polycount; +// int r_drawnpolycount; +// int r_wholepolycount; -int r_viewcluster, r_oldviewcluster; +int r_viewcluster, r_oldviewcluster; -cvar_t *sw_aliasstats; -cvar_t *sw_allow_modex; -cvar_t *sw_clearcolor; -cvar_t *sw_drawflat; -cvar_t *sw_draworder; -cvar_t *sw_maxedges; -cvar_t *sw_maxsurfs; -cvar_t *sw_reportedgeout; -cvar_t *sw_reportsurfout; -cvar_t *sw_stipplealpha; -cvar_t *sw_surfcacheoverride; -cvar_t *sw_waterwarp; -cvar_t *sw_texfilt; -cvar_t *sw_notransbrushes; -cvar_t *sw_noalphabrushes; +CVAR_DEFINE_AUTO( sw_clearcolor, "48999", 0, "screen clear color" ); +CVAR_DEFINE_AUTO( sw_drawflat, "0", FCVAR_CHEAT, "" ); +CVAR_DEFINE_AUTO( sw_draworder, "0", FCVAR_CHEAT, "" ); +CVAR_DEFINE_AUTO( sw_maxedges, "32", 0, "" ); +static CVAR_DEFINE_AUTO( sw_maxsurfs, "0", 0, "" ); +CVAR_DEFINE_AUTO( sw_mipscale, "1", FCVAR_GLCONFIG, "nothing" ); +CVAR_DEFINE_AUTO( sw_mipcap, "0", FCVAR_GLCONFIG, "nothing" ); +CVAR_DEFINE_AUTO( sw_surfcacheoverride, "0", 0, "" ); +static CVAR_DEFINE_AUTO( sw_waterwarp, "1", FCVAR_GLCONFIG, "nothing" ); +static CVAR_DEFINE_AUTO( sw_notransbrushes, "0", FCVAR_GLCONFIG, "do not apply transparency to water/glasses (faster)" ); +CVAR_DEFINE_AUTO( sw_noalphabrushes, "0", FCVAR_GLCONFIG, "do not draw brush holes (faster)" ); +CVAR_DEFINE_AUTO( r_traceglow, "0", FCVAR_GLCONFIG, "cull flares behind models" ); +CVAR_DEFINE_AUTO( sw_texfilt, "0", FCVAR_GLCONFIG, "texture dither" ); +static CVAR_DEFINE_AUTO( r_novis, "0", 0, "" ); -cvar_t *r_drawworld; -cvar_t *r_dspeeds; -cvar_t *r_lerpmodels; -cvar_t *r_novis; -cvar_t *r_traceglow; - -cvar_t *r_lightlevel; //FIXME HACK - -//PGM -cvar_t *sw_lockpvs; -//PGM DEFINE_ENGINE_SHARED_CVAR_LIST() -int r_viewcluster, r_oldviewcluster; +int r_viewcluster, r_oldviewcluster; -float d_sdivzstepu, d_tdivzstepu, d_zistepu; -float d_sdivzstepv, d_tdivzstepv, d_zistepv; -float d_sdivzorigin, d_tdivzorigin, d_ziorigin; +float d_sdivzstepu, d_tdivzstepu, d_zistepu; +float d_sdivzstepv, d_tdivzstepv, d_zistepv; +float d_sdivzorigin, d_tdivzorigin, d_ziorigin; -fixed16_t sadjust, tadjust, bbextents, bbextentt; +fixed16_t sadjust, tadjust, bbextents, bbextentt; -pixel_t *cacheblock; -int cachewidth; -pixel_t *d_viewbuffer; -short *d_pzbuffer; -unsigned int d_zrowbytes; -unsigned int d_zwidth; +pixel_t *cacheblock; +int cachewidth; +pixel_t *d_viewbuffer; +short *d_pzbuffer; +unsigned int d_zrowbytes; +unsigned int d_zwidth; -mvertex_t *r_pcurrentvertbase; +mvertex_t *r_pcurrentvertbase; -//int c_surf; -qboolean r_surfsonstack; -int r_clipflags; -byte r_warpbuffer[WARP_WIDTH * WARP_HEIGHT]; -int r_numallocatededges; +// int c_surf; +qboolean r_surfsonstack; +int r_clipflags; +byte r_warpbuffer[WARP_WIDTH * WARP_HEIGHT]; +int r_numallocatededges; -float r_aliasuvscale = 1.0; +float r_aliasuvscale = 1.0; static int R_RankForRenderMode( int rendermode ) { switch( rendermode ) { case kRenderTransTexture: - return 1; // draw second + return 1; // draw second case kRenderTransAdd: - return 2; // draw third + return 2; // draw third case kRenderGlow: - return 3; // must be last! + return 3; // must be last! } return 0; } -#if 0 -/* -================ -R_GetEntityRenderMode - -check for texture flags -================ -*/ -int R_GetEntityRenderMode( cl_entity_t *ent ) -{ - int i, opaque, trans; - mstudiotexture_t *ptexture; - cl_entity_t *oldent; - model_t *model; - studiohdr_t *phdr; - - oldent = RI.currententity; - RI.currententity = ent; - return ent->curstate.rendermode; -} -#endif void GAME_EXPORT R_AllowFog( qboolean allowed ) { @@ -157,17 +124,26 @@ R_OpaqueEntity Opaque entity can be brush or studio model but sprite =============== */ -static qboolean R_OpaqueEntity( cl_entity_t *ent ) +qboolean R_OpaqueEntity( cl_entity_t *ent ) { int rendermode = R_GetEntityRenderMode( ent ); if( rendermode == kRenderNormal ) + { + switch( ent->curstate.renderfx ) + { + case kRenderFxNone: + case kRenderFxDeadPlayer: + case kRenderFxLightMultiplier: + case kRenderFxExplode: + return true; + } + } + + if( sw_notransbrushes.value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransTexture ) return true; - if( sw_notransbrushes->value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransTexture ) - return true; - - if( sw_noalphabrushes->value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransAlpha ) + if( sw_noalphabrushes.value && ent->model && ent->model->type == mod_brush && rendermode == kRenderTransAlpha ) return true; return false; @@ -182,11 +158,11 @@ Sorting translucent entities by rendermode then by distance */ static int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) { - cl_entity_t *ent1, *ent2; - vec3_t vecLen, org; - float dist1, dist2; - int rendermode1; - int rendermode2; + cl_entity_t *ent1, *ent2; + vec3_t vecLen, org; + float dist1, dist2; + int rendermode1; + int rendermode2; ent1 = (cl_entity_t *)*a; ent2 = (cl_entity_t *)*b; @@ -194,23 +170,25 @@ static int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) rendermode2 = R_GetEntityRenderMode( ent2 ); // sort by distance - if( ( ent1->model && ent1->model->type != mod_brush ) || rendermode1 != kRenderTransAlpha ) + if(( ent1->model && ent1->model->type != mod_brush ) || rendermode1 != kRenderTransAlpha ) { VectorAverage( ent1->model->mins, ent1->model->maxs, org ); VectorAdd( ent1->origin, org, org ); VectorSubtract( RI.vieworg, org, vecLen ); dist1 = DotProduct( vecLen, vecLen ); } - else dist1 = 1000000000; + else + dist1 = 1000000000; - if( ( ent1->model && ent2->model->type != mod_brush ) || rendermode2 != kRenderTransAlpha ) + if(( ent1->model && ent2->model->type != mod_brush ) || rendermode2 != kRenderTransAlpha ) { VectorAverage( ent2->model->mins, ent2->model->maxs, org ); VectorAdd( ent2->origin, org, org ); VectorSubtract( RI.vieworg, org, vecLen ); dist2 = DotProduct( vecLen, vecLen ); } - else dist2 = 1000000000; + else + dist2 = 1000000000; if( dist1 > dist2 ) return -1; @@ -226,8 +204,6 @@ static int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) return 0; } -#if 1 - /* =============== R_WorldToScreen @@ -238,9 +214,9 @@ Returns true if we behind to screen */ int R_WorldToScreen( const vec3_t point, vec3_t screen ) { - matrix4x4 worldToScreen; - qboolean behind; - float w; + matrix4x4 worldToScreen; + qboolean behind; + float w; if( !point || !screen ) return true; @@ -275,8 +251,8 @@ Convert a given point from screen into world space */ void GAME_EXPORT R_ScreenToWorld( const vec3_t screen, vec3_t point ) { - matrix4x4 screenToWorld; - float w; + matrix4x4 screenToWorld; + float w; if( !point || !screen ) return; @@ -287,11 +263,10 @@ void GAME_EXPORT R_ScreenToWorld( const vec3_t screen, vec3_t point ) point[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3]; point[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3]; w = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3]; - if( w != 0.0f ) VectorScale( point, ( 1.0f / w ), point ); + if( w != 0.0f ) + VectorScale( point, ( 1.0f / w ), point ); } -#endif - /* =============== R_PushScene @@ -395,40 +370,10 @@ R_Clear */ static void R_Clear( int bitMask ) { - int bits; -#if 0 - if( gEngfuncs.CL_IsDevOverviewMode( )) - pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules) - else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); - - bits = GL_DEPTH_BUFFER_BIT; - - if( glState.stencilEnabled ) - bits |= GL_STENCIL_BUFFER_BIT; - - bits &= bitMask; - - pglClear( bits ); - - // change ordering for overview - if( RI.drawOrtho ) - { - gldepthmin = 1.0f; - gldepthmax = 0.0f; - } - else - { - gldepthmin = 0.0f; - gldepthmax = 1.0f; - } - - pglDepthFunc( GL_LEQUAL ); - pglDepthRange( gldepthmin, gldepthmax ); -#endif - memset( vid.buffer, 0, vid.width * vid.height *2); + memset( vid.buffer, 0, vid.width * vid.height * 2 ); } -//============================================================================= +// ============================================================================= /* =============== R_GetFarClip @@ -437,7 +382,7 @@ R_GetFarClip static float R_GetFarClip( void ) { if( WORLDMODEL && RI.drawWorld ) - return MOVEVARS->zmax * 1.73f; + return tr.movevars->zmax * 1.73f; return 2048.0f; } @@ -448,30 +393,15 @@ R_SetupFrustum */ void R_SetupFrustum( void ) { -#if 1 - //ref_overview_t *ov = gEngfuncs.GetOverviewParms(); - - /*if( RP_NORMALPASS() && ( ENGINE_GET_PARM( PARM_WATER_LEVEL ) >= 3 )) - { - RI.fov_x = atan( tan( DEG2RAD( RI.fov_x ) / 2 ) * ( 0.97 + sin( gpGlobals->time * 1.5 ) * 0.03 )) * 2 / (M_PI / 180.0); - RI.fov_y = atan( tan( DEG2RAD( RI.fov_y ) / 2 ) * ( 1.03 - sin( gpGlobals->time * 1.5 ) * 0.03 )) * 2 / (M_PI / 180.0); - }*/ - // build the transformation matrix for the given view angles AngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup ); - //if( !r_lockfrustum->value ) { VectorCopy( RI.vieworg, RI.cullorigin ); VectorCopy( RI.vforward, RI.cull_vforward ); VectorCopy( RI.vright, RI.cull_vright ); VectorCopy( RI.vup, RI.cull_vup ); } - -// if( RI.drawOrtho ) -// GL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); -// else GL_FrustumInitProj( &RI.frustum, 0.0f, R_GetFarClip(), RI.fov_x, RI.fov_y ); // NOTE: we ignore nearplane here (mirrors only) -#endif } /* @@ -481,8 +411,7 @@ R_SetupProjectionMatrix */ static void R_SetupProjectionMatrix( matrix4x4 m ) { -#if 1 - float xMin, xMax, yMin, yMax, zNear, zFar; + float xMin, xMax, yMin, yMax, zNear, zFar; if( RI.drawOrtho ) { @@ -503,7 +432,6 @@ static void R_SetupProjectionMatrix( matrix4x4 m ) xMin = -xMax; Matrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar ); -#endif } /* @@ -513,13 +441,11 @@ R_SetupModelviewMatrix */ static void R_SetupModelviewMatrix( matrix4x4 m ) { -#if 1 Matrix4x4_CreateModelview( m ); Matrix4x4_ConcatRotate( m, -RI.viewangles[2], 1, 0, 0 ); Matrix4x4_ConcatRotate( m, -RI.viewangles[0], 0, 1, 0 ); Matrix4x4_ConcatRotate( m, -RI.viewangles[1], 0, 0, 1 ); Matrix4x4_ConcatTranslate( m, -RI.vieworg[0], -RI.vieworg[1], -RI.vieworg[2] ); -#endif } /* @@ -529,16 +455,6 @@ R_LoadIdentity */ void R_LoadIdentity( void ) { -#if 0 - if( tr.modelviewIdentity ) return; - - Matrix4x4_LoadIdentity( RI.objectMatrix ); - Matrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix ); - - pglMatrixMode( GL_MODELVIEW ); - GL_LoadMatrix( RI.modelviewMatrix ); - tr.modelviewIdentity = true; -#endif } /* @@ -548,25 +464,6 @@ R_RotateForEntity */ void R_RotateForEntity( cl_entity_t *e ) { -#if 0 - float scale = 1.0f; - - if( e == gEngfuncs.GetEntityByIndex( 0 ) ) - { - R_LoadIdentity(); - return; - } - - if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) - scale = e->curstate.scale; - - Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, scale ); - Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); - - pglMatrixMode( GL_MODELVIEW ); - GL_LoadMatrix( RI.modelviewMatrix ); - tr.modelviewIdentity = false; -#endif } /* @@ -576,25 +473,6 @@ R_TranslateForEntity */ void R_TranslateForEntity( cl_entity_t *e ) { -#if 0 - float scale = 1.0f; - - if( e == gEngfuncs.GetEntityByIndex( 0 ) ) - { - R_LoadIdentity(); - return; - } - - if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) - scale = e->curstate.scale; - - Matrix4x4_CreateFromEntity( RI.objectMatrix, vec3_origin, e->origin, scale ); - Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); - - pglMatrixMode( GL_MODELVIEW ); - GL_LoadMatrix( RI.modelviewMatrix ); - tr.modelviewIdentity = false; -#endif } /* @@ -621,7 +499,7 @@ static void R_SetupFrame( void ) // if( !gl_nosort->value ) { // sort translucents entities by rendermode and distance - qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), (void*)R_TransEntityCompare ); + qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t * ), (void *)R_TransEntityCompare ); } // current viewleaf @@ -634,79 +512,6 @@ static void R_SetupFrame( void ) // setup twice until globals fully refactored R_SetupFrameQ(); } -#if 0 - -/* -============= -R_SetupGL -============= -*/ -void R_SetupGL( qboolean set_gl_state ) -{ - R_SetupModelviewMatrix( RI.worldviewMatrix ); - R_SetupProjectionMatrix( RI.projectionMatrix ); - - Matrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix ); - - if( !set_gl_state ) return; - - if( RP_NORMALPASS( )) - { - int x, x2, y, y2; - - // set up viewport (main, playersetup) - x = floor( RI.viewport[0] * gpGlobals->width / gpGlobals->width ); - x2 = ceil(( RI.viewport[0] + RI.viewport[2] ) * gpGlobals->width / gpGlobals->width ); - y = floor( gpGlobals->height - RI.viewport[1] * gpGlobals->height / gpGlobals->height ); - y2 = ceil( gpGlobals->height - ( RI.viewport[1] + RI.viewport[3] ) * gpGlobals->height / gpGlobals->height ); - - pglViewport( x, y2, x2 - x, y - y2 ); - } - else - { - // envpass, mirrorpass - pglViewport( RI.viewport[0], RI.viewport[1], RI.viewport[2], RI.viewport[3] ); - } - - pglMatrixMode( GL_PROJECTION ); - GL_LoadMatrix( RI.projectionMatrix ); - - pglMatrixMode( GL_MODELVIEW ); - GL_LoadMatrix( RI.worldviewMatrix ); - - if( FBitSet( RI.params, RP_CLIPPLANE )) - { - GLdouble clip[4]; - mplane_t *p = &RI.clipPlane; - - clip[0] = p->normal[0]; - clip[1] = p->normal[1]; - clip[2] = p->normal[2]; - clip[3] = -p->dist; - - pglClipPlane( GL_CLIP_PLANE0, clip ); - pglEnable( GL_CLIP_PLANE0 ); - } - - GL_Cull( GL_FRONT ); - - pglDisable( GL_BLEND ); - pglDisable( GL_ALPHA_TEST ); - pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); -} - -/* -============= -R_EndGL -============= -*/ -static void R_EndGL( void ) -{ - if( RI.params & RP_CLIPPLANE ) - pglDisable( GL_CLIP_PLANE0 ); -} - -#endif /* ============= @@ -719,6 +524,7 @@ watertexture to grab fog values from it static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down ) { image_t *tex = NULL; + mnode_t *children[2]; // assure the initial node is not null // we could check it here, but we would rather check it @@ -731,13 +537,13 @@ static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t if( node->contents < 0 ) { - mleaf_t *pleaf; - msurface_t **mark; - int i, c; + mleaf_t *pleaf; + msurface_t **mark; + int i, c; // ignore non-liquid leaves if( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME ) - return NULL; + return NULL; // find texture pleaf = (mleaf_t *)node; @@ -746,8 +552,8 @@ static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t for( i = 0; i < c; i++, mark++ ) { - if( (*mark)->flags & SURF_DRAWTURB && (*mark)->texinfo && (*mark)->texinfo->texture ) - return R_GetTexture( (*mark)->texinfo->texture->gl_texturenum ); + if(( *mark )->flags & SURF_DRAWTURB && ( *mark )->texinfo && ( *mark )->texinfo->texture ) + return R_GetTexture(( *mark )->texinfo->texture->gl_texturenum ); } // texture not found @@ -756,20 +562,23 @@ static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t // this is a regular node // traverse children - if( node->children[0] && ( node->children[0] != ignore )) + node_children( children, node, WORLDMODEL ); + + if( children[0] && ( children[0] != ignore )) { - tex = R_RecursiveFindWaterTexture( node->children[0], node, true ); + tex = R_RecursiveFindWaterTexture( children[0], node, true ); if( tex ) return tex; } - if( node->children[1] && ( node->children[1] != ignore )) + if( children[1] && ( children[1] != ignore )) { - tex = R_RecursiveFindWaterTexture( node->children[1], node, true ); + tex = R_RecursiveFindWaterTexture( children[1], node, true ); if( tex ) return tex; } // for down recursion, return immediately - if( down ) return NULL; + if( down ) + return NULL; // texture not found, step up if any if( node->parent ) @@ -779,32 +588,27 @@ static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t return NULL; } -extern void R_PolysetFillSpans8 ( void * ); -extern void R_PolysetDrawSpansConstant8_33( void *pspanpackage); -extern void R_PolysetDrawSpans8_33( void *pspanpackage); - /* ============= R_DrawEntitiesOnList ============= */ -void R_DrawEntitiesOnList( void ) +static void R_DrawEntitiesOnList( void ) { - extern void (*d_pdrawspans)(void *); - int i; - //extern int d_aflatcolor; - //d_aflatcolor = 0; + int i; + // extern int d_aflatcolor; + // d_aflatcolor = 0; tr.blend = 1.0f; // GL_CheckForErrors(); - //RI.currententity = gEngfuncs.GetEntityByIndex(0); + // RI.currententity = CL_GetEntityByIndex(0); d_pdrawspans = R_PolysetFillSpans8; - GL_SetRenderMode(kRenderNormal); + GL_SetRenderMode( kRenderNormal ); // first draw solid entities for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) { RI.currententity = tr.draw_list->solid_entities[i]; RI.currentmodel = RI.currententity->model; - //d_aflatcolor += 500; + // d_aflatcolor += 500; Assert( RI.currententity != NULL ); Assert( RI.currentmodel != NULL ); @@ -815,45 +619,17 @@ void R_DrawEntitiesOnList( void ) R_DrawBrushModel( RI.currententity ); break; case mod_alias: - //R_DrawAliasModel( RI.currententity ); + // R_DrawAliasModel( RI.currententity ); break; case mod_studio: R_SetUpWorldTransform(); R_DrawStudioModel( RI.currententity ); - #if 0 - // gradient debug (for colormap testing) - {finalvert_t fv[3]; - void R_AliasSetUpTransform (void); - extern void (*d_pdrawspans)(void *); - extern void R_PolysetFillSpans8 ( void * ); - d_pdrawspans = R_PolysetFillSpans8; - //RI.currententity = gEngfuncs.GetEntityByIndex(0); - R_AliasSetUpTransform(); - image_t *image = R_GetTexture(GL_LoadTexture("gfx/env/desertbk", NULL, 0, 0)); - r_affinetridesc.pskin = image->pixels[0]; - r_affinetridesc.skinwidth = image->width; - r_affinetridesc.skinheight = image->height; - R_SetupFinalVert( &fv[0], 0, -50, 50, 31 << 8, 0, 0); - R_SetupFinalVert( &fv[1], 0, 50, 50, 0 << 8, image->width, 0); - R_SetupFinalVert( &fv[2], 0, 0, 0, 0 << 8, image->width/2, image->height); - R_RenderTriangle( &fv[0], &fv[1], &fv[2] ); - } -#endif - - break; default: break; } } -// GL_CheckForErrors(); - - // quake-specific feature -// R_DrawAlphaTextureChains(); - -// GL_CheckForErrors(); - R_SetUpWorldTransform(); // draw sprites seperately, because of alpha blending for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) @@ -872,19 +648,14 @@ void R_DrawEntitiesOnList( void ) } } -// GL_CheckForErrors(); - if( !RI.onlyClientDraw ) { gEngfuncs.CL_DrawEFX( tr.frametime, false ); } -// GL_CheckForErrors(); - if( RI.drawWorld ) gEngfuncs.pfnDrawNormalTriangles(); -// GL_CheckForErrors(); d_pdrawspans = R_PolysetDrawSpans8_33; // then draw translucent entities for( i = 0; i < tr.draw_list->num_trans_entities && !RI.onlyClientDraw; i++ ) @@ -895,9 +666,11 @@ void R_DrawEntitiesOnList( void ) // handle studiomodels with custom rendermodes on texture if( RI.currententity->curstate.rendermode != kRenderNormal ) tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; - else tr.blend = 1.0f; // draw as solid but sorted by distance + else + tr.blend = 1.0f; // draw as solid but sorted by distance - if( tr.blend <= 0.0f ) continue; + if( tr.blend <= 0.0f ) + continue; Assert( RI.currententity != NULL ); Assert( RI.currentmodel != NULL ); @@ -908,7 +681,7 @@ void R_DrawEntitiesOnList( void ) R_DrawBrushModel( RI.currententity ); break; case mod_alias: - //R_DrawAliasModel( RI.currententity ); + // R_DrawAliasModel( RI.currententity ); break; case mod_studio: R_SetUpWorldTransform(); @@ -923,16 +696,11 @@ void R_DrawEntitiesOnList( void ) } } -// GL_CheckForErrors(); - if( RI.drawWorld ) { - // pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - gEngfuncs.pfnDrawTransparentTriangles (); + gEngfuncs.pfnDrawTransparentTriangles(); } -// GL_CheckForErrors(); - if( !RI.onlyClientDraw ) { R_AllowFog( false ); @@ -940,40 +708,34 @@ void R_DrawEntitiesOnList( void ) R_AllowFog( true ); } - //GL_CheckForErrors(); - -// pglDisable( GL_BLEND ); // Trinity Render issues - GL_SetRenderMode(kRenderNormal); + GL_SetRenderMode( kRenderNormal ); R_SetUpWorldTransform(); if( !RI.onlyClientDraw ) R_DrawViewModel(); gEngfuncs.CL_ExtraUpdate(); - //GL_CheckForErrors(); } -#if 1 qboolean insubmodel; - /* ============= R_BmodelCheckBBox ============= */ -int R_BmodelCheckBBox (float *minmaxs) +int R_BmodelCheckBBox( float *minmaxs ) { - int i, *pindex, clipflags; - vec3_t acceptpt, rejectpt; - float d; + int i, *pindex, clipflags; + vec3_t acceptpt, rejectpt; + float d; clipflags = 0; - for (i=0 ; i<4 ; i++) + for( i = 0; i < 4; i++ ) { - // generate accept and reject points - // FIXME: do with fast look-ups or integer tests based on the sign bit - // of the floating point values + // generate accept and reject points + // FIXME: do with fast look-ups or integer tests based on the sign bit + // of the floating point values pindex = qfrustum.pfrustum_indexes[i]; @@ -981,21 +743,21 @@ int R_BmodelCheckBBox (float *minmaxs) rejectpt[1] = minmaxs[pindex[1]]; rejectpt[2] = minmaxs[pindex[2]]; - d = DotProduct (rejectpt, qfrustum.view_clipplanes[i].normal); + d = DotProduct( rejectpt, qfrustum.view_clipplanes[i].normal ); d -= qfrustum.view_clipplanes[i].dist; - if (d <= 0) + if( d <= 0 ) return BMODEL_FULLY_CLIPPED; - acceptpt[0] = minmaxs[pindex[3+0]]; - acceptpt[1] = minmaxs[pindex[3+1]]; - acceptpt[2] = minmaxs[pindex[3+2]]; + acceptpt[0] = minmaxs[pindex[3 + 0]]; + acceptpt[1] = minmaxs[pindex[3 + 1]]; + acceptpt[2] = minmaxs[pindex[3 + 2]]; - d = DotProduct (acceptpt, qfrustum.view_clipplanes[i].normal); + d = DotProduct( acceptpt, qfrustum.view_clipplanes[i].normal ); d -= qfrustum.view_clipplanes[i].dist; - if (d <= 0) - clipflags |= (1<nodes; + node = WORLDMODEL->nodes; - while (1) + while( 1 ) + { + if( node->visframe != tr.visframecount ) + return NULL; // not visible at all + + if( node->contents < 0 ) { - if (node->visframe != tr.visframecount) - return NULL; // not visible at all - - if (node->contents < 0) - { - if (node->contents != CONTENTS_SOLID) - return node; // we've reached a non-solid leaf, so it's - // visible and not BSP clipped - return NULL; // in solid, so not visible - } - - splitplane = node->plane; - sides = BOX_ON_PLANE_SIDE (mins, maxs, splitplane); - - if (sides == 3) - return node; // this is the splitter - - // not split yet; recurse down the contacted side - if (sides & 1) - node = node->children[0]; - else - node = node->children[1]; + if( node->contents != CONTENTS_SOLID ) + return node; // we've reached a non-solid leaf, so it's + // visible and not BSP clipped + return NULL; // in solid, so not visible } + + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE( mins, maxs, splitplane ); + + if( sides == 3 ) + return node; // this is the splitter + + // not split yet; recurse down the contacted side + if( sides & 1 ) + node = node_child( node, 0, WORLDMODEL ); + else + node = node_child( node, 1, WORLDMODEL ); + } } @@ -1049,54 +811,54 @@ RotatedBBox Returns an axially aligned box that contains the input box at the given rotation ============= */ -void RotatedBBox (vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs) +void RotatedBBox( vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t tmaxs ) { - vec3_t tmp, v; - int i, j; - vec3_t forward, right, up; + vec3_t tmp, v; + int i, j; + vec3_t forward, right, up; - if (!angles[0] && !angles[1] && !angles[2]) + if( !angles[0] && !angles[1] && !angles[2] ) { - VectorCopy (mins, tmins); - VectorCopy (maxs, tmaxs); + VectorCopy( mins, tmins ); + VectorCopy( maxs, tmaxs ); return; } - for (i=0 ; i<3 ; i++) + for( i = 0; i < 3; i++ ) { tmins[i] = 99999; tmaxs[i] = -99999; } - AngleVectors (angles, forward, right, up); + AngleVectors( angles, forward, right, up ); - for ( i = 0; i < 8; i++ ) + for( i = 0; i < 8; i++ ) { - if ( i & 1 ) + if( i & 1 ) tmp[0] = mins[0]; else tmp[0] = maxs[0]; - if ( i & 2 ) + if( i & 2 ) tmp[1] = mins[1]; else tmp[1] = maxs[1]; - if ( i & 4 ) + if( i & 4 ) tmp[2] = mins[2]; else tmp[2] = maxs[2]; - VectorScale (forward, tmp[0], v); - VectorMA (v, -tmp[1], right, v); - VectorMA (v, tmp[2], up, v); + VectorScale( forward, tmp[0], v ); + VectorMA( v, -tmp[1], right, v ); + VectorMA( v, tmp[2], up, v ); - for (j=0 ; j<3 ; j++) + for( j = 0; j < 3; j++ ) { - if (v[j] < tmins[j]) + if( v[j] < tmins[j] ) tmins[j] = v[j]; - if (v[j] > tmaxs[j]) + if( v[j] > tmaxs[j] ) tmaxs[j] = v[j]; } } @@ -1108,122 +870,92 @@ void RotatedBBox (vec3_t mins, vec3_t maxs, vec3_t angles, vec3_t tmins, vec3_t R_DrawBEntitiesOnList ============= */ -void R_DrawBEntitiesOnList (void) +static void R_DrawBEntitiesOnList( void ) { - int i, clipflags; - vec3_t oldorigin; - vec3_t mins, maxs; - float minmaxs[6]; - mnode_t *topnode; + int i, clipflags; + vec3_t oldorigin; + vec3_t mins, maxs; + float minmaxs[6]; + mnode_t *topnode; - VectorCopy (tr.modelorg, oldorigin); + VectorCopy( tr.modelorg, oldorigin ); insubmodel = true; - //r_dlightframecount = r_framecount; for( i = 0; i < tr.draw_list->num_edge_entities && !RI.onlyClientDraw; i++ ) { int k; RI.currententity = tr.draw_list->edge_entities[i]; RI.currentmodel = RI.currententity->model; - if (!RI.currentmodel) + if( !RI.currentmodel ) continue; - if (RI.currentmodel->nummodelsurfaces == 0) - continue; // clip brush only - //if ( currententity->flags & RF_BEAM ) - //continue; - if (RI.currentmodel->type != mod_brush) + if( RI.currentmodel->nummodelsurfaces == 0 ) + continue; // clip brush only + if( RI.currentmodel->type != mod_brush ) continue; - // see if the bounding box lets us trivially reject, also sets - // trivial accept status - RotatedBBox (RI.currentmodel->mins, RI.currentmodel->maxs, - RI.currententity->angles, mins, maxs); -#if 0 - mins[0] = mins[0] - 100; - mins[1] = mins[1] - 100; - mins[2] = mins[2] - 100; - maxs[0] = maxs[0] + 100; - maxs[1] = maxs[1] + 100; - maxs[2] = maxs[2] + 100; -#endif - VectorAdd (mins, RI.currententity->origin, minmaxs); - VectorAdd (maxs, RI.currententity->origin, (minmaxs+3)); + // see if the bounding box lets us trivially reject, also sets + // trivial accept status + RotatedBBox( RI.currentmodel->mins, RI.currentmodel->maxs, + RI.currententity->angles, mins, maxs ); + VectorAdd( mins, RI.currententity->origin, minmaxs ); + VectorAdd( maxs, RI.currententity->origin, ( minmaxs + 3 )); - clipflags = R_BmodelCheckBBox (minmaxs); - if (clipflags == BMODEL_FULLY_CLIPPED) - continue; // off the edge of the screen - //clipflags = 0; + clipflags = R_BmodelCheckBBox( minmaxs ); + if( clipflags == BMODEL_FULLY_CLIPPED ) + continue; // off the edge of the screen + // clipflags = 0; - topnode = R_FindTopnode (minmaxs, minmaxs+3); - if (!topnode) - continue; // no part in a visible leaf + topnode = R_FindTopnode( minmaxs, minmaxs + 3 ); + if( !topnode ) + continue; // no part in a visible leaf - VectorCopy (RI.currententity->origin, r_entorigin); - VectorSubtract (RI.vieworg, r_entorigin, tr.modelorg); - //VectorSubtract (r_origin, RI.currententity->origin, modelorg); + VectorCopy( RI.currententity->origin, r_entorigin ); + VectorSubtract( RI.vieworg, r_entorigin, tr.modelorg ); + // VectorSubtract (r_origin, RI.currententity->origin, modelorg); r_pcurrentvertbase = RI.currentmodel->vertexes; - // FIXME: stop transforming twice - R_RotateBmodel (); - - // calculate dynamic lighting for bmodel - // this will reset RI.currententity, do we need this? - //R_PushDlights (); - /*if (clmodel->firstmodelsurface != 0) - { - for (k=0 ; knodes + clmodel->firstnode); - } - }*/ + // FIXME: stop transforming twice + R_RotateBmodel(); // calculate dynamic lighting for bmodel for( k = 0; k < MAX_DLIGHTS; k++ ) { - dlight_t *l = gEngfuncs.GetDynamicLight( k ); - vec3_t origin_l, oldorigin; + dlight_t *l = &tr.dlights[k]; + vec3_t origin_l, oldorigin; - if( l->die < gpGlobals->time || !l->radius ) + if( l->die < gp_cl->time || !l->radius ) continue; VectorCopy( l->origin, oldorigin ); // save lightorigin Matrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 ); Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); VectorCopy( origin_l, l->origin ); // move light in bmodel space - R_MarkLights( l, 1<nodes + RI.currentmodel->hulls[0].firstclipnode ); - VectorCopy( oldorigin, l->origin ); // restore lightorigin*/ - //R_MarkLights( l, 1<nodes + RI.currentmodel->hulls[0].firstclipnode ); + R_MarkLights( l, 1 << k, RI.currentmodel->nodes + RI.currentmodel->hulls[0].firstclipnode ); + VectorCopy( oldorigin, l->origin ); // restore lightorigin } - // RI.currentmodel = tr.draw_list->solid_entities[i]->model; - // RI.currententity = tr.draw_list->solid_entities[i]; RI.currententity->topnode = topnode; -//ASSERT( RI.currentmodel == tr.draw_list->solid_entities[i]->model ); - if (topnode->contents >= 0) + if( topnode->contents >= 0 ) { - // not a leaf; has to be clipped to the world BSP + // not a leaf; has to be clipped to the world BSP r_clipflags = clipflags; - R_DrawSolidClippedSubmodelPolygons (RI.currentmodel, topnode); + R_DrawSolidClippedSubmodelPolygons( RI.currentmodel, topnode ); } else { - // falls entirely in one leaf, so we just put all the - // edges in the edge list and let 1/z sorting handle - // drawing order - //ASSERT( RI.currentmodel == tr.draw_list->solid_entities[i]->model ); - - - R_DrawSubmodelPolygons (RI.currentmodel, clipflags, topnode); + // falls entirely in one leaf, so we just put all the + // edges in the edge list and let 1/z sorting handle + // drawing order + R_DrawSubmodelPolygons( RI.currentmodel, clipflags, topnode ); } RI.currententity->topnode = NULL; - // put back world rotation and frustum clipping - // FIXME: R_RotateBmodel should just work off base_vxx - VectorCopy (RI.base_vpn, RI.vforward); - VectorCopy (RI.base_vup, RI.vup); - VectorCopy (RI.base_vright, RI.vright); - VectorCopy (oldorigin, tr.modelorg); - R_TransformFrustum (); + // put back world rotation and frustum clipping + // FIXME: R_RotateBmodel should just work off base_vxx + VectorCopy( RI.base_vpn, RI.vforward ); + VectorCopy( RI.base_vup, RI.vup ); + VectorCopy( RI.base_vright, RI.vright ); + VectorCopy( oldorigin, tr.modelorg ); + R_TransformFrustum(); } insubmodel = false; @@ -1235,151 +967,121 @@ extern qboolean alphaspans; R_DrawBEntitiesOnList ============= */ -void R_DrawBrushModel(cl_entity_t *pent) +void R_DrawBrushModel( cl_entity_t *pent ) { - int i, clipflags; - vec3_t oldorigin; - vec3_t mins, maxs; - float minmaxs[6]; - mnode_t *topnode; - int k; - edge_t ledges[NUMSTACKEDGES + - ((CACHE_SIZE - 1) / sizeof(edge_t)) + 1]; - surf_t lsurfs[NUMSTACKSURFACES + - ((CACHE_SIZE - 1) / sizeof(surf_t)) + 1]; + int i, clipflags; + vec3_t oldorigin; + vec3_t mins, maxs; + float minmaxs[6]; + mnode_t *topnode; + int k; + edge_t ledges[NUMSTACKEDGES + + (( CACHE_SIZE - 1 ) / sizeof( edge_t )) + 1]; + surf_t lsurfs[NUMSTACKSURFACES + + (( CACHE_SIZE - 1 ) / sizeof( surf_t )) + 1]; - if ( !RI.drawWorld ) + if( !RI.drawWorld ) return; - if (auxedges) + if( auxedges ) { r_edges = auxedges; } else { - r_edges = (edge_t *) - (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + r_edges = (edge_t *) + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 )); } - if (r_surfsonstack) + if( r_surfsonstack ) { - surfaces = (surf_t *)(((uintptr_t)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 )); surf_max = &surfaces[r_cnumsurfs]; - // surface 0 doesn't really exist; it's just a dummy because index 0 - // is used to indicate no edge attached to surface - memset(&surfaces[0], 0, sizeof(surf_t)); + // surface 0 doesn't really exist; it's just a dummy because index 0 + // is used to indicate no edge attached to surface + memset( &surfaces[0], 0, sizeof( surf_t )); surfaces--; - R_SurfacePatch (); + R_SurfacePatch(); } R_BeginEdgeFrame(); - VectorCopy (tr.modelorg, oldorigin); + VectorCopy( tr.modelorg, oldorigin ); insubmodel = true; - //r_dlightframecount = r_framecount; - if (!RI.currentmodel) - return; - if (RI.currentmodel->nummodelsurfaces == 0) - return; // clip brush only - //if ( currententity->flags & RF_BEAM ) - //continue; - if (RI.currentmodel->type != mod_brush) - return; + if( !RI.currentmodel ) + return; + if( RI.currentmodel->nummodelsurfaces == 0 ) + return; // clip brush only + if( RI.currentmodel->type != mod_brush ) + return; // see if the bounding box lets us trivially reject, also sets // trivial accept status - RotatedBBox (RI.currentmodel->mins, RI.currentmodel->maxs, - RI.currententity->angles, mins, maxs); -#if 0 - mins[0] = mins[0] - 100; - mins[1] = mins[1] - 100; - mins[2] = mins[2] - 100; - maxs[0] = maxs[0] + 100; - maxs[1] = maxs[1] + 100; - maxs[2] = maxs[2] + 100; -#endif - VectorAdd (mins, RI.currententity->origin, minmaxs); - VectorAdd (maxs, RI.currententity->origin, (minmaxs+3)); + RotatedBBox( RI.currentmodel->mins, RI.currentmodel->maxs, + RI.currententity->angles, mins, maxs ); + VectorAdd( mins, RI.currententity->origin, minmaxs ); + VectorAdd( maxs, RI.currententity->origin, ( minmaxs + 3 )); - clipflags = R_BmodelCheckBBox (minmaxs); - if (clipflags == BMODEL_FULLY_CLIPPED) - return; // off the edge of the screen - //clipflags = 0; + clipflags = R_BmodelCheckBBox( minmaxs ); + if( clipflags == BMODEL_FULLY_CLIPPED ) + return; // off the edge of the screen + // clipflags = 0; - topnode = R_FindTopnode (minmaxs, minmaxs+3); - if (!topnode) - return; // no part in a visible leaf + topnode = R_FindTopnode( minmaxs, minmaxs + 3 ); + if( !topnode ) + return; // no part in a visible leaf - alphaspans = true; - VectorCopy (RI.currententity->origin, r_entorigin); - VectorSubtract (RI.vieworg, r_entorigin, tr.modelorg); - //VectorSubtract (r_origin, RI.currententity->origin, modelorg); - r_pcurrentvertbase = RI.currentmodel->vertexes; + alphaspans = true; + VectorCopy( RI.currententity->origin, r_entorigin ); + VectorSubtract( RI.vieworg, r_entorigin, tr.modelorg ); + // VectorSubtract (r_origin, RI.currententity->origin, modelorg); + r_pcurrentvertbase = RI.currentmodel->vertexes; // FIXME: stop transforming twice - R_RotateBmodel (); + R_RotateBmodel(); // calculate dynamic lighting for bmodel - // this will reset RI.currententity, do we need this? - //R_PushDlights (); - /*if (clmodel->firstmodelsurface != 0) - { - for (k=0 ; knodes + clmodel->firstnode); - } - }*/ + for( k = 0; k < MAX_DLIGHTS; k++ ) + { + dlight_t *l = &tr.dlights[k]; + vec3_t origin_l, oldorigin; - // calculate dynamic lighting for bmodel - for( k = 0; k < MAX_DLIGHTS; k++ ) - { - dlight_t *l = gEngfuncs.GetDynamicLight( k ); - vec3_t origin_l, oldorigin; + if( l->die < gp_cl->time || !l->radius ) + continue; - if( l->die < gpGlobals->time || !l->radius ) - continue; + VectorCopy( l->origin, oldorigin ); // save lightorigin + Matrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 ); + Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); + tr.modelviewIdentity = false; + VectorCopy( origin_l, l->origin ); // move light in bmodel space + R_MarkLights( l, 1 << k, RI.currentmodel->nodes + RI.currentmodel->hulls[0].firstclipnode ); + VectorCopy( oldorigin, l->origin ); // restore lightorigin*/ + } - VectorCopy( l->origin, oldorigin ); // save lightorigin - Matrix4x4_CreateFromEntity( RI.objectMatrix, RI.currententity->angles, RI.currententity->origin, 1 ); - Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); - tr.modelviewIdentity = false; - VectorCopy( origin_l, l->origin ); // move light in bmodel space - R_MarkLights( l, 1<nodes + RI.currentmodel->hulls[0].firstclipnode ); - VectorCopy( oldorigin, l->origin ); // restore lightorigin*/ - //R_MarkLights( l, 1<nodes + RI.currentmodel->hulls[0].firstclipnode ); - } - - // RI.currentmodel = tr.draw_list->solid_entities[i]->model; - // RI.currententity = tr.draw_list->solid_entities[i]; - RI.currententity->topnode = topnode; -//ASSERT( RI.currentmodel == tr.draw_list->solid_entities[i]->model ); - if (topnode->contents >= 0) - { + RI.currententity->topnode = topnode; + if( topnode->contents >= 0 ) + { // not a leaf; has to be clipped to the world BSP - r_clipflags = clipflags; - R_DrawSolidClippedSubmodelPolygons (RI.currentmodel, topnode); - } - else - { + r_clipflags = clipflags; + R_DrawSolidClippedSubmodelPolygons( RI.currentmodel, topnode ); + } + else + { // falls entirely in one leaf, so we just put all the // edges in the edge list and let 1/z sorting handle // drawing order - //ASSERT( RI.currentmodel == tr.draw_list->solid_entities[i]->model ); - - - R_DrawSubmodelPolygons (RI.currentmodel, clipflags, topnode); - } - RI.currententity->topnode = NULL; + R_DrawSubmodelPolygons( RI.currentmodel, clipflags, topnode ); + } + RI.currententity->topnode = NULL; // put back world rotation and frustum clipping // FIXME: R_RotateBmodel should just work off base_vxx - VectorCopy (RI.base_vpn, RI.vforward); - VectorCopy (RI.base_vup, RI.vup); - VectorCopy (RI.base_vright, RI.vright); - VectorCopy (oldorigin, tr.modelorg); - R_TransformFrustum (); + VectorCopy( RI.base_vpn, RI.vforward ); + VectorCopy( RI.base_vup, RI.vup ); + VectorCopy( RI.base_vright, RI.vright ); + VectorCopy( oldorigin, tr.modelorg ); + R_TransformFrustum(); insubmodel = false; @@ -1387,149 +1089,68 @@ void R_DrawBrushModel(cl_entity_t *pent) alphaspans = false; } -#endif - /* ================ R_EdgeDrawing ================ */ -void R_EdgeDrawing (void) +static void R_EdgeDrawing( void ) { - edge_t ledges[NUMSTACKEDGES + - ((CACHE_SIZE - 1) / sizeof(edge_t)) + 1]; - surf_t lsurfs[NUMSTACKSURFACES + - ((CACHE_SIZE - 1) / sizeof(surf_t)) + 1]; + edge_t ledges[NUMSTACKEDGES + + (( CACHE_SIZE - 1 ) / sizeof( edge_t )) + 1]; + surf_t lsurfs[NUMSTACKSURFACES + + (( CACHE_SIZE - 1 ) / sizeof( surf_t )) + 1]; - if ( !RI.drawWorld ) + if( !RI.drawWorld ) return; - if (auxedges) + if( auxedges ) { r_edges = auxedges; } else { - r_edges = (edge_t *) - (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + r_edges = (edge_t *) + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 )); } - if (r_surfsonstack) + if( r_surfsonstack ) { - surfaces = (surf_t *)(((uintptr_t)&lsurfs + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs + CACHE_SIZE - 1 ) & ~( CACHE_SIZE - 1 )); surf_max = &surfaces[r_cnumsurfs]; - + // surface 0 doesn't really exist; it's just a dummy because index 0 // is used to indicate no edge attached to surface - memset(surfaces, 0, sizeof(surf_t)); + memset( surfaces, 0, sizeof( surf_t )); surfaces--; - R_SurfacePatch (); + R_SurfacePatch(); } - R_BeginEdgeFrame (); + R_BeginEdgeFrame(); // this will prepare edges - R_RenderWorld (); + R_RenderWorld(); // move brushes to separate list to merge with edges? - R_DrawBEntitiesOnList (); + R_DrawBEntitiesOnList(); // display all edges - R_ScanEdges (); + R_ScanEdges(); } -#if 0 -/* -=============== -R_MarkLeaves - -Mark the leaves and nodes that are in the PVS for the current leaf -=============== -*/ -void R_MarkLeaves( void ) -{ - qboolean novis = false; - qboolean force = false; - mleaf_t *leaf = NULL; - mnode_t *node; - vec3_t test; - int i; - - if( !RI.drawWorld ) return; - - /*if( FBitSet( r_novis->flags, FCVAR_CHANGED ) || tr.fResetVis ) - { - // force recalc viewleaf - ClearBits( r_novis->flags, FCVAR_CHANGED ); - tr.fResetVis = false; - RI.viewleaf = NULL; - }*/ - - VectorCopy( RI.pvsorigin, test ); - - if( RI.viewleaf != NULL ) - { - // merge two leafs that can be a crossed-line contents - if( RI.viewleaf->contents == CONTENTS_EMPTY ) - { - VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] - 16.0f ); - leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes ); - } - else - { - VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] + 16.0f ); - leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes ); - } - - if(( leaf->contents != CONTENTS_SOLID ) && ( RI.viewleaf != leaf )) - force = true; - } - - if( RI.viewleaf == RI.oldviewleaf && RI.viewleaf != NULL && !force ) - return; - - // development aid to let you run around - // and see exactly where the pvs ends - //if( sw_lockpvs->value ) return; - - RI.oldviewleaf = RI.viewleaf; - tr.visframecount++; - - if( RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata ) - novis = true; - - gEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis ); - if( force && !novis ) gEngfuncs.R_FatPVS( test, REFPVS_RADIUS, RI.visbytes, true, novis ); - - for( i = 0; i < WORLDMODEL->numleafs; i++ ) - { - if( CHECKVISBIT( RI.visbytes, i )) - { - node = (mnode_t *)&WORLDMODEL->leafs[i+1]; - do - { - if( node->visframe == tr.visframecount ) - break; - node->visframe = tr.visframecount; - node = node->parent; - } while( node ); - } - } -} -#else /* =============== R_MarkLeaves =============== */ -void R_MarkLeaves (void) +static void R_MarkLeaves( void ) { - byte *vis; - mnode_t *node; - int i; + byte *vis; + mnode_t *node; + int i; - if (r_oldviewcluster == r_viewcluster && !r_novis->value && r_viewcluster != -1) + if( r_oldviewcluster == r_viewcluster && !r_novis.value && r_viewcluster != -1 ) return; tr.visframecount++; @@ -1538,27 +1159,23 @@ void R_MarkLeaves (void) gEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), false ); vis = RI.visbytes; - for (i = 0; i < WORLDMODEL->numleafs; i++) + for( i = 0; i < WORLDMODEL->numleafs; i++ ) { - if (vis[i>>3] & (1<<(i&7))) + if( vis[i >> 3] & ( 1 << ( i & 7 ))) { - node = (mnode_t *) &WORLDMODEL->leafs[i+1]; + node = (mnode_t *) &WORLDMODEL->leafs[i + 1]; do { - if (node->visframe == tr.visframecount) + if( node->visframe == tr.visframecount ) break; node->visframe = tr.visframecount; node = node->parent; - } while (node); + } + while( node ); } } } - - -#endif - - /* ================ R_RenderScene @@ -1569,12 +1186,13 @@ R_SetupRefParams must be called right before void GAME_EXPORT R_RenderScene( void ) { if( !WORLDMODEL && RI.drawWorld ) - gEngfuncs.Host_Error( "R_RenderView: NULL worldmodel\n" ); + gEngfuncs.Host_Error( "%s: NULL worldmodel\n", __func__ ); // frametime is valid only for normal pass if( RP_NORMALPASS( )) - tr.frametime = gpGlobals->time - gpGlobals->oldtime; - else tr.frametime = 0.0; + tr.frametime = gp_cl->time - gp_cl->oldtime; + else + tr.frametime = 0.0; // begin a new frame tr.framecount++; @@ -1597,14 +1215,14 @@ void GAME_EXPORT R_RenderScene( void ) tr.modelviewIdentity = true; // R_SetupGL( true ); - //R_Clear( ~0 ); + // R_Clear( ~0 ); R_MarkLeaves(); // R_PushDlights (r_worldmodel); ?? - //R_DrawWorld(); - R_EdgeDrawing (); + // R_DrawWorld(); + R_EdgeDrawing(); - gEngfuncs.CL_ExtraUpdate (); // don't let sound get messed up if going slow + gEngfuncs.CL_ExtraUpdate(); // don't let sound get messed up if going slow R_DrawEntitiesOnList(); @@ -1613,39 +1231,12 @@ void GAME_EXPORT R_RenderScene( void ) // R_EndGL(); } -/* -=============== -R_DoResetGamma - -gamma will be reset for -some type of screenshots -=============== -*/ -qboolean R_DoResetGamma( void ) +void R_GammaChanged( qboolean do_reset_gamma ) { - // FIXME: this looks ugly. apply the backward gamma changes to the output image - return false; -#if 0 - switch( cls.scrshot_action ) - { - case scrshot_normal: - if( CL_IsDevOverviewMode( )) - return true; - return false; - case scrshot_snapshot: - if( CL_IsDevOverviewMode( )) - return true; - return false; - case scrshot_plaque: - case scrshot_savegame: - case scrshot_envshot: - case scrshot_skyshot: - case scrshot_mapshot: - return true; - default: - return false; - } -#endif + if( do_reset_gamma ) // unused + return; + + D_FlushCaches( ); } /* @@ -1655,38 +1246,16 @@ R_BeginFrame */ void GAME_EXPORT R_BeginFrame( qboolean clearScene ) { -#if 0 // unused - if( R_DoResetGamma( )) - { - gEngfuncs.BuildGammaTable( 1.8f, 0.0f ); - D_FlushCaches( ); - - // next frame will be restored gamma - SetBits( vid_brightness->flags, FCVAR_CHANGED ); - SetBits( vid_gamma->flags, FCVAR_CHANGED ); - } - else -#endif - if( FBitSet( vid_gamma->flags, FCVAR_CHANGED ) || FBitSet( vid_brightness->flags, FCVAR_CHANGED )) - { - gEngfuncs.BuildGammaTable( vid_gamma->value, vid_brightness->value ); - - D_FlushCaches( ); - // next frame will be restored gamma - ClearBits( vid_brightness->flags, FCVAR_CHANGED ); - ClearBits( vid_gamma->flags, FCVAR_CHANGED ); - } - R_Set2DMode( true ); // draw buffer stuff - //pglDrawBuffer( GL_BACK ); + // pglDrawBuffer( GL_BACK ); // update texture parameters - //if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED )) - //R_SetTextureParameters(); + // if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED )) + // R_SetTextureParameters(); - gEngfuncs.CL_ExtraUpdate (); + gEngfuncs.CL_ExtraUpdate(); } /* @@ -1704,7 +1273,8 @@ void R_SetupRefParams( const ref_viewpass_t *rvp ) if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP )) RI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW ); - else RI.drawOrtho = false; + else + RI.drawOrtho = false; // setup viewport RI.viewport[0] = rvp->viewport[0]; @@ -1745,7 +1315,7 @@ void GAME_EXPORT R_RenderFrame( const ref_viewpass_t *rvp ) if( gEngfuncs.drawFuncs->GL_RenderFrame( rvp )) { - //R_GatherPlayerLight(); + // R_GatherPlayerLight(); tr.realframecount++; tr.fResetVis = true; return; @@ -1800,7 +1370,7 @@ void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ) R_RenderFrame( &rvp ); - RI.viewleaf = NULL; // force markleafs next frame + RI.viewleaf = NULL; // force markleafs next frame } /* @@ -1808,9 +1378,9 @@ void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ) R_NewMap =============== */ -void GAME_EXPORT R_NewMap (void) +void GAME_EXPORT R_NewMap( void ) { - int i; + int i; model_t *world = WORLDMODEL; r_viewcluster = -1; @@ -1823,44 +1393,50 @@ void GAME_EXPORT R_NewMap (void) R_ClearDecals(); // clear all level decals R_StudioResetPlayerModels(); - r_cnumsurfs = sw_maxsurfs->value; - - if (r_cnumsurfs <= MINSURFACES) - r_cnumsurfs = MINSURFACES; - - if (r_cnumsurfs > NUMSTACKSURFACES) + if( FBitSet( world->flags, MODEL_QBSP2 )) { - surfaces = Mem_Calloc (r_temppool, r_cnumsurfs * sizeof(surf_t)); - surface_p = surfaces; - surf_max = &surfaces[r_cnumsurfs]; - r_surfsonstack = false; - // surface 0 doesn't really exist; it's just a dummy because index 0 - // is used to indicate no edge attached to surface - surfaces--; - R_SurfacePatch (); + gEngfuncs.Host_Error( "Sorry, ref_soft can't load maps in BSP2 format.\n" ); + return; + } + + r_cnumsurfs = sw_maxsurfs.value; + + if( r_cnumsurfs <= MINSURFACES ) + r_cnumsurfs = MINSURFACES; + + if( r_cnumsurfs > NUMSTACKSURFACES ) + { + surfaces = Mem_Calloc( r_temppool, r_cnumsurfs * sizeof( surf_t )); + surface_p = surfaces; + surf_max = &surfaces[r_cnumsurfs]; + r_surfsonstack = false; + // surface 0 doesn't really exist; it's just a dummy because index 0 + // is used to indicate no edge attached to surface + surfaces--; + R_SurfacePatch(); } else { - r_surfsonstack = true; + r_surfsonstack = true; } - r_numallocatededges = sw_maxedges->value; + r_numallocatededges = sw_maxedges.value; - if (r_numallocatededges < MINEDGES) - r_numallocatededges = MINEDGES; + if( r_numallocatededges < MINEDGES ) + r_numallocatededges = MINEDGES; - if (r_numallocatededges <= NUMSTACKEDGES) + if( r_numallocatededges <= NUMSTACKEDGES ) { - auxedges = NULL; + auxedges = NULL; } else { - auxedges = Mem_Malloc (r_temppool, r_numallocatededges * sizeof(edge_t) ); + auxedges = Mem_Malloc( r_temppool, r_numallocatededges * sizeof( edge_t )); } // clear out efrags in case the level hasn't been reloaded for( i = 0; i < world->numleafs; i++ ) - world->leafs[i+1].efrags = NULL; + world->leafs[i + 1].efrags = NULL; tr.sample_size = gEngfuncs.Mod_SampleSizeForFace( &world->surfaces[0] ); @@ -1881,10 +1457,11 @@ void GAME_EXPORT R_NewMap (void) tr.sample_bits = 0; - for( sample_pot = 1; sample_pot < tr.sample_size; sample_pot <<= 1, tr.sample_bits++ ); + for( sample_pot = 1; sample_pot < tr.sample_size; sample_pot <<= 1, tr.sample_bits++ ) + ; } - gEngfuncs.Con_Printf("Map sample size is %d\n", tr.sample_size ); + gEngfuncs.Con_Printf( "Map sample size is %d\n", tr.sample_size ); } @@ -1893,15 +1470,15 @@ void GAME_EXPORT R_NewMap (void) R_InitTurb ================ */ -void R_InitTurb (void) +static void R_InitTurb( void ) { - int i; + int i; - for (i=0 ; i<1280 ; i++) + for( i = 0; i < 1280; i++ ) { - sintable[i] = AMP + sin(i*3.14159*2/CYCLE)*AMP; - intsintable[i] = AMP2 + sin(i*3.14159*2/CYCLE)*AMP2; // AMP2, not 20 - blanktable[i] = 0; //PGM + sintable[i] = AMP + sin( i * 3.14159 * 2 / CYCLE ) * AMP; + intsintable[i] = AMP2 + sin( i * 3.14159 * 2 / CYCLE ) * AMP2; // AMP2, not 20 + blanktable[i] = 0; // PGM } } @@ -1913,32 +1490,24 @@ qboolean GAME_EXPORT R_Init( void ) RETRIEVE_ENGINE_SHARED_CVAR_LIST(); -// sw_aliasstats = ri.Cvar_Get ("sw_polymodelstats", "0", 0); -// sw_allow_modex = ri.Cvar_Get( "sw_allow_modex", "1", CVAR_ARCHIVE ); - sw_clearcolor = gEngfuncs.Cvar_Get ("sw_clearcolor", "48999", 0, "screen clear color"); - sw_drawflat = gEngfuncs.Cvar_Get ("sw_drawflat", "0", 0, ""); - sw_draworder = gEngfuncs.Cvar_Get ("sw_draworder", "0", 0, ""); - sw_maxedges = gEngfuncs.Cvar_Get ("sw_maxedges", "32", 0, ""); - sw_maxsurfs = gEngfuncs.Cvar_Get ("sw_maxsurfs", "0", 0, ""); - sw_mipscale = gEngfuncs.Cvar_Get ("sw_mipscale", "1", FCVAR_GLCONFIG, "nothing"); - sw_mipcap = gEngfuncs.Cvar_Get( "sw_mipcap", "0", FCVAR_GLCONFIG, "nothing" ); - sw_reportedgeout = gEngfuncs.Cvar_Get ("sw_reportedgeout", "0", 0, ""); - sw_reportsurfout = gEngfuncs.Cvar_Get ("sw_reportsurfout", "0", 0, ""); - sw_stipplealpha = gEngfuncs.Cvar_Get( "sw_stipplealpha", "1", FCVAR_GLCONFIG, "nothing" ); - sw_surfcacheoverride = gEngfuncs.Cvar_Get ("sw_surfcacheoverride", "0", 0, ""); - sw_waterwarp = gEngfuncs.Cvar_Get ("sw_waterwarp", "1", FCVAR_GLCONFIG, "nothing"); - sw_notransbrushes = gEngfuncs.Cvar_Get( "sw_notransbrushes", "0", FCVAR_GLCONFIG, "do not apply transparency to water/glasses (faster)"); - sw_noalphabrushes = gEngfuncs.Cvar_Get( "sw_noalphabrushes", "0", FCVAR_GLCONFIG, "do not draw brush holes (faster)"); - r_traceglow = gEngfuncs.Cvar_Get( "r_traceglow", "1", FCVAR_GLCONFIG, "cull flares behind models" ); + + gEngfuncs.Cvar_RegisterVariable( &sw_clearcolor ); + gEngfuncs.Cvar_RegisterVariable( &sw_drawflat ); + gEngfuncs.Cvar_RegisterVariable( &sw_draworder ); + gEngfuncs.Cvar_RegisterVariable( &sw_maxedges ); + gEngfuncs.Cvar_RegisterVariable( &sw_maxsurfs ); + gEngfuncs.Cvar_RegisterVariable( &sw_mipscale ); + gEngfuncs.Cvar_RegisterVariable( &sw_mipcap ); + gEngfuncs.Cvar_RegisterVariable( &sw_surfcacheoverride ); + gEngfuncs.Cvar_RegisterVariable( &sw_waterwarp ); + gEngfuncs.Cvar_RegisterVariable( &sw_notransbrushes ); + gEngfuncs.Cvar_RegisterVariable( &sw_noalphabrushes ); + gEngfuncs.Cvar_RegisterVariable( &r_traceglow ); #ifndef DISABLE_TEXFILTER - sw_texfilt = gEngfuncs.Cvar_Get ("sw_texfilt", "0", FCVAR_GLCONFIG, "texture dither"); + gEngfuncs.Cvar_RegisterVariable( &sw_texfilt ); #endif -// r_speeds = ri.Cvar_Get ("r_speeds", "0", 0); - //r_drawworld = ri.Cvar_Get ("r_drawworld", "1", 0); - //r_dspeeds = ri.Cvar_Get ("r_dspeeds", "0", 0); -// r_lightlevel = ri.Cvar_Get ("r_lightlevel", "0", 0); - //r_lerpmodels = ri.Cvar_Get( "r_lerpmodels", "1", 0 ); - r_novis = gEngfuncs.Cvar_Get( "r_novis", "0", 0, "" ); + gEngfuncs.Cvar_RegisterVariable( &r_novis ); + gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); r_temppool = Mem_AllocPool( "ref_soft zone" ); @@ -1948,7 +1517,7 @@ qboolean GAME_EXPORT R_Init( void ) if( !glblit && !gEngfuncs.R_Init_Video( REF_SOFTWARE )) // request software blitter { gEngfuncs.R_Free_Video(); - gEngfuncs.Con_Printf("failed to initialize software blitter, fallback to glblit\n"); + gEngfuncs.Con_Printf( "failed to initialize software blitter, fallback to glblit\n" ); glblit = true; } @@ -1958,7 +1527,22 @@ qboolean GAME_EXPORT R_Init( void ) return false; } - R_InitBlit( glblit ); + // see R_ProcessEntData for tr.entities initialization + tr.movevars = (movevars_t *)ENGINE_GET_PARM( PARM_GET_MOVEVARS_PTR ); + tr.palette = (color24 *)ENGINE_GET_PARM( PARM_GET_PALETTE_PTR ); + tr.viewent = (cl_entity_t *)ENGINE_GET_PARM( PARM_GET_VIEWENT_PTR ); + tr.texgammatable = (byte *)ENGINE_GET_PARM( PARM_GET_TEXGAMMATABLE_PTR ); + tr.lightgammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LIGHTGAMMATABLE_PTR ); + tr.screengammatable = (uint *)ENGINE_GET_PARM( PARM_GET_SCREENGAMMATABLE_PTR ); + tr.lineargammatable = (uint *)ENGINE_GET_PARM( PARM_GET_LINEARGAMMATABLE_PTR ); + tr.dlights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_DLIGHTS_PTR ); + tr.elights = (dlight_t *)ENGINE_GET_PARM( PARM_GET_ELIGHTS_PTR ); + + if( !R_InitBlit( glblit )) + { + gEngfuncs.R_Free_Video(); + return false; + } R_InitImages(); // init draw stack @@ -1966,11 +1550,12 @@ qboolean GAME_EXPORT R_Init( void ) tr.draw_stack_pos = 0; qfrustum.view_clipplanes[0].leftedge = true; qfrustum.view_clipplanes[1].rightedge = true; - qfrustum.view_clipplanes[1].leftedge = qfrustum.view_clipplanes[2].leftedge =qfrustum.view_clipplanes[3].leftedge = false; + qfrustum.view_clipplanes[1].leftedge = qfrustum.view_clipplanes[2].leftedge = qfrustum.view_clipplanes[3].leftedge = false; qfrustum.view_clipplanes[0].rightedge = qfrustum.view_clipplanes[2].rightedge = qfrustum.view_clipplanes[3].rightedge = false; R_StudioInit(); R_SpriteInit(); R_InitTurb(); + GL_InitRandomTable(); return true; } @@ -1989,32 +1574,33 @@ CL_FxBlend */ int CL_FxBlend( cl_entity_t *e ) { - int blend = 0; - float offset, dist; - vec3_t tmp; + int blend = 0; + float offset, dist; + vec3_t tmp; offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx switch( e->curstate.renderfx ) { case kRenderFxPulseSlowWide: - blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 2 + offset ); + blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset ); break; case kRenderFxPulseFastWide: - blend = e->curstate.renderamt + 0x40 * sin( gpGlobals->time * 8 + offset ); + blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset ); break; case kRenderFxPulseSlow: - blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 2 + offset ); + blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset ); break; case kRenderFxPulseFast: - blend = e->curstate.renderamt + 0x10 * sin( gpGlobals->time * 8 + offset ); + blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset ); break; case kRenderFxFadeSlow: if( RP_NORMALPASS( )) { if( e->curstate.renderamt > 0 ) e->curstate.renderamt -= 1; - else e->curstate.renderamt = 0; + else + e->curstate.renderamt = 0; } blend = e->curstate.renderamt; break; @@ -2023,7 +1609,8 @@ int CL_FxBlend( cl_entity_t *e ) { if( e->curstate.renderamt > 3 ) e->curstate.renderamt -= 4; - else e->curstate.renderamt = 0; + else + e->curstate.renderamt = 0; } blend = e->curstate.renderamt; break; @@ -2032,7 +1619,8 @@ int CL_FxBlend( cl_entity_t *e ) { if( e->curstate.renderamt < 255 ) e->curstate.renderamt += 1; - else e->curstate.renderamt = 255; + else + e->curstate.renderamt = 255; } blend = e->curstate.renderamt; break; @@ -2041,34 +1629,45 @@ int CL_FxBlend( cl_entity_t *e ) { if( e->curstate.renderamt < 252 ) e->curstate.renderamt += 4; - else e->curstate.renderamt = 255; + else + e->curstate.renderamt = 255; } blend = e->curstate.renderamt; break; case kRenderFxStrobeSlow: - blend = 20 * sin( gpGlobals->time * 4 + offset ); - if( blend < 0 ) blend = 0; - else blend = e->curstate.renderamt; + blend = 20 * sin( gp_cl->time * 4 + offset ); + if( blend < 0 ) + blend = 0; + else + blend = e->curstate.renderamt; break; case kRenderFxStrobeFast: - blend = 20 * sin( gpGlobals->time * 16 + offset ); - if( blend < 0 ) blend = 0; - else blend = e->curstate.renderamt; + blend = 20 * sin( gp_cl->time * 16 + offset ); + if( blend < 0 ) + blend = 0; + else + blend = e->curstate.renderamt; break; case kRenderFxStrobeFaster: - blend = 20 * sin( gpGlobals->time * 36 + offset ); - if( blend < 0 ) blend = 0; - else blend = e->curstate.renderamt; + blend = 20 * sin( gp_cl->time * 36 + offset ); + if( blend < 0 ) + blend = 0; + else + blend = e->curstate.renderamt; break; case kRenderFxFlickerSlow: - blend = 20 * (sin( gpGlobals->time * 2 ) + sin( gpGlobals->time * 17 + offset )); - if( blend < 0 ) blend = 0; - else blend = e->curstate.renderamt; + blend = 20 * ( sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset )); + if( blend < 0 ) + blend = 0; + else + blend = e->curstate.renderamt; break; case kRenderFxFlickerFast: - blend = 20 * (sin( gpGlobals->time * 16 ) + sin( gpGlobals->time * 23 + offset )); - if( blend < 0 ) blend = 0; - else blend = e->curstate.renderamt; + blend = 20 * ( sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset )); + if( blend < 0 ) + blend = 0; + else + blend = e->curstate.renderamt; break; case kRenderFxHologram: case kRenderFxDistort: @@ -2087,8 +1686,10 @@ int CL_FxBlend( cl_entity_t *e ) else { e->curstate.renderamt = 180; - if( dist <= 100 ) blend = e->curstate.renderamt; - else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt ); + if( dist <= 100 ) + blend = e->curstate.renderamt; + else + blend = (int) (( 1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt ); blend += gEngfuncs.COM_RandomLong( -32, 31 ); } break; diff --git a/ref/soft/r_math.c b/ref/soft/r_math.c index 4c835623..bf377d4f 100644 --- a/ref/soft/r_math.c +++ b/ref/soft/r_math.c @@ -67,17 +67,17 @@ void Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yM void Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ) { - out[0][0] = 2.0f / (xRight - xLeft); - out[1][1] = 2.0f / (yTop - yBottom); - out[2][2] = -2.0f / (zFar - zNear); + out[0][0] = 2.0f / ( xRight - xLeft ); + out[1][1] = 2.0f / ( yTop - yBottom ); + out[2][2] = -2.0f / ( zFar - zNear ); out[3][3] = 1.0f; out[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f; out[2][0] = 0.0f; out[2][1] = 0.0f; - out[0][3] = -(xRight + xLeft) / (xRight - xLeft); - out[1][3] = -(yTop + yBottom) / (yTop - yBottom); - out[2][3] = -(zFar + zNear) / (zFar - zNear); + out[0][3] = -( xRight + xLeft ) / ( xRight - xLeft ); + out[1][3] = -( yTop + yBottom ) / ( yTop - yBottom ); + out[2][3] = -( zFar + zNear ) / ( zFar - zNear ); } /* @@ -99,46 +99,6 @@ void Matrix4x4_CreateModelview( matrix4x4 out ) out[1][2] = 1.0f; } -void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ) -{ - out[ 0] = in[0][0]; - out[ 1] = in[1][0]; - out[ 2] = in[2][0]; - out[ 3] = in[3][0]; - out[ 4] = in[0][1]; - out[ 5] = in[1][1]; - out[ 6] = in[2][1]; - out[ 7] = in[3][1]; - out[ 8] = in[0][2]; - out[ 9] = in[1][2]; - out[10] = in[2][2]; - out[11] = in[3][2]; - out[12] = in[0][3]; - out[13] = in[1][3]; - out[14] = in[2][3]; - out[15] = in[3][3]; -} - -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ) -{ - out[0][0] = in[0]; - out[1][0] = in[1]; - out[2][0] = in[2]; - out[3][0] = in[3]; - out[0][1] = in[4]; - out[1][1] = in[5]; - out[2][1] = in[6]; - out[3][1] = in[7]; - out[0][2] = in[8]; - out[1][2] = in[9]; - out[2][2] = in[10]; - out[3][2] = in[11]; - out[0][3] = in[12]; - out[1][3] = in[13]; - out[2][3] = in[14]; - out[3][3] = in[15]; -} - void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) { out[0][0] = 1.0f; @@ -161,36 +121,37 @@ void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ) { - float len, c, s; + float len, c, s; len = x * x + y * y + z * z; - if( len != 0.0f ) len = 1.0f / sqrt( len ); + if( len != 0.0f ) + len = 1.0f / sqrt( len ); x *= len; y *= len; z *= len; - angle *= (-M_PI_F / 180.0f); + angle *= ( -M_PI_F / 180.0f ); SinCos( angle, &s, &c ); - out[0][0]=x * x + c * (1 - x * x); - out[0][1]=x * y * (1 - c) + z * s; - out[0][2]=z * x * (1 - c) - y * s; - out[0][3]=0.0f; - out[1][0]=x * y * (1 - c) - z * s; - out[1][1]=y * y + c * (1 - y * y); - out[1][2]=y * z * (1 - c) + x * s; - out[1][3]=0.0f; - out[2][0]=z * x * (1 - c) + y * s; - out[2][1]=y * z * (1 - c) - x * s; - out[2][2]=z * z + c * (1 - z * z); - out[2][3]=0.0f; - out[3][0]=0.0f; - out[3][1]=0.0f; - out[3][2]=0.0f; - out[3][3]=1.0f; + out[0][0] = x * x + c * ( 1 - x * x ); + out[0][1] = x * y * ( 1 - c ) + z * s; + out[0][2] = z * x * ( 1 - c ) - y * s; + out[0][3] = 0.0f; + out[1][0] = x * y * ( 1 - c ) - z * s; + out[1][1] = y * y + c * ( 1 - y * y ); + out[1][2] = y * z * ( 1 - c ) + x * s; + out[1][3] = 0.0f; + out[2][0] = z * x * ( 1 - c ) + y * s; + out[2][1] = y * z * ( 1 - c ) - x * s; + out[2][2] = z * z + c * ( 1 - z * z ); + out[2][3] = 0.0f; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; } -void Matrix4x4_CreateScale( matrix4x4 out, float x ) +static void Matrix4x4_CreateScale( matrix4x4 out, float x ) { out[0][0] = x; out[0][1] = 0.0f; @@ -210,7 +171,7 @@ void Matrix4x4_CreateScale( matrix4x4 out, float x ) out[3][3] = 1.0f; } -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) +static void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) { out[0][0] = x; out[0][1] = 0.0f; @@ -247,21 +208,3 @@ void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float Matrix4x4_CreateRotate( temp, angle, x, y, z ); Matrix4x4_Concat( out, base, temp ); } - -void Matrix4x4_ConcatScale( matrix4x4 out, float x ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale( temp, x ); - Matrix4x4_Concat( out, base, temp ); -} - -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale3( temp, x, y, z ); - Matrix4x4_Concat( out, base, temp ); -} diff --git a/ref/soft/r_misc.c b/ref/soft/r_misc.c index e984a27e..7af67152 100644 --- a/ref/soft/r_misc.c +++ b/ref/soft/r_misc.c @@ -21,51 +21,30 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" -#define NUM_MIPS 4 +#define NUM_MIPS 4 -cvar_t *sw_mipcap; -cvar_t *sw_mipscale; +surfcache_t *d_initial_rover; +qboolean d_roverwrapped; +int d_minmip; +float d_scalemip[NUM_MIPS - 1]; -surfcache_t *d_initial_rover; -qboolean d_roverwrapped; -int d_minmip; -float d_scalemip[NUM_MIPS-1]; - -static float basemip[NUM_MIPS-1] = {1.0, 0.5*0.8, 0.25*0.8}; +static float basemip[NUM_MIPS - 1] = {1.0, 0.5 * 0.8, 0.25 * 0.8}; -//int d_vrectx, d_vrecty, d_vrectright_particle, d_vrectbottom_particle; +// int d_vrectx, d_vrecty, d_vrectright_particle, d_vrectbottom_particle; -//int d_pix_min, d_pix_max, d_pix_shift; +// int d_pix_min, d_pix_max, d_pix_shift; -int d_scantable[MAXHEIGHT]; -short *zspantable[MAXHEIGHT]; +int d_scantable[MAXHEIGHT]; +short *zspantable[MAXHEIGHT]; struct qfrustum_s qfrustum; /* ================ D_Patch ================ */ -void D_Patch (void) +static void D_Patch( void ) { -#if id386 - extern void D_Aff8Patch( void ); - static qboolean protectset8 = false; - extern void D_PolysetAff8Start( void ); - - if (!protectset8) - { - Sys_MakeCodeWriteable ((int)D_PolysetAff8Start, - (int)D_Aff8Patch - (int)D_PolysetAff8Start); - Sys_MakeCodeWriteable ((long)R_Surf8Start, - (long)R_Surf8End - (long)R_Surf8Start); - protectset8 = true; - } - colormap = vid.colormap; - - R_Surf8Patch (); - D_Aff8Patch(); -#endif } /* ================ @@ -73,12 +52,12 @@ D_ViewChanged ================ */ -void D_ViewChanged (void) +void D_ViewChanged( void ) { - int i; + int i; scale_for_mip = xscale; - if (yscale > xscale) + if( yscale > xscale ) scale_for_mip = yscale; d_zrowbytes = vid.width * 2; @@ -93,27 +72,27 @@ void D_ViewChanged (void) if (d_pix_max < 1) d_pix_max = 1;*/ - //d_vrectx = RI.vrect.x; - //d_vrecty = RI.vrect.y; - //d_vrectright_particle = gpGlobals->width - d_pix_max; - //d_vrectbottom_particle = - // gpGlobals->height - d_pix_max; + // d_vrectx = RI.vrect.x; + // d_vrecty = RI.vrect.y; + // d_vrectright_particle = gpGlobals->width - d_pix_max; + // d_vrectbottom_particle = + // gpGlobals->height - d_pix_max; - for (i=0 ; inormal); - *dist = p->dist - d; -// TODO: when we have rotating entities, this will need to use the view matrix - TransformVector (p->normal, normal); -} - - /* =============== R_SetUpFrustumIndexes =============== */ -void R_SetUpFrustumIndexes (void) +static void R_SetUpFrustumIndexes( void ) { - int i, j, *pindex; + int i, j, *pindex; pindex = qfrustum.frustum_indexes; - for (i=0 ; i<4 ; i++) + for( i = 0; i < 4; i++ ) { - for (j=0 ; j<3 ; j++) + for( j = 0; j < 3; j++ ) { - if (qfrustum.view_clipplanes[i].normal[j] < 0) + if( qfrustum.view_clipplanes[i].normal[j] < 0 ) { pindex[j] = j; - pindex[j+3] = j+3; + pindex[j + 3] = j + 3; } else { - pindex[j] = j+3; - pindex[j+3] = j; + pindex[j] = j + 3; + pindex[j + 3] = j; } } - // FIXME: do just once at start + // FIXME: do just once at start qfrustum.pfrustum_indexes[i] = pindex; pindex += 6; } @@ -214,23 +177,23 @@ Called every time the vid structure or r_refdef changes. Guaranteed to be called before the first refresh =============== */ -void R_ViewChanged (vrect_t *vr) +static void R_ViewChanged( vrect_t *vr ) { - int i; + int i; float verticalFieldOfView, horizontalFieldOfView, xOrigin, yOrigin; RI.vrect = *vr; - horizontalFieldOfView = 2*tan((float)RI.fov_x/360.0f * M_PI_F); - verticalFieldOfView = 2*tan((float)RI.fov_y/360.0f * M_PI_F); + horizontalFieldOfView = 2 * tan((float)RI.fov_x / 360.0f * M_PI_F ); + verticalFieldOfView = 2 * tan((float)RI.fov_y / 360.0f * M_PI_F ); RI.fvrectx = (float)RI.vrect.x; RI.fvrectx_adj = (float)RI.vrect.x - 0.5f; - RI.vrect_x_adj_shift20 = (RI.vrect.x<<20) + (1<<19) - 1; + RI.vrect_x_adj_shift20 = ( RI.vrect.x << 20 ) + ( 1 << 19 ) - 1; RI.fvrecty = (float)RI.vrect.y; RI.fvrecty_adj = (float)RI.vrect.y - 0.5f; RI.vrectright = RI.vrect.x + RI.vrect.width; - RI.vrectright_adj_shift20 = (RI.vrectright<<20) + (1<<19) - 1; + RI.vrectright_adj_shift20 = ( RI.vrectright << 20 ) + ( 1 << 19 ) - 1; RI.fvrectright = (float)RI.vrectright; RI.fvrectright_adj = (float)RI.vrectright - 0.5f; RI.vrectrightedge = (float)RI.vrectright - 0.99f; @@ -238,14 +201,14 @@ void R_ViewChanged (vrect_t *vr) RI.fvrectbottom = (float)RI.vrectbottom; RI.fvrectbottom_adj = (float)RI.vrectbottom - 0.5f; - RI.aliasvrect.x = (int)(RI.vrect.x * r_aliasuvscale); - RI.aliasvrect.y = (int)(RI.vrect.y * r_aliasuvscale); - RI.aliasvrect.width = (int)(RI.vrect.width * r_aliasuvscale); - RI.aliasvrect.height = (int)(RI.vrect.height * r_aliasuvscale); - RI.aliasvrectright = RI.aliasvrect.x + - RI.aliasvrect.width; - RI.aliasvrectbottom = RI.aliasvrect.y + - RI.aliasvrect.height; + RI.aliasvrect.x = (int)( RI.vrect.x * r_aliasuvscale ); + RI.aliasvrect.y = (int)( RI.vrect.y * r_aliasuvscale ); + RI.aliasvrect.width = (int)( RI.vrect.width * r_aliasuvscale ); + RI.aliasvrect.height = (int)( RI.vrect.height * r_aliasuvscale ); + RI.aliasvrectright = RI.aliasvrect.x + + RI.aliasvrect.width; + RI.aliasvrectbottom = RI.aliasvrect.y + + RI.aliasvrect.height; xOrigin = XCENTERING; yOrigin = YCENTERING; @@ -256,11 +219,11 @@ void R_ViewChanged (vrect_t *vr) // the polygon rasterization will never render in the first row or column // but will definately render in the [range] row and column, so adjust the // buffer origin to get an exact edge to edge fill - xcenter = ((float)RI.vrect.width * XCENTERING) + - RI.vrect.x - 0.5f; + xcenter = ((float)RI.vrect.width * XCENTERING ) + + RI.vrect.x - 0.5f; aliasxcenter = xcenter * r_aliasuvscale; - ycenter = ((float)RI.vrect.height * YCENTERING) + - RI.vrect.y - 0.5f; + ycenter = ((float)RI.vrect.height * YCENTERING ) + + RI.vrect.y - 0.5f; aliasycenter = ycenter * r_aliasuvscale; xscale = RI.vrect.width / horizontalFieldOfView; @@ -270,38 +233,38 @@ void R_ViewChanged (vrect_t *vr) yscale = xscale; aliasyscale = yscale * r_aliasuvscale; yscaleinv = 1.0f / yscale; - //xscaleshrink = (RI.vrect.width-6)/RI.horizontalFieldOfView; - //yscaleshrink = xscaleshrink; + // xscaleshrink = (RI.vrect.width-6)/RI.horizontalFieldOfView; + // yscaleshrink = xscaleshrink; // left side clip - qfrustum.screenedge[0].normal[0] = -1.0f / (xOrigin*horizontalFieldOfView); + qfrustum.screenedge[0].normal[0] = -1.0f / ( xOrigin * horizontalFieldOfView ); qfrustum.screenedge[0].normal[1] = 0; qfrustum.screenedge[0].normal[2] = 1; qfrustum.screenedge[0].type = PLANE_ANYZ; // right side clip - qfrustum.screenedge[1].normal[0] = - 1.0f / ((1.0f-xOrigin)*horizontalFieldOfView); + qfrustum.screenedge[1].normal[0] + = 1.0f / (( 1.0f - xOrigin ) * horizontalFieldOfView ); qfrustum.screenedge[1].normal[1] = 0; qfrustum.screenedge[1].normal[2] = 1; qfrustum.screenedge[1].type = PLANE_ANYZ; // top side clip qfrustum.screenedge[2].normal[0] = 0; - qfrustum.screenedge[2].normal[1] = -1.0f / (yOrigin*verticalFieldOfView); + qfrustum.screenedge[2].normal[1] = -1.0f / ( yOrigin * verticalFieldOfView ); qfrustum.screenedge[2].normal[2] = 1; qfrustum.screenedge[2].type = PLANE_ANYZ; // bottom side clip qfrustum.screenedge[3].normal[0] = 0; - qfrustum.screenedge[3].normal[1] = 1.0f / ((1.0f-yOrigin)*verticalFieldOfView); + qfrustum.screenedge[3].normal[1] = 1.0f / (( 1.0f - yOrigin ) * verticalFieldOfView ); qfrustum.screenedge[3].normal[2] = 1; qfrustum.screenedge[3].type = PLANE_ANYZ; - for (i=0 ; i<4 ; i++) - VectorNormalize (qfrustum.screenedge[i].normal); + for( i = 0; i < 4; i++ ) + VectorNormalize( qfrustum.screenedge[i].normal ); - D_ViewChanged (); + D_ViewChanged(); } @@ -310,29 +273,29 @@ void R_ViewChanged (vrect_t *vr) R_SetupFrame =============== */ -void R_SetupFrameQ (void) +void R_SetupFrameQ( void ) { - int i; - vrect_t vrect; + int i; + vrect_t vrect; - if (r_fullbright->flags & FCVAR_CHANGED) + if( r_fullbright->flags & FCVAR_CHANGED ) { r_fullbright->flags &= ~FCVAR_CHANGED; - D_FlushCaches( ); // so all lighting changes + D_FlushCaches( ); // so all lighting changes } - //tr.framecount++; + // tr.framecount++; // build the transformation matrix for the given view angles - VectorCopy (RI.vieworg, tr.modelorg); + VectorCopy( RI.vieworg, tr.modelorg ); - //AngleVectors (RI.viewangles, RI.vforward, RI.vright, RI.vup); + // AngleVectors (RI.viewangles, RI.vforward, RI.vright, RI.vup); // current viewleaf - if ( RI.drawWorld ) + if( RI.drawWorld ) { - RI.viewleaf = gEngfuncs.Mod_PointInLeaf (RI.vieworg, WORLDMODEL->nodes); + RI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.vieworg, WORLDMODEL->nodes ); r_viewcluster = RI.viewleaf->cluster; } @@ -352,16 +315,16 @@ void R_SetupFrameQ (void) d_viewbuffer = (void *)vid.buffer; r_screenwidth = vid.rowbytes; - R_ViewChanged (&vrect); + R_ViewChanged( &vrect ); // start off with just the four screen edge clip planes - R_TransformFrustum (); - R_SetUpFrustumIndexes (); + R_TransformFrustum(); + R_SetUpFrustumIndexes(); // save base values - VectorCopy (RI.vforward, RI.base_vpn); - VectorCopy (RI.vright, RI.base_vright); - VectorCopy (RI.vup, RI.base_vup); + VectorCopy( RI.vforward, RI.base_vpn ); + VectorCopy( RI.vright, RI.base_vright ); + VectorCopy( RI.vup, RI.base_vup ); // clear frame counts /* c_faceclip = 0; @@ -377,29 +340,14 @@ void R_SetupFrameQ (void) d_roverwrapped = false; d_initial_rover = sc_rover; - d_minmip = sw_mipcap->value; - if (d_minmip > 3) + d_minmip = sw_mipcap.value; + if( d_minmip > 3 ) d_minmip = 3; - else if (d_minmip < 0) + else if( d_minmip < 0 ) d_minmip = 0; - for (i=0 ; i<(NUM_MIPS-1) ; i++) - d_scalemip[i] = basemip[i] * sw_mipscale->value; + for( i = 0; i < ( NUM_MIPS - 1 ); i++ ) + d_scalemip[i] = basemip[i] * sw_mipscale.value; - //d_aflatcolor = 0; + // d_aflatcolor = 0; } - - -#if !id386 - -/* -================ -R_SurfacePatch -================ -*/ -/*void R_SurfacePatch (void) -{ - // we only patch code on Intel -} -*/ -#endif // !id386 diff --git a/ref/soft/r_part.c b/ref/soft/r_part.c index 790b843e..6424fdbd 100644 --- a/ref/soft/r_part.c +++ b/ref/soft/r_part.c @@ -21,21 +21,21 @@ GNU General Public License for more details. #include "pm_local.h" #include "studio.h" -static 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 }; +static 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 }; static color24 gTracerColors[] = { -{ 255, 255, 255 }, // White -{ 255, 0, 0 }, // Red -{ 0, 255, 0 }, // Green -{ 0, 0, 255 }, // Blue -{ 0, 0, 0 }, // Tracer default, filled in from cvars, etc. -{ 255, 167, 17 }, // Yellow-orange sparks -{ 255, 130, 90 }, // Yellowish streaks (garg) -{ 55, 60, 144 }, // Blue egon streak -{ 255, 130, 90 }, // More Yellowish streaks (garg) -{ 255, 140, 90 }, // More Yellowish streaks (garg) -{ 200, 130, 90 }, // More red streaks (garg) -{ 255, 120, 70 }, // Darker red streaks (garg) + { 255, 255, 255 }, // White + { 255, 0, 0 }, // Red + { 0, 255, 0 }, // Green + { 0, 0, 255 }, // Blue + { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. + { 255, 167, 17 }, // Yellow-orange sparks + { 255, 130, 90 }, // Yellowish streaks (garg) + { 55, 60, 144 }, // Blue egon streak + { 255, 130, 90 }, // More Yellowish streaks (garg) + { 255, 140, 90 }, // More Yellowish streaks (garg) + { 200, 130, 90 }, // More red streaks (garg) + { 255, 120, 70 }, // Darker red streaks (garg) }; /* @@ -47,23 +47,23 @@ update particle color, position, free expired and draw it */ void GAME_EXPORT CL_DrawParticles( double frametime, particle_t *cl_active_particles, float partsize ) { - particle_t *p; - vec3_t right, up; - color24 *pColor; - int alpha; - float size; + particle_t *p; + vec3_t right, up; + color24 color; + int alpha; + float size; if( !cl_active_particles ) - return; // nothing to draw? + return; // nothing to draw? - //pglEnable( GL_BLEND ); - //pglDisable( GL_ALPHA_TEST ); - //pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + // pglEnable( GL_BLEND ); + // pglDisable( GL_ALPHA_TEST ); + // pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); GL_SetRenderMode( kRenderTransAdd ); GL_Bind( XASH_TEXTURE0, tr.particleTexture ); - //pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - //pglDepthMask( GL_FALSE ); + // pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + // pglDepthMask( GL_FALSE ); for( p = cl_active_particles; p; p = p->next ) { @@ -72,29 +72,31 @@ void GAME_EXPORT CL_DrawParticles( double frametime, particle_t *cl_active_parti size = partsize; // get initial size of particle // scale up to keep particles from disappearing - size += (p->org[0] - RI.vieworg[0]) * RI.cull_vforward[0]; - size += (p->org[1] - RI.vieworg[1]) * RI.cull_vforward[1]; - size += (p->org[2] - RI.vieworg[2]) * RI.cull_vforward[2]; + size += ( p->org[0] - RI.vieworg[0] ) * RI.cull_vforward[0]; + size += ( p->org[1] - RI.vieworg[1] ) * RI.cull_vforward[1]; + size += ( p->org[2] - RI.vieworg[2] ) * RI.cull_vforward[2]; - if( size < 20.0f ) size = partsize; - else size = partsize + size * 0.002f; + if( size < 20.0f ) + size = partsize; + else + size = partsize + size * 0.002f; // scale the axes by radius VectorScale( RI.cull_vright, size, right ); VectorScale( RI.cull_vup, size, up ); p->color = bound( 0, p->color, 255 ); - pColor = gEngfuncs.CL_GetPaletteColor( p->color ); + color = tr.palette[p->color]; - alpha = 255 * (p->die - gpGlobals->time) * 16.0f; + alpha = 255 * ( p->die - gp_cl->time ) * 16.0f; if( alpha > 255 || p->type == pt_static ) alpha = 255; - //TriColor4ub( gEngfuncs.LightToTexGamma( pColor->r ), - // gEngfuncs.LightToTexGamma( pColor->g ), - // gEngfuncs.LightToTexGamma( pColor->b ), alpha ); - //TriBrightness( alpha / 255.0f ); - _TriColor4f(1.0f*alpha/255/255*pColor->r,1.0f*alpha/255/255*pColor->g,1.0f*alpha/255/255* pColor->b,1.0f ); + // TriColor4ub( LightToTexGamma( color.r ), + // LightToTexGamma( color.g ), + // LightToTexGamma( color.b ), alpha ); + // TriBrightness( alpha / 255.0f ); + _TriColor4f( 1.0f * alpha / 255 / 255 * color.r, 1.0f * alpha / 255 / 255 * color.g, 1.0f * alpha / 255 / 255 * color.b, 1.0f ); TriBegin( TRI_QUADS ); TriTexCoord2f( 0.0f, 1.0f ); @@ -113,7 +115,7 @@ void GAME_EXPORT CL_DrawParticles( double frametime, particle_t *cl_active_parti } TriEnd(); - //pglDepthMask( GL_TRUE ); + // pglDepthMask( GL_TRUE ); } /* @@ -125,8 +127,8 @@ check tracer bbox */ static qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end ) { - vec3_t mins, maxs; - int i; + vec3_t mins, maxs; + int i; return false; /* // compute the bounding box @@ -163,18 +165,18 @@ update tracer color, position, free expired and draw it */ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers ) { - float scale, atten, gravity; - vec3_t screenLast, screen; - vec3_t start, end, delta; - particle_t *p; + float scale, atten, gravity; + vec3_t screenLast, screen; + vec3_t start, end, delta; + particle_t *p; // update tracer color if this is changed - if( FBitSet( tracerred->flags|tracergreen->flags|tracerblue->flags|traceralpha->flags, FCVAR_CHANGED )) + if( FBitSet( tracerred->flags | tracergreen->flags | tracerblue->flags | traceralpha->flags, FCVAR_CHANGED )) { color24 *customColors = &gTracerColors[4]; - customColors->r = (byte)(tracerred->value * traceralpha->value * 255); - customColors->g = (byte)(tracergreen->value * traceralpha->value * 255); - customColors->b = (byte)(tracerblue->value * traceralpha->value * 255); + customColors->r = (byte)( tracerred->value * traceralpha->value * 255 ); + customColors->g = (byte)( tracergreen->value * traceralpha->value * 255 ); + customColors->b = (byte)( tracerblue->value * traceralpha->value * 255 ); ClearBits( tracerred->flags, FCVAR_CHANGED ); ClearBits( tracergreen->flags, FCVAR_CHANGED ); ClearBits( tracerblue->flags, FCVAR_CHANGED ); @@ -182,26 +184,28 @@ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers } if( !cl_active_tracers ) - return; // nothing to draw? + return; // nothing to draw? GL_SetRenderMode( kRenderTransAdd ); if( !TriSpriteTexture( gEngfuncs.GetDefaultSprite( REF_DOT_SPRITE ), 0 )) return; - //pglEnable( GL_BLEND ); - //pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - //pglDisable( GL_ALPHA_TEST ); - //pglDepthMask( GL_FALSE ); + // pglEnable( GL_BLEND ); + // pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + // pglDisable( GL_ALPHA_TEST ); + // pglDepthMask( GL_FALSE ); - gravity = frametime * MOVEVARS->gravity; - scale = 1.0 - (frametime * 0.9); - if( scale < 0.0f ) scale = 0.0f; + gravity = frametime * tr.movevars->gravity; + scale = 1.0 - ( frametime * 0.9 ); + if( scale < 0.0f ) + scale = 0.0f; for( p = cl_active_tracers; p; p = p->next ) { - atten = (p->die - gpGlobals->time); - if( atten > 0.1f ) atten = 0.1f; + atten = ( p->die - gp_cl->time ); + if( atten > 0.1f ) + atten = 0.1f; VectorScale( p->vel, ( p->ramp * atten ), delta ); VectorAdd( p->org, delta, end ); @@ -209,10 +213,10 @@ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers if( !CL_CullTracer( p, start, end )) { - vec3_t verts[4], tmp2; - vec3_t tmp, normal; - color24 *pColor; - short alpha = p->packedColor; + vec3_t verts[4], tmp2; + vec3_t tmp, normal; + color24 color; + short alpha = p->packedColor; // Transform point into screen space TriWorldToScreen( start, screen ); @@ -236,26 +240,25 @@ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers VectorAdd( verts[0], delta, verts[2] ); VectorAdd( verts[1], delta, verts[3] ); - if( p->color > sizeof( gTracerColors ) / sizeof( color24 ) ) + if( p->color < 0 || p->color >= sizeof( gTracerColors ) / sizeof( gTracerColors[0] )) { - gEngfuncs.Con_Printf( S_ERROR "UserTracer with color > %d\n", sizeof( gTracerColors ) / sizeof( color24 )); - p->color = 0; + p->color = TRACER_COLORINDEX_DEFAULT; } - pColor = &gTracerColors[p->color]; - //TriColor4ub( pColor->r, pColor->g, pColor->b, p->packedColor ); - _TriColor4f(1.0f*alpha/255/255*pColor->r,1.0f*alpha/255/255*pColor->g,1.0f*alpha/255/255* pColor->b,1.0f ); + color = gTracerColors[p->color]; + // TriColor4ub( color.r, color.g, color.b, p->packedColor ); + _TriColor4f( 1.0f * alpha / 255 / 255 * color.r, 1.0f * alpha / 255 / 255 * color.g, 1.0f * alpha / 255 / 255 * color.b, 1.0f ); TriBegin( TRI_QUADS ); - TriTexCoord2f( 0.0f, 0.8f ); - TriVertex3fv( verts[2] ); - TriTexCoord2f( 1.0f, 0.8f ); - TriVertex3fv( verts[3] ); - TriTexCoord2f( 1.0f, 0.0f ); - TriVertex3fv( verts[1] ); - TriTexCoord2f( 0.0f, 0.0f ); - TriVertex3fv( verts[0] ); + TriTexCoord2f( 0.0f, 0.8f ); + TriVertex3fv( verts[2] ); + TriTexCoord2f( 1.0f, 0.8f ); + TriVertex3fv( verts[3] ); + TriTexCoord2f( 1.0f, 0.0f ); + TriVertex3fv( verts[1] ); + TriTexCoord2f( 0.0f, 0.0f ); + TriVertex3fv( verts[0] ); TriEnd(); } @@ -268,8 +271,9 @@ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers p->vel[1] *= scale; p->vel[2] -= gravity; - p->packedColor = 255 * (p->die - gpGlobals->time) * 2; - if( p->packedColor > 255 ) p->packedColor = 255; + p->packedColor = 255 * ( p->die - gp_cl->time ) * 2; + if( p->packedColor > 255 ) + p->packedColor = 255; } else if( p->type == pt_slowgrav ) { @@ -277,7 +281,7 @@ void GAME_EXPORT CL_DrawTracers( double frametime, particle_t *cl_active_tracers } } - //pglDepthMask( GL_TRUE ); + // pglDepthMask( GL_TRUE ); } /* @@ -289,9 +293,8 @@ allow to draw effects from custom renderer */ void GAME_EXPORT CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime ) { - ref_instance_t oldRI = RI; + ref_instance_t oldRI = RI; - memcpy( &oldRI, &RI, sizeof( ref_instance_t )); R_SetupRefParams( rvp ); R_SetupFrustum(); // R_SetupGL( false ); // don't touch GL-states @@ -303,5 +306,5 @@ void GAME_EXPORT CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean t gEngfuncs.CL_DrawEFX( frametime, trans_pass ); // restore internal state - memcpy( &RI, &oldRI, sizeof( ref_instance_t )); + RI = oldRI; } diff --git a/ref/soft/r_polyse.c b/ref/soft/r_polyse.c index 0d36d386..4cc19083 100644 --- a/ref/soft/r_polyse.c +++ b/ref/soft/r_polyse.c @@ -24,47 +24,39 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // TODO: put in span spilling to shrink list size // !!! if this is changed, it must be changed in d_polysa.s too !!! -#define DPS_MAXSPANS MAXHEIGHT+1 - // 1 extra for spanpackage that marks end +#define DPS_MAXSPANS MAXHEIGHT + 1 +// 1 extra for spanpackage that marks end -// !!! if this is changed, it must be changed in asm_draw.h too !!! -typedef struct { - void *pdest; - short *pz; - int count; - pixel_t *ptex; - int sfrac, tfrac, light, zi; -} spanpackage_t; - -typedef struct { - int isflattop; - int numleftedges; - int *pleftedgevert0; - int *pleftedgevert1; - int *pleftedgevert2; - int numrightedges; - int *prightedgevert0; - int *prightedgevert1; - int *prightedgevert2; +typedef struct +{ + int isflattop; + int numleftedges; + int *pleftedgevert0; + int *pleftedgevert1; + int *pleftedgevert2; + int numrightedges; + int *prightedgevert0; + int *prightedgevert1; + int *prightedgevert2; } edgetable; aliastriangleparms_t aliastriangleparms; -int r_p0[6], r_p1[6], r_p2[6]; +int r_p0[6], r_p1[6], r_p2[6]; -int d_xdenom; +int d_xdenom; -edgetable *pedgetable; +edgetable *pedgetable; -edgetable edgetables[12] = { +edgetable edgetables[12] = { {0, 1, r_p0, r_p2, NULL, 2, r_p0, r_p1, r_p2 }, - {0, 2, r_p1, r_p0, r_p2, 1, r_p1, r_p2, NULL}, + {0, 2, r_p1, r_p0, r_p2, 1, r_p1, r_p2, NULL}, {1, 1, r_p0, r_p2, NULL, 1, r_p1, r_p2, NULL}, {0, 1, r_p1, r_p0, NULL, 2, r_p1, r_p2, r_p0 }, - {0, 2, r_p0, r_p2, r_p1, 1, r_p0, r_p1, NULL}, + {0, 2, r_p0, r_p2, r_p1, 1, r_p0, r_p1, NULL}, {0, 1, r_p2, r_p1, NULL, 1, r_p2, r_p0, NULL}, {0, 1, r_p2, r_p1, NULL, 2, r_p2, r_p0, r_p1 }, - {0, 2, r_p2, r_p1, r_p0, 1, r_p2, r_p0, NULL}, + {0, 2, r_p2, r_p1, r_p0, 1, r_p2, r_p0, NULL}, {0, 1, r_p1, r_p0, NULL, 1, r_p1, r_p2, NULL}, {1, 1, r_p2, r_p1, NULL, 1, r_p0, r_p1, NULL}, {1, 1, r_p1, r_p0, NULL, 1, r_p2, r_p0, NULL}, @@ -72,78 +64,57 @@ edgetable edgetables[12] = { }; // FIXME: some of these can become statics -int a_sstepxfrac, a_tstepxfrac, r_lstepx, a_ststepxwhole; -int r_sstepx, r_tstepx, r_lstepy, r_sstepy, r_tstepy; -int r_zistepx, r_zistepy; -int d_aspancount, d_countextrastep; +int a_sstepxfrac, a_tstepxfrac, r_lstepx, a_ststepxwhole; +int r_sstepx, r_tstepx, r_lstepy, r_sstepy, r_tstepy; +int r_zistepx, r_zistepy; +int d_aspancount, d_countextrastep; -spanpackage_t *a_spans; -spanpackage_t *d_pedgespanpackage; -static int ystart; -pixel_t *d_pdest, *d_ptex; -short *d_pz; -int d_sfrac, d_tfrac, d_light, d_zi; -int d_ptexextrastep, d_sfracextrastep; -int d_tfracextrastep, d_lightextrastep, d_pdestextrastep; -int d_lightbasestep, d_pdestbasestep, d_ptexbasestep; -int d_sfracbasestep, d_tfracbasestep; -int d_ziextrastep, d_zibasestep; -int d_pzextrastep, d_pzbasestep; +spanpackage_t *a_spans; +spanpackage_t *d_pedgespanpackage; +static int ystart; +pixel_t *d_pdest, *d_ptex; +short *d_pz; +int d_sfrac, d_tfrac, d_light, d_zi; +int d_ptexextrastep, d_sfracextrastep; +int d_tfracextrastep, d_lightextrastep, d_pdestextrastep; +int d_lightbasestep, d_pdestbasestep, d_ptexbasestep; +int d_sfracbasestep, d_tfracbasestep; +int d_ziextrastep, d_zibasestep; +int d_pzextrastep, d_pzbasestep; -static int ubasestep, errorterm, erroradjustup, erroradjustdown; +static int ubasestep, errorterm, erroradjustup, erroradjustdown; -typedef struct { - int quotient; - int remainder; +typedef struct +{ + int quotient; + int remainder; } adivtab_t; -static adivtab_t adivtab[32*32] = { +static adivtab_t adivtab[32 * 32] = { #include "adivtab.h" }; -byte *skintable[MAX_LBM_HEIGHT]; -int skinwidth; -byte *skinstart; +byte *skintable[MAX_LBM_HEIGHT]; +int skinwidth; +byte *skinstart; -void (*d_pdrawspans)(spanpackage_t *pspanpackage); +void (*d_pdrawspans)( spanpackage_t *pspanpackage ); -void R_PolysetStub (spanpackage_t *pspanpackage) +static void R_PolysetStub( spanpackage_t *pspanpackage ) { } -void R_PolysetDrawSpans8_33 (spanpackage_t *pspanpackage); -void R_PolysetDrawSpans8_66 (spanpackage_t *pspanpackage); -void R_PolysetDrawSpans8_Opaque (spanpackage_t *pspanpackage); - -qboolean R_PolysetCalcGradients(int skinwidth); -void R_DrawNonSubdiv (void); -void R_PolysetSetEdgeTable (void); -void R_RasterizeAliasPolySmooth (void); -void R_PolysetScanLeftEdge(int height); -qboolean R_PolysetScanLeftEdge_C(int height); - -/* -================ -R_PolysetUpdateTables -================ -*/ -void R_PolysetUpdateTables (void) -{ - int i; - byte *s; - - if (r_affinetridesc.skinwidth != skinwidth || - r_affinetridesc.pskin != skinstart) - { - skinwidth = r_affinetridesc.skinwidth; - skinstart = r_affinetridesc.pskin; - s = skinstart; - for (i=0 ; iv[1] - aliastriangleparms.b->v[1] ) * ( aliastriangleparms.a->v[0] - aliastriangleparms.c->v[0] ) - @@ -165,27 +136,27 @@ void R_DrawTriangle( void ) dv0_ab = aliastriangleparms.a->u - aliastriangleparms.b->u; dv1_ab = aliastriangleparms.a->v - aliastriangleparms.b->v; - if ( !( dv0_ab | dv1_ab ) ) + if( !( dv0_ab | dv1_ab )) return; dv0_ac = aliastriangleparms.a->u - aliastriangleparms.c->u; dv1_ac = aliastriangleparms.a->v - aliastriangleparms.c->v; - if ( !( dv0_ac | dv1_ac ) ) + if( !( dv0_ac | dv1_ac )) return; d_xdenom = ( dv0_ac * dv1_ab ) - ( dv0_ab * dv1_ac ); - if ( d_xdenom < 0 ) + if( d_xdenom < 0 ) { a_spans = spans; - r_p0[0] = aliastriangleparms.a->u; // u - r_p0[1] = aliastriangleparms.a->v; // v - r_p0[2] = aliastriangleparms.a->s; // s - r_p0[3] = aliastriangleparms.a->t; // t - r_p0[4] = aliastriangleparms.a->l; // light - r_p0[5] = aliastriangleparms.a->zi; // iz + r_p0[0] = aliastriangleparms.a->u; // u + r_p0[1] = aliastriangleparms.a->v; // v + r_p0[2] = aliastriangleparms.a->s; // s + r_p0[3] = aliastriangleparms.a->t; // t + r_p0[4] = aliastriangleparms.a->l; // light + r_p0[5] = aliastriangleparms.a->zi; // iz r_p1[0] = aliastriangleparms.b->u; r_p1[1] = aliastriangleparms.b->v; @@ -201,8 +172,8 @@ void R_DrawTriangle( void ) r_p2[4] = aliastriangleparms.c->l; r_p2[5] = aliastriangleparms.c->zi; - R_PolysetSetEdgeTable (); - R_RasterizeAliasPolySmooth (); + R_PolysetSetEdgeTable(); + R_RasterizeAliasPolySmooth(); } } @@ -216,56 +187,10 @@ static inline qboolean R_DrawCheckBounds( pixel_t *lptex ) return false; return true; } -#if 0 -static inline qboolean R_CheckBounds2( spanpackage_t *pspanpackage, int lcount ) -{ - int lsfrac, ltfrac; - pixel_t *lptex, *start, *end; - - lptex = pspanpackage->ptex; - lsfrac = pspanpackage->sfrac; - ltfrac = pspanpackage->tfrac; - start = r_affinetridesc.pskin; - end = skinend; - - do - { - if( lptex - start < 0 || lptex - end >= 0 ) - return false; - - lptex += a_ststepxwhole; - lsfrac += a_sstepxfrac; - lptex += lsfrac >> 16; - lsfrac &= 0xFFFF; - ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) - { - lptex += r_affinetridesc.skinwidth; - ltfrac &= 0xFFFF; - } - } while (--lcount); - - // span is linear, so only need to check first and last - - if( lptex - start < 0 || lptex - end >= 0 ) - return false; - - //if( !(--lcount) ) - //return true; - - lptex = lptex + a_ststepxwhole * lcount + ((lsfrac + ( a_sstepxfrac * lcount)) >> 16) + ((ltfrac + (a_tstepxfrac * lcount)) >> 16) * r_affinetridesc.skinwidth; - - if( lptex - start < 0 || lptex - end >= 0 ) - return false; - - - return true; -} -#endif static inline qboolean R_PolysetCheckBounds( pixel_t *lptex, int lsfrac, int ltfrac, int lcount ) { - pixel_t *start, *end; + pixel_t *start, *end; start = r_affinetridesc.pskin; end = skinend; @@ -273,10 +198,10 @@ static inline qboolean R_PolysetCheckBounds( pixel_t *lptex, int lsfrac, int ltf if( lptex - start < 0 || lptex - end >= 0 ) return false; - if( !(--lcount) ) + if( !( --lcount )) return true; - lptex = lptex + a_ststepxwhole * lcount + ((lsfrac + ( a_sstepxfrac * lcount)) >> 16) + ((ltfrac + (a_tstepxfrac * lcount)) >> 16) * r_affinetridesc.skinwidth; + lptex = lptex + a_ststepxwhole * lcount + (( lsfrac + ( a_sstepxfrac * lcount )) >> 16 ) + (( ltfrac + ( a_tstepxfrac * lcount )) >> 16 ) * r_affinetridesc.skinwidth; if( lptex - start < 0 || lptex - end >= 0 ) return false; @@ -291,7 +216,7 @@ static inline qboolean R_PolysetCheckBounds( pixel_t *lptex, int lsfrac, int ltf R_PolysetScanLeftEdge_C ==================== */ -qboolean R_PolysetScanLeftEdge_C(int height) +qboolean R_PolysetScanLeftEdge_C( int height ) { do { @@ -303,14 +228,14 @@ qboolean R_PolysetScanLeftEdge_C(int height) d_pedgespanpackage->sfrac = d_sfrac; d_pedgespanpackage->tfrac = d_tfrac; - // FIXME: need to clamp l, s, t, at both ends? + // FIXME: need to clamp l, s, t, at both ends? d_pedgespanpackage->light = d_light; d_pedgespanpackage->zi = d_zi; d_pedgespanpackage++; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_pdest += d_pdestextrastep; d_pz += d_pzextrastep; @@ -322,7 +247,7 @@ qboolean R_PolysetScanLeftEdge_C(int height) d_sfrac &= 0xFFFF; d_tfrac += d_tfracextrastep; - if (d_tfrac & 0x10000) + if( d_tfrac & 0x10000 ) { d_ptex += r_affinetridesc.skinwidth; d_tfrac &= 0xFFFF; @@ -341,7 +266,7 @@ qboolean R_PolysetScanLeftEdge_C(int height) d_ptex += d_sfrac >> 16; d_sfrac &= 0xFFFF; d_tfrac += d_tfracbasestep; - if (d_tfrac & 0x10000) + if( d_tfrac & 0x10000 ) { d_ptex += r_affinetridesc.skinwidth; d_tfrac &= 0xFFFF; @@ -349,7 +274,8 @@ qboolean R_PolysetScanLeftEdge_C(int height) d_light += d_lightbasestep; d_zi += d_zibasestep; } - } while (--height); + } + while( --height ); return true; } @@ -363,28 +289,28 @@ quotient must fit in 32 bits. FIXME: GET RID OF THIS! (FloorDivMod) ==================== */ -void FloorDivMod (float numer, float denom, int *quotient, - int *rem) +static void FloorDivMod( float numer, float denom, int *quotient, + int *rem ) { - int q, r; - float x; + int q, r; + float x; - if (numer >= 0.0f) + if( numer >= 0.0f ) { - x = floor(numer / denom); + x = floor( numer / denom ); q = (int)x; - r = (int)floor(numer - (x * denom)); + r = (int)floor( numer - ( x * denom )); } else { - // - // perform operations with positive values, and fix mod to make floor-based - // - x = floor(-numer / denom); + // + // perform operations with positive values, and fix mod to make floor-based + // + x = floor( -numer / denom ); q = -(int)x; - r = (int)floor(-numer - (x * denom)); - if (r != 0) + r = (int)floor( -numer - ( x * denom )); + if( r != 0 ) { q--; r = (int)denom - r; @@ -394,7 +320,7 @@ void FloorDivMod (float numer, float denom, int *quotient, { int i; d_pdrawspans = R_PolysetStub; - gEngfuncs.Con_Printf( S_ERROR "FloorDivMod: q overflow!\n" ); + gEngfuncs.Con_Printf( S_ERROR "%s: q overflow!\n", __func__ ); q = 1; } @@ -402,7 +328,7 @@ void FloorDivMod (float numer, float denom, int *quotient, { int i; d_pdrawspans = R_PolysetStub; - gEngfuncs.Con_Printf( S_ERROR "FloorDivMod: r overflow!\n"); + gEngfuncs.Con_Printf( S_ERROR "%s: r overflow!\n", __func__ ); r = 1; } @@ -416,12 +342,12 @@ void FloorDivMod (float numer, float denom, int *quotient, R_PolysetSetUpForLineScan ==================== */ -void R_PolysetSetUpForLineScan(fixed8_t startvertu, fixed8_t startvertv, - fixed8_t endvertu, fixed8_t endvertv) +static void R_PolysetSetUpForLineScan( fixed8_t startvertu, fixed8_t startvertv, + fixed8_t endvertu, fixed8_t endvertv ) { - float dm, dn; - int tm, tn; - adivtab_t *ptemp; + float dm, dn; + int tm, tn; + adivtab_t *ptemp; // TODO: implement x86 version @@ -430,10 +356,10 @@ void R_PolysetSetUpForLineScan(fixed8_t startvertu, fixed8_t startvertv, tm = endvertu - startvertu; tn = endvertv - startvertv; - if (((tm <= 16) && (tm >= -15)) && - ((tn <= 16) && (tn >= -15))) + if((( tm <= 16 ) && ( tm >= -15 )) + && (( tn <= 16 ) && ( tn >= -15 ))) { - ptemp = &adivtab[((tm+15) << 5) + (tn+15)]; + ptemp = &adivtab[(( tm + 15 ) << 5 ) + ( tn + 15 )]; ubasestep = ptemp->quotient; erroradjustup = ptemp->remainder; erroradjustdown = tn; @@ -443,7 +369,7 @@ void R_PolysetSetUpForLineScan(fixed8_t startvertu, fixed8_t startvertv, dm = tm; dn = tn; - FloorDivMod (dm, dn, &ubasestep, &erroradjustup); + FloorDivMod( dm, dn, &ubasestep, &erroradjustup ); erroradjustdown = dn; } @@ -456,269 +382,10 @@ void R_PolysetSetUpForLineScan(fixed8_t startvertu, fixed8_t startvertv, R_PolysetCalcGradients ================ */ -#if id386 && !defined __linux__ -void R_PolysetCalcGradients( int skinwidth ) +qboolean R_PolysetCalcGradients( int skinwidth ) { - static float xstepdenominv, ystepdenominv, t0, t1; - static float p01_minus_p21, p11_minus_p21, p00_minus_p20, p10_minus_p20; - static float one = 1.0F, negative_one = -1.0F; - static unsigned long t0_int, t1_int; - - extern unsigned long fpu_sp24_ceil_cw, fpu_ceil_cw, fpu_chop_cw; - - /* - p00_minus_p20 = r_p0[0] - r_p2[0]; - p01_minus_p21 = r_p0[1] - r_p2[1]; - p10_minus_p20 = r_p1[0] - r_p2[0]; - p11_minus_p21 = r_p1[1] - r_p2[1]; - */ - - __asm mov eax, dword ptr [r_p0+0] - __asm mov ebx, dword ptr [r_p0+4] - __asm sub eax, dword ptr [r_p2+0] - __asm sub ebx, dword ptr [r_p2+4] - __asm mov p00_minus_p20, eax - __asm mov p01_minus_p21, ebx - __asm fild dword ptr p00_minus_p20 - __asm fild dword ptr p01_minus_p21 - __asm mov eax, dword ptr [r_p1+0] - __asm mov ebx, dword ptr [r_p1+4] - __asm sub eax, dword ptr [r_p2+0] - __asm sub ebx, dword ptr [r_p2+4] - __asm fstp p01_minus_p21 - __asm fstp p00_minus_p20 - __asm mov p10_minus_p20, eax - __asm mov p11_minus_p21, ebx - __asm fild dword ptr p10_minus_p20 - __asm fild dword ptr p11_minus_p21 - __asm fstp p11_minus_p21 - __asm fstp p10_minus_p20 - - /* - xstepdenominv = 1.0 / (float)d_xdenom; - - ystepdenominv = -xstepdenominv; - */ - - /* - ** put FPU in single precision ceil mode - */ - __asm fldcw word ptr [fpu_sp24_ceil_cw] -// __asm fldcw word ptr [fpu_ceil_cw] - - __asm fild dword ptr d_xdenom ; d_xdenom - __asm fdivr one ; 1 / d_xdenom - __asm fst xstepdenominv ; - __asm fmul negative_one ; -( 1 / d_xdenom ) - -// ceil () for light so positive steps are exaggerated, negative steps -// diminished, pushing us away from underflow toward overflow. Underflow is -// very visible, overflow is very unlikely, because of ambient lighting - /* - t0 = r_p0[4] - r_p2[4]; - t1 = r_p1[4] - r_p2[4]; - r_lstepx = (int) - ceil((t1 * p01_minus_p21 - t0 * p11_minus_p21) * xstepdenominv); - r_lstepy = (int) - ceil((t1 * p00_minus_p20 - t0 * p10_minus_p20) * ystepdenominv); - */ - __asm mov eax, dword ptr [r_p0+16] - __asm mov ebx, dword ptr [r_p1+16] - __asm sub eax, dword ptr [r_p2+16] - __asm sub ebx, dword ptr [r_p2+16] - - __asm fstp ystepdenominv ; (empty) - - __asm mov t0_int, eax - __asm mov t1_int, ebx - __asm fild t0_int ; t0 - __asm fild t1_int ; t1 | t0 - __asm fxch st(1) ; t0 | t1 - __asm fstp t0 ; t1 - __asm fst t1 ; t1 - __asm fmul p01_minus_p21 ; t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p01_minus_p21 - __asm fmul p11_minus_p21 ; t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t1 ; t1 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p00_minus_p20 ; t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p10_minus_p20 ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fxch st(2) ; t0 * p11_minus_p21 | t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - __asm fsubp st(3), st ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fsubrp st(1), st ; t1 * p00_minus_p20 - t0 * p10_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fxch st(1) ; t1 * p01_minus_p21 - t0 * p11_minus_p21 | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fmul xstepdenominv ; r_lstepx | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fxch st(1) - __asm fmul ystepdenominv ; r_lstepy | r_lstepx - __asm fxch st(1) ; r_lstepx | r_lstepy - __asm fistp dword ptr [r_lstepx] - __asm fistp dword ptr [r_lstepy] - - /* - ** put FPU back into extended precision chop mode - */ - __asm fldcw word ptr [fpu_chop_cw] - - /* - t0 = r_p0[2] - r_p2[2]; - t1 = r_p1[2] - r_p2[2]; - r_sstepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_sstepy = (int)((t1 * p00_minus_p20 - t0* p10_minus_p20) * - ystepdenominv); - */ - __asm mov eax, dword ptr [r_p0+8] - __asm mov ebx, dword ptr [r_p1+8] - __asm sub eax, dword ptr [r_p2+8] - __asm sub ebx, dword ptr [r_p2+8] - __asm mov t0_int, eax - __asm mov t1_int, ebx - __asm fild t0_int ; t0 - __asm fild t1_int ; t1 | t0 - __asm fxch st(1) ; t0 | t1 - __asm fstp t0 ; t1 - __asm fst t1 ; (empty) - - __asm fmul p01_minus_p21 ; t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p01_minus_p21 - __asm fmul p11_minus_p21 ; t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t1 ; t1 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p00_minus_p20 ; t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p10_minus_p20 ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fxch st(2) ; t0 * p11_minus_p21 | t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - __asm fsubp st(3), st ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fsubrp st(1), st ; t1 * p00_minus_p20 - t0 * p10_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fxch st(1) ; t1 * p01_minus_p21 - t0 * p11_minus_p21 | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fmul xstepdenominv ; r_lstepx | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fxch st(1) - __asm fmul ystepdenominv ; r_lstepy | r_lstepx - __asm fxch st(1) ; r_lstepx | r_lstepy - __asm fistp dword ptr [r_sstepx] - __asm fistp dword ptr [r_sstepy] - - /* - t0 = r_p0[3] - r_p2[3]; - t1 = r_p1[3] - r_p2[3]; - r_tstepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_tstepy = (int)((t1 * p00_minus_p20 - t0 * p10_minus_p20) * - ystepdenominv); - */ - __asm mov eax, dword ptr [r_p0+12] - __asm mov ebx, dword ptr [r_p1+12] - __asm sub eax, dword ptr [r_p2+12] - __asm sub ebx, dword ptr [r_p2+12] - - __asm mov t0_int, eax - __asm mov t1_int, ebx - __asm fild t0_int ; t0 - __asm fild t1_int ; t1 | t0 - __asm fxch st(1) ; t0 | t1 - __asm fstp t0 ; t1 - __asm fst t1 ; (empty) - - __asm fmul p01_minus_p21 ; t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p01_minus_p21 - __asm fmul p11_minus_p21 ; t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t1 ; t1 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p00_minus_p20 ; t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p10_minus_p20 ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fxch st(2) ; t0 * p11_minus_p21 | t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - __asm fsubp st(3), st ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fsubrp st(1), st ; t1 * p00_minus_p20 - t0 * p10_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fxch st(1) ; t1 * p01_minus_p21 - t0 * p11_minus_p21 | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fmul xstepdenominv ; r_lstepx | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fxch st(1) - __asm fmul ystepdenominv ; r_lstepy | r_lstepx - __asm fxch st(1) ; r_lstepx | r_lstepy - __asm fistp dword ptr [r_tstepx] - __asm fistp dword ptr [r_tstepy] - - /* - t0 = r_p0[5] - r_p2[5]; - t1 = r_p1[5] - r_p2[5]; - r_zistepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_zistepy = (int)((t1 * p00_minus_p20 - t0 * p10_minus_p20) * - ystepdenominv); - */ - __asm mov eax, dword ptr [r_p0+20] - __asm mov ebx, dword ptr [r_p1+20] - __asm sub eax, dword ptr [r_p2+20] - __asm sub ebx, dword ptr [r_p2+20] - - __asm mov t0_int, eax - __asm mov t1_int, ebx - __asm fild t0_int ; t0 - __asm fild t1_int ; t1 | t0 - __asm fxch st(1) ; t0 | t1 - __asm fstp t0 ; t1 - __asm fst t1 ; (empty) - - __asm fmul p01_minus_p21 ; t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p01_minus_p21 - __asm fmul p11_minus_p21 ; t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t1 ; t1 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p00_minus_p20 ; t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fld t0 ; t0 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fmul p10_minus_p20 ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t0 * p11_minus_p21 | t1 * p01_minus_p21 - __asm fxch st(2) ; t0 * p11_minus_p21 | t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - __asm fsubp st(3), st ; t0 * p10_minus_p20 | t1 * p00_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fsubrp st(1), st ; t1 * p00_minus_p20 - t0 * p10_minus_p20 | t1 * p01_minus_p21 - t0 * p11_minus_p21 - __asm fxch st(1) ; t1 * p01_minus_p21 - t0 * p11_minus_p21 | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fmul xstepdenominv ; r_lstepx | t1 * p00_minus_p20 - t0 * p10_minus_p20 - __asm fxch st(1) - __asm fmul ystepdenominv ; r_lstepy | r_lstepx - __asm fxch st(1) ; r_lstepx | r_lstepy - __asm fistp dword ptr [r_zistepx] - __asm fistp dword ptr [r_zistepy] - - /* -#if id386ALIAS - a_sstepxfrac = r_sstepx << 16; - a_tstepxfrac = r_tstepx << 16; -#else - a_sstepxfrac = r_sstepx & 0xFFFF; - a_tstepxfrac = r_tstepx & 0xFFFF; -#endif - */ - __asm mov eax, d_pdrawspans - __asm cmp eax, offset R_PolysetDrawSpans8_Opaque - __asm mov eax, r_sstepx - __asm mov ebx, r_tstepx - __asm jne translucent -//#if id386ALIAS - __asm shl eax, 16 - __asm shl ebx, 16 - __asm jmp done_with_steps -//#else -translucent: - __asm and eax, 0ffffh - __asm and ebx, 0ffffh -//#endif -done_with_steps: - __asm mov a_sstepxfrac, eax - __asm mov a_tstepxfrac, ebx - - /* - a_ststepxwhole = skinwidth * (r_tstepx >> 16) + (r_sstepx >> 16); - */ - __asm mov ebx, r_tstepx - __asm mov ecx, r_sstepx - __asm sar ebx, 16 - __asm mov eax, skinwidth - __asm mul ebx - __asm sar ecx, 16 - __asm add eax, ecx - __asm mov a_ststepxwhole, eax -} -#else -qboolean R_PolysetCalcGradients (int skinwidth) -{ - float xstepdenominv, ystepdenominv, t0, t1; - float p01_minus_p21, p11_minus_p21, p00_minus_p20, p10_minus_p20; + float xstepdenominv, ystepdenominv, t0, t1; + float p01_minus_p21, p11_minus_p21, p00_minus_p20, p10_minus_p20; p00_minus_p20 = r_p0[0] - r_p2[0]; p01_minus_p21 = r_p0[1] - r_p2[1]; @@ -729,7 +396,7 @@ qboolean R_PolysetCalcGradients (int skinwidth) printf("%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]); printf("%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]); printf("%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]); -*/ + */ xstepdenominv = 1.0f / (float)d_xdenom; ystepdenominv = -xstepdenominv; @@ -740,74 +407,41 @@ qboolean R_PolysetCalcGradients (int skinwidth) t0 = r_p0[4] - r_p2[4]; t1 = r_p1[4] - r_p2[4]; r_lstepx = (int) - ceil((t1 * p01_minus_p21 - t0 * p11_minus_p21) * xstepdenominv); + ceil(( t1 * p01_minus_p21 - t0 * p11_minus_p21 ) * xstepdenominv ); r_lstepy = (int) - ceil((t1 * p00_minus_p20 - t0 * p10_minus_p20) * ystepdenominv); + ceil(( t1 * p00_minus_p20 - t0 * p10_minus_p20 ) * ystepdenominv ); t0 = r_p0[2] - r_p2[2]; t1 = r_p1[2] - r_p2[2]; - r_sstepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_sstepy = (int)((t1 * p00_minus_p20 - t0* p10_minus_p20) * - ystepdenominv); + r_sstepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 ) + * xstepdenominv ); + r_sstepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 ) + * ystepdenominv ); t0 = r_p0[3] - r_p2[3]; t1 = r_p1[3] - r_p2[3]; - r_tstepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_tstepy = (int)((t1 * p00_minus_p20 - t0 * p10_minus_p20) * - ystepdenominv); + r_tstepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 ) + * xstepdenominv ); + r_tstepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 ) + * ystepdenominv ); t0 = r_p0[5] - r_p2[5]; t1 = r_p1[5] - r_p2[5]; - r_zistepx = (int)((t1 * p01_minus_p21 - t0 * p11_minus_p21) * - xstepdenominv); - r_zistepy = (int)((t1 * p00_minus_p20 - t0 * p10_minus_p20) * - ystepdenominv); + r_zistepx = (int)(( t1 * p01_minus_p21 - t0 * p11_minus_p21 ) + * xstepdenominv ); + r_zistepy = (int)(( t1 * p00_minus_p20 - t0 * p10_minus_p20 ) + * ystepdenominv ); - /*if( r_zistepx > INT_MAX / 2 ) - return false; - if( r_zistepx < INT_MIN / 2 ) - return false; - if( r_zistepy > INT_MAX / 2 ) - return false; - if( r_zistepy < INT_MIN / 2 ) - return false;*/ - - -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) { - a_sstepxfrac = r_sstepx << 16; - a_tstepxfrac = r_tstepx << 16; - } - else -#endif - { -//#else a_sstepxfrac = r_sstepx & 0xFFFF; a_tstepxfrac = r_tstepx & 0xFFFF; } -//#endif - // do not allow big steps to make 512 byte extra bounds enough (still f**ng not) - /*if( r_sstepx <= -65535*8 ) - return false; - if( r_tstepx <= -65535*8) - return false; - if( r_sstepx >= 65535*8 ) - return false; - if( r_tstepx >= 65535*8 ) - return false;*/ + a_ststepxwhole = skinwidth * ( r_tstepx >> 16 ) + ( r_sstepx >> 16 ); - a_ststepxwhole = skinwidth * (r_tstepx >> 16) + (r_sstepx >> 16); - -// printf("%d %d %d %d\n",a_ststepxwhole, r_sstepx, r_tstepx, skinwidth ); - skinend = (pixel_t*)r_affinetridesc.pskin + r_affinetridesc.skinwidth * r_affinetridesc.skinheight; + skinend = (pixel_t *)r_affinetridesc.pskin + r_affinetridesc.skinwidth * r_affinetridesc.skinheight; return true; } -#endif /* @@ -815,22 +449,22 @@ qboolean R_PolysetCalcGradients (int skinwidth) R_PolysetDrawSpans8 ================ */ -void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage) +void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage ) { - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; do { lcount = d_aspancount - pspanpackage->count; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -840,7 +474,7 @@ void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { lpdest = pspanpackage->pdest; lptex = pspanpackage->ptex; @@ -851,33 +485,26 @@ void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage) lzi = pspanpackage->zi; pspanpackage++; #if BOUNDCHECK_MODE == 0 - if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ) ) + if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount )) continue; #endif do { - if ((lzi >> 16) >= *lpz) + if(( lzi >> 16 ) >= *lpz ) { -#if 0 - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } -#endif #if BOUNDCHECK_MODE == 1 - if( !R_DrawCheckBounds( lptex ) ) + if( !R_DrawCheckBounds( lptex )) return; #endif - pixel_t temp = *lptex;//vid.colormap[*lptex + ( llight & 0xFF00 )]; - int alpha = vid.alpha; - temp = BLEND_COLOR(temp, vid.color); + pixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )]; + int alpha = vid.alpha; + temp = BLEND_COLOR( temp, vid.color ); if( alpha == 7 ) *lpdest = temp; - else if(alpha) - *lpdest = BLEND_ALPHA(alpha,temp,*lpdest);//vid.alphamap[temp+ *lpdest*256]; + else if( alpha ) + *lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256]; } lpdest++; lzi += r_zistepx; @@ -888,15 +515,18 @@ void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); + } + while( --lcount ); } - else pspanpackage++; - } while (pspanpackage->count != -999999); + else + pspanpackage++; + } + while( pspanpackage->count != -999999 ); } @@ -905,22 +535,22 @@ void R_PolysetDrawSpansBlended( spanpackage_t *pspanpackage) R_PolysetDrawSpans8 ================ */ -void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage) +void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage ) { - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; do { lcount = d_aspancount - pspanpackage->count; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -930,7 +560,7 @@ void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { lpdest = pspanpackage->pdest; lptex = pspanpackage->ptex; @@ -941,29 +571,23 @@ void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage) lzi = pspanpackage->zi; pspanpackage++; #if BOUNDCHECK_MODE == 0 - if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ) ) + if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount )) continue; #endif do { - if ((lzi >> 16) >= *lpz) + if(( lzi >> 16 ) >= *lpz ) { #if BOUNDCHECK_MODE == 1 - if( !R_DrawCheckBounds( lptex ) ) + if( !R_DrawCheckBounds( lptex )) return; #endif -#if 0 - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } -#endif - pixel_t temp = *lptex;//vid.colormap[*lptex + ( llight & 0xFF00 )]; - temp = BLEND_COLOR(temp, vid.color); - *lpdest = BLEND_ADD(temp,*lpdest); + pixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )]; + temp = BLEND_COLOR( temp, vid.color ); + + *lpdest = BLEND_ADD( temp, *lpdest ); } lpdest++; @@ -975,15 +599,18 @@ void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); + } + while( --lcount ); } - else pspanpackage++; - } while (pspanpackage->count != -999999); + else + pspanpackage++; + } + while( pspanpackage->count != -999999 ); } @@ -992,22 +619,22 @@ void R_PolysetDrawSpansAdditive( spanpackage_t *pspanpackage) R_PolysetDrawSpans8 ================ */ -void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage) +void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage ) { - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; do { lcount = d_aspancount - pspanpackage->count; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -1017,7 +644,7 @@ void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { lpdest = pspanpackage->pdest; lptex = pspanpackage->ptex; @@ -1028,28 +655,21 @@ void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage) lzi = pspanpackage->zi; pspanpackage++; #if BOUNDCHECK_MODE == 0 - if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ) ) + if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount )) continue; #endif do { - //if ((lzi >> 16) >= *lpz) + // if ((lzi >> 16) >= *lpz) { #if BOUNDCHECK_MODE == 1 - if( !R_DrawCheckBounds( lptex ) ) + if( !R_DrawCheckBounds( lptex )) return; #endif -#if 0 - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } -#endif - pixel_t temp = *lptex;//vid.colormap[*lptex + ( llight & 0xFF00 )]; - temp = BLEND_COLOR(temp, vid.color); + pixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )]; + temp = BLEND_COLOR( temp, vid.color ); - *lpdest = BLEND_ADD(temp,*lpdest); + *lpdest = BLEND_ADD( temp, *lpdest ); } lpdest++; @@ -1061,16 +681,18 @@ void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); + } + while( --lcount ); } else pspanpackage++; - } while (pspanpackage->count != -999999); + } + while( pspanpackage->count != -999999 ); } @@ -1079,22 +701,22 @@ void R_PolysetDrawSpansGlow( spanpackage_t *pspanpackage) R_PolysetDrawSpans8 ================ */ -void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage) +void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage ) { - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; do { lcount = d_aspancount - pspanpackage->count; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -1104,7 +726,7 @@ void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { lpdest = pspanpackage->pdest; @@ -1116,33 +738,26 @@ void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage) lzi = pspanpackage->zi; pspanpackage++; #if BOUNDCHECK_MODE == 0 - if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ) ) + if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount )) continue; #endif do { - if ((lzi >> 16) >= *lpz) + if(( lzi >> 16 ) >= *lpz ) { #if BOUNDCHECK_MODE == 1 - if( !R_DrawCheckBounds( lptex ) ) + if( !R_DrawCheckBounds( lptex )) return; #endif -#if 0 - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } -#endif - pixel_t temp = *lptex;//vid.colormap[*lptex + ( llight & 0xFF00 )]; + pixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )]; - int alpha = temp >> 13; + int alpha = temp >> 13; temp = temp << 3; - temp = BLEND_COLOR(temp, vid.color); + temp = BLEND_COLOR( temp, vid.color ); if( alpha == 7 ) *lpdest = temp; - else if(alpha) - *lpdest = BLEND_ALPHA(alpha,temp,*lpdest);//vid.alphamap[temp+ *lpdest*256]; + else if( alpha ) + *lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256]; } lpdest++; lzi += r_zistepx; @@ -1153,16 +768,18 @@ void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); + } + while( --lcount ); } else pspanpackage++; - } while (pspanpackage->count != -999999); + } + while( pspanpackage->count != -999999 ); } @@ -1172,22 +789,22 @@ void R_PolysetDrawSpansTextureBlended( spanpackage_t *pspanpackage) R_PolysetDrawSpans8 ================ */ -void R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage) +void R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage ) { - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; do { lcount = d_aspancount - pspanpackage->count; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -1197,7 +814,7 @@ void R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { lpdest = pspanpackage->pdest; lptex = pspanpackage->ptex; @@ -1209,22 +826,15 @@ void R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage) do { - if ((lzi >> 16) >= *lpz) + if(( lzi >> 16 ) >= *lpz ) { -#if 0 - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } -#endif - pixel_t temp = *lptex;//vid.colormap[*lptex + ( llight & 0xFF00 )]; + pixel_t temp = *lptex; // vid.colormap[*lptex + ( llight & 0xFF00 )]; - int alpha = tr.blend * 7; + int alpha = tr.blend * 7; if( alpha == 7 ) *lpdest = temp; - else if(alpha) - *lpdest = BLEND_ALPHA(alpha,temp,*lpdest);//vid.alphamap[temp+ *lpdest*256]; + else if( alpha ) + *lpdest = BLEND_ALPHA( alpha, temp, *lpdest ); // vid.alphamap[temp+ *lpdest*256]; } lpdest++; lzi += r_zistepx; @@ -1235,287 +845,257 @@ void R_PolysetDrawSpans8_33( spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); - } - - pspanpackage++; - } while (pspanpackage->count != -999999); -} - -void R_PolysetDrawSpansConstant8_33( spanpackage_t *pspanpackage) -{ - int lcount; - pixel_t *lpdest; - int lzi; - short *lpz; - - do - { - lcount = d_aspancount - pspanpackage->count; - - errorterm += erroradjustup; - if (errorterm >= 0) - { - d_aspancount += d_countextrastep; - errorterm -= erroradjustdown; - } - else - { - d_aspancount += ubasestep; - } - - if (lcount) - { - lpdest = pspanpackage->pdest; - lpz = pspanpackage->pz; - lzi = pspanpackage->zi; - - do - { - if ((lzi >> 16) >= *lpz) - { - *lpdest = BLEND_ALPHA(2,r_aliasblendcolor,*lpdest);//vid.alphamap[r_aliasblendcolor + *lpdest*256]; - } - lpdest++; - lzi += r_zistepx; - lpz++; - } while (--lcount); - } - - pspanpackage++; - } while (pspanpackage->count != -999999); -} - -void R_PolysetDrawSpans8_66(spanpackage_t *pspanpackage) -{ - int lcount; - pixel_t *lpdest; - pixel_t *lptex; - int lsfrac, ltfrac; - int llight; - int lzi; - short *lpz; - - do - { - lcount = d_aspancount - pspanpackage->count; - - errorterm += erroradjustup; - if (errorterm >= 0) - { - d_aspancount += d_countextrastep; - errorterm -= erroradjustdown; - } - else - { - d_aspancount += ubasestep; - } - - if (lcount) - { - lpdest = pspanpackage->pdest; - lptex = pspanpackage->ptex; - lpz = pspanpackage->pz; - lsfrac = pspanpackage->sfrac; - ltfrac = pspanpackage->tfrac; - llight = pspanpackage->light; - lzi = pspanpackage->zi; - - do - { - if ((lzi >> 16) >= *lpz) - { - int temp = vid.colormap[*lptex + ( llight & 0xFF00 )]; - - *lpdest = BLEND_ALPHA(5,temp,*lpdest);//vid.alphamap[temp*256 + *lpdest]; - *lpz = lzi >> 16; - } - lpdest++; - lzi += r_zistepx; - lpz++; - llight += r_lstepx; - lptex += a_ststepxwhole; - lsfrac += a_sstepxfrac; - lptex += lsfrac >> 16; - lsfrac &= 0xFFFF; - ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) - { - lptex += r_affinetridesc.skinwidth; - ltfrac &= 0xFFFF; - } - } while (--lcount); - } - - pspanpackage++; - } while (pspanpackage->count != -999999); -} - -void R_PolysetDrawSpansConstant8_66( spanpackage_t *pspanpackage) -{ - int lcount; - pixel_t *lpdest; - int lzi; - short *lpz; - - do - { - lcount = d_aspancount - pspanpackage->count; - - errorterm += erroradjustup; - if (errorterm >= 0) - { - d_aspancount += d_countextrastep; - errorterm -= erroradjustdown; - } - else - { - d_aspancount += ubasestep; - } - - if (lcount) - { - lpdest = pspanpackage->pdest; - lpz = pspanpackage->pz; - lzi = pspanpackage->zi; - - do - { - if ((lzi >> 16) >= *lpz) - { - *lpdest = BLEND_ALPHA(5,r_aliasblendcolor,*lpdest);//vid.alphamap[r_aliasblendcolor*256 + *lpdest]; - } - lpdest++; - lzi += r_zistepx; - lpz++; - } while (--lcount); - } - - pspanpackage++; - } while (pspanpackage->count != -999999); -} - -#if !id386 -void R_PolysetDrawSpans8_Opaque (spanpackage_t *pspanpackage) -{ - int lcount; - - do - { - lcount = d_aspancount - pspanpackage->count; - - errorterm += erroradjustup; - if (errorterm >= 0) - { - d_aspancount += d_countextrastep; - errorterm -= erroradjustdown; - } - else - { - d_aspancount += ubasestep; - } - - if (lcount) - { - int lsfrac, ltfrac; - pixel_t *lpdest; - pixel_t *lptex; - int llight; - int lzi; - short *lpz; - - lpdest = pspanpackage->pdest; - lptex = pspanpackage->ptex; - lpz = pspanpackage->pz; - lsfrac = pspanpackage->sfrac; - ltfrac = pspanpackage->tfrac; - llight = pspanpackage->light; - lzi = pspanpackage->zi; - - do - { - if ((lzi >> 16) >= *lpz) - { -//PGM - /*if(r_newrefdef.rdflags & RDF_IRGOGGLES && RI.currententity->flags & RF_IR_VISIBLE) - *lpdest = ((byte *)vid.colormap)[irtable[*lptex]]; - else*/ - *lpdest = ((byte *)vid.colormap)[*lptex + (llight & 0xFF00)]; -//PGM - *lpz = lzi >> 16; - } - lpdest++; - lzi += r_zistepx; - lpz++; - llight += r_lstepx; - lptex += a_ststepxwhole; - lsfrac += a_sstepxfrac; - lptex += lsfrac >> 16; - lsfrac &= 0xFFFF; - ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) - { - lptex += r_affinetridesc.skinwidth; - ltfrac &= 0xFFFF; - } - } while (--lcount); - } - - pspanpackage++; - } while (pspanpackage->count != -999999); -} -#endif - -#if 0 -/* -================ -R_PolysetFillSpans8 -================ -*/ -void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) -{ - int color; - -// FIXME: do z buffering - - color = d_aflatcolor++; - - while (1) - { - int lcount; - byte *lpdest; - - lcount = pspanpackage->count; - - if (lcount == -1) - return; - - if (lcount) - { - lpdest = pspanpackage->pdest; - - do - { - *lpdest++ = color; - } while (--lcount); + } + while( --lcount ); } pspanpackage++; } + while( pspanpackage->count != -999999 ); } -#else -void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) + +void R_PolysetDrawSpansConstant8_33( spanpackage_t *pspanpackage ) { - //int color; - int lcount; + int lcount; + pixel_t *lpdest; + int lzi; + short *lpz; + + do + { + lcount = d_aspancount - pspanpackage->count; + + errorterm += erroradjustup; + if( errorterm >= 0 ) + { + d_aspancount += d_countextrastep; + errorterm -= erroradjustdown; + } + else + { + d_aspancount += ubasestep; + } + + if( lcount ) + { + lpdest = pspanpackage->pdest; + lpz = pspanpackage->pz; + lzi = pspanpackage->zi; + + do + { + if(( lzi >> 16 ) >= *lpz ) + { + *lpdest = BLEND_ALPHA( 2, r_aliasblendcolor, *lpdest ); // vid.alphamap[r_aliasblendcolor + *lpdest*256]; + } + lpdest++; + lzi += r_zistepx; + lpz++; + } + while( --lcount ); + } + + pspanpackage++; + } + while( pspanpackage->count != -999999 ); +} + +void R_PolysetDrawSpans8_66( spanpackage_t *pspanpackage ) +{ + int lcount; + pixel_t *lpdest; + pixel_t *lptex; + int lsfrac, ltfrac; + int llight; + int lzi; + short *lpz; + + do + { + lcount = d_aspancount - pspanpackage->count; + + errorterm += erroradjustup; + if( errorterm >= 0 ) + { + d_aspancount += d_countextrastep; + errorterm -= erroradjustdown; + } + else + { + d_aspancount += ubasestep; + } + + if( lcount ) + { + lpdest = pspanpackage->pdest; + lptex = pspanpackage->ptex; + lpz = pspanpackage->pz; + lsfrac = pspanpackage->sfrac; + ltfrac = pspanpackage->tfrac; + llight = pspanpackage->light; + lzi = pspanpackage->zi; + + do + { + if(( lzi >> 16 ) >= *lpz ) + { + int temp = vid.colormap[*lptex + ( llight & 0xFF00 )]; + + *lpdest = BLEND_ALPHA( 5, temp, *lpdest ); // vid.alphamap[temp*256 + *lpdest]; + *lpz = lzi >> 16; + } + lpdest++; + lzi += r_zistepx; + lpz++; + llight += r_lstepx; + lptex += a_ststepxwhole; + lsfrac += a_sstepxfrac; + lptex += lsfrac >> 16; + lsfrac &= 0xFFFF; + ltfrac += a_tstepxfrac; + if( ltfrac & 0x10000 ) + { + lptex += r_affinetridesc.skinwidth; + ltfrac &= 0xFFFF; + } + } + while( --lcount ); + } + + pspanpackage++; + } + while( pspanpackage->count != -999999 ); +} + +static void R_PolysetDrawSpansConstant8_66( spanpackage_t *pspanpackage ) +{ + int lcount; + pixel_t *lpdest; + int lzi; + short *lpz; + + do + { + lcount = d_aspancount - pspanpackage->count; + + errorterm += erroradjustup; + if( errorterm >= 0 ) + { + d_aspancount += d_countextrastep; + errorterm -= erroradjustdown; + } + else + { + d_aspancount += ubasestep; + } + + if( lcount ) + { + lpdest = pspanpackage->pdest; + lpz = pspanpackage->pz; + lzi = pspanpackage->zi; + + do + { + if(( lzi >> 16 ) >= *lpz ) + { + *lpdest = BLEND_ALPHA( 5, r_aliasblendcolor, *lpdest ); // vid.alphamap[r_aliasblendcolor*256 + *lpdest]; + } + lpdest++; + lzi += r_zistepx; + lpz++; + } + while( --lcount ); + } + + pspanpackage++; + } + while( pspanpackage->count != -999999 ); +} + +void R_PolysetDrawSpans8_Opaque( spanpackage_t *pspanpackage ) +{ + int lcount; + + do + { + lcount = d_aspancount - pspanpackage->count; + + errorterm += erroradjustup; + if( errorterm >= 0 ) + { + d_aspancount += d_countextrastep; + errorterm -= erroradjustdown; + } + else + { + d_aspancount += ubasestep; + } + + if( lcount ) + { + int lsfrac, ltfrac; + pixel_t *lpdest; + pixel_t *lptex; + int llight; + int lzi; + short *lpz; + + lpdest = pspanpackage->pdest; + lptex = pspanpackage->ptex; + lpz = pspanpackage->pz; + lsfrac = pspanpackage->sfrac; + ltfrac = pspanpackage->tfrac; + llight = pspanpackage->light; + lzi = pspanpackage->zi; + + do + { + if(( lzi >> 16 ) >= *lpz ) + { +// PGM + /*if(r_newrefdef.rdflags & RDF_IRGOGGLES && RI.currententity->flags & RF_IR_VISIBLE) + *lpdest = ((byte *)vid.colormap)[irtable[*lptex]]; + else*/ + *lpdest = ((byte *)vid.colormap )[*lptex + ( llight & 0xFF00 )]; +// PGM + *lpz = lzi >> 16; + } + lpdest++; + lzi += r_zistepx; + lpz++; + llight += r_lstepx; + lptex += a_ststepxwhole; + lsfrac += a_sstepxfrac; + lptex += lsfrac >> 16; + lsfrac &= 0xFFFF; + ltfrac += a_tstepxfrac; + if( ltfrac & 0x10000 ) + { + lptex += r_affinetridesc.skinwidth; + ltfrac &= 0xFFFF; + } + } + while( --lcount ); + } + + pspanpackage++; + } + while( pspanpackage->count != -999999 ); +} + +void R_PolysetFillSpans8( spanpackage_t *pspanpackage ) +{ + // int color; + int lcount; // FIXME: do z buffering - //color = d_aflatcolor++ * 10; + // color = d_aflatcolor++ * 10; do { @@ -1523,7 +1103,7 @@ void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) // d_ptex + a_ststepxwhole * lcount + ((a_sstepxfrac * lcount) >> 16) + ((a_tstepxfrac * lcount) >> 16)*r_affinetridesc.skinwidth; errorterm += erroradjustup; - if (errorterm >= 0) + if( errorterm >= 0 ) { d_aspancount += d_countextrastep; errorterm -= erroradjustdown; @@ -1533,14 +1113,14 @@ void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) d_aspancount += ubasestep; } - if (lcount) + if( lcount ) { - int lsfrac, ltfrac; - pixel_t *lpdest; - pixel_t *lptex; - int llight; - int lzi; - short *lpz; + int lsfrac, ltfrac; + pixel_t *lpdest; + pixel_t *lptex; + int llight; + int lzi; + short *lpz; lpdest = pspanpackage->pdest; @@ -1550,40 +1130,22 @@ void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) ltfrac = pspanpackage->tfrac; llight = pspanpackage->light; lzi = pspanpackage->zi; - pspanpackage ++; + pspanpackage++; #if BOUNDCHECK_MODE == 0 - if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount ) ) + if( !R_PolysetCheckBounds( lptex, lsfrac, ltfrac, lcount )) continue; #endif do { - if ((lzi >> 16) >= *lpz) + if(( lzi >> 16 ) >= *lpz ) { #if BOUNDCHECK_MODE == 1 - if( !R_DrawCheckBounds( lptex ) ) + if( !R_DrawCheckBounds( lptex )) return; -#endif -//PGM - /*if(r_newrefdef.rdflags & RDF_IRGOGGLES && RI.currententity->flags & RF_IR_VISIBLE) - *lpdest = ((byte *)vid.colormap)[irtable[*lptex]]; - else*/ - //*lpdest = *lptex; //((byte *)vid.colormap)[*lptex + (llight & 0xFF00)]; -#if 0 // check for texture bounds to make asan happy - if((int)(lptex - (pixel_t*)r_affinetridesc.pskin) > r_affinetridesc.skinwidth * r_affinetridesc.skinheight || (int)(lptex - (pixel_t*)r_affinetridesc.pskin) < 0 ) - { - printf("%d %d %d %d\n",(int)(lptex - (pixel_t*)r_affinetridesc.pskin), r_affinetridesc.skinwidth * r_affinetridesc.skinheight, lsfrac, a_ststepxwhole ); - return; - } #endif pixel_t src = *lptex; - //*lpdest = //vid.colormap[src & 0xff00|(llight>>8)] << 8 | (src & llight & 0xff) | ((src & 0xff) >> 3); - // very dirty, maybe need dual colormap? - //*lpdest = (vid.colormap[src >> 8 | (llight & 0xFF00)] << 8) | src & 0xff; - // 13 bit lighting, 32 light levels - *lpdest = vid.colormap[(src >> 3) | ((llight & 0x1F00) << 5)] | (src & 7); - - //PGM + *lpdest = vid.colormap[( src >> 3 ) | (( llight & 0x1F00 ) << 5 )] | ( src & 7 ); *lpz = lzi >> 16; } lpdest++; @@ -1595,27 +1157,30 @@ void R_PolysetFillSpans8 (spanpackage_t *pspanpackage) lptex += lsfrac >> 16; lsfrac &= 0xFFFF; ltfrac += a_tstepxfrac; - if (ltfrac & 0x10000) + if( ltfrac & 0x10000 ) { lptex += r_affinetridesc.skinwidth; ltfrac &= 0xFFFF; } - } while (--lcount); + } + while( --lcount ); } - else pspanpackage++; - } while (pspanpackage->count != -999999); + else + pspanpackage++; + } + while( pspanpackage->count != -999999 ); } -#endif + /* ================ R_RasterizeAliasPolySmooth ================ */ -void R_RasterizeAliasPolySmooth (void) +void R_RasterizeAliasPolySmooth( void ) { - int initialleftheight, initialrightheight; - int *plefttop, *prighttop, *pleftbottom, *prightbottom; - int working_lstepx, originalcount; + int initialleftheight, initialrightheight; + int *plefttop, *prighttop, *pleftbottom, *prightbottom; + int working_lstepx, originalcount; plefttop = pedgetable->pleftedgevert0; prighttop = pedgetable->prightedgevert0; @@ -1630,7 +1195,7 @@ void R_RasterizeAliasPolySmooth (void) // set the s, t, and light gradients, which are consistent across the triangle // because being a triangle, things are affine // - if( !R_PolysetCalcGradients (r_affinetridesc.skinwidth) ) + if( !R_PolysetCalcGradients( r_affinetridesc.skinwidth )) return; // // rasterize the polygon @@ -1644,32 +1209,21 @@ void R_RasterizeAliasPolySmooth (void) ystart = plefttop[1]; d_aspancount = plefttop[0] - prighttop[0]; - d_ptex = (pixel_t*)r_affinetridesc.pskin + (plefttop[2] >> 16) + - (plefttop[3] >> 16) * r_affinetridesc.skinwidth; + d_ptex = (pixel_t *)r_affinetridesc.pskin + ( plefttop[2] >> 16 ) + + ( plefttop[3] >> 16 ) * r_affinetridesc.skinwidth; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) - { - d_sfrac = (plefttop[2] & 0xFFFF) << 16; - d_tfrac = (plefttop[3] & 0xFFFF) << 16; - } -//#else - else -#endif { d_sfrac = plefttop[2] & 0xFFFF; d_tfrac = plefttop[3] & 0xFFFF; } -//#endif d_light = plefttop[4]; d_zi = plefttop[5]; - d_pdest = (pixel_t *)d_viewbuffer + - ystart * r_screenwidth + plefttop[0]; + d_pdest = (pixel_t *)d_viewbuffer + + ystart * r_screenwidth + plefttop[0]; d_pz = d_pzbuffer + ystart * d_zwidth + plefttop[0]; - if (initialleftheight == 1) + if( initialleftheight == 1 ) { d_pedgespanpackage->pdest = d_pdest; @@ -1680,97 +1234,57 @@ void R_RasterizeAliasPolySmooth (void) d_pedgespanpackage->sfrac = d_sfrac; d_pedgespanpackage->tfrac = d_tfrac; - // FIXME: need to clamp l, s, t, at both ends? + // FIXME: need to clamp l, s, t, at both ends? d_pedgespanpackage->light = d_light; d_pedgespanpackage->zi = d_zi; d_pedgespanpackage++; } else { - R_PolysetSetUpForLineScan(plefttop[0], plefttop[1], - pleftbottom[0], pleftbottom[1]); + R_PolysetSetUpForLineScan( plefttop[0], plefttop[1], + pleftbottom[0], pleftbottom[1] ); -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) - { - d_pzbasestep = (d_zwidth + ubasestep) << 1; - d_pzextrastep = d_pzbasestep + 2; - } -//#else - else -#endif { d_pzbasestep = d_zwidth + ubasestep; d_pzextrastep = d_pzbasestep + 1; } -//#endif d_pdestbasestep = r_screenwidth + ubasestep; d_pdestextrastep = d_pdestbasestep + 1; - // TODO: can reuse partial expressions here + // TODO: can reuse partial expressions here - // for negative steps in x along left edge, bias toward overflow rather than - // underflow (sort of turning the floor () we did in the gradient calcs into - // ceil (), but plus a little bit) - if (ubasestep < 0) + // for negative steps in x along left edge, bias toward overflow rather than + // underflow (sort of turning the floor () we did in the gradient calcs into + // ceil (), but plus a little bit) + if( ubasestep < 0 ) working_lstepx = r_lstepx - 1; else working_lstepx = r_lstepx; d_countextrastep = ubasestep + 1; - d_ptexbasestep = ((r_sstepy + r_sstepx * ubasestep) >> 16) + - ((r_tstepy + r_tstepx * ubasestep) >> 16) * - r_affinetridesc.skinwidth; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) + d_ptexbasestep = (( r_sstepy + r_sstepx * ubasestep ) >> 16 ) + + (( r_tstepy + r_tstepx * ubasestep ) >> 16 ) + * r_affinetridesc.skinwidth; { - d_sfracbasestep = (r_sstepy + r_sstepx * ubasestep) << 16; - d_tfracbasestep = (r_tstepy + r_tstepx * ubasestep) << 16; + d_sfracbasestep = ( r_sstepy + r_sstepx * ubasestep ) & 0xFFFF; + d_tfracbasestep = ( r_tstepy + r_tstepx * ubasestep ) & 0xFFFF; } - else -#endif - { -//#else - d_sfracbasestep = (r_sstepy + r_sstepx * ubasestep) & 0xFFFF; - d_tfracbasestep = (r_tstepy + r_tstepx * ubasestep) & 0xFFFF; - } -//#endif d_lightbasestep = r_lstepy + working_lstepx * ubasestep; d_zibasestep = r_zistepy + r_zistepx * ubasestep; - d_ptexextrastep = ((r_sstepy + r_sstepx * d_countextrastep) >> 16) + - ((r_tstepy + r_tstepx * d_countextrastep) >> 16) * - r_affinetridesc.skinwidth; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) + d_ptexextrastep = (( r_sstepy + r_sstepx * d_countextrastep ) >> 16 ) + + (( r_tstepy + r_tstepx * d_countextrastep ) >> 16 ) + * r_affinetridesc.skinwidth; { - d_sfracextrastep = (r_sstepy + r_sstepx*d_countextrastep) << 16; - d_tfracextrastep = (r_tstepy + r_tstepx*d_countextrastep) << 16; + d_sfracextrastep = ( r_sstepy + r_sstepx * d_countextrastep ) & 0xFFFF; + d_tfracextrastep = ( r_tstepy + r_tstepx * d_countextrastep ) & 0xFFFF; } - else -#endif - { -//#else - d_sfracextrastep = (r_sstepy + r_sstepx*d_countextrastep) & 0xFFFF; - d_tfracextrastep = (r_tstepy + r_tstepx*d_countextrastep) & 0xFFFF; - } -//#endif d_lightextrastep = d_lightbasestep + working_lstepx; d_ziextrastep = d_zibasestep + r_zistepx; -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) { - R_PolysetScanLeftEdge (initialleftheight); - } - else -#endif - { - if(!R_PolysetScanLeftEdge_C(initialleftheight)) + if( !R_PolysetScanLeftEdge_C( initialleftheight )) return; } } @@ -1778,9 +1292,9 @@ void R_RasterizeAliasPolySmooth (void) // // scan out the bottom part of the left edge, if it exists // - if (pedgetable->numleftedges == 2) + if( pedgetable->numleftedges == 2 ) { - int height; + int height; plefttop = pleftbottom; pleftbottom = pedgetable->pleftedgevert2; @@ -1791,8 +1305,8 @@ void R_RasterizeAliasPolySmooth (void) ystart = plefttop[1]; d_aspancount = plefttop[0] - prighttop[0]; - d_ptex = (pixel_t*)r_affinetridesc.pskin + (plefttop[2] >> 16) + - (plefttop[3] >> 16) * r_affinetridesc.skinwidth; + d_ptex = (pixel_t *)r_affinetridesc.pskin + ( plefttop[2] >> 16 ) + + ( plefttop[3] >> 16 ) * r_affinetridesc.skinwidth; d_sfrac = 0; d_tfrac = 0; @@ -1804,7 +1318,7 @@ void R_RasterizeAliasPolySmooth (void) - if (height == 1) + if( height == 1 ) { d_pedgespanpackage->pdest = d_pdest; d_pedgespanpackage->pz = d_pz; @@ -1814,92 +1328,52 @@ void R_RasterizeAliasPolySmooth (void) d_pedgespanpackage->sfrac = d_sfrac; d_pedgespanpackage->tfrac = d_tfrac; - // FIXME: need to clamp l, s, t, at both ends? + // FIXME: need to clamp l, s, t, at both ends? d_pedgespanpackage->light = d_light; d_pedgespanpackage->zi = d_zi; d_pedgespanpackage++; } else { - R_PolysetSetUpForLineScan(plefttop[0], plefttop[1], - pleftbottom[0], pleftbottom[1]); + R_PolysetSetUpForLineScan( plefttop[0], plefttop[1], + pleftbottom[0], pleftbottom[1] ); d_pdestbasestep = r_screenwidth + ubasestep; d_pdestextrastep = d_pdestbasestep + 1; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) - { - d_pzbasestep = (d_zwidth + ubasestep) << 1; - d_pzextrastep = d_pzbasestep + 2; - } -//#else - else -#endif { d_pzbasestep = d_zwidth + ubasestep; d_pzextrastep = d_pzbasestep + 1; } -//#endif - if (ubasestep < 0) + if( ubasestep < 0 ) working_lstepx = r_lstepx - 1; else working_lstepx = r_lstepx; d_countextrastep = ubasestep + 1; - d_ptexbasestep = ((r_sstepy + r_sstepx * ubasestep) >> 16) + - ((r_tstepy + r_tstepx * ubasestep) >> 16) * - r_affinetridesc.skinwidth; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) + d_ptexbasestep = (( r_sstepy + r_sstepx * ubasestep ) >> 16 ) + + (( r_tstepy + r_tstepx * ubasestep ) >> 16 ) + * r_affinetridesc.skinwidth; { - d_sfracbasestep = (r_sstepy + r_sstepx * ubasestep) << 16; - d_tfracbasestep = (r_tstepy + r_tstepx * ubasestep) << 16; + d_sfracbasestep = ( r_sstepy + r_sstepx * ubasestep ) & 0xFFFF; + d_tfracbasestep = ( r_tstepy + r_tstepx * ubasestep ) & 0xFFFF; } -//#else - else -#endif - { - d_sfracbasestep = (r_sstepy + r_sstepx * ubasestep) & 0xFFFF; - d_tfracbasestep = (r_tstepy + r_tstepx * ubasestep) & 0xFFFF; - } -//#endif d_lightbasestep = r_lstepy + working_lstepx * ubasestep; d_zibasestep = r_zistepy + r_zistepx * ubasestep; - d_ptexextrastep = ((r_sstepy + r_sstepx * d_countextrastep) >> 16) + - ((r_tstepy + r_tstepx * d_countextrastep) >> 16) * - r_affinetridesc.skinwidth; -//#if id386ALIAS -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) + d_ptexextrastep = (( r_sstepy + r_sstepx * d_countextrastep ) >> 16 ) + + (( r_tstepy + r_tstepx * d_countextrastep ) >> 16 ) + * r_affinetridesc.skinwidth; { - d_sfracextrastep = ((r_sstepy+r_sstepx*d_countextrastep) & 0xFFFF)<<16; - d_tfracextrastep = ((r_tstepy+r_tstepx*d_countextrastep) & 0xFFFF)<<16; + d_sfracextrastep = ( r_sstepy + r_sstepx * d_countextrastep ) & 0xFFFF; + d_tfracextrastep = ( r_tstepy + r_tstepx * d_countextrastep ) & 0xFFFF; } - else -#endif -//#endif - { - d_sfracextrastep = (r_sstepy+r_sstepx*d_countextrastep) & 0xFFFF; - d_tfracextrastep = (r_tstepy+r_tstepx*d_countextrastep) & 0xFFFF; - } -//#endif d_lightextrastep = d_lightbasestep + working_lstepx; d_ziextrastep = d_zibasestep + r_zistepx; -#if id386 - if ( d_pdrawspans == R_PolysetDrawSpans8_Opaque ) { - R_PolysetScanLeftEdge (height); - } - else -#endif - { - if(!R_PolysetScanLeftEdge_C(height)) + if( !R_PolysetScanLeftEdge_C( height )) return; } } @@ -1909,20 +1383,20 @@ void R_RasterizeAliasPolySmooth (void) // count field d_pedgespanpackage = a_spans; - R_PolysetSetUpForLineScan(prighttop[0], prighttop[1], - prightbottom[0], prightbottom[1]); + R_PolysetSetUpForLineScan( prighttop[0], prighttop[1], + prightbottom[0], prightbottom[1] ); d_aspancount = 0; d_countextrastep = ubasestep + 1; originalcount = a_spans[initialrightheight].count; a_spans[initialrightheight].count = -999999; // mark end of the spanpackages - (*d_pdrawspans) (a_spans); + ( *d_pdrawspans )( a_spans ); // scan out the bottom part of the right edge, if it exists - if (pedgetable->numrightedges == 2) + if( pedgetable->numrightedges == 2 ) { - int height; - spanpackage_t *pstart; + int height; + spanpackage_t *pstart; pstart = a_spans + initialrightheight; pstart->count = originalcount; @@ -1934,13 +1408,13 @@ void R_RasterizeAliasPolySmooth (void) height = prightbottom[1] - prighttop[1]; - R_PolysetSetUpForLineScan(prighttop[0], prighttop[1], - prightbottom[0], prightbottom[1]); + R_PolysetSetUpForLineScan( prighttop[0], prighttop[1], + prightbottom[0], prightbottom[1] ); d_countextrastep = ubasestep + 1; a_spans[initialrightheight + height].count = -999999; // mark end of the spanpackages - (*d_pdrawspans) (pstart); + ( *d_pdrawspans )( pstart ); } } @@ -1950,22 +1424,22 @@ void R_RasterizeAliasPolySmooth (void) R_PolysetSetEdgeTable ================ */ -void R_PolysetSetEdgeTable (void) +void R_PolysetSetEdgeTable( void ) { - int edgetableindex; + int edgetableindex; - edgetableindex = 0; // assume the vertices are already in - // top to bottom order + edgetableindex = 0; // assume the vertices are already in + // top to bottom order // // determine which edges are right & left, and the order in which // to rasterize them // - if (r_p0[1] >= r_p1[1]) + if( r_p0[1] >= r_p1[1] ) { - if (r_p0[1] == r_p1[1]) + if( r_p0[1] == r_p1[1] ) { - if (r_p0[1] < r_p2[1]) + if( r_p0[1] < r_p2[1] ) pedgetable = &edgetables[2]; else pedgetable = &edgetables[5]; @@ -1978,18 +1452,18 @@ void R_PolysetSetEdgeTable (void) } } - if (r_p0[1] == r_p2[1]) + if( r_p0[1] == r_p2[1] ) { - if (edgetableindex) + if( edgetableindex ) pedgetable = &edgetables[8]; else pedgetable = &edgetables[9]; return; } - else if (r_p1[1] == r_p2[1]) + else if( r_p1[1] == r_p2[1] ) { - if (edgetableindex) + if( edgetableindex ) pedgetable = &edgetables[10]; else pedgetable = &edgetables[11]; @@ -1997,10 +1471,10 @@ void R_PolysetSetEdgeTable (void) return; } - if (r_p0[1] > r_p2[1]) + if( r_p0[1] > r_p2[1] ) edgetableindex += 2; - if (r_p1[1] > r_p2[1]) + if( r_p1[1] > r_p2[1] ) edgetableindex += 4; pedgetable = &edgetables[edgetableindex]; diff --git a/ref/soft/r_rast.c b/ref/soft/r_rast.c index 023f326e..3dff5d94 100644 --- a/ref/soft/r_rast.c +++ b/ref/soft/r_rast.c @@ -23,210 +23,103 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" -#define MAXLEFTCLIPEDGES 100 +#define MAXLEFTCLIPEDGES 100 // !!! if these are changed, they must be changed in asm_draw.h too !!! -#define FULLY_CLIPPED_CACHED 0x80000000 -#define FRAMECOUNT_MASK 0x7FFFFFFF +#define FULLY_CLIPPED_CACHED 0x80000000 +#define FRAMECOUNT_MASK 0x7FFFFFFF -unsigned int cacheoffset; +unsigned int cacheoffset; -int c_faceclip; // number of faces clipped +int c_faceclip; // number of faces clipped -clipplane_t *entity_clipplanes; -clipplane_t world_clipplanes[16]; +clipplane_t *entity_clipplanes; +clipplane_t world_clipplanes[16]; -medge_t *r_pedge; +medge16_t *r_pedge; -qboolean r_leftclipped, r_rightclipped; -static qboolean makeleftedge, makerightedge; -qboolean r_nearzionly; +qboolean r_leftclipped, r_rightclipped; +static qboolean makeleftedge, makerightedge; +qboolean r_nearzionly; -int sintable[1280]; -int intsintable[1280]; -int blanktable[1280]; // PGM +int sintable[1280]; +int intsintable[1280]; +int blanktable[1280]; // PGM -mvertex_t r_leftenter, r_leftexit; -mvertex_t r_rightenter, r_rightexit; +mvertex_t r_leftenter, r_leftexit; +mvertex_t r_rightenter, r_rightexit; typedef struct { - float u,v; - int ceilv; + float u, v; + int ceilv; } evert_t; -int r_emitted; -float r_nearzi; -float r_u1, r_v1, r_lzi1; -int r_ceilv1; +int r_emitted; +float r_nearzi; +float r_u1, r_v1, r_lzi1; +int r_ceilv1; -qboolean r_lastvertvalid; -int r_skyframe; +qboolean r_lastvertvalid; +int r_skyframe; -msurface_t *r_skyfaces; -mplane_t r_skyplanes[6]; -mtexinfo_t r_skytexinfo[6]; -mvertex_t *r_skyverts; -medge_t *r_skyedges; -int *r_skysurfedges; +msurface_t *r_skyfaces; +mplane_t r_skyplanes[6]; +mtexinfo_t r_skytexinfo[6]; +mvertex_t *r_skyverts; +medge16_t *r_skyedges; +int *r_skysurfedges; // I just copied this data from a box map... -int skybox_planes[12] = {2,-128, 0,-128, 2,128, 1,128, 0,128, 1,-128}; +int skybox_planes[12] = {2, -128, 0, -128, 2, 128, 1, 128, 0, 128, 1, -128}; -int box_surfedges[24] = { 1,2,3,4, -1,5,6,7, 8,9,-6,10, -2,-7,-9,11, - 12,-3,-11,-8, -12,-10,-5,-4}; -int 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}; +int box_surfedges[24] = { 1, 2, 3, 4, -1, 5, 6, 7, 8, 9, -6, 10, -2, -7, -9, 11, + 12, -3, -11, -8, -12, -10, -5, -4}; +int 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}; -int box_faces[6] = {0,0,2,2,2,0}; +int box_faces[6] = {0, 0, 2, 2, 2, 0}; -vec3_t box_vecs[6][2] = { - { {0,-1,0}, {-1,0,0} }, - { {0,1,0}, {0,0,-1} }, - { {0,-1,0}, {1,0,0} }, - { {1,0,0}, {0,0,-1} }, - { {0,-1,0}, {0,0,-1} }, - { {-1,0,0}, {0,0,-1} } +vec3_t box_vecs[6][2] = { + { {0, -1, 0}, {-1, 0, 0} }, + { {0, 1, 0}, {0, 0, -1} }, + { {0, -1, 0}, {1, 0, 0} }, + { {1, 0, 0}, {0, 0, -1} }, + { {0, -1, 0}, {0, 0, -1} }, + { {-1, 0, 0}, {0, 0, -1} } }; -float box_verts[8][3] = { - {-1,-1,-1}, - {-1,1,-1}, - {1,1,-1}, - {1,-1,-1}, - {-1,-1,1}, - {-1,1,1}, - {1,-1,1}, - {1,1,1} +float box_verts[8][3] = { + {-1, -1, -1}, + {-1, 1, -1}, + {1, 1, -1}, + {1, -1, -1}, + {-1, -1, 1}, + {-1, 1, 1}, + {1, -1, 1}, + {1, 1, 1} }; // down, west, up, north, east, south // {"rt", "bk", "lf", "ft", "up", "dn"}; -#if 0 -/* -================ -R_InitSkyBox - -================ -*/ -void R_InitSkyBox (void) -{ - int i; - extern model_t *loadmodel; - - r_skyfaces = loadmodel->surfaces + loadmodel->numsurfaces; - loadmodel->numsurfaces += 6; - r_skyverts = loadmodel->vertexes + loadmodel->numvertexes; - loadmodel->numvertexes += 8; - r_skyedges = loadmodel->edges + loadmodel->numedges; - loadmodel->numedges += 12; - r_skysurfedges = loadmodel->surfedges + loadmodel->numsurfedges; - loadmodel->numsurfedges += 24; - if (loadmodel->numsurfaces > MAX_MAP_FACES - || loadmodel->numvertexes > MAX_MAP_VERTS - || loadmodel->numedges > MAX_MAP_EDGES) - ri.Sys_Error (ERR_DROP, "InitSkyBox: map overflow"); - - memset (r_skyfaces, 0, 6*sizeof(*r_skyfaces)); - for (i=0 ; i<6 ; i++) - { - r_skyplanes[i].normal[skybox_planes[i*2]] = 1; - r_skyplanes[i].dist = skybox_planes[i*2+1]; - - VectorCopy (box_vecs[i][0], r_skytexinfo[i].vecs[0]); - VectorCopy (box_vecs[i][1], r_skytexinfo[i].vecs[1]); - - r_skyfaces[i].plane = &r_skyplanes[i]; - r_skyfaces[i].numedges = 4; - r_skyfaces[i].flags = box_faces[i] | SURF_DRAWSKYBOX; - r_skyfaces[i].firstedge = loadmodel->numsurfedges-24+i*4; - r_skyfaces[i].texinfo = &r_skytexinfo[i]; - r_skyfaces[i].texturemins[0] = -128; - r_skyfaces[i].texturemins[1] = -128; - r_skyfaces[i].extents[0] = 256; - r_skyfaces[i].extents[1] = 256; - } - - for (i=0 ; i<24 ; i++) - if (box_surfedges[i] > 0) - r_skysurfedges[i] = loadmodel->numedges-13 + box_surfedges[i]; - else - r_skysurfedges[i] = - (loadmodel->numedges-13 + -box_surfedges[i]); - - for(i=0 ; i<12 ; i++) - { - r_skyedges[i].v[0] = loadmodel->numvertexes-9+box_edges[i*2+0]; - r_skyedges[i].v[1] = loadmodel->numvertexes-9+box_edges[i*2+1]; - r_skyedges[i].cachededgeoffset = 0; - } -} - -/* -================ -R_EmitSkyBox -================ -*/ -void R_EmitSkyBox (void) -{ - int i, j; - int oldkey; - - if (insubmodel) - return; // submodels should never have skies - if (r_skyframe == r_framecount) - return; // already set this frame - - r_skyframe = r_framecount; - - // set the eight fake vertexes - for (i=0 ; i<8 ; i++) - for (j=0 ; j<3 ; j++) - r_skyverts[i].position[j] = r_origin[j] + box_verts[i][j]*128; - - // set the six fake planes - for (i=0 ; i<6 ; i++) - if (skybox_planes[i*2+1] > 0) - r_skyplanes[i].dist = r_origin[skybox_planes[i*2]]+128; - else - r_skyplanes[i].dist = r_origin[skybox_planes[i*2]]-128; - - // fix texture offseets - for (i=0 ; i<6 ; i++) - { - r_skytexinfo[i].vecs[0][3] = -DotProduct (r_origin, r_skytexinfo[i].vecs[0]); - r_skytexinfo[i].vecs[1][3] = -DotProduct (r_origin, r_skytexinfo[i].vecs[1]); - } - - // emit the six faces - oldkey = r_currentkey; - r_currentkey = 0x7ffffff0; - for (i=0 ; i<6 ; i++) - { - R_RenderFace (r_skyfaces + i, 15); - } - r_currentkey = oldkey; // bsp sorting order -} - -#endif -#if !id386 /* ================ R_EmitEdge ================ */ -void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) +static void R_EmitEdge( mvertex_t *pv0, mvertex_t *pv1 ) { - edge_t *edge, *pcheck; - int u_check; - float u, u_step; - vec3_t local, transformed; - float *world; - int v, v2, ceilv0; - float scale, lzi0, u0, v0; - int side; + edge_t *edge, *pcheck; + int u_check; + float u, u_step; + vec3_t local, transformed; + float *world; + int v, v2, ceilv0; + float scale, lzi0, u0, v0; + int side; - if (r_lastvertvalid) + if( r_lastvertvalid ) { u0 = r_u1; v0 = r_v1; @@ -237,84 +130,84 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) { world = &pv0->position[0]; - // transform and project - VectorSubtract (world, tr.modelorg, local); - TransformVector (local, transformed); + // transform and project + VectorSubtract( world, tr.modelorg, local ); + TransformVector( local, transformed ); - if (transformed[2] < NEAR_CLIP) + if( transformed[2] < NEAR_CLIP ) transformed[2] = NEAR_CLIP; lzi0 = 1.0f / transformed[2]; - // FIXME: build x/yscale into transform? + // FIXME: build x/yscale into transform? scale = xscale * lzi0; - u0 = (xcenter + scale*transformed[0]); - if (u0 < RI.fvrectx_adj) + u0 = ( xcenter + scale * transformed[0] ); + if( u0 < RI.fvrectx_adj ) u0 = RI.fvrectx_adj; - if (u0 > RI.fvrectright_adj) + if( u0 > RI.fvrectright_adj ) u0 = RI.fvrectright_adj; scale = yscale * lzi0; - v0 = (ycenter - scale*transformed[1]); - if (v0 < RI.fvrecty_adj) + v0 = ( ycenter - scale * transformed[1] ); + if( v0 < RI.fvrecty_adj ) v0 = RI.fvrecty_adj; - if (v0 > RI.fvrectbottom_adj) + if( v0 > RI.fvrectbottom_adj ) v0 = RI.fvrectbottom_adj; - ceilv0 = (int) ceil(v0); + ceilv0 = (int) ceil( v0 ); } world = &pv1->position[0]; // transform and project - VectorSubtract (world, tr.modelorg, local); - TransformVector (local, transformed); + VectorSubtract( world, tr.modelorg, local ); + TransformVector( local, transformed ); - if (transformed[2] < NEAR_CLIP) + if( transformed[2] < NEAR_CLIP ) transformed[2] = NEAR_CLIP; r_lzi1 = 1.0f / transformed[2]; scale = xscale * r_lzi1; - r_u1 = (xcenter + scale*transformed[0]); - if (r_u1 < RI.fvrectx_adj) + r_u1 = ( xcenter + scale * transformed[0] ); + if( r_u1 < RI.fvrectx_adj ) r_u1 = RI.fvrectx_adj; - if (r_u1 > RI.fvrectright_adj) + if( r_u1 > RI.fvrectright_adj ) r_u1 = RI.fvrectright_adj; scale = yscale * r_lzi1; - r_v1 = (ycenter - scale*transformed[1]); - if (r_v1 < RI.fvrecty_adj) + r_v1 = ( ycenter - scale * transformed[1] ); + if( r_v1 < RI.fvrecty_adj ) r_v1 = RI.fvrecty_adj; - if (r_v1 > RI.fvrectbottom_adj) + if( r_v1 > RI.fvrectbottom_adj ) r_v1 = RI.fvrectbottom_adj; - if (r_lzi1 > lzi0) + if( r_lzi1 > lzi0 ) lzi0 = r_lzi1; - if (lzi0 > r_nearzi) // for mipmap finding + if( lzi0 > r_nearzi ) // for mipmap finding r_nearzi = lzi0; // for right edges, all we want is the effect on 1/z - if (r_nearzionly) + if( r_nearzionly ) return; r_emitted = 1; - r_ceilv1 = (int) ceil(r_v1); + r_ceilv1 = (int) ceil( r_v1 ); // create the edge - if (ceilv0 == r_ceilv1 || ceilv0 < 0 ) + if( ceilv0 == r_ceilv1 || ceilv0 < 0 ) { - // we cache unclipped horizontal edges as fully clipped - if (cacheoffset != 0x7FFFFFFF) + // we cache unclipped horizontal edges as fully clipped + if( cacheoffset != 0x7FFFFFFF ) { - cacheoffset = FULLY_CLIPPED_CACHED | - (tr.framecount & FRAMECOUNT_MASK); + cacheoffset = FULLY_CLIPPED_CACHED + | ( tr.framecount & FRAMECOUNT_MASK ); } - return; // horizontal edge + return; // horizontal edge } side = ceilv0 > r_ceilv1; @@ -325,13 +218,13 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) edge->nearzi = lzi0; - if (side == 0) + if( side == 0 ) { - // trailing edge (go from p1 to p2) + // trailing edge (go from p1 to p2) v = ceilv0; v2 = r_ceilv1 - 1; - if( v < 0 || v > MAXHEIGHT ) + if( v < 0 || v >= MAXHEIGHT ) { gEngfuncs.Con_Printf( S_ERROR "trailing edge overflow : %d\n", v ); return; @@ -340,16 +233,16 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) edge->surfs[0] = surface_p - surfaces; edge->surfs[1] = 0; - u_step = ((r_u1 - u0) / (r_v1 - v0)); - u = u0 + ((float)v - v0) * u_step; + u_step = (( r_u1 - u0 ) / ( r_v1 - v0 )); + u = u0 + ((float)v - v0 ) * u_step; } else { - // leading edge (go from p2 to p1) + // leading edge (go from p2 to p1) v2 = ceilv0 - 1; v = r_ceilv1; - if( v < 0 || v > MAXHEIGHT ) + if( v < 0 || v >= MAXHEIGHT ) { gEngfuncs.Con_Printf( S_ERROR "leading edge overflow : %d\n", v ); return; @@ -358,12 +251,12 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) edge->surfs[0] = 0; edge->surfs[1] = surface_p - surfaces; - u_step = ((u0 - r_u1) / (v0 - r_v1)); - u = r_u1 + ((float)v - r_v1) * u_step; + u_step = (( u0 - r_u1 ) / ( v0 - r_v1 )); + u = r_u1 + ((float)v - r_v1 ) * u_step; } - edge->u_step = u_step*0x100000; - edge->u = u*0x100000 + 0xFFFFF; + edge->u_step = u_step * 0x100000; + edge->u = u * 0x100000 + 0xFFFFF; // we need to do this to avoid stepping off the edges if a very nearly // horizontal edge is less than epsilon above a scan, and numeric error causes @@ -376,9 +269,9 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) edge->u = x; if (edge->u > r) edge->u = r;*/ - if (edge->u < RI.vrect_x_adj_shift20) + if( edge->u < RI.vrect_x_adj_shift20 ) edge->u = RI.vrect_x_adj_shift20; - if (edge->u > RI.vrectright_adj_shift20) + if( edge->u > RI.vrectright_adj_shift20 ) edge->u = RI.vrectright_adj_shift20; @@ -386,10 +279,10 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) // sort the edge in normally // u_check = edge->u; - if (edge->surfs[0]) - u_check++; // sort trailers after leaders + if( edge->surfs[0] ) + u_check++; // sort trailers after leaders - if (!newedges[v] || newedges[v]->u >= u_check) + if( !newedges[v] || newedges[v]->u >= u_check ) { edge->next = newedges[v]; newedges[v] = edge; @@ -397,7 +290,7 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) else { pcheck = newedges[v]; - while (pcheck->next && pcheck->next->u < u_check) + while( pcheck->next && pcheck->next->u < u_check ) pcheck = pcheck->next; edge->next = pcheck->next; pcheck->next = edge; @@ -413,122 +306,120 @@ void R_EmitEdge (mvertex_t *pv0, mvertex_t *pv1) R_ClipEdge ================ */ -void R_ClipEdge (mvertex_t *pv0, mvertex_t *pv1, clipplane_t *clip) +static void R_ClipEdge( mvertex_t *pv0, mvertex_t *pv1, clipplane_t *clip ) { - float d0, d1, f; - mvertex_t clipvert; + float d0, d1, f; + mvertex_t clipvert; - if (clip) + if( clip ) { do { - d0 = DotProduct (pv0->position, clip->normal) - clip->dist; - d1 = DotProduct (pv1->position, clip->normal) - clip->dist; + d0 = DotProduct( pv0->position, clip->normal ) - clip->dist; + d1 = DotProduct( pv1->position, clip->normal ) - clip->dist; - if (d0 >= 0) + if( d0 >= 0 ) { - // point 0 is unclipped - if (d1 >= 0) + // point 0 is unclipped + if( d1 >= 0 ) { - // both points are unclipped + // both points are unclipped continue; } - // only point 1 is clipped + // only point 1 is clipped - // we don't cache clipped edges + // we don't cache clipped edges cacheoffset = 0x7FFFFFFF; - f = d0 / (d0 - d1); - clipvert.position[0] = pv0->position[0] + - f * (pv1->position[0] - pv0->position[0]); - clipvert.position[1] = pv0->position[1] + - f * (pv1->position[1] - pv0->position[1]); - clipvert.position[2] = pv0->position[2] + - f * (pv1->position[2] - pv0->position[2]); + f = d0 / ( d0 - d1 ); + clipvert.position[0] = pv0->position[0] + + f * ( pv1->position[0] - pv0->position[0] ); + clipvert.position[1] = pv0->position[1] + + f * ( pv1->position[1] - pv0->position[1] ); + clipvert.position[2] = pv0->position[2] + + f * ( pv1->position[2] - pv0->position[2] ); - if (clip->leftedge) + if( clip->leftedge ) { r_leftclipped = true; r_leftexit = clipvert; } - else if (clip->rightedge) + else if( clip->rightedge ) { r_rightclipped = true; r_rightexit = clipvert; } - R_ClipEdge (pv0, &clipvert, clip->next); + R_ClipEdge( pv0, &clipvert, clip->next ); return; } else { - // point 0 is clipped - if (d1 < 0) + // point 0 is clipped + if( d1 < 0 ) { - // both points are clipped - // we do cache fully clipped edges - if (!r_leftclipped) - cacheoffset = FULLY_CLIPPED_CACHED | - (tr.framecount & FRAMECOUNT_MASK); + // both points are clipped + // we do cache fully clipped edges + if( !r_leftclipped ) + cacheoffset = FULLY_CLIPPED_CACHED + | ( tr.framecount & FRAMECOUNT_MASK ); return; } - // only point 0 is clipped + // only point 0 is clipped r_lastvertvalid = false; - // we don't cache partially clipped edges + // we don't cache partially clipped edges cacheoffset = 0x7FFFFFFF; - f = d0 / (d0 - d1); - clipvert.position[0] = pv0->position[0] + - f * (pv1->position[0] - pv0->position[0]); - clipvert.position[1] = pv0->position[1] + - f * (pv1->position[1] - pv0->position[1]); - clipvert.position[2] = pv0->position[2] + - f * (pv1->position[2] - pv0->position[2]); + f = d0 / ( d0 - d1 ); + clipvert.position[0] = pv0->position[0] + + f * ( pv1->position[0] - pv0->position[0] ); + clipvert.position[1] = pv0->position[1] + + f * ( pv1->position[1] - pv0->position[1] ); + clipvert.position[2] = pv0->position[2] + + f * ( pv1->position[2] - pv0->position[2] ); - if (clip->leftedge) + if( clip->leftedge ) { r_leftclipped = true; r_leftenter = clipvert; } - else if (clip->rightedge) + else if( clip->rightedge ) { r_rightclipped = true; r_rightenter = clipvert; } - R_ClipEdge (&clipvert, pv1, clip->next); + R_ClipEdge( &clipvert, pv1, clip->next ); return; } - } while ((clip = clip->next) != NULL); + } + while(( clip = clip->next ) != NULL ); } // add the edge - R_EmitEdge (pv0, pv1); + R_EmitEdge( pv0, pv1 ); } -#endif // !id386 - - /* ================ R_EmitCachedEdge ================ */ -void R_EmitCachedEdge (void) +static void R_EmitCachedEdge( void ) { - edge_t *pedge_t; + edge_t *pedge_t; - pedge_t = (edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset); + pedge_t = (edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset ); - if (!pedge_t->surfs[0]) + if( !pedge_t->surfs[0] ) pedge_t->surfs[0] = surface_p - surfaces; else pedge_t->surfs[1] = surface_p - surfaces; - if (pedge_t->nearzi > r_nearzi) // for mipmap finding + if( pedge_t->nearzi > r_nearzi )// for mipmap finding r_nearzi = pedge_t->nearzi; r_emitted = 1; @@ -540,43 +431,43 @@ void R_EmitCachedEdge (void) R_RenderFace ================ */ -void R_RenderFace (msurface_t *fa, int clipflags) +void R_RenderFace( msurface_t *fa, int clipflags ) { - int i, lindex; - unsigned mask; - mplane_t *pplane; - float distinv; - vec3_t p_normal; - medge_t *pedges, tedge; - clipplane_t *pclip; + int i, lindex; + unsigned mask; + mplane_t *pplane; + float distinv; + vec3_t p_normal; + medge16_t *pedges, tedge; + clipplane_t *pclip; // translucent surfaces are not drawn by the edge renderer - if (fa->flags & (SURF_DRAWTURB|SURF_TRANSPARENT)) + if( fa->flags & ( SURF_DRAWTURB | SURF_TRANSPARENT )) { - //fa->nextalphasurface = r_alpha_surfaces; - //r_alpha_surfaces = fa; - //return; + // fa->nextalphasurface = r_alpha_surfaces; + // r_alpha_surfaces = fa; + // return; } // sky surfaces encountered in the world will cause the // environment box surfaces to be emited - if ( fa->flags & SURF_DRAWSKY ) + if( fa->flags & SURF_DRAWSKY ) { - //R_EmitSkyBox (); - // return; + // R_EmitSkyBox (); + // return; } // skip out if no more surfs - if ((surface_p) >= surf_max) + if(( surface_p ) >= surf_max ) { - // r_outofsurfaces++; + // r_outofsurfaces++; return; } // ditto if not enough edges left, or switch to auxedges if possible - if ((edge_p + fa->numedges + 4) >= edge_max) + if(( edge_p + fa->numedges + 4 ) >= edge_max ) { - //r_outofedges += fa->numedges; + // r_outofedges += fa->numedges; return; } @@ -585,9 +476,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) // set up clip planes pclip = NULL; - for (i=3, mask = 0x08 ; i>=0 ; i--, mask >>= 1) + for( i = 3, mask = 0x08; i >= 0; i--, mask >>= 1 ) { - if (clipflags & mask) + if( clipflags & mask ) { qfrustum.view_clipplanes[i].next = pclip; pclip = &qfrustum.view_clipplanes[i]; @@ -599,24 +490,24 @@ void R_RenderFace (msurface_t *fa, int clipflags) r_nearzi = 0; r_nearzionly = false; makeleftedge = makerightedge = false; - pedges = RI.currentmodel->edges; + pedges = RI.currentmodel->edges16; r_lastvertvalid = false; - for (i=0 ; inumedges ; i++) + for( i = 0; i < fa->numedges; i++ ) { lindex = RI.currentmodel->surfedges[fa->firstedge + i]; - if (lindex > 0) + if( lindex > 0 ) { r_pedge = &pedges[lindex]; - // if the edge is cached, we can just reuse the edge - if (!insubmodel) + // if the edge is cached, we can just reuse the edge + if( !insubmodel ) { - if (r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED) + if( r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED ) { - if ((r_pedge->cachededgeoffset & FRAMECOUNT_MASK) == - tr.framecount) + if(( r_pedge->cachededgeoffset & FRAMECOUNT_MASK ) + == tr.framecount ) { r_lastvertvalid = false; continue; @@ -624,29 +515,29 @@ void R_RenderFace (msurface_t *fa, int clipflags) } else { - if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > - r_pedge->cachededgeoffset) && - (((edge_t *)((uintptr_t)r_edges + - r_pedge->cachededgeoffset))->owner == r_pedge)) + if((((uintptr_t)edge_p - (uintptr_t)r_edges ) + > r_pedge->cachededgeoffset ) + && (((edge_t *)((uintptr_t)r_edges + + r_pedge->cachededgeoffset ))->owner == r_pedge )) { - R_EmitCachedEdge (); + R_EmitCachedEdge(); r_lastvertvalid = false; continue; } } } - // assume it's cacheable + // assume it's cacheable cacheoffset = (byte *)edge_p - (byte *)r_edges; r_leftclipped = r_rightclipped = false; - R_ClipEdge (&r_pcurrentvertbase[r_pedge->v[0]], - &r_pcurrentvertbase[r_pedge->v[1]], - pclip); + R_ClipEdge( &r_pcurrentvertbase[r_pedge->v[0]], + &r_pcurrentvertbase[r_pedge->v[1]], + pclip ); r_pedge->cachededgeoffset = cacheoffset; - if (r_leftclipped) + if( r_leftclipped ) makeleftedge = true; - if (r_rightclipped) + if( r_rightclipped ) makerightedge = true; r_lastvertvalid = true; } @@ -654,13 +545,13 @@ void R_RenderFace (msurface_t *fa, int clipflags) { lindex = -lindex; r_pedge = &pedges[lindex]; - // if the edge is cached, we can just reuse the edge - if (!insubmodel) + // if the edge is cached, we can just reuse the edge + if( !insubmodel ) { - if (r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED) + if( r_pedge->cachededgeoffset & FULLY_CLIPPED_CACHED ) { - if ((r_pedge->cachededgeoffset & FRAMECOUNT_MASK) == - tr.framecount) + if(( r_pedge->cachededgeoffset & FRAMECOUNT_MASK ) + == tr.framecount ) { r_lastvertvalid = false; continue; @@ -668,31 +559,31 @@ void R_RenderFace (msurface_t *fa, int clipflags) } else { - // it's cached if the cached edge is valid and is owned - // by this medge_t - if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > - r_pedge->cachededgeoffset) && - (((edge_t *)((uintptr_t)r_edges + - r_pedge->cachededgeoffset))->owner == r_pedge)) + // it's cached if the cached edge is valid and is owned + // by this medge16_t + if((((uintptr_t)edge_p - (uintptr_t)r_edges ) + > r_pedge->cachededgeoffset ) + && (((edge_t *)((uintptr_t)r_edges + + r_pedge->cachededgeoffset ))->owner == r_pedge )) { - R_EmitCachedEdge (); + R_EmitCachedEdge(); r_lastvertvalid = false; continue; } } } - // assume it's cacheable + // assume it's cacheable cacheoffset = (byte *)edge_p - (byte *)r_edges; r_leftclipped = r_rightclipped = false; - R_ClipEdge (&r_pcurrentvertbase[r_pedge->v[1]], - &r_pcurrentvertbase[r_pedge->v[0]], - pclip); + R_ClipEdge( &r_pcurrentvertbase[r_pedge->v[1]], + &r_pcurrentvertbase[r_pedge->v[0]], + pclip ); r_pedge->cachededgeoffset = cacheoffset; - if (r_leftclipped) + if( r_leftclipped ) makeleftedge = true; - if (r_rightclipped) + if( r_rightclipped ) makerightedge = true; r_lastvertvalid = true; } @@ -701,24 +592,24 @@ void R_RenderFace (msurface_t *fa, int clipflags) // if there was a clip off the left edge, add that edge too // FIXME: faster to do in screen space? // FIXME: share clipped edges? - if (makeleftedge) + if( makeleftedge ) { r_pedge = &tedge; r_lastvertvalid = false; - R_ClipEdge (&r_leftexit, &r_leftenter, pclip->next); + R_ClipEdge( &r_leftexit, &r_leftenter, pclip->next ); } // if there was a clip off the right edge, get the right r_nearzi - if (makerightedge) + if( makerightedge ) { r_pedge = &tedge; r_lastvertvalid = false; r_nearzionly = true; - R_ClipEdge (&r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next); + R_ClipEdge( &r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next ); } // if no edges made it out, return without posting the surface - if (!r_emitted) + if( !r_emitted ) return; // r_polycount++; @@ -734,15 +625,15 @@ void R_RenderFace (msurface_t *fa, int clipflags) pplane = fa->plane; // FIXME: cache this? - TransformVector (pplane->normal, p_normal); + TransformVector( pplane->normal, p_normal ); // FIXME: cache this? - distinv = 1.0f / (pplane->dist - DotProduct (tr.modelorg, pplane->normal)); + distinv = 1.0f / ( pplane->dist - DotProduct( tr.modelorg, pplane->normal )); surface_p->d_zistepu = p_normal[0] * xscaleinv * distinv; surface_p->d_zistepv = -p_normal[1] * yscaleinv * distinv; - surface_p->d_ziorigin = p_normal[2] * distinv - - xcenter * surface_p->d_zistepu - - ycenter * surface_p->d_zistepv; + surface_p->d_ziorigin = p_normal[2] * distinv + - xcenter * surface_p->d_zistepu + - ycenter * surface_p->d_zistepv; surface_p++; } @@ -753,15 +644,15 @@ void R_RenderFace (msurface_t *fa, int clipflags) R_RenderBmodelFace ================ */ -void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf) +void R_RenderBmodelFace( bedge_t *pedges, msurface_t *psurf ) { - int i; - unsigned mask; - mplane_t *pplane; - float distinv; - vec3_t p_normal; - medge_t tedge; - clipplane_t *pclip; + int i; + unsigned mask; + mplane_t *pplane; + float distinv; + vec3_t p_normal; + medge16_t tedge; + clipplane_t *pclip; /*if (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66)) { @@ -771,16 +662,16 @@ void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf) }*/ // skip out if no more surfs - if (surface_p >= surf_max) + if( surface_p >= surf_max ) { - //r_outofsurfaces++; + // r_outofsurfaces++; return; } // ditto if not enough edges left, or switch to auxedges if possible - if ((edge_p + psurf->numedges + 4) >= edge_max) + if(( edge_p + psurf->numedges + 4 ) >= edge_max ) { - //r_outofedges += psurf->numedges; + // r_outofedges += psurf->numedges; return; } @@ -792,9 +683,9 @@ void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf) // set up clip planes pclip = NULL; - for (i=3, mask = 0x08 ; i>=0 ; i--, mask >>= 1) + for( i = 3, mask = 0x08; i >= 0; i--, mask >>= 1 ) { - if (r_clipflags & mask) + if( r_clipflags & mask ) { qfrustum.view_clipplanes[i].next = pclip; pclip = &qfrustum.view_clipplanes[i]; @@ -810,39 +701,39 @@ void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf) // can be used? r_lastvertvalid = false; - for ( ; pedges ; pedges = pedges->pnext) + for( ; pedges; pedges = pedges->pnext ) { r_leftclipped = r_rightclipped = false; - R_ClipEdge (pedges->v[0], pedges->v[1], pclip); + R_ClipEdge( pedges->v[0], pedges->v[1], pclip ); - if (r_leftclipped) + if( r_leftclipped ) makeleftedge = true; - if (r_rightclipped) + if( r_rightclipped ) makerightedge = true; } // if there was a clip off the left edge, add that edge too // FIXME: faster to do in screen space? // FIXME: share clipped edges? - if (makeleftedge) + if( makeleftedge ) { r_pedge = &tedge; - R_ClipEdge (&r_leftexit, &r_leftenter, pclip->next); + R_ClipEdge( &r_leftexit, &r_leftenter, pclip->next ); } // if there was a clip off the right edge, get the right r_nearzi - if (makerightedge) + if( makerightedge ) { r_pedge = &tedge; r_nearzionly = true; - R_ClipEdge (&r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next); + R_ClipEdge( &r_rightexit, &r_rightenter, qfrustum.view_clipplanes[1].next ); } // if no edges made it out, return without posting the surface - if (!r_emitted) + if( !r_emitted ) return; - //r_polycount++; + // r_polycount++; surface_p->msurf = psurf; surface_p->nearzi = r_nearzi; @@ -855,15 +746,15 @@ void R_RenderBmodelFace (bedge_t *pedges, msurface_t *psurf) pplane = psurf->plane; // FIXME: cache this? - TransformVector (pplane->normal, p_normal); + TransformVector( pplane->normal, p_normal ); // FIXME: cache this? - distinv = 1.0f / (pplane->dist - DotProduct (tr.modelorg, pplane->normal)); + distinv = 1.0f / ( pplane->dist - DotProduct( tr.modelorg, pplane->normal )); surface_p->d_zistepu = p_normal[0] * xscaleinv * distinv; surface_p->d_zistepv = -p_normal[1] * yscaleinv * distinv; - surface_p->d_ziorigin = p_normal[2] * distinv - - xcenter * surface_p->d_zistepu - - ycenter * surface_p->d_zistepv; + surface_p->d_ziorigin = p_normal[2] * distinv + - xcenter * surface_p->d_zistepu + - ycenter * surface_p->d_zistepv; surface_p++; } diff --git a/ref/soft/r_scan.c b/ref/soft/r_scan.c index 28ed582b..631609ad 100644 --- a/ref/soft/r_scan.c +++ b/ref/soft/r_scan.c @@ -23,150 +23,80 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" -pixel_t *r_turb_pbase, *r_turb_pdest; -short *r_turb_pz; -fixed16_t r_turb_s, r_turb_t, r_turb_sstep, r_turb_tstep; -int r_turb_izistep, r_turb_izi; -int *r_turb_turb; -static int r_turb_spancount; -int alpha; - -void D_DrawTurbulent8Span (void); - - -/* -============= -D_WarpScreen - -this performs a slight compression of the screen at the same time as -the sine warp, to keep the edges from wrapping -============= -*/ -void D_WarpScreen (void) -{ -#if 0 - int w, h; - int u,v, u2, v2; - byte *dest; - int *turb; - int *col; - byte **row; - - static int cached_width, cached_height; - static byte *rowptr[1200+AMP2*2]; - static int column[1600+AMP2*2]; - - // - // these are constant over resolutions, and can be saved - // - w = r_newrefdef.width; - h = r_newrefdef.height; - if (w != cached_width || h != cached_height) - { - cached_width = w; - cached_height = h; - for (v=0 ; v>16)&(CYCLE-1)])>>16)&63; - tturb = ((r_turb_t + r_turb_turb[(r_turb_s>>16)&(CYCLE-1)])>>16)&63; - *r_turb_pdest++ = *(r_turb_pbase + (tturb<<6) + sturb); + sturb = (( r_turb_s + r_turb_turb[( r_turb_t >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63; + tturb = (( r_turb_t + r_turb_turb[( r_turb_s >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63; + *r_turb_pdest++ = *( r_turb_pbase + ( tturb << 6 ) + sturb ); r_turb_s += r_turb_sstep; r_turb_t += r_turb_tstep; - } while (--r_turb_spancount > 0); + } + while( --r_turb_spancount > 0 ); } - /* ============= D_DrawTurbulent8Span ============= */ -void D_DrawTurbulent8ZSpan (void) +static void D_DrawTurbulent8ZSpan( void ) { - int sturb, tturb; + int sturb, tturb; do { - sturb = ((r_turb_s + r_turb_turb[(r_turb_t>>16)&(CYCLE-1)])>>16)&63; - tturb = ((r_turb_t + r_turb_turb[(r_turb_s>>16)&(CYCLE-1)])>>16)&63; - if (*r_turb_pz <= (r_turb_izi >> 16)) + sturb = (( r_turb_s + r_turb_turb[( r_turb_t >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63; + tturb = (( r_turb_t + r_turb_turb[( r_turb_s >> 16 ) & ( CYCLE - 1 )] ) >> 16 ) & 63; + if( *r_turb_pz <= ( r_turb_izi >> 16 )) { - pixel_t btemp = *(r_turb_pbase + (tturb<<6) + sturb); + pixel_t btemp = *( r_turb_pbase + ( tturb << 6 ) + sturb ); if( alpha == 7 ) *r_turb_pdest = btemp; else - *r_turb_pdest = BLEND_ALPHA( alpha, btemp, *r_turb_pdest); + *r_turb_pdest = BLEND_ALPHA( alpha, btemp, *r_turb_pdest ); } r_turb_pdest++; r_turb_pz++; r_turb_izi += r_turb_izistep; r_turb_s += r_turb_sstep; r_turb_t += r_turb_tstep; - } while (--r_turb_spancount > 0); + } + while( --r_turb_spancount > 0 ); } -#endif // !id386 - - /* ============= Turbulent8 ============= */ -void Turbulent8 (espan_t *pspan) +void Turbulent8( espan_t *pspan ) { - int count; - fixed16_t snext, tnext; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz16stepu, tdivz16stepu, zi16stepu; + int count; + fixed16_t snext, tnext; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz16stepu, tdivz16stepu, zi16stepu; - r_turb_turb = sintable + ((int)(gpGlobals->time*SPEED)&(CYCLE-1)); + r_turb_turb = sintable + ((int)( gp_cl->time * SPEED ) & ( CYCLE - 1 )); - r_turb_sstep = 0; // keep compiler happy - r_turb_tstep = 0; // ditto + r_turb_sstep = 0; // keep compiler happy + r_turb_tstep = 0; // ditto r_turb_pbase = cacheblock; @@ -176,125 +106,126 @@ void Turbulent8 (espan_t *pspan) do { - r_turb_pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); + r_turb_pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - r_turb_s = (int)(sdivz * z) + sadjust; - if (r_turb_s > bbextents) + r_turb_s = (int)( sdivz * z ) + sadjust; + if( r_turb_s > bbextents ) r_turb_s = bbextents; - else if (r_turb_s < 0) + else if( r_turb_s < 0 ) r_turb_s = 0; - r_turb_t = (int)(tdivz * z) + tadjust; - if (r_turb_t > bbextentt) + r_turb_t = (int)( tdivz * z ) + tadjust; + if( r_turb_t > bbextentt ) r_turb_t = bbextentt; - else if (r_turb_t < 0) + else if( r_turb_t < 0 ) r_turb_t = 0; do { - // calculate s and t at the far end of the span - if (count >= 16) + // calculate s and t at the far end of the span + if( count >= 16 ) r_turb_spancount = 16; else r_turb_spancount = count; count -= r_turb_spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz16stepu; tdivz += tdivz16stepu; zi += zi16stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - r_turb_sstep = (snext - r_turb_s) >> 4; - r_turb_tstep = (tnext - r_turb_t) >> 4; + r_turb_sstep = ( snext - r_turb_s ) >> 4; + r_turb_tstep = ( tnext - r_turb_t ) >> 4; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(r_turb_spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( r_turb_spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - if (r_turb_spancount > 1) + if( r_turb_spancount > 1 ) { - r_turb_sstep = (snext - r_turb_s) / (r_turb_spancount - 1); - r_turb_tstep = (tnext - r_turb_t) / (r_turb_spancount - 1); + r_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 ); + r_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 ); } } - r_turb_s = r_turb_s & ((CYCLE<<16)-1); - r_turb_t = r_turb_t & ((CYCLE<<16)-1); + r_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 ); + r_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 ); - D_DrawTurbulent8Span (); + D_DrawTurbulent8Span(); r_turb_s = snext; r_turb_t = tnext; - } while (count > 0); + } + while( count > 0 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } - /* ============= Turbulent8 ============= */ -void TurbulentZ8 (espan_t *pspan, int alpha1) +void TurbulentZ8( espan_t *pspan, int alpha1 ) { - int count; - fixed16_t snext, tnext; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz16stepu, tdivz16stepu, zi16stepu; + int count; + fixed16_t snext, tnext; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz16stepu, tdivz16stepu, zi16stepu; alpha = alpha1; if( alpha > 7 ) @@ -302,152 +233,154 @@ void TurbulentZ8 (espan_t *pspan, int alpha1) if( alpha == 0 ) return; - r_turb_turb = sintable + ((int)(gpGlobals->time*SPEED)&(CYCLE-1)); + r_turb_turb = sintable + ((int)( gp_cl->time * SPEED ) & ( CYCLE - 1 )); - r_turb_sstep = 0; // keep compiler happy - r_turb_tstep = 0; // ditto + r_turb_sstep = 0; // keep compiler happy + r_turb_tstep = 0; // ditto r_turb_pbase = cacheblock; sdivz16stepu = d_sdivzstepu * 16; tdivz16stepu = d_tdivzstepu * 16; zi16stepu = d_zistepu * 16; - r_turb_izistep = (int)(d_zistepu * 0x8000 * 0x10000); + r_turb_izistep = (int)( d_zistepu * 0x8000 * 0x10000 ); do { - r_turb_pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); - r_turb_pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u; + r_turb_pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); + r_turb_pz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u; count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - r_turb_izi = (int)(zi * 0x8000 * 0x10000); + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + r_turb_izi = (int)( zi * 0x8000 * 0x10000 ); - r_turb_s = (int)(sdivz * z) + sadjust; - if (r_turb_s > bbextents) + r_turb_s = (int)( sdivz * z ) + sadjust; + if( r_turb_s > bbextents ) r_turb_s = bbextents; - else if (r_turb_s < 0) + else if( r_turb_s < 0 ) r_turb_s = 0; - r_turb_t = (int)(tdivz * z) + tadjust; - if (r_turb_t > bbextentt) + r_turb_t = (int)( tdivz * z ) + tadjust; + if( r_turb_t > bbextentt ) r_turb_t = bbextentt; - else if (r_turb_t < 0) + else if( r_turb_t < 0 ) r_turb_t = 0; do { - // calculate s and t at the far end of the span - if (count >= 16) + // calculate s and t at the far end of the span + if( count >= 16 ) r_turb_spancount = 16; else r_turb_spancount = count; count -= r_turb_spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz16stepu; tdivz += tdivz16stepu; zi += zi16stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - r_turb_sstep = (snext - r_turb_s) >> 4; - r_turb_tstep = (tnext - r_turb_t) >> 4; + r_turb_sstep = ( snext - r_turb_s ) >> 4; + r_turb_tstep = ( tnext - r_turb_t ) >> 4; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(r_turb_spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( r_turb_spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - if (r_turb_spancount > 1) + if( r_turb_spancount > 1 ) { - r_turb_sstep = (snext - r_turb_s) / (r_turb_spancount - 1); - r_turb_tstep = (tnext - r_turb_t) / (r_turb_spancount - 1); + r_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 ); + r_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 ); } } - r_turb_s = r_turb_s & ((CYCLE<<16)-1); - r_turb_t = r_turb_t & ((CYCLE<<16)-1); + r_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 ); + r_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 ); - D_DrawTurbulent8ZSpan (); + D_DrawTurbulent8ZSpan(); r_turb_s = snext; r_turb_t = tnext; - } while (count > 0); + } + while( count > 0 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } -//==================== -//PGM +// ==================== +// PGM /* ============= NonTurbulent8 - this is for drawing scrolling textures. they're warping water textures but the turbulence is automatically 0. ============= */ -void NonTurbulent8 (espan_t *pspan) +void NonTurbulent8( espan_t *pspan ) { - int count; - fixed16_t snext, tnext; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz16stepu, tdivz16stepu, zi16stepu; + int count; + fixed16_t snext, tnext; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz16stepu, tdivz16stepu, zi16stepu; // r_turb_turb = sintable + ((int)(r_newrefdef.time*SPEED)&(CYCLE-1)); r_turb_turb = blanktable; - r_turb_sstep = 0; // keep compiler happy - r_turb_tstep = 0; // ditto + r_turb_sstep = 0; // keep compiler happy + r_turb_tstep = 0; // ditto r_turb_pbase = cacheblock; @@ -457,132 +390,131 @@ void NonTurbulent8 (espan_t *pspan) do { - r_turb_pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); + r_turb_pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - r_turb_s = (int)(sdivz * z) + sadjust; - if (r_turb_s > bbextents) + r_turb_s = (int)( sdivz * z ) + sadjust; + if( r_turb_s > bbextents ) r_turb_s = bbextents; - else if (r_turb_s < 0) + else if( r_turb_s < 0 ) r_turb_s = 0; - r_turb_t = (int)(tdivz * z) + tadjust; - if (r_turb_t > bbextentt) + r_turb_t = (int)( tdivz * z ) + tadjust; + if( r_turb_t > bbextentt ) r_turb_t = bbextentt; - else if (r_turb_t < 0) + else if( r_turb_t < 0 ) r_turb_t = 0; do { - // calculate s and t at the far end of the span - if (count >= 16) + // calculate s and t at the far end of the span + if( count >= 16 ) r_turb_spancount = 16; else r_turb_spancount = count; count -= r_turb_spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz16stepu; tdivz += tdivz16stepu; zi += zi16stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - r_turb_sstep = (snext - r_turb_s) >> 4; - r_turb_tstep = (tnext - r_turb_t) >> 4; + r_turb_sstep = ( snext - r_turb_s ) >> 4; + r_turb_tstep = ( tnext - r_turb_t ) >> 4; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(r_turb_spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( r_turb_spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 16) - snext = 16; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 16 ) + snext = 16; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 16) - tnext = 16; // guard against round-off error on <0 steps + else if( tnext < 16 ) + tnext = 16; // guard against round-off error on <0 steps - if (r_turb_spancount > 1) + if( r_turb_spancount > 1 ) { - r_turb_sstep = (snext - r_turb_s) / (r_turb_spancount - 1); - r_turb_tstep = (tnext - r_turb_t) / (r_turb_spancount - 1); + r_turb_sstep = ( snext - r_turb_s ) / ( r_turb_spancount - 1 ); + r_turb_tstep = ( tnext - r_turb_t ) / ( r_turb_spancount - 1 ); } } - r_turb_s = r_turb_s & ((CYCLE<<16)-1); - r_turb_t = r_turb_t & ((CYCLE<<16)-1); + r_turb_s = r_turb_s & (( CYCLE << 16 ) - 1 ); + r_turb_t = r_turb_t & (( CYCLE << 16 ) - 1 ); - D_DrawTurbulent8Span (); + D_DrawTurbulent8Span(); r_turb_s = snext; r_turb_t = tnext; - } while (count > 0); + } + while( count > 0 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } -//PGM -//==================== - - -#if !id386 +// PGM +// ==================== int kernel[2][2][2] = { - { - {16384,0}, - {49152,32768} - } - , - { - {32768,49152}, - {0,16384} - } + { + {16384, 0}, + {49152, 32768} + } + , + { + {32768, 49152}, + {0, 16384} + } }; #ifndef DISABLE_TEXFILTER -#define SW_TEXFILT (sw_texfilt->value == 1.0f) +#define SW_TEXFILT ( sw_texfilt.value == 1.0f ) #else #define SW_TEXFILT 0 #endif @@ -593,16 +525,16 @@ D_DrawSpans16 FIXME: actually make this subdivide by 16 instead of 8!!! ============= */ -void D_DrawSpans16 (espan_t *pspan) +void D_DrawSpans16( espan_t *pspan ) { - int count, spancount; - pixel_t *pbase, *pdest; - fixed16_t s, t, snext, tnext, sstep, tstep; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz8stepu, tdivz8stepu, zi8stepu; + int count, spancount; + pixel_t *pbase, *pdest; + fixed16_t s, t, snext, tnext, sstep, tstep; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz8stepu, tdivz8stepu, zi8stepu; - sstep = 0; // keep compiler happy - tstep = 0; // ditto + sstep = 0; // keep compiler happy + tstep = 0; // ditto pbase = cacheblock; @@ -612,338 +544,346 @@ void D_DrawSpans16 (espan_t *pspan) do { - pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); + pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - s = (int)(sdivz * z) + sadjust; - if (s > bbextents) + s = (int)( sdivz * z ) + sadjust; + if( s > bbextents ) s = bbextents; - else if (s < 0) + else if( s < 0 ) s = 0; - t = (int)(tdivz * z) + tadjust; - if (t > bbextentt) + t = (int)( tdivz * z ) + tadjust; + if( t > bbextentt ) t = bbextentt; - else if (t < 0) + else if( t < 0 ) t = 0; do { - // calculate s and t at the far end of the span - if (count >= 8) + // calculate s and t at the far end of the span + if( count >= 8 ) spancount = 8; else spancount = count; count -= spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz8stepu; tdivz += tdivz8stepu; zi += zi8stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - sstep = (snext - s) >> 3; - tstep = (tnext - t) >> 3; + sstep = ( snext - s ) >> 3; + tstep = ( tnext - t ) >> 3; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - if (spancount > 1) + if( spancount > 1 ) { - sstep = (snext - s) / (spancount - 1); - tstep = (tnext - t) / (spancount - 1); + sstep = ( snext - s ) / ( spancount - 1 ); + tstep = ( tnext - t ) / ( spancount - 1 ); } - } + } // Drawing phrase - if (!SW_TEXFILT) + if( !SW_TEXFILT ) + { + do { - do - { - *pdest++ = *(pbase + (s >> 16) + (t >> 16) * cachewidth); - s += sstep; - t += tstep; - } while (--spancount > 0); + *pdest++ = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth ); + s += sstep; + t += tstep; } - else + while( --spancount > 0 ); + } + else + { + do { - do + int idiths = s; + int iditht = t; + + int X = ( pspan->u + spancount ) & 1; + int Y = ( pspan->v ) & 1; + + // Using the kernel + idiths += kernel[X][Y][0]; + iditht += kernel[X][Y][1]; + + idiths = idiths >> 16; + idiths = idiths ? idiths - 1 : idiths; + + + iditht = iditht >> 16; + iditht = iditht ? iditht - 1 : iditht; + + + *pdest++ = *( pbase + idiths + iditht * cachewidth ); + s += sstep; + t += tstep; + } + while( --spancount > 0 ); + } + + + } + while( count > 0 ); + + } + while(( pspan = pspan->pnext ) != NULL ); +} + + +/* +============= +D_DrawSpans16 + + FIXME: actually make this subdivide by 16 instead of 8!!! +============= +*/ +void D_AlphaSpans16( espan_t *pspan ) +{ + int count, spancount; + pixel_t *pbase, *pdest; + fixed16_t s, t, snext, tnext, sstep, tstep; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz8stepu, tdivz8stepu, zi8stepu; + int izi, izistep; + short *pz; + + sstep = 0; // keep compiler happy + tstep = 0; // ditto + + pbase = cacheblock; + + sdivz8stepu = d_sdivzstepu * 8; + tdivz8stepu = d_tdivzstepu * 8; + zi8stepu = d_zistepu * 8; + izistep = (int)( d_zistepu * 0x8000 * 0x10000 ); + + do + { + pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); + pz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u; + + count = pspan->count; + + // calculate the initial s/z, t/z, 1/z, s, and t and clamp + du = (float)pspan->u; + dv = (float)pspan->v; + + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + izi = (int)( zi * 0x8000 * 0x10000 ); + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + + s = (int)( sdivz * z ) + sadjust; + if( s > bbextents ) + s = bbextents; + else if( s < 0 ) + s = 0; + + t = (int)( tdivz * z ) + tadjust; + if( t > bbextentt ) + t = bbextentt; + else if( t < 0 ) + t = 0; + + do + { + // calculate s and t at the far end of the span + if( count >= 8 ) + spancount = 8; + else + spancount = count; + + count -= spancount; + + if( count ) + { + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting + sdivz += sdivz8stepu; + tdivz += tdivz8stepu; + zi += zi8stepu; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) + snext = bbextents; + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture + + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) + tnext = bbextentt; + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps + + sstep = ( snext - s ) >> 3; + tstep = ( tnext - t ) >> 3; + } + else + { + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( spancount - 1 ); + sdivz += d_sdivzstepu * spancountminus1; + tdivz += d_tdivzstepu * spancountminus1; + zi += d_zistepu * spancountminus1; + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) + snext = bbextents; + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture + + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) + tnext = bbextentt; + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps + + if( spancount > 1 ) + { + sstep = ( snext - s ) / ( spancount - 1 ); + tstep = ( tnext - t ) / ( spancount - 1 ); + } + } + + + // Drawing phrase + if( !SW_TEXFILT ) + { + do + { + pixel_t btemp; + + btemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth ); + if( btemp != TRANSPARENT_COLOR ) { - int idiths = s; - int iditht = t; + if( *pz <= ( izi >> 16 )) + { + *pdest = btemp; + *pz = izi >> 16; + } + } + pdest++; + pz++; + izi += izistep; + s += sstep; + t += tstep; + } + while( --spancount > 0 ); + } + else + { + do + { + if( *pz <= ( izi >> 16 )) + { + int idiths = s; + int iditht = t; - int X = (pspan->u + spancount) & 1; - int Y = (pspan->v)&1; + int X = ( pspan->u + spancount ) & 1; + int Y = ( pspan->v ) & 1; + pixel_t btemp; - //Using the kernel + // Using the kernel idiths += kernel[X][Y][0]; iditht += kernel[X][Y][1]; idiths = idiths >> 16; - idiths = idiths ? idiths -1 : idiths; + idiths = idiths ? idiths - 1 : idiths; iditht = iditht >> 16; - iditht = iditht ? iditht -1 : iditht; + iditht = iditht ? iditht - 1 : iditht; - *pdest++ = *(pbase + idiths + iditht * cachewidth); - s += sstep; - t += tstep; - } while (--spancount > 0); + btemp = *( pbase + idiths + iditht * cachewidth ); + if( btemp != TRANSPARENT_COLOR ) + { + *pdest = btemp; + *pz = izi >> 16; + } + } + pdest++; + pz++; + s += sstep; + t += tstep; } - - - } while (count > 0); - - } while ((pspan = pspan->pnext) != NULL); -} - - -/* -============= -D_DrawSpans16 - - FIXME: actually make this subdivide by 16 instead of 8!!! -============= -*/ -void D_AlphaSpans16 (espan_t *pspan) -{ - int count, spancount; - pixel_t *pbase, *pdest; - fixed16_t s, t, snext, tnext, sstep, tstep; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz8stepu, tdivz8stepu, zi8stepu; - int izi, izistep; - short *pz; - - sstep = 0; // keep compiler happy - tstep = 0; // ditto - - pbase = cacheblock; - - sdivz8stepu = d_sdivzstepu * 8; - tdivz8stepu = d_tdivzstepu * 8; - zi8stepu = d_zistepu * 8; - izistep = (int)(d_zistepu * 0x8000 * 0x10000); - - do - { - pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); - pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u; - - count = pspan->count; - - // calculate the initial s/z, t/z, 1/z, s, and t and clamp - du = (float)pspan->u; - dv = (float)pspan->v; - - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - izi = (int)(zi * 0x8000 * 0x10000); - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - - s = (int)(sdivz * z) + sadjust; - if (s > bbextents) - s = bbextents; - else if (s < 0) - s = 0; - - t = (int)(tdivz * z) + tadjust; - if (t > bbextentt) - t = bbextentt; - else if (t < 0) - t = 0; - - do - { - // calculate s and t at the far end of the span - if (count >= 8) - spancount = 8; - else - spancount = count; - - count -= spancount; - - if (count) - { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting - sdivz += sdivz8stepu; - tdivz += tdivz8stepu; - zi += zi8stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) - snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture - - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) - tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps - - sstep = (snext - s) >> 3; - tstep = (tnext - t) >> 3; + while( --spancount > 0 ); } - else - { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(spancount - 1); - sdivz += d_sdivzstepu * spancountminus1; - tdivz += d_tdivzstepu * spancountminus1; - zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) - snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture - - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) - tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps - - if (spancount > 1) - { - sstep = (snext - s) / (spancount - 1); - tstep = (tnext - t) / (spancount - 1); - } - } - // Drawing phrase - if (!SW_TEXFILT) - { - do - { - pixel_t btemp; + } + while( count > 0 ); - btemp = *(pbase + (s >> 16) + (t >> 16) * cachewidth); - if (btemp != TRANSPARENT_COLOR) - { - if (*pz <= (izi >> 16)) - { - *pdest = btemp; - *pz = izi >> 16; - } - } - pdest++; - pz++; - izi += izistep; - s += sstep; - t += tstep; - } while (--spancount > 0); - } - else - { - do - { - if (*pz <= (izi >> 16)) - { - int idiths = s; - int iditht = t; - - int X = (pspan->u + spancount) & 1; - int Y = (pspan->v)&1; - pixel_t btemp; - - //Using the kernel - idiths += kernel[X][Y][0]; - iditht += kernel[X][Y][1]; - - idiths = idiths >> 16; - idiths = idiths ? idiths -1 : idiths; - - - iditht = iditht >> 16; - iditht = iditht ? iditht -1 : iditht; - - - btemp = *(pbase + idiths + iditht * cachewidth); - if (btemp != TRANSPARENT_COLOR) - { - *pdest = btemp; - *pz = izi >> 16; - } - } - pdest++; - pz++; - s += sstep; - t += tstep; - } while (--spancount > 0); - } - - - } while (count > 0); - - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } @@ -955,202 +895,206 @@ D_DrawSpans16 FIXME: actually make this subdivide by 16 instead of 8!!! ============= */ -void D_BlendSpans16 (espan_t *pspan, int alpha) +void D_BlendSpans16( espan_t *pspan, int alpha ) { - int count, spancount; - pixel_t *pbase, *pdest; - fixed16_t s, t, snext, tnext, sstep, tstep; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz8stepu, tdivz8stepu, zi8stepu; - int izi, izistep; - short *pz; + int count, spancount; + pixel_t *pbase, *pdest; + fixed16_t s, t, snext, tnext, sstep, tstep; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz8stepu, tdivz8stepu, zi8stepu; + int izi, izistep; + short *pz; if( alpha > 7 ) alpha = 7; if( alpha == 0 ) return; - sstep = 0; // keep compiler happy - tstep = 0; // ditto + sstep = 0; // keep compiler happy + tstep = 0; // ditto pbase = cacheblock; sdivz8stepu = d_sdivzstepu * 8; tdivz8stepu = d_tdivzstepu * 8; zi8stepu = d_zistepu * 8; - izistep = (int)(d_zistepu * 0x8000 * 0x10000); + izistep = (int)( d_zistepu * 0x8000 * 0x10000 ); do { - pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); - pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u; + pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); + pz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u; count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - izi = (int)(zi * 0x8000 * 0x10000); - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + izi = (int)( zi * 0x8000 * 0x10000 ); + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - s = (int)(sdivz * z) + sadjust; - if (s > bbextents) + s = (int)( sdivz * z ) + sadjust; + if( s > bbextents ) s = bbextents; - else if (s < 0) + else if( s < 0 ) s = 0; - t = (int)(tdivz * z) + tadjust; - if (t > bbextentt) + t = (int)( tdivz * z ) + tadjust; + if( t > bbextentt ) t = bbextentt; - else if (t < 0) + else if( t < 0 ) t = 0; do { - // calculate s and t at the far end of the span - if (count >= 8) + // calculate s and t at the far end of the span + if( count >= 8 ) spancount = 8; else spancount = count; count -= spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz8stepu; tdivz += tdivz8stepu; zi += zi8stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - sstep = (snext - s) >> 3; - tstep = (tnext - t) >> 3; + sstep = ( snext - s ) >> 3; + tstep = ( tnext - t ) >> 3; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - if (spancount > 1) + if( spancount > 1 ) { - sstep = (snext - s) / (spancount - 1); - tstep = (tnext - t) / (spancount - 1); + sstep = ( snext - s ) / ( spancount - 1 ); + tstep = ( tnext - t ) / ( spancount - 1 ); } - } + } // Drawing phrase - if (!SW_TEXFILT) + if( !SW_TEXFILT ) + { + do { - do + if( *pz <= ( izi >> 16 )) { - if (*pz <= (izi >> 16)) + pixel_t btemp; + + btemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth ); + + if( btemp != TRANSPARENT_COLOR ) { - pixel_t btemp; - - btemp = *(pbase + (s >> 16) + (t >> 16) * cachewidth); - - if( btemp != TRANSPARENT_COLOR ) - { - if( alpha != 7 ) - btemp = BLEND_ALPHA( alpha, btemp, *pdest); - *pdest = btemp; - } - //*pz = izi >> 16; + if( alpha != 7 ) + btemp = BLEND_ALPHA( alpha, btemp, *pdest ); + *pdest = btemp; } - pdest++; - pz++; - izi += izistep; - s += sstep; - t += tstep; - } while (--spancount > 0); + // *pz = izi >> 16; + } + pdest++; + pz++; + izi += izistep; + s += sstep; + t += tstep; } - else + while( --spancount > 0 ); + } + else + { + do { - do + int idiths = s; + int iditht = t; + + int X = ( pspan->u + spancount ) & 1; + int Y = ( pspan->v ) & 1; + if( *pz <= ( izi >> 16 )) { - int idiths = s; - int iditht = t; + pixel_t btemp; - int X = (pspan->u + spancount) & 1; - int Y = (pspan->v)&1; - if (*pz <= (izi >> 16)) + // Using the kernel + idiths += kernel[X][Y][0]; + iditht += kernel[X][Y][1]; + + idiths = idiths >> 16; + idiths = idiths ? idiths - 1 : idiths; + + + iditht = iditht >> 16; + iditht = iditht ? iditht - 1 : iditht; + + btemp = *( pbase + idiths + iditht * cachewidth ); + + if( btemp != TRANSPARENT_COLOR ) { - pixel_t btemp; - - //Using the kernel - idiths += kernel[X][Y][0]; - iditht += kernel[X][Y][1]; - - idiths = idiths >> 16; - idiths = idiths ? idiths -1 : idiths; - - - iditht = iditht >> 16; - iditht = iditht ? iditht -1 : iditht; - - btemp = *(pbase + idiths + iditht * cachewidth); - - if( btemp != TRANSPARENT_COLOR ) - { - if( alpha != 7 ) - btemp = BLEND_ALPHA( alpha, btemp, *pdest); - *pdest = btemp; - } - //*pz = izi >> 16; + if( alpha != 7 ) + btemp = BLEND_ALPHA( alpha, btemp, *pdest ); + *pdest = btemp; } - pdest++; - pz++; - izi += izistep; - s += sstep; - t += tstep; - } while (--spancount > 0); + // *pz = izi >> 16; + } + pdest++; + pz++; + izi += izistep; + s += sstep; + t += tstep; } + while( --spancount > 0 ); + } - } while (count > 0); + } + while( count > 0 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } @@ -1162,242 +1106,241 @@ D_DrawSpans16 FIXME: actually make this subdivide by 16 instead of 8!!! ============= */ -void D_AddSpans16 (espan_t *pspan) +void D_AddSpans16( espan_t *pspan ) { - int count, spancount; - pixel_t *pbase, *pdest; - fixed16_t s, t, snext, tnext, sstep, tstep; - float sdivz, tdivz, zi, z, du, dv, spancountminus1; - float sdivz8stepu, tdivz8stepu, zi8stepu; - int izi, izistep; - short *pz; + int count, spancount; + pixel_t *pbase, *pdest; + fixed16_t s, t, snext, tnext, sstep, tstep; + float sdivz, tdivz, zi, z, du, dv, spancountminus1; + float sdivz8stepu, tdivz8stepu, zi8stepu; + int izi, izistep; + short *pz; - sstep = 0; // keep compiler happy - tstep = 0; // ditto + sstep = 0; // keep compiler happy + tstep = 0; // ditto pbase = cacheblock; sdivz8stepu = d_sdivzstepu * 8; tdivz8stepu = d_tdivzstepu * 8; zi8stepu = d_zistepu * 8; - izistep = (int)(d_zistepu * 0x8000 * 0x10000); + izistep = (int)( d_zistepu * 0x8000 * 0x10000 ); do { - pdest = (d_viewbuffer + - (r_screenwidth * pspan->v) + pspan->u); - pz = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u; + pdest = ( d_viewbuffer + + ( r_screenwidth * pspan->v ) + pspan->u ); + pz = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u; count = pspan->count; - // calculate the initial s/z, t/z, 1/z, s, and t and clamp + // calculate the initial s/z, t/z, 1/z, s, and t and clamp du = (float)pspan->u; dv = (float)pspan->v; - sdivz = d_sdivzorigin + dv*d_sdivzstepv + du*d_sdivzstepu; - tdivz = d_tdivzorigin + dv*d_tdivzstepv + du*d_tdivzstepu; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - izi = (int)(zi * 0x8000 * 0x10000); - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + sdivz = d_sdivzorigin + dv * d_sdivzstepv + du * d_sdivzstepu; + tdivz = d_tdivzorigin + dv * d_tdivzstepv + du * d_tdivzstepu; + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + izi = (int)( zi * 0x8000 * 0x10000 ); + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - s = (int)(sdivz * z) + sadjust; - if (s > bbextents) + s = (int)( sdivz * z ) + sadjust; + if( s > bbextents ) s = bbextents; - else if (s < 0) + else if( s < 0 ) s = 0; - t = (int)(tdivz * z) + tadjust; - if (t > bbextentt) + t = (int)( tdivz * z ) + tadjust; + if( t > bbextentt ) t = bbextentt; - else if (t < 0) + else if( t < 0 ) t = 0; do { - // calculate s and t at the far end of the span - if (count >= 8) + // calculate s and t at the far end of the span + if( count >= 8 ) spancount = 8; else spancount = count; count -= spancount; - if (count) + if( count ) { - // calculate s/z, t/z, zi->fixed s and t at far end of span, - // calculate s and t steps across span by shifting + // calculate s/z, t/z, zi->fixed s and t at far end of span, + // calculate s and t steps across span by shifting sdivz += sdivz8stepu; tdivz += tdivz8stepu; zi += zi8stepu; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - sstep = (snext - s) >> 3; - tstep = (tnext - t) >> 3; + sstep = ( snext - s ) >> 3; + tstep = ( tnext - t ) >> 3; } else { - // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so - // can't step off polygon), clamp, calculate s and t steps across - // span by division, biasing steps low so we don't run off the - // texture - spancountminus1 = (float)(spancount - 1); + // calculate s/z, t/z, zi->fixed s and t at last pixel in span (so + // can't step off polygon), clamp, calculate s and t steps across + // span by division, biasing steps low so we don't run off the + // texture + spancountminus1 = (float)( spancount - 1 ); sdivz += d_sdivzstepu * spancountminus1; tdivz += d_tdivzstepu * spancountminus1; zi += d_zistepu * spancountminus1; - z = (float)0x10000 / zi; // prescale to 16.16 fixed-point - snext = (int)(sdivz * z) + sadjust; - if (snext > bbextents) + z = (float)0x10000 / zi; // prescale to 16.16 fixed-point + snext = (int)( sdivz * z ) + sadjust; + if( snext > bbextents ) snext = bbextents; - else if (snext < 8) - snext = 8; // prevent round-off error on <0 steps from - // from causing overstepping & running off the - // edge of the texture + else if( snext < 8 ) + snext = 8; // prevent round-off error on <0 steps from + // from causing overstepping & running off the + // edge of the texture - tnext = (int)(tdivz * z) + tadjust; - if (tnext > bbextentt) + tnext = (int)( tdivz * z ) + tadjust; + if( tnext > bbextentt ) tnext = bbextentt; - else if (tnext < 8) - tnext = 8; // guard against round-off error on <0 steps + else if( tnext < 8 ) + tnext = 8; // guard against round-off error on <0 steps - if (spancount > 1) + if( spancount > 1 ) { - sstep = (snext - s) / (spancount - 1); - tstep = (tnext - t) / (spancount - 1); + sstep = ( snext - s ) / ( spancount - 1 ); + tstep = ( tnext - t ) / ( spancount - 1 ); } - } + } // Drawing phrase - if (!SW_TEXFILT) + if( !SW_TEXFILT ) + { + do { - do + if( *pz <= ( izi >> 16 )) { - if (*pz <= (izi >> 16)) + pixel_t btemp; + + btemp = *( pbase + ( s >> 16 ) + ( t >> 16 ) * cachewidth ); + + if( btemp != TRANSPARENT_COLOR ) { - pixel_t btemp; - - btemp = *(pbase + (s >> 16) + (t >> 16) * cachewidth); - - if( btemp != TRANSPARENT_COLOR ) - { - btemp = BLEND_ADD( btemp, *pdest); - *pdest = btemp; - } - //*pz = izi >> 16; + btemp = BLEND_ADD( btemp, *pdest ); + *pdest = btemp; } - pdest++; - pz++; - izi += izistep; - s += sstep; - t += tstep; - } while (--spancount > 0); + // *pz = izi >> 16; + } + pdest++; + pz++; + izi += izistep; + s += sstep; + t += tstep; } - else + while( --spancount > 0 ); + } + else + { + do { - do + int idiths = s; + int iditht = t; + + int X = ( pspan->u + spancount ) & 1; + int Y = ( pspan->v ) & 1; + if( *pz <= ( izi >> 16 )) { - int idiths = s; - int iditht = t; + pixel_t btemp; - int X = (pspan->u + spancount) & 1; - int Y = (pspan->v)&1; - if (*pz <= (izi >> 16)) + // Using the kernel + idiths += kernel[X][Y][0]; + iditht += kernel[X][Y][1]; + + idiths = idiths >> 16; + idiths = idiths ? idiths - 1 : idiths; + + + iditht = iditht >> 16; + iditht = iditht ? iditht - 1 : iditht; + + btemp = *( pbase + idiths + iditht * cachewidth ); + + if( btemp != TRANSPARENT_COLOR ) { - pixel_t btemp; - - //Using the kernel - idiths += kernel[X][Y][0]; - iditht += kernel[X][Y][1]; - - idiths = idiths >> 16; - idiths = idiths ? idiths -1 : idiths; - - - iditht = iditht >> 16; - iditht = iditht ? iditht -1 : iditht; - - btemp = *(pbase + idiths + iditht * cachewidth); - - if( btemp != TRANSPARENT_COLOR ) - { - btemp = BLEND_ADD( btemp, *pdest); - *pdest = btemp; - } - //*pz = izi >> 16; + btemp = BLEND_ADD( btemp, *pdest ); + *pdest = btemp; } - pdest++; - pz++; - izi += izistep; - s += sstep; - t += tstep; - } while (--spancount > 0); + // *pz = izi >> 16; + } + pdest++; + pz++; + izi += izistep; + s += sstep; + t += tstep; } + while( --spancount > 0 ); + } - } while (count > 0); + } + while( count > 0 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } -#endif - - -#if !id386 - /* ============= D_DrawZSpans ============= */ -void D_DrawZSpans (espan_t *pspan) +void D_DrawZSpans( espan_t *pspan ) { - int count, doublecount, izistep; - int izi; - short *pdest; - unsigned ltemp; - float zi; - float du, dv; + int count, doublecount, izistep; + int izi; + short *pdest; + unsigned ltemp; + float zi; + float du, dv; // FIXME: check for clamping/range problems // we count on FP exceptions being turned off to avoid range problems - izistep = (int)(d_zistepu * 0x8000 * 0x10000); + izistep = (int)( d_zistepu * 0x8000 * 0x10000 ); do { - pdest = d_pzbuffer + (d_zwidth * pspan->v) + pspan->u; + pdest = d_pzbuffer + ( d_zwidth * pspan->v ) + pspan->u; count = pspan->count; - // calculate the initial 1/z + // calculate the initial 1/z du = (float)pspan->u; dv = (float)pspan->v; - zi = d_ziorigin + dv*d_zistepv + du*d_zistepu; - // we count on FP exceptions being turned off to avoid range problems - izi = (int)(zi * 0x8000 * 0x10000); + zi = d_ziorigin + dv * d_zistepv + du * d_zistepu; + // we count on FP exceptions being turned off to avoid range problems + izi = (int)( zi * 0x8000 * 0x10000 ); - if ((uintptr_t)pdest & 0x02) + if((uintptr_t)pdest & 0x02 ) { - *pdest++ = (short)(izi >> 16); + *pdest++ = (short)( izi >> 16 ); izi += izistep; count--; } - if ((doublecount = count >> 1) > 0) + if(( doublecount = count >> 1 ) > 0 ) { do { @@ -1407,14 +1350,14 @@ void D_DrawZSpans (espan_t *pspan) izi += izistep; *(int *)pdest = ltemp; pdest += 2; - } while (--doublecount > 0); + } + while( --doublecount > 0 ); } - if (count & 1) - *pdest = (short)(izi >> 16); + if( count & 1 ) + *pdest = (short)( izi >> 16 ); - } while ((pspan = pspan->pnext) != NULL); + } + while(( pspan = pspan->pnext ) != NULL ); } -#endif - diff --git a/ref/soft/r_sprite.c b/ref/soft/r_sprite.c index 5aa0f74d..20d36cea 100644 --- a/ref/soft/r_sprite.c +++ b/ref/soft/r_sprite.c @@ -19,15 +19,13 @@ GNU General Public License for more details. #include "studio.h" #include "entity_types.h" -// it's a Valve default value for LoadMapSprite (probably must be power of two) -#define MAPSPRITE_SIZE 128 -#define GLARE_FALLOFF 19000.0f +#define GLARE_FALLOFF 19000.0f -char sprite_name[MAX_QPATH]; -char group_suffix[8]; -static uint r_texFlags = 0; -static int sprite_version; -float sprite_radius; +char sprite_name[MAX_QPATH]; +char group_suffix[8]; +static uint r_texFlags = 0; +static int sprite_version; +float sprite_radius; /* ==================== @@ -46,15 +44,15 @@ R_SpriteLoadFrame upload a single frame ==================== */ -static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num ) +static const byte *R_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num ) { - dspriteframe_t pinframe; - mspriteframe_t *pspriteframe; - int gl_texturenum = 0; - char texname[128]; - int bytes = 1; + dspriteframe_t pinframe; + mspriteframe_t *pspriteframe; + int gl_texturenum = 0; + char texname[128]; + int bytes = 1; - memcpy( &pinframe, pin, sizeof(dspriteframe_t)); + memcpy( &pinframe, pin, sizeof( dspriteframe_t )); if( sprite_version == SPRITE_VERSION_32 ) bytes = 4; @@ -82,7 +80,7 @@ static const dframetype_t *R_SpriteLoadFrame( model_t *mod, const void *pin, msp pspriteframe->gl_texturenum = gl_texturenum; *ppframe = pspriteframe; - return ( const dframetype_t* )(( const byte* )pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes ); + return (const byte *)((const byte *)pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes ); } /* @@ -92,24 +90,24 @@ R_SpriteLoadGroup upload a group frames ==================== */ -static const dframetype_t *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) +static const void *R_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) { - const dspritegroup_t *pingroup; - mspritegroup_t *pspritegroup; - const dspriteinterval_t *pin_intervals; - float *poutintervals; - int i, groupsize, numframes; - const void *ptemp; + const dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + const dspriteinterval_t *pin_intervals; + float *poutintervals; + int i, groupsize, numframes; + const void *ptemp; pingroup = (const dspritegroup_t *)pin; numframes = pingroup->numframes; - groupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] ); + groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] ); pspritegroup = Mem_Calloc( mod->mempool, groupsize ); pspritegroup->numframes = numframes; *ppframe = (mspriteframe_t *)pspritegroup; - pin_intervals = (const dspriteinterval_t *)(pingroup + 1); + pin_intervals = (const dspriteinterval_t *)( pingroup + 1 ); poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float )); pspritegroup->intervals = poutintervals; @@ -128,7 +126,7 @@ static const dframetype_t *R_SpriteLoadGroup( model_t *mod, const void *pin, msp ptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); } - return (const dframetype_t *)ptemp; + return ptemp; } /* @@ -140,11 +138,11 @@ load sprite model */ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ) { - const dsprite_t *pin; - const short *numi = NULL; - const dframetype_t *pframetype; - msprite_t *psprite; - int i; + const dsprite_t *pin; + const short *numi = NULL; + const byte *pframetype; + msprite_t *psprite; + int i; pin = buffer; psprite = mod->cache.data; @@ -152,7 +150,7 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 ) numi = NULL; else if( pin->version == SPRITE_VERSION_HL ) - numi = (const short *)((const byte*)buffer + sizeof( dsprite_hl_t )); + numi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t )); r_texFlags = texFlags; sprite_version = pin->version; @@ -161,37 +159,38 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui if( numi == NULL ) { - rgbdata_t *pal; + rgbdata_t *pal; pal = gEngfuncs.FS_LoadImage( "#id.pal", (byte *)&i, 768 ); - pframetype = (const dframetype_t *)((const byte*)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1 - gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data + pframetype = ((const byte *)buffer + sizeof( dsprite_q1_t )); // pinq1 + 1 + gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data } - else if( *numi == 256 ) + else if( *numi <= 256 ) { const byte *src = (const byte *)(numi+1); rgbdata_t *pal; + size_t pal_bytes = *numi * 3; // install palette switch( psprite->texFormat ) { case SPR_INDEXALPHA: - pal = gEngfuncs.FS_LoadImage( "#gradient.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#gradient.pal", src, pal_bytes ); break; case SPR_ALPHTEST: - pal = gEngfuncs.FS_LoadImage( "#masked.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#masked.pal", src, pal_bytes ); break; default: - pal = gEngfuncs.FS_LoadImage( "#normal.pal", src, 768 ); + pal = gEngfuncs.FS_LoadImage( "#texgamma.pal", src, pal_bytes ); break; } - pframetype = (const dframetype_t *)(src + 768); + pframetype = (const byte *)(src + pal_bytes); gEngfuncs.FS_FreeImage( pal ); // palette installed, no reason to keep this data } else { - gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be 256)\n", mod->name, *numi ); + gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be less or equal than 256)\n", mod->name, *numi ); return; } @@ -200,149 +199,34 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui for( i = 0; i < mod->numframes; i++ ) { - frametype_t frametype = pframetype->type; + frametype_t frametype; + dframetype_t dframetype; + + memcpy( &dframetype, pframetype, sizeof( dframetype )); + frametype = dframetype.type; psprite->frames[i].type = (spriteframetype_t)frametype; switch( frametype ) { case FRAME_SINGLE: Q_strncpy( group_suffix, "frame", sizeof( group_suffix )); - pframetype = R_SpriteLoadFrame( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; case FRAME_GROUP: Q_strncpy( group_suffix, "group", sizeof( group_suffix )); - pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; case FRAME_ANGLED: Q_strncpy( group_suffix, "angle", sizeof( group_suffix )); - pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + pframetype = R_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i ); break; } - if( pframetype == NULL ) break; // technically an error + if( pframetype == NULL ) + break; // technically an error } - if( loaded ) *loaded = true; // done -} - -/* -==================== -Mod_LoadMapSprite - -Loading a bitmap image as sprite with multiple frames -as pieces of input image -==================== -*/ -void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) -{ - byte *src, *dst; - rgbdata_t *pix, temp; - char texname[128]; - int i, j, x, y, w, h; - int xl, yl, xh, yh; - int linedelta, numframes; - mspriteframe_t *pspriteframe; - msprite_t *psprite; - - if( loaded ) *loaded = false; - Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); - gEngfuncs.Image_SetForceFlags( IL_OVERVIEW ); - pix = gEngfuncs.FS_LoadImage( texname, buffer, size ); - gEngfuncs.Image_ClearForceFlags(); - if( !pix ) return; // bad image or something else - - mod->type = mod_sprite; - r_texFlags = 0; // no custom flags for map sprites - - if( pix->width % MAPSPRITE_SIZE ) - w = pix->width - ( pix->width % MAPSPRITE_SIZE ); - else w = pix->width; - - if( pix->height % MAPSPRITE_SIZE ) - h = pix->height - ( pix->height % MAPSPRITE_SIZE ); - else h = pix->height; - - if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; - if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; - - // resample image if needed - gEngfuncs.Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f ); - - w = h = MAPSPRITE_SIZE; - - // check range - if( w > pix->width ) w = pix->width; - if( h > pix->height ) h = pix->height; - - // determine how many frames we needs - numframes = (pix->width * pix->height) / (w * h); - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); - psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); - mod->cache.data = psprite; // make link to extradata - - psprite->type = SPR_FWD_PARALLEL_ORIENTED; - psprite->texFormat = SPR_ALPHTEST; - psprite->numframes = mod->numframes = numframes; - psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); - - mod->mins[0] = mod->mins[1] = -w / 2; - mod->maxs[0] = mod->maxs[1] = w / 2; - mod->mins[2] = -h / 2; - mod->maxs[2] = h / 2; - - // create a temporary pic - memset( &temp, 0, sizeof( temp )); - temp.width = w; - temp.height = h; - temp.type = pix->type; - temp.flags = pix->flags; - temp.size = w * h * gEngfuncs.Image_GetPFDesc(temp.type)->bpp; - temp.buffer = Mem_Malloc( r_temppool, temp.size ); - temp.palette = NULL; - - // chop the image and upload into video memory - for( i = xl = yl = 0; i < numframes; i++ ) - { - xh = xl + w; - yh = yl + h; - - src = pix->buffer + ( yl * pix->width + xl ) * 4; - linedelta = ( pix->width - w ) * 4; - dst = temp.buffer; - - // cut block from source - for( y = yl; y < yh; y++ ) - { - for( x = xl; x < xh; x++ ) - for( j = 0; j < 4; j++ ) - *dst++ = *src++; - src += linedelta; - } - - // build uinque frame name - Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); - - psprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t )); - pspriteframe = psprite->frames[i].frameptr; - pspriteframe->width = w; - pspriteframe->height = h; - pspriteframe->up = ( h >> 1 ); - pspriteframe->left = -( w >> 1 ); - pspriteframe->down = ( h >> 1 ) - h; - pspriteframe->right = w + -( w >> 1 ); - pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE ); - - xl += w; - if( xl >= pix->width ) - { - xl = 0; - yl += h; - } - } - - gEngfuncs.FS_FreeImage( pix ); - Mem_Free( temp.buffer ); - - if( loaded ) *loaded = true; + if( loaded ) + *loaded = true; // done } /* @@ -354,32 +238,31 @@ release sprite model and frames */ void Mod_SpriteUnloadTextures( void *data ) { - msprite_t *psprite; - mspritegroup_t *pspritegroup; - mspriteframe_t *pspriteframe; - int i, j; + msprite_t *psprite = data; + int i; - psprite = data; + if( !data ) + return; - if( psprite ) + // release all textures + for( i = 0; i < psprite->numframes; i++ ) { - // release all textures - for( i = 0; i < psprite->numframes; i++ ) - { - if( psprite->frames[i].type == SPR_SINGLE ) - { - pspriteframe = psprite->frames[i].frameptr; - GL_FreeTexture( pspriteframe->gl_texturenum ); - } - else - { - pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + if( !psprite->frames[i].frameptr ) + continue; - for( j = 0; j < pspritegroup->numframes; j++ ) - { - pspriteframe = pspritegroup->frames[i]; - GL_FreeTexture( pspriteframe->gl_texturenum ); - } + if( psprite->frames[i].type == SPR_SINGLE ) + { + GL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum ); + } + else + { + mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + int j; + + for( j = 0; j < pspritegroup->numframes; j++ ) + { + if( pspritegroup->frames[j] ) + GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum ); } } } @@ -394,12 +277,12 @@ assume pModel is valid */ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) { - msprite_t *psprite; - mspritegroup_t *pspritegroup; - mspriteframe_t *pspriteframe = NULL; - float *pintervals, fullinterval; - int i, numframes; - float targettime; + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe = NULL; + float *pintervals, fullinterval; + int i, numframes; + float targettime; Assert( pModel != NULL ); psprite = pModel->cache.data; @@ -411,7 +294,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) else if( frame >= psprite->numframes ) { if( frame > psprite->numframes ) - gEngfuncs.Con_Printf( S_WARN "R_GetSpriteFrame: no such frame %d (%s)\n", frame, pModel->name ); + gEngfuncs.Con_Printf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, pModel->name ); frame = psprite->numframes - 1; } @@ -424,13 +307,13 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pintervals = pspritegroup->intervals; numframes = pspritegroup->numframes; - fullinterval = pintervals[numframes-1]; + fullinterval = pintervals[numframes - 1]; // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // are positive, so we don't have to worry about division by zero - targettime = gpGlobals->time - ((int)( gpGlobals->time / fullinterval )) * fullinterval; + targettime = gp_cl->time - ((int)( gp_cl->time / fullinterval )) * fullinterval; - for( i = 0; i < (numframes - 1); i++ ) + for( i = 0; i < ( numframes - 1 ); i++ ) { if( pintervals[i] > targettime ) break; @@ -439,7 +322,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) } else if( psprite->frames[frame].type == FRAME_ANGLED ) { - int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + int angleframe = (int)( Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7; // e.g. doom-style sprite monsters pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; @@ -457,21 +340,21 @@ NOTE: we using prevblending[0] and [1] for holds interval between frames where are we lerping ================ */ -float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) +static float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) { - msprite_t *psprite; - mspritegroup_t *pspritegroup; - int i, j, numframes, frame; - float lerpFrac, time, jtime, jinterval; - float *pintervals, fullinterval, targettime; - int m_fDoInterp; + msprite_t *psprite; + mspritegroup_t *pspritegroup; + int i, j, numframes, frame; + float lerpFrac, time, jtime, jinterval; + float *pintervals, fullinterval, targettime; + int m_fDoInterp; psprite = ent->model->cache.data; frame = (int)ent->curstate.frame; lerpFrac = 1.0f; // misc info - m_fDoInterp = (ent->curstate.effects & EF_NOINTERP) ? false : true; + m_fDoInterp = ( ent->curstate.effects & EF_NOINTERP ) ? false : true; if( frame < 0 ) { @@ -479,7 +362,7 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, } else if( frame >= psprite->numframes ) { - gEngfuncs.Con_Reportf( S_WARN "R_GetSpriteFrameInterpolant: no such frame %d (%s)\n", frame, ent->model->name ); + gEngfuncs.Con_Reportf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, ent->model->name ); frame = psprite->numframes - 1; } @@ -492,25 +375,26 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 1.0f; } - if( ent->latched.sequencetime < gpGlobals->time ) + if( ent->latched.sequencetime < gp_cl->time ) { if( frame != ent->latched.prevblending[1] ) { ent->latched.prevblending[0] = ent->latched.prevblending[1]; ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } - else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * 11.0f; + else + lerpFrac = ( gp_cl->time - ent->latched.sequencetime ) * 11.0f; } else { ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } } @@ -524,31 +408,33 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, { // reset interpolation on change model ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } // get the interpolated frames - if( oldframe ) *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr; - if( curframe ) *curframe = psprite->frames[frame].frameptr; + if( oldframe ) + *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr; + if( curframe ) + *curframe = psprite->frames[frame].frameptr; } else if( psprite->frames[frame].type == FRAME_GROUP ) { pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pintervals = pspritegroup->intervals; numframes = pspritegroup->numframes; - fullinterval = pintervals[numframes-1]; + fullinterval = pintervals[numframes - 1]; jinterval = pintervals[1] - pintervals[0]; - time = gpGlobals->time; + time = gp_cl->time; jtime = 0.0f; // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // are positive, so we don't have to worry about division by zero - targettime = time - ((int)(time / fullinterval)) * fullinterval; + targettime = time - ((int)( time / fullinterval )) * fullinterval; // LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, // i instead measure the time of the first frame, hoping it is consistent - for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) + for( i = 0, j = numframes - 1; i < ( numframes - 1 ); i++ ) { if( pintervals[i] > targettime ) break; @@ -558,18 +444,21 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, } if( m_fDoInterp ) - lerpFrac = (targettime - jtime) / jinterval; - else j = i; // no lerping + lerpFrac = ( targettime - jtime ) / jinterval; + else + j = i; // no lerping // get the interpolated frames - if( oldframe ) *oldframe = pspritegroup->frames[j]; - if( curframe ) *curframe = pspritegroup->frames[i]; + if( oldframe ) + *oldframe = pspritegroup->frames[j]; + if( curframe ) + *curframe = pspritegroup->frames[i]; } else if( psprite->frames[frame].type == FRAME_ANGLED ) { // e.g. doom-style sprite monsters - float yaw = ent->angles[YAW]; - int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + float yaw = ent->angles[YAW]; + int angleframe = (int)( Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7; if( m_fDoInterp ) { @@ -578,25 +467,26 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 1.0f; } - if( ent->latched.sequencetime < gpGlobals->time ) + if( ent->latched.sequencetime < gp_cl->time ) { if( frame != ent->latched.prevblending[1] ) { ent->latched.prevblending[0] = ent->latched.prevblending[1]; ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } - else lerpFrac = (gpGlobals->time - ent->latched.sequencetime) * ent->curstate.framerate; + else + lerpFrac = ( gp_cl->time - ent->latched.sequencetime ) * ent->curstate.framerate; } else { ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; - ent->latched.sequencetime = gpGlobals->time; + ent->latched.sequencetime = gp_cl->time; lerpFrac = 0.0f; } } @@ -607,10 +497,12 @@ float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, } pspritegroup = (mspritegroup_t *)psprite->frames[ent->latched.prevblending[0]].frameptr; - if( oldframe ) *oldframe = pspritegroup->frames[angleframe]; + if( oldframe ) + *oldframe = pspritegroup->frames[angleframe]; pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; - if( curframe ) *curframe = pspritegroup->frames[angleframe]; + if( curframe ) + *curframe = pspritegroup->frames[angleframe]; } return lerpFrac; @@ -623,10 +515,10 @@ R_CullSpriteModel Cull sprite model by bbox ================ */ -qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) +static qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) { - vec3_t sprite_mins, sprite_maxs; - float scale = 1.0f; + vec3_t sprite_mins, sprite_maxs; + float scale = 1.0f; if( !e->model->cache.data ) return true; @@ -655,16 +547,16 @@ Set sprite brightness factor */ static float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale ) { - float dist, brightness; - vec3_t glowDist; - pmtrace_t *tr; + float dist, brightness; + vec3_t glowDist; + pmtrace_t *tr; VectorSubtract( origin, RI.vieworg, glowDist ); dist = VectorLength( glowDist ); if( RP_NORMALPASS( )) { - tr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow->value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE)); + tr = gEngfuncs.EV_VisTraceLine( RI.vieworg, origin, r_traceglow.value ? PM_GLASS_IGNORE : ( PM_GLASS_IGNORE | PM_STUDIO_IGNORE )); if(( 1.0f - tr->fraction ) * dist > 8.0f ) return 0.0f; @@ -687,12 +579,12 @@ R_SpriteOccluded Do occlusion test for glow-sprites ================ */ -qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) +static qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) { if( e->curstate.rendermode == kRenderGlow ) { - float blend; - vec3_t v; + float blend; + vec3_t v; TriWorldToScreen( origin, v ); @@ -723,7 +615,7 @@ R_DrawSpriteQuad */ static void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale ) { - vec3_t point; + vec3_t point; image_t *image; r_stats.c_sprite_polys++; @@ -733,87 +625,23 @@ static void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, r_affinetridesc.skinheight = image->height;*/ TriBegin( TRI_QUADS ); - TriTexCoord2f( 0.0f, 1.0f ); - VectorMA( org, frame->down * scale, v_up, point ); - VectorMA( point, frame->left * scale, v_right, point ); - TriVertex3fv( point ); - TriTexCoord2f( 0.0f, 0.0f ); - VectorMA( org, frame->up * scale, v_up, point ); - VectorMA( point, frame->left * scale, v_right, point ); - TriVertex3fv( point ); - TriTexCoord2f( 1.0f, 0.0f ); - VectorMA( org, frame->up * scale, v_up, point ); - VectorMA( point, frame->right * scale, v_right, point ); - TriVertex3fv( point ); - TriTexCoord2f( 1.0f, 1.0f ); - VectorMA( org, frame->down * scale, v_up, point ); - VectorMA( point, frame->right * scale, v_right, point ); - TriVertex3fv( point ); + TriTexCoord2f( 0.0f, 1.0f ); + VectorMA( org, frame->down * scale, v_up, point ); + VectorMA( point, frame->left * scale, v_right, point ); + TriVertex3fv( point ); + TriTexCoord2f( 0.0f, 0.0f ); + VectorMA( org, frame->up * scale, v_up, point ); + VectorMA( point, frame->left * scale, v_right, point ); + TriVertex3fv( point ); + TriTexCoord2f( 1.0f, 0.0f ); + VectorMA( org, frame->up * scale, v_up, point ); + VectorMA( point, frame->right * scale, v_right, point ); + TriVertex3fv( point ); + TriTexCoord2f( 1.0f, 1.0f ); + VectorMA( org, frame->down * scale, v_up, point ); + VectorMA( point, frame->right * scale, v_right, point ); + TriVertex3fv( point ); TriEnd(); - -#if 0 - image_t *pic = R_GetTexture(frame->gl_texturenum); - r_polydesc.pixels = pic->pixels[0]; - r_polydesc.pixel_width = pic->width; - r_polydesc.pixel_height = pic->height; - r_polydesc.dist = 0; - - // generate the sprite's axes, completely parallel to the viewplane. - VectorCopy (v_up, r_polydesc.vup); - VectorCopy (v_right, r_polydesc.vright); - VectorCopy (vpn, r_polydesc.vpn); - -// build the sprite poster in worldspace - VectorScale (r_polydesc.vright, - frame->width - frame->origin_x, right); - VectorScale (r_polydesc.vup, - s_psprframe->height - s_psprframe->origin_y, up); - VectorScale (r_polydesc.vright, - -s_psprframe->origin_x, left); - VectorScale (r_polydesc.vup, - -s_psprframe->origin_y, down); - - // invert UP vector for sprites - VectorInverse( r_polydesc.vup ); - - pverts = r_clip_verts[0]; - - pverts[0][0] = r_entorigin[0] + up[0] + left[0]; - pverts[0][1] = r_entorigin[1] + up[1] + left[1]; - pverts[0][2] = r_entorigin[2] + up[2] + left[2]; - pverts[0][3] = 0; - pverts[0][4] = 0; - - pverts[1][0] = r_entorigin[0] + up[0] + right[0]; - pverts[1][1] = r_entorigin[1] + up[1] + right[1]; - pverts[1][2] = r_entorigin[2] + up[2] + right[2]; - pverts[1][3] = s_psprframe->width; - pverts[1][4] = 0; - - pverts[2][0] = r_entorigin[0] + down[0] + right[0]; - pverts[2][1] = r_entorigin[1] + down[1] + right[1]; - pverts[2][2] = r_entorigin[2] + down[2] + right[2]; - pverts[2][3] = s_psprframe->width; - pverts[2][4] = s_psprframe->height; - - pverts[3][0] = r_entorigin[0] + down[0] + left[0]; - pverts[3][1] = r_entorigin[1] + down[1] + left[1]; - pverts[3][2] = r_entorigin[2] + down[2] + left[2]; - pverts[3][3] = 0; - pverts[3][4] = s_psprframe->height; - - r_polydesc.nump = 4; - r_polydesc.s_offset = ( r_polydesc.pixel_width >> 1); - r_polydesc.t_offset = ( r_polydesc.pixel_height >> 1); - VectorCopy( modelorg, r_polydesc.viewer_position ); - - r_polydesc.stipple_parity = 1; - if ( currententity->flags & RF_TRANSLUCENT ) - R_ClipAndDrawPoly ( currententity->alpha, false, true ); - else - R_ClipAndDrawPoly ( 1.0F, false, true ); - r_polydesc.stipple_parity = 0; -#endif } static qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat ) @@ -872,42 +700,44 @@ R_DrawSpriteModel */ void R_DrawSpriteModel( cl_entity_t *e ) { - mspriteframe_t *frame, *oldframe; - msprite_t *psprite; - model_t *model; - int i, type; - float angle, dot, sr, cr; - float lerp = 1.0f, ilerp, scale; - vec3_t v_forward, v_right, v_up; - vec3_t origin, color, color2; + mspriteframe_t *frame = NULL, *oldframe = NULL; + msprite_t *psprite; + model_t *model; + int i, type; + float angle, dot, sr, cr; + float lerp = 1.0f, ilerp, scale; + vec3_t v_forward, v_right, v_up; + vec3_t origin, color, color2; if( RI.params & RP_ENVVIEW ) return; model = e->model; - psprite = (msprite_t * )model->cache.data; - VectorCopy( e->origin, origin ); // set render origin + psprite = (msprite_t *)model->cache.data; + VectorCopy( e->origin, origin ); // set render origin // do movewith if( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW ) { - cl_entity_t *parent; + cl_entity_t *parent; - parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); + parent = CL_GetEntityByIndex( e->curstate.aiment ); if( parent && parent->model ) { if( parent->model->type == mod_studio && e->curstate.body > 0 ) { int num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS ); - VectorCopy( parent->attachment[num-1], origin ); + VectorCopy( parent->attachment[num - 1], origin ); } - else VectorCopy( parent->origin, origin ); + else + VectorCopy( parent->origin, origin ); } } scale = e->curstate.scale; - if( !scale ) scale = 1.0f; + if( !scale ) + scale = 1.0f; if( R_SpriteOccluded( e, origin, &scale )) return; // sprite culled @@ -918,34 +748,7 @@ void R_DrawSpriteModel( cl_entity_t *e ) R_AllowFog( false ); GL_SetRenderMode( e->curstate.rendermode ); -#if 0 - // select properly rendermode - switch( e->curstate.rendermode ) - { - case kRenderTransAlpha: - pglDepthMask( GL_FALSE ); - case kRenderTransColor: - case kRenderTransTexture: - pglEnable( GL_BLEND ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - break; - case kRenderGlow: - pglDisable( GL_DEPTH_TEST ); - case kRenderTransAdd: - pglEnable( GL_BLEND ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - pglDepthMask( GL_FALSE ); - break; - case kRenderNormal: - default: - pglDisable( GL_BLEND ); - break; - } - // all sprites can have color - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglEnable( GL_ALPHA_TEST ); -#endif // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) { @@ -968,12 +771,13 @@ void R_DrawSpriteModel( cl_entity_t *e ) color2[1] = (float)lightColor.g * ( 1.0f / 255.0f ); color2[2] = (float)lightColor.b * ( 1.0f / 255.0f ); // NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0 - // pglAlphaFunc( GL_GREATER, 0.5f ); + // pglAlphaFunc( GL_GREATER, 0.5f ); } if( R_SpriteAllowLerping( e, psprite )) lerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame ); - else frame = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] ); + else + frame = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] ); type = psprite->type; @@ -985,28 +789,28 @@ void R_DrawSpriteModel( cl_entity_t *e ) { case SPR_ORIENTED: AngleVectors( e->angles, v_forward, v_right, v_up ); - VectorScale( v_forward, 0.01f, v_forward ); // to avoid z-fighting + VectorScale( v_forward, 0.01f, v_forward ); // to avoid z-fighting VectorSubtract( origin, v_forward, origin ); break; case SPR_FACING_UPRIGHT: - VectorSet( v_right, origin[1] - RI.vieworg[1], -(origin[0] - RI.vieworg[0]), 0.0f ); + VectorSet( v_right, origin[1] - RI.vieworg[1], -( origin[0] - RI.vieworg[0] ), 0.0f ); VectorSet( v_up, 0.0f, 0.0f, 1.0f ); VectorNormalize( v_right ); break; case SPR_FWD_PARALLEL_UPRIGHT: dot = RI.vforward[2]; - if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 + if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 return; // invisible VectorSet( v_up, 0.0f, 0.0f, 1.0f ); VectorSet( v_right, RI.vforward[1], -RI.vforward[0], 0.0f ); VectorNormalize( v_right ); break; case SPR_FWD_PARALLEL_ORIENTED: - angle = e->angles[ROLL] * (M_PI2 / 360.0f); + angle = e->angles[ROLL] * ( M_PI2 / 360.0f ); SinCos( angle, &sr, &cr ); for( i = 0; i < 3; i++ ) { - v_right[i] = (RI.vright[i] * cr + RI.vup[i] * sr); + v_right[i] = ( RI.vright[i] * cr + RI.vup[i] * sr ); v_up[i] = RI.vright[i] * -sr + RI.vup[i] * cr; } break; @@ -1017,8 +821,8 @@ void R_DrawSpriteModel( cl_entity_t *e ) break; } - //if( psprite->facecull == SPR_CULL_NONE ) - //GL_Cull( GL_NONE ); + // if( psprite->facecull == SPR_CULL_NONE ) + // GL_Cull( GL_NONE ); if( oldframe == frame ) { @@ -1047,39 +851,4 @@ void R_DrawSpriteModel( cl_entity_t *e ) R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); } } -#if 0 - // draw the sprite 'lightmap' :-) - if( R_SpriteHasLightmap( e, psprite->texFormat )) - { - if( !r_lightmap->value ) - pglEnable( GL_BLEND ); - else pglDisable( GL_BLEND ); - pglDepthFunc( GL_EQUAL ); - pglDisable( GL_ALPHA_TEST ); - pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - - pglColor4f( color2[0], color2[1], color2[2], tr.blend ); - GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); - R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); - pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); - pglDepthFunc( GL_LEQUAL ); - } - - if( psprite->facecull == SPR_CULL_NONE ) - GL_Cull( GL_FRONT ); - - pglDisable( GL_ALPHA_TEST ); - pglDepthMask( GL_TRUE ); - - if( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd ) - R_AllowFog( true ); - - if( e->curstate.rendermode != kRenderNormal ) - { - pglDisable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - pglEnable( GL_DEPTH_TEST ); - } -#endif } diff --git a/ref/soft/r_studio.c b/ref/soft/r_studio.c index aa413bae..8212272f 100644 --- a/ref/soft/r_studio.c +++ b/ref/soft/r_studio.c @@ -22,34 +22,34 @@ GNU General Public License for more details. #include "pm_local.h" #include "pmtrace.h" -#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events -#define MAX_LOCALLIGHTS 4 +#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events +#define MAX_LOCALLIGHTS 4 typedef struct { - char name[MAX_OSPATH]; - char modelname[MAX_OSPATH]; - model_t *model; + char name[MAX_OSPATH]; + char modelname[MAX_OSPATH]; + model_t *model; } player_model_t; -cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; +CVAR_DEFINE_AUTO( r_shadows, "0", 0, "draw ugly shadows" ); -static vec3_t hullcolor[8] = +static const vec3_t hullcolor[8] = { -{ 1.0f, 1.0f, 1.0f }, -{ 1.0f, 0.5f, 0.5f }, -{ 0.5f, 1.0f, 0.5f }, -{ 1.0f, 1.0f, 0.5f }, -{ 0.5f, 0.5f, 1.0f }, -{ 1.0f, 0.5f, 1.0f }, -{ 0.5f, 1.0f, 1.0f }, -{ 1.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.5f, 0.5f }, + { 0.5f, 1.0f, 0.5f }, + { 1.0f, 1.0f, 0.5f }, + { 0.5f, 0.5f, 1.0f }, + { 1.0f, 0.5f, 1.0f }, + { 0.5f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f }, }; typedef struct sortedmesh_s { - mstudiomesh_t *mesh; - int flags; // face flags + mstudiomesh_t *mesh; + int flags; // face flags } sortedmesh_t; #if XASH_LOW_MEMORY @@ -59,81 +59,79 @@ typedef struct sortedmesh_s typedef struct { - double time; - double frametime; - int framecount; // studio framecount - qboolean interpolate; - int rendermode; - float blend; // blend value + double time; + double frametime; + int framecount; // studio framecount + qboolean interpolate; + int rendermode; + float blend; // blend value // bones - matrix3x4 rotationmatrix; - matrix3x4 bonestransform[MAXSTUDIOBONES]; - matrix3x4 lighttransform[MAXSTUDIOBONES]; + matrix3x4 rotationmatrix; + matrix3x4 bonestransform[MAXSTUDIOBONES]; + matrix3x4 lighttransform[MAXSTUDIOBONES]; // boneweighting stuff - matrix3x4 worldtransform[MAXSTUDIOBONES]; + matrix3x4 worldtransform[MAXSTUDIOBONES]; // cached bones - matrix3x4 cached_bonestransform[MAXSTUDIOBONES]; - matrix3x4 cached_lighttransform[MAXSTUDIOBONES]; - char cached_bonenames[MAXSTUDIOBONES][32]; - int cached_numbones; // number of bones in cache + matrix3x4 cached_bonestransform[MAXSTUDIOBONES]; + matrix3x4 cached_lighttransform[MAXSTUDIOBONES]; + char cached_bonenames[MAXSTUDIOBONES][32]; + int cached_numbones; // number of bones in cache - sortedmesh_t meshes[MAXSTUDIOMESHES]; // sorted meshes - vec3_t verts[MAXSTUDIOVERTS]; - vec3_t norms[MAXSTUDIOVERTS]; + sortedmesh_t meshes[MAXSTUDIOMESHES]; // sorted meshes + vec3_t verts[MAXSTUDIOVERTS]; + vec3_t norms[MAXSTUDIOVERTS]; // lighting state - float ambientlight; - float shadelight; - vec3_t lightvec; // averaging light direction - vec3_t lightspot; // shadow spot - vec3_t lightcolor; // averaging lightcolor - vec3_t blightvec[MAXSTUDIOBONES]; // bone light vecs - vec3_t lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel + float ambientlight; + float shadelight; + vec3_t lightvec; // averaging light direction + vec3_t lightspot; // shadow spot + vec3_t lightcolor; // averaging lightcolor + vec3_t blightvec[MAXSTUDIOBONES]; // bone light vecs + vec3_t lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel // chrome stuff - vec3_t chrome_origin; - vec2_t chrome[MAXSTUDIOVERTS]; // texture coords for surface normals - vec3_t chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames - vec3_t chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames - int chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated + vec3_t chrome_origin; + vec2_t chrome[MAXSTUDIOVERTS]; // texture coords for surface normals + vec3_t chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames + vec3_t chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames + int chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated // glowshell stuff - int normaltable[MAXSTUDIOVERTS]; // glowshell uses this + int normaltable[MAXSTUDIOVERTS]; // glowshell uses this // elights cache - int numlocallights; - int lightage[MAXSTUDIOBONES]; - dlight_t *locallight[MAX_LOCALLIGHTS]; - color24 locallightcolor[MAX_LOCALLIGHTS]; - vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; - vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; - float locallightR2[MAX_LOCALLIGHTS]; + int numlocallights; + int lightage[MAXSTUDIOBONES]; + dlight_t *locallight[MAX_LOCALLIGHTS]; + uint locallightcolor[MAX_LOCALLIGHTS][3]; + vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; + vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; + float locallightR2[MAX_LOCALLIGHTS]; // playermodels - player_model_t player_models[MAX_CLIENTS]; + player_model_t player_models[MAX_CLIENTS]; } studio_draw_state_t; // studio-related cvars -static cvar_t *r_studio_sort_textures; -static cvar_t *cl_righthand = NULL; -static cvar_t *r_studio_drawelements; +CVAR_DEFINE_AUTO( r_studio_sort_textures, "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); +static cvar_t *cl_righthand = NULL; -static r_studio_interface_t *pStudioDraw; -static studio_draw_state_t g_studio; // global studio state +static r_studio_interface_t *pStudioDraw; +static studio_draw_state_t g_studio; // global studio state // global variables -static qboolean m_fDoRemap; -mstudiomodel_t *m_pSubModel; -mstudiobodyparts_t *m_pBodyPart; -player_info_t *m_pPlayerInfo; -studiohdr_t *m_pStudioHeader; -float m_flGaitMovement; -int g_iBackFaceCull; -int g_nTopColor, g_nBottomColor; // remap colors -int g_nFaceFlags, g_nForceFaceFlags; +static qboolean m_fDoRemap; +mstudiomodel_t *m_pSubModel; +mstudiobodyparts_t *m_pBodyPart; +player_info_t *m_pPlayerInfo; +studiohdr_t *m_pStudioHeader; +float m_flGaitMovement; +int g_nTopColor, g_nBottomColor; // remap colors +int g_nFaceFlags, g_nForceFaceFlags; /* ==================== @@ -143,9 +141,6 @@ R_StudioInit */ void R_StudioInit( void ) { - r_studio_sort_textures = gEngfuncs.Cvar_Get( "r_studio_sort_textures", "0", FCVAR_ARCHIVE, "change draw order for additive meshes" ); - r_drawviewmodel = gEngfuncs.Cvar_Get( "r_drawviewmodel", "1", 0, "draw firstperson weapon model" ); - Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); // g-cont. cvar disabled by Valve @@ -168,14 +163,14 @@ static void R_StudioSetupTimings( void ) if( RI.drawWorld ) { // synchronize with server time - g_studio.time = gpGlobals->time; - g_studio.frametime = gpGlobals->time - gpGlobals->oldtime; + g_studio.time = gp_cl->time; + g_studio.frametime = gp_cl->time - gp_cl->oldtime; } else { // menu stuff - g_studio.time = gpGlobals->realtime; - g_studio.frametime = gpGlobals->frametime; + g_studio.time = gp_host->realtime; + g_studio.frametime = gp_host->frametime; } } @@ -190,7 +185,7 @@ static qboolean R_AllowFlipViewModel( cl_entity_t *e ) { if( cl_righthand && cl_righthand->value > 0 ) { - if( e == gEngfuncs.GetViewModel() ) + if( e == tr.viewent ) return true; } @@ -206,11 +201,11 @@ Compute a full bounding box for current sequence */ static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) { - vec3_t studio_mins, studio_maxs; - vec3_t mins, maxs, p1, p2; - cl_entity_t *e = RI.currententity; - mstudioseqdesc_t *pseqdesc; - int i; + vec3_t studio_mins, studio_maxs; + vec3_t mins, maxs, p1, p2; + cl_entity_t *e = RI.currententity; + mstudioseqdesc_t *pseqdesc; + int i; if( !m_pStudioHeader ) return false; @@ -231,7 +226,7 @@ static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) e->curstate.sequence = 0; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence; // add sequence box to the model box AddPointToBounds( pseqdesc->bbmin, mins, maxs ); @@ -241,25 +236,26 @@ static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) // compute a full bounding box for( i = 0; i < 8; i++ ) { - p1[0] = ( i & 1 ) ? mins[0] : maxs[0]; - p1[1] = ( i & 2 ) ? mins[1] : maxs[1]; - p1[2] = ( i & 4 ) ? mins[2] : maxs[2]; + p1[0] = ( i & 1 ) ? mins[0] : maxs[0]; + p1[1] = ( i & 2 ) ? mins[1] : maxs[1]; + p1[2] = ( i & 4 ) ? mins[2] : maxs[2]; Matrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 ); AddPointToBounds( p2, studio_mins, studio_maxs ); - if( bbox ) VectorCopy( p2, bbox[i] ); + if( bbox ) + VectorCopy( p2, bbox[i] ); } if( !bbox && R_CullModel( e, studio_mins, studio_maxs )) - return false; // model culled - return true; // visible + return false; // model culled + return true; // visible } -void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) +static void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) { - float flWeight0, flWeight1, flWeight2, flWeight3; - int i, numbones = 0; - float flTotal; + float flWeight0, flWeight1, flWeight2, flWeight3; + int i, numbones = 0; + float flTotal; for( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) { @@ -279,7 +275,8 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu flWeight3 = boneweights->weight[3] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; - if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + if( flTotal < 1.0f ) + flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; @@ -304,7 +301,8 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu flWeight2 = boneweights->weight[2] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2; - if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + if( flTotal < 1.0f ) + flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; @@ -327,7 +325,8 @@ void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 resu flWeight1 = boneweights->weight[1] / 255.0f; flTotal = flWeight0 + flWeight1; - if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + if( flTotal < 1.0f ) + flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; @@ -390,7 +389,7 @@ pfnGetPlayerState =============== */ -entity_state_t *R_StudioGetPlayerState( int index ) +static entity_state_t *R_StudioGetPlayerState( int index ) { if( !RI.drawWorld ) return &RI.currententity->curstate; @@ -406,7 +405,7 @@ pfnGetViewEntity */ static cl_entity_t *pfnGetViewEntity( void ) { - return gEngfuncs.GetViewModel(); + return tr.viewent; } /* @@ -417,9 +416,12 @@ pfnGetEngineTimes */ static void pfnGetEngineTimes( int *framecount, double *current, double *old ) { - if( framecount ) *framecount = tr.realframecount; - if( current ) *current = gpGlobals->time; - if( old ) *old = gpGlobals->oldtime; + if( framecount ) + *framecount = tr.realframecount; + if( current ) + *current = gp_cl->time; + if( old ) + *old = gp_cl->oldtime; } /* @@ -430,10 +432,14 @@ pfnGetViewInfo */ static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) { - if( origin ) VectorCopy( RI.vieworg, origin ); - if( forwardv ) VectorCopy( RI.vforward, forwardv ); - if( rightv ) VectorCopy( RI.vright, rightv ); - if( upv ) VectorCopy( RI.vup, upv ); + if( origin ) + VectorCopy( RI.vieworg, origin ); + if( forwardv ) + VectorCopy( RI.vforward, forwardv ); + if( rightv ) + VectorCopy( RI.vright, rightv ); + if( upv ) + VectorCopy( RI.vup, upv ); } /* @@ -467,8 +473,10 @@ pfnGetAliasScale */ static void pfnGetAliasScale( float *x, float *y ) { - if( x ) *x = 1.0f; - if( y ) *y = 1.0f; + if( x ) + *x = 1.0f; + if( y ) + *y = 1.0f; } /* @@ -521,10 +529,10 @@ StudioPlayerBlend ==================== */ -void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +static void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing - *pBlend = (*pPitch * 3.0f); + *pBlend = ( *pPitch * 3.0f ); if( *pBlend < pseqdesc->blendstart[0] ) { @@ -540,7 +548,8 @@ void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch { if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error *pBlend = 127; - else *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + else + *pBlend = 255 * ( *pBlend - pseqdesc->blendstart[0] ) / ( pseqdesc->blendend[0] - pseqdesc->blendstart[0] ); *pPitch = 0.0f; } } @@ -553,7 +562,7 @@ R_StudioLerpMovement */ void GAME_EXPORT R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ) { - float f = 1.0f; + float f = 1.0f; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit @@ -566,14 +575,15 @@ void GAME_EXPORT R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origi if( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON )) { - vec4_t q, q1, q2; + vec4_t q, q1, q2; AngleQuaternion( e->curstate.angles, q1, false ); AngleQuaternion( e->latched.prevangles, q2, false ); QuaternionSlerp( q2, q1, f, q ); QuaternionAngle( q, angles ); } - else VectorCopy( e->curstate.angles, angles ); + else + VectorCopy( e->curstate.angles, angles ); } /* @@ -582,24 +592,25 @@ StudioSetUpTransform ==================== */ -void R_StudioSetUpTransform( cl_entity_t *e ) +static void R_StudioSetUpTransform( cl_entity_t *e ) { - vec3_t origin, angles; + vec3_t origin, angles; VectorCopy( e->origin, origin ); VectorCopy( e->angles, angles ); // interpolate monsters position (moved into UpdateEntityFields by user request) - if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPUTE_STUDIO_LERP )) + if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gp_host->features, ENGINE_COMPUTE_STUDIO_LERP )) { R_StudioLerpMovement( e, g_studio.time, origin, angles ); } - if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) + if( !FBitSet( gp_host->features, ENGINE_COMPENSATE_QUAKE_BUG )) angles[PITCH] = -angles[PITCH]; // stupid quake bug // don't rotate clients, only aim - if( e->player ) angles[PITCH] = 0.0f; + if( e->player ) + angles[PITCH] = 0.0f; Matrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f ); @@ -617,33 +628,40 @@ StudioEstimateFrame ==================== */ -float GAME_EXPORT R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +float GAME_EXPORT R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ) { - double dfdt, f; + double dfdt, f; if( g_studio.interpolate ) { - if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; - else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + if( time < e->curstate.animtime ) + dfdt = 0.0; + else + dfdt = ( time - e->curstate.animtime ) * e->curstate.framerate * pseqdesc->fps; } - else dfdt = 0; + else + dfdt = 0; - if( pseqdesc->numframes <= 1 ) f = 0.0; - else f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + if( pseqdesc->numframes <= 1 ) + f = 0.0; + else + f = ( e->curstate.frame * ( pseqdesc->numframes - 1 )) / 256.0; f += dfdt; if( pseqdesc->flags & STUDIO_LOOPING ) { if( pseqdesc->numframes > 1 ) - f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); - if( f < 0 ) f += (pseqdesc->numframes - 1); + f -= (int)( f / ( pseqdesc->numframes - 1 )) * ( pseqdesc->numframes - 1 ); + if( f < 0 ) + f += ( pseqdesc->numframes - 1 ); } else { if( f >= pseqdesc->numframes - 1.001 ) f = pseqdesc->numframes - 1.001; - if( f < 0.0 ) f = 0.0; + if( f < 0.0 ) + f = 0.0; } return f; } @@ -654,55 +672,27 @@ StudioEstimateInterpolant ==================== */ -float R_StudioEstimateInterpolant( cl_entity_t *e ) +static float R_StudioEstimateInterpolant( cl_entity_t *e ) { - float dadt = 1.0f; + float dadt = 1.0f; if( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) { dadt = ( g_studio.time - e->curstate.animtime ) / 0.1f; - if( dadt > 2.0f ) dadt = 2.0f; + if( dadt > 2.0f ) + dadt = 2.0f; } return dadt; } -/* -==================== -CL_GetSequenceDuration - -==================== -*/ -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) -{ - studiohdr_t *pstudiohdr; - mstudioseqdesc_t *pseqdesc; - - if( ent->model != NULL && ent->model->type == mod_studio ) - { - pstudiohdr = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, ent->model ); - - if( pstudiohdr ) - { - sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); - pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - - if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) - return (float)pseqdesc->numframes / (float)pseqdesc->fps; - } - } - - return 0.1f; -} - - /* ==================== StudioFxTransform ==================== */ -void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) +static void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) { switch( ent->curstate.renderfx ) { @@ -710,33 +700,36 @@ void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) case kRenderFxHologram: if( !gEngfuncs.COM_RandomLong( 0, 49 )) { - int axis = gEngfuncs.COM_RandomLong( 0, 1 ); + int axis = gEngfuncs.COM_RandomLong( 0, 1 ); - if( axis == 1 ) axis = 2; // choose between x & z + if( axis == 1 ) + axis = 2; // choose between x & z VectorScale( transform[axis], gEngfuncs.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] ); } else if( !gEngfuncs.COM_RandomLong( 0, 49 )) { - float offset; - int axis = gEngfuncs.COM_RandomLong( 0, 1 ); + float offset; + int axis = gEngfuncs.COM_RandomLong( 0, 1 ); - if( axis == 1 ) axis = 2; // choose between x & z + if( axis == 1 ) + axis = 2; // choose between x & z offset = gEngfuncs.COM_RandomFloat( -10.0f, 10.0f ); transform[gEngfuncs.COM_RandomLong( 0, 2 )][3] += offset; } break; case kRenderFxExplode: - { - float scale; + { + float scale; - scale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f; - if( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200% + scale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f; + if( scale > 2.0f ) + scale = 2.0f; // don't blow up more than 200% - transform[0][1] *= scale; - transform[1][1] *= scale; - transform[2][1] *= scale; - } - break; + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; } } @@ -746,13 +739,13 @@ StudioCalcBoneAdj ==================== */ -void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +static void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) { - mstudiobonecontroller_t *pbonecontroller; - float value = 0.0f; - int i, j; + mstudiobonecontroller_t *pbonecontroller; + float value = 0.0f; + int i, j; - pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex ); for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) { @@ -763,7 +756,7 @@ void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, cons // mouth hardcoded at controller 4 value = (float)mouthopen / 64.0f; value = bound( 0.0f, value, 1.0f ); - value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + value = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end; } else if( i < 4 ) { @@ -772,20 +765,20 @@ void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, cons { if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 ) { - int a = (pcontroller1[i] + 128) % 256; - int b = (pcontroller2[i] + 128) % 256; - value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start; + int a = ( pcontroller1[i] + 128 ) % 256; + int b = ( pcontroller2[i] + 128 ) % 256; + value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128 ) * ( 360.0f / 256.0f ) + pbonecontroller[j].start; } else { - value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start; + value = (( pcontroller1[i] * dadt + ( pcontroller2[i] ) * ( 1.0f - dadt ))) * ( 360.0f / 256.0f ) + pbonecontroller[j].start; } } else { - value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f; + value = ( pcontroller1[i] * dadt + pcontroller2[i] * ( 1.0f - dadt )) / 255.0f; value = bound( 0.0f, value, 1.0f ); - value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + value = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end; } } @@ -811,12 +804,12 @@ StudioCalcRotations ==================== */ -void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +static void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) { - int i, frame; - float adj[MAXSTUDIOCONTROLLERS]; - float s, dadt; - mstudiobone_t *pbone; + int i, frame; + float adj[MAXSTUDIOCONTROLLERS]; + float s, dadt; + mstudiobone_t *pbone; // bah, fix this bug with changing sequences too fast if( f > pseqdesc->numframes - 1 ) @@ -834,22 +827,22 @@ void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudiose frame = (int)f; dadt = R_StudioEstimateInterpolant( e ); - s = (f - frame); + s = ( f - frame ); // add in programtic controllers - pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); R_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen ); for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ ) - { - gEngfuncs.R_StudioCalcBoneQuaternion( frame, s, pbone, panim, adj, q[i] ); - gEngfuncs.R_StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); - } + R_StudioCalcBones( frame, s, pbone, panim, adj, pos[i], q[i] ); - if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; - if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f; - if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f; + if( pseqdesc->motiontype & STUDIO_X ) + pos[pseqdesc->motionbone][0] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Y ) + pos[pseqdesc->motionbone][1] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Z ) + pos[pseqdesc->motionbone][2] = 0.0f; } /* @@ -858,27 +851,27 @@ StudioMergeBones ==================== */ -void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) +static void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) { - int i, j; - mstudiobone_t *pbones; - mstudioseqdesc_t *pseqdesc; - mstudioanim_t *panim; - matrix3x4 bonematrix; - static vec4_t q[MAXSTUDIOBONES]; - static float pos[MAXSTUDIOBONES][3]; - float f; + int i, j; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec4_t q[MAXSTUDIOBONES]; + static float pos[MAXSTUDIOBONES][3]; + float f; - if( e->curstate.sequence >= m_pStudioHeader->numseq ) + if( e->curstate.sequence >= m_pStudioHeader->numseq ) e->curstate.sequence = 0; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); - pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); for( i = 0; i < m_pStudioHeader->numbones; i++ ) { @@ -918,45 +911,45 @@ StudioSetupBones ==================== */ -void R_StudioSetupBones( cl_entity_t *e ) +static void R_StudioSetupBones( cl_entity_t *e ) { - float f; - mstudiobone_t *pbones; - mstudioseqdesc_t *pseqdesc; - mstudioanim_t *panim; - matrix3x4 bonematrix; - static vec3_t pos[MAXSTUDIOBONES]; - static vec4_t q[MAXSTUDIOBONES]; - static vec3_t pos2[MAXSTUDIOBONES]; - static vec4_t q2[MAXSTUDIOBONES]; - static vec3_t pos3[MAXSTUDIOBONES]; - static vec4_t q3[MAXSTUDIOBONES]; - static vec3_t pos4[MAXSTUDIOBONES]; - static vec4_t q4[MAXSTUDIOBONES]; - int i; + float f; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec3_t pos[MAXSTUDIOBONES]; + static vec4_t q[MAXSTUDIOBONES]; + static vec3_t pos2[MAXSTUDIOBONES]; + static vec4_t q2[MAXSTUDIOBONES]; + static vec3_t pos3[MAXSTUDIOBONES]; + static vec4_t q3[MAXSTUDIOBONES]; + static vec3_t pos4[MAXSTUDIOBONES]; + static vec4_t q4[MAXSTUDIOBONES]; + int i; if( e->curstate.sequence >= m_pStudioHeader->numseq ) e->curstate.sequence = 0; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); if( pseqdesc->numblends > 1 ) { - float s; - float dadt; + float s; + float dadt; panim += m_pStudioHeader->numbones; R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f ); dadt = R_StudioEstimateInterpolant( e ); - s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + s = ( e->curstate.blending[0] * dadt + e->latched.prevblending[0] * ( 1.0f - dadt )) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { @@ -966,22 +959,22 @@ void R_StudioSetupBones( cl_entity_t *e ) panim += m_pStudioHeader->numbones; R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f ); - s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + s = ( e->curstate.blending[0] * dadt + e->latched.prevblending[0] * ( 1.0f - dadt )) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); - s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); + s = ( e->curstate.blending[1] * dadt + e->latched.prevblending[1] * ( 1.0f - dadt )) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); } } if( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq )) { // blend from last sequence - static vec3_t pos1b[MAXSTUDIOBONES]; - static vec4_t q1b[MAXSTUDIOBONES]; - float s; + static vec3_t pos1b[MAXSTUDIOBONES]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + e->latched.prevsequence; panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); // clip prevframe @@ -992,8 +985,8 @@ void R_StudioSetupBones( cl_entity_t *e ) panim += m_pStudioHeader->numbones; R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe ); - s = (e->latched.prevseqblending[0]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); + s = ( e->latched.prevseqblending[0] ) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { @@ -1003,16 +996,16 @@ void R_StudioSetupBones( cl_entity_t *e ) panim += m_pStudioHeader->numbones; R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe ); - s = (e->latched.prevseqblending[0]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + s = ( e->latched.prevseqblending[0] ) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); - s = (e->latched.prevseqblending[1]) / 255.0f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); + s = ( e->latched.prevseqblending[1] ) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); } } s = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f; - gEngfuncs.R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); } else { @@ -1020,17 +1013,17 @@ void R_StudioSetupBones( cl_entity_t *e ) e->latched.prevframe = f; } - pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); // calc gait animation if( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) { - qboolean copy_bones = true; + qboolean copy_bones = true; if( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq ) m_pPlayerInfo->gaitsequence = 0; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pPlayerInfo->gaitsequence; panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); @@ -1042,7 +1035,8 @@ void R_StudioSetupBones( cl_entity_t *e ) else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) copy_bones = true; - if( !copy_bones ) continue; + if( !copy_bones ) + continue; VectorCopy( pos2[i], pos[i] ); Vector4Copy( q2[i], q[i] ); @@ -1077,10 +1071,10 @@ StudioSaveBones */ static void R_StudioSaveBones( void ) { - mstudiobone_t *pbones; - int i; + mstudiobone_t *pbones; + int i; - pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); g_studio.cached_numbones = m_pStudioHeader->numbones; for( i = 0; i < m_pStudioHeader->numbones; i++ ) @@ -1098,11 +1092,11 @@ StudioBuildNormalTable NOTE: m_pSubModel must be set ==================== */ -void R_StudioBuildNormalTable( void ) +static void R_StudioBuildNormalTable( void ) { - cl_entity_t *e = RI.currententity; - mstudiomesh_t *pmesh; - int i, j; + cl_entity_t *e = RI.currententity; + mstudiomesh_t *pmesh; + int i, j; Assert( m_pSubModel != NULL ); @@ -1115,14 +1109,15 @@ void R_StudioBuildNormalTable( void ) for( j = 0; j < m_pSubModel->nummesh; j++ ) { - short *ptricmds; + short *ptricmds; - pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; - ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex ); while(( i = *( ptricmds++ ))) { - if( i < 0 ) i = -i; + if( i < 0 ) + i = -i; for( ; i > 0; i--, ptricmds += 4 ) { @@ -1138,7 +1133,8 @@ void R_StudioBuildNormalTable( void ) if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) TriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 ); - else TriColor4ub( 255, 255, 255, 255 ); + else + TriColor4ub( 255, 255, 255, 255 ); } /* @@ -1149,12 +1145,12 @@ NOTE: m_pSubModel must be set g_studio.verts must be computed ==================== */ -void R_StudioGenerateNormals( void ) +static void R_StudioGenerateNormals( void ) { - int v0, v1, v2; - vec3_t e0, e1, norm; - mstudiomesh_t *pmesh; - int i, j; + int v0, v1, v2; + vec3_t e0, e1, norm; + mstudiomesh_t *pmesh; + int i, j; Assert( m_pSubModel != NULL ); @@ -1163,10 +1159,10 @@ void R_StudioGenerateNormals( void ) for( j = 0; j < m_pSubModel->nummesh; j++ ) { - short *ptricmds; + short *ptricmds; - pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; - ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex ); while(( i = *( ptricmds++ ))) { @@ -1203,7 +1199,7 @@ void R_StudioGenerateNormals( void ) { if( i > 2 ) { - qboolean odd = false; + qboolean odd = false; v0 = ptricmds[0]; ptricmds += 4; v1 = ptricmds[0]; ptricmds += 4; @@ -1220,8 +1216,10 @@ void R_StudioGenerateNormals( void ) VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] ); VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] ); - if( odd ) v1 = v2; - else v0 = v2; + if( odd ) + v1 = v2; + else + v0 = v2; odd = !odd; } @@ -1244,41 +1242,41 @@ StudioSetupChrome ==================== */ -void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) +static void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) { - float n; + float n; if( g_studio.chromeage[bone] != g_studio.framecount ) { // calculate vectors from the viewer to the bone. This roughly adjusts for position - vec3_t chromeupvec; // g_studio.chrome t vector in world reference frame - vec3_t chromerightvec; // g_studio.chrome s vector in world reference frame - vec3_t tmp; // vector pointing at bone in world reference frame + vec3_t chromeupvec; // g_studio.chrome t vector in world reference frame + vec3_t chromerightvec; // g_studio.chrome s vector in world reference frame + vec3_t tmp; // vector pointing at bone in world reference frame VectorNegate( g_studio.chrome_origin, tmp ); - tmp[0] += g_studio.bonestransform[bone][0][3]; - tmp[1] += g_studio.bonestransform[bone][1][3]; - tmp[2] += g_studio.bonestransform[bone][2][3]; + tmp[0] += g_studio.lighttransform[bone][0][3]; + tmp[1] += g_studio.lighttransform[bone][1][3]; + tmp[2] += g_studio.lighttransform[bone][2][3]; VectorNormalize( tmp ); CrossProduct( tmp, RI.vright, chromeupvec ); VectorNormalize( chromeupvec ); - CrossProduct( tmp, chromeupvec, chromerightvec ); + CrossProduct( chromeupvec, tmp, chromerightvec ); VectorNormalize( chromerightvec ); - Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] ); - Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] ); + Matrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromeupvec, g_studio.chromeup[bone] ); + Matrix3x4_VectorIRotate( g_studio.lighttransform[bone], chromerightvec, g_studio.chromeright[bone] ); g_studio.chromeage[bone] = g_studio.framecount; } // calc s coord n = DotProduct( normal, g_studio.chromeright[bone] ); - pchrome[0] = (n + 1.0f) * 32.0f; + pchrome[0] = ( n + 1.0f ) * 32.0f; // calc t coord n = DotProduct( normal, g_studio.chromeup[bone] ); - pchrome[1] = (n + 1.0f) * 32.0f; + pchrome[1] = ( n + 1.0f ) * 32.0f; } /* @@ -1289,20 +1287,20 @@ StudioCalcAttachments */ static void R_StudioCalcAttachments( void ) { - mstudioattachment_t *pAtt; - vec3_t forward, bonepos; - vec3_t localOrg, localAng; - int i; + mstudioattachment_t *pAtt; + vec3_t forward, bonepos; + vec3_t localOrg, localAng; + int i; // calculate attachment points - pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex ); for( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) { Matrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] ); VectorSubtract( RI.currententity->attachment[i], RI.currententity->origin, localOrg ); Matrix3x4_OriginFromMatrix( g_studio.lighttransform[pAtt[i].bone], bonepos ); - VectorSubtract( localOrg, bonepos, forward ); // make forward + VectorSubtract( localOrg, bonepos, forward ); // make forward VectorNormalizeFast( forward ); VectorAngles( forward, localAng ); } @@ -1316,20 +1314,22 @@ pfnStudioSetupModel */ static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel ) { - int index; + int index; if( bodypart > m_pStudioHeader->numbodyparts ) bodypart = 0; - m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex ) + bodypart; index = RI.currententity->curstate.body / m_pBodyPart->base; index = index % m_pBodyPart->nummodels; - m_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index; + m_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex ) + index; - if( ppbodypart ) *ppbodypart = m_pBodyPart; - if( ppsubmodel ) *ppsubmodel = m_pSubModel; + if( ppbodypart ) + *ppbodypart = m_pBodyPart; + if( ppsubmodel ) + *ppsubmodel = m_pSubModel; } /* @@ -1352,15 +1352,15 @@ R_StudioDynamicLight =============== */ -void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) +static void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) { - movevars_t *mv = gEngfuncs.pfnGetMoveVars(); - vec3_t lightDir, vecSrc, vecEnd; - vec3_t origin, dist, finalLight; - float add, radius, total; - colorVec light; - uint lnum; - dlight_t *dl; + movevars_t *mv = tr.movevars; + vec3_t lightDir, vecSrc, vecEnd; + vec3_t origin, dist, finalLight; + float add, radius, total; + colorVec light; + uint lnum; + dlight_t *dl; if( !plight || !ent || !ent->model ) return; @@ -1378,7 +1378,8 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) // determine plane to get lightvalues from: ceil or floor if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); - else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); + else + VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); VectorCopy( ent->origin, origin ); @@ -1387,10 +1388,10 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) { - msurface_t *psurf = NULL; - pmtrace_t trace; + msurface_t *psurf = NULL; + pmtrace_t trace; - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) + if( FBitSet( gp_host->features, ENGINE_WRITE_LARGE_COORD )) { vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; @@ -1404,23 +1405,25 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) } trace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY ); - if( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd ); - else psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd ); + if( trace.ent > 0 ) + psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd ); + else + psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd ); if( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY ))) { VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); - light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); - light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); - light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + light.r = mv->skycolor_r; + light.g = mv->skycolor_g; + light.b = mv->skycolor_b; } } - if(( light.r + light.g + light.b ) < 16 ) // TESTTEST + if(( light.r + light.g + light.b ) == 0 ) { - colorVec gcolor; - float grad[4]; + colorVec gcolor; + float grad[4]; VectorScale( lightDir, 2048.0f, vecEnd ); VectorAdd( vecEnd, vecSrc, vecEnd ); @@ -1465,18 +1468,26 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) } } + if( ent->curstate.renderfx == kRenderFxLightMultiplier && ent->curstate.iuser4 != 10 ) + { + light.r *= ent->curstate.iuser4 / 10.0f; + light.g *= ent->curstate.iuser4 / 10.0f; + light.b *= ent->curstate.iuser4 / 10.0f; + } + VectorSet( finalLight, light.r, light.g, light.b ); ent->cvFloorColor = light; total = Q_max( Q_max( light.r, light.g ), light.b ); - if( total == 0.0f ) total = 1.0f; + if( total == 0.0f ) + total = 1.0f; // scale lightdir by light intentsity VectorScale( lightDir, total, lightDir ); for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { - dl = gEngfuncs.GetDynamicLight( lnum ); + dl = &tr.dlights[lnum]; if( dl->die < g_studio.time || !r_dynamic->value ) continue; @@ -1484,7 +1495,7 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) VectorSubtract( ent->origin, dl->origin, dist ); radius = VectorLength( dist ); - add = (dl->radius - radius); + add = ( dl->radius - radius ); if( add > 0.0f ) { @@ -1492,19 +1503,21 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) if( radius > 1.0f ) VectorScale( dist, ( add / radius ), dist ); - else VectorScale( dist, add, dist ); + else + VectorScale( dist, add, dist ); VectorAdd( lightDir, dist, lightDir ); - finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; - finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; - finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; + finalLight[0] += dl->color.r * ( add / 256.0f ); + finalLight[1] += dl->color.g * ( add / 256.0f ); + finalLight[2] += dl->color.b * ( add / 256.0f ); } } if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) add = 0.6f; - else add = 0.9f; + else + add = bound( 0.75f, v_direct->value, 1.0f ); VectorScale( lightDir, add, lightDir ); @@ -1519,7 +1532,8 @@ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) plight->color[1] = finalLight[1] * ( 1.0f / total ); plight->color[2] = finalLight[2] * ( 1.0f / total ); } - else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + else + VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); if( plight->ambientlight > 128 ) plight->ambientlight = 128; @@ -1536,14 +1550,14 @@ pfnStudioEntityLight =============== */ -void R_StudioEntityLight( alight_t *lightinfo ) +static void R_StudioEntityLight( alight_t *lightinfo ) { - int lnum, i, j, k; - float minstrength, dist2, f, r2; - float lstrength[MAX_LOCALLIGHTS]; - cl_entity_t *ent = RI.currententity; - vec3_t mid, origin, pos; - dlight_t *el; + int lnum, i, j, k; + float minstrength, dist2, f, r2; + float lstrength[MAX_LOCALLIGHTS]; + cl_entity_t *ent = RI.currententity; + vec3_t mid, origin, pos; + dlight_t *el; g_studio.numlocallights = 0; @@ -1559,17 +1573,19 @@ void R_StudioEntityLight( alight_t *lightinfo ) for( lnum = 0; lnum < MAX_ELIGHTS; lnum++ ) { - el = gEngfuncs.GetEntityLight( lnum ); + el = &tr.elights[lnum]; if( el->die < g_studio.time || el->radius <= 0.0f ) continue; if(( el->key & 0xFFF ) == ent->index ) { - int att = (el->key >> 12) & 0xF; + int att = ( el->key >> 12 ) & 0xF; - if( att ) VectorCopy( ent->attachment[att], el->origin ); - else VectorCopy( ent->origin, el->origin ); + if( att ) + VectorCopy( ent->attachment[att], el->origin ); + else + VectorCopy( ent->origin, el->origin ); } VectorCopy( el->origin, pos ); @@ -1578,8 +1594,10 @@ void R_StudioEntityLight( alight_t *lightinfo ) f = DotProduct( mid, mid ); r2 = el->radius * el->radius; - if( f > r2 ) minstrength = r2 / f; - else minstrength = 1.0f; + if( f > r2 ) + minstrength = r2 / f; + else + minstrength = 1.0f; if( minstrength > 0.05f ) { @@ -1594,13 +1612,14 @@ void R_StudioEntityLight( alight_t *lightinfo ) } } } - else k = g_studio.numlocallights; + else + k = g_studio.numlocallights; if( k != -1 ) { - g_studio.locallightcolor[k].r = gEngfuncs.LightToTexGamma( el->color.r ); - g_studio.locallightcolor[k].g = gEngfuncs.LightToTexGamma( el->color.g ); - g_studio.locallightcolor[k].b = gEngfuncs.LightToTexGamma( el->color.b ); + g_studio.locallightcolor[k][0] = LinearGammaTable( el->color.r << 2 ); + g_studio.locallightcolor[k][1] = LinearGammaTable( el->color.g << 2 ); + g_studio.locallightcolor[k][2] = LinearGammaTable( el->color.b << 2 ); g_studio.locallightR2[k] = r2; g_studio.locallight[k] = el; lstrength[k] = minstrength; @@ -1618,10 +1637,10 @@ R_StudioSetupLighting =============== */ -void R_StudioSetupLighting( alight_t *plight ) +static void R_StudioSetupLighting( alight_t *plight ) { - float scale = 1.0f; - int i; + float scale = 1.0f; + int i; if( !m_pStudioHeader || !plight ) return; @@ -1636,7 +1655,8 @@ void R_StudioSetupLighting( alight_t *plight ) for( i = 0; i < m_pStudioHeader->numbones; i++ ) { Matrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] ); - if( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled + if( scale > 1.0f ) + VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled } VectorCopy( plight->color, g_studio.lightcolor ); @@ -1648,9 +1668,9 @@ R_StudioLighting =============== */ -void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) +static void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) { - float illum; + float illum; if( FBitSet( flags, STUDIO_NF_FULLBRIGHT )) { @@ -1666,27 +1686,30 @@ void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) } else { - float r, lightcos; + float r, lightcos; - if( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] ); - else lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite - if( lightcos > 1.0f ) lightcos = 1.0f; + if( bone != -1 ) + lightcos = DotProduct( normal, g_studio.blightvec[bone] ); + else + lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) + lightcos = 1.0f; illum += g_studio.shadelight; r = SHADE_LAMBERT; - // do modified hemispherical lighting + // do modified hemispherical lighting if( r <= 1.0f ) { r += 1.0f; - lightcos = (( r - 1.0f ) - lightcos) / r; + lightcos = (( r - 1.0f ) - lightcos ) / r; if( lightcos > 0.0f ) illum += g_studio.shadelight * lightcos; } else { - lightcos = (lightcos + ( r - 1.0f )) / r; + lightcos = ( lightcos + ( r - 1.0f )) / r; if( lightcos > 0.0f ) illum -= g_studio.shadelight * lightcos; } @@ -1695,7 +1718,8 @@ void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) } illum = Q_min( illum, 255.0f ); - *lv = illum * (1.0f / 255.0f); + + *lv = LightToTexGamma( illum * 4 ) / 1023.0f; } /* @@ -1704,74 +1728,78 @@ R_LightLambert ==================== */ -void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) +static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out ) { - vec3_t finalLight; - vec3_t localLight; - int i; + vec3_t finalLight; + int i; - VectorCopy( color, finalLight ); + if( !g_studio.numlocallights ) + { + VectorScale( color, 255.0f, out ); + return; + } + + VectorSet( finalLight, 0, 0, 0 ); for( i = 0; i < g_studio.numlocallights; i++ ) { - float r, r2; + float r; - if( tr.fFlipViewModel ) - r = DotProduct( normal, light[i] ); - else r = -DotProduct( normal, light[i] ); + r = DotProduct( normal, light[i] ); + if( likely( !tr.fFlipViewModel )) + r = -r; if( r > 0.0f ) { + vec3_t localLight; + float temp; + if( light[i][3] == 0.0f ) { - r2 = DotProduct( light[i], light[i] ); + float r2 = DotProduct( light[i], light[i] ); if( r2 > 0.0f ) light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); - else light[i][3] = 0.0001f; + else + light[i][3] = 0.0001f; } - localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); - localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); - localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); - VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + temp = r * light[i][3]; - finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); - finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); - finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + VectorAddScalar( g_studio.locallightcolor[i], temp, localLight ); + VectorAdd( finalLight, localLight, finalLight ); } } - out[0] = finalLight[0] * 255; - out[1] = finalLight[1] * 255; - out[2] = finalLight[2] * 255; -} - -static void R_StudioSetColorBegin(short *ptricmds, vec3_t *pstudionorms ) -{ - float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; - rgba_t color; - - if( g_studio.numlocallights ) + if( !VectorIsNull( finalLight )) { - color[3] = tr.blend * 255; - R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); - //pglColor4ubv( color ); - TriColor4ub(color[0], color[1], color[2], color[3]); + for( i = 0; i < 3; i++ ) + { + float c = finalLight[i] + LinearGammaTable( color[i] * 1023.0f ); + + if( c > 1023.0f ) + out[i] = 255; + else + out[i] = ScreenGammaTable( c ) >> 2; + } } else { - if( RI.currententity->curstate.rendermode == kRenderTransColor ) - { - color[3] = tr.blend * 255; - VectorCopy( (byte*)&RI.currententity->curstate.rendercolor, color ); - //pglColor4ubv( color ); - TriColor4ub(color[0], color[1], color[2], color[3]); - } - else _TriColor4f( lv[0], lv[1], lv[2], tr.blend ); + VectorScale( color, 255.0f, out ); } } +static void R_StudioSetColorBegin( short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; + rgba_t color; + + color[3] = tr.blend * 255; + + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color ); + TriColor4ub( color[0], color[1], color[2], color[3] ); +} + /* ==================== @@ -1779,9 +1807,9 @@ R_LightStrength ==================== */ -void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) +static void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) { - int i; + int i; if( g_studio.lightage[bone] != g_studio.framecount ) { @@ -1809,11 +1837,11 @@ R_StudioSetupSkin */ static void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index ) { - mstudiotexture_t *ptexture = NULL; + mstudiotexture_t *ptexture = NULL; if( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME )) { - GL_Bind( XASH_TEXTURE0, tr.whiteTexture); + GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); return; } @@ -1821,12 +1849,15 @@ static void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index ) return; // NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable - if( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )->ptexture; - if( !ptexture ) ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex); // fallback + if( m_fDoRemap ) + ptexture = gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )->ptexture; + if( !ptexture ) + ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex ); // fallback if( r_lightmap->value && !r_fullbright->value ) GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); - else GL_Bind( XASH_TEXTURE0, ptexture[index].index ); + else + GL_Bind( XASH_TEXTURE0, ptexture[index].index ); } /* @@ -1838,24 +1869,28 @@ Doesn't changes studio global state at all */ mstudiotexture_t * GAME_EXPORT R_StudioGetTexture( cl_entity_t *e ) { - mstudiotexture_t *ptexture; - studiohdr_t *phdr, *thdr; + mstudiotexture_t *ptexture; + studiohdr_t *phdr, *thdr; if(( phdr = gEngfuncs.Mod_Extradata( mod_studio, e->model )) == NULL ) return NULL; thdr = m_pStudioHeader; - if( !thdr ) return NULL; + if( !thdr ) + return NULL; - if( m_fDoRemap ) ptexture = gEngfuncs.CL_GetRemapInfoForEntity( e )->ptexture; - else ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex); + if( m_fDoRemap ) + ptexture = gEngfuncs.CL_GetRemapInfoForEntity( e )->ptexture; + else + ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex ); return ptexture; } -void R_StudioSetRenderamt( int iRenderamt ) +static void R_StudioSetRenderamt( int iRenderamt ) { - if( !RI.currententity ) return; + if( !RI.currententity ) + return; RI.currententity->curstate.renderamt = iRenderamt; tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; @@ -1868,9 +1903,9 @@ R_StudioSetCullState sets true for enable backculling (for left-hand viewmodel) =============== */ -void R_StudioSetCullState( int iCull ) +static void R_StudioSetCullState( int iCull ) { - g_iBackFaceCull = iCull; + // This function intentionally does nothing } /* @@ -1880,25 +1915,25 @@ R_StudioRenderShadow just a prefab for render shadow =============== */ -void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) +static void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) { if( !p1 || !p2 || !p3 || !p4 ) return; - if( TriSpriteTexture( gEngfuncs.pfnGetModelByIndex( iSprite ), 0 )) + if( TriSpriteTexture( CL_ModelHandle( iSprite ), 0 )) { TriRenderMode( kRenderTransAlpha ); _TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); TriBegin( TRI_QUADS ); - TriTexCoord2f( 0.0f, 0.0f ); - TriVertex3fv( p1 ); - TriTexCoord2f( 0.0f, 1.0f ); - TriVertex3fv( p2 ); - TriTexCoord2f( 1.0f, 1.0f ); - TriVertex3fv( p3 ); - TriTexCoord2f( 1.0f, 0.0f ); - TriVertex3fv( p4 ); + TriTexCoord2f( 0.0f, 0.0f ); + TriVertex3fv( p1 ); + TriTexCoord2f( 0.0f, 1.0f ); + TriVertex3fv( p2 ); + TriTexCoord2f( 1.0f, 1.0f ); + TriVertex3fv( p3 ); + TriTexCoord2f( 1.0f, 0.0f ); + TriVertex3fv( p4 ); TriEnd(); TriRenderMode( kRenderNormal ); @@ -1930,10 +1965,10 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { - float *lv; - int i; + float *lv; + int i; while(( i = *( ptricmds++ ))) { @@ -1942,7 +1977,8 @@ _inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, floa TriBegin( TRI_TRIANGLE_FAN ); i = -i; } - else TriBegin( TRI_TRIANGLE_STRIP ); + else + TriBegin( TRI_TRIANGLE_STRIP ); for( ; i > 0; i--, ptricmds += 4 ) { @@ -1963,10 +1999,10 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { - float *lv; - int i; + float *lv; + int i; while(( i = *( ptricmds++ ))) { @@ -1975,7 +2011,8 @@ _inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) TriBegin( TRI_TRIANGLE_FAN ); i = -i; } - else TriBegin( TRI_TRIANGLE_STRIP ); + else + TriBegin( TRI_TRIANGLE_STRIP ); for( ; i > 0; i--, ptricmds += 4 ) { @@ -1995,12 +2032,12 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { - float *lv, *av; - int i, idx; - qboolean glowShell = (scale > 0.0f) ? true : false; - vec3_t vert; + float *lv, *av; + int i, idx; + qboolean glowShell = ( scale > 0.0f ) ? true : false; + vec3_t vert; while(( i = *( ptricmds++ ))) { @@ -2009,7 +2046,8 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa TriBegin( TRI_TRIANGLE_FAN ); i = -i; } - else TriBegin( TRI_TRIANGLE_STRIP ); + else + TriBegin( TRI_TRIANGLE_STRIP ); for( ; i > 0; i--, ptricmds += 4 ) { @@ -2048,38 +2086,39 @@ R_StudioDrawPoints */ static void R_StudioDrawPoints( void ) { - int i, j, k, m_skinnum; - float shellscale = 0.0f; - qboolean need_sort = false; - byte *pvertbone; - byte *pnormbone; - vec3_t *pstudioverts; - vec3_t *pstudionorms; - mstudiotexture_t *ptexture; - mstudiomesh_t *pmesh; - short *pskinref; - float lv_tmp; + int i, j, k, m_skinnum; + float shellscale = 0.0f; + qboolean need_sort = false; + byte *pvertbone; + byte *pnormbone; + vec3_t *pstudioverts; + vec3_t *pstudionorms; + mstudiotexture_t *ptexture; + mstudiomesh_t *pmesh; + short *pskinref; + float lv_tmp; - if( !m_pStudioHeader ) return; + if( !m_pStudioHeader ) + return; - // safety bounding the skinnum - m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); - ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); - pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); - pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex); + m_skinnum = RI.currententity->curstate.skin; + ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex ); + pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex ); + pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex ); - pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); - pstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex); - pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ); + pstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex ); + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex ); - pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); - if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); + pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex ); + if( m_skinnum > 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) + pskinref += ( m_skinnum * m_pStudioHeader->numskinref ); if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) { - mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); - mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex); - matrix3x4 skinMat; + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex ); + mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex ); + matrix3x4 skinMat; for( i = 0; i < m_pSubModel->numverts; i++ ) { @@ -2106,7 +2145,7 @@ static void R_StudioDrawPoints( void ) // generate shared normals for properly scaling glowing shell if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) { - float factor = (1.0f / 128.0f); + float factor = ( 1.0f / 128.0f ); shellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor ); R_StudioBuildNormalTable(); R_StudioGenerateNormals(); @@ -2120,7 +2159,7 @@ static void R_StudioDrawPoints( void ) g_studio.meshes[j].flags = g_nFaceFlags; g_studio.meshes[j].mesh = &pmesh[j]; - if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED | STUDIO_NF_ADDITIVE )) need_sort = true; if( RI.currententity->curstate.rendermode == kRenderTransAdd ) @@ -2138,7 +2177,8 @@ static void R_StudioDrawPoints( void ) { if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS )) R_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] ); - else R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); + else + R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); @@ -2147,23 +2187,23 @@ static void R_StudioDrawPoints( void ) } } - if( r_studio_sort_textures->value && need_sort ) + if( r_studio_sort_textures.value && need_sort ) { // resort opaque and translucent meshes draw order - qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), (void*)R_StudioMeshCompare ); + qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), (void *)R_StudioMeshCompare ); } // NOTE: rewind normals at start - pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex ); for( j = 0; j < m_pSubModel->nummesh; j++ ) { - float oldblend = tr.blend; - short *ptricmds; - float s, t; + float oldblend = tr.blend; + short *ptricmds; + float s, t; pmesh = g_studio.meshes[j].mesh; - ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex ); g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags; @@ -2172,9 +2212,9 @@ static void R_StudioDrawPoints( void ) if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) { - //pglEnable( GL_ALPHA_TEST ); - //pglAlphaFunc( GL_GREATER, 0.5f ); - //pglDepthMask( GL_TRUE ); + // pglEnable( GL_ALPHA_TEST ); + // pglAlphaFunc( GL_GREATER, 0.5f ); + // pglDepthMask( GL_TRUE ); if( R_ModelOpaque( RI.currententity->curstate.rendermode )) tr.blend = 1.0f; } @@ -2182,12 +2222,12 @@ static void R_StudioDrawPoints( void ) { if( R_ModelOpaque( RI.currententity->curstate.rendermode )) { - //pglBlendFunc( GL_ONE, GL_ONE ); - //pglDepthMask( GL_FALSE ); - //pglEnable( GL_BLEND ); + // pglBlendFunc( GL_ONE, GL_ONE ); + // pglDepthMask( GL_FALSE ); + // pglEnable( GL_BLEND ); R_AllowFog( false ); } - //else pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + // else pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); } R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); @@ -2197,18 +2237,19 @@ static void R_StudioDrawPoints( void ) R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) R_StudioDrawFloatMesh( ptricmds, pstudionorms ); - else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); + else + R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); } if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) { - //pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); - //pglDisable( GL_ALPHA_TEST ); + // pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); + // pglDisable( GL_ALPHA_TEST ); } else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode )) { - //pglDepthMask( GL_TRUE ); - //pglDisable( GL_BLEND ); + // pglDepthMask( GL_TRUE ); + // pglDisable( GL_BLEND ); R_AllowFog( true ); } @@ -2225,51 +2266,6 @@ R_StudioDrawHulls */ static void R_StudioDrawHulls( void ) { -#if 0 - float alpha, lv; - int i, j; - - if( r_drawentities->value == 4 ) - alpha = 0.5f; - else alpha = 1.0f; - - GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); - //pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - - for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) - { - mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); - vec3_t tmp, p[8]; - - for( j = 0; j < 8; j++ ) - { - tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; - tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; - tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; - - Matrix3x4_VectorTransform( g_studio.bonestransform[pbbox[i].bone], tmp, p[j] ); - } - - j = (pbbox[i].group % 8); - - TriBegin( TRI_QUADS ); - _TriColor4f( hullcolor[j][0], hullcolor[j][1], hullcolor[j][2], alpha ); - - for( j = 0; j < 6; j++ ) - { - VectorClear( tmp ); - tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; - R_StudioLighting( &lv, pbbox[i].bone, 0, tmp ); - - TriBrightness( lv ); - TriVertex3fv( p[boxpnt[j][0]] ); - TriVertex3fv( p[boxpnt[j][1]] ); - TriVertex3fv( p[boxpnt[j][2]] ); - TriVertex3fv( p[boxpnt[j][3]] ); - } - TriEnd(); - } -#endif } /* @@ -2280,12 +2276,12 @@ R_StudioDrawAbsBBox */ static void R_StudioDrawAbsBBox( void ) { - vec3_t p[8], tmp; - float lv; - int i; + vec3_t p[8], tmp; + float lv; + int i; // looks ugly, skip - if( RI.currententity == gEngfuncs.GetViewModel() ) + if( RI.currententity == tr.viewent ) return; if( !R_StudioComputeBBox( p )) @@ -2299,7 +2295,7 @@ static void R_StudioDrawAbsBBox( void ) for( i = 0; i < 6; i++ ) { VectorClear( tmp ); - tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; + tmp[i % 3] = ( i < 3 ) ? 1.0f : -1.0f; R_StudioLighting( &lv, -1, 0, tmp ); TriBrightness( lv ); @@ -2320,99 +2316,10 @@ R_StudioDrawBones */ static void R_StudioDrawBones( void ) { - mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); - vec3_t point; - int i; -#if 0 - pglDisable( GL_TEXTURE_2D ); - - for( i = 0; i < m_pStudioHeader->numbones; i++ ) - { - if( pbones[i].parent >= 0 ) - { - pglPointSize( 3.0f ); - pglColor3f( 1, 0.7f, 0 ); - pglBegin( GL_LINES ); - - Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); - pglVertex3fv( point ); - Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); - pglVertex3fv( point ); - - pglEnd(); - - pglColor3f( 0, 0, 0.8f ); - pglBegin( GL_POINTS ); - if( pbones[pbones[i].parent].parent != -1 ) - { - Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); - pglVertex3fv( point ); - } - Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); - pglVertex3fv( point ); - pglEnd(); - } - else - { - // draw parent bone node - pglPointSize( 5.0f ); - pglColor3f( 0.8f, 0, 0 ); - pglBegin( GL_POINTS ); - Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); - pglVertex3fv( point ); - pglEnd(); - } - } - - pglPointSize( 1.0f ); - pglEnable( GL_TEXTURE_2D ); -#endif } static void R_StudioDrawAttachments( void ) { - int i; -#if 0 - pglDisable( GL_TEXTURE_2D ); - pglDisable( GL_DEPTH_TEST ); - - for( i = 0; i < m_pStudioHeader->numattachments; i++ ) - { - mstudioattachment_t *pattachments; - vec3_t v[4]; - - pattachments = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); - Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].org, v[0] ); - Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[0], v[1] ); - Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[1], v[2] ); - Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[2], v[3] ); - - pglBegin( GL_LINES ); - pglColor3f( 1, 0, 0 ); - pglVertex3fv( v[0] ); - pglColor3f( 1, 1, 1 ); - pglVertex3fv (v[1] ); - pglColor3f( 1, 0, 0 ); - pglVertex3fv (v[0] ); - pglColor3f( 1, 1, 1 ); - pglVertex3fv (v[2] ); - pglColor3f( 1, 0, 0 ); - pglVertex3fv (v[0] ); - pglColor3f( 1, 1, 1 ); - pglVertex3fv( v[3] ); - pglEnd(); - - pglPointSize( 5.0f ); - pglColor3f( 0, 1, 0 ); - pglBegin( GL_POINTS ); - pglVertex3fv( v[0] ); - pglEnd(); - pglPointSize( 1.0f ); - } - - pglEnable( GL_TEXTURE_2D ); - pglEnable( GL_DEPTH_TEST ); -#endif } /* @@ -2423,13 +2330,8 @@ R_StudioSetRemapColors */ static void R_StudioSetRemapColors( int newTop, int newBottom ) { - gEngfuncs.CL_AllocRemapInfo( RI.currententity, RI.currentmodel, newTop, newBottom ); - - if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) - { - gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); + if( gEngfuncs.CL_EntitySetRemapColors( RI.currententity, RI.currentmodel, newTop, newBottom )) m_fDoRemap = true; - } } void R_StudioResetPlayerModels( void ) @@ -2445,8 +2347,11 @@ R_StudioSetupPlayerModel */ static model_t *R_StudioSetupPlayerModel( int index ) { - player_info_t *info = gEngfuncs.pfnPlayerInfo( index ); - player_model_t *state; + player_info_t *info = gEngfuncs.pfnPlayerInfo( index ); + player_model_t *state; + + if( index < 0 || index >= gp_cl->maxclients ) + return NULL; state = &g_studio.player_models[index]; @@ -2462,7 +2367,8 @@ static model_t *R_StudioSetupPlayerModel( int index ) if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); - else state->model = NULL; + else + state->model = NULL; if( !state->model ) state->model = RI.currententity->model; @@ -2487,18 +2393,20 @@ check for texture flags */ int R_GetEntityRenderMode( cl_entity_t *ent ) { - int i, opaque, trans; - mstudiotexture_t *ptexture; - cl_entity_t *oldent; - model_t *model; - studiohdr_t *phdr; + int i, opaque, trans; + mstudiotexture_t *ptexture; + cl_entity_t *oldent; + model_t *model = NULL; + studiohdr_t *phdr; oldent = RI.currententity; RI.currententity = ent; if( ent->player ) // check it for real playermodel model = R_StudioSetupPlayerModel( ent->curstate.number - 1 ); - else model = ent->model; + + if( !model ) + model = ent->model; RI.currententity = oldent; @@ -2512,14 +2420,15 @@ int R_GetEntityRenderMode( cl_entity_t *ent ) } return ent->curstate.rendermode; } - ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex); + ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex ); for( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ ) { // ignore chrome & additive it's just a specular-like effect if( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME )) trans++; - else opaque++; + else + opaque++; } // if model is more additive than opaque @@ -2536,11 +2445,11 @@ R_StudioClientEvents */ static void R_StudioClientEvents( void ) { - mstudioseqdesc_t *pseqdesc; - mstudioevent_t *pevent; - cl_entity_t *e = RI.currententity; - int i, sequence; - float end, start; + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + cl_entity_t *e = RI.currententity; + int i, sequence; + float end, start; if( g_studio.frametime == 0.0 ) return; // gamepaused @@ -2556,11 +2465,11 @@ static void R_StudioClientEvents( void ) if( FBitSet( e->curstate.effects, EF_MUZZLEFLASH )) { - dlight_t *el = gEngfuncs.CL_AllocElight( 0 ); + dlight_t *el = gEngfuncs.CL_AllocElight( 0 ); ClearBits( e->curstate.effects, EF_MUZZLEFLASH ); VectorCopy( e->attachment[0], el->origin ); - el->die = gpGlobals->time + 0.05f; + el->die = gp_cl->time + 0.05f; el->color.r = 255; el->color.g = 192; el->color.b = 64; @@ -2569,15 +2478,15 @@ static void R_StudioClientEvents( void ) } sequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 ); - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + sequence; // no events for this animation if( pseqdesc->numevents == 0 ) return; - end = R_StudioEstimateFrame( e, pseqdesc ); - start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps; - pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); + end = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); + start = end - e->curstate.framerate * gp_host->frametime * pseqdesc->fps; + pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex ); if( e->latched.sequencetime == e->curstate.animtime ) { @@ -2591,7 +2500,7 @@ static void R_StudioClientEvents( void ) if( pevent[i].event < EVENT_CLIENT ) continue; - if( (float)pevent[i].frame > start && pevent[i].frame <= end ) + if((float)pevent[i].frame > start && pevent[i].frame <= end ) gEngfuncs.pfnStudioEvent( &pevent[i], e ); } } @@ -2602,7 +2511,7 @@ R_StudioGetForceFaceFlags =============== */ -int R_StudioGetForceFaceFlags( void ) +static int R_StudioGetForceFaceFlags( void ) { return g_nForceFaceFlags; } @@ -2613,7 +2522,7 @@ R_StudioSetForceFaceFlags =============== */ -void R_StudioSetForceFaceFlags( int flags ) +static void R_StudioSetForceFaceFlags( int flags ) { g_nForceFaceFlags = flags; } @@ -2624,7 +2533,7 @@ pfnStudioSetHeader =============== */ -void R_StudioSetHeader( studiohdr_t *pheader ) +static void R_StudioSetHeader( studiohdr_t *pheader ) { m_pStudioHeader = pheader; m_fDoRemap = false; @@ -2636,7 +2545,7 @@ R_StudioSetRenderModel =============== */ -void R_StudioSetRenderModel( model_t *model ) +static void R_StudioSetRenderModel( model_t *model ) { RI.currentmodel = model; } @@ -2649,16 +2558,13 @@ R_StudioSetupRenderer */ static void R_StudioSetupRenderer( int rendermode ) { - studiohdr_t *phdr = m_pStudioHeader; - int i; + studiohdr_t *phdr = m_pStudioHeader; + int i; - if( rendermode > kRenderTransAdd ) rendermode = 0; + if( rendermode > kRenderTransAdd ) + rendermode = 0; g_studio.rendermode = bound( 0, rendermode, kRenderTransAdd ); - //pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - //pglDisable( GL_ALPHA_TEST ); - //pglShadeModel( GL_SMOOTH ); - // a point to setup local to world transform for boneweighted models if( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO )) { @@ -2678,11 +2584,6 @@ R_StudioRestoreRenderer */ static void R_StudioRestoreRenderer( void ) { - //if( g_studio.rendermode != kRenderNormal ) - //pglDisable( GL_BLEND ); - - //pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); - //pglShadeModel( GL_FLAT ); m_fDoRemap = false; } @@ -2692,7 +2593,7 @@ R_StudioSetChromeOrigin =============== */ -void R_StudioSetChromeOrigin( void ) +static void R_StudioSetChromeOrigin( void ) { VectorCopy( RI.vieworg, g_studio.chrome_origin ); } @@ -2706,7 +2607,7 @@ Xash3D is always works in hardware mode */ static int pfnIsHardware( void ) { - return 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D + return 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D } /* @@ -2717,17 +2618,17 @@ R_StudioDrawPointsShadow */ static void R_StudioDrawPointsShadow( void ) { - float *av, height; - float vec_x, vec_y; - mstudiomesh_t *pmesh; - vec3_t point; - int i, k; + float *av, height; + float vec_x, vec_y; + mstudiomesh_t *pmesh; + vec3_t point; + int i, k; if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) return; - //if( glState.stencilEnabled ) - //pglEnable( GL_STENCIL_TEST ); + // if( glState.stencilEnabled ) + // pglEnable( GL_STENCIL_TEST ); height = g_studio.lightspot[2] + 1.0f; vec_x = -g_studio.lightvec[0] * 8.0f; @@ -2735,10 +2636,10 @@ static void R_StudioDrawPointsShadow( void ) for( k = 0; k < m_pSubModel->nummesh; k++ ) { - short *ptricmds; + short *ptricmds; - pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + k; - ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex ) + k; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex ); r_stats.c_studio_polys += pmesh->numtris; @@ -2758,8 +2659,8 @@ static void R_StudioDrawPointsShadow( void ) for( ; i > 0; i--, ptricmds += 4 ) { av = g_studio.verts[ptricmds[0]]; - point[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] )); - point[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] )); + point[0] = av[0] - ( vec_x * ( av[2] - g_studio.lightspot[2] )); + point[1] = av[1] - ( vec_y * ( av[2] - g_studio.lightspot[2] )); point[2] = g_studio.lightspot[2] + 1.0f; TriVertex3fv( point ); @@ -2769,8 +2670,8 @@ static void R_StudioDrawPointsShadow( void ) } } - //if( glState.stencilEnabled ) - //pglDisable( GL_STENCIL_TEST ); + // if( glState.stencilEnabled ) + // pglDisable( GL_STENCIL_TEST ); } /* @@ -2780,35 +2681,9 @@ GL_StudioSetRenderMode set rendermode for studiomodel =============== */ -void GL_StudioSetRenderMode( int rendermode ) +static void GL_StudioSetRenderMode( int rendermode ) { GL_SetRenderMode( rendermode ); -#if 0 - switch( rendermode ) - { - case kRenderNormal: - break; - case kRenderTransColor: - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglEnable( GL_BLEND ); - break; - case kRenderTransAdd: - pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglColor4f( tr.blend, tr.blend, tr.blend, 1.0f ); - pglBlendFunc( GL_ONE, GL_ONE ); - pglDepthMask( GL_FALSE ); - pglEnable( GL_BLEND ); - break; - default: - pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend ); - pglDepthMask( GL_TRUE ); - pglEnable( GL_BLEND ); - break; - } -#endif } /* @@ -2822,28 +2697,6 @@ studio shadows with some asm tricks */ static void GL_StudioDrawShadow( void ) { -#if 0 - pglDepthMask( GL_TRUE ); - - if( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT )) - { - float color = 1.0 - (tr.blend * 0.5); - - pglDisable( GL_TEXTURE_2D ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglEnable( GL_BLEND ); - pglColor4f( 0.0f, 0.0f, 0.0f, 1.0f - color ); - - pglDepthFunc( GL_LESS ); - R_StudioDrawPointsShadow(); - pglDepthFunc( GL_LEQUAL ); - - pglEnable( GL_TEXTURE_2D ); - pglDisable( GL_BLEND ); - pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); - pglShadeModel( GL_SMOOTH ); - } -#endif } /* @@ -2852,9 +2705,9 @@ StudioRenderFinal ==================== */ -void R_StudioRenderFinal( void ) +static void R_StudioRenderFinal( void ) { - int i, rendermode; + int i, rendermode; rendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode; R_StudioSetupRenderer( rendermode ); @@ -2871,7 +2724,7 @@ void R_StudioRenderFinal( void ) { for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { - R_StudioSetupModel( i, (void**)&m_pBodyPart, (void**)&m_pSubModel ); + R_StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); GL_StudioSetRenderMode( rendermode ); R_StudioDrawPoints(); @@ -2895,42 +2748,8 @@ void R_StudioRenderFinal( void ) { R_StudioDrawAttachments(); } -#if 0 - if( r_drawentities->value == 7 ) - { - vec3_t origin; - - pglDisable( GL_TEXTURE_2D ); - pglDisable( GL_DEPTH_TEST ); - - Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin ); - - pglBegin( GL_LINES ); - pglColor3f( 1, 0.5, 0 ); - pglVertex3fv( origin ); - pglVertex3fv( g_studio.lightspot ); - pglEnd(); - - pglBegin( GL_LINES ); - pglColor3f( 0, 0.5, 1 ); - VectorMA( g_studio.lightspot, -64.0f, g_studio.lightvec, origin ); - pglVertex3fv( g_studio.lightspot ); - pglVertex3fv( origin ); - pglEnd(); - - pglPointSize( 5.0f ); - pglColor3f( 1, 0, 0 ); - pglBegin( GL_POINTS ); - pglVertex3fv( g_studio.lightspot ); - pglEnd(); - pglPointSize( 1.0f ); - - pglEnable( GL_DEPTH_TEST ); - pglEnable( GL_TEXTURE_2D ); - } R_StudioRestoreRenderer(); -#endif } /* @@ -2939,7 +2758,7 @@ StudioRenderModel ==================== */ -void R_StudioRenderModel( void ) +static void R_StudioRenderModel( void ) { R_StudioSetChromeOrigin(); R_StudioSetForceFaceFlags( 0 ); @@ -2968,10 +2787,10 @@ StudioEstimateGait ==================== */ -void R_StudioEstimateGait( entity_state_t *pplayer ) +static void R_StudioEstimateGait( entity_state_t *pplayer ) { - vec3_t est_velocity; - float dt; + vec3_t est_velocity; + float dt; dt = bound( 0.0f, g_studio.frametime, 1.0f ); @@ -2994,26 +2813,31 @@ void R_StudioEstimateGait( entity_state_t *pplayer ) if( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f ) { - float flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; + float flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; - flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; - if( flYawDiff > 180.0f ) flYawDiff -= 360.0f; - if( flYawDiff < -180.0f ) flYawDiff += 360.0f; + flYawDiff = flYawDiff - (int)( flYawDiff / 360 ) * 360; + if( flYawDiff > 180.0f ) + flYawDiff -= 360.0f; + if( flYawDiff < -180.0f ) + flYawDiff += 360.0f; if( dt < 0.25f ) flYawDiff *= dt * 4.0f; - else flYawDiff *= dt; + else + flYawDiff *= dt; m_pPlayerInfo->gaityaw += flYawDiff; - m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)( m_pPlayerInfo->gaityaw / 360 ) * 360; m_flGaitMovement = 0.0f; } else { m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F ); - if( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f; - if( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f; + if( m_pPlayerInfo->gaityaw > 180.0f ) + m_pPlayerInfo->gaityaw = 180.0f; + if( m_pPlayerInfo->gaityaw < -180.0f ) + m_pPlayerInfo->gaityaw = -180.0f; } } @@ -3024,18 +2848,18 @@ StudioProcessGait ==================== */ -void R_StudioProcessGait( entity_state_t *pplayer ) +static void R_StudioProcessGait( entity_state_t *pplayer ) { - mstudioseqdesc_t *pseqdesc; - int iBlend; - float dt, flYaw; // view direction relative to movement + mstudioseqdesc_t *pseqdesc; + int iBlend; + float dt, flYaw; // view direction relative to movement if( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq ) RI.currententity->curstate.sequence = 0; dt = bound( 0.0f, g_studio.frametime, 1.0f ); - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + RI.currententity->curstate.sequence; R_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] ); @@ -3047,9 +2871,11 @@ void R_StudioProcessGait( entity_state_t *pplayer ) // calc side to side turning flYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; - flYaw = flYaw - (int)(flYaw / 360) * 360; - if( flYaw < -180.0f ) flYaw = flYaw + 360.0f; - if( flYaw > 180.0f ) flYaw = flYaw - 360.0f; + flYaw = flYaw - (int)( flYaw / 360 ) * 360; + if( flYaw < -180.0f ) + flYaw = flYaw + 360.0f; + if( flYaw > 180.0f ) + flYaw = flYaw - 360.0f; if( flYaw > 120.0f ) { @@ -3065,32 +2891,35 @@ void R_StudioProcessGait( entity_state_t *pplayer ) } // adjust torso - RI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); - RI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); - RI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); - RI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[0] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); + RI.currententity->curstate.controller[1] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); + RI.currententity->curstate.controller[2] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); + RI.currententity->curstate.controller[3] = (( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0]; RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1]; RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2]; RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3]; RI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw; - if( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f; + if( RI.currententity->angles[YAW] < -0 ) + RI.currententity->angles[YAW] += 360.0f; RI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW]; if( pplayer->gaitsequence >= m_pStudioHeader->numseq ) pplayer->gaitsequence = 0; - pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + pplayer->gaitsequence; // calc gait frame if( pseqdesc->linearmovement[0] > 0 ) - m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; - else m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + m_pPlayerInfo->gaitframe += ( m_flGaitMovement / pseqdesc->linearmovement[0] ) * pseqdesc->numframes; + else + m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; // do modulo - m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; - if( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes; + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)( m_pPlayerInfo->gaitframe / pseqdesc->numframes ) * pseqdesc->numframes; + if( m_pPlayerInfo->gaitframe < 0 ) + m_pPlayerInfo->gaitframe += pseqdesc->numframes; } /* @@ -3101,13 +2930,13 @@ R_StudioDrawPlayer */ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) { - int m_nPlayerIndex; - alight_t lighting; - vec3_t dir; + int m_nPlayerIndex; + alight_t lighting; + vec3_t dir; m_nPlayerIndex = pplayer->number - 1; - if( m_nPlayerIndex < 0 || m_nPlayerIndex >= ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + if( m_nPlayerIndex < 0 || m_nPlayerIndex >= gp_cl->maxclients ) return 0; RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex ); @@ -3176,20 +3005,21 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) // copy attachments into global entity array if( RI.currententity->index > 0 ) { - cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); } } if( flags & STUDIO_RENDER ) { - if( cl_himodels->value && RI.currentmodel != RI.currententity->model ) + // change body if it's a menu entity + if( cl_himodels->value && ( RI.currentmodel != RI.currententity->model || !RI.drawWorld )) { // show highest resolution multiplayer model RI.currententity->curstate.body = 255; } - if( !( !gpGlobals->developer && ENGINE_GET_PARM( PARM_MAX_CLIENTS ) == 1 ) && ( RI.currentmodel == RI.currententity->model )) + if( !( !gpGlobals->developer && gp_cl->maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model )) RI.currententity->curstate.body = 1; // force helmet lighting.plightvec = dir; @@ -3206,10 +3036,14 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) g_nTopColor = m_pPlayerInfo->topcolor; g_nBottomColor = m_pPlayerInfo->bottomcolor; - if( g_nTopColor < 0 ) g_nTopColor = 0; - if( g_nTopColor > 360 ) g_nTopColor = 360; - if( g_nBottomColor < 0 ) g_nBottomColor = 0; - if( g_nBottomColor > 360 ) g_nBottomColor = 360; + if( g_nTopColor < 0 ) + g_nTopColor = 0; + if( g_nTopColor > 360 ) + g_nTopColor = 360; + if( g_nBottomColor < 0 ) + g_nBottomColor = 0; + if( g_nBottomColor > 360 ) + g_nBottomColor = 360; R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); @@ -3218,8 +3052,8 @@ static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) if( pplayer->weaponmodel ) { - cl_entity_t saveent = *RI.currententity; - model_t *pweaponmodel = gEngfuncs.pfnGetModelByIndex( pplayer->weaponmodel ); + cl_entity_t saveent = *RI.currententity; + model_t *pweaponmodel = CL_ModelHandle( pplayer->weaponmodel ); m_pStudioHeader = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, pweaponmodel ); @@ -3243,16 +3077,16 @@ R_StudioDrawModel */ static int R_StudioDrawModel( int flags ) { - alight_t lighting; - vec3_t dir; + alight_t lighting; + vec3_t dir; if( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer ) { - entity_state_t deadplayer; - int result; + entity_state_t deadplayer; + int result; - if( RI.currententity->curstate.renderamt <= 0 || - RI.currententity->curstate.renderamt > ENGINE_GET_PARM( PARM_MAX_CLIENTS ) ) + if( RI.currententity->curstate.renderamt <= 0 + || RI.currententity->curstate.renderamt > gp_cl->maxclients ) return 0; // get copy of player @@ -3293,7 +3127,8 @@ static int R_StudioDrawModel( int flags ) if( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW ) R_StudioMergeBones( RI.currententity, RI.currentmodel ); - else R_StudioSetupBones( RI.currententity ); + else + R_StudioSetupBones( RI.currententity ); R_StudioSaveBones(); @@ -3305,7 +3140,7 @@ static int R_StudioDrawModel( int flags ) // copy attachments into global entity array if( RI.currententity->index > 0 ) { - cl_entity_t *ent = gEngfuncs.GetEntityByIndex( RI.currententity->index ); + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); } } @@ -3322,7 +3157,7 @@ static int R_StudioDrawModel( int flags ) // get remap colors g_nTopColor = RI.currententity->curstate.colormap & 0xFF; - g_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8; + g_nBottomColor = ( RI.currententity->curstate.colormap & 0xFF00 ) >> 8; R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); @@ -3337,23 +3172,38 @@ static int R_StudioDrawModel( int flags ) R_StudioDrawModelInternal ================= */ -void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) +static void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) { if( !RI.drawWorld ) { if( e->player ) R_StudioDrawPlayer( flags, &e->curstate ); - else R_StudioDrawModel( flags ); + else + R_StudioDrawModel( flags ); } else { // select the properly method if( e->player ) pStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 )); - else pStudioDraw->StudioDrawModel( flags ); + else + pStudioDraw->StudioDrawModel( flags ); } } +static cl_entity_t *R_FindParentEntity( cl_entity_t *e, cl_entity_t **entities, uint num_entities ) +{ + uint i; + + for( i = 0; i < num_entities; i++ ) + { + if( entities[i]->index == e->curstate.aiment ) + return entities[i]; + } + + return NULL; +} + /* ================= R_DrawStudioModel @@ -3368,28 +3218,38 @@ void R_DrawStudioModel( cl_entity_t *e ) if( e->player ) { - R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + R_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS ); + } + else if( e->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *parent = CL_GetEntityByIndex( e->curstate.aiment ); + + if( !parent || !parent->model || parent->model->type != mod_studio ) + return; + + parent = R_FindParentEntity( e, tr.draw_list->solid_entities, tr.draw_list->num_solid_entities ); + + if( !parent ) + parent = R_FindParentEntity( e, tr.draw_list->trans_entities, tr.draw_list->num_trans_entities ); + + if( parent ) + { + RI.currententity = parent; + R_StudioDrawModelInternal( RI.currententity, 0 ); + VectorCopy( RI.currententity->curstate.origin, e->curstate.origin ); + VectorCopy( RI.currententity->origin, e->origin ); + RI.currententity = e; + + R_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS ); + } } else { - if( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 ) - { - cl_entity_t *parent = gEngfuncs.GetEntityByIndex( e->curstate.aiment ); - - if( parent && parent->model && parent->model->type == mod_studio ) - { - RI.currententity = parent; - R_StudioDrawModelInternal( RI.currententity, 0 ); - VectorCopy( parent->curstate.origin, e->curstate.origin ); - VectorCopy( parent->origin, e->origin ); - RI.currententity = e; - } - } - - R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + R_StudioDrawModelInternal( e, STUDIO_RENDER | STUDIO_EVENTS ); } } + /* ================= R_RunViewmodelEvents @@ -3397,7 +3257,7 @@ R_RunViewmodelEvents */ void R_RunViewmodelEvents( void ) { - int i; + int i; vec3_t simorg; if( r_drawviewmodel->value == 0 ) @@ -3410,14 +3270,14 @@ void R_RunViewmodelEvents( void ) if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer()) return; - RI.currententity = gEngfuncs.GetViewModel(); + RI.currententity = tr.viewent; if( !RI.currententity->model || RI.currententity->model->type != mod_studio ) return; R_StudioSetupTimings(); - gEngfuncs.GetPredictedOrigin( simorg ); + VectorCopy( gp_cl->simorg, simorg ); for( i = 0; i < 4; i++ ) VectorCopy( simorg, RI.currententity->attachment[i] ); RI.currentmodel = RI.currententity->model; @@ -3432,13 +3292,11 @@ R_GatherPlayerLight */ void R_GatherPlayerLight( void ) { - cl_entity_t *view = gEngfuncs.GetViewModel(); - colorVec c; + cl_entity_t *view = tr.viewent; + colorVec c; - tr.ignore_lightgamma = true; c = R_LightPoint( view->origin ); - tr.ignore_lightgamma = false; - gEngfuncs.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 ); + gEngfuncs.SetLocalLightLevel(( c.r + c.g + c.b ) / 3 ); } /* @@ -3448,7 +3306,7 @@ R_DrawViewModel */ void R_DrawViewModel( void ) { - cl_entity_t *view = gEngfuncs.GetViewModel(); + cl_entity_t *view = tr.viewent; R_GatherPlayerLight(); @@ -3472,21 +3330,21 @@ void R_DrawViewModel( void ) return; // adjust the depth range to prevent view model from poking into walls - //pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); + // pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); s_ziscale = (float)0x8000 * (float)0x10000 * 3.0f; RI.currentmodel = RI.currententity->model; // backface culling for left-handed weapons - if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) + if( R_AllowFlipViewModel( RI.currententity )) { tr.fFlipViewModel = true; - //pglFrontFace( GL_CW ); + // pglFrontFace( GL_CW ); } switch( RI.currententity->model->type ) { case mod_alias: - // R_DrawAliasModel( RI.currententity ); + // R_DrawAliasModel( RI.currententity ); break; case mod_studio: R_StudioSetupTimings(); @@ -3495,14 +3353,14 @@ void R_DrawViewModel( void ) } // restore depth range - //pglDepthRange( gldepthmin, gldepthmax ); + // pglDepthRange( gldepthmin, gldepthmax ); s_ziscale = (float)0x8000 * (float)0x10000; // backface culling for left-handed weapons - if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) + if( R_AllowFlipViewModel( RI.currententity )) { tr.fFlipViewModel = false; - //pglFrontFace( GL_CCW ); + // pglFrontFace( GL_CCW ); } } @@ -3515,23 +3373,23 @@ load model texture with unique name */ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture ) { - size_t size; - int flags = 0; - char texname[128], name[128], mdlname[128]; - texture_t *tx = NULL; + size_t size; + int flags = 0; + char texname[128], name[128], mdlname[128]; + texture_t *tx = NULL; if( ptexture->flags & STUDIO_NF_NORMALMAP ) - flags |= (TF_NORMALMAP); + flags |= ( TF_NORMALMAP ); // store some textures for remapping if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ) || !Q_strnicmp( ptexture->name, "remap", 5 )) { - int i, size; - char val[6]; - byte *pixels; + int i, size; + char val[6]; + byte *pixels; i = mod->numtextures; - mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); + mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t * )); size = ptexture->width * ptexture->height + 768; tx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size ); mod->textures[i] = tx; @@ -3541,9 +3399,9 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture { Q_strncpy( tx->name, "DM_Base", sizeof( tx->name )); tx->anim_min = PLATE_HUE_START; // topcolor start - tx->anim_max = PLATE_HUE_END; // topcolor end + tx->anim_max = PLATE_HUE_END; // topcolor end // bottomcolor start always equal is (topcolor end + 1) - tx->anim_total = SUIT_HUE_END;// bottomcolor end + tx->anim_total = SUIT_HUE_END; // bottomcolor end } else { @@ -3562,27 +3420,27 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture // the pixels immediately follow the structures pixels = (byte *)phdr + ptexture->index; - memcpy( tx+1, pixels, size ); + memcpy( tx + 1, pixels, size ); - ptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image + ptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image flags |= TF_FORCE_COLOR; - mod->numtextures++; // done + mod->numtextures++; // done } Q_strncpy( mdlname, mod->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) SetBits( flags, TF_NOMIPMAP ); // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it - //ptexture->index = (int)((byte *)phdr) + ptexture->index; - gEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index); + // ptexture->index = (int)((byte *)phdr) + ptexture->index; + gEngfuncs.Image_SetMDLPointer((byte *)phdr + ptexture->index ); size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; - if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_LOAD_DELUXEDATA ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + if( FBitSet( gp_host->features, ENGINE_LOAD_DELUXEDATA ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) flags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing // build the texname @@ -3607,14 +3465,14 @@ Mod_StudioLoadTextures */ void GAME_EXPORT Mod_StudioLoadTextures( model_t *mod, void *data ) { - studiohdr_t *phdr = (studiohdr_t *)data; - mstudiotexture_t *ptexture; - int i; + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; if( !phdr ) return; - ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + ptexture = (mstudiotexture_t *)(((byte *)phdr ) + phdr->textureindex ); if( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS ) { for( i = 0; i < phdr->numtextures; i++ ) @@ -3629,14 +3487,14 @@ Mod_StudioUnloadTextures */ void Mod_StudioUnloadTextures( void *data ) { - studiohdr_t *phdr = (studiohdr_t *)data; - mstudiotexture_t *ptexture; - int i; + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; if( !phdr ) return; - ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + ptexture = (mstudiotexture_t *)(((byte *)phdr ) + phdr->textureindex ); // release all textures for( i = 0; i < phdr->numtextures; i++ ) @@ -3649,7 +3507,7 @@ void Mod_StudioUnloadTextures( void *data ) static model_t *pfnModelHandle( int modelindex ) { - return gEngfuncs.pfnGetModelByIndex( modelindex ); + return CL_ModelHandle( modelindex ); } static void *pfnMod_CacheCheck( struct cache_user_s *c ) @@ -3669,7 +3527,7 @@ static void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu ) static cvar_t *pfnGetCvarPointer( const char *name ) { - return (cvar_t*)gEngfuncs.pfnGetCvarPointer( name, 0 ); + return (cvar_t *)gEngfuncs.pfnGetCvarPointer( name, 0 ); } static void *pfnMod_Calloc( int number, size_t size ) @@ -3677,7 +3535,7 @@ static void *pfnMod_Calloc( int number, size_t size ) return gEngfuncs.Mod_Calloc( number, size ); } -static engine_studio_api_t gStudioAPI = +static engine_studio_api_t gStudioAPI = { pfnMod_Calloc, pfnMod_CacheCheck, @@ -3708,13 +3566,13 @@ static engine_studio_api_t gStudioAPI = R_StudioDrawHulls, R_StudioDrawAbsBBox, R_StudioDrawBones, - (void*)R_StudioSetupSkin, + (void *)R_StudioSetupSkin, R_StudioSetRemapColors, R_StudioSetupPlayerModel, R_StudioClientEvents, R_StudioGetForceFaceFlags, R_StudioSetForceFaceFlags, - (void*)R_StudioSetHeader, + (void *)R_StudioSetHeader, R_StudioSetRenderModel, R_StudioSetupRenderer, R_StudioRestoreRenderer, @@ -3748,9 +3606,6 @@ void GAME_EXPORT CL_InitStudioAPI( void ) // trying to grab them from client.dll cl_righthand = gEngfuncs.pfnGetCvarPointer( "cl_righthand", 0 ); - if( cl_righthand == NULL ) - cl_righthand = gEngfuncs.Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" ); - // Xash will be used internal StudioModelRenderer if( gEngfuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI )) return; diff --git a/ref/soft/r_surf.c b/ref/soft/r_surf.c index e1d44681..3a08f1b4 100644 --- a/ref/soft/r_surf.c +++ b/ref/soft/r_surf.c @@ -22,49 +22,47 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mod_local.h" -drawsurf_t r_drawsurf; +drawsurf_t r_drawsurf; -uint lightleft, sourcesstep, blocksize, sourcetstep; -uint lightdelta, lightdeltastep; -uint lightright, lightleftstep, lightrightstep, blockdivshift; -unsigned blockdivmask; -void *prowdestbase; -pixel_t *pbasesource; -int surfrowbytes; // used by ASM files -unsigned *r_lightptr; -int r_stepback; -int r_lightwidth; -int r_numhblocks, r_numvblocks; -pixel_t *r_source, *r_sourcemax; +uint lightleft, sourcesstep, blocksize, sourcetstep; +uint lightdelta, lightdeltastep; +uint lightright, lightleftstep, lightrightstep, blockdivshift; +unsigned blockdivmask; +void *prowdestbase; +pixel_t *pbasesource; +int surfrowbytes; // used by ASM files +unsigned *r_lightptr; +int r_stepback; +int r_lightwidth; +int r_numhblocks, r_numvblocks; +pixel_t *r_source, *r_sourcemax; -void R_DrawSurfaceBlock8_mip0 (void); -void R_DrawSurfaceBlock8_mip1 (void); -void R_DrawSurfaceBlock8_mip2 (void); -void R_DrawSurfaceBlock8_mip3 (void); -void R_DrawSurfaceBlock8_Generic (void); -void R_DrawSurfaceBlock8_World (void); +void R_DrawSurfaceBlock8_mip0( void ); +void R_DrawSurfaceBlock8_mip1( void ); +void R_DrawSurfaceBlock8_mip2( void ); +void R_DrawSurfaceBlock8_mip3( void ); +void R_DrawSurfaceBlock8_Generic( void ); +void R_DrawSurfaceBlock8_World( void ); -static float worldlux_s, worldlux_t; +static float worldlux_s, worldlux_t; -static void (*surfmiptable[4])(void) = { +static void (*surfmiptable[4])( void ) = { R_DrawSurfaceBlock8_mip0, R_DrawSurfaceBlock8_mip1, R_DrawSurfaceBlock8_mip2, R_DrawSurfaceBlock8_mip3 }; -//void R_BuildLightMap (void); -extern unsigned blocklights[10240]; // allow some very large lightmaps +// void R_BuildLightMap (void); +extern unsigned blocklights[10240]; // allow some very large lightmaps float surfscale; qboolean r_cache_thrash; // set if surface cache is thrashing -int sc_size; -surfcache_t *sc_rover, *sc_base; +int sc_size; +surfcache_t *sc_rover, *sc_base; -static int rtable[MOD_FRAMES][MOD_FRAMES]; - -#if 1 +static int rtable[MOD_FRAMES][MOD_FRAMES]; static void R_BuildLightMap( void ); /* @@ -72,25 +70,21 @@ static void R_BuildLightMap( void ); R_AddDynamicLights =============== */ -void R_AddDynamicLights( msurface_t *surf ) +static void R_AddDynamicLights( const msurface_t *surf ) { - float dist, rad, minlight; - int lnum, s, t, sd, td, smax, tmax; - float sl, tl, sacc, tacc; - vec3_t impact, origin_l; - mextrasurf_t *info = surf->info; - int sample_frac = 1.0; - float sample_size; - mtexinfo_t *tex; - dlight_t *dl; - uint *bl; + const mextrasurf_t *info = surf->info; + int lnum, smax, tmax; + int sample_frac = 1.0; + float sample_size; + mtexinfo_t *tex; // no dlighted surfaces here - //if( !R_CountSurfaceDlights( surf )) return; + if( !surf->dlightbits ) + return; sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - smax = (info->lightextents[0] / sample_size) + 1; - tmax = (info->lightextents[1] / sample_size) + 1; + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; tex = surf->texinfo; if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) @@ -99,15 +93,22 @@ void R_AddDynamicLights( msurface_t *surf ) sample_frac = surf->texinfo->faceinfo->texture_step; else if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) sample_frac = LM_SAMPLE_EXTRASIZE; - else sample_frac = LM_SAMPLE_SIZE; + else + sample_frac = LM_SAMPLE_SIZE; } for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { - if( !FBitSet( surf->dlightbits, BIT( lnum ))) - continue; // not lit by this light + dlight_t *dl; + vec3_t impact, origin_l; + float dist, rad, minlight; + float sl, tl; + int t, monolight; - dl = gEngfuncs.GetDynamicLight( lnum ); + if( !FBitSet( surf->dlightbits, BIT( lnum ))) + continue; // not lit by this light + + dl = &tr.dlights[lnum]; // transform light origin to local bmodel space if( !tr.modelviewIdentity ) @@ -131,32 +132,38 @@ void R_AddDynamicLights( msurface_t *surf ) VectorCopy( origin_l, impact ); impact[surf->plane->type] -= dist; } - else VectorMA( origin_l, -dist, surf->plane->normal, impact ); + else + VectorMA( origin_l, -dist, surf->plane->normal, impact ); sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; - bl = blocklights; - for( t = 0, tacc = 0; t < tmax; t++, tacc += sample_size ) + monolight = LightToTexGamma(( dl->color.r + dl->color.g + dl->color.b ) / 3 * 4 ) * 3; + + for( t = 0; t < tmax; t++ ) { - td = (tl - tacc) * sample_frac; - if( td < 0 ) td = -td; + int td = ( tl - sample_size * t ) * sample_frac; + int s; - for( s = 0, sacc = 0; s < smax; s++, sacc += sample_size, bl += 1 ) + if( td < 0 ) + td = -td; + + for( s = 0; s < smax; s++ ) { - sd = (sl - sacc) * sample_frac; - if( sd < 0 ) sd = -sd; + int sd = ( sl - sample_size * s ) * sample_frac; + float dist; - if( sd > td ) dist = sd + (td >> 1); - else dist = td + (sd >> 1); + if( sd < 0 ) + sd = -sd; + + if( sd > td ) + dist = sd + ( td >> 1 ); + else + dist = td + ( sd >> 1 ); if( dist < minlight ) { - //printf("dlight %f\n", dist); - //*(void**)0 = 0; - bl[0] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( (dl->color.r + dl->color.g + dl->color.b ) / 3) * 3) / 256; - //bl[1] += ((int)((rad - dist) * 256) * 2.5) / 256; - //bl[2] += ((int)((rad - dist) * 256) * 2.5) / 256; + blocklights[( s + ( t * smax ))] += ((int)(( rad - dist ) * 256 ) * monolight ) / 256; } } } @@ -174,25 +181,15 @@ format in r_blocklights */ static void R_BuildLightMap( void ) { - int smax, tmax; - uint *bl, scale; - int i, map, size, s, t; - int sample_size; - msurface_t *surf = r_drawsurf.surf; - mextrasurf_t *info = surf->info; - color24 *lm; - qboolean dynamic = 0; + int map, t, i; + const msurface_t *surf = r_drawsurf.surf; + const mextrasurf_t *info = surf->info; + const int sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); + int smax = ( info->lightextents[0] / sample_size ) + 1; + int tmax = ( info->lightextents[1] / sample_size ) + 1; + int size = smax * tmax; - sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - smax = ( info->lightextents[0] / sample_size ) + 1; - tmax = ( info->lightextents[1] / sample_size ) + 1; - - //smax = (surf->extents[0]>>4)+1; - //tmax = (surf->extents[1]>>4)+1; - - size = smax * tmax; - - if( surf->flags & SURF_CONVEYOR ) + if( FBitSet( surf->flags, SURF_CONVEYOR )) { smax = ( info->lightextents[0] * 3 / sample_size ) + 1; size = smax * tmax; @@ -200,162 +197,50 @@ static void R_BuildLightMap( void ) return; } - lm = surf->samples; - memset( blocklights, 0, sizeof( uint ) * size ); // add all the lightmaps - for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + for( map = 0; map < MAXLIGHTMAPS && surf->samples; map++ ) { + const color24 *lm = &surf->samples[map * size]; + uint scale; + int i; + + if( surf->styles[map] >= 255 ) + break; + scale = tr.lightstylevalue[surf->styles[map]]; - for( i = 0, bl = blocklights; i < size; i++, bl += 1, lm++ ) - { - bl[0] += gEngfuncs.LightToTexGamma( lm->r ) * scale; - bl[0] += gEngfuncs.LightToTexGamma( lm->g ) * scale; - bl[0] += gEngfuncs.LightToTexGamma( lm->b ) * scale; - - //printf("test\n"); - //bl[1] += gEngfuncs.LightToTexGamma( lm->g ) * scale; - //bl[2] += gEngfuncs.LightToTexGamma( lm->b ) * scale; - } + for( i = 0; i < size; i++ ) + blocklights[i] += ( lm[i].r + lm[i].g + lm[i].b ) * scale; } // add all the dynamic lights if( surf->dlightframe == tr.framecount ) R_AddDynamicLights( surf ); - // Put into texture format - //stride -= (smax << 2); - //bl = blocklights; - - /*for( t = 0; t < tmax; t++, dest += stride ) - { - for( s = 0; s < smax; s++ ) - { - dest[0] = Q_min((bl[0] >> 7), 255 ); - //dest[1] = Q_min((bl[1] >> 7), 255 ); - //dest[2] = Q_min((bl[2] >> 7), 255 ); - //dest[3] = 255; - - bl += 3; - dest += 4; - } - }*/ // bound, invert, and shift - for (i=0 ; i> 6 ) << 6; + else t = (int)blocklights[i]; - if (t < 0) - t = 0; - if( t > 65535 * 3 ) - t = 65535 * 3; - t = t / 2048 / 3;//(255*256 - t) >> (8 - VID_CBITS); - //if (t < (1 << 6)) - //t = (1 << 6); - t = t << 8; + t = bound( 0, t, 65535 * 3 ); + t = t / 2048 / 3; // (255*256 - t) >> (8 - VID_CBITS); - blocklights[i] = t; - } -} -#else - -/* -=============== -R_BuildLightMap - -Combine and scale multiple lightmaps into the 8.8 format in blocklights -=============== -*/ -void R_BuildLightMap (void) -{ - int smax, tmax; - int t; - int i, size; - byte *lightmap; - unsigned scale; - int maps; - msurface_t *surf; - - surf = r_drawsurf.surf; - - //smax = (surf->extents[0]>>4)+1; - //tmax = (surf->extents[1]>>4)+1; - mextrasurf_t *info = surf->info; - int sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); - smax = ( info->lightextents[0] / sample_size ) + 1; - tmax = ( info->lightextents[1] / sample_size ) + 1; - - size = smax*tmax; - - if (r_fullbright->value ) - { - for (i=0 ; isamples; - if (lightmap) - for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ; - maps++) - { - scale = r_drawsurf.lightadj[maps]; // 8.8 fraction - for (i=0 ; idlightframe == r_framecount) - //R_AddDynamicLights (); - -// bound, invert, and shift - /*for (i=0 ; i> (8 - VID_CBITS); - - if (t < (1 << 6)) - t = (1 << 6); - - blocklights[i] = t; - }*/ - for (i=0 ; i 767 ) - t = 767; - t = t * 31 / 256/3;//(255*256 - t) >> (8 - VID_CBITS); - - //if (t < (1 << 6)) - //t = (1 << 6); + // if (t < (1 << 6)) + // t = (1 << 6); t = t << 8; blocklights[i] = t; } } -#endif - -void R_InitRandomTable( void ) +void GL_InitRandomTable( void ) { - int tu, tv; + int tu, tv; for( tu = 0; tu < MOD_FRAMES; tu++ ) { @@ -375,10 +260,10 @@ R_TextureAnim Returns the proper texture for a given time and base texture, do not process random tiling =============== */ -texture_t *R_TextureAnim( texture_t *b ) +static texture_t *R_TextureAnim( texture_t *b ) { texture_t *base = b; - int count, reletive; + int count, reletive; if( RI.currententity->curstate.frame ) { @@ -394,14 +279,15 @@ texture_t *R_TextureAnim( texture_t *b ) } else { - int speed; + int speed; // Quake1 textures uses 10 frames per second if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) speed = 10; - else speed = 20; + else + speed = 20; - reletive = (int)(gpGlobals->time * speed) % base->anim_total; + reletive = (int)( gp_cl->time * speed ) % base->anim_total; } @@ -425,10 +311,10 @@ R_TextureAnimation Returns the proper texture for a given time and surface =============== */ -texture_t *R_TextureAnimation( msurface_t *s ) +static texture_t *R_TextureAnimation( msurface_t *s ) { - texture_t *base = s->texinfo->texture; - int count, reletive; + texture_t *base = s->texinfo->texture; + int count, reletive; if( RI.currententity && RI.currententity->curstate.frame ) { @@ -441,21 +327,22 @@ texture_t *R_TextureAnimation( msurface_t *s ) if( base->name[0] == '-' ) { - int tx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES; - int ty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES; + int tx = (int)(( s->texturemins[0] + ( base->width << 16 )) / base->width ) % MOD_FRAMES; + int ty = (int)(( s->texturemins[1] + ( base->height << 16 )) / base->height ) % MOD_FRAMES; reletive = rtable[tx][ty] % base->anim_total; } else { - int speed; + int speed; // Quake1 textures uses 10 frames per second if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) speed = 10; - else speed = 20; + else + speed = 20; - reletive = (int)(gpGlobals->time * speed) % base->anim_total; + reletive = (int)( gp_cl->time * speed ) % base->anim_total; } count = 0; @@ -476,21 +363,21 @@ texture_t *R_TextureAnimation( msurface_t *s ) R_DrawSurface =============== */ -void R_DrawSurface (void) +void R_DrawSurface( void ) { - pixel_t *basetptr; - int smax, tmax, twidth; - int u; - int soffset, basetoffset, texwidth; - int horzblockstep; - pixel_t *pcolumndest; - void (*pblockdrawer)(void); - image_t *mt; - uint sample_size, sample_bits, sample_pot; + pixel_t *basetptr; + int smax, tmax, twidth; + int u; + int soffset, basetoffset, texwidth; + int horzblockstep; + pixel_t *pcolumndest; + void (*pblockdrawer)( void ); + image_t *mt; + uint sample_size, sample_bits, sample_pot; surfrowbytes = r_drawsurf.rowbytes; - sample_size = LM_SAMPLE_SIZE_AUTO(r_drawsurf.surf); + sample_size = LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ); if( sample_size == 16 ) sample_bits = 4, sample_pot = sample_size; else @@ -500,7 +387,8 @@ void R_DrawSurface (void) if( sample_bits == -1 ) { sample_bits = 0; - for( sample_pot = 1; sample_pot < sample_size; sample_pot <<= 1, sample_bits++ ); + for( sample_pot = 1; sample_pot < sample_size; sample_pot <<= 1, sample_bits++ ) + ; } else sample_pot = 1 << sample_bits; @@ -516,10 +404,10 @@ void R_DrawSurface (void) blocksize = sample_pot >> r_drawsurf.surfmip; blockdivshift = sample_bits - r_drawsurf.surfmip; - blockdivmask = (1 << blockdivshift) - 1; + blockdivmask = ( 1 << blockdivshift ) - 1; if( sample_size == 16 ) - r_lightwidth = ( r_drawsurf.surf->info->lightextents[0]>>4)+1; + r_lightwidth = ( r_drawsurf.surf->info->lightextents[0] >> 4 ) + 1; else r_lightwidth = ( r_drawsurf.surf->info->lightextents[0] / sample_size ) + 1; @@ -527,7 +415,7 @@ void R_DrawSurface (void) r_numvblocks = r_drawsurf.surfheight >> blockdivshift; -//============================== +// ============================== if( sample_size == 16 ) pblockdrawer = surfmiptable[r_drawsurf.surfmip]; @@ -543,7 +431,7 @@ void R_DrawSurface (void) sourcetstep = texwidth; r_stepback = tmax * twidth; - r_sourcemax = r_source + (tmax * smax); + r_sourcemax = r_source + ( tmax * smax ); // glitchy and slow way to draw some lightmap if( r_drawsurf.surf->texinfo->flags & TEX_WORLD_LUXELS ) @@ -557,18 +445,18 @@ void R_DrawSurface (void) soffset = r_drawsurf.surf->texturemins[0]; basetoffset = r_drawsurf.surf->texturemins[1]; - //soffset = r_drawsurf.surf->info->lightmapmins[0] * worldlux_s; - //basetoffset = r_drawsurf.surf->info->lightmapmins[1] * worldlux_t; + // soffset = r_drawsurf.surf->info->lightmapmins[0] * worldlux_s; + // basetoffset = r_drawsurf.surf->info->lightmapmins[1] * worldlux_t; // << 16 components are to guarantee positive values for % - soffset = ((soffset >> r_drawsurf.surfmip) + (smax << 16)) % smax; - basetptr = &r_source[((((basetoffset >> r_drawsurf.surfmip) - + (tmax << 16)) % tmax) * twidth)]; + soffset = (( soffset >> r_drawsurf.surfmip ) + ( smax << 16 )) % smax; + basetptr = &r_source[(((( basetoffset >> r_drawsurf.surfmip ) + + ( tmax << 16 )) % tmax ) * twidth )]; pcolumndest = r_drawsurf.surfdat; - for (u=0 ; u= smax) + if( soffset >= smax ) soffset = 0; pcolumndest += horzblockstep; @@ -585,17 +473,17 @@ void R_DrawSurface (void) return; } - soffset = r_drawsurf.surf->info->lightmapmins[0]; + soffset = r_drawsurf.surf->info->lightmapmins[0]; basetoffset = r_drawsurf.surf->info->lightmapmins[1]; // << 16 components are to guarantee positive values for % - soffset = ((soffset >> r_drawsurf.surfmip) + (smax << 16)) % smax; - basetptr = &r_source[((((basetoffset >> r_drawsurf.surfmip) - + (tmax << 16)) % tmax) * twidth)]; + soffset = (( soffset >> r_drawsurf.surfmip ) + ( smax << 16 )) % smax; + basetptr = &r_source[(((( basetoffset >> r_drawsurf.surfmip ) + + ( tmax << 16 )) % tmax ) * twidth )]; pcolumndest = r_drawsurf.surfdat; - for (u=0 ; u= smax) + if( soffset >= smax ) soffset = 0; pcolumndest += horzblockstep; } - // test what if we have very slow cache building - //usleep(10000); + // test what if we have very slow cache building + // usleep(10000); } -//============================================================================= +// ============================================================================= -#if !id386 -#define BLEND_LM(pix, light) vid.colormap[(pix >> 3) | ((light & 0x1f00) << 5)] | ( pix & 7 ); +#define BLEND_LM( pix, light ) vid.colormap[( pix >> 3 ) | (( light & 0x1f00 ) << 5 )] | ( pix & 7 ); /* ================ @@ -628,42 +515,42 @@ R_DrawSurfaceBlock8_World Does not draw lightmap correclty, but scale it correctly. Better than nothing ================ */ -void R_DrawSurfaceBlock8_World (void) +void R_DrawSurfaceBlock8_World( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; - int lightpos = 0; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; + int lightpos = 0; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> (4-r_drawsurf.surfmip); - lightrightstep =(r_lightptr[(lightpos/r_lightwidth) * r_lightwidth+1] - lightright) >> (4-r_drawsurf.surfmip); + lightleftstep = ( r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth] - lightleft ) >> ( 4 - r_drawsurf.surfmip ); + lightrightstep = ( r_lightptr[( lightpos / r_lightwidth ) * r_lightwidth + 1] - lightright ) >> ( 4 - r_drawsurf.surfmip ); - for (i=0 ; i> (4-r_drawsurf.surfmip); + lightstep = lighttemp >> ( 4 - r_drawsurf.surfmip ); light = lightright; - for (b=blocksize-1; b>=0; b--) + for( b = blocksize - 1; b >= 0; b-- ) { - //pix = psource[(uint)(b * worldlux_s)]; + // pix = psource[(uint)(b * worldlux_s)]; pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light); + prowdest[b] = BLEND_LM( pix, light ); if( pix == TRANSPARENT_COLOR ) prowdest[b] = TRANSPARENT_COLOR; - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -673,7 +560,7 @@ void R_DrawSurfaceBlock8_World (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } @@ -684,40 +571,40 @@ void R_DrawSurfaceBlock8_World (void) R_DrawSurfaceBlock8_Generic ================ */ -void R_DrawSurfaceBlock8_Generic (void) +void R_DrawSurfaceBlock8_Generic( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> (4-r_drawsurf.surfmip); - lightrightstep = (r_lightptr[1] - lightright) >> (4-r_drawsurf.surfmip); + lightleftstep = ( r_lightptr[0] - lightleft ) >> ( 4 - r_drawsurf.surfmip ); + lightrightstep = ( r_lightptr[1] - lightright ) >> ( 4 - r_drawsurf.surfmip ); - for (i=0 ; i> (4-r_drawsurf.surfmip); + lightstep = lighttemp >> ( 4 - r_drawsurf.surfmip ); light = lightright; - for (b=blocksize-1; b>=0; b--) + for( b = blocksize - 1; b >= 0; b-- ) { pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light); + prowdest[b] = BLEND_LM( pix, light ); if( pix == TRANSPARENT_COLOR ) prowdest[b] = TRANSPARENT_COLOR; - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -727,7 +614,7 @@ void R_DrawSurfaceBlock8_Generic (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } @@ -738,42 +625,42 @@ void R_DrawSurfaceBlock8_Generic (void) R_DrawSurfaceBlock8_mip0 ================ */ -void R_DrawSurfaceBlock8_mip0 (void) +void R_DrawSurfaceBlock8_mip0( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> 4; - lightrightstep = (r_lightptr[1] - lightright) >> 4; + lightleftstep = ( r_lightptr[0] - lightleft ) >> 4; + lightrightstep = ( r_lightptr[1] - lightright ) >> 4; - for (i=0 ; i<16 ; i++) + for( i = 0; i < 16; i++ ) { lighttemp = lightleft - lightright; lightstep = lighttemp >> 4; light = lightright; - for (b=15; b>=0; b--) + for( b = 15; b >= 0; b-- ) { pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light); + prowdest[b] = BLEND_LM( pix, light ); if( pix == TRANSPARENT_COLOR ) prowdest[b] = TRANSPARENT_COLOR; - // pix; - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + // pix; + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -783,7 +670,7 @@ void R_DrawSurfaceBlock8_mip0 (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } @@ -794,38 +681,38 @@ void R_DrawSurfaceBlock8_mip0 (void) R_DrawSurfaceBlock8_mip1 ================ */ -void R_DrawSurfaceBlock8_mip1 (void) +void R_DrawSurfaceBlock8_mip1( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> 3; - lightrightstep = (r_lightptr[1] - lightright) >> 3; + lightleftstep = ( r_lightptr[0] - lightleft ) >> 3; + lightrightstep = ( r_lightptr[1] - lightright ) >> 3; - for (i=0 ; i<8 ; i++) + for( i = 0; i < 8; i++ ) { lighttemp = lightleft - lightright; lightstep = lighttemp >> 3; light = lightright; - for (b=7; b>=0; b--) + for( b = 7; b >= 0; b-- ) { pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light); - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + prowdest[b] = BLEND_LM( pix, light ); + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -835,7 +722,7 @@ void R_DrawSurfaceBlock8_mip1 (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } @@ -846,38 +733,38 @@ void R_DrawSurfaceBlock8_mip1 (void) R_DrawSurfaceBlock8_mip2 ================ */ -void R_DrawSurfaceBlock8_mip2 (void) +void R_DrawSurfaceBlock8_mip2( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> 2; - lightrightstep = (r_lightptr[1] - lightright) >> 2; + lightleftstep = ( r_lightptr[0] - lightleft ) >> 2; + lightrightstep = ( r_lightptr[1] - lightright ) >> 2; - for (i=0 ; i<4 ; i++) + for( i = 0; i < 4; i++ ) { lighttemp = lightleft - lightright; lightstep = lighttemp >> 2; light = lightright; - for (b=3; b>=0; b--) + for( b = 3; b >= 0; b-- ) { pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light);; - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + prowdest[b] = BLEND_LM( pix, light );; + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -887,7 +774,7 @@ void R_DrawSurfaceBlock8_mip2 (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } @@ -898,38 +785,38 @@ void R_DrawSurfaceBlock8_mip2 (void) R_DrawSurfaceBlock8_mip3 ================ */ -void R_DrawSurfaceBlock8_mip3 (void) +void R_DrawSurfaceBlock8_mip3( void ) { - int v, i, b; - uint lightstep, lighttemp, light; - pixel_t pix, *psource, *prowdest; + int v, i, b; + uint lightstep, lighttemp, light; + pixel_t pix, *psource, *prowdest; psource = pbasesource; prowdest = prowdestbase; - for (v=0 ; v> 1; - lightrightstep = (r_lightptr[1] - lightright) >> 1; + lightleftstep = ( r_lightptr[0] - lightleft ) >> 1; + lightrightstep = ( r_lightptr[1] - lightright ) >> 1; - for (i=0 ; i<2 ; i++) + for( i = 0; i < 2; i++ ) { lighttemp = lightleft - lightright; lightstep = lighttemp >> 1; light = lightright; - for (b=1; b>=0; b--) + for( b = 1; b >= 0; b-- ) { pix = psource[b]; - prowdest[b] = BLEND_LM(pix, light);; - //((unsigned char *)vid.colormap) - //[(light & 0xFF00) + pix]; + prowdest[b] = BLEND_LM( pix, light );; + // ((unsigned char *)vid.colormap) + // [(light & 0xFF00) + pix]; light += lightstep; } @@ -939,46 +826,41 @@ void R_DrawSurfaceBlock8_mip3 (void) prowdest += surfrowbytes; } - if (psource >= r_sourcemax) + if( psource >= r_sourcemax ) psource -= r_stepback; } } -#endif - - -//============================================================================ - - +// ============================================================================ /* ================ R_InitCaches ================ */ -void R_InitCaches (void) +void R_InitCaches( void ) { - int size; - int pix; + int size; + int pix; // calculate size to allocate - if (sw_surfcacheoverride->value) + if( sw_surfcacheoverride.value ) { - size = sw_surfcacheoverride->value; + size = sw_surfcacheoverride.value; } else { size = SURFCACHE_SIZE_AT_320X240 * 2; pix = vid.width * vid.height * 2; - if (pix > 64000) - size += (pix-64000)*3; + if( pix > 64000 ) + size += ( pix - 64000 ) * 3; } // round up to page size - size = (size + 8191) & ~8191; + size = ( size + 8191 ) & ~8191; - gEngfuncs.Con_Printf ("%s surface cache\n", Q_memprint(size)); + gEngfuncs.Con_Printf( "%s surface cache\n", Q_memprint( size )); sc_size = size; if( sc_base ) @@ -986,7 +868,7 @@ void R_InitCaches (void) D_FlushCaches( ); Mem_Free( sc_base ); } - sc_base = (surfcache_t *)Mem_Calloc(r_temppool,size); + sc_base = (surfcache_t *)Mem_Calloc( r_temppool, size ); sc_rover = sc_base; sc_base->next = NULL; @@ -1002,14 +884,14 @@ D_FlushCaches */ void D_FlushCaches( void ) { - surfcache_t *c; + surfcache_t *c; // if newmap, surfaces already freed if( !tr.map_unload ) { - for(c = sc_base ; c ; c = c->next ) + for( c = sc_base; c; c = c->next ) { - if ( c->owner ) + if( c->owner ) *c->owner = NULL; } } @@ -1025,28 +907,28 @@ void D_FlushCaches( void ) D_SCAlloc ================= */ -surfcache_t *D_SCAlloc (int width, int size) +static surfcache_t *D_SCAlloc( int width, int size ) { - surfcache_t *new; - qboolean wrapped_this_time; + surfcache_t *new; + qboolean wrapped_this_time; - if ((width < 0) )// || (width > 256)) - gEngfuncs.Host_Error ("D_SCAlloc: bad cache width %d\n", width); + if(( width < 0 ))// || (width > 256)) + gEngfuncs.Host_Error( "%s: bad cache width %d\n", __func__, width ); - if ((size <= 0) || (size > 0x10000000)) - gEngfuncs.Host_Error ("D_SCAlloc: bad cache size %d\n", size); + if(( size <= 0 ) || ( size > 0x10000000 )) + gEngfuncs.Host_Error( "%s: bad cache size %d\n", __func__, size ); - size = (int)&((surfcache_t *)0)->data[size]; - size = (size + 3) & ~3; - if (size > sc_size) - gEngfuncs.Host_Error ("D_SCAlloc: %i > cache size of %i",size, sc_size); + size = (int)&((surfcache_t *)0 )->data[size]; + size = ( size + 3 ) & ~3; + if( size > sc_size ) + gEngfuncs.Host_Error( "%s: %i > cache size of %i", __func__, size, sc_size ); // if there is not size bytes after the rover, reset to the start wrapped_this_time = false; - if ( !sc_rover || (byte *)sc_rover - (byte *)sc_base > sc_size - size) + if( !sc_rover || (byte *)sc_rover - (byte *)sc_base > sc_size - size ) { - if (sc_rover) + if( sc_rover ) { wrapped_this_time = true; } @@ -1055,16 +937,16 @@ surfcache_t *D_SCAlloc (int width, int size) // colect and free surfcache_t blocks until the rover block is large enough new = sc_rover; - if (sc_rover->owner) + if( sc_rover->owner ) *sc_rover->owner = NULL; - while (new->size < size) + while( new->size < size ) { - // free another + // free another sc_rover = sc_rover->next; - if (!sc_rover) - gEngfuncs.Host_Error ("D_SCAlloc: hit the end of memory"); - if (sc_rover->owner) + if( !sc_rover ) + gEngfuncs.Host_Error( "%s: hit the end of memory", __func__ ); + if( sc_rover->owner ) *sc_rover->owner = NULL; new->size += sc_rover->size; @@ -1072,9 +954,9 @@ surfcache_t *D_SCAlloc (int width, int size) } // create a fragment out of any leftovers - if (new->size - size > 256) + if( new->size - size > 256 ) { - sc_rover = (surfcache_t *)( (byte *)new + size); + sc_rover = (surfcache_t *)((byte *)new + size ); sc_rover->size = new->size - size; sc_rover->next = new->next; sc_rover->width = 0; @@ -1087,17 +969,17 @@ surfcache_t *D_SCAlloc (int width, int size) new->width = width; // DEBUG - if (width > 0) - new->height = (size - sizeof(*new) + sizeof(new->data)) / width; + if( width > 0 ) + new->height = ( size - sizeof( *new ) + sizeof( new->data )) / width; - new->owner = NULL; // should be set properly after return + new->owner = NULL; // should be set properly after return - if (d_roverwrapped) + if( d_roverwrapped ) { - if (wrapped_this_time || (sc_rover >= d_initial_rover)) + if( wrapped_this_time || ( sc_rover >= d_initial_rover )) r_cache_thrash = true; } - else if (wrapped_this_time) + else if( wrapped_this_time ) { d_roverwrapped = true; } @@ -1105,186 +987,139 @@ surfcache_t *D_SCAlloc (int width, int size) return new; } - -/* -================= -D_SCDump -================= -*/ -void D_SCDump (void) -{ - surfcache_t *test; - - for (test = sc_base ; test ; test = test->next) - { - if (test == sc_rover) - gEngfuncs.Con_Printf ("ROVER:\n"); - gEngfuncs.Con_Printf ("%p : %i bytes %i width\n",test, test->size, test->width); - } -} - -//============================================================================= - -// if the num is not a power of 2, assume it will not repeat - -int MaskForNum (int num) -{ - if (num==128) - return 127; - if (num==64) - return 63; - if (num==32) - return 31; - if (num==16) - return 15; - return 255; -} - -int D_log2 (int num) -{ - int c; - - c = 0; - - while (num>>=1) - c++; - return c; -} - -//============================================================================= -void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ); -void R_DrawSurfaceDecals( void ) +// ============================================================================= +static void R_DrawSurfaceDecals( void ) { msurface_t *fa = r_drawsurf.surf; - decal_t *p; + decal_t *p; - for( p = fa->pdecals; p; p = p->pnext) + for( p = fa->pdecals; p; p = p->pnext ) { - pixel_t *dest, *source; - vec4_t textureU, textureV; - image_t *tex = R_GetTexture( p->texture ); - int s1 = 0,t1 = 0, s2 = tex->width, t2 = tex->height; - unsigned int height; - unsigned int f, fstep; - int skip; - pixel_t *buffer; - qboolean transparent; - int x, y, u,v, sv, w, h; - vec3_t basis[3]; + pixel_t *dest, *source; + vec4_t textureU, textureV; + image_t *tex = R_GetTexture( p->texture ); + int s1 = 0, t1 = 0, s2 = tex->width, t2 = tex->height; + unsigned int height; + unsigned int f, fstep; + int skip; + pixel_t *buffer; + qboolean transparent; + int x, y, u, v, sv, w, h; + vec3_t basis[3]; - Vector4Copy( fa->texinfo->vecs[0], textureU ); - Vector4Copy( fa->texinfo->vecs[1], textureV ); + Vector4Copy( fa->texinfo->vecs[0], textureU ); + Vector4Copy( fa->texinfo->vecs[1], textureV ); - R_DecalComputeBasis( fa, 0, basis ); + R_DecalComputeBasis( fa, 0, basis ); - w = fabs( tex->width * DotProduct( textureU, basis[0] )) + - fabs( tex->height * DotProduct( textureU, basis[1] )); - h = fabs( tex->width * DotProduct( textureV, basis[0] )) + - fabs( tex->height * DotProduct( textureV, basis[1] )); + w = fabs( tex->width * DotProduct( textureU, basis[0] )) + + fabs( tex->height * DotProduct( textureU, basis[1] )); + h = fabs( tex->width * DotProduct( textureV, basis[0] )) + + fabs( tex->height * DotProduct( textureV, basis[1] )); - // project decal center into the texture space of the surface - x = DotProduct( p->position, textureU ) + textureU[3] - fa->texturemins[0] - w/2; - y = DotProduct( p->position, textureV ) + textureV[3] - fa->texturemins[1] - h/2; + // project decal center into the texture space of the surface + x = DotProduct( p->position, textureU ) + textureU[3] - fa->texturemins[0] - w / 2; + y = DotProduct( p->position, textureV ) + textureV[3] - fa->texturemins[1] - h / 2; - x = x >> r_drawsurf.surfmip; - y = y >> r_drawsurf.surfmip; - w = w >> r_drawsurf.surfmip; - h = h >> r_drawsurf.surfmip; + x = x >> r_drawsurf.surfmip; + y = y >> r_drawsurf.surfmip; + w = w >> r_drawsurf.surfmip; + h = h >> r_drawsurf.surfmip; - if( w < 1 || h < 1 ) - continue; + if( w < 1 || h < 1 ) + continue; + + if( x < 0 ) + { + s1 += ( -x ) * ( s2 - s1 ) / w; + x = 0; + } + if( x + w > r_drawsurf.surfwidth ) + { + s2 -= ( x + w - r_drawsurf.surfwidth ) * ( s2 - s1 ) / w; + w = r_drawsurf.surfwidth - x; + } + if( y + h > r_drawsurf.surfheight ) + { + t2 -= ( y + h - r_drawsurf.surfheight ) * ( t2 - t1 ) / h; + h = r_drawsurf.surfheight - y; + } + + if( s1 < 0 ) + s1 = 0; + if( t1 < 0 ) + t1 = 0; + + if( s2 > tex->width ) + s2 = tex->width; + if( t2 > tex->height ) + t2 = tex->height; + + if( !tex->pixels[0] || s1 >= s2 || t1 >= t2 || !w ) + continue; + + if( tex->alpha_pixels ) + { + buffer = tex->alpha_pixels; + transparent = true; + } + else + buffer = tex->pixels[0]; + + height = h; + if( y < 0 ) + { + skip = -y; + height += y; + y = 0; + } + else + skip = 0; + + dest = ((pixel_t *)r_drawsurf.surfdat ) + y * r_drawsurf.rowbytes + x; + + for( v = 0; v < height; v++ ) + { + // int alpha1 = vid.alpha; + sv = ( skip + v ) * ( t2 - t1 ) / h + t1; + source = buffer + sv * tex->width + s1; - if( x < 0 ) { - s1 += (-x)*(s2-s1) / w; - x = 0; - } - if( x + w > r_drawsurf.surfwidth ) - { - s2 -= (x + w - r_drawsurf.surfwidth) * (s2 - s1)/ w ; - w = r_drawsurf.surfwidth - x; - } - if( y + h > r_drawsurf.surfheight ) - { - t2 -= (y + h - r_drawsurf.surfheight) * (t2 - t1) / h; - h = r_drawsurf.surfheight - y; - } - - if( s1 < 0 ) - s1 = 0; - if( t1 < 0 ) - t1 = 0; - - if( s2 > tex->width ) - s2 = tex->width; - if( t2 > tex->height ) - t2 = tex->height; - - if( !tex->pixels[0] || s1 >= s2 || t1 >= t2 || !w ) - continue; - - if( tex->alpha_pixels ) - { - buffer = tex->alpha_pixels; - transparent = true; - } - else - buffer = tex->pixels[0]; - - height = h; - if (y < 0) - { - skip = -y; - height += y; - y = 0; - } - else - skip = 0; - - dest = ((pixel_t*)r_drawsurf.surfdat) + y * r_drawsurf.rowbytes + x; - - for (v=0 ; vwidth + s1; + f = 0; + fstep = ( s2 - s1 ) * 0x10000 / w; + if( w == s2 - s1 ) + fstep = 0x10000; + for( u = 0; u < w; u++ ) { - f = 0; - fstep = (s2-s1)*0x10000/w; - if( w == s2 - s1 ) - fstep = 0x10000; + pixel_t src = source[f >> 16]; + int alpha = 7; + f += fstep; - for (u=0 ; u>16]; - int alpha = 7; - f += fstep; + alpha &= src >> ( 16 - 3 ); + src = src << 3; + } - if( transparent ) - { - alpha &= src >> (16 - 3); - src = src << 3; - } + if( alpha <= 0 ) + continue; - if( alpha <= 0 ) + if( alpha < 7 ) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) ) + { + pixel_t screen = dest[u]; // | 0xff & screen & src ; + if( screen == TRANSPARENT_COLOR ) continue; - - if( alpha < 7) // && (vid.rendermode == kRenderTransAlpha || vid.rendermode == kRenderTransTexture ) ) - { - pixel_t screen = dest[u]; // | 0xff & screen & src ; - if( screen == TRANSPARENT_COLOR ) - continue; - dest[u] = BLEND_ALPHA( alpha, src, screen); - - } - else - dest[u] = src; + dest[u] = BLEND_ALPHA( alpha, src, screen ); } + else + dest[u] = src; + } - dest += r_drawsurf.rowbytes; } + dest += r_drawsurf.rowbytes; + } } } @@ -1294,42 +1129,42 @@ void R_DrawSurfaceDecals( void ) D_CacheSurface ================ */ -surfcache_t *D_CacheSurface (msurface_t *surface, int miplevel) +surfcache_t *D_CacheSurface( msurface_t *surface, int miplevel ) { - surfcache_t *cache; - int maps; + surfcache_t *cache; + int maps; // // if the surface is animating or flashing, flush the cache // - r_drawsurf.image = R_GetTexture(R_TextureAnimation (surface)->gl_texturenum); + r_drawsurf.image = R_GetTexture( R_TextureAnimation( surface )->gl_texturenum ); // does not support conveyors with world luxels now if( surface->texinfo->flags & TEX_WORLD_LUXELS ) surface->flags &= ~SURF_CONVEYOR; - if( surface->flags & SURF_CONVEYOR) + if( surface->flags & SURF_CONVEYOR ) { - if( miplevel >= 1) + if( miplevel >= 1 ) { - surface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) * 2 ; + surface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) * 2; surface->info->lightmapmins[0] = -surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ); } else { - surface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) ; - surface->info->lightmapmins[0] = -surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf )/2; + surface->extents[0] = surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ); + surface->info->lightmapmins[0] = -surface->info->lightextents[0] * LM_SAMPLE_SIZE_AUTO( r_drawsurf.surf ) / 2; } } /// todo: port this - //r_drawsurf.lightadj[0] = r_newrefdef.lightstyles[surface->styles[0]].white*128; - //r_drawsurf.lightadj[1] = r_newrefdef.lightstyles[surface->styles[1]].white*128; - //r_drawsurf.lightadj[2] = r_newrefdef.lightstyles[surface->styles[2]].white*128; - //r_drawsurf.lightadj[3] = r_newrefdef.lightstyles[surface->styles[3]].white*128; + // r_drawsurf.lightadj[0] = r_newrefdef.lightstyles[surface->styles[0]].white*128; + // r_drawsurf.lightadj[1] = r_newrefdef.lightstyles[surface->styles[1]].white*128; + // r_drawsurf.lightadj[2] = r_newrefdef.lightstyles[surface->styles[2]].white*128; + // r_drawsurf.lightadj[3] = r_newrefdef.lightstyles[surface->styles[3]].white*128; // // see if the cache holds apropriate data // - cache = CACHESPOT(surface)[miplevel]; + cache = CACHESPOT( surface )[miplevel]; // check for lightmap modification for( maps = 0; maps < MAXLIGHTMAPS && surface->styles[maps] != 255; maps++ ) @@ -1341,28 +1176,28 @@ surfcache_t *D_CacheSurface (msurface_t *surface, int miplevel) } - if (cache && !cache->dlight && surface->dlightframe != tr.framecount - && cache->image == r_drawsurf.image - && cache->lightadj[0] == r_drawsurf.lightadj[0] - && cache->lightadj[1] == r_drawsurf.lightadj[1] - && cache->lightadj[2] == r_drawsurf.lightadj[2] - && cache->lightadj[3] == r_drawsurf.lightadj[3] ) + if( cache && !cache->dlight && surface->dlightframe != tr.framecount + && cache->image == r_drawsurf.image + && cache->lightadj[0] == r_drawsurf.lightadj[0] + && cache->lightadj[1] == r_drawsurf.lightadj[1] + && cache->lightadj[2] == r_drawsurf.lightadj[2] + && cache->lightadj[3] == r_drawsurf.lightadj[3] ) return cache; if( surface->dlightframe == tr.framecount ) { int i; // invalidate dlight cache - for( i = 0; i < 4; i++) + for( i = 0; i < 4; i++ ) { - if( CACHESPOT(surface)[i] ) - CACHESPOT(surface)[i]->image = NULL; + if( CACHESPOT( surface )[i] ) + CACHESPOT( surface )[i]->image = NULL; } } // // determine shape of surface // - surfscale = 1.0 / (1<flags & SURF_CONVEYOR ) r_drawsurf.surfwidth = surface->extents[0] >> miplevel; @@ -1383,16 +1218,16 @@ surfcache_t *D_CacheSurface (msurface_t *surface, int miplevel) // // allocate memory if needed // - if (!cache) // if a texture just animated, don't reallocate it + if( !cache ) // if a texture just animated, don't reallocate it { - cache = D_SCAlloc (r_drawsurf.surfwidth, - r_drawsurf.surfwidth * r_drawsurf.surfheight * 2); - CACHESPOT(surface)[miplevel] = cache; - cache->owner = &CACHESPOT(surface)[miplevel]; + cache = D_SCAlloc( r_drawsurf.surfwidth, + r_drawsurf.surfwidth * r_drawsurf.surfheight * 2 ); + CACHESPOT( surface )[miplevel] = cache; + cache->owner = &CACHESPOT( surface )[miplevel]; cache->mipscale = surfscale; } - if (surface->dlightframe == tr.framecount) + if( surface->dlightframe == tr.framecount ) cache->dlight = 1; else cache->dlight = 0; @@ -1413,13 +1248,13 @@ surfcache_t *D_CacheSurface (msurface_t *surface, int miplevel) // r_drawsurf.surf = surface; - //c_surf++; + // c_surf++; // calculate the lightings - R_BuildLightMap ( ); + R_BuildLightMap( ); // rasterize the surface into the cache - R_DrawSurface (); + R_DrawSurface(); R_DrawSurfaceDecals(); return cache; diff --git a/ref/soft/r_trialias.c b/ref/soft/r_trialias.c index 51e7bfdc..07c43539 100644 --- a/ref/soft/r_trialias.c +++ b/ref/soft/r_trialias.c @@ -3,30 +3,29 @@ // not really draw alias models here, but use this to draw triangles -affinetridesc_t r_affinetridesc; +affinetridesc_t r_affinetridesc; -int r_aliasblendcolor; +int r_aliasblendcolor; -float aliastransform[3][4]; -float aliasworldtransform[3][4]; -float aliasoldworldtransform[3][4]; +float aliastransform[3][4]; +float aliasworldtransform[3][4]; +float aliasoldworldtransform[3][4]; -float s_ziscale; -static vec3_t s_alias_forward, s_alias_right, s_alias_up; +float s_ziscale; +static vec3_t s_alias_forward, s_alias_right, s_alias_up; -#define NUMVERTEXNORMALS 162 +#define NUMVERTEXNORMALS 162 -float r_avertexnormals[NUMVERTEXNORMALS][3] = { +float r_avertexnormals[NUMVERTEXNORMALS][3] = { #include "anorms.h" }; -void R_AliasSetUpTransform (void); -void R_AliasTransformVector (vec3_t in, vec3_t out, float m[3][4] ); -void R_AliasProjectAndClipTestFinalVert (finalvert_t *fv); +void R_AliasSetUpTransform( void ); +void R_AliasProjectAndClipTestFinalVert( finalvert_t *fv ); void R_AliasTransformFinalVerts( int numpoints, finalvert_t *fv, dtrivertx_t *oldv, dtrivertx_t *newv ); @@ -42,20 +41,7 @@ R_AliasCheckBBox #define BBOX_MUST_CLIP_Z 2 #define BBOX_TRIVIAL_REJECT 8 - -/* -================ -R_AliasTransformVector -================ -*/ -void R_AliasTransformVector(vec3_t in, vec3_t out, float xf[3][4] ) -{ - out[0] = DotProduct(in, xf[0]) + xf[0][3]; - out[1] = DotProduct(in, xf[1]) + xf[1][3]; - out[2] = DotProduct(in, xf[2]) + xf[2][3]; -} - -void VectorInverse (vec3_t v) +static void VectorInverse( vec3_t v ) { v[0] = -v[0]; v[1] = -v[1]; @@ -67,11 +53,11 @@ void VectorInverse (vec3_t v) R_SetUpWorldTransform ================ */ -void R_SetUpWorldTransform (void) +void R_SetUpWorldTransform( void ) { - int i; - static float viewmatrix[3][4]; - vec3_t angles; + int i; + static float viewmatrix[3][4]; + vec3_t angles; // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table @@ -86,23 +72,23 @@ void R_SetUpWorldTransform (void) // TODO: can do this with simple matrix rearrangement - memset( aliasworldtransform, 0, sizeof( aliasworldtransform ) ); - memset( aliasoldworldtransform, 0, sizeof( aliasworldtransform ) ); + memset( aliasworldtransform, 0, sizeof( aliasworldtransform )); + memset( aliasoldworldtransform, 0, sizeof( aliasworldtransform )); - for (i=0 ; i<3 ; i++) + for( i = 0; i < 3; i++ ) { - aliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i]; + aliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i]; aliasoldworldtransform[i][0] = aliasworldtransform[i][1] = -s_alias_right[i]; - aliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i]; + aliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i]; } aliasworldtransform[0][3] = -RI.vieworg[0]; aliasworldtransform[1][3] = -RI.vieworg[1]; aliasworldtransform[2][3] = -RI.vieworg[2]; - //aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0]; - //aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1]; - //aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2]; + // aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0]; + // aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1]; + // aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2]; // FIXME: can do more efficiently than full concatenation // memcpy( rotationmatrix, t2matrix, sizeof( rotationmatrix ) ); @@ -110,11 +96,11 @@ void R_SetUpWorldTransform (void) // R_ConcatTransforms (t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set - VectorCopy (RI.vright, viewmatrix[0]); - VectorCopy (RI.vup, viewmatrix[1]); - VectorInverse (viewmatrix[1]); - //VectorScale(viewmatrix[1], -1, viewmatrix[1]); - VectorCopy (RI.vforward, viewmatrix[2]); + VectorCopy( RI.vright, viewmatrix[0] ); + VectorCopy( RI.vup, viewmatrix[1] ); + VectorInverse( viewmatrix[1] ); + // VectorScale(viewmatrix[1], -1, viewmatrix[1]); + VectorCopy( RI.vforward, viewmatrix[2] ); viewmatrix[0][3] = 0; viewmatrix[1][3] = 0; @@ -122,16 +108,16 @@ void R_SetUpWorldTransform (void) // memcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) ); - //R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform); - Matrix3x4_ConcatTransforms(aliastransform, viewmatrix, aliasworldtransform ); + // R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform); + Matrix3x4_ConcatTransforms( aliastransform, viewmatrix, aliasworldtransform ); aliasworldtransform[0][3] = 0; aliasworldtransform[1][3] = 0; aliasworldtransform[2][3] = 0; - //aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]; - //aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]; - //aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]; + // aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]; + // aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]; + // aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]; } @@ -140,11 +126,11 @@ void R_SetUpWorldTransform (void) R_AliasSetUpTransform ================ */ -void R_AliasSetUpTransform (void) +void R_AliasSetUpTransform( void ) { - int i; - static float viewmatrix[3][4]; - vec3_t angles; + int i; + static float viewmatrix[3][4]; + vec3_t angles; // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table @@ -159,23 +145,23 @@ void R_AliasSetUpTransform (void) // TODO: can do this with simple matrix rearrangement - memset( aliasworldtransform, 0, sizeof( aliasworldtransform ) ); - memset( aliasoldworldtransform, 0, sizeof( aliasworldtransform ) ); + memset( aliasworldtransform, 0, sizeof( aliasworldtransform )); + memset( aliasoldworldtransform, 0, sizeof( aliasworldtransform )); - for (i=0 ; i<3 ; i++) + for( i = 0; i < 3; i++ ) { - aliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i]; + aliasoldworldtransform[i][0] = aliasworldtransform[i][0] = s_alias_forward[i]; aliasoldworldtransform[i][0] = aliasworldtransform[i][1] = -s_alias_right[i]; - aliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i]; + aliasoldworldtransform[i][0] = aliasworldtransform[i][2] = s_alias_up[i]; } - aliasworldtransform[0][3] = RI.currententity->origin[0]-RI.vieworg[0]; - aliasworldtransform[1][3] = RI.currententity->origin[1]-RI.vieworg[1]; - aliasworldtransform[2][3] = RI.currententity->origin[2]-RI.vieworg[2]; + aliasworldtransform[0][3] = RI.currententity->origin[0] - RI.vieworg[0]; + aliasworldtransform[1][3] = RI.currententity->origin[1] - RI.vieworg[1]; + aliasworldtransform[2][3] = RI.currententity->origin[2] - RI.vieworg[2]; - //aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0]; - //aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1]; - //aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2]; + // aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]-r_origin[0]; + // aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]-r_origin[1]; + // aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]-r_origin[2]; // FIXME: can do more efficiently than full concatenation // memcpy( rotationmatrix, t2matrix, sizeof( rotationmatrix ) ); @@ -183,11 +169,11 @@ void R_AliasSetUpTransform (void) // R_ConcatTransforms (t2matrix, tmatrix, rotationmatrix); // TODO: should be global, set when vright, etc., set - VectorCopy (RI.vright, viewmatrix[0]); - VectorCopy (RI.vup, viewmatrix[1]); - VectorInverse (viewmatrix[1]); - //VectorScale(viewmatrix[1], -1, viewmatrix[1]); - VectorCopy (RI.vforward, viewmatrix[2]); + VectorCopy( RI.vright, viewmatrix[0] ); + VectorCopy( RI.vup, viewmatrix[1] ); + VectorInverse( viewmatrix[1] ); + // VectorScale(viewmatrix[1], -1, viewmatrix[1]); + VectorCopy( RI.vforward, viewmatrix[2] ); viewmatrix[0][3] = 0; viewmatrix[1][3] = 0; @@ -195,16 +181,16 @@ void R_AliasSetUpTransform (void) // memcpy( aliasworldtransform, rotationmatrix, sizeof( aliastransform ) ); - //R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform); - Matrix3x4_ConcatTransforms(aliastransform, viewmatrix, aliasworldtransform ); + // R_ConcatTransforms (viewmatrix, aliasworldtransform, aliastransform); + Matrix3x4_ConcatTransforms( aliastransform, viewmatrix, aliasworldtransform ); aliasworldtransform[0][3] = RI.currententity->origin[0]; aliasworldtransform[1][3] = RI.currententity->origin[1]; aliasworldtransform[2][3] = RI.currententity->origin[2]; - //aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]; - //aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]; - //aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]; + // aliasoldworldtransform[0][3] = RI.currententity->oldorigin[0]; + // aliasoldworldtransform[1][3] = RI.currententity->oldorigin[1]; + // aliasoldworldtransform[2][3] = RI.currententity->oldorigin[2]; } /* @@ -214,8 +200,8 @@ R_AliasProjectAndClipTestFinalVert */ void R_AliasProjectAndClipTestFinalVert( finalvert_t *fv ) { - float zi; - float x, y, z; + float zi; + float x, y, z; // project points x = fv->xyz[0]; @@ -225,39 +211,32 @@ void R_AliasProjectAndClipTestFinalVert( finalvert_t *fv ) fv->zi = zi * s_ziscale; - fv->u = (x * aliasxscale * zi) + aliasxcenter; - fv->v = (y * aliasyscale * zi) + aliasycenter; + fv->u = ( x * aliasxscale * zi ) + aliasxcenter; + fv->v = ( y * aliasyscale * zi ) + aliasycenter; - if (fv->u < RI.aliasvrect.x) + if( fv->u < RI.aliasvrect.x ) fv->flags |= ALIAS_LEFT_CLIP; - if (fv->v < RI.aliasvrect.y) + if( fv->v < RI.aliasvrect.y ) fv->flags |= ALIAS_TOP_CLIP; - if (fv->u > RI.aliasvrectright) + if( fv->u > RI.aliasvrectright ) fv->flags |= ALIAS_RIGHT_CLIP; - if (fv->v > RI.aliasvrectbottom) + if( fv->v > RI.aliasvrectbottom ) fv->flags |= ALIAS_BOTTOM_CLIP; } -void R_AliasWorldToScreen( const float *v, float *out ) -{ - out[0] = DotProduct(v, aliastransform[0]) + aliastransform[0][3]; - out[1] = DotProduct(v, aliastransform[1]) + aliastransform[1][3]; - out[2] = DotProduct(v, aliastransform[2]) + aliastransform[2][3]; -} - void R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, int s, int t ) { vec3_t v = {x, y, z}; - fv->xyz[0] = DotProduct(v, aliastransform[0]) + aliastransform[0][3]; - fv->xyz[1] = DotProduct(v, aliastransform[1]) + aliastransform[1][3]; - fv->xyz[2] = DotProduct(v, aliastransform[2]) + aliastransform[2][3]; + fv->xyz[0] = DotProduct( v, aliastransform[0] ) + aliastransform[0][3]; + fv->xyz[1] = DotProduct( v, aliastransform[1] ) + aliastransform[1][3]; + fv->xyz[2] = DotProduct( v, aliastransform[2] ) + aliastransform[2][3]; fv->flags = 0; fv->l = light; - if ( fv->xyz[2] < ALIAS_Z_CLIP_PLANE ) + if( fv->xyz[2] < ALIAS_Z_CLIP_PLANE ) { fv->flags |= ALIAS_Z_CLIP; } @@ -270,14 +249,14 @@ void R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, in fv->t = t << 16; } -void R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 ) +void R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 ) { - if ( fv1->flags & fv2->flags & fv3->flags ) - return ; // completely clipped + if( fv1->flags & fv2->flags & fv3->flags ) + return; // completely clipped - if ( ! (fv1->flags | fv2->flags | fv3->flags) ) - { // totally unclipped + if( !( fv1->flags | fv2->flags | fv3->flags )) + { // totally unclipped aliastriangleparms.a = fv1; aliastriangleparms.b = fv2; aliastriangleparms.c = fv3; @@ -285,8 +264,8 @@ void R_RenderTriangle( finalvert_t *fv1, finalvert_t *fv2, finalvert_t *fv3 ) R_DrawTriangle(); } else - { // partially clipped - R_AliasClipTriangle (fv1, fv2, fv3); + { // partially clipped + R_AliasClipTriangle( fv1, fv2, fv3 ); } } diff --git a/ref/soft/r_triapi.c b/ref/soft/r_triapi.c index c1867083..db829c77 100644 --- a/ref/soft/r_triapi.c +++ b/ref/soft/r_triapi.c @@ -19,15 +19,15 @@ GNU General Public License for more details. static struct { - int renderMode; // override kRenderMode from TriAPI - vec4_t triRGBA; + int renderMode; // override kRenderMode from TriAPI + vec4_t triRGBA; } ds; -finalvert_t triv[3]; -int vertcount, n; -int mode; -short s,t; -uint light; +static finalvert_t triv[3]; +static int vertcount, n; +static int mode; +static short s, t; +static uint light; /* =============================================================== @@ -46,33 +46,6 @@ set rendermode void GAME_EXPORT TriRenderMode( int mode ) { ds.renderMode = vid.rendermode = mode; -#if 0 - switch( mode ) - { - case kRenderNormal: - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglDisable( GL_BLEND ); - pglDepthMask( GL_TRUE ); - break; - case kRenderTransAlpha: - pglEnable( GL_BLEND ); - pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - pglDepthMask( GL_FALSE ); - break; - case kRenderTransColor: - case kRenderTransTexture: - pglEnable( GL_BLEND ); - pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - break; - case kRenderGlow: - case kRenderTransAdd: - pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); - pglEnable( GL_BLEND ); - pglDepthMask( GL_FALSE ); - break; - } -#endif } /* @@ -84,42 +57,10 @@ begin triangle sequence */ void GAME_EXPORT TriBegin( int mode1 ) { -#if 0 - switch( mode ) - { - case TRI_POINTS: - mode = GL_POINTS; - break; - case TRI_TRIANGLES: - mode = GL_TRIANGLES; - break; - case TRI_TRIANGLE_FAN: - mode = GL_TRIANGLE_FAN; - break; - case TRI_QUADS: - mode = GL_QUADS; - break; - case TRI_LINES: - mode = GL_LINES; - break; - case TRI_TRIANGLE_STRIP: - mode = GL_TRIANGLE_STRIP; - break; - case TRI_QUAD_STRIP: - mode = GL_QUAD_STRIP; - break; - case TRI_POLYGON: - default: - mode = GL_POLYGON; - break; - } - - pglBegin( mode ); -#endif if( mode1 == TRI_QUADS ) mode1 = TRI_TRIANGLE_FAN; mode = mode1; - vertcount = n = vertcount = 0; + n = vertcount = 0; } /* @@ -131,8 +72,6 @@ draw triangle sequence */ void GAME_EXPORT TriEnd( void ) { - //if( vertcount == 3 ) - //pglEnd( ); } /* @@ -143,16 +82,16 @@ _TriColor4f */ void GAME_EXPORT _TriColor4f( float rr, float gg, float bb, float aa ) { - //pglColor4f( r, g, b, a ); - unsigned short r,g,b; - unsigned int major, minor; + // pglColor4f( r, g, b, a ); + unsigned short r, g, b; + unsigned int major, minor; if( vid.rendermode == kRenderTransAdd || vid.rendermode == kRenderGlow ) rr *= aa, gg *= aa, bb *= aa; - //gEngfuncs.Con_Printf("%d\n", vid.alpha); + // gEngfuncs.Con_Printf("%d\n", vid.alpha); - light = (rr + gg + bb) * 31 / 3; + light = ( rr + gg + bb ) * 31 / 3; if( light > 31 ) light = 31; @@ -177,12 +116,12 @@ void GAME_EXPORT _TriColor4f( float rr, float gg, float bb, float aa ) b = 31; - major = (((r >> 2) & MASK(3)) << 5) |( (( (g >> 3) & MASK(3)) << 2 ) )| (((b >> 3) & MASK(2))); + major = ((( r >> 2 ) & MASK( 3 )) << 5 ) | (((( g >> 3 ) & MASK( 3 )) << 2 )) | ((( b >> 3 ) & MASK( 2 ))); // save minor GBRGBRGB - minor = 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); + minor = 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 ); - vid.color = major << 8 | (minor & 0xFF); + vid.color = major << 8 | ( minor & 0xFF ); } /* @@ -193,10 +132,10 @@ TriColor4ub */ void TriColor4ub( byte r, byte g, byte b, byte a ) { - ds.triRGBA[0] = r * (1.0f / 255.0f); - ds.triRGBA[1] = g * (1.0f / 255.0f); - ds.triRGBA[2] = b * (1.0f / 255.0f); - ds.triRGBA[3] = a * (1.0f / 255.0f); + ds.triRGBA[0] = r * ( 1.0f / 255.0f ); + ds.triRGBA[1] = g * ( 1.0f / 255.0f ); + ds.triRGBA[2] = b * ( 1.0f / 255.0f ); + ds.triRGBA[3] = a * ( 1.0f / 255.0f ); _TriColor4f( ds.triRGBA[0], ds.triRGBA[1], ds.triRGBA[2], 1.0f ); } @@ -209,10 +148,10 @@ TriColor4ub */ void GAME_EXPORT _TriColor4ub( byte r, byte g, byte b, byte a ) { - _TriColor4f( r * (1.0f / 255.0f), - g * (1.0f / 255.0f), - b * (1.0f / 255.0f), - a * (1.0f / 255.0f)); + _TriColor4f( r * ( 1.0f / 255.0f ), + g * ( 1.0f / 255.0f ), + b * ( 1.0f / 255.0f ), + a * ( 1.0f / 255.0f )); } /* @@ -222,11 +161,12 @@ TriColor4f */ void TriColor4f( float r, float g, float b, float a ) { - //if( a < 0.5 ) + // if( a < 0.5 ) // a = 1; if( ds.renderMode == kRenderTransAlpha ) TriColor4ub( r * 255.0f, g * 255.0f, b * 255.0f, a * 255.0f ); - else _TriColor4f( r * a, g * a, b * a, 1.0 ); + else + _TriColor4f( r * a, g * a, b * a, 1.0 ); ds.triRGBA[0] = r; ds.triRGBA[1] = g; @@ -243,8 +183,8 @@ TriTexCoord2f void GAME_EXPORT TriTexCoord2f( float u, float v ) { double u1 = 0, v1 = 0; - u = fmodf(u, 10); - v = fmodf(v, 10); + u = fmodf( u, 10 ); + v = fmodf( v, 10 ); if( u < 1000 && u > -1000 ) u1 = u; if( v < 1000 && v > -1000 ) @@ -260,8 +200,8 @@ void GAME_EXPORT TriTexCoord2f( float u, float v ) v1 = v1 - 1; - s = r_affinetridesc.skinwidth * bound(0.01,u1,0.99); - t = r_affinetridesc.skinheight * bound(0.01,v1,0.99); + s = r_affinetridesc.skinwidth * bound( 0.01, u1, 0.99 ); + t = r_affinetridesc.skinheight * bound( 0.01, v1, 0.99 ); } /* @@ -272,7 +212,6 @@ TriVertex3fv */ void GAME_EXPORT TriVertex3fv( const float *v ) { - //pglVertex3fv( v ); TriVertex3f( v[0], v[1], v[2] ); } @@ -286,35 +225,35 @@ void GAME_EXPORT TriVertex3f( float x, float y, float z ) { if( mode == TRI_TRIANGLES ) { - R_SetupFinalVert( &triv[vertcount], x, y, z, light << 8,s,t); + R_SetupFinalVert( &triv[vertcount], x, y, z, light << 8, s, t ); vertcount++; if( vertcount == 3 ) { R_RenderTriangle( &triv[0], &triv[1], &triv[2] ); - //R_RenderTriangle( &triv[2], &triv[1], &triv[0] ); + // R_RenderTriangle( &triv[2], &triv[1], &triv[0] ); vertcount = 0; } } if( mode == TRI_TRIANGLE_FAN ) { - R_SetupFinalVert( &triv[vertcount], x, y, z, light << 8,s,t); + R_SetupFinalVert( &triv[vertcount], x, y, z, light << 8, s, t ); vertcount++; if( vertcount >= 3 ) { R_RenderTriangle( &triv[0], &triv[1], &triv[2] ); - //R_RenderTriangle( &triv[2], &triv[1], &triv[0] ); + // R_RenderTriangle( &triv[2], &triv[1], &triv[0] ); triv[1] = triv[2]; vertcount = 2; } } if( mode == TRI_TRIANGLE_STRIP ) { - R_SetupFinalVert( &triv[n], x, y, z, light << 8,s,t); + R_SetupFinalVert( &triv[n], x, y, z, light << 8, s, t ); n++; vertcount++; if( n == 3 ) n = 0; - if (vertcount >= 3) + if( vertcount >= 3 ) { if( vertcount & 1 ) R_RenderTriangle( &triv[0], &triv[1], &triv[2] ); @@ -322,29 +261,6 @@ void GAME_EXPORT TriVertex3f( float x, float y, float z ) R_RenderTriangle( &triv[2], &triv[1], &triv[0] ); } } -#if 0 - if( mode == TRI_TRIANGLE_STRIP ) - { - R_SetupFinalVert( &triv[vertcount], x, y, z, 0,s,t); - vertcount++; - if( vertcount == 3 ) - { - - R_RenderTriangle( triv ); - finalvert_t fv = triv[0]; - - triv[0] = triv[2]; - triv[2] = fv; - R_RenderTriangle( triv ); - fv = triv[0]; - triv[0] = triv[2]; - triv[2] = fv; - triv[0] = triv[1]; - triv[1] = triv[2]; - vertcount = 2; - } - } -#endif } /* @@ -368,12 +284,12 @@ bind current texture */ int TriSpriteTexture( model_t *pSpriteModel, int frame ) { - int gl_texturenum; + int gl_texturenum; if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 ) return 0; - if( gl_texturenum <= 0 || gl_texturenum > MAX_TEXTURES ) + if( gl_texturenum <= 0 || gl_texturenum >= MAX_TEXTURES ) gl_texturenum = tr.defaultTexture; GL_Bind( XASH_TEXTURE0, gl_texturenum ); @@ -390,39 +306,6 @@ enables global fog on the level */ void GAME_EXPORT TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) { -#if 0 - // overrided by internal fog - if( RI.fogEnabled ) return; - RI.fogCustom = bOn; - - // check for invalid parms - if( flEnd <= flStart ) - { - RI.fogCustom = false; - pglDisable( GL_FOG ); - return; - } - - if( RI.fogCustom ) - pglEnable( GL_FOG ); - else pglDisable( GL_FOG ); - - // copy fog params - RI.fogColor[0] = flFogColor[0] / 255.0f; - RI.fogColor[1] = flFogColor[1] / 255.0f; - RI.fogColor[2] = flFogColor[2] / 255.0f; - RI.fogStart = flStart; - RI.fogColor[3] = 1.0f; - RI.fogDensity = 0.0f; - RI.fogSkybox = true; - RI.fogEnd = flEnd; - - pglFogi( GL_FOG_MODE, GL_LINEAR ); - pglFogfv( GL_FOG_COLOR, RI.fogColor ); - pglFogf( GL_FOG_START, RI.fogStart ); - pglFogf( GL_FOG_END, RI.fogEnd ); - pglHint( GL_FOG_HINT, GL_NICEST ); -#endif } /* @@ -434,7 +317,6 @@ very strange export */ void GAME_EXPORT TriGetMatrix( const int pname, float *matrix ) { - //pglGetFloatv( pname, matrix ); } /* @@ -445,8 +327,6 @@ TriForParams */ void GAME_EXPORT TriFogParams( float flDensity, int iFogSkybox ) { - //RI.fogDensity = flDensity; - //RI.fogSkybox = iFogSkybox; } /* @@ -457,21 +337,6 @@ TriCullFace */ void GAME_EXPORT TriCullFace( TRICULLSTYLE mode ) { -#if 0 - int glMode; - - switch( mode ) - { - case TRI_FRONT: - glMode = GL_FRONT; - break; - default: - glMode = GL_NONE; - break; - } - - GL_Cull( mode ); -#endif } /* @@ -481,15 +346,11 @@ TriBrightness */ void TriBrightness( float brightness ) { - float r, g, b; + float r, g, b; - //if( brightness < 0.5 ) -// brightness = 1; //0.5; -//ds.triRGBA[3] = 1; r = ds.triRGBA[0] * ds.triRGBA[3] * brightness; g = ds.triRGBA[1] * ds.triRGBA[3] * brightness; b = ds.triRGBA[2] * ds.triRGBA[3] * brightness; _TriColor4f( r, g, b, 1.0f ); } - diff --git a/ref/soft/r_vgui.c b/ref/soft/r_vgui.c deleted file mode 100644 index 70d2740b..00000000 --- a/ref/soft/r_vgui.c +++ /dev/null @@ -1,225 +0,0 @@ -/* -gl_vgui.c - OpenGL vgui draw methods -Copyright (C) 2011 Uncle Mike -Copyright (C) 2019 a1batross - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. -*/ - -#include "r_local.h" - -#define VGUI_MAX_TEXTURES ( MAX_TEXTURES / 2 ) // a half of total textures count - -static int g_textures[VGUI_MAX_TEXTURES]; -static int g_textureId = 0; -static int g_iBoundTexture; - -/* -================ -VGUI_DrawInit - -Startup VGUI backend -================ -*/ -void GAME_EXPORT VGUI_DrawInit( void ) -{ - memset( g_textures, 0, sizeof( g_textures )); - g_textureId = g_iBoundTexture = 0; -} - -/* -================ -VGUI_DrawShutdown - -Release all textures -================ -*/ -void GAME_EXPORT VGUI_DrawShutdown( void ) -{ - int i; - - for( i = 1; i < g_textureId; i++ ) - { - GL_FreeTexture( g_textures[i] ); - } -} - -/* -================ -VGUI_GenerateTexture - -generate unique texture number -================ -*/ -int GAME_EXPORT VGUI_GenerateTexture( void ) -{ - if( ++g_textureId >= VGUI_MAX_TEXTURES ) - gEngfuncs.Host_Error( "VGUI_GenerateTexture: VGUI_MAX_TEXTURES limit exceeded\n" ); - return g_textureId; -} - -/* -================ -VGUI_UploadTexture - -Upload texture into video memory -================ -*/ -void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height ) -{ - rgbdata_t r_image; - char texName[32]; - - if( id <= 0 || id >= VGUI_MAX_TEXTURES ) - { - gEngfuncs.Con_DPrintf( S_ERROR "VGUI_UploadTexture: bad texture %i. Ignored\n", id ); - return; - } - - Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); - memset( &r_image, 0, sizeof( r_image )); - - r_image.width = width; - r_image.height = height; - r_image.type = PF_RGBA_32; - r_image.size = r_image.width * r_image.height * 4; - r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; - r_image.buffer = (byte *)buffer; - - g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE ); -} - -/* -================ -VGUI_CreateTexture - -Create empty rgba texture and upload them into video memory -================ -*/ -void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height ) -{ - rgbdata_t r_image; - char texName[32]; - - if( id <= 0 || id >= VGUI_MAX_TEXTURES ) - { - gEngfuncs.Con_Reportf( S_ERROR "VGUI_CreateTexture: bad texture %i. Ignored\n", id ); - return; - } - - Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); - memset( &r_image, 0, sizeof( r_image )); - - r_image.width = width; - r_image.height = height; - r_image.type = PF_RGBA_32; - r_image.size = r_image.width * r_image.height * 4; - r_image.flags = IMAGE_HAS_ALPHA; - r_image.buffer = NULL; - - g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE|TF_NEAREST ); - g_iBoundTexture = id; -} - -void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight ) -{ - if( id <= 0 || id >= VGUI_MAX_TEXTURES || g_textures[id] == 0 || g_textures[id] == tr.whiteTexture ) - { - gEngfuncs.Con_Reportf( S_ERROR "VGUI_UploadTextureBlock: bad texture %i. Ignored\n", id ); - return; - } - - //pglTexSubImage2D( GL_TEXTURE_2D, 0, drawX, drawY, blockWidth, blockHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba ); - g_iBoundTexture = id; -} - -void GAME_EXPORT VGUI_SetupDrawingRect( int *pColor ) -{ - -} - -void GAME_EXPORT VGUI_SetupDrawingText( int *pColor ) -{ - -} - -void GAME_EXPORT VGUI_SetupDrawingImage( int *pColor ) -{ - -} - -void GAME_EXPORT VGUI_BindTexture( int id ) -{ - if( id > 0 && id < VGUI_MAX_TEXTURES && g_textures[id] ) - { - GL_Bind( XASH_TEXTURE0, g_textures[id] ); - g_iBoundTexture = id; - } - else - { - // NOTE: same as bogus index 2700 in GoldSrc - id = g_iBoundTexture = 1; - GL_Bind( XASH_TEXTURE0, g_textures[id] ); - } -} - -/* -================ -VGUI_GetTextureSizes - -returns wide and tall for currently binded texture -================ -*/ -void GAME_EXPORT VGUI_GetTextureSizes( int *width, int *height ) -{ - image_t *glt; - int texnum; - - if( g_iBoundTexture ) - texnum = g_textures[g_iBoundTexture]; - else texnum = tr.defaultTexture; - - glt = R_GetTexture( texnum ); - if( width ) *width = glt->srcWidth; - if( height ) *height = glt->srcHeight; -} - -/* -================ -VGUI_EnableTexture - -disable texturemode for fill rectangle -================ -*/ -void GAME_EXPORT VGUI_EnableTexture( qboolean enable ) -{ - -} - -/* -================ -VGUI_DrawQuad - -generic method to fill rectangle -================ -*/ -void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ) -{ - int width, height; - float xscale, yscale; - - gEngfuncs.CL_GetScreenInfo( &width, &height ); - - xscale = gpGlobals->width / (float)width; - yscale = gpGlobals->height / (float)height; - - ASSERT( ul != NULL && lr != NULL ); -} diff --git a/ref/soft/wscript b/ref/soft/wscript index e5cf7ca2..95e37ec6 100644 --- a/ref/soft/wscript +++ b/ref/soft/wscript @@ -8,28 +8,23 @@ import os top = '.' def options(opt): - # stub return def configure(conf): - # check for dedicated server build - if conf.options.DEDICATED: - return - - if conf.options.SUPPORT_BSP2_FORMAT: - conf.env.append_unique('DEFINES', 'SUPPORT_BSP2_FORMAT') - - conf.env.append_unique('DEFINES', 'REF_DLL') + return def build(bld): - if bld.env.DEDICATED: - return + libs = [ 'engine_includes', 'werror' ] + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS == 'psvita': + libs += [ 'sdk_includes' ] + else: + libs += [ 'public', 'M' ] - bld.shlib(source = bld.path.ant_glob(['*.c']), + bld.shlib(source = bld.path.ant_glob('*.c'), target = 'ref_soft', - features = 'c', includes = '.', - use = 'engine_includes public M', - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM + defines = 'REF_DLL=1', + use = libs, + install_path = bld.env.LIBDIR ) diff --git a/scripts/build-ninja.py b/scripts/build-ninja.py new file mode 100644 index 00000000..4c99e1db --- /dev/null +++ b/scripts/build-ninja.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Copyright (C) 2025 Velaron +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import argparse +import os +import shutil +import subprocess +import sys + + +def run_cmake(bin_path, libs, inst_path): + cmake_exec = ["cmake", "--build", bin_path] + cmake_process = subprocess.Popen(cmake_exec) + cmake_process.communicate() + + if libs: + for lib in libs: + src = os.path.join(bin_path, *lib.split("/")) + dest = os.path.join(inst_path, lib.split("/")[-1]) + + dest_dir = os.path.dirname(dest) + + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + + shutil.copyfile(src, dest) + else: + cmake_exec = ["cmake", "--install", bin_path, "--prefix", inst_path] + cmake_process = subprocess.Popen(cmake_exec) + cmake_process.communicate() + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("cmd") + parser.add_argument("top_dir") + parser.add_argument("out_dir") + parser.add_argument("waflock") + parser.add_argument("--targets", type=str, default="") + + args = parser.parse_args() + + waf_path = os.path.join(args.top_dir, "waf") + + env = os.environ.copy() + env["WAFLOCK"] = args.waflock + + waf_exec = [sys.executable, waf_path, args.cmd, "-t", args.top_dir] + + if args.targets: + waf_exec += ["--targets={}".format(args.targets)] + else: + # build SDL2 and hlsdk-portable with cmake + sdl_bin_path = os.path.join(args.out_dir, "SDL") + hlsdk_bin_path = os.path.join(args.out_dir, "hlsdk-portable") + + abi = args.waflock.replace(".lock-waf_android_", "").replace("_build", "") + inst_path = os.path.join(args.top_dir, "android", "app", "src", "main", "jniLibs", abi) + + if not os.path.exists(inst_path): + os.makedirs(inst_path) + + run_cmake(sdl_bin_path, ["libSDL2.so"], inst_path) + run_cmake(hlsdk_bin_path, None, inst_path) + + process = subprocess.Popen(waf_exec, env=env) + process.communicate() + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/cirrus/build_freebsd.sh b/scripts/cirrus/build_freebsd.sh index b1b06207..87c580f0 100755 --- a/scripts/cirrus/build_freebsd.sh +++ b/scripts/cirrus/build_freebsd.sh @@ -2,25 +2,12 @@ . scripts/lib.sh -if [ "$1" = "dedicated" ]; then - APP=xashds -else # elif [ "$1" = "full" ]; then - APP=xash3d-fwgs -fi - build_engine() { # Build engine cd "$CIRRUS_WORKING_DIR" || die - if [ "$APP" = "xashds" ]; then - ./waf configure -T release -d || die - elif [ "$APP" = "xash3d-fwgs" ]; then - ./waf configure -T release --enable-stb --enable-utils --enable-gl4es --enable-gles1 --enable-gles2 || die - else - die - fi - + ./waf configure --enable-utils --enable-all-renderers --enable-tests --enable-dedicated || die_configure ./waf build || die } diff --git a/scripts/configure-ninja.py b/scripts/configure-ninja.py new file mode 100755 index 00000000..678ed40a --- /dev/null +++ b/scripts/configure-ninja.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Copyright (C) 2025 Velaron +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from __future__ import print_function + +import argparse +import os +import subprocess +import sys +import io + + +def check_repo(name, branch, url, path): + if not os.path.exists(path): + print("{} not found. Cloning...".format(name)) + git_exec = ["git", "clone", "--branch", branch, url, path] + git_process = subprocess.Popen(git_exec) + git_process.communicate() + + +def run_cmake(root, out, toolchain, abi, build_type, ndk_root, min_sdk, *args): + cmake_exec = ["cmake", "-H{}".format(root), "-DCMAKE_BUILD_TYPE={}".format(build_type), + "-DCMAKE_TOOLCHAIN_FILE={}".format(toolchain), "-DANDROID_ABI={}".format(abi), + "-DANDROID_NDK={}".format(ndk_root), + "-DANDROID_PLATFORM=android-{}".format(min_sdk), + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", + "-DCMAKE_SYSTEM_NAME=Android", "-DCMAKE_SYSTEM_VERSION={}".format(min_sdk), + "-B{}".format(out), "-GNinja"] + + cmake_exec.extend(args) + cmake_process = subprocess.Popen(cmake_exec) + cmake_process.communicate() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("wscript_path") + parser.add_argument("--variant") + parser.add_argument("--abi") + parser.add_argument("--configuration-dir") + parser.add_argument("--ndk-version") + parser.add_argument("--min-sdk-version") + parser.add_argument("--ndk-root") + + args, unknown = parser.parse_known_args() + + abi = args.abi + + cmake_build_type = "Debug" if args.variant in ["debug", "asan"] else "Release" + cmake_toolchain_path = os.path.join(args.ndk_root, "build", "cmake", "android.toolchain.cmake") + + # configure SDL2 + sdl_path = os.path.join(args.wscript_path, "3rdparty", "SDL") + check_repo("SDL", "release-2.32.8", "https://github.com/libsdl-org/SDL", sdl_path) + + sdl_out_path = os.path.join(args.configuration_dir, "SDL") + + run_cmake(sdl_path, sdl_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root, args.min_sdk_version, + "-DSDL_RENDER=OFF", "-DSDL_POWER=OFF", "-DSDL_VULKAN=OFF", "-DSDL_DISKAUDIO=OFF", + "-DSDL_DUMMYAUDIO=OFF", "-DSDL_DUMMYVIDEO=OFF", + "-DSDL_VULKAN=OFF", "-DSDL_OFFSCREEN=OFF", "-DSDL_STATIC=OFF") + + # configure hlsdk-portable + hlsdk_path = os.path.join(args.wscript_path, "3rdparty", "hlsdk-portable") + check_repo("hlsdk-portable", "mobile_hacks", "https://github.com/FWGS/hlsdk-portable", hlsdk_path) + + hlsdk_out_path = os.path.join(args.configuration_dir, "hlsdk-portable") + + run_cmake(hlsdk_path, hlsdk_out_path, cmake_toolchain_path, abi, cmake_build_type, args.ndk_root, + args.min_sdk_version, "-DANDROID_APK=ON") + + # waf configure + waf_path = os.path.join(args.wscript_path, "waf") + out_path = os.path.join(args.configuration_dir, "xash3d-fwgs") + + waf_build_type = "debug" if args.variant in ["debug", "asan"] else "release" + + env = os.environ.copy() + env["WAFLOCK"] = ".lock-waf_android_{}_build".format(abi) + env["ANDROID_NDK"] = args.ndk_root + env["BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY"] = sdl_out_path + + waf_exec = [sys.executable, waf_path, "configure", "-t", args.wscript_path, "-o", out_path, + "-T", waf_build_type, "--android={},,{}".format(abi, args.min_sdk_version), "-s", + sdl_path, "--skip-sdl2-sanity-check", "--enable-bundled-deps", "ninja"] + + process = subprocess.Popen(waf_exec, env=env) + process.communicate() + + with io.open(os.path.join(args.configuration_dir, "build.ninja.txt"), "w", encoding="utf-8") as f: + f.write(os.path.join(out_path, "build.ninja")) + + # required for Android Studio + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/continious_upload.sh b/scripts/continious_upload.sh deleted file mode 100755 index fb91611b..00000000 --- a/scripts/continious_upload.sh +++ /dev/null @@ -1,283 +0,0 @@ -#!/bin/bash - -# MIT License -# -# Copyright (c) 2016 Simon Peter -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -set +x # Do not leak information - -# Exit immediately if one of the files given as arguments is not there -# because we don't want to delete the existing release if we don't have -# the new files that should be uploaded -for file in "$@" -do - if [ ! -e "$file" ] - then echo "$file is missing, giving up." >&2; exit 1 - fi -done - -if [ $# -eq 0 ]; then - echo "No artifacts to use for release, giving up." - exit 0 -fi - -if command -v sha256sum >/dev/null 2>&1 ; then - shatool="sha256sum" -elif command -v shasum >/dev/null 2>&1 ; then - shatool="shasum -a 256" # macOS fallback -else - echo "Neither sha256sum nor shasum is available, cannot check hashes" -fi - -RELEASE_BODY="" -GIT_REPO_SLUG="$REPO_SLUG" - -if [ ! -z "$GITHUB_ACTIONS" ] ; then - GIT_COMMIT="$GITHUB_SHA" - GIT_REPO_SLUG="$GITHUB_REPOSITORY" - if [[ "$GITHUB_REF" == "refs/tags/"* ]] ; then - GIT_TAG="${GITHUB_REF#refs/tags/}" - fi - RELEASE_BODY="GitHub Actions build log: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" -fi - -if [ ! -z "$UPLOADTOOL_BODY" ] ; then - RELEASE_BODY="$UPLOADTOOL_BODY" -fi - -# The calling script (usually .travis.yml) can set a suffix to be used for -# the tag and release name. This way it is possible to have a release for -# the output of the CI/CD pipeline (marked as 'continuous') and also test -# builds for other branches. -# If this build was triggered by a tag, call the result a Release -if [ ! -z "$UPLOADTOOL_SUFFIX" ] ; then - if [ "$UPLOADTOOL_SUFFIX" = "$GIT_TAG" ] ; then - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Release build ($GIT_TAG)" - is_prerelease="false" - else - RELEASE_NAME="continuous-$UPLOADTOOL_SUFFIX" - RELEASE_TITLE="Continuous build ($UPLOADTOOL_SUFFIX)" - if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then - is_prerelease="false" - else - is_prerelease="true" - fi - - fi -else - if [ "$GITHUB_ACTIONS" = "true" ]; then - if [ "$GITHUB_REF_TYPE" == "branch" ]; then - if [ "$GITHUB_REF_NAME" == "master" ]; then - RELEASE_NAME="continuous" - RELEASE_TITLE="Continuous build" - else - RELEASE_NAME="continuous-$GITHUB_REF_NAME" - RELEASE_TITLE="Continuous build ($GITHUB_REF_NAME)" - fi - if [ -z "$UPLOADTOOL_ISPRERELEASE" ]; then - is_prerelease="false" - else - is_prerelease="true" - fi - elif [ "$GITHUB_REF_TYPE" == "tag" ]; then - case $(tr '[:upper:]' '[:lower:]' <<< "$GITHUB_REF_NAME") in - *-alpha*|*-beta*|*-rc*) - RELEASE_NAME="$GITHUB_REF_NAME" - RELEASE_TITLE="Pre-release build ($GITHUB_REF_NAME)" - is_prerelease="true" - ;; - *) - RELEASE_NAME="$GITHUB_REF_NAME" - RELEASE_TITLE="Release build ($GITHUB_REF_NAME)" - is_prerelease="false" - ;; - esac - fi - else - # ,, is a bash-ism to convert variable to lower case - case $(tr '[:upper:]' '[:lower:]' <<< "$GIT_TAG") in - "") - # Do not use "latest" as it is reserved by GitHub - RELEASE_NAME="continuous" - RELEASE_TITLE="Continuous build" - if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then - is_prerelease="false" - else - is_prerelease="true" - fi - ;; - *-alpha*|*-beta*|*-rc*) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Pre-release build ($GIT_TAG)" - is_prerelease="true" - ;; - *) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Release build ($GIT_TAG)" - is_prerelease="false" - ;; - esac - fi -fi - -# Do not upload non-master branch builds -if [ "$GITHUB_EVENT_NAME" == "pull_request" ] ; then - echo "Release uploading disabled for pull requests, uploading to transfer.sh instead" - rm -f ./uploaded-to - for FILE in "$@" ; do - BASENAME="$(basename "${FILE}")" - curl --upload-file $FILE "https://transfer.sh/$BASENAME" > ./one-upload - echo "$(cat ./one-upload)" # this way we get a newline - echo -n "$(cat ./one-upload)\\n" >> ./uploaded-to # this way we get a \n but no newline - done - $shatool "$@" - exit 0 -fi - -if [ ! -z "$GITHUB_ACTIONS" ] ; then - echo "Running on GitHub Actions" - if [ -z "$GITHUB_TOKEN" ] ; then - echo "\$GITHUB_TOKEN missing, please add the following to your run action:" - echo "env:" - echo " GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}" - exit 1 - fi -else - echo "Not running on known CI" - if [ -z "$GIT_REPO_SLUG" ] ; then - read -r -p "Repo Slug (GitHub username/reponame): " GIT_REPO_SLUG - fi - if [ -z "$GITHUB_TOKEN" ] ; then - read -r -s -p "Token (https://github.com/settings/tokens): " GITHUB_TOKEN - fi -fi - -tag_url="https://api.github.com/repos/$GIT_REPO_SLUG/git/refs/tags/$RELEASE_NAME" -tag_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${tag_url}") -echo "tag_infos: $tag_infos" -tag_sha=$(echo "$tag_infos" | grep '"sha":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "tag_sha: $tag_sha" - -release_url="https://api.github.com/repos/$GIT_REPO_SLUG/releases/tags/$RELEASE_NAME" -echo "Getting the release ID..." -echo "release_url: $release_url" -release_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${release_url}") -echo "release_infos: $release_infos" -release_id=$(echo "$release_infos" | grep "\"id\":" | head -n 1 | tr -s " " | cut -f 3 -d" " | cut -f 1 -d ",") -echo "release ID: $release_id" -upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "upload_url: $upload_url" -release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "release_url: $release_url" -target_commit_sha=$(echo "$release_infos" | grep '"target_commitish":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "target_commit_sha: $target_commit_sha" - -if [ "$GIT_COMMIT" != "$target_commit_sha" ] ; then - - echo "GIT_COMMIT != target_commit_sha, hence deleting $RELEASE_NAME..." - - if [ ! -z "$release_id" ]; then - delete_url="https://api.github.com/repos/$GIT_REPO_SLUG/releases/$release_id" - echo "Delete the release..." - echo "delete_url: $delete_url" - curl -XDELETE \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - "${delete_url}" - fi - - # echo "Checking if release with the same name is still there..." - # echo "release_url: $release_url" - # curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" \ - # "$release_url" - - if [ "$RELEASE_NAME" == "continuous" ] ; then - # if this is a continuous build tag, then delete the old tag - # in preparation for the new release - echo "Delete the tag..." - delete_url="https://api.github.com/repos/$GIT_REPO_SLUG/git/refs/tags/$RELEASE_NAME" - echo "delete_url: $delete_url" - curl -XDELETE \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - "${delete_url}" - fi - - echo "Create release..." - - release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \ - --data '{"tag_name": "'"$RELEASE_NAME"'","target_commitish": "'"$GIT_COMMIT"'","name": "'"$RELEASE_TITLE"'","body": "'"$RELEASE_BODY"'","draft": false,"prerelease": '$is_prerelease'}' "https://api.github.com/repos/$GIT_REPO_SLUG/releases") - - echo "$release_infos" - - unset upload_url - upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) - echo "upload_url: $upload_url" - - unset release_url - release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) - echo "release_url: $release_url" - -fi # if [ "$GIT_COMMIT" != "$tag_sha" ] - -if [ -z "$release_url" ] ; then - echo "Cannot figure out the release URL for $RELEASE_NAME" - exit 1 -fi - -echo "Upload binaries to the release..." - -# Need to URL encode the basename, so we have this function to do so -urlencode() { - # urlencode - old_lc_collate=$LC_COLLATE - LC_COLLATE=C - local length="${#1}" - for (( i = 0; i < length; i++ )); do - local c="${1:$i:1}" - case $c in - [a-zA-Z0-9.~_-]) printf '%s' "$c" ;; - *) printf '%%%02X' "'$c" ;; - esac - done - LC_COLLATE=$old_lc_collate -} - -for FILE in "$@" ; do - FULLNAME="${FILE}" - BASENAME="$(basename "${FILE}")" - curl -H "Authorization: token ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.manifold-preview" \ - -H "Content-Type: application/octet-stream" \ - --data-binary "@$FULLNAME" \ - "$upload_url?name=$(urlencode "$BASENAME")" - echo "" -done - -$shatool "$@" - -if [ "$GIT_COMMIT" != "$tag_sha" ] ; then - echo "Publish the release..." - - release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \ - --data '{"draft": false}' "$release_url") - - echo "$release_infos" -fi # if [ "$GIT_COMMIT" != "$tag_sha" ] diff --git a/scripts/flatpak/run.sh b/scripts/flatpak/run.sh new file mode 100755 index 00000000..c0fc4667 --- /dev/null +++ b/scripts/flatpak/run.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +die() +{ + echo "$@" + exit 1 +} + +echo "Xash3D FWGS installed as Flatpak." + +# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +# $XDG_DATA_HOME defines the base directory relative to which user-specific data files should be stored. +# If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. +if [ -z "$XDG_DATA_HOME" ]; then + export XDG_DATA_HOME="$HOME/.local/share" +fi + +if [ -z "$XASH3D_BASEDIR" ]; then + export XASH3D_BASEDIR="$XDG_DATA_HOME/xash3d-fwgs/" +fi + +mkdir -p "$XASH3D_BASEDIR" +cd "$XASH3D_BASEDIR" || die "Can't cd into $XASH3D_BASEDIR" +echo "XASH3D_BASEDIR is $XASH3D_BASEDIR" + +if [ -z "$XASH3D_RODIR" ]; then + # TODO: detect by libraryfolders.vdf and installed apps + STEAMDIRS="\ + $HOME/.local/share/Steam/steamapps/common \ + $HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common \ + $HOME/.steam/steam/steamapps/common" + HALFLIFEDIR="Half-Life" + + for i in $STEAMDIRS; do + if [ ! -d "$i" ]; then + continue + fi + + echo "Detected Steam library in $i, probing Half-Life..." + + if [ ! -d "$i/$HALFLIFEDIR" ]; then + continue + fi + + echo "Detected Half-Life installation in $i/$HALFLIFEDIR..." + + export XASH3D_RODIR="$i/$HALFLIFEDIR" + break + done +fi +echo "XASH3D_RODIR is $XASH3D_RODIR" + +if [ -z "$XASH3D_EXTRAS_PAK1" ]; then + export XASH3D_EXTRAS_PAK1=/app/share/xash3d/valve/extras.pk3 +fi +echo "XASH3D_EXTRAS_PAK1 is $XASH3D_EXTRAS_PAK1" + +exec $DEBUGGER /app/lib32/xash3d/xash3d "$@" diff --git a/scripts/flatpak/su.xash.Engine.Compat.i386.desktop b/scripts/flatpak/su.xash.Engine.Compat.i386.desktop new file mode 100644 index 00000000..cec00eca --- /dev/null +++ b/scripts/flatpak/su.xash.Engine.Compat.i386.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Categories=Game;Shooter; +Comment=Half-Life compatible game engine +Exec= +Icon=su.xash.Engine.Compat.i386 +Keywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer; +Name=Xash3D FWGS +PrefersNonDefaultGPU=true +Terminal=false +Type=Application diff --git a/scripts/flatpak/su.xash.Engine.Compat.i386.yml b/scripts/flatpak/su.xash.Engine.Compat.i386.yml new file mode 100644 index 00000000..ddfb6355 --- /dev/null +++ b/scripts/flatpak/su.xash.Engine.Compat.i386.yml @@ -0,0 +1,93 @@ +app-id: su.xash.Engine.Compat.i386 +runtime: org.freedesktop.Platform +runtime-version: &runtime-version '24.08' +x-gl-version: &gl-version '1.4' +x-gl-versions: &gl-versions 24.08;1.4 +sdk: org.freedesktop.Sdk +command: run.sh +separate-locales: false +sdk-extensions: + - org.freedesktop.Sdk.Compat.i386 + - org.freedesktop.Sdk.Extension.toolchain-i386 + +finish-args: + - --share=ipc + - --socket=wayland + - --socket=x11 + - --socket=pulseaudio + - --share=network + # Steam library path on Ubuntu + - --filesystem=~/.steam/steam/steamapps/common:ro + # ... on SteamOS 3 + - --filesystem=~/.local/share/Steam/steamapps/common:ro + # ... on Steam Flatpak + - --filesystem=~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/common:ro + # allow talking with Discord for mods + # TODO: enable when somebody asks for it + # - --filesystem=xdg-run/app/com.discordapp.Discord:create + - --device=all + - --allow=multiarch + - --allow=devel + - --allow=bluetooth + +add-extensions: + org.freedesktop.Platform.Compat.i386: + directory: lib/i386-linux-gnu + version: *runtime-version + + org.freedesktop.Platform.Compat.i386.Debug: + directory: lib/debug/lib/i386-linux-gnu + version: *runtime-version + no-autodownload: true + + org.freedesktop.Platform.GL32: + directory: lib/i386-linux-gnu/GL + version: *gl-version + versions: *gl-versions + subdirectories: true + autodelete: false + add-ld-path: lib + merge-dirs: vulkan/icd.d;glvnd/egl_vendor.d;OpenCL/vendors;lib/dri;lib/d3d;vulkan/explicit_layer.d;vulkan/implicit_layer.d + download-if: active-gl-driver + enable-if: active-gl-driver + +x-compat-i386-opts: &compat-i386-opts + prepend-pkg-config-path: /app/lib32/pkgconfig:/usr/lib/i386-linux-gnu/pkgconfig + ldflags: -L/app/lib32 + prepend-path: /usr/lib/sdk/toolchain-i386/bin + env: + CC: i686-unknown-linux-gnu-gcc + CXX: i686-unknown-linux-gnu-g++ + libdir: /app/lib32 + no-debuginfo: true # libbacktrace relies on this + +modules: + - name: bundle-setup + buildsystem: simple + build-commands: + - | + mkdir -p /app/lib/i386-linux-gnu + mkdir -p /app/lib/debug/lib/i386-linux-gnu + mkdir -p /app/lib/i386-linux-gnu/GL + mkdir -p /app/etc + echo "/app/lib32" > /app/etc/ld.so.conf + echo "/app/lib/i386-linux-gnu" >> /app/etc/ld.so.conf + - name: xash + buildsystem: simple + build-options: *compat-i386-opts + build-commands: +# HACKHACK: removed --enable-lto because it causes linker errors in Flatpak build + - | + python waf configure -T release --enable-all-renderers --enable-packaging --prefix=/app --libdir=/app/lib32 --bindir=/app/lib32/xash3d + python waf build + python waf install --destdir=/ + mkdir -p /app/bin + mkdir -p /app/share/icons/hicolor/256x256/apps/ + mkdir -p /app/share/applications + install -m 0755 3rdparty/vgui_support/vgui-dev/lib/vgui.so /app/lib32/xash3d/vgui.so + install -m 0755 scripts/flatpak/run.sh /app/bin/run.sh + install -m 0644 game_launch/icon-xash-material.png /app/share/icons/hicolor/256x256/apps/su.xash.Engine.Compat.i386.png + install -m 0644 scripts/flatpak/su.xash.Engine.Compat.i386.desktop /app/share/applications/su.xash.Engine.Compat.i386.desktop + sources: + - type: dir + path: ../../ diff --git a/scripts/fwgs.pfx b/scripts/fwgs.pfx new file mode 100644 index 00000000..e9c312f4 Binary files /dev/null and b/scripts/fwgs.pfx differ diff --git a/scripts/gha/build_android.sh b/scripts/gha/build_android.sh index a5f4df5c..09ac1321 100755 --- a/scripts/gha/build_android.sh +++ b/scripts/gha/build_android.sh @@ -1,25 +1,28 @@ #!/bin/bash -export ANDROID_SDK_HOME=$GITHUB_WORKSPACE/sdk -export ANDROID_NDK_HOME=$ANDROID_SDK_HOME/ndk-bundle +unset ANDROID_SDK_ROOT +export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6 +export ANDROID_HOME=$GITHUB_WORKSPACE/sdk +export PATH=$PATH:$JAVA_HOME/bin:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin pushd android -if [[ "$GH_CPU_ARCH" == "32" ]]; then - export ARCHS="armeabi armeabi-v7a x86" -elif [[ "$GH_CPU_ARCH" == "64" ]]; then - export ARCHS="aarch64 x86_64" -elif [[ "$GH_CPU_ARCH" == "32&64" ]]; then - export ARCHS="armeabi armeabi-v7a x86 aarch64 x86_64" -fi -sh compile.sh release +./gradlew assembleContinuous --no-daemon || exit 1 +pushd app/build/outputs/apk/continuous + +$ANDROID_HOME/build-tools/36.0.0/apksigner sign \ + --ks $GITHUB_WORKSPACE/android/debug.keystore \ + --ks-key-alias androiddebugkey \ + --ks-pass pass:android \ + --key-pass pass:android \ + --out app-continuous-signed.apk app-continuous-unsigned.apk || exit 1 + +popd popd mkdir -p artifacts/ -if [[ "$GH_CPU_ARCH" == "64" ]]; then - mv android/xashdroid.apk artifacts/xashdroid-64.apk -else - mv android/xashdroid.apk artifacts/xashdroid-32.apk -fi +mv android/app/build/outputs/apk/continuous/app-continuous-signed.apk artifacts/xash3d-fwgs-android.apk +tar -cJvf artifacts/xash3d-fwgs-android-mappings.tar.zst -C android/app/build/outputs/mapping/continuous '.' + diff --git a/scripts/gha/build_apple.sh b/scripts/gha/build_apple.sh new file mode 100755 index 00000000..2b1521f9 --- /dev/null +++ b/scripts/gha/build_apple.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +. scripts/lib.sh + +cd $GITHUB_WORKSPACE || die + +pushd hlsdk || die +./waf configure build install --destdir=../bin || die +popd + +./waf configure --enable-utils --enable-tests --enable-lto --enable-tui build install --destdir=bin || die_configure + +cp -vr /Library/Frameworks/SDL2.framework bin + +mkdir -p artifacts/ +tar -cJvf artifacts/xash3d-fwgs-apple-$ARCH.tar.xz -C bin . # skip the bin directory from resulting tar archive diff --git a/scripts/gha/build_linux-e2k.sh b/scripts/gha/build_linux-e2k.sh new file mode 100755 index 00000000..f5079950 --- /dev/null +++ b/scripts/gha/build_linux-e2k.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -e + +. scripts/lib.sh +. scripts/lib-e2k.sh + +export CC=${E2K_CROSS_COMPILER_PATH[$GH_CPU_ARCH]}/bin/lcc +export CXX=${E2K_CROSS_COMPILER_PATH[$GH_CPU_ARCH]}/bin/l++ + +APP=xash3d-fwgs + +APPDIR=$APP-linux-$ARCH # FIXME: not conforms to libpublic's build arch strings but in parity with xashds directory name +APPTARGZ=$APP-linux-$ARCH.tar.gz + +DS=xashds-linux +DSDIR=$DS-$ARCH +DSTARGZ=$DS-$ARCH.tar.gz + +build_engine() +{ + # Build engine + cd "$BUILDDIR" + + ./waf configure --enable-dedicated -s usr --enable-stbtt --enable-utils --enable-bundled-deps --enable-all-renderers || die_configure + ./waf build || die_configure +} + +make_client_tarball() +{ + cd "$BUILDDIR" + ./waf install --destdir="$APPDIR" + tar -czvf "artifacts/$APPTARGZ" "$APPDIR" +} + +make_server_tarball() +{ + cd "$BUILDDIR" + + # FIXME: make an option for Waf to only install dedicated + mkdir -p "$DSDIR" + cp -v "$APPDIR"/xash "$APPDIR"/filesystem_stdio.so "$DSDIR" + tar -czvf "artifacts/$DSTARGZ" "$DSDIR" +} + +mkdir -p artifacts/ + +rm -rf build # clean-up build directory +build_engine + +make_client_tarball +make_server_tarball diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index 3ed86cae..3f87244e 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -1,43 +1,56 @@ #!/bin/bash +# As e2k builds for distro that's vastly different from Ubuntu/Debian and specially handles cross-compiling +# keep it in separate script for now +if [[ $GH_CPU_ARCH == e2k* ]]; then + exec bash scripts/gha/build_linux-e2k.sh +fi + . scripts/lib.sh +# "booo, bash feature!" +declare -A ARCH_TRIPLET CROSS_COMPILE_CC CROSS_COMPILE_CXX +ARCH_TRIPLET[amd64]=x86_64-linux-gnu +ARCH_TRIPLET[i386]=i386-linux-gnu +ARCH_TRIPLET[arm64]=aarch64-linux-gnu +ARCH_TRIPLET[armhf]=arm-linux-gnueabihf +ARCH_TRIPLET[riscv64]=riscv64-linux-gnu +ARCH_TRIPLET[ppc64el]=powerpc64le-linux-gnu +CROSS_COMPILE_CC[amd64]=cc +CROSS_COMPILE_CC[i386]="cc -m32" +CROSS_COMPILE_CXX[amd64]=c++ +CROSS_COMPILE_CXX[i386]="c++ -m32" +for i in arm64 armhf riscv64 ppc64el; do + CROSS_COMPILE_CC[$i]=${ARCH_TRIPLET[$i]}-gcc + CROSS_COMPILE_CXX[$i]=${ARCH_TRIPLET[$i]}-g++ +done +export PKG_CONFIG_PATH=$PWD/ffmpeg/lib/pkgconfig:${ARCH_TRIPLET[$GH_CPU_ARCH]} +export CC=${CROSS_COMPILE_CC[$GH_CPU_ARCH]} +export CXX=${CROSS_COMPILE_CXX[$GH_CPU_ARCH]} + APP=xash3d-fwgs APPDIR=$APP.AppDir APPIMAGE=$APP-$ARCH.AppImage +APPDIR2=$APP-linux-$ARCH # FIXME: not conforms to libpublic's build arch strings but in parity with xashds directory name +APPTARGZ=$APP-linux-$ARCH.tar.gz + DS=xashds-linux DSDIR=$DS-$ARCH DSTARGZ=$DS-$ARCH.tar.gz +N=$(nproc) build_sdl2() { cd "$BUILDDIR"/SDL2_src || die - if [ "$ARCH" = "i386" ]; then - export CFLAGS="-msse2 -march=i686 -m32 -ggdb -O2" - export LDFLAGS="-m32" - fi - ./configure \ - --disable-render \ - --disable-haptic \ - --disable-power \ - --disable-filesystem \ - --disable-file \ - --disable-libudev \ - --disable-dbus \ - --disable-ibus \ - --disable-ime \ - --disable-fcitx \ - --enable-alsa-shared \ - --enable-pulseaudio-shared \ - --enable-wayland-shared \ - --enable-x11-shared \ - --prefix / || die # get rid of /usr/local stuff - make -j2 || die - mkdir -p "$BUILDDIR"/SDL2_linux - make install DESTDIR="$BUILDDIR"/SDL2_linux || die - export CFLAGS="" - export LDFLAGS="" + + # a1ba: let's make something different. Rather than removing features + # let's enable everything we can + mkdir -p build || die + pushd build || die + cmake ../ -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$BUILDDIR"/SDL2_linux -DCMAKE_C_FLAGS=-O3 -DSDL_STATIC=OFF -DSDL_RENDER=OFF || die + ninja install -j$((N+1)) || die + popd || die } build_engine() @@ -46,81 +59,78 @@ build_engine() cd "$BUILDDIR" || die if [ "$ARCH" = "amd64" ]; then # we need enabling 64-bit target only on Intel-compatible CPUs - AMD64="-8" + WAF_EXTRA_ARGS="-8" fi - if [ "$1" = "dedicated" ]; then - ./waf configure -T release -d $AMD64 || die - elif [ "$1" = "full" ]; then - ./waf configure --sdl2=SDL2_linux -T release --enable-stb $AMD64 --enable-utils || die - else - die + if [ -d "ffmpeg" ]; then + WAF_EXTRA_ARGS+=" --enable-ffmpeg --enable-ffmpeg-dlopen" fi - ./waf build || die + if [ "$GH_CROSSCOMPILING" != "true" ]; then + WAF_EXTRA_ARGS+=" --enable-tests" + fi + + ./waf configure $WAF_EXTRA_ARGS --enable-lto --enable-bundled-deps -s SDL2_linux --enable-stbtt --enable-utils --enable-tui --enable-dedicated || die_configure + + ./waf build || die_configure +} + +deploy_engine() +{ + cd "$BUILDDIR" || die + ./waf install --destdir="$APPDIR" || die + + cp -av SDL2_linux/lib/libSDL2.so SDL2_linux/lib/libSDL2-* "$APPDIR/" + + if [ "$GH_CPU_ARCH" = "i386" ]; then + cp -av 3rdparty/vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" + fi + + if [ -d "ffmpeg" ]; then + cp -av ffmpeg/lib/libav* ffmpeg/lib/libsw* "$APPDIR/" + fi } build_appimage() { - cd "$BUILDDIR" || die - - ./waf install --destdir="$APPDIR" || die - - cp SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" - if [ "$ARCH" = "i386" ]; then - cp 3rdparty/vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" - fi - - cat > "$APPDIR"/AppRun << 'EOF' -#!/bin/sh - -if [ "$XASH3D_BASEDIR" = "" ]; then - export XASH3D_BASEDIR=$PWD -fi -echo "Xash3D FWGS installed as AppImage." -echo "Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this" - -export XASH3D_EXTRAS_PAK1="${APPDIR}"/valve/extras.pk3 -${DEBUGGER} "${APPDIR}"/xash3d "$@" -exit $? -EOF - - chmod +x "$APPDIR"/xash3d "$APPDIR"/AppRun # Engine launcher & engine launcher script - - echo "Contents of AppImage: " - ls -R "$APPDIR" + deploy_engine + cp scripts/gha/linux/AppRun "$APPDIR/AppRun" + cp scripts/gha/linux/xash3d-fwgs.desktop "$APPDIR/$APP.desktop" wget "https://raw.githubusercontent.com/FWGS/fwgs-artwork/master/xash3d/icon_512.png" -O "$APPDIR/$APP.png" - cat > "$APPDIR/$APP.desktop" <> config.cfg +echo 'bind A_BUTTON "+use"' >> config.cfg +echo 'bind B_BUTTON "+jump"' >> config.cfg +echo 'bind X_BUTTON "+reload"' >> config.cfg +echo 'bind Y_BUTTON "+duck"' >> config.cfg +echo 'bind L1_BUTTON "+attack2"' >> config.cfg +echo 'bind R1_BUTTON "+attack"' >> config.cfg +echo 'bind START "escape"' >> config.cfg +echo 'bind DPAD_UP "lastinv"' >> config.cfg +echo 'bind DPAD_DOWN "impulse 100"' >> config.cfg +echo 'bind DPAD_LEFT "invprev"' >> config.cfg +echo 'bind DPAD_RIGHT "invnext"' >> config.cfg +echo 'gl_vsync "1"' >> config.cfg +echo 'sv_autosave "0"' >> config.cfg + +touch video.cfg +echo 'fullscreen "1"' >> video.cfg +echo 'width "960"' >> video.cfg +echo 'height "544"' >> video.cfg +echo 'r_refdll "gl"' >> video.cfg + +touch opengl.cfg +echo 'gl_nosort "1"' >> opengl.cfg + +cp *.cfg ../gearbox/ +cp *.cfg ../bshift/ + +popd + +echo "Packaging artifacts..." + +pushd pkgtemp || die +7z a -t7z ../artifacts/xash3d-fwgs-psvita.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r xash.vpk data/ +popd diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 2416ac2c..fd380a67 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -5,25 +5,49 @@ # Build engine cd $BUILDDIR +WAF_EXTRA_ARGS="" + if [ "$ARCH" = "amd64" ]; then # we need enabling 64-bit target only on Intel-compatible CPUs - AMD64="-8" + WAF_EXTRA_ARGS="-8" +fi + +if [ -d "ffmpeg" ]; then + export PKGCONFIG="$PWD/pkgconf/bin/pkgconf.exe" + export PKG_CONFIG_PATH="$PWD/ffmpeg/lib/pkgconfig" + WAF_EXTRA_ARGS+=" --enable-ffmpeg --enable-ffmpeg-dlopen" fi # NOTE: to build with other version use --msvc_version during configuration # NOTE: sometimes you may need to add WinSDK to %PATH% -./waf.bat configure -s "SDL2_VC" -T "release" --enable-utils --prefix=`pwd` $AMD64 || die -./waf.bat build -v || die -./waf.bat install || die +# NOTE: --enable-msvcdeps only used for CI builds, enabling it non-English versions of MSVC causes useless console spam +./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto --enable-msvcdeps --enable-tui $WAF_EXTRA_ARGS || die_configure +./waf.bat build || die +./waf.bat install --destdir=. || die if [ "$ARCH" = "i386" ]; then - cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 - cp 3rdparty/vgui_support/vgui-dev/lib/win32_vc6/vgui.dll . + cp -v SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 + cp -v SDL2_VC/lib/x86/SDL2.pdb . elif [ "$ARCH" = "amd64" ]; then - cp SDL2_VC/lib/x64/SDL2.dll . + cp -v SDL2_VC/lib/x64/SDL2.dll . + cp -v SDL2_VC/lib/x64/SDL2.pdb . else die fi +if [ -d "ffmpeg" ]; then + cp -v ffmpeg/bin/av* ffmpeg/bin/sw* . +fi + +WINSDK_LATEST=$(ls -1 "C:/Program Files (x86)/Windows Kits/10/bin" | grep -E '^10' | sort -rV | head -n1) +echo "Latest installed Windows SDK is $WINSDK_LATEST" + +"C:/Program Files (x86)/Windows Kits/10/bin/$WINSDK_LATEST/x64/signtool.exe" \ + sign //f scripts/fwgs.pfx //fd SHA256 //p "$FWGS_PFX_PASSWORD" *.dll *.exe + +if [ "$ARCH" = "i386" ]; then # VGUI is already signed + cp 3rdparty/vgui_support/vgui-dev/lib/win32_vc6/vgui.dll . +fi + mkdir -p artifacts/ 7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on \ *.dll *.exe *.pdb activities.txt \ diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index 59b5c549..e1624c4f 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -1,38 +1,39 @@ #!/bin/bash -echo "Download HLSDK" - cd $GITHUB_WORKSPACE -git clone --depth 1 --recursive https://github.com/FWGS/hlsdk-xash3d -b mobile_hacks hlsdk || exit 1 -echo "Download and unpack Android SDK" +ANDROID_COMMANDLINE_TOOLS_VER="13114758" +ANDROID_BUILD_TOOLS_VER="36.0.0" +ANDROID_PLATFORM_VER="android-35" +ANDROID_NDK_VERSION="28.2.13676358" + +echo "Download JDK 17" +wget 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 +export JAVA_HOME=$GITHUB_WORKSPACE/jdk-17.0.15+6 +export PATH=$PATH:$JAVA_HOME/bin + +echo "Download hlsdk-portable" +git clone --depth 1 --recursive https://github.com/FWGS/hlsdk-portable -b mobile_hacks 3rdparty/hlsdk-portable || exit 1 + +echo "Download SDL" +pushd 3rdparty +wget https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz -qO- | tar -xzf - || exit 1 +mv SDL2-$SDL_VERSION SDL +popd + +echo "Download Android SDK" mkdir -p sdk || exit 1 pushd sdk -wget https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip -qO sdk.zip > /dev/null || exit 1 +wget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_COMMANDLINE_TOOLS_VER}_latest.zip -qO sdk.zip || exit 1 unzip -q sdk.zip || exit 1 +mv cmdline-tools tools +mkdir -p cmdline-tools +mv tools cmdline-tools/tools +unset ANDROID_SDK_ROOT +export ANDROID_HOME=$GITHUB_WORKSPACE/sdk +export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/tools/bin popd -echo "Download all needed tools and NDK" -yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) -sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 > /dev/null 2>/dev/null -wget https://dl.google.com/android/repository/android-ndk-r25-linux.zip -qO ndk.zip > /dev/null || exit 1 -unzip -q ndk.zip || exit 1 -mv android-ndk-r25 sdk/ndk-bundle || exit 1 - -echo "Download Xash3D FWGS Android source" -git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 -pushd android - -mv xash3d-fwgs xash3d-fwgs-sub -ln -s $GITHUB_WORKSPACE xash3d-fwgs -echo "Installed Xash3D FWGS source symlink" - -mv hlsdk-xash3d hlsdk-xash3d-sub -ln -s $GITHUB_WORKSPACE/hlsdk hlsdk-xash3d -echo "Installed HLSDK source symlink" - -mv xash-extras xash-extras-sub -ln -s $GITHUB_WORKSPACE/xash-extras xash-extras -echo "Installed xash-extras symlink" - -popd +echo "Download all needed tools and Android NDK" +yes | sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) +sdkmanager --install build-tools\;${ANDROID_BUILD_TOOLS_VER} platform-tools platforms\;${ANDROID_PLATFORM_VER} ndk\;${ANDROID_NDK_VERSION} diff --git a/scripts/gha/deps_apple.sh b/scripts/gha/deps_apple.sh new file mode 100755 index 00000000..29e86094 --- /dev/null +++ b/scripts/gha/deps_apple.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd $GITHUB_WORKSPACE + +wget https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.dmg -O SDL2.dmg +hdiutil mount SDL2.dmg +sudo cp -vr /Volumes/SDL2/SDL2.framework /Library/Frameworks + +git clone https://github.com/FWGS/hlsdk-portable hlsdk --depth=1 diff --git a/scripts/gha/deps_linux-e2k.sh b/scripts/gha/deps_linux-e2k.sh new file mode 100755 index 00000000..80af8cd7 --- /dev/null +++ b/scripts/gha/deps_linux-e2k.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +cd "$GITHUB_WORKSPACE" + +. scripts/lib-e2k.sh + +wget "${E2K_CROSS_COMPILER_URL[$GH_CPU_ARCH]}" -O- | sudo tar -C / -xzvf - + +for i in ${E2K_PACKAGES_URLS[$GH_CPU_ARCH]}; do + wget "$i" -O package.deb + ar x package.deb data.tar.xz + tar -xvf data.tar.xz + rm package.deb data.tar.xz +done diff --git a/scripts/gha/deps_linux.sh b/scripts/gha/deps_linux.sh index 7d351a9a..75c3cb9f 100755 --- a/scripts/gha/deps_linux.sh +++ b/scripts/gha/deps_linux.sh @@ -1,29 +1,95 @@ #!/bin/bash -cd $GITHUB_WORKSPACE - -if [ "$GH_CPU_ARCH" == "i386" ]; then - sudo dpkg --add-architecture i386 - sudo apt update - sudo apt install gcc-multilib g++-multilib libx11-dev:i386 libxext-dev:i386 x11-utils libgl1-mesa-dev libasound-dev libstdc++6:i386 libfuse2:i386 zlib1g:i386 - - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-i686.AppImage" -O appimagetool.AppImage -elif [ "$GH_CPU_ARCH" == "amd64" ]; then - sudo apt update - sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g - - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O appimagetool.AppImage -elif [ "$GH_CPU_ARCH" == "aarch64" ]; then - sudo apt update - sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g - - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage" -O appimagetool.AppImage -else - exit 1 +# As e2k builds for distro that's vastly different from Ubuntu/Debian and specially handles cross-compiling +# keep it in separate script for now +if [[ $GH_CPU_ARCH == e2k* ]]; then + exec bash scripts/gha/deps_linux-e2k.sh fi -chmod +x appimagetool.AppImage +. scripts/lib.sh -wget http://libsdl.org/release/SDL2-$SDL_VERSION.zip -O SDL2.zip -unzip -q SDL2.zip -mv SDL2-$SDL_VERSION SDL2_src +cd "$GITHUB_WORKSPACE" || exit 1 + +# "booo, bash feature!", -- posix sh users, probably +declare -A BASE_BUILD_PACKAGES SDL_BUILD_PACKAGES APPIMAGETOOL RUST_TARGET + +# bzip2 and opus are added from submodules, freetype replaced by stb_truetype in this build, so it's just compiler toolchain +BASE_BUILD_PACKAGES[common]="desktop-file-utils" +BASE_BUILD_PACKAGES[amd64]="build-essential" +BASE_BUILD_PACKAGES[i386]="gcc-multilib g++-multilib" +BASE_BUILD_PACKAGES[arm64]="crossbuild-essential-arm64" +BASE_BUILD_PACKAGES[armhf]="crossbuild-essential-armhf" +BASE_BUILD_PACKAGES[riscv64]="crossbuild-essential-riscv64" +BASE_BUILD_PACKAGES[ppc64el]="crossbuild-essential-ppc64el" + +SDL_BUILD_PACKAGES[common]="cmake ninja-build" +# TODO: add libpipewire-0.3-dev and libdecor-0-dev after we migrate from 20.04 +# TODO: figure out how to install fcitx and ibus dev in cross compile environment on gha +# In theory, we better run this in limited container. Right now, some preinstalled PHP shit breaks libpcre builds +# and prevents us from installing crosscompiling packages +SDL_BUILD_PACKAGES[amd64]="libasound2-dev libpulse-dev \ + libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev \ + libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libwayland-dev \ + libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \ + libegl1-mesa-dev libdbus-1-dev libudev-dev" +SDL_BUILD_PACKAGES[i386]="${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:i386} libjack0:i386" # test +SDL_BUILD_PACKAGES[arm64]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:arm64} +SDL_BUILD_PACKAGES[armhf]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:armhf} +SDL_BUILD_PACKAGES[riscv64]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:riscv64} +SDL_BUILD_PACKAGES[ppc64el]=${SDL_BUILD_PACKAGES[amd64]//-dev/-dev:ppc64el} + +APPIMAGETOOL[amd64]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage +APPIMAGETOOL[i386]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-i686.AppImage + +# can't run AppImageTool yet because it's compiled for these platforms natively and don't support cross compilation yet +# uncomment when we will enable qemu-user for tests +# APPIMAGETOOL[arm64]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage +# APPIMAGETOOL[armhf]=https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-armhf.AppImage + +RUST_TARGET[amd64]=x86_64-unknown-linux-gnu +RUST_TARGET[i386]=i686-unknown-linux-gnu +RUST_TARGET[arm64]=aarch64-unknown-linux-gnu +RUST_TARGET[armhf]=thumbv7neon-unknown-linux-gnueabihf +RUST_TARGET[riscv64]=riscv64gc-unknown-linux-gnu +RUST_TARGET[ppc64el]=powerpc64le-unknown-linux-gnu + +regenerate_sources_list() +{ + # this is evil but to speed up update, specify all repositories manually + sudo rm /etc/apt/sources.list + sudo rm -rf /etc/apt/sources.list.d + + codename=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2) + + for i in "$codename" "$codename-updates" "$codename-backports" "$codename-security"; do + echo "deb [arch=$GH_CPU_ARCH] http://azure.ports.ubuntu.com/ubuntu-ports $i main universe" | sudo tee -a /etc/apt/sources.list + echo "deb [arch=amd64] http://azure.archive.ubuntu.com/ubuntu $i main universe" | sudo tee -a /etc/apt/sources.list + done +} + +if [ "$GH_CPU_ARCH" != "amd64" ] && [ -n "$GH_CPU_ARCH" ]; then + if [ "$GH_CPU_ARCH" != "i386" ]; then + regenerate_sources_list + fi + sudo dpkg --add-architecture "$GH_CPU_ARCH" +fi + +sudo apt update || die +sudo apt install aptitude || die # aptitude is just more reliable at resolving dependencies + +# shellcheck disable=SC2086 # splitting is intended here +sudo aptitude install -y ${BASE_BUILD_PACKAGES[common]} ${BASE_BUILD_PACKAGES[$GH_CPU_ARCH]} ${SDL_BUILD_PACKAGES[common]} ${SDL_BUILD_PACKAGES[$GH_CPU_ARCH]} || die + +if [ -n "${APPIMAGETOOL[$GH_CPU_ARCH]}" ]; then + wget -O appimagetool.AppImage "${APPIMAGETOOL[$GH_CPU_ARCH]}" + chmod +x appimagetool.AppImage +fi + +FFMPEG_ARCHIVE=$(get_ffmpeg_archive) +wget https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.tar.xz -qO- | tar -xJf - +mv $FFMPEG_ARCHIVE ffmpeg + +wget "https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/SDL2-$SDL_VERSION.tar.gz" -qO- | tar -xzf - +mv "SDL2-$SDL_VERSION" SDL2_src + +rustup target add "${RUST_TARGET[$GH_CPU_ARCH]}" diff --git a/scripts/gha/deps_nswitch.sh b/scripts/gha/deps_nswitch.sh new file mode 100644 index 00000000..7f7933f6 --- /dev/null +++ b/scripts/gha/deps_nswitch.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +cd $GITHUB_WORKSPACE + +echo "Downloading devkitA64 docker container..." + +docker pull devkitpro/devkita64:latest || exit 1 + +echo "Downloading libsolder..." + +rm -rf libsolder +git clone https://github.com/fgsfdsfgs/libsolder.git --depth=1 || exit 1 + +echo "Downloading HLSDK..." + +rm -rf hlsdk-xash3d hlsdk-portable +git clone --recursive https://github.com/FWGS/hlsdk-portable || exit 1 diff --git a/scripts/gha/deps_psvita.sh b/scripts/gha/deps_psvita.sh new file mode 100644 index 00000000..10cd6920 --- /dev/null +++ b/scripts/gha/deps_psvita.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +cd $GITHUB_WORKSPACE + +# pinning cmake version to 3.28.3, because with cmake 4.x SDL vita fork doesn't building +# it is known problem and cmake 4.x update breaks many CI pipelines around the world :) +sudo apt-get update || exit 1 +sudo apt-get install cmake=3.28.3-1build7 +sudo ln -sf /usr/bin/cmake /usr/local/bin/cmake + +echo "Downloading vitasdk..." + +export VITASDK=/usr/local/vitasdk + +VITAGL_SRCREV="064db9efb15833e18777a3e768b8b1fb2abee78f" # lock vitaGL version to avoid compilation errors + +install_package() +{ + ./vdpm $1 || exit 1 +} + +git clone https://github.com/vitasdk/vdpm.git --depth=1 || exit 1 +pushd vdpm +./bootstrap-vitasdk.sh || exit 1 +install_package taihen +install_package kubridge +install_package zlib +install_package SceShaccCgExt +install_package vitaShaRK +install_package libmathneon +popd + +echo "Downloading vitaGL..." + +git clone https://github.com/Rinnegatamante/vitaGL.git || exit 1 +pushd vitaGL +git checkout $VITAGL_SRCREV || exit 1 +popd + +echo "Downloading vitaGL fork of SDL2..." + +git clone https://github.com/Northfear/SDL.git --depth=1 || exit 1 + +echo "Downloading vita-rtld..." + +git clone https://github.com/fgsfdsfgs/vita-rtld.git --depth=1 || exit 1 + +echo "Downloading HLSDK..." + +rm -rf hlsdk-xash3d hlsdk-portable +git clone --recursive https://github.com/FWGS/hlsdk-portable || exit 1 diff --git a/scripts/gha/deps_win32.sh b/scripts/gha/deps_win32.sh index f73c47ea..ae38c5d1 100755 --- a/scripts/gha/deps_win32.sh +++ b/scripts/gha/deps_win32.sh @@ -1,5 +1,24 @@ #!/bin/bash -curl http://libsdl.org/release/SDL2-devel-$SDL_VERSION-VC.zip -o SDL2.zip +. scripts/lib.sh + +curl -L http://libsdl.org/release/SDL2-devel-$SDL_VERSION-VC.zip -o SDL2.zip unzip -q SDL2.zip mv SDL2-$SDL_VERSION SDL2_VC + +if [ "$GH_CPU_ARCH" = "i386" ]; then + rustup target add i686-pc-windows-msvc +fi + +curl -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 +7z x pkgconf.tar.zst +7z x pkgconf.tar +rm pkgconf.tar* +mv mingw64 pkgconf + +FFMPEG_ARCHIVE=$(get_ffmpeg_archive) +curl -L https://github.com/FWGS/FFmpeg-Builds/releases/download/latest/$FFMPEG_ARCHIVE.zip -o ffmpeg.zip +if [ -f ffmpeg.zip ]; then + unzip -x ffmpeg.zip + mv $FFMPEG_ARCHIVE ffmpeg +fi diff --git a/scripts/gha/linux/AppRun b/scripts/gha/linux/AppRun new file mode 100644 index 00000000..6af4e13b --- /dev/null +++ b/scripts/gha/linux/AppRun @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ "$XASH3D_BASEDIR" = "" ]; then + export XASH3D_BASEDIR="$PWD" +fi +echo "Xash3D FWGS installed as AppImage." +echo "Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this." + +export XASH3D_EXTRAS_PAK1="${APPDIR}/valve/extras.pk3" +exec $DEBUGGER "${APPDIR}/xash3d" "$@" diff --git a/scripts/gha/linux/xash3d-fwgs.desktop b/scripts/gha/linux/xash3d-fwgs.desktop new file mode 100644 index 00000000..7b57dd9f --- /dev/null +++ b/scripts/gha/linux/xash3d-fwgs.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Categories=Game;Shooter; +Comment=Half-Life compatible game engine +Exec=AppRun +Icon=xash3d-fwgs +Keywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer; +Name=Xash3D FWGS +# Doesn't pass validation with old desktop-file-utils +#PrefersNonDefaultGPU=true +Terminal=false +Type=Application diff --git a/scripts/lib-e2k.sh b/scripts/lib-e2k.sh new file mode 100644 index 00000000..c00adc0b --- /dev/null +++ b/scripts/lib-e2k.sh @@ -0,0 +1,25 @@ +declare -A E2K_CROSS_COMPILER_URL E2K_CROSS_COMPILER_PATH E2K_PACKAGES_URLS + +# NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE +# NOTE NOTE +# NOTE The cross-compiler is officially distributed on dev.mcst.ru website. NOTE +# NOTE setwd.ws is OpenE2K's unofficial mirror for Elbrus dev community. NOTE +# NOTE Do NOT hammer down the server with your CI/CD, AI and other bullshit! NOTE +# NOTE Cache the archives on YOUR OWN INFRASTRUCTURE! NOTE +# NOTE NOTE +# NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE +E2K_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" +E2K_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" +E2K_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" +E2K_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" +E2K_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" +E2K_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" + +# TODO: fill in the gaps +E2K_CROSS_COMPILER_PATH[e2k-8c]="/opt/mcst/lcc-1.27.21.e2k-v4.5.10/" + +# TODO: split by some character, as bash can't assign lists to array members +E2K_PACKAGES_URLS[e2k-4c]="https://setwd.ws/osl/8.2/pool/main/e2k-4c/SDL2_2.30.0-vd8u4_e2k-4c.deb" +E2K_PACKAGES_URLS[e2k-8c]="https://setwd.ws/osl/8.2/pool/main/e2k-8c/SDL2_2.30.0-vd8u4_e2k-8c.deb" +E2K_PACKAGES_URLS[e2k-8c2]="https://setwd.ws/osl/8.2/pool/main/e2k-8c2/SDL2_2.30.0-vd8u4_e2k-8c2.deb" +E2K_PACKAGES_URLS[e2k-16c]="https://setwd.ws/osl/8.2/pool/main/e2k-16c/SDL2_2.30.0-vd8u4_e2k-16c.deb" diff --git a/scripts/lib.sh b/scripts/lib.sh index d62cee6a..cfe5ee14 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -1,9 +1,35 @@ die() { - cat build/config.log exit 1 } +die_configure() +{ + cat build/config.log + die +} + +get_ffmpeg_archive() +{ + if [ "$GH_CPU_OS" == "win32" ]; then + A=win + else + A="$GH_CPU_OS" + fi + + if [ "$GH_CPU_ARCH" == "amd64" ]; then + B=64 + elif [ "$GH_CPU_ARCH" == "i386" ]; then + B=32 + else + B="$GH_CPU_ARCH" + fi + + FLAVOR=lgpl-shared-minimal + + echo "ffmpeg-n$FFMPEG_VERSION-latest-$A$B-$FLAVOR-$FFMPEG_VERSION" +} + if [ -n "$TRAVIS_BUILD_DIR" ]; then BUILDDIR=$TRAVIS_BUILD_DIR elif [ -n "$GITHUB_WORKSPACE" ]; then diff --git a/scripts/sailfish/build.sh b/scripts/sailfish/build.sh new file mode 100755 index 00000000..a451a845 --- /dev/null +++ b/scripts/sailfish/build.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +. scripts/lib.sh + +download_sign_key() +{ + echo -n "Downloading $1: " + curl https://community.omprussia.ru/documentation/files/doc/$1 -o $1 &> /dev/null || die "Can't download $1" && echo "OK" +} + +pack_sources() +{ + echo "Packing sources to $1..." + git ls-files --recurse-submodules | tar caf "$1" -T- +} + +dependencies="SDL2-devel ImageMagick" +build_dir="build_rpm" + +if [ "$1" == "aurora" ]; then + name="su.xash.Engine" + flavor="aurora" +else + name="harbour-xash3d-fwgs" + flavor="sailfish" +fi + +if [ $flavor == 'aurora' ]; then + download_sign_key regular_key.pem + download_sign_key regular_cert.pem +fi + +rm -fr ${build_dir}/{BUILD,SRPMS} +mkdir -p ${build_dir}/SOURCES +pack_sources ${build_dir}/SOURCES/${name}.tar +git clone https://github.com/FWGS/hlsdk-portable -b mobile_hacks +pushd hlsdk-portable +pack_sources ../${build_dir}/SOURCES/hlsdk-portable.tar +popd + +sfdk_targets=`sfdk engine exec sb2-config -l | grep default | grep -v i486` + +for each in $sfdk_targets; do + target_arch=${each##*-} + target_arch=${target_arch/.default/} + echo "Build for '$each' target with '$target_arch' architecture" + + # install deps for current target + sfdk engine exec sb2 -t $each -R -m sdk-install zypper in -y ${dependencies} + + # build RPM for current target + sfdk engine exec sb2 -t $each rpmbuild \ + --define "_topdir `pwd`/${build_dir}" \ + --define "_arch $target_arch" \ + --define "_packagename $name" \ + --define "_flavor $flavor" \ + -ba scripts/sailfish/harbour-xash3d-fwgs.spec || die "Build for ${each}: FAIL" + + # sign RPM packacge + if [ "$flavor" == "aurora" ]; then + echo -n "Signing RPMs: " + sfdk 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" + fi + + echo -n "Validate RPM: " + if [ "$flavor" == "aurora" ]; then + validator_output=`sfdk engine exec rpm-validator -p regular $(pwd)/${build_dir}/RPMS/${target_arch}/$name-0* 2>&1` + else + sfdk config target=${each/.default/} + validator_output=`sfdk check $(pwd)/${build_dir}/RPMS/${target_arch}/$name-0* 2>&1` + fi + if [ $? -ne 0 ] ; then + echo "FAIL" + echo "${validator_output}" + break; + fi + echo "OK" +done +echo "All build done! All your packages in `pwd`/build_rpm/RPMS" diff --git a/scripts/sailfish/deploy.sh b/scripts/sailfish/deploy.sh new file mode 100755 index 00000000..90b95556 --- /dev/null +++ b/scripts/sailfish/deploy.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +PASSWORD="12345" +IP="192.168.1.10" + +if [ "$1" == "aurora" ]; then + name="su.xash.Engine" + arch="armv7hl" # lame! +else + name="harbour-xash3d-fwgs" + arch="aarch64" # absolutely lame... +fi +package=$(ls build_rpm/RPMS/$arch/$name-0*.rpm) + +sshpass -p $PASSWORD scp $package defaultuser@$IP:~/ + +# sandwich of programs: +# 1. Call sshpass to automatically fill password for ssh session +# 2. Call devel-su that reads password from stdin +# 3. devel-su calls pkcon install-local -y and installs the package +echo $PASSWORD | sshpass -p $PASSWORD ssh defaultuser@$IP devel-su pkcon install-local -y $(basename $package) diff --git a/scripts/sailfish/harbour-xash3d-fwgs.desktop b/scripts/sailfish/harbour-xash3d-fwgs.desktop new file mode 100644 index 00000000..cadf0b60 --- /dev/null +++ b/scripts/sailfish/harbour-xash3d-fwgs.desktop @@ -0,0 +1,17 @@ +[Desktop Entry] +Categories=Game;Shooter; +Comment=Half-Life compatible game engine +Exec=__REPLACE_EXEC__ +Icon=__REPLACE_ICON__ +Keywords=first;person;shooter;multiplayer;half-life;halflife;singleplayer; +Name=Xash3D FWGS +Name[en]=Xash3D FWGS +PrefersNonDefaultGPU=true +Terminal=false +Type=Application +X-Nemo-Application-Type=silica-qt5 + +[X-Sailjail] +Permissions=Audio;Internet;Microphone;PublicDir; +OrganizationName=su.xash +ApplicationName=Engine diff --git a/scripts/sailfish/harbour-xash3d-fwgs.spec b/scripts/sailfish/harbour-xash3d-fwgs.spec new file mode 100644 index 00000000..1efa1946 --- /dev/null +++ b/scripts/sailfish/harbour-xash3d-fwgs.spec @@ -0,0 +1,83 @@ +# Based on harbour-quake2 spec file + +Name: %{_packagename} +Summary: Xash3D FWGS +Release: 1 +Version: 0.21 +Group: Amusements/Games +License: GPLv2 +BuildArch: %{_arch} +URL: https://github.com/FWGS/xash3d-fwgs +Source0: %{name}.tar +Source1: hlsdk-portable.tar +BuildRequires: SDL2-devel ImageMagick + +%define __provides_exclude_from ^%{_datadir}/%{name}/lib/.*$ + +%description +Xash3D FWGS is a game engine compatible with Half-Life 1 and mods. + +%prep +tar -xf %{_topdir}/SOURCES/%{name}.tar +python3 waf configure \ + -T release \ + --sailfish=%{_flavor} \ + --enable-stbtt \ + --enable-bundled-deps \ + --enable-packaging \ + --disable-gl \ + --enable-gles2 \ + --enable-gl4es \ + --prefix=/usr \ + --libdir=%{_datadir}/%{name}/lib \ + --bindir=%{_bindir} + +mkdir -p hlsdk-portable +pushd hlsdk-portable +tar -xf %{_topdir}/SOURCES/hlsdk-portable.tar +python3 waf configure -T release +popd + +%build +python3 waf build -j$(($(nproc)+1)) +pushd hlsdk-portable +python3 waf build -j$(($(nproc)+1)) +popd + +%install +python3 waf install --destdir=%{buildroot} +pushd hlsdk-portable +python3 waf install --destdir=%{buildroot}%{_datadir}/%{name}/rodir +popd +# rename real binary +mv %{buildroot}/usr/bin/xash3d %{buildroot}/usr/bin/%{name} + +install -d %{buildroot}/%{_datadir}/applications +sed "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 +chmod 644 %{buildroot}/%{_datadir}/applications/%{name}.desktop + +install -d %{buildroot}/%{_datadir}/icons/hicolor/86x86/apps +install -d %{buildroot}/%{_datadir}/icons/hicolor/108x108/apps +install -d %{buildroot}/%{_datadir}/icons/hicolor/128x128/apps +install -d %{buildroot}/%{_datadir}/icons/hicolor/172x172/apps +convert game_launch/icon-xash-material.png -resize 86x86 %{buildroot}/%{_datadir}/icons/hicolor/86x86/apps/%{name}.png +convert game_launch/icon-xash-material.png -resize 108x108 %{buildroot}/%{_datadir}/icons/hicolor/108x108/apps/%{name}.png +convert game_launch/icon-xash-material.png -resize 128x128 %{buildroot}/%{_datadir}/icons/hicolor/128x128/apps/%{name}.png +convert game_launch/icon-xash-material.png -resize 172x172 %{buildroot}/%{_datadir}/icons/hicolor/172x172/apps/%{name}.png + +%files +%defattr(-,root,root,-) +%attr(755,root,root) %{_bindir}/%{name} +%attr(755,root,root) %{_datadir}/%{name}/lib/* +%attr(644,root,root) %{_datadir}/%{name}/rodir/valve/extras.pk3 +%attr(755,root,root) %{_datadir}/%{name}/rodir/valve/cl_dlls/* +%attr(755,root,root) %{_datadir}/%{name}/rodir/valve/dlls/* +%attr(644,root,root) %{_datadir}/icons/hicolor/86x86/apps/%{name}.png +%attr(644,root,root) %{_datadir}/icons/hicolor/108x108/apps/%{name}.png +%attr(644,root,root) %{_datadir}/icons/hicolor/128x128/apps/%{name}.png +%attr(644,root,root) %{_datadir}/icons/hicolor/172x172/apps/%{name}.png +%{_datadir}/applications/%{name}.desktop + +%changelog +* Thu Jun 1 2023 a1batross +- initial port diff --git a/scripts/sailfish/run.sh b/scripts/sailfish/run.sh new file mode 100644 index 00000000..cdd59e53 --- /dev/null +++ b/scripts/sailfish/run.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +die() +{ + echo "$@" + exit 1 +} + +echo "Xash3D FWGS installed as Sailfish RPM" + +# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +# $XDG_DATA_HOME defines the base directory relative to which user-specific data files should be stored. +# If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. +if [ -z "$XDG_DATA_HOME" ]; then + export XDG_DATA_HOME="$HOME/.local/share" +fi + +if [ -z "$XASH3D_BASEDIR" ]; then + export XASH3D_BASEDIR="$XDG_DATA_HOME/xash3d-fwgs/" +fi + +mkdir -p "$XASH3D_BASEDIR" +cd "$XASH3D_BASEDIR" || die "Can't cd into $XASH3D_BASEDIR" +echo "XASH3D_BASEDIR is $XASH3D_BASEDIR" + +#if [ -z "$XASH3D_EXTRAS_PAK1" ]; then +# export XASH3D_EXTRAS_PAK1=/app/share/xash3d/valve/extras.pk3 +#fi +#echo "XASH3D_EXTRAS_PAK1 is $XASH3D_EXTRAS_PAK1" + +exec $DEBUGGER /app/lib32/xash3d/xash3d "$@" diff --git a/scripts/waifulib/c_emscripten.py b/scripts/waifulib/c_emscripten.py new file mode 100644 index 00000000..47e2e79b --- /dev/null +++ b/scripts/waifulib/c_emscripten.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# -*- coding: utf-8 vi:ts=4:noexpandtab + +from waflib.Tools import ccroot, gcc, gxx +from waflib.Configure import conf +from waflib.TaskGen import feature, after_method +from waflib import Utils + +@conf +def get_emscripten_version(conf, cc): + k = conf.get_cc_version(cc, clang=True) + + if '__EMSCRIPTEN__' not in k: + conf.fatal('Could not determine the emscripten compiler version') + + conf.env.DEST_OS = 'emscripten' + conf.env.DEST_CPU = 'wasm' + +@conf +def find_emcc(conf): + cc = conf.find_program(['emcc'], var='CC') + conf.get_emscripten_version(cc) + conf.env.CC_NAME = 'clang' + +@conf +def find_emxx(conf): + cxx = conf.find_program(['em++'], var='CXX') + conf.get_emscripten_version(cxx) + conf.env.CXX_NAME = 'clang' + +@conf +def gcc_modifier_emscripten(conf): + v = conf.env + + conf.env.cshlib_PATTERN = 'lib%s.wasm' + conf.env.cprogram_PATTERN = '%s.html' + + conf.env.CFLAGS_cshlib = ['-fPIC', '-sSIDE_MODULE=1'] + conf.env.CFLAGS_cstlib = ['-fPIC'] + conf.env.CFLAGS_cprogram = ['-sMAIN_MODULE=1'] + + conf.env.LINKFLAGS_cshlib = ['-sSIDE_MODULE=1'] + conf.env.LINKFLAGS_cprogram = ['-sMAIN_MODULE=1'] + +@conf +def gxx_modifier_emscripten(conf): + v = conf.env + + conf.env.cxxshlib_PATTERN = 'lib%s.wasm' + conf.env.cxxprogram_PATTERN = '%s.html' + conf.env.CXXFLAGS_cxxshlib = ['-fPIC', '-sSIDE_MODULE=1'] + conf.env.CXXFLAGS_cxxstlib = ['-fPIC'] + conf.env.CXXFLAGS_cxxprogram = ['-sMAIN_MODULE=1'] + + conf.env.LINKFLAGS_cxxshlib = ['-sSIDE_MODULE=1'] + conf.env.LINKFLAGS_cxxprogram = ['-sMAIN_MODULE=1'] + +@feature('cxxprogram', 'cprogram') +@after_method('apply_link') +def apply_indexhtml(self): + if self.env.DEST_OS != 'emscripten': + return + + tsk = self.link_task + node = tsk.outputs[0] + + tsk.outputs.append(node.change_ext('.js')) + tsk.outputs.append(node.change_ext('.wasm')) + + if '--preload-file' in getattr(self, 'linkflags', []) + self.env.LINKFLAGS: + tsk.outputs.append(node.change_ext('.data')) + + inst_to = getattr(self, 'special_install_path', None) + if inst_to: + self.add_install_as(install_to=inst_to + '/index.html', + install_from=tsk.outputs[0], chmod=Utils.O644, task=tsk) + + self.add_install_files(install_to=inst_to, + install_from=tsk.outputs[1:], chmod=Utils.O644, task=tsk) + +def configure(conf): + if not conf.env.CC: + conf.find_emcc() + conf.gcc_common_flags() + conf.gcc_modifier_platform() + conf.cc_load_tools() + conf.cc_add_flags() + conf.link_add_flags() + + if not conf.env.AR: + conf.find_program(['emar'], var='AR') + conf.find_ar() + + if not conf.env.CXX: + conf.find_emxx() + conf.gxx_common_flags() + conf.gxx_modifier_platform() + conf.cxx_load_tools() + conf.cxx_add_flags() + conf.link_add_flags() diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 3d653770..d12577fa 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -30,7 +30,7 @@ compiler_optimizations.CFLAGS['gottagofast'] = { } ''' -VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'sanitize', 'none'] +VALID_BUILD_TYPES = ['fastnative', 'fast', 'humanrights', 'debug', 'sanitize', 'msan', 'asan', 'none'] LINKFLAGS = { 'common': { @@ -38,6 +38,15 @@ LINKFLAGS = { 'gcc': ['-Wl,--no-undefined'], 'owcc': ['-Wl,option stack=512k'] }, + 'msan': { + 'clang': ['-fsanitize=memory', '-pthread'], + 'default': ['NO_MSAN_HERE'] + }, + 'asan': { + 'clang': ['-fsanitize=address', '-pthread'], + 'gcc': ['-fsanitize=address', '-pthread'], + 'msvc': ['/SAFESEH:NO'] + }, 'sanitize': { 'clang': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'gcc': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], @@ -52,28 +61,34 @@ CFLAGS = { 'common': { # disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP', '/Zc:__cplusplus'], - 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics'], - 'gcc': ['-g', '-fvisibility=hidden'], + 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics', '-fasynchronous-unwind-tables'], + 'gcc': ['-g', '-fvisibility=hidden', '-fasynchronous-unwind-tables'], 'owcc': ['-fno-short-enum', '-ffloat-store', '-g3'] }, 'fast': { 'msvc': ['/O2', '/Oy', '/Zi'], 'gcc': { '3': ['-O3', '-fomit-frame-pointer'], - 'default': ['-Ofast', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer'] + '4': ['-Ofast', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer'], + 'default': ['-Ofast', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer', '-fno-semantic-interposition'] }, 'clang': ['-Ofast'], 'default': ['-O3'] }, 'fastnative': { 'msvc': ['/O2', '/Oy', '/Zi'], - 'gcc': ['-Ofast', '-march=native', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer'], + 'gcc': ['-Ofast', '-march=native', '-funsafe-math-optimizations', '-funsafe-loop-optimizations', '-fomit-frame-pointer', '-fno-semantic-interposition'], 'clang': ['-Ofast', '-march=native'], 'default': ['-O3'] }, - 'release': { + 'humanrights': { 'msvc': ['/O2', '/Zi'], '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'], + 'gcc': { + '4': ['-O3'], + '3': ['-O3'], + 'default': ['-O3','-fno-semantic-interposition'], + }, 'default': ['-O3'] }, 'debug': { @@ -81,6 +96,16 @@ CFLAGS = { 'owcc': ['-O0', '-fno-omit-frame-pointer', '-funwind-tables', '-fno-omit-leaf-frame-pointer'], 'default': ['-O0'] }, + 'msan': { + 'clang': ['-O2', '-g', '-fno-omit-frame-pointer', '-fsanitize=memory', '-pthread'], + 'default': ['NO_MSAN_HERE'] + }, + 'asan': { + 'msvc': ['/Od', '/RTC1', '/Zi', '/fsanitize=address'], + 'gcc': ['-Og', '-fsanitize=address', '-pthread'], + 'clang': ['-Og', '-fsanitize=address', '-pthread'], + 'default': ['-O0'] + }, 'sanitize': { 'msvc': ['/Od', '/RTC1', '/Zi', '/fsanitize=address'], 'gcc': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], @@ -91,13 +116,13 @@ CFLAGS = { LTO_CFLAGS = { 'msvc': ['/GL'], - 'gcc': ['-flto'], + 'gcc': ['-flto=auto'], 'clang': ['-flto'] } LTO_LINKFLAGS = { 'msvc': ['/LTCG'], - 'gcc': ['-flto'], + 'gcc': ['-flto=auto'], 'clang': ['-flto'] } @@ -107,30 +132,76 @@ POLLY_CFLAGS = { # msvc sosat :( } +OPENMP_CFLAGS = { + 'gcc': ['-fopenmp', '-DHAVE_OPENMP=1'], + 'clang': ['-fopenmp', '-DHAVE_OPENMP=1'], + 'msvc': ['/openmp', '/DHAVE_OPENMP=1'] +} + +OPENMP_LINKFLAGS = { + 'gcc': ['-fopenmp'], + 'clang': ['-fopenmp'], +} + +PROFILE_GENERATE_CFLAGS = { + 'gcc': ['-fprofile-generate=xash3d-prof'], +} + +PROFILE_GENERATE_LINKFLAGS = { + 'gcc': ['-fprofile-generate=xash3d-prof'], +} + +PROFILE_USE_CFLAGS = { + 'gcc': ['-fprofile-use=%s'], +} + +PROFILE_USE_LINKFLAGS = { + 'gcc': ['-fprofile-use=%s'], +} + def options(opt): grp = opt.add_option_group('Compiler optimization options') - grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default=None, + grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default='humanrights', help = 'build type: debug, release or none(custom flags)') grp.add_option('--enable-lto', action = 'store_true', dest = 'LTO', default = False, - help = 'enable Link Time Optimization if possible [default: %default]') + help = 'enable Link Time Optimization if possible [default: %(default)s]') grp.add_option('--enable-poly-opt', action = 'store_true', dest = 'POLLY', default = False, - help = 'enable polyhedral optimization if possible [default: %default]') + help = 'enable polyhedral optimization if possible [default: %(default)s]') + + grp.add_option('--enable-openmp', action = 'store_true', dest = 'OPENMP', default = False, + help = 'enable OpenMP extensions [default: %(default)s]') + + grp.add_option('--enable-profile', action = 'store_true', dest = 'PROFILE_GENERATE', default = False, + help = 'enable profile generating build (stored in xash3d-prof directory) [default: %(default)s]') + + grp.add_option('--enable-limited-debuginfo', action = 'store_true', dest = 'LIMITED_DEBUGINFO', default = False, + help = 'only save line debuginfo, useful for release builds [default: %(default)s]') + + grp.add_option('--use-profile', action = 'store', dest = 'PROFILE_USE', default = None, + help = 'use profile during build [default: %(default)s]') def configure(conf): conf.start_msg('Build type') - if conf.options.BUILD_TYPE == None: - conf.end_msg('not set', color='RED') - conf.fatal('Set a build type, for example "-T release"') - elif not conf.options.BUILD_TYPE in VALID_BUILD_TYPES: + + # legacy naming for default release build + # https://chaos.social/@karolherbst/111340511652012860 + if conf.options.BUILD_TYPE == 'release': + conf.options.BUILD_TYPE = 'humanrights' + + if not conf.options.BUILD_TYPE in VALID_BUILD_TYPES: conf.end_msg(conf.options.BUILD_TYPE, color='RED') conf.fatal('Invalid build type. Valid are: %s' % ', '.join(VALID_BUILD_TYPES)) + conf.end_msg(conf.options.BUILD_TYPE) conf.msg('LTO build', 'yes' if conf.options.LTO else 'no') conf.msg('PolyOpt build', 'yes' if conf.options.POLLY else 'no') + conf.msg('OpenMP build', 'yes' if conf.options.OPENMP else 'no') + conf.msg('Generate profile', 'yes' if conf.options.PROFILE_GENERATE else 'no') + conf.msg('Use profile', conf.options.PROFILE_USE if not conf.options.PROFILE_GENERATE else 'no') # -march=native should not be used if conf.options.BUILD_TYPE.startswith('fast'): @@ -161,4 +232,47 @@ def get_optimization_flags(conf): if conf.options.POLLY: cflags += conf.get_flags_by_compiler(POLLY_CFLAGS, conf.env.COMPILER_CC) + if conf.options.OPENMP: + linkflags+= conf.get_flags_by_compiler(OPENMP_LINKFLAGS, conf.env.COMPILER_CC) + cflags += conf.get_flags_by_compiler(OPENMP_CFLAGS, conf.env.COMPILER_CC) + + if conf.options.PROFILE_GENERATE: + linkflags+= conf.get_flags_by_compiler(PROFILE_GENERATE_LINKFLAGS, conf.env.COMPILER_CC) + cflags += conf.get_flags_by_compiler(PROFILE_GENERATE_CFLAGS, conf.env.COMPILER_CC) + elif conf.options.PROFILE_USE: + linkflags+= [conf.get_flags_by_compiler(PROFILE_USE_LINKFLAGS, conf.env.COMPILER_CC)[0] % conf.options.PROFILE_USE] + cflags += [conf.get_flags_by_compiler(PROFILE_USE_CFLAGS, conf.env.COMPILER_CC)[0] % conf.options.PROFILE_USE] + + if conf.env.DEST_OS == 'nswitch': + if conf.options.BUILD_TYPE == 'debug': + # enable remote debugger + cflags.append('-DNSWITCH_DEBUG') + # this port don't have stack printing support + cflags.remove('-fasynchronous-unwind-tables') + elif conf.env.DEST_OS == 'psvita': + # this optimization is broken in vitasdk + cflags.append('-fno-optimize-sibling-calls') + # remove fvisibility to allow everything to be exported by default + cflags.remove('-fvisibility=hidden') + # this port don't have stack printing support + cflags.remove('-fasynchronous-unwind-tables') + + if conf.env.COMPILER_CC in ['gcc', 'clang'] and conf.options.LIMITED_DEBUGINFO: + # probably not a good idea to do this, but it should save space on Android builds especially + # that are never going to be run under debugger, but we still want that readable fileline + # info in backtraces + # might enable this for release/fast/fastnative builds in the future + cflags = ['-gline-tables-only' if flag.startswith('-g') else flag for flag in cflags] + + if conf.env.COMPILER_CC in ['gcc', 'clang'] and conf.env.DEST_OS not in ['android']: + # HLSDK by default compiles with these options under Linux + # no reason for us to not do the same + if conf.env.DEST_CPU == 'x86': + cflags.append('-march=pentium-m') + cflags.append('-mtune=core2') + + # on all compilers (except MSVC?) we need to copy CFLAGS to LINKFLAGS + if conf.options.LTO and conf.env.COMPILER_CC != 'msvc': + linkflags += cflags + return cflags, linkflags diff --git a/scripts/waifulib/ninja.py b/scripts/waifulib/ninja.py new file mode 100644 index 00000000..13eb3469 --- /dev/null +++ b/scripts/waifulib/ninja.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# encoding: utf-8 +# Copyright (C) 2025 Velaron +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import io +import os +import abc + +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + +from abc import abstractmethod + +try: + from typing import List, Type, Union +except ImportError: + pass + +from waflib import Logs, Task, Build, Options, Node +from ninja_syntax import Writer, escape_path + +try: + ABC = abc.ABC +except AttributeError: + class ABC(object): + __metaclass__ = abc.ABCMeta + +Task.Task.keep_last_cmd = True + + +def get_node_path(node): # type: (Union[Node, str]) -> str + if isinstance(node, str): + return escape_path(node) + else: + return escape_path(node.abspath()) + + +class TaskAdapter(ABC): + task_type = None # type: str + + def __init__(self, task): # type: (Task) -> None + if not task.inputs: + raise ValueError() + + if not task.outputs: + raise ValueError() + + self.task = task # type: Task + + @classmethod + def write_rule(cls, writer): # type: (Writer) -> None + writer.rule( + name=cls.task_type, + command="$cmd", + description="Building {} object $out".format(cls.task_type) + ) + writer.newline() + + @abstractmethod + def write_build(self, writer): # type: (Writer) -> None + pass + + def write_target(self, writer): # type: (Writer) -> None + pass + + def get_inputs(self): # type: () -> List[str] + return [] + + def get_outputs(self): # type: () -> List[str] + return [] + + +class CAdapter(TaskAdapter): + task_type = "c" # type: str + + def write_build(self, writer): # type: (Writer) -> None + output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode) + input_files = [] + + cmd = " ".join(self.task.last_cmd) + + for node in self.task.inputs: + cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath()) + input_files.append(node.abspath()) + + cmd = cmd.replace(self.task.outputs[0].abspath(), output_file) + + for inc in self.task.env.INCPATHS: + cwd = self.task.get_cwd().abspath() + path = os.path.normpath(os.path.join(cwd, inc)) + cmd = cmd.replace(self.task.env.CPPPATH_ST % inc, + self.task.env.CPPPATH_ST % path) + + writer.build( + rule=self.task_type, + outputs=output_file, + inputs=input_files, + variables={ + "cmd": cmd, + }) + writer.newline() + + +class CxxAdapter(CAdapter): + task_type = "cxx" # type: str + + +class CStLibAdapter(TaskAdapter): + task_type = "cstlib" # type: str + + def write_build(self, writer): # type: (Writer) -> None + output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode) + input_files = [] + + cmd = " ".join(self.task.last_cmd) + + for node in self.task.inputs: + cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath()) + input_files.append(node.abspath()) + + cmd = cmd.replace(self.task.outputs[0].abspath(), output_file) + + writer.build( + rule=self.task_type, + outputs=output_file, + inputs=input_files, + variables={ + "cmd": cmd, + }) + writer.newline() + + def write_target(self, writer): # type: (Writer) -> None + writer.build( + rule="waf_build", + outputs="{}.passthrough".format(self.get_outputs()[0]), + inputs=self.get_inputs(), + variables={ + "tgt": self.task.generator.name + }) + writer.newline() + + def get_inputs(self): # type: () -> List[str] + return [node.abspath() for node in self.task.generator.source] + + def get_outputs(self): # type: () -> List[str] + return [self.task.outputs[0].path_from(self.task.generator.bld.bldnode)] + + +class CShLibAdapter(CStLibAdapter): + task_type = "cshlib" # type: str + + def write_build(self, writer): # type: (Writer) -> None + output_file = self.task.outputs[0].path_from(self.task.generator.bld.bldnode) + input_files = [] + + cmd = " ".join(self.task.last_cmd) + + for node in self.task.inputs: + cmd = cmd.replace(node.path_from(self.task.get_cwd()), node.abspath()) + input_files.append(node.abspath()) + + cmd = cmd.replace(self.task.outputs[0].abspath(), output_file) + + for lib in self.task.env.STLIBPATH: + cwd = self.task.get_cwd().abspath() + path = os.path.normpath(os.path.join(cwd, lib)) + cmd = cmd.replace(self.task.env.STLIBPATH_ST % lib, + self.task.env.STLIBPATH_ST % path) + + for lib in self.task.env.LIBPATH: + cwd = self.task.get_cwd().abspath() + path = os.path.normpath(os.path.join(cwd, lib)) + cmd = cmd.replace(self.task.env.LIBPATH_ST % lib, + self.task.env.LIBPATH_ST % path) + + writer.build( + rule=self.task_type, + outputs=output_file, + inputs=input_files, + variables={ + "cmd": cmd, + }) + writer.newline() + + +class CxxStLibAdapter(CStLibAdapter): + task_type = "cxxstlib" # type: str + + +class CxxShLibAdapter(CShLibAdapter): + task_type = "cxxshlib" # type: str + + +def get_subclasses(cls): + subs = set() + for sub in cls.__subclasses__(): + subs.add(sub) + subs.update(get_subclasses(sub)) + return list(subs) + + +Adapters = get_subclasses(TaskAdapter) # type: List[Type[TaskAdapter]] + + +class NinjaContext(Build.BuildContext): + cmd = "ninja" + + def execute(self): + self.restore() + + tasks = [] # type: List[TaskAdapter] + + if not self.all_envs: + self.load_envs() + + self.recurse([self.run_dir]) + self.pre_build() + + def exec_command(self, *k, **kw): + return 0 + + for group in self.groups: + for task_gen in group: + try: + if hasattr(task_gen, "post"): + task_gen.post() + except AttributeError: + pass + + if isinstance(task_gen, Task.Task): + current_tasks = [task_gen] + else: + current_tasks = task_gen.tasks + + for task in current_tasks: + try: + adapter = next(a for a in Adapters if a.task_type == task.__class__.__name__) + except StopIteration: + continue + + if adapter: + try: + tasks.append(adapter(task)) + except ValueError: + continue + + task.nocache = True + + old_exec = task.exec_command + task.exec_command = exec_command + try: + task.run() + except Exception as e: + Logs.error("Error running task {}: {}".format(task, e)) + finally: + task.exec_command = old_exec + + ninja_file_node = self.bldnode.make_node("build.ninja") + + Logs.info("Ninja build commands will be stored in %s", ninja_file_node.abspath()) + + string_buffer = StringIO() + writer = Writer(string_buffer) + + writer.variable(key="ninja_required_version", value="1.5") + writer.newline() + + for a in Adapters: + a.write_rule(writer) + + writer.rule( + "waf_build", + command="python {} build {} {} {} --targets=$tgt".format( + os.path.join(self.top_dir, "scripts", "build-ninja.py"), + self.top_dir, os.path.dirname(self.out_dir), Options.lockfile) + ) + writer.newline() + + writer.rule( + "waf_build_all", + command="python {} build {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"), + self.top_dir, os.path.dirname(self.out_dir), Options.lockfile) + ) + writer.newline() + + writer.rule( + "waf_clean", + command="python {} clean {} {} {}".format(os.path.join(self.top_dir, "scripts", "build-ninja.py"), + self.top_dir, os.path.dirname(self.out_dir), Options.lockfile) + ) + writer.newline() + + for task in tasks: + task.write_build(writer) + + for task in tasks: + task.write_target(writer) + + outputs = [] # type: List[str] + for task in tasks: + outputs += task.get_outputs() + + writer.build( + outputs="all", + rule="phony", + inputs=outputs + ) + writer.newline() + + inputs = [] # type: List[str] + for task in tasks: + inputs += task.get_inputs() + + writer.build( + outputs="all.passthrough", + rule="waf_build_all", + inputs=inputs + ) + writer.newline() + + writer.build(outputs="clean", rule="waf_clean") + writer.newline() + + writer.default("all") + + file_content = string_buffer.getvalue() + + with io.open(ninja_file_node.abspath(), "w", encoding="utf-8") as f: + f.write(file_content) diff --git a/scripts/waifulib/ninja_syntax.py b/scripts/waifulib/ninja_syntax.py new file mode 100644 index 00000000..39300af2 --- /dev/null +++ b/scripts/waifulib/ninja_syntax.py @@ -0,0 +1,235 @@ +#!/usr/bin/python + +# Copyright 2011 Google Inc. All Rights Reserved. +# Copyright 2025 Velaron (edited for Python 2.7 compatibility) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python module for generating .ninja files. + +Note that this is emphatically not a required piece of Ninja; it's +just a helpful utility for build-file-generation systems that already +use Python. +""" + +import re +import textwrap +from io import TextIOWrapper + +try: + from typing import Dict, List, Optional, Tuple, Union +except ImportError: + pass + + +def escape_path(word): # type: (str) -> str + return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') + + +class Writer(object): + def __init__(self, output, width=78): # type: (TextIOWrapper, int) -> None + self.output = output + self.width = width + + def newline(self): + self.output.write('\n') + + def comment(self, text): # type: (str) -> None + for line in textwrap.wrap(text, self.width - 2, break_long_words=False, + break_on_hyphens=False): + self.output.write('# ' + line + '\n') + + def variable( + self, + key, + value, + indent=0, + ): # type: (str, Optional[Union[bool, int, float, str, List[str]]], int) -> None + if value is None: + return + if isinstance(value, list): + value = ' '.join(filter(None, value)) # Filter out empty strings. + self._line('%s = %s' % (key, value), indent) + + def pool(self, name, depth): # type: (str, int) -> None + self._line('pool %s' % name) + self.variable('depth', depth, indent=1) + + def rule( + self, + name, + command, + description=None, + depfile=None, + generator=False, + pool=None, + restat=False, + rspfile=None, + rspfile_content=None, + deps=None, + ): # type: (str, str, Optional[str], Optional[str], bool, Optional[str], bool, Optional[str], Optional[str], Optional[Union[str, List[str]]]) -> None + self._line('rule %s' % name) + self.variable('command', command, indent=1) + if description: + self.variable('description', description, indent=1) + if depfile: + self.variable('depfile', depfile, indent=1) + if generator: + self.variable('generator', '1', indent=1) + if pool: + self.variable('pool', pool, indent=1) + if restat: + self.variable('restat', '1', indent=1) + if rspfile: + self.variable('rspfile', rspfile, indent=1) + if rspfile_content: + self.variable('rspfile_content', rspfile_content, indent=1) + if deps: + self.variable('deps', deps, indent=1) + + def build( + self, + outputs, + rule, + inputs=None, + implicit=None, + order_only=None, + variables=None, + implicit_outputs=None, + pool=None, + dyndep=None, + ): # 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] + outputs = as_list(outputs) + out_outputs = [escape_path(x) for x in outputs] + all_inputs = [escape_path(x) for x in as_list(inputs)] + + if implicit: + implicit = [escape_path(x) for x in as_list(implicit)] + all_inputs.append('|') + all_inputs.extend(implicit) + if order_only: + order_only = [escape_path(x) for x in as_list(order_only)] + all_inputs.append('||') + all_inputs.extend(order_only) + if implicit_outputs: + implicit_outputs = [escape_path(x) + for x in as_list(implicit_outputs)] + out_outputs.append('|') + out_outputs.extend(implicit_outputs) + + self._line('build %s: %s' % (' '.join(out_outputs), + ' '.join([rule] + all_inputs))) + if pool is not None: + self._line(' pool = %s' % pool) + if dyndep is not None: + self._line(' dyndep = %s' % dyndep) + + if variables: + if isinstance(variables, dict): + iterator = iter(variables.items()) + else: + iterator = iter(variables) + + for key, val in iterator: + self.variable(key, val, indent=1) + + return outputs + + def include(self, path): # type: (str) -> None + self._line('include %s' % path) + + def subninja(self, path): # type: (str) -> None + self._line('subninja %s' % path) + + def default(self, paths): # type: (Union[str, List[str]]) -> None + self._line('default %s' % ' '.join(as_list(paths))) + + def _count_dollars_before_index(self, s, i): # type: (str, int) -> int + """Returns the number of '$' characters right in front of s[i].""" + dollar_count = 0 + dollar_index = i - 1 + while dollar_index > 0 and s[dollar_index] == '$': + dollar_count += 1 + dollar_index -= 1 + return dollar_count + + def _line(self, text, indent=0): # type: (str, int) -> None + """Write 'text' word-wrapped at self.width characters.""" + leading_space = ' ' * indent + while len(leading_space) + len(text) > self.width: + # The text is too wide; wrap if possible. + + # Find the rightmost space that would obey our width constraint and + # that's not an escaped space. + available_space = self.width - len(leading_space) - len(' $') + space = available_space + while True: + space = text.rfind(' ', 0, space) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + + if space < 0: + # No such space; just use the first unescaped space we can find. + space = available_space - 1 + while True: + space = text.find(' ', space + 1) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + if space < 0: + # Give up on breaking. + break + + self.output.write(leading_space + text[0:space] + ' $\n') + text = text[space + 1:] + + # Subsequent lines are continuations, so indent them. + leading_space = ' ' * (indent + 2) + + self.output.write(leading_space + text + '\n') + + def close(self): + self.output.close() + + +def as_list(input): # type: (Optional[Union[str, List[str]]]) -> List[str] + if input is None: + return [] + if isinstance(input, list): + return input + return [input] + + +def escape(string): # type: (str) -> str + """Escape a string such that it can be embedded into a Ninja file without + further interpretation.""" + assert '\n' not in string, 'Ninja syntax does not allow newlines' + # We only have one special metacharacter: '$'. + return string.replace('$', '$$') + + +def expand(string, vars, local_vars={}): # type: (str, Dict[str, str], Dict[str, str]) -> str + """Expand a string containing $vars as Ninja would. + + Note: doesn't handle the full Ninja variable syntax, but it's enough + to make configure.py's use of it work. + """ + + def exp(m): # type (Match[str]) -> str: + var = m.group(1) + if var == '$': + return '$' + return local_vars.get(var, vars.get(var, '')) + + return re.sub(r'\$(\$|\w*)', exp, string) diff --git a/scripts/waifulib/nswitch.py b/scripts/waifulib/nswitch.py new file mode 100644 index 00000000..d70fa0b1 --- /dev/null +++ b/scripts/waifulib/nswitch.py @@ -0,0 +1,64 @@ +# encoding: utf-8 +# nswitch.py -- switch NRO task +# Copyright (C) 2018 a1batross +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from waflib.Tools import ccroot +from waflib import * + +def configure(conf): + conf.find_program('elf2nro') + + v = conf.env + + v.ELF2NRO_NACP_F = ['--nacp='] + v.ELF2NRO_ICON_F = ['--icon='] + +class elf2nro(Task.Task): + color = 'RED' + run_str = '${ELF2NRO} ${ELFFILE} ${TGT} ${ELF2NRO_NACP_F?NACP}${NACP} ${ELF2NRO_ICON_F?ICON}${ICON}' + + def keyword(self): + if Logs.colors_lst['USE']: # red/blue switch colors :) + return '%sConverting to NRO' % Logs.colors_lst['CYAN'] + return 'Converting to NRO' + +@TaskGen.feature('cxxprogram', 'cprogram') +@TaskGen.after_method('apply_link') +def apply_nro(self): + elffile = self.link_task.outputs[0] + + nodes = [elffile] + + def add_source_file(ctx, nodes, f): + if f: + if isinstance(f, str): + node = ctx.path.make_node(f) + elif isinstance(f, Node.Node): + node = f + + nodes += [node] + return node + return None + + nacpfile = add_source_file(self, nodes, getattr(self, 'nacp', None)) + iconfile = add_source_file(self, nodes, getattr(self, 'icon', None)) + self.env.ELFFILE = str(elffile) + if nacpfile: self.env.NACP = str(nacpfile) + if iconfile: self.env.ICON = str(iconfile) + + tsk = self.nro_task = self.create_task('elf2nro', nodes) + self.nro_task.set_outputs(nodes[0].change_ext('.nro')) + + inst_to = getattr(self, 'special_install_path', None) + if inst_to: + self.add_install_files(install_to=inst_to, + install_from=tsk.outputs[:], chmod=Utils.O755, task=tsk) diff --git a/scripts/waifulib/psp.py b/scripts/waifulib/psp.py new file mode 100644 index 00000000..74f4f953 --- /dev/null +++ b/scripts/waifulib/psp.py @@ -0,0 +1,82 @@ +# encoding: utf-8 +# psp.py -- PSP EBOOT task +# Copyright (C) 2023 Sergey Galushko +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +################################## +# PSP Tools # +################################## +class psp_fixup(Task.Task): + run_str = '${FIXUP} -o ${TGT} ${SRC}' + color = 'BLUE' + +class psp_prxgen(Task.Task): + run_str = '${PRXGEN} ${SRC} ${TGT}' + color = 'BLUE' + +class psp_strip(Task.Task): + run_str = '${STRIP} -o ${TGT} ${SRC}' + color = 'BLUE' + +class psp_mksfo(Task.Task): + run_str = '${MKSFO} -d MEMSIZE=1 ${PSP_EBOOT_TITLE} ${TGT}' + color = 'YELLOW' + +class psp_packpbp(Task.Task): + run_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}' + color = 'GREEN' + + +@TaskGen.feature('cshlib', 'cxxshlib') +@TaskGen.after_method('apply_link') +def build_module(self): + link_output = self.link_task.outputs[0] + for d in self.env.STATIC_LINKING: + if link_output.name.startswith(d): + return + fixup_output = self.path.find_or_declare(link_output.name + '_fixup') + prxgen_output = self.path.find_or_declare(link_output.change_ext('.prx').name) + + task = self.create_task('psp_fixup', src=link_output, tgt=fixup_output) + task = self.create_task('psp_prxgen', src=fixup_output, tgt=prxgen_output) + + if getattr(self, 'install_path', None): + if self.bld.is_install: + for k in self.install_task.inputs: + if k == self.path.find_or_declare(link_output.name): + self.install_task.inputs.remove(k) + self.add_install_files(install_to=self.install_path, install_from=prxgen_output) + +@TaskGen.feature('cprogram', 'cxxprogram', 'cprogram_static', 'cxxprogram_static') +@TaskGen.after_method('apply_link') +def build_eboot(self): + finalobj_ext = '.elf' + finalobj_tool = 'psp_strip' + if self.env.PSP_BUILD_PRX: + finalobj_ext = '.prx' + finalobj_tool = 'psp_prxgen' + + link_output = self.link_task.outputs[0] + fixup_output = self.path.find_or_declare(link_output.name + '_fixup') + finalobj_output = self.path.find_or_declare(link_output.change_ext(finalobj_ext).name) + + mksfo_output = self.path.find_or_declare('PARAM.SFO') + packpbp_output = self.path.find_or_declare('EBOOT.PBP') + + task = self.create_task('psp_fixup', src=link_output, tgt=fixup_output) + task = self.create_task(finalobj_tool, src=fixup_output, tgt=finalobj_output) + task = self.create_task('psp_mksfo', tgt=mksfo_output) + task = self.create_task('psp_packpbp', src=[finalobj_output, mksfo_output], tgt=packpbp_output) + + if getattr(self, 'install_path', None): + if getattr(self, 'install_task', None): + self.install_task.inputs = self.install_task.outputs = [] + self.add_install_files(install_to=self.install_path, install_from=[packpbp_output, finalobj_output]) diff --git a/scripts/waifulib/psvita.py b/scripts/waifulib/psvita.py new file mode 100644 index 00000000..48894d6f --- /dev/null +++ b/scripts/waifulib/psvita.py @@ -0,0 +1,122 @@ +# encoding: utf-8 +# psvita.py -- PSVita VPK task +# Copyright (C) 2023 fgsfds +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +from waflib.Tools import ccroot +from waflib import * + +def add_source_file(ctx, nodes, f): + if f: + if isinstance(f, str): + node = ctx.path.make_node(f) + elif isinstance(f, Node.Node): + node = f + nodes += [node] + return node + return None + +def configure(conf): + conf.find_program('vita-elf-create', var='ELF_CREATE') + conf.find_program('vita-make-fself', var='MAKE_FSELF') + conf.find_program('vita-mksfoex', var='MKSFOEX') + conf.find_program('vita-pack-vpk', var='PACKVPK') + + conf.env.SCESYS_ST = '-a%s=sce_sys' + +class mkvelf(Task.Task): + color = 'CYAN' + run_str = '${ELF_CREATE} -g ${YMLFILE} ${ELFFILE} ${TGT}' + +class mkfself(Task.Task): + color = 'CYAN' + run_str = '${MAKE_FSELF} ${VELFFILE} ${TGT}' + +class mksfoex(Task.Task): + color = 'CYAN' + # ATTRIBUTE2=12 enables the biggest extended memory mode + run_str = '${MKSFOEX} -s TITLE_ID=${TITLEID} -d ATTRIBUTE2=12 ${APPNAME} ${TGT}' + +class mkvpk(Task.Task): + color = 'CYAN' + run_str = '${PACKVPK} -s ${SFOFILE} -b ${FSELFFILE} ${SCESYS_ST:SCESYS} ${TGT}' + +@TaskGen.feature('cxxprogram', 'cprogram') +@TaskGen.after_method('apply_link') +def apply_velf(self): + elffile = self.link_task.outputs[0] + in_nodes = [elffile] + + ymlfile = elffile.change_ext('.yml') + velffile = elffile.change_ext('.velf') + out_nodes = [velffile, ymlfile] + + self.env.ELFFILE = str(elffile) + self.env.VELFFILE = str(velffile) + self.env.YMLFILE = str(ymlfile) + + self.velf_task = self.create_task('mkvelf', in_nodes) + self.velf_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram', 'cprogram') +@TaskGen.after_method('apply_velf') +def apply_fself(self): + velffile = self.velf_task.outputs[0] + in_nodes = [velffile] + + fselffile = velffile.change_ext('.bin') + out_nodes = [fselffile] + + self.env.FSELFFILE = str(fselffile) + + self.fself_task = self.create_task('mkfself', in_nodes) + self.fself_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram', 'cprogram') +@TaskGen.after_method('apply_fself') +def apply_sfo(self): + fselffile = self.fself_task.outputs[0] + in_nodes = [fselffile] + scetitleid = getattr(self, 'title_id', 'TEST10000') + sceappname = getattr(self, 'app_name', 'test') + + sfofile = fselffile.change_ext('.sfo') + out_nodes = [sfofile] + + if scetitleid: self.env.TITLEID = scetitleid + if sceappname: self.env.APPNAME = sceappname + self.env.SFOFILE = str(sfofile) + + self.sfo_task = self.create_task('mksfoex', in_nodes) + self.sfo_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram', 'cprogram') +@TaskGen.after_method('apply_sfo') +def apply_vpk(self): + fselffile = self.fself_task.outputs[0] + sfofile = self.sfo_task.outputs[0] + in_nodes = [fselffile, sfofile] + scesysdir = add_source_file(self, in_nodes, getattr(self, 'sce_sys', None)) + + vpkfile = sfofile.change_ext('.vpk') + out_nodes = [vpkfile] + + if scesysdir: + self.env.SCESYS = [str(scesysdir)] + self.env.VPKFILE = str(vpkfile) + + tsk = self.vpk_task = self.create_task('mkvpk', in_nodes) + self.vpk_task.set_outputs(out_nodes) + + inst_to = getattr(self, 'special_install_path', None) + if inst_to: + self.add_install_files(install_to=inst_to, + install_from=tsk.outputs[:], chmod=Utils.O755, task=tsk) diff --git a/scripts/waifulib/sdl2.py b/scripts/waifulib/sdl2.py new file mode 100644 index 00000000..d4c943ac --- /dev/null +++ b/scripts/waifulib/sdl2.py @@ -0,0 +1,122 @@ +# encoding: utf-8 +# sdl2.py -- sdl2 waf plugin +# Copyright (C) 2018 a1batross +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +import os + +SDL_SANITY_FRAGMENT='''#define SDL_MAIN_HANDLED +#include <%s> +int main( void ) +{ + SDL_Init( SDL_INIT_AUDIO ); + return 0; +}''' + +def options(opt): + grp = opt.add_option_group('SDL2/SDL3 options') + grp.add_option('-s', '--sdl2', action='store', dest = 'SDL_PATH', default = None, + help = 'path to precompiled SDL library (required for Windows)') + + grp.add_option('--use-sdl3', action='store_true', dest='SDL3', default = False, help = 'configure for SDL3 [default: %(default)s]') + + grp.add_option('--skip-sdl2-sanity-check', action='store_false', default = True, dest='SDL_SANITY_CHECK', + help = 'skip checking SDL sanity [default: %(default)s]') + + grp.add_option('--sdl-use-pkgconfig', action='store_true', default = False, dest='SDL_USE_PKGCONFIG', + help = 'force use of pkg-config to find sdl [default: %(default)s]') + +def my_dirname(path): + # really dumb, will not work with /path/framework//, but still enough + if path[-1] == '/': + path = path[:-1] + return os.path.dirname(path) + +def sdl2_configure_path(conf, path, libname): + conf.start_msg('Configuring %s by path' % libname) + + HAVE = 'HAVE_' + libname + INCLUDES = 'INCLUDES_' + libname + FRAMEWORKPATH = 'FRAMEWORKPATH_' + libname + FRAMEWORK = 'FRAMEWORK_' + libname + LIBPATH = 'LIBPATH_' + libname + LIB = 'LIB_' + libname + + conf.env[HAVE] = 1 + if conf.env.DEST_OS == 'darwin': + conf.env[INCLUDES] = [os.path.abspath(os.path.join(path, 'Headers'))] + conf.env[FRAMEWORKPATH] = [my_dirname(path)] + conf.env[FRAMEWORK] = [libname] + conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[FRAMEWORK], conf.env[FRAMEWORKPATH], conf.env[INCLUDES])) + elif conf.env.DEST_OS == 'android': + # Special setup for waf called from CMake, through ExternalProject_Add + conf.env[INCLUDES] = [os.path.abspath(os.path.join(path, 'include'))] + conf.env[LIBPATH] = [os.environ['BUILD_CMAKE_LIBRARY_OUTPUT_DIRECTORY']] + conf.env[LIB] = [libname] + conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[LIB], conf.env[LIBPATH], conf.env[INCLUDES])) + else: + conf.env[INCLUDES] = [ + os.path.abspath(os.path.join(path, 'include')), + os.path.abspath(os.path.join(path, 'include/%s' % libname)) + ] + libpath = 'lib' + if conf.env.COMPILER_CC == 'msvc': + if conf.env.DEST_CPU == 'x86_64': + libpath = 'lib/x64' + else: + libpath = 'lib/' + conf.env.DEST_CPU + conf.env[LIBPATH] = [os.path.abspath(os.path.join(path, libpath))] + conf.env[LIB] = [libname] + conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env[LIB], conf.env[LIBPATH], conf.env[INCLUDES])) + +def configure(conf): + if conf.options.SDL3: + libname = 'SDL3' + else: + libname = 'SDL2' + + HAVE = 'HAVE_' + libname + CFLAGS = 'CFLAGS_' + libname + CXXFLAGS = 'CFLAGS_' + libname + LINKFLAGS = 'LINKFLAGS_' + libname + + if conf.options.SDL_PATH: + sdl2_configure_path(conf, conf.options.SDL_PATH, libname) + elif conf.env.DEST_OS == 'darwin' and conf.options.SDL_USE_PKGCONFIG == False: + sdl2_configure_path(conf, '/Library/Frameworks/%s.framework' % libname, libname) + elif conf.env.DEST_OS == 'emscripten': + flag = '-sUSE_SDL=%d' % (3 if conf.options.SDL3 else 2) + conf.env[HAVE] = 1 + conf.env[CFLAGS] = [flag] + conf.env[CXXFLAGS] = [flag] + conf.env[LINKFLAGS] = [flag] + else: + try: + conf.check_cfg(package=libname.lower(), args='--cflags --libs', + msg='Checking for %s (pkg-config)' % libname) + except conf.errors.ConfigurationError: + try: + if not conf.env.SDLCONFIG: + conf.find_program('%s-config' % libname.lower(), var='SDLCONFIG') + + conf.check_cfg(path=conf.env.SDLCONFIG, args='--cflags --libs', + msg='Checking for %s (%s-config)' % (libname, libname.lower()), package='', + uselib_store=libname) + except conf.errors.ConfigurationError: + conf.env[HAVE] = 0 + + if conf.env[HAVE] and conf.options.SDL_SANITY_CHECK: + if conf.options.SDL3: + fragment = SDL_SANITY_FRAGMENT % 'SDL3/SDL.h' + else: + fragment = SDL_SANITY_FRAGMENT % 'SDL.h' + + conf.env[HAVE] = conf.check_cc(fragment=fragment, msg = 'Checking for %s sanity' % libname, use = libname, execute = False, mandatory = False) diff --git a/scripts/waifulib/vgui.py b/scripts/waifulib/vgui.py new file mode 100644 index 00000000..00dc0358 --- /dev/null +++ b/scripts/waifulib/vgui.py @@ -0,0 +1,97 @@ +#! /usr/bin/env python +# encoding: utf-8 +# mittorn, 2018 + +from waflib.Configure import conf +from waflib import Logs +import os + +VGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux'] + +VGUI_FRAGMENT = '''#include +int main() { return 0; }''' + +def options(opt): + grp = opt.add_option_group('VGUI options') + + vgui_dev_path = os.path.join(opt.path.path_from(opt.root), 'vgui-dev') + + grp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default=vgui_dev_path, + help = 'path to vgui-dev repo [default: %(default)s]') + + grp.add_option('--enable-unsupported-vgui', action = 'store_true', dest = 'ENABLE_UNSUPPORTED_VGUI', default=False, + help = 'ignore all checks and allow link against anything [default: %(default)s]') + + grp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=True, + help = 'skip checking VGUI sanity [default: %(default)s]' ) + return + +@conf +def check_vgui(conf): + if not conf.options.ENABLE_UNSUPPORTED_VGUI: + conf.start_msg('Does this architecture support VGUI?') + + if conf.env.DEST_CPU != 'x86': + conf.end_msg('no') + Logs.warn('vgui is not supported on this CPU: ' + str(conf.env.DEST_CPU)) + return False + else: conf.end_msg('yes') + + conf.start_msg('Does this OS support VGUI?') + if conf.env.DEST_OS not in VGUI_SUPPORTED_OS: + conf.end_msg('no') + Logs.warn('vgui is not supported on this OS: ' + str(conf.env.DEST_OS)) + return False + else: conf.end_msg('yes') + + conf.start_msg('Does this toolchain able to link VGUI?') + if conf.env.DEST_OS == 'win32' and conf.env.COMPILER_CXX == 'g++': + conf.end_msg('no') + # we have ABI incompatibility ONLY on MinGW + Logs.warn('vgui can\'t be linked with MinGW') + return False + else: conf.end_msg('yes') + + conf.start_msg('Configuring VGUI by provided path') + vgui_dev = conf.options.VGUI_DEV + + libpath = os.path.abspath(os.path.join(vgui_dev, 'lib')) + + if conf.env.DEST_OS == 'win32': + conf.env.LIB_VGUI = ['vgui'] + libpath = os.path.join(libpath, 'win32_vc6') + if conf.env.DEST_CPU != 'x86': + # for 32-bit x86 it's expected to be under win32_vc6 + # for others, it's expected to be under win32_vc6 subdirectory matching CPU arch (x86_64 for 64-bit CPUs) + libpath = os.path.join(libpath, conf.env.DEST_CPU) + conf.env.LIBPATH_VGUI = [libpath] + elif conf.env.DEST_OS == 'linux': + conf.env.LIB_VGUI = [':vgui.so'] + if conf.env.DEST_CPU != 'x86': + libpath = os.path.join(libpath, conf.env.DEST_CPU) + conf.env.LIBPATH_VGUI = [libpath] + elif conf.env.DEST_OS == 'darwin': + if conf.env.DEST_CPU != 'x86': + conf.env.LDFLAGS_VGUI = [os.path.join(libpath, conf.env.DEST_CPU, 'vgui.dylib')] + else: + conf.env.LDFLAGS_VGUI = [os.path.join(libpath, 'vgui.dylib')] + else: + # TODO: figure out what to do here + conf.env.LIB_VGUI = ['vgui'] + conf.env.LIBPATH_VGUI = [os.path.join(libpath, conf.env.DEST_OS, conf.env.DEST_CPU)] + + conf.env.INCLUDES_VGUI = [os.path.abspath(os.path.join(vgui_dev, 'include'))] + + conf.env.HAVE_VGUI = 1 + conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env.LIB_VGUI, conf.env.LIBPATH_VGUI, conf.env.INCLUDES_VGUI)) + + if conf.env.HAVE_VGUI and conf.options.VGUI_SANITY_CHECK: + try: + conf.check_cxx(fragment=VGUI_FRAGMENT, + msg = 'Checking for library VGUI sanity', + use = 'VGUI', + execute = False) + except conf.errors.ConfigurationError: + conf.fatal("Can't compile simple program. Check your path to vgui-dev repository.") + + return True diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index b9ce398f..0f5dd4d1 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -20,16 +20,29 @@ import os import sys ANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK'] -ANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25] +ANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25, 27, 28] ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15 ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag -ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16, 25: 19 } # minimal API level ndk revision supports +ANDROID_NDK_BUGGED_LINKER_MAX = 22 +ANDROID_NDK_API_MIN = { + 10: 3, + 19: 16, + 20: 16, + 23: 16, + 25: 19, + 27: 19, + 28: 21, +} # minimal API level ndk revision supports ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets +NSWITCH_ENVVARS = ['DEVKITPRO'] + +PSVITA_ENVVARS = ['VITASDK'] + # This class does support ONLY r10e and r19c/r20 NDK class Android: ctx = None # waf context @@ -46,6 +59,7 @@ class Android: self.api = api self.toolchain = toolchain self.arch = arch + self.exe = '.exe' if sys.platform.startswith('win32') or sys.platform.startswith('cygwin') else '' for i in ANDROID_NDK_ENVVARS: self.ndk_home = os.getenv(i) @@ -192,10 +206,10 @@ class Android: def gen_toolchain_path(self): if self.is_clang(): - triplet = '%s%d-' % (self.ndk_triplet(llvm_toolchain = True), self.api) + base = '' else: - triplet = self.ndk_triplet() + '-' - return os.path.join(self.gen_gcc_toolchain_path(), 'bin', triplet) + base = self.ndk_triplet() + '-' + return os.path.join(self.gen_gcc_toolchain_path(), 'bin', base) def gen_binutils_path(self): if self.ndk_rev >= 23: @@ -211,7 +225,11 @@ class Android: s = environ['CC'] return '%s --target=%s%d' % (s, self.ndk_triplet(), self.api) - return self.gen_toolchain_path() + ('clang' if self.is_clang() else 'gcc') + + if self.is_clang(): + return '%s --target=%s%d' % (self.gen_toolchain_path() + 'clang' + self.exe, self.ndk_triplet(), self.api) + + return self.gen_toolchain_path() + 'gcc' def cxx(self): if self.is_host(): @@ -222,19 +240,33 @@ class Android: s = environ['CXX'] return '%s --target=%s%d' % (s, self.ndk_triplet(), self.api) - return self.gen_toolchain_path() + ('clang++' if self.is_clang() else 'g++') + + if self.is_clang(): + return '%s --target=%s%d' % (self.gen_toolchain_path() + 'clang++' + self.exe, self.ndk_triplet(), self.api) + + return self.gen_toolchain_path() + 'g++' def strip(self): if self.is_host(): environ = getattr(self.ctx, 'environ', os.environ) - if 'STRIP' in environ: return environ['STRIP'] return 'llvm-strip' if self.ndk_rev >= 23: - return os.path.join(self.gen_binutils_path(), 'llvm-strip') - return os.path.join(self.gen_binutils_path(), 'strip') + return os.path.join(self.gen_binutils_path(), 'llvm-strip' + self.exe) + return os.path.join(self.gen_binutils_path(), 'strip' + self.exe) + + def ar(self): + if self.is_host(): + environ = getattr(self.ctx, 'environ', os.environ) + if 'AR' in environ: + return environ['AR'] + return 'llvm-ar' + + if self.ndk_rev >= 23: + return os.path.join(self.gen_binutils_path(), 'llvm-ar' + self.exe) + return os.path.join(self.gen_binutils_path(), 'ar' + self.exe) def system_stl(self): # TODO: proper STL support @@ -323,6 +355,12 @@ class Android: else: linkflags += ['-no-canonical-prefixes'] linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined'] + + linkflags += ["-Wl,-z,max-page-size=16384"] + + if self.ndk_rev <= ANDROID_NDK_BUGGED_LINKER_MAX: + linkflags += ["-Wl,-z,common-page-size=16384"] + return linkflags def ldflags(self): @@ -332,7 +370,7 @@ class Android: ldflags += ['-lgcc'] if self.is_clang() or self.is_host(): - ldflags += ['-stdlib=libstdc++'] + ldflags += ['-stdlib=libstdc++', '-lc++abi'] else: ldflags += ['-no-canonical-prefixes'] if self.is_arm(): @@ -348,59 +386,278 @@ class Android: ldflags += ['-march=armv5te'] return ldflags -def options(opt): - android = opt.add_option_group('Android options') - android.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, - help='enable building for android, format: --android=,,, example: --android=armeabi-v7a-hard,4.9,9') +class NintendoSwitch: + ctx = None # waf context + arch = "arm64" + dkp_dir = None + portlibs_dir = None + dka64_dir = None + libnx_dir = None - magx = opt.add_option_group('MotoMAGX options') - magx.add_option('--enable-magx', action = 'store_true', dest = 'MAGX', default = False, - help = 'enable targetting for MotoMAGX phones [default: %default]') + def __init__(self, ctx): + self.ctx = ctx + + for i in NSWITCH_ENVVARS: + self.dkp_dir = os.getenv(i) + if self.dkp_dir != None: + break + else: + ctx.fatal('Set %s environment variable pointing to the DEVKITPRO home!' % + ' or '.join(NSWITCH_ENVVARS)) + + self.dkp_dir = os.path.abspath(self.dkp_dir) + + self.dka64_dir = os.path.join(self.dkp_dir, 'devkitA64') + if not os.path.exists(self.dka64_dir): + ctx.fatal('devkitA64 not found in `%s`. Install devkitA64!' % self.dka64_dir) + + self.libnx_dir = os.path.join(self.dkp_dir, 'libnx') + if not os.path.exists(self.libnx_dir): + ctx.fatal('libnx not found in `%s`. Install libnx!' % self.libnx_dir) + + self.portlibs_dir = os.path.join(self.dkp_dir, 'portlibs', 'switch') + if not os.path.exists(self.portlibs_dir): + ctx.fatal('No Switch libraries found in `%s`!' % self.portlibs_dir) + + def gen_toolchain_prefix(self): + return 'aarch64-none-elf-' + + def gen_gcc_toolchain_path(self): + return os.path.join(self.dka64_dir, 'bin', self.gen_toolchain_prefix()) + + def cc(self): + return self.gen_gcc_toolchain_path() + 'gcc' + + def cxx(self): + return self.gen_gcc_toolchain_path() + 'g++' + + def strip(self): + return self.gen_gcc_toolchain_path() + 'strip' + + def pkgconfig(self): + # counter-intuitively, this motherfucker is in $DEVKITPRO/portlibs/switch/bin + return os.path.join(self.portlibs_dir, 'bin', self.gen_toolchain_prefix() + 'pkg-config') + + def cflags(self, cxx = False): + cflags = [] + # arch flags + cflags += ['-D__SWITCH__', '-march=armv8-a+crc+crypto', '-mtune=cortex-a57', '-mtp=soft', '-ftls-model=local-exec', '-fPIE'] + # help the linker out + cflags += ['-ffunction-sections', '-fdata-sections'] + # base include dirs + cflags += ['-isystem %s/include' % self.libnx_dir, '-I%s/include' % self.portlibs_dir] + # the game wants GNU extensions + if cxx: + cflags += ['-std=gnu++17', '-D_GNU_SOURCE'] + else: + cflags += ['-std=gnu11', '-D_GNU_SOURCE'] + return cflags + + # they go before object list + def linkflags(self): + linkflags = ['-fPIE', '-specs=%s/switch.specs' % self.libnx_dir] + # libsolder only supports sysv hashes and we need to build everything with -rdynamic + linkflags += ['-Wl,--hash-style=sysv', '-rdynamic'] + # avoid pulling in and exposing mesa's internals, that crashes it for some god forsaken reason + linkflags += ['-Wl,--exclude-libs=libglapi.a', '-Wl,--exclude-libs=libdrm_nouveau.a'] + return linkflags + + def ldflags(self): + # NOTE: shared libraries should be built without standard libs, so that they could import their contents from the NRO, + # but executables, including the SDL2 sanity check, will generally require libstdc++ and libm, which we will add manually + ldflags = [] # ['-lm', '-lstdc++'] + return ldflags + +class PSVita: + ctx = None # waf context + arch ='armeabi-v7a-hard' + vitasdk_dir = None + + def __init__(self, ctx): + self.ctx = ctx + + for i in PSVITA_ENVVARS: + self.vitasdk_dir = os.getenv(i) + if self.vitasdk_dir != None: + break + else: + ctx.fatal('Set %s environment variable pointing to the VitaSDK directory!' % + ' or '.join(PSVITA_ENVVARS)) + + def gen_toolchain_prefix(self): + return 'arm-vita-eabi-' + + def gen_gcc_toolchain_path(self): + return os.path.join(self.vitasdk_dir, 'bin', self.gen_toolchain_prefix()) + + def cc(self): + return self.gen_gcc_toolchain_path() + 'gcc' + + def cxx(self): + return self.gen_gcc_toolchain_path() + 'g++' + + def strip(self): + return self.gen_gcc_toolchain_path() + 'strip' + + def ar(self): + return self.gen_gcc_toolchain_path() + 'ar' + + def pkgconfig(self): + return self.gen_gcc_toolchain_path() + 'pkg-config' + + def cflags(self, cxx = False): + cflags = [] + # arch flags + cflags += ['-D__vita__', '-mtune=cortex-a9', '-mfpu=neon'] + # necessary linker flags + cflags += ['-Wl,-q', '-Wl,-z,nocopyreloc'] + # this optimization is broken in vitasdk + cflags += ['-fno-optimize-sibling-calls'] + # disable some ARM bullshit + cflags += ['-fno-short-enums', '-Wno-attributes'] + # base include dir + cflags += ['-isystem %s/arm-vita-eabi/include' % self.vitasdk_dir] + # SDL include dir + cflags += ['-I%s/arm-vita-eabi/include/SDL2' % self.vitasdk_dir] + return cflags + + # they go before object list + def linkflags(self): + linkflags = ['-Wl,--hash-style=sysv', '-Wl,-q', '-Wl,-z,nocopyreloc', '-mtune=cortex-a9', '-mfpu=neon'] + # enforce no-short-enums again + linkflags += ['-Wl,-no-enum-size-warning', '-fno-short-enums'] + return linkflags + + def ldflags(self): + ldflags = [] + return ldflags + +def options(opt): + xc = opt.add_option_group('Cross compile options') + xc.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, + help='enable building for android, format: --android=,,, example: --android=armeabi-v7a-hard,4.9,9') + xc.add_option('--enable-magx', action='store_true', dest='MAGX', default=False, + help='enable building for Motorola MAGX [default: %(default)s]') + xc.add_option('--enable-msvc-wine', action='store_true', dest='MSVC_WINE', default=False, + help='enable building with MSVC using Wine [default: %(default)s]') + xc.add_option('--nswitch', action='store_true', dest='NSWITCH', default = False, + help='enable building for Nintendo Switch [default: %(default)s]') + xc.add_option('--psvita', action='store_true', dest='PSVITA', default = False, + help='enable building for PlayStation Vita [default: %(default)s]') + xc.add_option('--sailfish', action='store', dest='SAILFISH', default = None, + help='enable building for Sailfish/Aurora') + xc.add_option('--emscripten', action='store_true', dest='EMSCRIPTEN', default = None, + help='enable building for Emscripten') def configure(conf): - if conf.options.ANDROID_OPTS: + if 'CROSS_COMPILE' in conf.environ: + toolchain_path = conf.environ['CROSS_COMPILE'] + conf.environ['CC'] = toolchain_path + 'cc' + conf.environ['CXX'] = toolchain_path + 'c++' + conf.environ['STRIP'] = toolchain_path + 'strip' + conf.environ['OBJDUMP'] = toolchain_path + 'objdump' + conf.environ['AR'] = toolchain_path + 'ar' + elif conf.options.ANDROID_OPTS: values = conf.options.ANDROID_OPTS.split(',') if len(values) != 3: conf.fatal('Invalid --android paramater value!') valid_archs = ['x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'armeabi-v7a-hard', 'aarch64'] + if values[0] == 'arm64-v8a': + values[0] = 'aarch64' + if values[0] not in valid_archs: conf.fatal('Unknown arch: %s. Supported: %r' % (values[0], ', '.join(valid_archs))) conf.android = android = Android(conf, values[0], values[1], int(values[2])) + conf.environ['CC'] = android.cc() conf.environ['CXX'] = android.cxx() conf.environ['STRIP'] = android.strip() + conf.environ['AR'] = android.ar() conf.env.CFLAGS += android.cflags() conf.env.CXXFLAGS += android.cflags(True) conf.env.LINKFLAGS += android.linkflags() conf.env.LDFLAGS += android.ldflags() + from waflib.Tools.compiler_c import c_compiler + from waflib.Tools.compiler_cxx import cxx_compiler + c_compiler['win32'] = ['clang' if android.is_clang() or android.is_host() else 'gcc'] + cxx_compiler['win32'] = ['clang++' if android.is_clang() or android.is_host() else 'gxx'] + conf.env.HAVE_M = True if android.is_hardfp(): conf.env.LIB_M = ['m_hard'] else: conf.env.LIB_M = ['m'] - conf.env.PREFIX = '/lib/%s' % android.apk_arch() - conf.msg('Selected Android NDK', '%s, version: %d' % (android.ndk_home, android.ndk_rev)) # no need to print C/C++ compiler, as it would be printed by compiler_c/cxx conf.msg('... C/C++ flags', ' '.join(android.cflags()).replace(android.ndk_home, '$NDK/')) conf.msg('... link flags', ' '.join(android.linkflags()).replace(android.ndk_home, '$NDK/')) conf.msg('... ld flags', ' '.join(android.ldflags()).replace(android.ndk_home, '$NDK/')) - - # conf.env.ANDROID_OPTS = android - conf.env.DEST_OS2 = 'android' elif conf.options.MAGX: # useless to change toolchain path, as toolchain meant to be placed in this path toolchain_path = '/opt/toolchains/motomagx/arm-eabi2/lib/' conf.env.INCLUDES_MAGX = [toolchain_path + i for i in ['ezx-z6/include', 'qt-2.3.8/include']] conf.env.LIBPATH_MAGX = [toolchain_path + i for i in ['ezx-z6/lib', 'qt-2.3.8/lib']] conf.env.LINKFLAGS_MAGX = ['-Wl,-rpath-link=' + i for i in conf.env.LIBPATH_MAGX] + elif conf.options.MSVC_WINE: + try: + toolchain_path = conf.environ['MSVC_WINE_PATH'] + except KeyError: + conf.fatal('Set MSVC_WINE_PATH environment variable to the MSVC toolchain root!') + + conf.environ['CC'] = conf.environ['CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'cl') + conf.environ['LINK_CXX'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'link') + conf.environ['AR'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'lib') + conf.environ['WINRC'] = os.path.join(toolchain_path, 'bin', conf.env.MSVC_TARGETS[0], 'rc') + conf.env.DEST_OS = 'win32' + conf.env.DEST_CPU = conf.env.MSVC_TARGETS[0] + conf.env.COMPILER_CXX = conf.env.COMPILER_CC = 'msvc' + elif conf.options.NSWITCH: + conf.nswitch = nswitch = NintendoSwitch(conf) + conf.environ['CC'] = nswitch.cc() + conf.environ['CXX'] = nswitch.cxx() + conf.environ['STRIP'] = nswitch.strip() + conf.env.PKGCONFIG = nswitch.pkgconfig() + conf.env.CFLAGS += nswitch.cflags() + conf.env.CXXFLAGS += nswitch.cflags(True) + conf.env.LINKFLAGS += nswitch.linkflags() + conf.env.LDFLAGS += nswitch.ldflags() + conf.env.HAVE_M = True + conf.env.LIB_M = ['m'] + conf.env.DEST_OS = 'nswitch' + elif conf.options.PSVITA: + conf.psvita = psvita = PSVita(conf) + conf.environ['CC'] = psvita.cc() + conf.environ['CXX'] = psvita.cxx() + conf.environ['STRIP'] = psvita.strip() + conf.environ['AR'] = psvita.ar() + conf.env.PKGCONFIG = psvita.pkgconfig() + conf.env.CFLAGS += psvita.cflags() + conf.env.CXXFLAGS += psvita.cflags(True) + conf.env.LINKFLAGS += psvita.linkflags() + conf.env.LDFLAGS += psvita.ldflags() + conf.env.HAVE_M = True + conf.env.LIB_M = ['m'] + conf.env.VRTLD = ['vrtld'] + conf.env.DEST_OS = 'psvita' + elif conf.options.EMSCRIPTEN: + # Emscripten compiler is just wrapper to clang + # But we need to setup platform modifiers, they all are contained inside c_emscripten.py for now + # In future, that could be upstreamed to waf itself and this wouldn't be needed + conf.environ['CC'] = 'emcc' + conf.environ['CXX'] = 'em++' + conf.environ['AR'] = 'emar' + conf.environ['STRIP'] = 'emstrip' + conf.environ['OBJCOPY'] = 'llvm-objcopy' + conf.load('c_emscripten') conf.env.MAGX = conf.options.MAGX - MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android' }) + conf.env.MSVC_WINE = conf.options.MSVC_WINE + conf.env.SAILFISH = conf.options.SAILFISH + MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch', '__vita__' : 'psvita', '__wasi__': 'wasi', '__EMSCRIPTEN__' : 'emscripten' }) for k in c_config.MACRO_TO_DESTOS: MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important c_config.MACRO_TO_DESTOS = MACRO_TO_DESTOS @@ -433,11 +690,17 @@ compiler_cxx_configure = getattr(compiler_cxx, 'configure') compiler_c_configure = getattr(compiler_c, 'configure') def patch_compiler_cxx_configure(conf): - compiler_cxx_configure(conf) + if not conf.env.MSVC_WINE: + compiler_cxx_configure(conf) + else: + conf.load('msvc', funs='no_autodetect') post_compiler_cxx_configure(conf) def patch_compiler_c_configure(conf): - compiler_c_configure(conf) + if not conf.env.MSVC_WINE: + compiler_c_configure(conf) + else: + conf.load('msvc', funs='no_autodetect') post_compiler_c_configure(conf) setattr(compiler_cxx, 'configure', patch_compiler_cxx_configure) diff --git a/scripts/waifulib/xshlib.py b/scripts/waifulib/xshlib.py index 60c10120..72e673ab 100644 --- a/scripts/waifulib/xshlib.py +++ b/scripts/waifulib/xshlib.py @@ -96,7 +96,7 @@ def apply_static(main, *reloc): for k in ('cshlib', 'cxxshlib'): if k in self.features: self.features.remove(k) - self.features.append('xshlib') + self.features.insert(0, 'xshlib') in_node = self.path.get_src().make_node('exports.txt') bldnode = self.path.get_bld() bldnode.mkdir() diff --git a/scripts/waifulib/zip.py b/scripts/waifulib/zip.py index 6e903ba7..5afb7f9f 100644 --- a/scripts/waifulib/zip.py +++ b/scripts/waifulib/zip.py @@ -2,7 +2,7 @@ # encoding: utf-8 from waflib import TaskGen, Task, Logs, Utils -import zipfile +import zipfile, sys class ziparchive(Task.Task): color = 'YELLOW' @@ -16,16 +16,26 @@ class ziparchive(Task.Task): return 'Creating' def run(self): - outfile = self.outputs[0].path_from(self.outputs[0].ctx.launch_node()) - comp = zipfile.ZIP_STORED if self.compresslevel == 0 else zipfile.ZIP_DEFLATED + outfile = self.outputs[0].abspath() + kwargs = {} + kwargs['mode'] = 'w' + kwargs['compression'] = zipfile.ZIP_STORED if self.compresslevel == 0 else zipfile.ZIP_DEFLATED - with zipfile.ZipFile(outfile, mode='w', compression=comp) as zf: + if sys.hexversion >= 0x3070000: + kwargs['compresslevel'] = self.compresslevel + + with zipfile.ZipFile(outfile, **kwargs) as zf: for src in self.inputs: infile = src.path_from(src.ctx.launch_node()) arcfile = src.path_from(self.relative_to) + # TODO: pass excluded list from TaskGen Logs.debug('%s: %s <- %s as %s', self.__class__.__name__, outfile, infile, arcfile) - zf.write(infile, arcfile) + + if infile.endswith('.png'): + zf.write(infile, arcfile, zipfile.ZIP_STORED) + else: + zf.write(infile, arcfile) @TaskGen.feature('zip') def create_zip_archive(self): diff --git a/scripts/xashds@.service b/scripts/xashds@.service new file mode 100644 index 00000000..4f73073c --- /dev/null +++ b/scripts/xashds@.service @@ -0,0 +1,21 @@ +[Unit] +Description=%I - Xash3D FWGS Dedicated Server +After=network.target + +[Service] +# replace xash by your user +# !!! do not run xash as root !!! +User=xash +Group=xash +Restart=always + +# replace /opt/xash/srv by your path +WorkingDirectory=/opt/xash/srv/%I +EnvironmentFile=-/opt/xash/srv/%I/env +ExecStart=/opt/xash/bin/xash $EXTRA_OPTS + +# engine should call sd_notify once half a second +WatchdogSec=5s + +[Install] +WantedBy=multi-user.target diff --git a/uncrustify.cfg b/uncrustify.cfg new file mode 100644 index 00000000..04f8149f --- /dev/null +++ b/uncrustify.cfg @@ -0,0 +1,121 @@ +# NOTE: Uncrustify 0.80+ is required + +align_enum_equ_span = 5 +align_func_params_span = 1 +align_func_params = true +align_keep_extra_space = false +align_nl_cont = 1 +align_number_right = true +align_on_tabstop = false +align_pp_define_span = 1 +align_right_cmt_same_level = true +align_right_cmt_span = 2 +align_var_class_span = 3 +align_var_class_thresh = 5 +align_var_def_amp_style = 2 +align_var_def_colon = true +align_var_def_span = 3 +align_var_def_star_style = 1 +align_var_def_thresh = 5 +align_var_struct_span = 3 +align_var_struct_thresh = 5 +align_with_tabs = false +cmt_indent_multi = false +indent_align_string = true +indent_with_tabs = 2 +indent_class = true +newlines = lf +nl_brace_catch = force +nl_brace_else = force +nl_brace_while = force +nl_catch_brace = force +nl_do_brace = force +nl_else_brace = force +nl_elseif_brace = force +nl_enum_brace = force +nl_finally_brace = force +nl_for_brace = force +nl_if_brace = force +nl_start_of_file = remove +nl_struct_brace = force +nl_switch_brace = force +nl_try_brace = force +nl_union_brace = force +nl_while_brace = force +nl_func_decl_start = remove +nl_func_decl_end = remove +nl_func_decl_args_multi_line = false +nl_func_decl_args = remove +nl_func_decl_empty = remove +nl_func_def_start = remove +nl_func_def_paren = remove +nl_func_def_end = remove +nl_func_def_args_multi_line = false +nl_func_def_args = remove +nl_func_def_empty = remove +nl_func_paren = remove +nl_fdef_brace = force +nl_after_func_body = 2 +nl_after_semicolon = true +pos_arith = lead +pos_assign = lead +pos_bool = lead +pos_comma = trail +pos_compare = lead +pos_conditional = lead +pos_shift = lead +sp_after_comma = force +sp_after_ptr_star = remove +sp_after_ptr_star_qualifier = remove +sp_after_ptr_star_func = remove +sp_after_semi_for_empty = force +sp_after_semi_for = force +sp_after_tparen_close = remove +sp_arith = force +sp_assign = force +sp_attribute_paren = remove +sp_before_ptr_star = force +sp_before_semi_for_empty = force +sp_before_semi_for = remove +sp_before_sparen = remove +sp_before_tr_cmt = force +sp_between_semi_for_empty = remove +sp_bool = force +sp_brace_brace = remove +sp_catch_paren = remove +sp_cmt_cpp_start = add +sp_compare = force +sp_cond_colon = force +sp_cond_question = force +sp_cparen_oparen = remove +sp_cpp_cast_paren = remove +sp_decltype_paren = remove +sp_defined_paren = remove +sp_endif_cmt = add +sp_endif_cmt = force +sp_enum_assign = force +sp_func_call_paren_empty = remove +sp_func_call_paren = remove +sp_func_class_paren_empty = remove +sp_func_class_paren = remove +sp_func_def_paren_empty = remove +sp_func_def_paren = remove +sp_func_proto_paren_empty = remove +sp_func_proto_paren = remove +sp_inside_for = force +sp_inside_fparen = force +sp_inside_paren_cast = remove +sp_inside_paren = force +sp_inside_rparen = force +sp_inside_sparen = force +sp_inside_tparen = remove +sp_num_before_tr_cmt = 1 +sp_paren_paren = remove +sp_pp_concat = force +sp_pp_stringify = remove +sp_return_paren = remove +sp_sizeof_paren = remove +sp_sparen_paren = remove +sp_square_fparen = remove +sp_throw_paren = remove +sp_type_func = remove diff --git a/utils/mdldec/Makefile b/utils/mdldec/Makefile index d804465a..dd0bb06a 100644 --- a/utils/mdldec/Makefile +++ b/utils/mdldec/Makefile @@ -11,8 +11,10 @@ SYS = $(shell $(CC) -dumpmachine) ifneq (, $(findstring mingw, $(SYS))) EXT = .exe +CFLAGS += -DHAVE_STRICMP -DHAVE_STRNICMP else EXT = +CFLAGS += -DHAVE_STRCASECMP -DHAVE_STRNCASECMP endif APP = $(MODULE)$(EXT) @@ -24,7 +26,8 @@ SRC = mdldec.c \ utils.c \ ../../public/xash3d_mathlib.c \ ../../public/matrixlib.c \ - ../../public/crtlib.c + ../../public/crtlib.c \ + ../../public/getopt.c INCLUDE = -I. \ -I../../common \ diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index c137be93..9ef3f429 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -13,9 +13,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +#include #include #include #include +#include "build.h" +#if !XASH_WIN32 + #include +#else + #include "getopt.h" +#endif #include "const.h" #include "com_model.h" #include "crtlib.h" @@ -25,12 +32,176 @@ GNU General Public License for more details. #include "texture.h" #include "utils.h" #include "version.h" +#include "settings.h" +#include "mdldec.h" char destdir[MAX_SYSPATH]; char modelfile[MAX_SYSPATH]; studiohdr_t *model_hdr; studiohdr_t *texture_hdr; studiohdr_t **anim_hdr; +int globalsettings; + +/* +============ +IsValidName +============ +*/ +static qboolean IsValidName( char *name ) +{ + if( !( isalpha( *name ) || isdigit( *name ))) + return false; + + while( *( ++name )) + { + if( isalpha( *name ) || isdigit( *name ) + || *name == '.' || *name == '-' || *name == '_' + || *name == ' ' || *name == '(' || *name == ')' + || *name == '[' || *name == ']') + continue; + // Found control character Ctrl+Shift+A(SOH|^A|0x1) in the end of name in some models. + else if( name[1] == '\0' ) + { + *name = '\0'; + return true; + } + + return false; + } + + return true; +} + +/* +============ +TextureNameFix +============ +*/ +static void TextureNameFix( void ) +{ + int i, j, len, counter, protected = 0; + qboolean hasduplicates = false; + mstudiotexture_t *texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ), *texture1; + + for( i = 0; i < texture_hdr->numtextures; ++i, ++texture ) + { + ExtractFileName( texture->name, sizeof( texture->name )); + + if( !( ( ( texture->width == 1 || texture->height == 1 ) + && texture->name[0] == '#' ) || IsValidName( texture->name ))) + Q_snprintf( texture->name, sizeof( texture->name ), "MDLDEC_Texture%i.bmp", ++protected ); + } + + texture -= i; + + for( i = 0; i < texture_hdr->numtextures; ++i, ++texture ) + { + counter = 0; + + texture1 = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ); + + for( j = 0; j < texture_hdr->numtextures; ++j, ++texture1 ) + { + if( j != i && !Q_strncmp( texture1->name, texture->name, sizeof( texture1->name))) + { + len = Q_snprintf( texture1->name, sizeof( texture1->name ), "%s_%i.bmp", texture1->name, ++counter ); + + if( len == -1 ) + Q_snprintf( texture1->name, sizeof( texture1->name ), "MDLDEC_Texture%i_%i.bmp", j, counter ); + } + } + + if( counter > 0 ) + { + LogPrintf( "WARNING: Texture name \"%s\" is repeated %i times.", texture->name, counter ); + + hasduplicates = true; + } + } + + if( protected ) + LogPrintf( "WARNING: Gived name to %i protected texture%s.", protected, protected > 1 ? "s" : "" ); + + if( hasduplicates ) + LogPutS( "WARNING: Added numeric suffix to repeated texture name(s)." ); +} + +/* +============ +BodypartNameFix +============ +*/ +static void BodypartNameFix( void ) +{ + int i, j, k, l, len, counter, protected = 0, protected_models = 0; + qboolean hasduplicates = false; + mstudiobodyparts_t *bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ); + mstudiobodyparts_t *bodypart1; + mstudiomodel_t *model, *model1; + + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) + { + ExtractFileName( bodypart->name, sizeof( bodypart->name )); + if( !IsValidName( bodypart->name )) + Q_snprintf( bodypart->name, sizeof( bodypart->name ), "MDLDEC_Bodypart%i", ++protected ); + + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); + + for( j = 0; j < bodypart->nummodels; ++j, ++model ) + { + ExtractFileName( model->name, sizeof( model->name )); + COM_StripExtension( model->name ); + if( !IsValidName( model->name )) + Q_snprintf( model->name, sizeof( model->name ), "MDLDEC_Model%i", ++protected_models ); + } + } + + bodypart -= i; + + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) + { + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); + + for( j = 0; j < bodypart->nummodels; ++j, ++model ) + { + counter = 0; + + bodypart1 = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ); + + for( k = 0; k < model_hdr->numbodyparts; ++k, ++bodypart1 ) + { + model1 = (mstudiomodel_t *)( (byte *)model_hdr + bodypart1->modelindex ); + + for( l = 0; l < bodypart1->nummodels; ++l, ++model1 ) + { + if( !( i==k && j==l ) && !Q_strncmp( model1->name, model->name, sizeof( model1->name ))) + { + len = Q_snprintf( model1->name, sizeof( model1->name ), "%s_%i", model1->name, ++counter ); + + if( len == -1 ) + Q_snprintf( model1->name, sizeof( model1->name ), "MDLDEC_Model%i_%i", l, counter ); + } + } + + if( counter > 0 ) + { + LogPrintf( "WARNING: Model name \"%s\" is repeated %i times.", model->name, counter ); + + hasduplicates = true; + } + } + } + } + + if( protected ) + LogPrintf( "WARNING: Gived name to %i protected bodypart%s.", protected, protected > 1 ? "s" : "" ); + + if( protected_models ) + LogPrintf( "WARNING: Gived name to %i protected model%s.", protected_models, protected_models > 1 ? "s" : "" ); + + if( hasduplicates ) + LogPutS( "WARNING: Added numeric suffix to repeated bodypart name(s)." ); +} /* ============ @@ -39,36 +210,50 @@ SequenceNameFix */ static void SequenceNameFix( void ) { - int i, j, counter; + int i, j, len, counter, protected = 0; qboolean hasduplicates = false; - mstudioseqdesc_t *seqdesc, *seqdesc1; + mstudioseqdesc_t *seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ), *seqdesc1; - for( i = 0; i < model_hdr->numseq; i++ ) + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; + ExtractFileName( seqdesc->label, sizeof( seqdesc->label )); + COM_StripExtension( seqdesc->label ); + if( !IsValidName( seqdesc->label )) + Q_snprintf( seqdesc->label, sizeof( seqdesc->label ), "MDLDEC_Sequence%i", ++protected ); + } - counter = 1; + seqdesc -= i; - for( j = 0; j < model_hdr->numseq; j++ ) + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) + { + counter = 0; + + seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); + + for( j = 0; j < model_hdr->numseq; ++j, ++seqdesc1 ) { - seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + j; + if( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label ))) + { + len = Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "%s_%i", seqdesc1->label, ++counter ); - if( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label ) ) ) - Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "%s_%i", seqdesc1->label, ++counter ); + if( len == -1 ) + Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "MDLDEC_Sequence%i_%i", j, counter ); + } } - if( counter > 1 ) + if( counter > 0 ) { - printf( "WARNING: Sequence name \"%s\" is repeated %i times.\n", seqdesc->label, counter ); - - Q_snprintf( seqdesc->label, sizeof( seqdesc->label ), "%s_1", seqdesc->label ); + LogPrintf( "WARNING: Sequence name \"%s\" is repeated %i times.", seqdesc->label, counter ); hasduplicates = true; } } + if( protected ) + LogPrintf( "WARNING: Gived name to %i protected sequence%s.", protected, protected > 1 ? "s" : "" ); + if( hasduplicates ) - puts( "WARNING: Added numeric suffix to repeated sequence name(s)." ); + LogPutS( "WARNING: Added numeric suffix to repeated sequence name(s)." ); } /* @@ -78,19 +263,19 @@ BoneNameFix */ static void BoneNameFix( void ) { - int i, counter = 0; - mstudiobone_t *bone; + int i, protected = 0; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); - for( i = 0; i < model_hdr->numbones; i++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; + bone->name[sizeof( bone->name ) - 1] = '\0'; - if( bone->name[0] == '\0' ) - Q_sprintf( bone->name, "MDLDEC_Bone%i", ++counter ); + if( !IsValidName( bone->name ) ) + Q_snprintf( bone->name, sizeof( bone->name ), "MDLDEC_Bone%i", ++protected ); } - if( counter ) - printf( "WARNING: Gived name to %i unnamed bone(s).\n", counter ); + if( protected ) + LogPrintf( "WARNING: Gived name to %i protected bone%s.", protected, protected > 1 ? "s" : "" ); } /* @@ -102,19 +287,20 @@ static qboolean LoadMDL( const char *modelname ) { int i; size_t len; + off_t filesize; char texturename[MAX_SYSPATH]; char seqgroupname[MAX_SYSPATH]; const char *ext; const char id_mdlhdr[] = {'I', 'D', 'S', 'T'}; const char id_seqhdr[] = {'I', 'D', 'S', 'Q'}; - printf( "MDL: %s\n", modelname ); + LogPrintf( "MDL: %s.", modelname ); len = Q_strlen( modelname ); if( len > MAX_SYSPATH - 3 ) { - fputs( "ERROR: Source path is too long.\n", stderr ); + LogPutS( "ERROR: Source path is too long."); return false; } @@ -122,67 +308,83 @@ static qboolean LoadMDL( const char *modelname ) if( !ext ) { - fprintf( stderr, "ERROR: Source file does not have extension.\n" ); + LogPutS( "ERROR: Source file does not have extension." ); return false; } - if( Q_strcmp( ext, "mdl" ) ) + if( Q_stricmp( ext, "mdl" ) ) { - fprintf( stderr, "ERROR: Only .mdl-files is supported.\n" ); + LogPutS( "ERROR: Only .mdl-files is supported." ); return false; } - model_hdr = (studiohdr_t *)LoadFile( modelname ); + model_hdr = (studiohdr_t *)LoadFile( modelname, &filesize ); if( !model_hdr ) { - fprintf( stderr, "ERROR: Can't open %s\n", modelname ); + LogPrintf( "ERROR: Can't open %s.", modelname ); return false; } + if( filesize < sizeof( studiohdr_t )) + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", modelname ); + return false; + } + + if( filesize != model_hdr->length ) + { + // Some bad studio model compiler don't write file length + if( globalsettings & SETTINGS_NOVALIDATION ) + LogPrintf( "WARNING: Wrong file size! File %s may be corrupted!", modelname ); + else + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", modelname ); + return false; + } + } + if( memcmp( &model_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) ) { if( !memcmp( &model_hdr->ident, id_seqhdr, sizeof( id_seqhdr ) ) ) - fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname ); + LogPrintf( "ERROR: %s is not a main HL model file.", modelname ); else - fprintf( stderr, "ERROR: %s is not a valid HL model file.\n", modelname ); + LogPrintf( "ERROR: %s is not a valid HL model file.", modelname ); return false; } if( model_hdr->version != STUDIO_VERSION ) { - fprintf( stderr, "ERROR: %s has unknown Studio MDL format version.\n", modelname ); + LogPrintf( "ERROR: %s has unknown Studio MDL format version %d.", modelname, model_hdr->version ); return false; } if( !model_hdr->numbodyparts ) { - fprintf( stderr, "ERROR: %s is not a main HL model file.\n", modelname ); + LogPrintf( "ERROR: %s is not a main HL model file.", modelname ); return false; } if( destdir[0] != '\0' ) { - if( !MakeDirectory( destdir ) ) - { - fprintf( stderr, "ERROR: Couldn't create directory %s\n", destdir ); + if( !MakeFullPath( destdir )) return false; - } - - COM_PathSlashFix( destdir ); } else COM_ExtractFilePath( modelname, destdir ); + if( destdir[0] != '\0' ) + COM_PathSlashFix( destdir ); + len -= ( sizeof( ".mdl" ) - 1 ); // path length without extension if( !model_hdr->numtextures ) { - Q_strcpy( texturename, modelname ); - Q_strcpy( &texturename[len], "t.mdl" ); + Q_strncpy( texturename, modelname, sizeof( texturename )); + Q_strncpy( &texturename[len], "t.mdl", sizeof( texturename ) - len ); - texture_hdr = (studiohdr_t *)LoadFile( texturename ); + texture_hdr = (studiohdr_t *)LoadFile( texturename, &filesize ); if( !texture_hdr ) { @@ -190,12 +392,30 @@ static qboolean LoadMDL( const char *modelname ) // dirty hack for casesensetive filesystems texturename[len] = 'T'; - texture_hdr = (studiohdr_t *)LoadFile( texturename ); + texture_hdr = (studiohdr_t *)LoadFile( texturename, &filesize ); if( !texture_hdr ) #endif { - fprintf( stderr, "ERROR: Can't open external textures file %s\n", texturename ); + LogPrintf( "ERROR: Can't open external textures file %s.", texturename ); + return false; + } + } + + if( filesize < sizeof( studiohdr_t ) ) + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", texturename ); + return false; + } + + if( filesize != texture_hdr->length ) + { + // Some bad studio model compiler don't write file length + if( globalsettings & SETTINGS_NOVALIDATION ) + LogPrintf( "WARNING: Wrong file size! File %s may be corrupted!", texturename ); + else + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", texturename ); return false; } } @@ -203,7 +423,7 @@ static qboolean LoadMDL( const char *modelname ) if( memcmp( &texture_hdr->ident, id_mdlhdr, sizeof( id_mdlhdr ) ) || !texture_hdr->numtextures ) { - fprintf( stderr, "ERROR: %s is not a valid external textures file.\n", texturename ); + LogPrintf( "ERROR: %s is not a valid external textures file.", texturename ); return false; } } @@ -214,7 +434,7 @@ static qboolean LoadMDL( const char *modelname ) if( !anim_hdr ) { - fputs( "ERROR: Couldn't allocate memory for sequences.\n", stderr ); + LogPutS( "ERROR: Couldn't allocate memory for sequences." ); return false; } @@ -222,29 +442,63 @@ static qboolean LoadMDL( const char *modelname ) if( model_hdr->numseqgroups > 1 ) { - Q_strcpy( seqgroupname, modelname ); + Q_strncpy( seqgroupname, modelname, sizeof( seqgroupname )); for( i = 1; i < model_hdr->numseqgroups; i++ ) { - Q_sprintf( &seqgroupname[len], "%02d.mdl", i ); + Q_snprintf( &seqgroupname[len], sizeof( seqgroupname ) - len, "%02d.mdl", i ); - anim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname ); + anim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname, &filesize ); if( !anim_hdr[i] ) { - fprintf( stderr, "ERROR: Can't open sequence file %s\n", seqgroupname ); + LogPrintf( "ERROR: Can't open sequence file %s.", seqgroupname ); return false; } + if( filesize < sizeof( studiohdr_t )) + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", seqgroupname ); + return false; + } + + if( filesize != anim_hdr[i]->length ) + { + // Some bad studio model compiler don't write file length + if( globalsettings & SETTINGS_NOVALIDATION ) + LogPrintf( "WARNING: Wrong file size! File %s may be corrupted!", seqgroupname ); + else + { + LogPrintf( "ERROR: Wrong file size! File %s may be corrupted!", seqgroupname ); + return false; + } + } + if( memcmp( &anim_hdr[i]->ident, id_seqhdr, sizeof( id_seqhdr ) ) ) { - fprintf( stderr, "ERROR: %s is not a valid sequence file.\n", seqgroupname ); + LogPrintf( "ERROR: %s is not a valid sequence file.", seqgroupname ); return false; } } } - COM_FileBase( modelname, modelfile ); + COM_FileBase( modelname, modelfile, sizeof( modelfile )); + + // Some validation checks was found in mdldec-golang by Psycrow101 + if( model_hdr->numhitboxes > model_hdr->numbones * ( MAXSTUDIOSRCBONES / MAXSTUDIOBONES )) + { + LogPrintf( "WARNING: Invalid hitboxes number %d.", model_hdr->numhitboxes ); + model_hdr->numhitboxes = 0; + } + else if( model_hdr->hitboxindex + model_hdr->numhitboxes * ( sizeof( mstudiobbox_t ) + sizeof( mstudiohitboxset_t )) > model_hdr->length ) + { + LogPrintf( "WARNING: Invalid hitboxes offset %d.", model_hdr->hitboxindex ); + model_hdr->numhitboxes = 0; + } + + TextureNameFix(); + + BodypartNameFix(); SequenceNameFix(); @@ -253,6 +507,18 @@ static qboolean LoadMDL( const char *modelname ) return true; } +/* +============ +ShowVersion +============ +*/ +static void ShowVersion( void ) +{ + LogPutS( "\nHalf-Life Studio Model Decompiler " APP_VERSION ); + LogPutS( "Copyright Flying With Gauss 2020-2025 (c) " ); + LogPutS( "--------------------------------------------------" ); +} + /* ============ ShowHelp @@ -260,44 +526,88 @@ ShowHelp */ static void ShowHelp( const char *app_name ) { - printf( "usage: %s source_file\n", app_name ); - printf( " %s source_file target_directory\n", app_name ); + LogPrintf( "usage: %s [-ahlmtuVv] ", app_name ); + LogPrintf( " %s [-ahlmtuVv] ", app_name ); + LogPutS( "\nnote:" ); + LogPutS( "\tby default this decompiler aimed to support extended MDL10 format from XashXT/PrimeXT/Paranoia2." ); + LogPutS( "\tif you use an old GoldSource studio model compiler you may be need to edit .qc-file after decompilation." ); + LogPutS( "\noptions:" ); + LogPutS( "\t-a\tplace files with animations to separate directory." ); + LogPutS( "\t-l\tdo not output logs." ); + LogPutS( "\t-m\tuse GoldSource-compatible motion types." ); + LogPutS( "\t-t\tplace texture files to separate directory." ); + LogPutS( "\t-u\tenable UV coords shifting for DoomMusic's and Sven-Coop's studio model compilers." ); + LogPutS( "\t-V\tignore some validation checks for broken models." ); + LogPutS( "\t-v\tshow version." ); + LogPutS( "\t-h\tthis message." ); } int main( int argc, char *argv[] ) { - puts( "\nHalf-Life Studio Model Decompiler " APP_VERSION ); - puts( "Copyright Flying With Gauss 2020 (c) " ); - puts( "--------------------------------------------------" ); + int opt, ret = 0 ; + + while( ( opt = getopt( argc, argv, "ahlmtuVv" )) != -1 ) + { + switch( opt ) + { + case 'a': globalsettings |= SETTINGS_SEPARATEANIMSFOLDER; break; + case 't': globalsettings |= SETTINGS_SEPARATETEXTURESFOLDER; break; + case 'l': globalsettings |= SETTINGS_NOLOGS; break; + case 'm': globalsettings |= SETTINGS_LEGACYMOTION; break; + case 'u': globalsettings |= SETTINGS_UVSHIFTING; break; + case 'V': globalsettings |= SETTINGS_NOVALIDATION; break; + case 'h': + case '?': + globalsettings = 0; + ShowVersion(); + ShowHelp( argv[0] ); + ret = 2; + goto end; + case 'v': + globalsettings = 0; + ShowVersion(); + ret = 2; + goto end; + } + } + + ShowVersion(); + + argc -= (optind - 1); if( argc == 1 ) { ShowHelp( argv[0] ); + ret = 2; goto end; } else if( argc == 3 ) { - if( Q_strlen( argv[2] ) > MAX_SYSPATH - 2 ) + if( Q_strlen( argv[optind + 1] ) > MAX_SYSPATH - 2 ) { - fputs( "ERROR: Destination path is too long.\n", stderr ); + LogPutS( "ERROR: Destination path is too long."); + ret = 1; goto end; } - Q_strcpy( destdir, argv[2] ); + Q_strncpy( destdir, argv[optind + 1], sizeof( destdir )); } - if( !LoadActivityList( argv[0] ) || !LoadMDL( argv[1] ) ) + if( !(LoadActivityList( argv[0] ) && LoadMDL( argv[optind] ))) + { + ret = 1; goto end; + } WriteQCScript(); WriteSMD(); WriteTextures(); - puts( "Done." ); + LogPutS( "Done." ); end: - puts( "--------------------------------------------------" ); + LogPutS( "--------------------------------------------------" ); - return 0; + return ret; } diff --git a/utils/mdldec/qc.c b/utils/mdldec/qc.c index 9a8aef82..bcf3f9d3 100644 --- a/utils/mdldec/qc.c +++ b/utils/mdldec/qc.c @@ -15,12 +15,16 @@ GNU General Public License for more details. #include #include +#include "xash3d_mathlib.h" #include "eiface.h" #include "studio.h" #include "crtlib.h" #include "version.h" #include "mdldec.h" #include "utils.h" +#include "smd.h" +#include "texture.h" +#include "settings.h" #include "qc.h" static char **activity_names; @@ -46,8 +50,8 @@ qboolean LoadActivityList( const char *appname ) if( !p ) { - fprintf( stderr, "ERROR: Couldn't find file " ACTIVITIES_FILE ".\n" \ - "Place " ACTIVITIES_FILE " beside %s or set MDLDEC_ACT_PATH environment variable.\n", appname ); + LogPrintf( "ERROR: Couldn't find file " ACTIVITIES_FILE ".\n" \ + "Place " ACTIVITIES_FILE " beside %s or set MDLDEC_ACT_PATH environment variable.", appname ); return false; } @@ -61,7 +65,7 @@ qboolean LoadActivityList( const char *appname ) if( !fp ) { - fputs( "ERROR: Can't open file " ACTIVITIES_FILE ".\n", stderr ); + LogPutS( "ERROR: Couldn't open file " ACTIVITIES_FILE "." ); return false; } } @@ -72,17 +76,17 @@ qboolean LoadActivityList( const char *appname ) if( !activity_names ) { - fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr ); + LogPutS( "ERROR: Couldn't allocate memory for activities strings." ); return false; } - COM_RemoveLineFeed( buf ); + COM_RemoveLineFeed( buf, sizeof( buf )); activity_names[activity_count - 1] = strdup( buf ); if( !activity_names[activity_count - 1] ) { - fputs( "ERROR: Couldn't allocate memory for activities strings.\n", stderr ); + LogPutS( "ERROR: Couldn't allocate memory for activities strings." ); return false; } } @@ -110,7 +114,7 @@ static const char *FindActivityName( int type ) GetMotionTypeString ============ */ -static void GetMotionTypeString( int type, char *str, qboolean is_composite ) +static void GetMotionTypeString( int type, char *str, size_t size, qboolean is_composite ) { const char *p = NULL; @@ -119,47 +123,69 @@ static void GetMotionTypeString( int type, char *str, qboolean is_composite ) if( is_composite ) { if( type & STUDIO_X ) - Q_strcat( str, " X" ); + Q_strncat( str, " X", size ); if( type & STUDIO_Y ) - Q_strcat( str, " Y" ); + Q_strncat( str, " Y", size ); if( type & STUDIO_Z ) - Q_strcat( str, " Z" ); + Q_strncat( str, " Z", size ); if( type & STUDIO_XR ) - Q_strcat( str, " XR" ); + Q_strncat( str, " XR", size ); if( type & STUDIO_YR ) - Q_strcat( str, " YR" ); + Q_strncat( str, " YR", size ); if( type & STUDIO_ZR ) - Q_strcat( str, " ZR" ); + Q_strncat( str, " ZR", size ); if( type & STUDIO_LX ) - Q_strcat( str, " LX" ); + Q_strncat( str, " LX", size ); if( type & STUDIO_LY ) - Q_strcat( str, " LY" ); + Q_strncat( str, " LY", size ); if( type & STUDIO_LZ ) - Q_strcat( str, " LZ" ); + Q_strncat( str, " LZ", size ); - if( type & STUDIO_LXR ) - Q_strcat( str, " LXR" ); + if( globalsettings & SETTINGS_LEGACYMOTION ) + { + if( type & STUDIO_LXR ) + Q_strncat( str, " AX", size ); - if( type & STUDIO_LYR ) - Q_strcat( str, " LYR" ); + if( type & STUDIO_LYR ) + Q_strncat( str, " AY", size ); - if( type & STUDIO_LZR ) - Q_strcat( str, " LZR" ); + if( type & STUDIO_LZR ) + Q_strncat( str, " AZ", size ); - if( type & STUDIO_LINEAR ) - Q_strcat( str, " LM" ); + if( type & STUDIO_LINEAR ) + Q_strncat( str, " AXR", size ); - if( type & STUDIO_QUADRATIC_MOTION ) - Q_strcat( str, " LQ" ); + if( type & STUDIO_QUADRATIC_MOTION ) + Q_strncat( str, " AYR", size ); + if( type & STUDIO_RESERVED ) + Q_strncat( str, " AZR", size ); + } + else + { + if( type & STUDIO_LXR ) + Q_strncat( str, " LXR", size ); + + if( type & STUDIO_LYR ) + Q_strncat( str, " LYR", size ); + + if( type & STUDIO_LZR ) + Q_strncat( str, " LZR", size ); + + if( type & STUDIO_LINEAR ) + Q_strncat( str, " LM", size ); + + if( type & STUDIO_QUADRATIC_MOTION ) + Q_strncat( str, " LQ", size ); + } return; } @@ -176,16 +202,37 @@ static void GetMotionTypeString( int type, char *str, qboolean is_composite ) case STUDIO_LX: p = "LX"; break; case STUDIO_LY: p = "LY"; break; case STUDIO_LZ: p = "LZ"; break; - case STUDIO_LXR: p = "LXR"; break; - case STUDIO_LYR: p = "LYR"; break; - case STUDIO_LZR: p = "LZR"; break; - case STUDIO_LINEAR: p = "LM"; break; - case STUDIO_QUADRATIC_MOTION: p = "LQ"; break; default: break; } + if( globalsettings & SETTINGS_LEGACYMOTION ) + { + switch( type ) + { + case STUDIO_LXR: p = "AX"; break; + case STUDIO_LYR: p = "AY"; break; + case STUDIO_LZR: p = "AZ"; break; + case STUDIO_LINEAR: p = "AXR"; break; + case STUDIO_QUADRATIC_MOTION: p = "AYR"; break; + case STUDIO_RESERVED: p = "AZR"; break; + default: break; + } + } + else + { + switch( type ) + { + case STUDIO_LXR: p = "LXR"; break; + case STUDIO_LYR: p = "LYR"; break; + case STUDIO_LZR: p = "LZR"; break; + case STUDIO_LINEAR: p = "LM"; break; + case STUDIO_QUADRATIC_MOTION: p = "LQ"; break; + default: break; + } + } + if( p ) - Q_strcpy( str, p ); + Q_strncpy( str, p, size ); } /* @@ -197,6 +244,7 @@ static void WriteTextureRenderMode( FILE *fp ) { int i; mstudiotexture_t *texture; + long pos = ftell( fp ); for( i = 0; i < texture_hdr->numtextures; i++ ) { @@ -234,6 +282,9 @@ static void WriteTextureRenderMode( FILE *fp ) if( texture->flags & STUDIO_NF_TWOSIDE ) fprintf( fp, "$texrendermode \"%s\" \"twoside\" \n", texture->name ); } + + if( ftell( fp ) != pos ) + fputs( "\n", fp ); } /* @@ -244,42 +295,41 @@ WriteSkinFamilyInfo static void WriteSkinFamilyInfo( FILE *fp ) { int i, j, k; - short *skinref, index; + short *skinref, *index; mstudiotexture_t *texture; if( texture_hdr->numskinfamilies < 2 ) return; - fprintf( fp, "\n// %i skin families\n", texture_hdr->numskinfamilies ); + fprintf( fp, "// %i skin families\n", texture_hdr->numskinfamilies ); - fputs( "$texturegroup skinfamilies \n{\n", fp ); + fputs( "$texturegroup \"skinfamilies\"\n{\n", fp ); skinref = (short *)( (byte *)texture_hdr + texture_hdr->skinindex ); + texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ); for( i = 0; i < texture_hdr->numskinfamilies; ++i ) { - fputs( "{", fp ); + fputs( "\t{\n", fp ); - for( j = 0; j < texture_hdr->numskinref; ++j ) + index = skinref + i * texture_hdr->numskinref; + + for( j = 0; j < texture_hdr->numskinref; ++j, ++index ) { - index = *( skinref + i * texture_hdr->numskinref + j ); - for( k = 0; k < texture_hdr->numskinfamilies; ++k ) { - if( index == *( skinref + k * texture_hdr->numskinref + j ) ) + if( *index == *( skinref + k * texture_hdr->numskinref + j ) ) continue; - texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + index; - - fprintf( fp, " \"%s\" ", texture->name ); + fprintf( fp, "\t\t\"%s\"\n", texture[*index].name ); break; } } - fputs( "}\n", fp ); + fputs( "\t}\n", fp ); } - fputs( "}\n", fp ); + fputs( "}\n\n", fp ); } /* @@ -296,15 +346,15 @@ static void WriteAttachmentInfo( FILE *fp ) if( !model_hdr->numattachments ) return; - fprintf( fp, "\n// %i attachment(s)\n", model_hdr->numattachments ); + attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); - for( i = 0; i < model_hdr->numattachments; ++i ) - { - attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + attachment->bone; + fprintf( fp, "// %i attachment%s\n", model_hdr->numattachments, model_hdr->numattachments > 1 ? "s" : "" ); - fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone->name, attachment->org[0], attachment->org[1], attachment->org[2] ); - } + for( i = 0; i < model_hdr->numattachments; ++i, ++attachment ) + fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone[attachment->bone].name, attachment->org[0], attachment->org[1], attachment->org[2] ); + + fputs( "\n", fp ); } /* @@ -315,23 +365,18 @@ WriteBodyGroupInfo static void WriteBodyGroupInfo( FILE *fp ) { int i, j; - mstudiobodyparts_t *bodypart; + mstudiobodyparts_t *bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ); mstudiomodel_t *model; - char modelname[64]; - fputs( "\n// reference mesh(es)\n", fp ); + fprintf( fp, "// %i reference mesh%s\n", model_hdr->numbodyparts, model_hdr->numbodyparts > 1 ? "es" : "" ); - for( i = 0; i < model_hdr->numbodyparts; ++i ) + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) { - bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ) + i; + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); if( bodypart->nummodels == 1 ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); - - COM_FileBase( model->name, modelname ); - - fprintf( fp, "$body \"%s\" \"%s\"\n\n", bodypart->name, model->name ); + fprintf( fp, "$body \"%s\" \"%s\"\n", bodypart->name, model->name ); continue; } @@ -339,23 +384,21 @@ static void WriteBodyGroupInfo( FILE *fp ) fputs( "{\n", fp ); - for( j = 0; j < bodypart->nummodels; ++j ) + for( j = 0; j < bodypart->nummodels; ++j, ++model ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; - if( !Q_strncmp( model->name, "blank", 5 ) ) { - fputs( "blank\n", fp ); + fputs( "\tblank\n", fp ); continue; } - COM_FileBase( model->name, modelname ); - - fprintf( fp, "studio \"%s\"\n", modelname ); + fprintf( fp, "\tstudio \"%s\"\n", model->name ); } - fputs( "}\n\n" , fp ); + fputs( "}\n", fp ); } + + fputs( "\n", fp ); } /* @@ -373,19 +416,28 @@ static void WriteControllerInfo( FILE *fp ) if( !model_hdr->numbonecontrollers ) return; - fprintf( fp, "\n// %i bone controller(s)\n", model_hdr->numbonecontrollers ); + bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); - for( i = 0; i < model_hdr->numbonecontrollers; ++i ) + fprintf( fp, "// %i bone controller%s\n", model_hdr->numbonecontrollers, model_hdr->numbonecontrollers > 1 ? "s" : "" ); + + for( i = 0; i < model_hdr->numbonecontrollers; ++i, ++bonecontroller ) { - bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + bonecontroller->bone; + GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, sizeof( motion_types ), false ); - GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, false ); + fputs( "$controller ", fp ); - fprintf( fp, "$controller %i \"%s\" %s %f %f\n", - bonecontroller->index, bone->name, motion_types, + if( bonecontroller->index == 4 ) + fputs( "Mouth", fp ); + else + fprintf( fp, "%i", bonecontroller->index ); + + fprintf( fp, " \"%s\" %s %f %f\n", + bone[bonecontroller->bone].name, motion_types, bonecontroller->start, bonecontroller->end ); } + + fputs( "\n", fp ); } /* @@ -402,18 +454,41 @@ static void WriteHitBoxInfo( FILE *fp ) if( !model_hdr->numhitboxes ) return; - fprintf( fp, "\n// %i hit box(es)\n", model_hdr->numhitboxes ); + hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); - for( i = 0; i < model_hdr->numhitboxes; i++ ) - { - hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + hitbox->bone; + fprintf( fp, "// %i hit box%s\n", model_hdr->numhitboxes, model_hdr->numhitboxes > 1 ? "es" : "" ); + for( i = 0; i < model_hdr->numhitboxes; ++i, ++hitbox ) fprintf( fp, "$hbox %i \"%s\" %f %f %f %f %f %f\n", - hitbox->group, bone->name, + hitbox->group, bone[hitbox->bone].name, hitbox->bbmin[0], hitbox->bbmin[1], hitbox->bbmin[2], hitbox->bbmax[0], hitbox->bbmax[1], hitbox->bbmax[2] ); + + fputs( "\n", fp ); +} + +/* +============ +CalcSequenceGroupSize +============ +*/ +static int CalcSequenceGroupSize( void ) +{ + int i, maxsize = 0, groupsize = DEFAULT_SEQGROUPSIZE; + + for( i = 1; i < model_hdr->numseqgroups; i++ ) + maxsize = Q_max( anim_hdr[i]->length, maxsize ); + + if( maxsize > 0 ) + { + groupsize = maxsize / 1024; + + if( maxsize % 1024 ) + groupsize++; } + + return groupsize; } /* @@ -428,63 +503,29 @@ static void WriteSequenceInfo( FILE *fp ) char motion_types[256]; mstudioevent_t *event; mstudioseqdesc_t *seqdesc; + const char *seq_path; if( model_hdr->numseqgroups > 1 ) - fputs( "\n$sequencegroupsize 64\n", fp ); + fprintf( fp, "$sequencegroupsize %d\n\n", CalcSequenceGroupSize( ) ); if( model_hdr->numseq > 0 ) - fprintf( fp, "\n// %i animation sequence(s)\n", model_hdr->numseq ); + fprintf( fp, "// %i animation sequence%s\n", model_hdr->numseq, model_hdr->numseq > 1 ? "s" : "" ); + else return; - for( i = 0; i < model_hdr->numseq; ++i ) + seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); + + seq_path = ( globalsettings & SETTINGS_SEPARATEANIMSFOLDER ) ? SEQUENCEPATH : ""; + + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; - - fprintf( fp, "$sequence \"%s\" ", seqdesc->label ); + fprintf( fp, "$sequence \"%s\" {\n", seqdesc->label ); if( seqdesc->numblends > 1 ) { - if( seqdesc->numblends > 2 ) - { - fputs( "{\n", fp ); - - for( j = 0; j < seqdesc->numblends; j++ ) - { - fputs( " ", fp ); - - fprintf( fp, "\"%s_blend%i\" ", seqdesc->label, j + 1 ); - - fputs( "\n", fp ); - } - - fputs( " ", fp ); - } - else - { - fprintf( fp, "\"%s_blend1\" ", seqdesc->label ); - fprintf( fp, "\"%s_blend2\" ", seqdesc->label ); - } - - GetMotionTypeString( seqdesc->blendtype[0], motion_types, false ); - - fprintf( fp, "blend %s %.0f %.0f", - motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] ); + for( j = 0; j < seqdesc->numblends; j++ ) + fprintf( fp, "\t\"%s%s_blend%02i\"\n", seq_path, seqdesc->label, j + 1 ); } - else - { - fprintf( fp, "\"%s\"", seqdesc->label ); - } - - if( seqdesc->motiontype ) - { - GetMotionTypeString( seqdesc->motiontype, motion_types, true ); - - fprintf( fp, "%s", motion_types ); - } - - fprintf( fp, " fps %.0f ", seqdesc->fps ); - - if( seqdesc->flags == 1 ) - fputs( "loop ", fp ); + else fprintf( fp, "\t\"%s%s\"\n", seq_path, seqdesc->label ); if( seqdesc->activity ) { @@ -492,70 +533,67 @@ static void WriteSequenceInfo( FILE *fp ) if( activity ) { - fprintf( fp, "%s %i ", activity, seqdesc->actweight ); + fprintf( fp, "\t%s %i\n", activity, seqdesc->actweight ); } else { - printf( "WARNING: Sequence %s has a custom activity flag (ACT_%i %i).\n", + LogPrintf( "WARNING: Sequence %s has a custom activity flag (ACT_%i %i).", seqdesc->label, seqdesc->activity, seqdesc->actweight ); - fprintf( fp, "ACT_%i %i ", seqdesc->activity, seqdesc->actweight ); + fprintf( fp, "\tACT_%i %i\n", seqdesc->activity, seqdesc->actweight ); } } + if( seqdesc->numblends > 1 ) + { + GetMotionTypeString( seqdesc->blendtype[0], motion_types, sizeof( motion_types ), false ); + + fprintf( fp, "\tblend %s %.0f %.0f\n", + motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] ); + + if( !seqdesc->blendtype[0] ) + LogPrintf( "WARNING: Something wrong with blending type for sequence: %s", seqdesc->label ); + } + + event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ); + + for( j = 0; j < seqdesc->numevents; ++j, ++event ) + { + fprintf( fp, "\t{ event %i %i", event->event, event->frame ); + + if( event->options[0] != '\0' ) + fprintf( fp, " \"%s\"", event->options ); + + fputs( " }\n", fp ); + } + + + fprintf( fp, "\tfps %.0f\n", seqdesc->fps ); + + if( seqdesc->flags == 1 ) + fputs( "\tloop\n", fp ); + + if( seqdesc->motiontype ) + { + GetMotionTypeString( seqdesc->motiontype, motion_types, sizeof( motion_types ), true ); + + fprintf( fp, "\t%s\n", motion_types ); + } + if( seqdesc->entrynode && seqdesc->exitnode ) { if( seqdesc->entrynode == seqdesc->exitnode ) - fprintf( fp, "node %i ", seqdesc->entrynode ); + fprintf( fp, "\tnode %i\n", seqdesc->entrynode ); else if( seqdesc->nodeflags ) - fprintf( fp, "rtransition %i %i ", seqdesc->entrynode, seqdesc->exitnode ); + fprintf( fp, "\trtransition %i %i\n", seqdesc->entrynode, seqdesc->exitnode ); else - fprintf( fp, "transition %i %i ", seqdesc->entrynode, seqdesc->exitnode ); + fprintf( fp, "\ttransition %i %i\n", seqdesc->entrynode, seqdesc->exitnode ); } - if( seqdesc->numevents > 2 ) - { - fputs( "{\n ", fp ); - - for( j = 0; j < seqdesc->numevents; j++ ) - { - if( seqdesc->numblends <= 2 ) - fputs( " ", fp ); - else - fputs( " ", fp ); - - event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j; - - fprintf( fp, "{ event %i %i", event->event, event->frame ); - - if( event->options[0] != '\0' ) - fprintf( fp, " \"%s\"", event->options ); - - fputs( " }\n ", fp ); - } - - fputs( "}", fp ); - } - else - { - for( j = 0; j < seqdesc->numevents; j++ ) - { - event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j; - - fprintf( fp, "{ event %i %i", event->event, event->frame ); - - if( event->options[0] != '\0') - fprintf( fp, " \"%s\"", event->options ); - - fputs( " } ", fp ); - } - } - - fputs( "\n", fp ); - - if( seqdesc->numblends > 2 ) - fputs( "}\n", fp ); + fputs( "}\n", fp ); } + + fputs( "\n", fp ); } /* @@ -573,7 +611,7 @@ void WriteQCScript( void ) if( len == -1 ) { - fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.qc\n", modelfile ); + LogPrintf( "ERROR: Destination path is too long. Couldn't write %s.qc.", modelfile ); return; } @@ -581,7 +619,7 @@ void WriteQCScript( void ) if( !fp ) { - fprintf( stderr, "ERROR: Can't write %s\n", filename ); + LogPrintf( "ERROR: Couldn't write %s.", filename ); return; } @@ -601,43 +639,44 @@ void WriteQCScript( void ) fprintf( fp, "$modelname \"%s.mdl\"\n", modelfile ); - fputs( "$cd \".\\\"\n", fp ); - fputs( "$cdtexture \".\\\"\n", fp ); - fputs( "$scale 1.0\n", fp ); + fputs( "$cd \".\"\n", fp ); + if( globalsettings & SETTINGS_SEPARATETEXTURESFOLDER ) + fputs( "$cdtexture \"./" TEXTUREPATH "\"\n", fp ); + else fputs( "$cdtexture \"./\"\n", fp ); fputs( "$cliptotextures\n", fp ); + fputs( "$scale 1.0\n", fp ); fputs( "\n", fp ); - if( !model_hdr->numtextures ) - fputs( "$externaltextures\n", fp ); - - if( model_hdr->flags != 0 ) + if( model_hdr->flags & STUDIO_HAS_BONEINFO ) { - fprintf( fp, "$flags %i\n", model_hdr->flags ); - - printf( "WARNING: This model uses the $flags keyword set to %i\n", model_hdr->flags ); + if( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS ) + fputs( "$boneweights\n\n", fp ); } - fputs( "\n", fp ); + WriteBodyGroupInfo( fp ); + + fprintf( fp, "$flags %u\n\n", model_hdr->flags &~( STUDIO_HAS_BONEINFO | STUDIO_HAS_BONEWEIGHTS ) ); + fprintf( fp, "$eyeposition %f %f %f\n\n", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] ); + + if( !model_hdr->numtextures ) + fputs( "$externaltextures\n\n", fp ); + + WriteSkinFamilyInfo( fp ); + WriteTextureRenderMode( fp ); + WriteAttachmentInfo( fp ); fprintf( fp, "$bbox %f %f %f", model_hdr->min[0], model_hdr->min[1], model_hdr->min[2] ); - fprintf( fp, " %f %f %f\n", model_hdr->max[0], model_hdr->max[1], model_hdr->max[2] ); + fprintf( fp, " %f %f %f\n\n", model_hdr->max[0], model_hdr->max[1], model_hdr->max[2] ); fprintf( fp, "$cbox %f %f %f", model_hdr->bbmin[0], model_hdr->bbmin[1], model_hdr->bbmin[2] ); - fprintf( fp, " %f %f %f\n", model_hdr->bbmax[0], model_hdr->bbmax[1], model_hdr->bbmax[2] ); - fprintf( fp, "$eyeposition %f %f %f\n", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] ); + fprintf( fp, " %f %f %f\n\n", model_hdr->bbmax[0], model_hdr->bbmax[1], model_hdr->bbmax[2] ); - fputs( "\n", fp ); - - WriteBodyGroupInfo( fp ); - WriteTextureRenderMode( fp ); - WriteSkinFamilyInfo( fp ); - WriteAttachmentInfo( fp ); - WriteControllerInfo( fp ); WriteHitBoxInfo( fp ); + WriteControllerInfo( fp ); WriteSequenceInfo( fp ); - fputs( "\n// End of QC script.\n", fp ); + fputs( "// End of QC script.\n", fp ); fclose( fp ); - printf( "QC Script: %s\n", filename ); + LogPrintf( "QC Script: %s", filename ); } diff --git a/utils/mdldec/qc.h b/utils/mdldec/qc.h index 05d538ac..75d8b5bc 100644 --- a/utils/mdldec/qc.h +++ b/utils/mdldec/qc.h @@ -16,6 +16,7 @@ GNU General Public License for more details. #ifndef QC_H #define QC_H +#define DEFAULT_SEQGROUPSIZE 64 #define ACTIVITIES_FILE "activities.txt" qboolean LoadActivityList( const char *appname ); diff --git a/utils/mdldec/res/activities.txt b/utils/mdldec/res/activities.txt index 5d966502..601075b6 100644 --- a/utils/mdldec/res/activities.txt +++ b/utils/mdldec/res/activities.txt @@ -75,29 +75,3 @@ ACT_FLINCH_LEFTARM ACT_FLINCH_RIGHTARM ACT_FLINCH_LEFTLEG ACT_FLINCH_RIGHTLEG -ACT_VM_NONE -ACT_VM_DEPLOY -ACT_VM_DEPLOY_EMPTY -ACT_VM_HOLSTER -ACT_VM_HOLSTER_EMPTY -ACT_VM_IDLE1 -ACT_VM_IDLE2 -ACT_VM_IDLE3 -ACT_VM_RANGE_ATTACK1 -ACT_VM_RANGE_ATTACK2 -ACT_VM_RANGE_ATTACK3 -ACT_VM_MELEE_ATTACK1 -ACT_VM_MELEE_ATTACK2 -ACT_VM_MELEE_ATTACK3 -ACT_VM_SHOOT_EMPTY -ACT_VM_START_RELOAD -ACT_VM_RELOAD -ACT_VM_RELOAD_EMPTY -ACT_VM_TURNON -ACT_VM_TURNOFF -ACT_VM_PUMP -ACT_VM_PUMP_EMPTY -ACT_VM_START_CHARGE -ACT_VM_CHARGE -ACT_VM_OVERLOAD -ACT_VM_IDLE_EMPTY diff --git a/utils/mdldec/settings.h b/utils/mdldec/settings.h new file mode 100644 index 00000000..437dc2a2 --- /dev/null +++ b/utils/mdldec/settings.h @@ -0,0 +1,17 @@ +#pragma once +#ifndef SETTINGS_H +#define SETTINGS_H + +extern int globalsettings; + +enum +{ + SETTINGS_LEGACYMOTION = BIT(0), + SETTINGS_UVSHIFTING = BIT(1), + SETTINGS_NOLOGS = BIT(2), + SETTINGS_NOVALIDATION = BIT(3), + SETTINGS_SEPARATEANIMSFOLDER = BIT(4), + SETTINGS_SEPARATETEXTURESFOLDER = BIT(5), +}; + +#endif // SETTINGS_H diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c index 4e80e3b0..beb2038f 100644 --- a/utils/mdldec/smd.c +++ b/utils/mdldec/smd.c @@ -22,22 +22,25 @@ GNU General Public License for more details. #include "crtlib.h" #include "studio.h" #include "mdldec.h" +#include "utils.h" +#include "settings.h" #include "smd.h" static matrix3x4 *bonetransform; +static matrix3x4 *worldtransform; /* ============ CreateBoneTransformMatrices ============ */ -static qboolean CreateBoneTransformMatrices( void ) +static qboolean CreateBoneTransformMatrices( matrix3x4 **matrix ) { - bonetransform = calloc( model_hdr->numbones, sizeof( matrix3x4 ) ); + *matrix = calloc( model_hdr->numbones, sizeof( matrix3x4 ) ); - if( !bonetransform ) + if( !*matrix ) { - fputs( "ERROR: Couldn't allocate memory for bone transformation matrices!\n", stderr ); + LogPutS( "ERROR: Couldn't allocate memory for bone transformation matrices!"); return false; } @@ -52,14 +55,12 @@ FillBoneTransformMatrices static void FillBoneTransformMatrices( void ) { int i; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); matrix3x4 bonematrix; vec4_t q; - for( i = 0; i < model_hdr->numbones; i++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - AngleQuaternion( &bone->value[3], q, true ); Matrix3x4_FromOriginQuat( bonematrix, q, bone->value ); @@ -73,14 +74,28 @@ static void FillBoneTransformMatrices( void ) } } +/* +============ +FillWorldTransformMatrices +============ +*/ +static void FillWorldTransformMatrices( void ) +{ + int i; + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)( (byte *)model_hdr + model_hdr->boneindex + model_hdr->numbones * sizeof( mstudiobone_t ) ); + + for( i = 0; i < model_hdr->numbones; ++i, ++boneinfo ) + Matrix3x4_ConcatTransforms( worldtransform[i], bonetransform[i], boneinfo->poseToBone ); +} + /* ============ RemoveBoneTransformMatrices ============ */ -static void RemoveBoneTransformMatrices( void ) +static void RemoveBoneTransformMatrices( matrix3x4 **matrix ) { - free( bonetransform ); + free( *matrix ); } /* @@ -109,19 +124,13 @@ ProperBoneRotationZ */ static void ProperBoneRotationZ( vec_t *motion, float angle ) { - float c, s, x, y; - float rot; + float tmp, rot; rot = DEG2RAD( angle ); - s = sin( rot ); - c = cos( rot ); - - x = motion[0]; - y = motion[1]; - - motion[0] = c * x - s * y; - motion[1] = s * x + c * y; + tmp = motion[0]; + motion[0] = motion[1]; + motion[1] = -tmp; motion[5] += rot; } @@ -171,16 +180,12 @@ WriteNodes static void WriteNodes( FILE *fp ) { int i; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fputs( "nodes\n", fp ); - for( i = 0; i < model_hdr->numbones; i++ ) - { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) fprintf( fp, "%3i \"%s\" %i\n", i, bone->name, bone->parent ); - } fputs( "end\n", fp ); } @@ -193,15 +198,13 @@ WriteSkeleton static void WriteSkeleton( FILE *fp ) { int i, j; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fputs( "skeleton\n", fp ); fputs( "time 0\n", fp ); - for( i = 0; i < model_hdr->numbones; i++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - fprintf( fp, "%3i", i ); for( j = 0; j < 6; j++ ) @@ -220,64 +223,130 @@ WriteTriangleInfo */ static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t *texture, mstudiotrivert_t **triverts, qboolean isevenstrip ) { - int i, indices[3]; - int vert_index; - int norm_index; - int bone_index; - float s, t, u, v; - byte *vertbone; - vec3_t *studioverts; - vec3_t *studionorms; - vec3_t vert, norm; - - if( isevenstrip ) - { - indices[0] = 1; - indices[1] = 2; - indices[2] = 0; - } - else - { - indices[0] = 0; - indices[1] = 1; - indices[2] = 2; - } + int i, j, k, l, index; + int vert_index; + int norm_index; + int bone_index; + int valid_bones; + int width, height; + float u, v; + byte *vertbone; + vec3_t *studioverts; + vec3_t *studionorms; + vec3_t vert, norm; + float weights[MAXSTUDIOBONEWEIGHTS], oldweight, totalweight; + matrix3x4 bonematrix[MAXSTUDIOBONEWEIGHTS], skinmatrix, *pskinmatrix; + mstudioboneweight_t *studioboneweights; + char buffer[64]; vertbone = ( (byte *)model_hdr + model->vertinfoindex ); studioverts = (vec3_t *)( (byte *)model_hdr + model->vertindex ); studionorms = (vec3_t *)( (byte *)model_hdr + model->normindex ); + studioboneweights = (mstudioboneweight_t *)( (byte *)model_hdr + model->blendvertinfoindex ); - s = 1.0f / texture->width; - t = 1.0f / texture->height; + Q_strncpy( buffer, texture->name, sizeof( buffer )); - fprintf( fp, "%s\n", texture->name ); + // Many filesystems couldn't write files if "#" is first character in the name. + if( buffer[0] == '#' ) buffer[0] = 's'; + + fprintf( fp, "%s\n", buffer ); for( i = 0; i < 3; i++ ) { - vert_index = triverts[indices[i]]->vertindex; - norm_index = triverts[indices[i]]->normindex; + index = isevenstrip ? ( i + 1 ) % 3 : i; + vert_index = triverts[index]->vertindex; + norm_index = triverts[index]->normindex; bone_index = vertbone[vert_index]; - Matrix3x4_VectorTransform( bonetransform[bone_index], studioverts[vert_index], vert ); - Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm ); + if( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS ) + { + valid_bones = 0, totalweight = 0; + memset( skinmatrix, 0, sizeof( matrix3x4 ) ); + + for( j = 0; j < MAXSTUDIOBONEWEIGHTS; ++j ) + if( studioboneweights[vert_index].bone[j] != -1 ) + valid_bones++; + + for( j = 0; j < valid_bones; ++j ) + { + Matrix3x4_Copy( bonematrix[j], worldtransform[studioboneweights[vert_index].bone[j]] ); + weights[j] = studioboneweights[vert_index].weight[j] / 255.0f; + totalweight += weights[j]; + } + + oldweight = weights[0]; + + if( totalweight < 1.0f ) + weights[0] += 1.0f - totalweight; + + for( j = 0; j < valid_bones; ++j ) + for( k = 0; k < 3; ++k ) + for( l = 0; l < 4; ++l ) + skinmatrix[k][l] += bonematrix[j][k][l] * weights[j]; + + pskinmatrix = &skinmatrix; + } + else + pskinmatrix = &bonetransform[bone_index]; + + Matrix3x4_VectorTransform( *pskinmatrix, studioverts[vert_index], vert ); + Matrix3x4_VectorRotate( *pskinmatrix, studionorms[norm_index], norm ); VectorNormalize( norm ); if( texture->flags & STUDIO_NF_UV_COORDS ) { - u = HalfToFloat( triverts[indices[i]]->s ); - v = -HalfToFloat( triverts[indices[i]]->t ); + u = HalfToFloat( triverts[index]->s ); + v = -HalfToFloat( triverts[index]->t ); + } + else if( texture->width == 1 || texture->height == 1 ) + { + if( texture->name[0] == '#' ) + { + Q_strncpy( buffer, &texture->name[1], 4 ); + width = Q_atoi( buffer ); + + Q_strncpy( buffer, &texture->name[4], 4 ); + height = Q_atoi( buffer ); + + u = (float)triverts[index]->s / width; + v = 1.0f - (float)triverts[index]->t / height; + } + else + { + u = (float)triverts[index]->s; + v = 1.0f - (float)triverts[index]->t; + } + } + else if( globalsettings & SETTINGS_UVSHIFTING ) + { + u = (float)triverts[index]->s / texture->width; + v = 1.0f - (float)triverts[index]->t / texture->height; } else { - u = ( triverts[indices[i]]->s + 1.0f ) * s; - v = 1.0f - triverts[indices[i]]->t * t; + u = (float)triverts[index]->s / ( texture->width - 1 ); + v = 1.0f - (float)triverts[index]->t / ( texture->height - 1 ); } - fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n", + fprintf( fp, "%3i %f %f %f %f %f %f %f %f", bone_index, vert[0], vert[1], vert[2], norm[0], norm[1], norm[2], u, v ); + + if( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS ) + { + fprintf( fp, " %d", valid_bones ); + + weights[0] = oldweight; + + for( j = 0; j < valid_bones; ++j ) + fprintf( fp, " %d %f", + studioboneweights[vert_index].bone[j], + weights[j] ); + } + + fputs( "\n", fp ); } } @@ -289,16 +358,15 @@ WriteTriangles static void WriteTriangles( FILE *fp, mstudiomodel_t *model ) { int i, j, k; - mstudiomesh_t *mesh; + mstudiomesh_t *mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ); mstudiotexture_t *texture; mstudiotrivert_t *triverts[3]; short *tricmds; fputs( "triangles\n", fp ); - for( i = 0; i < model->nummesh; i++ ) + for( i = 0; i < model->nummesh; ++i, ++mesh ) { - mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ) + i; tricmds = (short *)( (byte *)model_hdr + mesh->triindex ); texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + mesh->skinref; @@ -383,21 +451,24 @@ WriteFrameInfo static void WriteFrameInfo( FILE *fp, mstudioanim_t *anim, mstudioseqdesc_t *seqdesc, int frame ) { int i, j; + float scale; vec_t motion[6]; // x, y, z, xr, yr, zr - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fprintf( fp, "time %i\n", frame ); - for( i = 0; i < model_hdr->numbones; i++, anim++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++anim, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - CalcBonePosition( anim, bone, motion, frame ); if( bone->parent == -1 ) { - for( j = 0; j < 3; j++ ) - motion[j] += frame * 1.0f / seqdesc->numframes * seqdesc->linearmovement[j]; + if( seqdesc->numframes > 1 && frame > 0 ) + { + scale = frame / (float)( seqdesc->numframes - 1 ); + + VectorMA( motion, scale, seqdesc->linearmovement, motion ); + } ProperBoneRotationZ( motion, 270.0f ); } @@ -446,43 +517,46 @@ static void WriteReferences( void ) FILE *fp; mstudiomodel_t *model; mstudiobodyparts_t *bodypart; - char name[64]; char filename[MAX_SYSPATH]; - if( !CreateBoneTransformMatrices() ) + if( !CreateBoneTransformMatrices( &bonetransform ) ) return; FillBoneTransformMatrices(); - for( i = 0; i < model_hdr->numbodyparts; i++ ) + if( model_hdr->flags & STUDIO_HAS_BONEINFO ) { - bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ) + i; + if( !CreateBoneTransformMatrices( &worldtransform ) ) + return; - for( j = 0; j < bodypart->nummodels; j++ ) + FillWorldTransformMatrices(); + } + + bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ); + + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) + { + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); + + for( j = 0; j < bodypart->nummodels; ++j, ++model ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; - if( !Q_strncmp( model->name, "blank", 5 ) ) continue; - COM_FileBase( model->name, name ); - - len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, name ); + len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, model->name ); if( len == -1 ) { - fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", name ); - RemoveBoneTransformMatrices(); - return; + LogPrintf( "ERROR: Destination path is too long. Couldn't write %s.smd.", model->name ); + goto _fail; } fp = fopen( filename, "w" ); if( !fp ) { - fprintf( stderr, "ERROR: Can't write %s\n", filename ); - RemoveBoneTransformMatrices(); - return; + LogPrintf( "ERROR: Couldn't write %s.", filename ); + goto _fail; } fputs( "version 1\n", fp ); @@ -493,11 +567,15 @@ static void WriteReferences( void ) fclose( fp ); - printf( "Reference: %s\n", filename ); + LogPrintf( "Reference: %s.", filename ); } } - RemoveBoneTransformMatrices(); +_fail: + RemoveBoneTransformMatrices( &bonetransform ); + + if( model_hdr->flags & STUDIO_HAS_BONEINFO ) + RemoveBoneTransformMatrices( &worldtransform ); } /* @@ -508,33 +586,41 @@ WriteSequences static void WriteSequences( void ) { int i, j; - int len; + int len, namelen, emptyplace; FILE *fp; - char filename[MAX_SYSPATH]; - mstudioseqdesc_t *seqdesc; + char path[MAX_SYSPATH]; + mstudioseqdesc_t *seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); - for( i = 0; i < model_hdr->numseq; i++ ) + len = Q_snprintf( path, MAX_SYSPATH, ( globalsettings & SETTINGS_SEPARATEANIMSFOLDER ) ? "%s" SEQUENCEPATH : "%s", destdir ); + + if( len == -1 || !MakeDirectory( path )) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; + LogPutS( "ERROR: Destination path is too long or write permission denied. Couldn't create directory for sequences." ); + return; + } + emptyplace = MAX_SYSPATH - len; + + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) + { for( j = 0; j < seqdesc->numblends; j++ ) { if( seqdesc->numblends == 1 ) - len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, seqdesc->label ); + namelen = Q_snprintf( &path[len], emptyplace, "%s.smd", seqdesc->label ); else - len = Q_snprintf( filename, MAX_SYSPATH, "%s%s_blend%i.smd", destdir, seqdesc->label, j + 1 ); + namelen = Q_snprintf( &path[len], emptyplace, "%s_blend%02i.smd", seqdesc->label, j + 1 ); - if( len == -1 ) + if( namelen == -1 ) { - fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", seqdesc->label ); + LogPrintf( "ERROR: Destination path is too long. Couldn't write %s.smd.", seqdesc->label ); return; } - fp = fopen( filename, "w" ); + fp = fopen( path, "w" ); if( !fp ) { - fprintf( stderr, "ERROR: Can't write %s\n", filename ); + LogPrintf( "ERROR: Couldn't write %s.", path ); return; } @@ -545,7 +631,7 @@ static void WriteSequences( void ) fclose( fp ); - printf( "Sequence: %s\n", filename ); + LogPrintf( "Sequence: %s.", path ); } } } diff --git a/utils/mdldec/smd.h b/utils/mdldec/smd.h index 2bcdcadb..cee7f092 100644 --- a/utils/mdldec/smd.h +++ b/utils/mdldec/smd.h @@ -16,6 +16,8 @@ GNU General Public License for more details. #ifndef SMD_H #define SMD_H +#define SEQUENCEPATH "anims/" + void WriteSMD( void ); #endif // SMD_H diff --git a/utils/mdldec/texture.c b/utils/mdldec/texture.c index d38062f0..1ff34095 100644 --- a/utils/mdldec/texture.c +++ b/utils/mdldec/texture.c @@ -20,7 +20,10 @@ GNU General Public License for more details. #include "crtlib.h" #include "studio.h" #include "img_bmp.h" +#include "img_tga.h" #include "mdldec.h" +#include "utils.h" +#include "settings.h" #include "texture.h" /* @@ -28,37 +31,13 @@ GNU General Public License for more details. WriteBMP ============ */ -static void WriteBMP( mstudiotexture_t *texture ) +static void WriteBMP( FILE *fp, mstudiotexture_t *texture ) { - int i, len; - FILE *fp; + int i; const byte *p; - byte *palette, *pic, *buf; - char filename[MAX_SYSPATH], texturename[64]; + byte *palette, *pic; rgba_t rgba_palette[256]; bmp_t bmp_hdr = {0,}; - size_t texture_size; - - COM_FileBase( texture->name, texturename ); - len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.bmp", destdir, texturename ); - - if( len == -1 ) - { - fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.bmp\n", texturename ); - return; - } - - fp = fopen( filename, "wb" ); - - if( !fp ) - { - fprintf( stderr, "ERROR: Can't write texture file %s\n", filename ); - return; - } - - texture_size = texture->height * texture->width; - pic = (byte *)texture_hdr + texture->index; - palette = pic + texture_size; bmp_hdr.id[0] = 'B'; bmp_hdr.id[1] = 'M'; @@ -66,13 +45,16 @@ static void WriteBMP( mstudiotexture_t *texture ) bmp_hdr.height = texture->height; bmp_hdr.planes = 1; bmp_hdr.bitsPerPixel = 8; - bmp_hdr.bitmapDataSize = texture_size; + bmp_hdr.bitmapDataSize = bmp_hdr.width * bmp_hdr.height; bmp_hdr.colors = 256; - bmp_hdr.fileSize = sizeof( bmp_hdr ) + texture_size + sizeof( rgba_palette ); + bmp_hdr.fileSize = sizeof( bmp_hdr ) + bmp_hdr.bitmapDataSize + sizeof( rgba_palette ); bmp_hdr.bitmapDataOffset = sizeof( bmp_hdr ) + sizeof( rgba_palette ); bmp_hdr.bitmapHeaderSize = BI_SIZE; + pic = (byte *)texture_hdr + texture->index; + palette = pic + bmp_hdr.bitmapDataSize; + fwrite( &bmp_hdr, sizeof( bmp_hdr ), 1, fp ); p = palette; @@ -87,24 +69,60 @@ static void WriteBMP( mstudiotexture_t *texture ) fwrite( rgba_palette, sizeof( rgba_palette ), 1, fp ); - buf = malloc( texture_size ); - p = pic; p += ( bmp_hdr.height - 1 ) * bmp_hdr.width; for( i = 0; i < bmp_hdr.height; i++ ) { - memcpy( buf + bmp_hdr.width * i, p, bmp_hdr.width ); + fwrite( p, bmp_hdr.width, 1, fp ); p -= bmp_hdr.width; } +} - fwrite( buf, texture_size, 1, fp ); +/* +============ +WriteTGA +============ +*/ +static void WriteTGA( FILE *fp, mstudiotexture_t *texture ) +{ + int i; + const byte *p; + byte *palette, *pic; + rgb_t rgb_palette[256]; + tga_t tga_hdr = {0,}; - fclose( fp ); + tga_hdr.colormap_type = tga_hdr.image_type = 1; + tga_hdr.colormap_length = 256; + tga_hdr.colormap_size = 24; + tga_hdr.pixel_size = 8; + tga_hdr.width = texture->width; + tga_hdr.height = texture->height; - free( buf ); + pic = (byte *)texture_hdr + texture->index; + palette = pic + tga_hdr.width * tga_hdr.height; - printf( "Texture: %s\n", filename ); + fwrite( &tga_hdr, sizeof( tga_hdr ), 1, fp ); + + p = palette; + + for( i = 0; i < (int)tga_hdr.colormap_length; i++ ) + { + rgb_palette[i][2] = *p++; + rgb_palette[i][1] = *p++; + rgb_palette[i][0] = *p++; + } + + fwrite( rgb_palette, sizeof( rgb_palette ), 1, fp ); + + p = pic; + p += ( tga_hdr.height - 1 ) * tga_hdr.width; + + for( i = 0; i < tga_hdr.height; i++ ) + { + fwrite( p, tga_hdr.width, 1, fp ); + p -= tga_hdr.width; + } } /* @@ -114,14 +132,50 @@ WriteTextures */ void WriteTextures( void ) { - int i; - mstudiotexture_t *texture; + int i, len, namelen, emptyplace; + FILE *fp; + mstudiotexture_t *texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ); + char path[MAX_SYSPATH]; - for( i = 0; i < texture_hdr->numtextures; i++ ) + len = Q_snprintf( path, MAX_SYSPATH, ( globalsettings & SETTINGS_SEPARATETEXTURESFOLDER ) ? "%s" TEXTUREPATH : "%s", destdir ); + + if( len == -1 || !MakeDirectory( path )) { - texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i; + LogPutS( "ERROR: Destination path is too long or write permission denied. Couldn't create directory for textures." ); + return; + } - WriteBMP( texture ); + emptyplace = MAX_SYSPATH - len; + + for( i = 0; i < texture_hdr->numtextures; ++i, ++texture ) + { + namelen = Q_strncpy( &path[len], texture->name, emptyplace ); + + if( emptyplace - namelen < 0 ) + { + LogPrintf( "ERROR: Destination path is too long. Couldn't write %s.", texture->name ); + return; + } + + fp = fopen( path, "wb" ); + + if( !fp ) + { + LogPrintf( "ERROR: Couldn't write texture file %s.", path ); + return; + } + + // Many filesystems couldn't write files if "#" is first character in the name. + if( texture->name[0] == '#' ) texture->name[0] = 's'; + + if( !Q_stricmp( COM_FileExtension( texture->name ), "tga" )) + WriteTGA( fp, texture ); + else + WriteBMP( fp, texture ); + + fclose( fp ); + + LogPrintf( "Texture: %s.", path ); } } diff --git a/utils/mdldec/texture.h b/utils/mdldec/texture.h index ba1cbe0b..90f338b5 100644 --- a/utils/mdldec/texture.h +++ b/utils/mdldec/texture.h @@ -16,6 +16,8 @@ GNU General Public License for more details. #ifndef TEXTURE_H #define TEXTURE_H +#define TEXTUREPATH "textures/" + void WriteTextures( void ); #endif // TEXTURE_H diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c index d43fe266..4d6a2254 100644 --- a/utils/mdldec/utils.c +++ b/utils/mdldec/utils.c @@ -21,6 +21,7 @@ GNU General Public License for more details. #include "xash3d_types.h" #include "port.h" #include "crtlib.h" +#include "settings.h" #include "utils.h" /* @@ -39,7 +40,6 @@ qboolean MakeDirectory( const char *path ) // with FS_CreatePath #if XASH_WIN32 DWORD dwFlags = GetFileAttributes( path ); - return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); #else struct stat buf; @@ -54,6 +54,64 @@ qboolean MakeDirectory( const char *path ) return true; } +/* +============ +MakeFullPath +============ +*/ +qboolean MakeFullPath( const char *path ) +{ + char *p = (char *)path, tmp; + + if( *p == '/' ) + p++; + + for( ; *p; ) + { + p = Q_strpbrk( p, "/\\" ); + + if( p ) + { + tmp = *p; + *p = '\0'; + } + + if( !MakeDirectory( path )) + { + LogPrintf( "ERROR: Couldn't create directory %s.", path ); + return false; + } + + if( !p ) + break; + + *p++ = tmp; + } + + return true; +} + +/* +============ +ExtractFileName +============ +*/ +void ExtractFileName( char *name, size_t size ) +{ + char tmp[MAX_SYSPATH]; + + if( !( name && *name ) || size <= 0 ) + return; + + name[size - 1] = '\0'; + + if( Q_strpbrk( name, "/\\" )) + { + Q_strncpy( tmp, COM_FileWithoutPath( name ), sizeof( tmp )); + Q_strncpy( name, tmp, size ); + } +} + /* ============ GetFileSize @@ -75,27 +133,63 @@ off_t GetSizeOfFile( FILE *fp ) LoadFile ============ */ -byte *LoadFile( const char *filename ) +byte *LoadFile( const char *filename, off_t *size ) { FILE *fp; byte *buf; - off_t size; fp = fopen( filename, "rb" ); if( !fp ) return NULL; - size = GetSizeOfFile( fp ); + *size = GetSizeOfFile( fp ); - buf = malloc( size ); + buf = malloc( *size ); if( !buf ) return NULL; - fread( buf, size, 1, fp ); + fread( buf, *size, 1, fp ); fclose( fp ); return buf; } +/* +============ +LogPutS +============ +*/ +void LogPutS( const char *str ) +{ + if( ( globalsettings & SETTINGS_NOLOGS )) + return; + + if( Q_strncmp( str, "ERROR:", sizeof( "ERROR:" ) - 1 )) + puts( str ); + else fprintf( stderr, "%s\n", str ); +} + +/* +============ +LogPrintf +============ +*/ +void LogPrintf( const char *szFmt, ... ) +{ + va_list args; + static char buffer[2048]; + + if( ( globalsettings & SETTINGS_NOLOGS )) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + + if( Q_strncmp( buffer, "ERROR:", sizeof( "ERROR:" ) - 1 )) + puts( buffer ); + else fprintf( stderr, "%s\n", buffer ); +} + diff --git a/utils/mdldec/utils.h b/utils/mdldec/utils.h index 08d07202..812081a1 100644 --- a/utils/mdldec/utils.h +++ b/utils/mdldec/utils.h @@ -17,8 +17,12 @@ GNU General Public License for more details. #define UTILS_H qboolean MakeDirectory( const char *path ); +qboolean MakeFullPath( const char *path ); +void ExtractFileName( char *name, size_t size ); off_t GetSizeOfFile( FILE *fp ); -byte *LoadFile( const char *filename ); +byte *LoadFile( const char *filename, off_t *size ); +void LogPutS( const char *str ); +void LogPrintf( const char *szFmt, ... ); #endif // UTILS_H diff --git a/utils/mdldec/version.h b/utils/mdldec/version.h index 131b9be8..5bb5b337 100644 --- a/utils/mdldec/version.h +++ b/utils/mdldec/version.h @@ -2,7 +2,7 @@ #ifndef VERSION_H #define VERSION_H -#define APP_VERSION "1.1" +#define APP_VERSION "1.2.1" #endif // VERSION_H diff --git a/utils/mdldec/wscript b/utils/mdldec/wscript index 72b310d0..790cc61a 100644 --- a/utils/mdldec/wscript +++ b/utils/mdldec/wscript @@ -7,7 +7,7 @@ def options(opt): grp = opt.get_option_group('Utilities options') grp.add_option('--disable-utils-mdldec', action = 'store_true', dest = 'DISABLE_UTILS_MDLDEC', default = False, - help = 'disable studio model decompiler utility [default: %default]') + help = 'disable studio model decompiler utility [default: %(default)s]') def configure(conf): conf.env.DISABLE_UTILS_MDLDEC = conf.options.DISABLE_UTILS_MDLDEC @@ -16,11 +16,10 @@ def build(bld): if bld.env.DISABLE_UTILS_MDLDEC: return - bld(source = bld.path.ant_glob('*.c'), + bld.program(source = bld.path.ant_glob('*.c'), target = 'mdldec', - features = 'c cprogram', includes = '.', - use = 'engine_includes public M', + use = 'engine_includes public M werror', install_path = bld.env.BINDIR, subsystem = bld.env.CONSOLE_SUBSYSTEM ) diff --git a/utils/run-fuzzer/wscript b/utils/run-fuzzer/wscript index 11174c8a..4301aa67 100644 --- a/utils/run-fuzzer/wscript +++ b/utils/run-fuzzer/wscript @@ -6,7 +6,7 @@ def options(opt): pass def configure(conf): - if conf.options.BUILD_TYPE != 'sanitize': + if conf.options.BUILD_TYPE not in ['sanitize', 'asan']: conf.fatal('useless without -T sanitize') if conf.env.COMPILER_CC != 'clang': @@ -18,14 +18,12 @@ def configure(conf): def add_runner_target(bld, lib, func): source = bld.path.ant_glob('*.c') includes = '.' - libs = [ 'DL' ] - bld( + bld.program( source = source, target = 'run-fuzzer-' + func, - features = 'c cprogram', includes = includes, - use = libs, + use = 'DL werror', defines = ['FUNC="Fuzz_' + func + '"', 'LIB="' + lib + '"'], install_path = bld.env.BINDIR, subsystem = bld.env.CONSOLE_SUBSYSTEM @@ -33,8 +31,10 @@ def add_runner_target(bld, lib, func): def build(bld): add_runner_target(bld, 'libxash.so', 'Sound_LoadMPG') + add_runner_target(bld, 'libxash.so', 'Sound_ParseID3Tag') add_runner_target(bld, 'libxash.so', 'Sound_LoadWAV') add_runner_target(bld, 'libxash.so', 'Image_LoadBMP') add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') add_runner_target(bld, 'libxash.so', 'Image_LoadDDS') add_runner_target(bld, 'libxash.so', 'Image_LoadTGA') + add_runner_target(bld, 'libxash.so', 'Mod_LoadModel') diff --git a/utils/xar/wscript b/utils/xar/wscript new file mode 100644 index 00000000..f1c7a1dc --- /dev/null +++ b/utils/xar/wscript @@ -0,0 +1,19 @@ +#! /usr/bin/env python +# encoding: utf-8 +# a1batross, mittorn, 2018 + +def options(opt): + pass + +def configure(conf): + pass + +def build(bld): + bld.program(source = bld.path.ant_glob('*.c'), + target = 'xar', + includes = '.', + use = 'public filesystem_includes werror', + rpath = bld.env.DEFAULT_RPATH, + install_path = bld.env.BINDIR, + subsystem = bld.env.CONSOLE_SUBSYSTEM + ) diff --git a/utils/xar/xar.c b/utils/xar/xar.c new file mode 100644 index 00000000..cbbf9875 --- /dev/null +++ b/utils/xar/xar.c @@ -0,0 +1,203 @@ +/* +xar.c -- Xash ARchives (XAR) +Copyright (C) 2023 Alibek Omarov + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "port.h" +#include "build.h" +#include +#include +#include +#include +#include "filesystem.h" +#if XASH_POSIX +#include +#include +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +#elif XASH_WIN32 +#include +#endif + +static void *g_hModule; +static FSAPI g_pfnGetFSAPI; +static fs_api_t g_fs; +static fs_globals_t *g_nullglobals; + +static qboolean LoadFilesystem( void ) +{ +#if 0 + string cwd; + + if( getcwd( cwd, sizeof( cwd )) == NULL ) + { + printf( "getcwd() failed: %s\n", strerror( errno )); + return false; + } +#endif + g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT ); + if( !g_hModule ) + return false; + + g_pfnGetFSAPI = (void*)GetProcAddress( g_hModule, GET_FS_API ); + if( !g_pfnGetFSAPI ) + return false; + + if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL )) + return false; + + // g_fs.InitStdio( true, cwd, ); + + return true; +} + +static void FS_CreatePath( char *path ) +{ + char *ofs, save; + + for( ofs = path + 1; *ofs; ofs++ ) + { + if( *ofs == '/' || *ofs == '\\' ) + { + // create the directory + save = *ofs; + *ofs = 0; + _mkdir( path ); + *ofs = save; + } + } +} + +static void usage( const char *arg0 ) +{ + printf( "%s: [option...] \n", arg0 ); + puts( "XAR is a simple frontend to Xash3D FWGS's filesystem_stdio library" ); + puts( "that allows interacting with archive types supported by it" ); + puts( "Options:" ); + puts( "\tx\t\teXtract the archive" ); + puts( "\tt\t\tlisT the archive" ); + // TODO: make an interface for modifying + // puts( "\tc\t\tCreate the archive" ); + // puts( "\tu\t\tUpdate the archive" ); + // puts( "\tr\t\tRemove from the archive" ); + + puts( "Extract and list options:" ); + puts( "\t-wads\tauto-mount WADs inside archives" ); + exit( 1 ); +} + +int main( int argc, char **argv ) +{ + const char *filename; + search_t *search; + char action; + int i, flags = FS_NOWRITE_PATH | FS_SKIP_ARCHIVED_WADS; + + if( argc < 3 ) + usage( argv[0] ); + + if( !LoadFilesystem()) + { + puts( "Can't load filesystem_stdio!" ); + return 2; + } + + action = argv[1][0]; + if( action != 'x' && action != 't' ) + { + printf( "Unknown action: %c\n", action ); + usage( argv[0] ); + } + + for( i = 2; i < argc - 1; i++ ) + { + if( !strcmp( argv[i], "-wads" )) + ClearBits( flags, FS_SKIP_ARCHIVED_WADS ); + else + { + printf( "Unknown option: %s\n", argv[i] ); + usage( argv[0] ); + } + } + + filename = argv[argc-1]; + + if( g_fs.MountArchive_Fullpath( filename, flags ) == NULL ) + { + printf( "Can't mount %s\n", filename ); + return 3; + } + + // suboptimal, but that's what is available with current FS API + search = g_fs.Search( "*", false, false ); + if( !search ) + { + printf( "Can't find any files in %s\n", filename ); + return 4; + } + + if( action == 't' ) + { + puts( "File list:" ); + for( i = 0; i < search->numfilenames; i++ ) + { + printf( "\t%s\n", search->filenames[i] ); + } + } + else if( action == 'x' ) + { + for( i = 0; i < search->numfilenames; i++ ) + { + struct stat st; + char *path = search->filenames[i]; + file_t *from; + char buffer[4096]; + FILE *to; + + if(( from = g_fs.Open( path, "rb", false )) == NULL ) + { + printf( "Can't open %s in archive, skipping...\n", path ); + continue; + } + + FS_CreatePath( path ); + if( stat( path, &st ) == 0 ) + { + printf( "Will not overwrite existing %s file\n", path ); + g_fs.Close( from ); + continue; + } + + if(( to = fopen( search->filenames[i], "wb" )) == NULL ) + { + printf( "fopen() failed: %s\n", strerror( errno )); + g_fs.Close( from ); + continue; + } + + printf( "Unpacking %s...\n", path ); + while( !g_fs.Eof( from )) + { + size_t len = g_fs.Read( from, buffer, sizeof( buffer )); + fwrite( buffer, 1, len, to ); + } + fclose( to ); + g_fs.Close( from ); + } + } + + free( search ); + + return 0; +} diff --git a/waf b/waf index 82ac5125..0a9b1980 100755 --- a/waf +++ b/waf @@ -1,7 +1,7 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # encoding: latin-1 # Thomas Nagy, 2005-2018 -# +# SPDX-License-Identifier: BSD-3-Clause """ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect -VERSION="2.0.24" -REVISION="99462435aaeec331fa35f40b2179472b" -GIT="c140c3f538c4a21f3d88bab9403b42c696759dcb" +VERSION="2.1.6" +REVISION="d84a728949459c912824e2e01d861c89" +GIT="f233bf2c8d1a89b06541a9be576a8064d0b6af38" INSTALL='' -C1='#5' -C2='#/' -C3='#+' +C1='#>' +C2='#=' +C3='#6' cwd = os.getcwd() join = os.path.join @@ -98,7 +98,11 @@ def unpack_wafdir(dir, src): err("Waf cannot be unpacked, check that bzip2 support is present") try: - for x in t: t.extract(x) + for x in t: + if hasattr(tarfile, 'data_filter'): + t.extract(x, filter='data') + else: + t.extract(x) finally: t.close() @@ -164,12 +168,11 @@ sys.path.insert(0, wafdir) if __name__ == '__main__': from waflib import Context - Context.WAFNAME='waifu' - Context.WAIFUVERSION='1.1.0' + Context.WAIFUVERSION='1.3.0' sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts', 'waifulib')) from waflib import Scripting Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SYoY?&W$m~V#+0c>z@#+#+#+#+#+#+#+#+#+#+#+#+#+#+P#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ᄉ[Ju{޷}wI^w(wݲ|^ûx-r)6vlVƝwYjޗn`Zey}9yʶk;X=<{wnԳuu=`ͧ{M׸wAT|}66oW޷Tt4wmh/[I(KqvNEIEPA^үwqw>#+b#+#5w[ڻ4ʢ(fR:ji;ˊ)UbmRe#/x7RӸݕ.R2(:VT(H#+I{dVTTIܛ}#/}im7ݺTg -r8#+#+#+#+#+>@>x!][#+*%$],Pփ\rth5]"v#5n9GZPu\@#5#/;aR#5{*@#+P#+@ NI#5@׾ϼ"lwvDn@64 Pm2b |>VtToyKgohs}nͽ竷o=/oC}kۜsv6޷i>Ӿumz}|;nƝܓ|#5=p$3WWF JP.Mۋڴwawv=Ol'-7_Ǡ:w07ڪm>Q_b{/{mct;yM̯uפ^u>>Zw}e6픝﷾wۓo@;k}{nڽs˛O'#5ּqoYVtmek=si[kwOvMw1qnl+ju}ϾUܧo޹QfOuøv-tܰwNk[csjQݮs==:k;75nu{zؾ/kG ٻl[k.\Yݱ>:zۚrC͸Dxw9L!׵){{@.`P^ׇKW;:|.>"T>VZ#+VeT@-R=#5Bm@[]k6]v7rEPҁ{kq ݷmWj۰#moyz#5JURs$ AGDs;eϻo#+tP#+ϻ#+#+]9o]͢sNk7Sly=#55רcdtt#+ե{zLz\skw#/\[Eې n溋EvyǀHɬRνY^#5iv:#/(}7U磽qC'Q풄=l@vsWtۧsg6ã{k[p`ç{f)oqw1m:{_Y=#/5I#+(%+Bom:zO{^{L#+Pf%V,]6#+q}woi#+#+(#5'6KX,#+OMġSWf7@qy$8}o2xLˮZ9۵ns@[;`r=/p#+'l(l=۶#+^]:Q#5js#+5]rۭv6UM(#+V9޻=#+@P#+#+#+vQvr}۝ۗ{})]Uҁ@[jK]]8l@ wJ4]briJow#+zhR#+[mR#+6TW`{sew;M˵ڷ#w\#5@ks5ۙRI#+&uL)''(`הH>yu4r|~o?w¯?}]/>Yt)w7+4U$/ѭM]gГF'OhkI< M}3PqOeK2v[$<;'O9Vv !@oslP/+~Lo2Y4RFǙE .EDZV&mt?Ѫc^ !)EEU|aDmؔ3nӠ'3oF#5@`a1o9ą@J>to3@y#Qӫ-߃i>I՘4 4)NűSm<0.\rK w8a̦$DGY5'8tAZC$'J%P((kT'Kb,U#/=%ԓ{p4'8:^4D,}䝓ZBho,?4d9Fah^O$=l~֎lr+m%#Ty5B2V~7M\LQ׹'u[yTS*uぺZEVس8) p-&)hTa.Y'|(VF9kl6qpFS$ЭV93skpu\Ζqaf66f]aI-9߱\ ͉ 3UJl#/1?Bd>7gޝܱjE+*E>Q hg;Gɘ#/Z#4_eFm\XpΕt#׏Ǐi_ S.fWI{N{|d?}A>T?^'eBP/z#*E~с#/>ҋفeH MCԑLs1[rO˜;>F( [Lw0r@oUuͭ2VaXT|p{Z1IEwpxAhOMjPRlZC1OwvèwmP䆈^(X~(0 I}i#5,Y;DC1zQAu_UehHBXӞ,8%-=*vBqЪv*#5RJ`,"2d" QAڊ9/ʩ"$hUU3?0ݘ(y)%⃭֏nlGGk)`iT2nl khq8qw'b=3PJiqd=T6bw͎*'|I-}M !mI)D;B<*R` e]xȲ(`ϱ%8BuR+ILq%Rp#.KYPPa#+Q<٦8ie Y:aN;kÈԇac%;9l a:XN6ʴ"_Lh~Xئ@y#5e."#5#5lnj>Ɋss㿲pO#+HJP+ 9ۈ 3ZkC U\*U'2f,RyAh#/Y2IQ;WhO7S_Q:өQJ{Cj{o~Mɍ#5Qd{9g%Fo6yQ9nߋzZ\(W%J-֢5Xۭ|ko3Mcl,IЌ;'4*G90f@:-1(Hlu=z}b,s6\Lq֩sJƈƷ6)ͭ-"tk3 Sm (Pz7PAeݜoׄwG1b40re* y`\gv|>%ї*Xp:5cu q u(><\HzYBS%ຢ!qK)&&)&ďwBRG/]ѨY#/a~Zq#5$_hgIzI8hOY8jV>^gWQYxgSR%<̔M!"ŞLF'͢ajGwURcx w=_T49wEJ|rPod&8PaE p ]cQT)8--~<$E2bCpc#Y8H/[Q&v Ԛ^Rzs{x~,_(Ug{2Z1֛èg:YҺqA#/$0g1 U^Qھ#/Uy!Q䃫6c#/4,IPbPV"zzrIoδZNl7}f\8ჴY\|q[6oync]xRE :e2^Sǐubw>&>*랎:ᄍŒĎh8íM%[Xg.5SJJoaeF3vM3YrE}t$}W0ken#5PX(W=g zڠ (8˖9q#5/i1F(#:BSL7?ZŸgNY8O޻Y'O;YH~n?ۦl,eȵsxVn3 n^\$H؃2}TnY7YNP̎_@ҡ&l|Oϙ&ͽf5U6.LJup&lKkWuNQw];> #z*/[j?-ښ#5V&-%qo73FWI "D:AȂiBt9iyN-⿁񰵭ub-#5CWTa#/L ̃sU`'>SVsxY5f-kzLppd%;_kpqp72\C>;A5;'jx02^p\*=qcTW*X67bka >Ǻ?]IGef`t$,/ cq6O'fu8>N=olGҠ)HQfUU)!D N%s6B=#5m>fW?fKD99/Vfe/HRp6ʕ!J(R^񬠆}~f'T fBBh@Մߧp׮qcec<o6x1tfPÚ6m-TָO.ֻ!CGBnCjH̥r`/7) "(W,Zf#/KJl뿟!S޵"yƔ8>}"ZRN#/k֩cWT#ofKVO+[DDgjMZIis۱TGRd4W6 TpM=f&)x:)j2>\n'w?"vĢC˭yFÎ8%J_Pe't5ٶV  (A/7NIVl#5Ïn1D I[ۡ@QRJ'?6Mg\09Ol|!nN>'20#//6*@PtڔR1~ PO꾟= PIE3T"0r9ݒj#/8L#/:M\סY3@pnuNe^ǔOt>3.!,˩ 貄QT0$#/MzY 10lE-L+ͼ`ʥ 9H(ӱ|g˄K{u#5)HVZ`H*ͻ{M~}M>O\&P#/td@D%"y2$֡P+CpH(d0vO'|wNMМ[#5@4fPT{#+δ!cGF%"XY>hhf1Vf2һ6ja}^LJ*4Tog (R@(H$d>碑$_ж|n%=x^.{I,чu*|sw4[kW~Br\@_LK~IJ"{řNw&(/,9sR/_}KZ#5,iX+!g0מs(Ub;fgʳv]f^R>  HԝwKb~Ux}b$Hi3F#/hfLE4yE {jӇazg^%3i*:d,7Q:I:ir|j՜*ZK㵿_L>W"߲r:XUX+\9.Xjtp}#/aiЄ=Ca˼+_õ!,)CFA^=#5329~s郞#C߫^֢ɵH8#5݃tPИ$H{4"OI"2T9큵Ñv})Ӎ(߇Sv{;(f 0ξm)ֵaJZ_/2@{OBΑ}-BEĘ*gO0(D*`VN7-9bT5#5-Z@ɜ`"kGyKa,}~Qq+8bM{M(*tڥbv 3qCRF`E{ t5rW_> .`WOb)(Ywvo=#wg&VsU#/|k؆u4л!sqe5EkQ׿aS0,'W_րxwtwji|ڇc;Q=Nt4j:^+ohkmܡ8M(՞PZ6k#+Q*AhٔSS#p4YQn0tY.-ׅoHWeˡAG8YZ蔇ÏN)ۉ)dMSѩ=*:Ԭ 4awKnUR#/pg}3!2CHkFQ#/tCe)Jgs#/w9#+*j/8?g|99N<9),q߅؞m7{?'7w?}mU)vqW){,D[yIL|\d/wZGbl֕E{{#5_ Wi,?Fm8um{T%EI1i$TK⫑H޸f#5(\r/n&#Qx@9+5Rcu4Sqjyҩ:sq 'NJo(Fo$֓lM=v|흱zO`ldުg#+ŏٍVhI믱-H`J>eֳ 26()mʹu Tr)q B|#ܷ ;\bhveۑ7V_HEŁ#r}i2]:t;Nʖa Bs¼]%1Qr#ip (tT F.d!_5"ժt oDLKyz65V)=&ZS1ȉ!6D dF>6Hq~/|iģ;>Ӵ8aD=0Y5,XŏUA#5\\W?~Z^>p{Ω-#/CTvW!G#5OWgOa;3o 2d<^0bokxm::Lmw+BJ#5Υ u#<~L#/NbD`Z>&ݾ֙qA> U}T?GXhodixӓWm[3u#/5zs{oDbGӋ],kw|ơ'^~7G+>]/(SYp Yg|":Z)<k0oSHMpݸ庪+/m>~4Sv;(<^|<WVqs/]QnR-OTd7/Z07t,mwȠ\V{2:2>ls乍8 $>[Ks0]8jZ;2C+T(H"Ҁ`Q7lcl-xh9g=[9<#8xcbeWaJZ=HͱUYP]N{R+~tJh-(Ik/H'ĉĒ^#/daǑfz#5L4ybJ}"#/Z}x%SZ(\ꢵ(ܻ0F\5`h#5B(o%Kˤq}eSa~!Ey^,]BmKY}śpg+Vue/]덈*w/~#+z@GNr) Ѐp|+vbT1;qͼ >?vq݃r#5Js|'O?աɿ>\ɂrX}fj7US~!GN#+;Bdd<8*#/gpSp?,_x`b:HKx%4#8M(jP"¶拗OYPrHaRE#+B-F7k~L+T4w;/qQ:|}P#+|K#釘OO2)"AFDѾwF'ȒUpQ2XFQQR=nzfrϳA JEgg#+#5GdȞ-?~[T#TL8M> E9sjaSAyC5(s'we#?]u`U@.:GpK.! 넡#5i,T(r*ɣxs!3=SI i K,"Rd3j jL+;9G+m)i3jSriǷ>:\c#5܏. }Gh?$al^|>#/#+P#5d1gNE.^g'I ^L"DC {cRZiZr)OD#/eV2 0,d_$iM_^rH.Y|.[HS$)-xAywF+袊`8#/+(?̯6h0|]'w;w(7b_\xQ""7ylv*~Uqşip׷k3_g၎iEчYg[5ͣ=>|VO?I V ;}'ǟzL_c{kHB#+bx=?|#/8~jLHV EaD?5xfH;`Lc~26R<эɀC(Xp]l*XXb]4$ZA<&"T]/Y;>#/״&@}y8= r&M罓C YԐn4sX{7Qz ZCKחmWz]٘3EԓQ'd͉8t#/gASϣ)nWO8"\(W!ωO8zdpSfN#/JKo0t@U{OkgqTbo^i5?=E\~'XdJgFً?Wh'U+zvn>Q #* ,VV{ {'5j$/$&|oW=]/㏢Xu TC%AdB:o{eqt#+L̓#+FTJ TUo*Eh|m9ߏ,O\}xr#+aKaa<'_.4=i~#+ٷ~ QŻ8[|C\p_߅R![J`=\燅c@8C@P4ߔa!ÌcW׋z0a;,D)>p+diQ0@Pp@ڬ&C&pX#5Ɛ8yheG-0p8NfDhC$ g} A޻ {9ظ G+>PMyHC[ZA%(GY?֣?\[B}Σ W Q]0Qxˍ\Ό3enOq|`zkx8U:"#57Jg0htʥ6z]H|6c7V\#+nW?:gkQ#`#Ms &ދlzjShA۶uOsqoTWdz13%,@ݢH1fRL=)MTkWд#j KENqhoxsk|͋D$G<{ eHHI,`eh7~k8;X s'bΣB3-cK6#Cf#B!TϹk;QDRPFj&b#Ⱦ +NwhM6䡖֗!  74c1$)#/C7oMBD10*+' a`% #/#/EIBfrnF b* ^ꗉm&as1&.iIk>az9R,!$UښԞ--Z8iRz,gbXXL9.}*8IHZhwA7tfݖ(G:80v3Ӑ#/,>`7/#5z!F<"hȳ.$c7^Ntc 'ieiFyI03 R0o9E}\ #+~GPrl?g5xPJPn8w<Ǥǒ>a'۽z9X @F4aWSAl+Dpy]LȎÎʎ#/Y}TGܴԚ"1]<`asñ1.X+}O/»֭7tmhv7 2oGO_ ^dw֟y}QGw |}%>XمyJZj#+ drYOZ;vL]#/^{^'|IBxA4[7eBI:k}H١ֲ`vMc;-g_~ay?mNrc<~˼}tQ#550orũ(SGYÕĎ#WkF~~2c|l/r]|FuAxEvIdԛ\KŠ[uQbŊsI/ V%\=͘({#/8\5IIy {ЁR<*OC#/ #-:@2r)@򁔞lhF >o~MHej]-H;"U(m_0QhS#/lfCv!%ff0fbw='#-DȀ$Zp]W"#5ڢJ{$Ʉ9#/5DL2@]#4Rfj{{S #+?RMS[/"}) >)ayU*Owvr5TvJG3mP:T?e됈jfKP_Zl*9_wq_ 1($͠Tn~Ł(CC7-7io x!;>3~rTMx#i?N?'I4'wՉL2Ky%2Rl*;N(!ƩމwQoZ -7L]UHiw3#8bLҹXݏ^4tQ3EʛX 9>YxYx|>¡"ቯar?&J3#+m~I?*lV4?x^7pL=yLfQے(PF(/Oa޴{Jhy̩w>')/wHsI*a676/G0/Cܙtl㷆cu;^EIBA#+m[qYx#5-L8V媁x$x7[-uUK˧g5缤E,8 L9 W`2xfg֛+@Ff#+Ix>nM稠e_$зG('h*06ٳ!DyOFH+l2&[g5H8kϯ:(s!(i fTmx!Tc۪;j.J#ƾoܡʙrPq`%/rݘύ/P|5W>#[*{;F7]u+i}:kˆ:D#/kXis(㦍jexJXk4J6"HWMHi˕IH΢]q ny#`+bŝЋֽ4z撇<,%#5@QQUNH;B3=M%Ϡ@ïsgF./sqA]Ey+Wo #+a#5RZG 0y\( 燑X-Ү0:$p2j>Н(.wB/rAǮz'"'GU/&FoC^-j}ڼSol̿}AԶ#wt.W,m޳?/[$%xN^)Ĺկe. ۮ٣Yr3nf>'ȲNXp%-;L7KM(*T!7{_^E#5A|xpj6-I)5X@o0Cyxhg"<  ;;9׵j₩Sml❾#/A$鼩fkQQGSs')aJYQx'B5 tJ檋W\t pwtnߏC-湑@HF-I08w#56bSA.S<*"(Pw#=bb.KPO G쨶7uDM @ׁt1^r}'"ÿ-l`"#/Dxpvt᳓<>3 AZohh~+kzgA6y(jO版iN 8uVZ΢9lJz0Wv~1{au:60;cG哴c#+>l/{[ToRdF$|WZNcG|s#.5٧4ܞqܫu3k\E`NBd.}C=L:\ \~T]*9n"b!{uF\] 3}c7ZsS<}4@ds]s": GrZo68ǯ^NW8aGrH5ҿwSs @Xjmp}h\*j0w"#=7+`Hb+r q~g`.)^fNqimnԟ/|W;>")8EףA-US:#5:}̈́d>-4t$m#/N)׏l<56ǷGH8`d=.bImX"::dm촢,k4S٠ݭe L"^\aq1]K&-zK޲}iT̻2_;64!bWPw$;-4xtD?Җ/[)+8#/'9AX繞i-2(H%0qllk#5F.h&oCit@o-A0y㎳F6PDN +`b21cj͋0ZiUv,UdY -#51{,wdLlE,@ʖHx@0Ajx/}~_(f#So'QеPc!o/wIċ;' +>yt_RFGL9&[<1ʉDՙxz=.9ppT`\kI~p tE Y$גFL-ʏ#pQrk9aeg { ơ!#/f-bӟRQзI쐵%t q#5mGH wR;dK0pIV~ 8cc.\*k@3XTxN 0 xr6#/7%ؖh\P@g4*1APÞ>08`.b[Ne>P>?܇_R#5Du/T,9!#5n7gv!`0! CyY}7}\ ?DGBI$!Ov?}瓼!7>,30[aFDž$$2ޠ&#/)X2YP(.5n0x <4w8M #+U*+Fʶ]2j/pϷs߃k/h9E*i2ZSd࣢yzq"A Q0_#*uXaM\@c &"Eѣcx답9ʖC-ۃ ȁ+ǀ{נr ÎϽ R:x" 8r?u㮝;s2""p"*eԀ#1EgpC>'9zgq$QI0)X10cq0ȲMz{kǷ4?qM3۠tAk [,9֮=G^287#Лjyg2,#5#5hr#nD!'Iz Fyy.Q9)( W#5QY;UE2S{<3larBf=n)KgGcT]&%˄5O?6ۘs5a6VHǜ#VBPL,v:rUèї"%iYjia"Od-%S@m%}ZD &`{xC '$^b-L)܌i.39o#+A*(hOl#/Zmq!fQ3-fft˓?Ӻx?Y[iL++lє*]*#_@d/\Ofԯ1/}, 4XuXu(ɥ҅K-y#5wmwC߇|!GO9aؠnwvߦdڵ+Q*e^bPU3bUVW6qzWMShkBow g)\'B#s5ϡ[XNK9)0as+sH8;ͦĽhLS`6 z#/6W5#5e[55s|%nkl4fno]=qmIawM8lKrOr}ylT6Y'2GW˜msy L]?AحCWW5I*#c_Iߒva͹e/.gg7Jꍣ_ۦ;=hufF%w̸VUjK#+2E+9R=/6#O`u?pP`[^&*,52.5$SHwͬ0`ָZr"WGzZ#/E6kP:23ύׯ(z5ܝ8ÇdփĶ!ˊgar89A>$Y`n-]seӺQ+yuQ7*9-yuCBݻ#/zb|z8C$#/!LnMoڈ?P[ ~4Q"dwmvG]͞F:?#/pvx(ɩr@H3Pj릝c^5. _R8HT Õp/,r/}]Bq<❀y`PEchgӵ9$-ta'Qm}ٓx}c7 WN_ 9|W#/p6r}9 G)(-E[\ m~ч=xBSxx/Nzq.>W엹Ta#+}]ig"1a#5bc\.B_pcm#+"aQx?;Y\ӀJe[H={`76_28xs."$(7tf,JWCkT`_l, ))T&#/KoйT14;l$0F&(zgAe6B񍃷+̤|PAkj*٪5YNeMuxOʎi7m[;W f1&pNw7e0{Y /a>x5do <)UPD7HEƞ&#5Dnmj3qםP"*o=mf>!rf#X0CdOt %նItIRl'|T*ֲf' QBN_3kH(ډ{Axlghp榓:&!=nbCBqo`n'd]}y^ڋsh/C+Snę]'8Xu*>8Y)dLa#l hL6X9Kt+#5>blGo#5@DJDݻ[*i R#+ ׵p..e"kBJj>A"bcu53ʦMwf6-CSZf4i;T^zv&#_̌f8x Z{f?,>]9{; k^ FM LqN&~ln+.ը7>8 }X1 &K+Nݺf V be#+xhHI%|L²t &oQ#/e_)Ֆ=ǡ8h\JAJ;$_;e]{keX "A+*%YHQ~G$2qN6P󅨥pU{Zn1ڢBqQ@ NX_uW n/Bv$;}Ǧ>rkyq`"g\CV<\/C=Bd&%B$ #vcji}jYW:W FL>x1G;MT-Zzlb#+v4R=!Lf.'|ܛpCŤ6v113J9[81ik]}{FQL#/Ud) y#5n-gyu Ry_Vӎ'I۷ȻLrꅋf~ƍ/K!K`BF ue, VWǭ 8rKXp2lC˵nIvHǂ-* 6k3nPFL𛥊BsB?w*htp>y_Bc"RW[E]rz\`^ky/Ud)Sӏ4gYQ߫#/"*1yc=s,9?y^G{##/ B#5o+(SyRGyՍgJ#5l*"'|j^K[2St1wO6n*^:2Cs2il)}CU򯏍LU,jv^* 9oU zLĝ|۹X¯ pB; wlQ1&ft`y~F];֟5G=#+r+^`Q`(S'I֙hUumӎIm0pG^wPQZ /wl@ $+%[#5m~8dH!_݅hx+#6D7׿H M m#5lSxw>gJЍrxea=DtPh.:|?-YsЮOva#/JGn݊-^v̍䢷봕w1ƐOF~c 4AU c#+D+:i)f(Py ÈB7f)G>3߫C>oH|B/s.gL+/*"`Ԯ5^X0nѢl4eVq\{YZ#561 8=4C3S C~5#+lh9//~kۺu<$LcAQUX.!o*}`>|$-c/Iv9Sol_5ֵ@-%A18a01c,#/|Q a#+'Ѯ1J\BpL_I$ k㏇6?N3{/޲2(fx'wG99}0`]e99r䍼8fs!3$#+R!&nLW|Kko) <˃lF#5CV%!pw4Eײ%>Hh^#5^@ڰeS;.dgOn}eGJSns*]RN>ֵ!r}i>xFՅnݲvwVml~lܛoG\gު$ûZhĬraJU%Jk9B@ W¸ #5xr n;X#/h>*TPv]j)1PeCܮF'$}gI[bK~!կK8,kS|==[y#/1PVVu'YI+VϕPI{nWů0P  &"l1r2)U?E0Y4NqeK&.T3Q7[]VI@`;3[ mKE -)KG>Dp`E4UfOUű_=طslCJ!zmt66r[^67ѵ5|75# Uq'oUؼ-AsDKlEưM_ Kig)lǑCIÚwm@~9+Vgey|h>"ur>o%q>C1nB^=Im-^7꩹G!Q;Kl-x}f1=+\3o0{|#+ԨG}(Mq[jT@x$BSu+pE'f*1_nbcvub=Gkg٠vD8sC#/DD|,A."qߜ=˼w"/#5U33 sS].dOuS7׌9w$0Z*09^.[t+]te~RY`oS=5Kӕ *0c{$P.NZݪbyZGuM[0@TZrۿ2c?gW1ߎmH,/f1-IHQYm7[Y-f s#1hB#/~gƑAed{=dVO΁c'1l:!XʃhB"pyFLYfvJ>@c6}Tf=pǟK|_:ht>Ly* O3]փ5f3^n)tђlhh/(+|`¬@D ':!h2#5MszyцS#/+ f~OtU1l#pk$kjAk*oG Ź\WRj7PsQM#/Xiip=ӝŚrj#/ A#/Hf ^x}81s N*~GldЃ>. @:#5p/}=P|#8ِrr<Wwf\2ŠQGl=g3nѱ,^*7mrU>8}&s#5:$"Ae#/v3t_^1 °aߦ9>N1BBU &rypކƖZ /2 chvپVpn׏2F>YeEoR/GiSyE'?bܶ<P&Bժ"B렶 ;㋵3<&+,u$ 5=z..;DAhˊ @S"nAAb`3 G7\cDUYH)C0|5)NX*r:G!pS O_%׎BTꙫUt9ֲA!RC JͲ2UuRP Es<"qlT=[PRW+Tqx6HP#5}- HAŠғ!!zftɲ6εn@0*OZ*e&:v'R2J mL$sGH.qx=#+DPks@T j$t: ACc,B4{f2_/+MO߾'CCY `(ܷ4(\+YxλӪ@@-}޽G'y荒X+g3}⹶HDBgR Kj9& ˳JNba:a(p9=B÷KB(v,_K1HߝZ·$DxHB[MD'{JGtM/oP*c܃Ռbq:IFb.<5vx`)"]\W!$)"ۿ|_}.%Q@~#e\o!{"YyxsSYQ;9piS*Yh#56"#Dm5#/AqBt׀S{[ [\_'.2F!Cenm]A3BFpՖ6er`;)jC:g'CjD{;m`jvnY3B}-\*ҜwP~#/6l68E4/gj$U!/gDw<=];>WoX񯌳+F0PV 6Ow&&(ͭ lCoSGPO##5#/S^L;(;(# g!2-M#+4k{2X4Bj05q zQݙ>#/ygFjI3gfq3Y-Tjd۹V0OnԍNCy<=='pgpxaWS sۆDn#.l`A0D#3>e;&_ eG(Sfv80lջygBۼc:=5A>~Є!/#5#5&uv QN. {^woCʓH2-N4Hȓmakq-+#5QܘnKs[#/@%ҵU ##/5lfP ~66E=yo(ǧmAG#/9BlLܮMmJ#+mJ͘$1#YjLNfRAVf x3ZR#}Ym3v#+]wQ7t#k+٠n5+L$ w;}-r|M'ģd8@o; 0G*=K{M:Y]!2GEWư ۋgא#55hXȨNXF4jՓ9H.St1w[ vjwā,w7sc 0v` 0]]_aC/Z1GM+(>VO۟"#/} pS3c ϭoL^]r8WqB6tZoBWA}`޶P+&~Sُ=x$#5(#5)QR_})J6otoҶ{lշ%7A?^<]A.Nj[6QRoVu_  %֤LK VL+/m*r;#5͗)V#T\f#5\o0oZWQ\wztrm9#L2-:öpxGE=@g熪զ&N#5n{d1^iAdipTVh&]EE6XV:> Xȭd xs!XԶ fsetO>ԋQiX&Dx:tVyvYXR:A!i׿rЇhDX,Rn]~nu5C\fd7 T-Bj#+m+/0o0raI{^;&79苵+$l1!S#+r4$8NKo0ӿ}񒇛,\MF[܏PU6JboŰJ}#/\vP4.$}m}Z==u*#ws{.t0RJxlO`'#<+2Ǿ5YJ "M:W#bl뭣ibF0OjOIKA)ankxp7%#/D#3hxam(v$1/Q*[9e}W+zQ >N)!U4;m\^/ ڋ6٧?Ǩy;=Ca#/ixo=:פ3ґ8UB>^!ř6oYOqݹfӅD@LT;Ֆ2iU$SO(>[[)> 5U#/\R5)8Ym+) #5PMˢuwֳf^VT#+;!,@@5i>RDkJXѫ|C~ #\ &T"6e"&S(}E!S13[;鹧6AB0aC&abQM_Q_w)O9~U!鞢:C^ X1!y&ѧ^X؈Ƨe)iq&+gK|)6{ nB(%|^t2{Gޮ<M}m bG*߷KX:|7D9gXtsMw˷!v P^+W$6//~.񡶓ߨkNg}@z>IY #/ÕovX-X=,7q=M:R=-]B;\\_;$]'S57_yxn OA`#/qYFt$5#5)NmA{|Gx[޷@6^NgCmnFLtm>}@ѱۥຏ|66|jFAy2>0HV<ȃ!㋔+s|h;$`yѤzn['d=v7#5/׬fw%8&S42r0)fI:h S[u_7ås>{0l~Qd1voyJboY;sgEߩͦ/al6;I(:7[$>V!7WiE@2%~%Flij#/ck'p8bCeTq҃*u6+tgna\jEp9x9?9F}&+އ,FcYXc_7xK!Qr䃷~c0hJ{Cd=ݷ(/[S{Ocu#+|ǩet$m1ɣ^Gze(^#+bLseeo0s #5 3&ϓyw#/2&Fy"زa )k4`K10 vUpw^SvsR6ԅ&q zǴi',j7Vc*ZԢp 2:3W9 W")T2pRӨS楘{֍i/Cѐqiv<:f>#+un\s|p]U#+RU ~N ߌM`>HM?ơ6*1H싣8;%T0o3 ܂qgkb("\е{Hq}Cmcfntz~$/!?[RKav+1mz()0Fm=KK6o]6PLd}s[Kew[\ةQ0DAb|q!#/H%׭*@cmY[cU6|e=bN66jb;+2Txop<A)oUW$v(&i^<OKjm̗]GDNp/!0Ve#5J9‡mjQp襎RGo:N#5$<8 ڰΑ{c3P]!RD%#+]6۲(:k?m6Ng:9:orzGS^.&GzP<#tvf\<]7pcЛ}&, uOE{~&voFs)*@dȯ/+ }h E)Vmݷ;Oyriͧx1cG1g5th֠q,!x=/go]}WXoX=Gup)Dk>fp::Yޑns xsnnRbO[*ZJ0+F /|#Hh/Dv_auՇJWAbA!xY)N##+&PhB@z}.#+*?BKR;1`B/nu|n$r0LE^F2)i9#5jBӛ tF|.*m1TFǍ`|d)aLPIKØD/$]L#xxCTlZxY,OU+->ZKPZLWR 8$ ԗ8#5^xb__LEUN[;YWQ:<8ɧF_J<-Suv56l5O2AWS G73#+w߳P~|} IӌeXVTaA`EE)^zDIS}MlR (#/ 3cP#/<6mGyH=(ZF4oY U3r>iUhtK 2X^s_o]b61h&Ky6S5 ɟi1=_͝XޟIng7H*Y;n=}7 >nk:スxhӁH`jR/z08 B-0)X!!$z]54PCEUTE)KEDYĆ!К+1 ^s(|899=5$FJjm0le^D&8Ig#+0%JfXSf[#5ksBT4k#/ߊȣgnǶ;#/Iǧ+vݩ%39O!uilOI3\*wdm‚)2hoY{޻5B `}03QHb\傑bY#+>E{JϦ*|2LXnr>gN09;AyS|zNtY`5z/=K#5*U83 G0@83[D0:piQr|8X=4m6OMB=oJgRjTx_)MQb,hZ%o(bw[ܛ)(x(RD󝮝vciM6lZ4\1Dl{nw{[+zn';p~mh:?R]Gc׳s>o8*bBtc6Du]WiɽcX Mv#5#5. 9qŔ-f=4$cu!Գ%`3~`MqH{Û3Ǟ%4m3iro3r1g*EbDVo 3H}05׵\c{HZORQ-u"9@iMORtomH>.kmg璎kXZ|D:mjN .pD*U|ɗ*:|DHa1u7b H|]eg >ZT.\%+++l)%  29Pof]z+cNw;#/#L2uϝU#5ߝp(9}V3ufҦraOf'^|G+*'%~fV`zumknn#6#<+\ZZT9GlXK~Nxܾ8pP-&|ZLjS*^c"xJfr; Э C#/H֎B &5vBJ#/uP"F5>9ON;ℴ7gsӯ<]8~usXxeW5<~Ds8#/;wYNqG1#lh}j&Q#5ǝ|jY&sF^尳:֨9U贈dP۫I0V&Se2I7t2:3Zʲ+< ’T!Rv70HNUg2]}s[EK<%qj:o~2YeV걅 um7c6T:V >_uYuoo^}uQZϳ_{\ F`ȱ\lR}1|\ϺY1"#QLuRjYt'x2&v"xLHY{FXZ:*QcC:`֭)Ԩ/F0첧ڔcb AICzb1yN:n}'hsZ;bui;/;bt*gw[D쵎8Ub㨪OvUnZ63j&WXEUH]#5=ˌZ0Q#/-#veFٞƖtW4l۹O|mU5f˧#9Œ#/bG͏⇎ɬ^WxiٚōGIαtkIf3>8UhTȕAst:4UvGm.-LN|u3G [ηv'm 9)[1fߞ¬wp6#olڐ KCfR#j}w@JK"5#564XO`#Q) "TUh!j0zfNchʡi}:Ykqh4 6KabH;1񾳎`oZjSpls"v]>Jz|5(YPJ㤎ɝbf6D&eZ+Iu{t{faIE[Ջ~*q 5bZ:.{)#5?oujZ.XtBz~u=)2明}p֮#/XjҚìjQv!AX05D(S#/ۃ{WJeK0A&U9WLvqpf=NmkeaشH%~ݫ՛\1r"; ;o^KGǰzx?m"cN,r>e|d[38kT !VA:#5#+#5(7%0~MԶYdn~OkvR("%I,sǙm+ko]&2Dl1 bU:d8*a, ,x `d#5Eh0do=^w}m;s"W/QST9NzBt,_Ug,88H>LdK2}MU:Xc;X )bI= "I|/sbtƒ)7UO0ud֒kB$C#+쑝7h(ɭ#/悝q=MǓ_5AciD6ս^JC ht7GF5W}P$Pq,6z/*p3#/ +H#Cbd![q58_62kV7ż뭴η>)KB"J*<y$1L#f;y5F %d-0e1*!.o;~KκP $zؿ#[=(DUg錵̠֭Pk~xÿaԇɰ|fS߱W DN!\ɸC'̷ظdF.tS78׾2cLъ\v^$7>Wx8L_Bv q{jJqW0.o`'9HـG*Kiu"'u~~Jon>+ ڏUmw;7F;L1R҆KXn6KչQ`TX8[HE4CP,}kdU^8\vw_ 6JHI 瀇?̇>Kxq ߨ#/GWhԵ<%_sݻj 9l@@0{HD@XUr5|JM[tnЖ#+F #5xY R%=9#m@z*SD'jx*M ڎٻ_8]Rey<ӗcuOtif_>3z]֯n祸W~q~.*ͮla{g?ءpVW;Ypm8(hU¹ɿw|{9c9B(f;|ـ健}8Boh3Ċ=ʮq7/~ă 8w_miq#/7rpU;z3MfOnτkW[).'.`W+ 1GqŴvF]chBP?oI7#/ "g_0s`b)W'%m<@SM W^%q=\'j]x_b t.HDDĬP@U*YUJyz]i\7RAdoV Jje"bR"tvO^s״T7dAH,Do406XZ*"i趧ч|p6Aۀ&3%B XP]y ތS@Zu]Ym#+d,hBF&<6{.ٟkU#aӧC\},d|KGh~* 7φQ80ۙ#5#5AAIrF!#/Ic|>_%<]]{1pGp_.G~ziU7Ъlv|g79'lK/偂0ۣ>þߡWZ;>U'9Djru* ?t)? ? O#+k?~SxzM]Z>@\!HȻyIHO>h*.#+8XI{%|ɯ-/I@#+@+_u,r(xGRgo:J&w_Th{[B̾aPu?Dygu:m#tU|xY_s%|_#/[FڍA8'2v&/&!Q'Ҋ(҃BQEC)+/HHz J:c/ ץXơyi?eW D~#+НI=~8wAp!(PPa5CgDHOGf#+#dۻs#5NR7w ⫪#j^Rw2I 9IrCOZ7Ϛ[q5w\2liati7䃤ҾdBC)?6vde RC늲/fj EA X)$)%R%(IJ"#Qy#5Aޑ3#/P{J.|.r߮]ZtL#/w[íXrƀ>o<=~ΛmyLw }1?L#+sה's+~Kt u[oU'ݿakuo/!#+GH_C&%.#/L\bRJd_:!F^#5:cT_o/#@QпZ|څvۉ4)LRpSO?{z=g6fpi0HܩuwPy哲Fv*K`aSY$_aaKM1]Z@BJG_ڴfIm@ǦQ*HHi?:%QF0hi 112 Nɀ`&6mO`A#"EAFnuQj3.F:BԾI!h&iV0qr 񗫌\+Ik!Z(1PEj=EIgG F"G{Rv.Nl5HhV46pܰUlnZъ 1wG昢/6[rS#խQ_M*ޤ9n vd[9j\8n(FQ3MBc)zs#/ʈ$5Ȥ6[X[~ *vI~_pJ!B^o/MCRLV`fӻqνٰ;5hիj O-@?,GT<|O?;2_zYx)?k>*`()jYcM`Je(HD|Ŕ#+c~Xf#+SHñc8IGv8~0y{|˄r(vQB,#/F@?R= 1EJ"(L$?T6v##0P%Sʒ%PU[mlSpcN$^8F VYw+D.?jtkFOr(x\6ܺ0$fvӄx|P@45u3 >4T;Fvӧ?ïXGa)h,{DŘ109hF?rR Z~_>!0_rbJ$O'Vp%f@cM)| X7z b=+#tTaoN_QPִ,=f_P8LCoԥ/:[~W?ح/1zb5lZS=*o1FtOG$%{R#5o[} |K}%,3>#+G.~X^M*㫗v#+9:J'r zf$9^NE?NmQ{6ݐU?Mnd<]9О&eC =fAgx#/kYcSHepp#+<r.dnA9nn-_)ÐnzgUYJnEn5\<=ڱڽx|1nh5yLg@>b-0R27v9)S2ң!U7AϘ\*\Sjn[*j+/_ߊЄM#5x<] ZUO۠o:Q(=ޥS@eS{H*=(!cb ׿u>pR:x ] 1^j ).;-6=kN$wF4q)ewȃ/pOx(0L6~Nl>Bo_SSyHjRch$s2)F*JXIVY(g0eQm#+DNB3".]w|!]yl7y_siۅͪ=.C-8slFEw'Ξk{MD=z&9@OwutH0w'9#+O9Asag=s:nz>>4>ZgcVWߝO}:G}|e?>Ixp |]\/eZ;LG9N^#/jN+]z 5zwfq\#/"wnsGgN|q 6£ϯa<4'W-߷n89~>w|yuoU;!;caZ===~>Hàr뗏xu+w~up٫hgv!x2=IMy1?g91w·U;2sW=z=ۋ#JKB哸K۟Kf\nͫ;v}.'_#5Ի.07L]ܾei\mXe8<}c×G'\|.ugVY$ÃշO¤|r\#^uѥ#/Ӑ^%D!}KFcv-Z?qoGsu'6zvݨ|0Nwo=/}W6&޾J9Ի|TGs1e#>-Op-@Z kxcW~)|?mޞ}qncp_2''Cܮ^<4t>]xl3̶6N;p#8].} |>mh#@ki~oEWu&ӭ[~#59))/$kw:0C˵e|~? 9?Ӟ?L3Di$523xj!uռ|?!~>mz%YsRW~g4Ǚ`Ǯ^t~X~kh!ۏ8m|OviSʐHK$:ojVOa%9KF0w'{φmXٷyk#5j4Oѯg'Z|:i;WUӷx}:{;?w^ je|=:ǓUs6Bo539cS]|۫#+8?T|f [Pûٱ5K48Y8Roܣv'?mDNj=X!W7Z\:c<\#+=#5>½#5DK{7F~#+n?p}DD$:C>.1yO" ycyy`wx#;p>ʢIkbOJuA.Y\J)&"*)z1&du:U bN!+^+QKI^%NyqjmMP0ze)v2qs,m *DS0_蜨t4eS`Lcz##d~5Q^{F7mÔ}q$ΐÁ$ I;pj7DyY3CY,Nʋ:5|Y3Ud=dCG9"S)^H'=R#/>?#/:d]]՟W&ڦV2ܦ[ 3v6SFOv'~0`3x#+F cd{%>}w`s0D=zbɢozu!cIِ'1ylP\/@dP|"*[iWOAP@%(E;:)_=GX{yO\[Uj)~n^AL|\/Kj#ѿ+ Ht<qYkD'l|wՁz?R#&ي=oßIc=75qD-'ս61hTJ\L6$MbqX؃AsfX9'!RFSG1i1K\9p1΀8.VҘ*$I8Xb64Lij<uqޞt 'h#/1E;w80Չa0#+f1G*vݫvҌTOۆ`+!Oapdbn=#_Szڹg2mo C5ˏ'Mo{]B `PPo|DI CI0Qѡ;'e%A$$4UY)a(ADNVϿRhៗ~KEeխ?'[WY2v)sD^6/-%)`os/g_?Ɲ^=w~-NS[G@w׫w7b_WJLސGX5e~:.{R_.<"(aYJjӎkpl#Ϸ[b2av,vH?G#5?w>??ʉ/~9ȊjY#/{}xfV#/Pt#+OwKY9<ƛ ]&ZC*(?O*/6&UTZD*k3G"*ܺr1(0#51saq-7-Dӈfb3D'vM#+ksoV^$LQDkG2bKF-jCBYd+CM4ו\fÍ 1X6cզP4FQ%Q`*J693>\0-cqq.`%bp(oI!s7Bh#V)#+pd[lYxG#+7'yrIL2K l*b8+H6r1$60pbEu,E<'Pv&p+hȈp$ lX`[YJg8R6Y#/6)DxHh_7 /h^ j?vyXu g>7֞=g.F窑D=W-߈t$}s|㨦ˆNU{xfnɀ)GJ>T$bi[#8CaǮ=_廏La$Q:FԼ,}P (#s&"AvlCϥx#}…ݷ,L)75BjRT1>%߇&xjx=N;JS zw#T=p@x#,GkaUj+st,Cű~~R<:s5P1Mo|4m*xҙ"gK8bj9͘3<5<ɯģB) #/.E:zČg/t8Ln21:Ann{+D۩(FIƤ9%5sTnKqE}0v9i4T0|ݹ$#/(a؈doylhR4ݲ1QX0شEއ#+qmZ>E4nε1Qžf&ڌM` q)F#4!C!;=D@IO(#*:%nFL@'Lf&rL۔wlk!c9_vX1C`4n~ȢKć0Zj'H#/0d AcΟ-~ ^n]߲0 w"yC#+"7Q1xx)(xޣii5a##5Tm?͇s\g#SS #57 eᙢ5;(v!;%ؤ$ lSmڼy =*Os!]$Տ7.N4Rjtivj.CtfH2"߆a2MV0p!6c;m M@FF3F3#5w׉ָϐqmAxnRo!h5p靯EX@ya];&8ʋ3ɹF hMAĔQLYuqE>Nbڄkln# mrz@Ump39!↷<(8lJ#Im~yjBIx|Og~w9|X bD {K|Ջզ6?Q62m&5][=6w1P݄%d!zx ?O!+x[#/پP?\j#/Fܞ*Q&{s*D#H}a2Bn)9#5M5)}-gnH\=4t_hp_+O#X3]-)EvH&)7٫QYϧ0x -6I @b+U#+~ k0&v`K$\x91T3(zVPSʏi7t?% 3O-a Z.mz}]!YD0wi$(jթx#5\7+d,5F{ ,k{^߿aOhXWW<`Aa#-[#/Ku16_"陿jf#+ ”J pEۋCH1_P(&OzӟQ+r,2'$w#m1S=u#5C/5.$Q!&0i`HN/m.P!JUI4Vg;sݠ2@nc,\ ED@,Wjϯ#QvۣKowo5هrNi7v0 d0-9C0g,2zZhvxxDǻIa۟j0ꔛ5޻d|V1Gĉ"|w8ǺwjEH{|pSX8̟+Iz+۹i%{2nKex _ I.OZ5S֖^k62π*vC~5UMsA(%z2β#/rL`=կhj]NA|N|s(sfˎBFLY&rdt"#5h0@JzT|Yh ;/uDdmB}C`|N&"w#y*`&C>o&\Nv~hg)Y[cgxːX/,wrc=qF kwQpL@-^kr6uuK\0=IhV<5ɭ{1\t9r Opڶ9Zl4[Ki{(!:^{C~d r|%SFALj 0RQjGDTlwwr\uwS|o[P^M#5fl9xn-=hP67\n`6o_/ ׺$Rc׃ vds:iLa5&'Ж['**-Id};g4H[61#Ze|L>O<&o[[I}b,a0*/ᑂk{ņS(dʡ8k8\⑅> :&`~YW}β `AaPZׁoTYu#/}]}U<3D_^֠JOV+PҬLbsJq](mM)9CwZR'c'}<ۯ7;^g=}\yHHѼ_)P9"3] ah@",L76 j{r~Q拗|v:I oT氰! ٟIwl(⦥ܛKOQ3wYpZ. =APjt:ohSwK Q7ѭXF򘻕멟Cs<1$um-#MG2-#Z;sEܼf"m<5;Ym@y7N],#o]{,֛ݦE`llv2R=Kt7sw L]3՛͆^Bwpj⊺c}YLAζgx#5;N~;X߹F%%%;?%o["#5mOMȑ;8l 9%J]i)9;ffmZ~]='dÉUߚPrp6/Yn5LAJM N $ʻdz[0}U&H"^I>\/8a8g[Nԭ;塢8vPbէZcNK-Ζf stӦ Q6#uG\]Ԏ5ZN޳tex0^ifJ%y5g|̝ǚwBBxٲxga7z*L M2ԁC-B!u,[j MNMq7(w&uo[rDQ0ܙMuᴎN?yq#/vC;b"e̹N߈,1[:,nVuK֫ka8E枀j/v({?9_gQd1:&.>esY\կP;Z;=wvj;8>WN`o>j×M0[tdMwct&"=/,,n{{>Tc3nQIF]']M^c\c25FvI-z9Vj{,`ĥunͺaNo]< wD`(8ZG[;$Цt򊈑#+\6IXxzZe+8>W'3CƗQ>geC=p-G: BA6Aê8 #/ pmFW2~:ȷF:;Q>潻crF6x?jrѴo-҂~%& d5G7Nhϭ 1±Xb^$Aƈߛ)aEyi nxޏϸw p'u0@-`W.`UU Ʉ_Hҭُ5vH0ࡧ#/6.Մv˳UEd,2xMdbD0}3fսԅ}tpk퟿*EumYE`]vïؚ p #/WeFzYgx2[?t-Lg:NW}m8J"}Exr9$mX=v1tn_d۷9} ڕN-,Gmw~aQbkFϻ W:S îzn* UXdr f5dؼYE,md5_,B#+P/LC%,AomxLV-RwB[{)6W²Q]yt5š]#5֎n75rd'.{Zm!Yd/{9֧{z8YuQ?諭YɶBO#5o˱[B)O} Tpug9BORI ±6?;8tG]Lw(^~״`uG}L0.2##/Um{2){żw].5]˻IfbcY*#+̬9Ӥ4{ߢq{(!4žra_J?Ɨ0d}9!h7|Őtɣ|I5(qk-~:iP:PZBQZ#+^ul!>y)-4#/<{%ΔϦ "ږ,q8!y\Y?*YmFTx^zIm;nд=|VPAx$_2[)>-^_j$QkDl /tjZR6|VpKJLܕ5N>״$H?u=]a~sݣnauz>Cl>>uT’}n{ Um;=yXMvd#QA)*YBɥ`W1'S1j$ӊz/}ֺN*nCwBY0?3[s%ysGK4oPCL(9<<_]~"g:4Ax8F8rXap\l_l)#/ܡa;+fWAӄJ|Q=S/v~%dY=YH;OFeZwVS.xt^<fa70UY23#Rlw]ADX:[/xqة|ʹhѭ/1vƻn+w0y[)~6m=zP\;f&xZ% ckFY#/!3pk< C\B12<23$3.|KWV1;u8?S;Io,ހҏy}u:8wuSA{#5:E>}Y\͜g(ghhXC>p6\7OmԬ'#/xAblϼ5.1ٲ[fuZ#/8eׂIbtc(kN'cX`#// ^:WRgejF}j0Z<թe>eG IC]xwMP7=UlTXСC+QUwSg]EnŰtVVnk c̯N-o{x9Wv>TjɑSrнݫWK{ݜbڔutxǐu-_mƔCءt%IMZA}CɢXIb)Br.#/Jɖ,<)u6\^%`s2t'HDaݔqsJk;bV3Hw-1w0mڗ=(X YPPY/F#/Ǩ3.n9@K.~b9/N4㾀x|F\+MVb;UX|n#+ƞ2]xd#+?)|*j#+BAC#WB H(B8ê8WH:y3=RE#/%e6(Us12E4 ,^@)0;i~pʀ9t‰rUrtΙönv j**Xw]+,$G#5zUO~HwU9@r.x1pnu5"y]Tne>Z]9N(V4qœ=WRsy*k#5`k9 BȢ_SSs|C_9F0=&ٖskPƺծm*Ry]M o֐b[f@|fA uW:=;4k !^uR 8E3 b#5Gw7;O8EQ5Y4AYwwBPXOۦk괜'eRP>>{k-vB9ޮ 0O@#\'6^,U9%!ӆ/wԈ੶7rYUtzt00;MBϗ[NJ@4Ҝ.b'p @ۿ|r6mAZU9vspL;%~DLPE(~=E> Ժd99&j#+şh`9\btkI?.zTK$/>j;U4TlUNw>55+1D@8>;A]yR7=j8Ih6DO'cAWjt#yHȎ6lï P1"pi\@`w=lb8B}(b^6m(mQjE6WACUw9F#/Lan:ۂ9o3w\cci}hjwJ]Dcjib~9If#T'H5u`Jjqmm}͜ZrxM^hSu&/{dЎā3]YYS-Y1~{q۳+|`3cCw*F*lt|jB ij^p}qd\#}zӶQN&Q";7Rd$xxIdËIP1!;R*vPBw)R2OaI-t,J Ȟ6b٪`Jx$6\7Gke38ƤZW S9=CLĪNvI?=Hfnڴ_mwlɟk FU@DAfS6”Z jBKkٮ0scN|2BFH(;) 8,e"zOQ#ˎٺwؠfyӿ.aOcbQ }:]MA`V`Ero0qxlRpPI73h.ם#5tƌ)#5d*Ѷ#G';'sPllK1#/f'gs[j"UPoGviL?h(h@G#.{]xzo_?ٓ *7PxO􂣕cN.}nPѾt\q'ox5ho`Up"p]`2sx!j֬ 'Bkyi#+˝ogup?w7&Tp!G!#5J"s#5* #5=:ۺ'{Sl&>ɣw($?|ýy~@9=J)TQT!5ȡۯcpQe?P;9"oxn>.;Bh"nGiR/ C5hpZ l;0Y2SK&^BH*lՀg9 #5.1+=Qgk[=<#eçf1.k%gaay{.}WZG9HVmOL)Z'/38cIÜ!"i?/适ZJ&$b!DJ #+Pp>WkgGϷ_gGӗ#+#5_#/6Bҡ. VRܑI_1RHclOYޚ}ex5EwiD2j@s\ϛOUHTI(&Hjc *)Z6#+|:\I !ix9QD#5#5HHFJ#/@` QS*0P w^mcS+R\& l+-Uܫ?bHbI4K}%@瘧`L#+0)t_]c|9#+F}  #+]#/H"4IFRݓ@KH4b$iWN/}qe#5321 ȵXPLUA wR@[]j?~ޒ"5R/~apbfoYj\Nw)$PV7\%ԋ$HۂI IKg_L?ś8kFG c,1 s3.M~X=Cam Q꠯g/ͦ{NҺ!UWu~m% )'.X{dxCOW͇#m) 2D~Q?Lx6^TU$C~δl=*`tǴ;#/XfyV#mws/@z&܊{:t-`eS}B+BdHt:Ћmn1/5͵~8ؔE#/L`f&Mzp4޾Od׵7Q" Q0=/ !$/2.qC8:ܹ:SfYȫwOGď#pu?$7aFJ%6iBkK=#5X{YAU7/xn{7ǦN evBL7܂1(#5Y!Q 7m3bW8) oÍnd4bXB.j1D9D?$fߏQѤ-r +p#5XSKƽøk^4_SwȘ_ z:6G~3.}wɳB8=Bz*sYcjru;7zwsC$Er7NxFr[YB,H#5`̠=w a#\`7 *L %9 wew[wf(w0S z+RaxH ҃?é;&4>0 ۡ7muOP"NwC!%yr'\gQIJIh<܅kUgP TNC'':S'Ae";M%e41<Dt`.զ7t|՚HPYv>;72h< ޻=߅.NK#5#/5WJR.oŅ+̺YWBn%DB~q uE:>c`y=? usp :5.uW(=tDҵ=x?Ei}zdϯ!Onk[I)WǢam wy|T^m@sf>#Ϫxۆ0sZqu su%ryPQ#+#+ J]S{>} @ޗM(Fٹ`|<06 M#+*or84 +zH k6y^.Ivrs:sD%Fc~Dk#5Z="#+%խ5)U[>P,r=f|"$r)xWjQ(D̻ˎ\$J,0\J;q%N_Nn3-3[bIT8k}>#5a#+ҥ 週.J c<qDp{6 yG_NI[rÀ+D'rW3 Yzx#/ӪC#]#5#5c#5A0T` u(pd*)CNLT°xN:B+ͥ':t;A~K쬐UË&fvǔ8̄A|‰D[Є4!ٮi=։t#/xiu|FUM$T/wD 0f.HfݥL#5&J uK;0Uj#5;ujixXmLX;אj+Fd82$ 'E#5*Q7vͩ?Rgtc#$L9|ӱYsDF6?ф0+(~#+%o~Xe[VIH@)[s]8-mR`l) >޽:iY6#/}MݠnGs@ǩb!ovs4j#p}8)(c7iTeNc={4aɜz#wy^X8ϏpH\Aaϋ>t)뛉0#\bl[yTQH[ntL|YbSCO#/mckG7ǎ~`GzF3}(c~ې#/ Y!ٝ55{ c x:x >Uoo 9m7& xHlU\7NuIJ:#5+$5-lx!;92˴GO9AUQ%QC\tAjhb`g|8h-wAAGq[MX*CoB\/ůa>>O_? ʹX#b\7{I^8Rf'ߦOG>da޷,pK:}% nbK5nijJJ{8hp,̻ _;;VpeqVu]oYl& 8uY&u2d􍦂 M[^s1}TRM~gT~E{)#-d _E?/>_pPDg` հX0n{>GQgL)K k܌ꑵ+8٬]3?f_\ΓRg 0o5I{?Jp:+xH߼dU[u[$ cà P@Kz_:}lvunwz0=HOOc#j%Cٶ[~~9>p#+1H8(#1YdOov/nO7WGpW?'pog'_/Wzk^%'OЊ4_b { -<9?V$O {mٝkWU Ʉ 0mV\zxgz؍-L#5zFcW#+?Js$#/Mc,|/3Aң72T)Ȅ1 TD uAl<x>ͫd 0B/`b^2=!^ *>0h{NĩZJ@H!?[|OKǢ2 .l #/8n qFJ9Huw /[ϯc||QXJ'@.@H7#+#+⢽P|ٌﶴYf?$W?Kx:d'Ilu*{G^wol8 A/smb"LPƑ"oT`'Q/ ŴP#׼3e3j4EF;e5?,{0{==;,"i!5#+gC%L#;&RuGb>[I(I={q^NfJWg+LBlGΎm *]ăULe4֦ɠemvq_CR NUZJ_aZ.\{&:bdCe#/4_\)׬,\2V0FI=m]T8x!5FC<9ϥS\%˿C%̚lCB쨎fխdf\%$l!.qS9O|%C%yTwJ#/ A2@#+y^gm m$>Lf4F>W:q'-J|tTT(@PH2Wˀ1r ;w VLؽQy!ξ#/!"'%ԇȞ 3ϐ!j{fv}ɜ8T&Q@]x*%}z΢q ȈJ4a7#+ll@uv6`MvCiؙKIlav%9+&y*X!Ȅ@)x;l!GP(=&㞢<6(gD' &ٱ4C]߸ ԁG>ӦХזzf:S KJJs$ DH#V di I5[uυ @̚ݱAf.q>C P$2'Tc΍dz^:O*NP7 C(Qw+X; GsPŷbK6? Q_&;[aa ͵=Tđ!KB!Q-!f*v#+g3:N?aҢAI8q ]Q8gKI Abצq2 Iyh_]/_[rhlQnELYƂ#5Fٌp|s<~\~feYTܰ^4JjQ9p0Ht=8"S}'sN#5VI\죸o l#/d0mg{xҳ9q=矐ycCL'm--MGO|}|Li:W H6LBq#>Lb8N'$0I0oN!C@[)זdsd,chQ> '`CA*|-j:ٻaNط-T2EsN"#+|(4(*0\U4Di;Ag›b#5:hE&BcxWh܊u,Q#+qsedxr}S7) IH)2hzc'}SY:9fA˭bV/[#5I) ؒƒMs HTU3LQEuw82>$mᗶ˝I&1w9H~0 a@g]j]zSmcL'*͘$FepvScIDs~x՟҈91I5A'-N]L7ԩ~3j{*s#/HLJRM+TS7dZ AfɆ8)XùU|g][8 jyćF~-7#5s(Ge~]e,ط%6c`C#+#+h^Xa<s8gRz`KXY?7޺Vza&e3N2PbNn\7$lm5J{W,s^(ꁳ$X0]ɈyQUɫՊ",ANL#/هfx7#M2gOnb ;7("i;S`bł+K8Z<'PwvP$f:ju*H#/0#+7Xl7TnHi`ؑrrpmZ°KPl/t)h8Z&N96rDŽg#rbm9~Cءw1pi0pԼt|ga#,͡}~ CٶaNy9a-T#5:-gR$ҩSW:(0m#+VmT'ajHف\Vks8߹ќbrgj*}ά(ngnZi!6F ٻк:X"^$6lOlc>aU^Zz~c~y9 z >:{3xݝчm9 p>v BۮuЩ W~|6&qUvt>i{8@rw&.H\ܧ{3ya=vy ؠ@8{XoAiT //^+>h0hƓ;&q K:6wH#/tt)&">(PzN^%GA$a;CADzdy&Q[FoT=޽Cp9tx0ueT$`qmt<^Od݉>9_>|?!$2BI+B2E߭JPW:lh9 =9r:I`GyGY$+.(H\م&% ,!l-E<3H7*ٻ!pqT(?)4݅Rtg񟍔6{N|~(tCްp/[(p]Gye(?V?3Sb.oOW""@\#+D p yTF!#+?_ÿ_oF27QZ3-G9fM'E5{m(V֕p}P@#5R@7;7rW#5}9:S﷈x*0^),Hеۡqg"IPI$Q妇#+u.!VUQ =ʬ f"[fw 0~( '!-Ͱ.f-6&f}u풝6w" #+Am>!SԒS!- _by!P|/f8#+=d)[TI2۞P%qߗP"?>uiFR-mUYީ>s}1u^9 =11W9?pG W;G/_qDdT#5hxz({Ai#[[{}c{`jnXS l89DE jG߈~#+h4g$ P:WZA},ؠ к_(dA+wJT|ȺahW'_am^#/ 1+-72{Ͼ4ӞVxϼD_st?YοbJ8SYN}P?8>D@D={ .@N`(GwPϟ9c`|#ϡRS聓o=n?#5}0G0N?oƃ"oW"D{^>`tqCD s,ck}:~o:>Aw>"^>4ޫf HEEO d$>@}߯IC^pϖL~?1:Wge6f@]#5BH^H^𶚢(T"JƇ*ʨPkk+#+Q*#5O#/Jɵqy~L擽̟oaJϳV8u!&ɣZg$M>/ݟ-v.WqH &oR~`pnj)K(Y<*=}V\ޓCPR阘`yx!',>jβ^Ix#/ ͽE~|s f];Ƶ8b7Y;q2^kl#/RRamhAi#s7oұdg S%/sEHK`!#+Zr:z59VW?U_*f())v|uhuwk#+V@hRX|#+v(O=}ۜDg:-~8v)1χ־N:>yga$<5W=$qK33x;kw aC Na(y=nLΆM=]{aףqɥK}ө0E#&{M`,B+o,c#/tI2ݝ@ "#5J Uu#={V*#+HG7?|ހ=DD^R/3W#ׯ!5k-܂5A h%3j[v@<v9G!^pZo=/As#dd1JW.6s#/}n+0TЦ91YE@@Ώs#5`D33jx E#5?g-4hHW+ .Dv΃yUHߺSpBc"Ub CLkϬ,Ld !H |p9C=//X(+{.}e&}AQ^pե$T{%#/TQ myF!h{[`GY"sQy*xk%^V##/d;,|e\##/ʬ˭ r؏4sd#Ed@k9ƯbΊy_3ab"'bkUTirf?L9bp('KRI(R=Qz̺>OnWڲ^aƳ(gcHPhB~b_| 7v؁yv9VOI/͸)3N~rjM%Qe{y6؇]eCU<-#/E9^. lă*V2 ?uvRQ !7k^W\d@7'na$9wURq1 PM|ȱ~R-9`&9pyxx?w떨<˵;⾸| $&%[E}4؞ph];`D~&AIr(Vv״e<`z+l؝2K+U2W&]Wuu b_wnuXTsEZSN (I9LzFޛt;Pu;ː-{U6ˊ `qv䪅|֕ӞcWƸ1o#7>Wwދ=].%'n׊^Qf$KoKɇnd!f-^cx]#/*i_iY#5|~Y"1n,s &7"&(#+. #5d#5I%dtUZx|Wk]!̅D1SyOyh{k5CTdxQq'5[rhk50T*zE C":Ձ✚clzp7~oŀrNj16 _g|2>:q#/?\2`>8gT}|p\1/%zNISe /Epu{_mA#i؎q#{9ǪoI{7߳'O3Bzw\zYv$臄t}{:[UF#'7R14[੧w)ό:?5hbmٵ47>E8hrd *( }N]+v#5id5޼P'&ࠎ6*26>YM]$>X~5T}imϩi\~'nؿ*JQjWs~He>tD_^f9Ö>OV޳M#598ӦI889͏<2#5!J!DzSx Cx0?QdfR#/6#5Q,$UJ(B<b*3QT#/3'>=zO1"#/ H Riy$zq#5@Эڷwmörb)nwZuS8h5B)ۖscV9f-~Cț;ѷS4"J <(/VC8!`_<6u4c-4`\qh,AbVA`:>w6$Pg\B#5QenF((E1Ḽ8;>Jёi|CR~>: Bӂ Nk7(5T$K:6T$#/ }El$W9-Y bvF#59lA^mQ@*RHRAv+glf0$ p,IʪJ*S,d'MޜXe)CAQsp$8}#/=6[Q4/`Ӣ< ]l^Dhz aML$UshQaٙgsMgyĵ0gxpM$WAŰ$pؒ,,_m.?\mh&8DRߍDlAm3(䅘lTtN)4MfWXzZ#_x6;}`)M9 4H`?ه+=1׮=NU4ˆC\#5մp˅O5?i  TxfDFTݶMM@8#/d?y5Eʨ/v$Ov۱X|Ó󧿏u僺e^ GV%iow)&=ݎbB lXXCHU7%%|eFbt"g .#/۹#+g<;wE#5'G$ %+˽p4#5N"#WڇDJq$b#57ӈ{<(}r<z!n~??#+!J#ۿԼ?rrU<#+j@+JЧ!$#5iAhUSy|Ys&sax!`뾳Hp~&4rښMQt#W|@$ռ;#+7ʟĜ_/xCCáOI" =/i=5#/11EByPa 9 #s߱c\z~=3L8?>t `C(0FaM2+Ei}FYNXPv'=F#+4U?zB!+;AÇHy bać*뷏]9 ;% aSD?OPgC_ye݀>t`TH|Q'D|k#+0phfBiƲylIYIQXr):#/i4ĭʘ3FZ'Lߡ#/Zo!/wȝ"j6ru v6E51:ƚFޡ@CFYYhY#M03ZJhZO>~IGҊ#+#5_[0cmNn#5tx4@n0#+  ;=DD|K,=G@պ$YM#/1#/"]lLv)7-g[a:C#5~Ƴ >4v6Y17͇;~nɏ7c1]E9#/vwȌR. k#5mղ\!;4_N^lFo@ͮIqM#/x\?y7mN#+B1㽷ݿci>xs0܃Gߏ_p݌Ƣ^?.#/ZKV1xIG Be8""-`.28giݳ%_g)stMR0up>#/#50PJJze?x٧eY_yt1b+G8gܯJ K>o ih5mUj H|#+9{D,QW y@e)  @6 N U8Tkuأ̈́4:k% y8W u<{R6'?A?ΈH8wg>#+#+0޽稻18}#i(ɶQI#/f #I\ Eݞᤖ-WwMG'4z`j7{=zO^,=^aԑݒFVIrwl"`]xERi8*/HT/#+"t8N+묌$цOfOazir{7NYΧoㄚ7Gwr'S2lB;ߧO.g ͂NEIHrQ!dޮǎ'~ qU/yeϫR!2 HFf\2Hwrv&~\|a`d#52,#/R`JLx`"G3@; m(@><ķA1碠$sH15T ۡ#+/'ಆE{3yِvP%Mq`!Ja|<8+Pu~Pxʯ^jOd >k˻ [ߙ%Ddi6sz|S%7|C⒨,|8խ t=ð(]u2' #5'iS9m)n*0 ثL=y }G/=gWia$Z>c / c2&!qu.83yنlDzo)sN!0xM%(P 4;|Ҹ(i >sG%}aρ}G)ӤÅ#F I4acLp:ǀixх\ ##53!B(36_|݃H'ZvZbLSx8v|z~08Fk#/BE zX8\*#Ǔ^P#/mِIL?6o ɖZv| f#5#/xFwΡٷX$6g38|czgbSBRQYᆒRkGFAb8JD@yQ,Pr ӏ\%EKLjY˶YXXRF/4t|6~р?#5"׍SpBx~_׿95~]>쿷3p.M<_;:#5){y(uHXi8=k\`3tw0$3N@#5C5P(~. o JbԗjةSDCЃ``Sv0X{1y`ok #5)#5 RHògp;A!9xPi"ִ?x9nIh#+;&B'I6BT=VCq뉱c(aj{m ÕwJzF-Cah6P&8.KU%*Imi%Jjt˳l2I %oҪX4LJ3H_bl".#5 #YB˷]0?f-=+/381T,_ 2u=;N[8NͱAm#/`C5FVQM~~ q(#5Q` O?"}C5t7H62`SĕCejjP S6+b=$E?/Ü蟼uQ\)D4XE+'|#+~PVj`Xa$#?FK8fkJ#54)r^,蛟7#/ԭ ~Q&%^8#*3T\7q1#5y鸂9|~ ڻ*8z#5 6̔=CY j`2KX%|t\չr-6PjPT I7#/^ H7y4H:O[Z,7I:tB#/:CcZ!#+ܾ[9&a<6{ zR "M9}8בܝ>=ZU y{w.liuuf{yMG{9l3-o#}P߷Thx)<0#+@6~$Ob")e#+/`hh71|r;΢H.DD@I#5$q+o:vzػEҧ!p?ϊQV9Wb|0)䘩"e-Z*gRcY7sA(BI=}Stҵvv}NݠX&ٖ"b4CC-5#/a ;-:]5BLmf*VKka$ۋlXmڮeQS$EDc/[(T'\7>o;ī&AqIMv啀-92}W@/XnF0Ao^';#,pl1#/Rh4DA(޿|E,dā~*˻Z(n##~,_alDoVl6É,8kM0_,d ;c7_Ae'Š#+#?FAT#/qh>=J{&C`k|<" `HV61q hy ӀYWv6y&L Hhaasv?3w;7&qCGcձ俞MO#$U~Ï﮲b0e]}=#/~IJd#PQA:d_P C AyWAt>_ fU#5I5>#+fc=t 9(#/tMG;C #+?n>?2svI#/#+H&{~`{C>+_$?a$~=Qk0P$ ]cX J*8~ToT~/Њ#+ˈ;ie^6>Ǵ#/ɉ6}fo:^p;4ԜRT)thC Ӝ$#5uĩT kL~C(O;?do|M'%=h= LurμgT`D#/!{z'vRYcJ3ӡrkN^n#/zg45M#4-  s>&'O8:b#+}&鈯_sۜ{gdfFYCcFۃW7%t'w~jguɢ#5hy*g^p TybLGYjt~ ߋ ov2}~#g3VWC657ױ/&L)hD}bVtt1:'}a#+&< #y.#Jъ3OO OsYu $H8tQ Q37$0>6"_K>\[`r[NNj~&ϟǘN<~n &`C[&I?@o& 9Lx'U~8z %/L&^އݰyXlw4KmKvʽ?4COk;65w\!FA'|ՈG$C*z!CSp>PV##+$}#5ƥBU4BxLE%-if9l6":ɱkâ I횟mHlÇ4(E(Ew +E>c }ĩb `?sDjԹ`ؖ|OO=@>VIN~X|B{C~yΧ#X ##+!#/>ٽ'o:z}Rf?{qMNSV?~til}sX?v&'$sHT+,MeյMM%sR-Dr CWhs$xFIsps4 9*$.^>GQ!\.s60wgpY9}xr$C`_vvovlY!#>?MDFGv2w@{~p;o$ X&91|ރM$Sa.f#|tc|33p-u`8}((0q fN*K3(hP3{Q:z)B׺Ƣfo ,̂e*._.=~ ,OӷWdSo%nuı)`YCH704n+I1C.eKg#/9ĕ~ֳC$R!Ԃf5ųAT#/Bń?"C).+,@>c:pw~T$K0c/h &%DN!#+!YѡUP#+*! [/4#w檯<[0R#+sz@zC93MnQ#5J Fd!-ldߢ;Pt7 'ȆL!AP9&p8ppbHP z|nB~-OWRW?Çdd=&S0sL+ "eF?:c7ubC=z!H RPRJAHD0 ' ~}HV_8؇b3@pCPgpHD*plrgBOEv H`a|,:Lg 1 C=ϫ#/YvGY{PV(8AԱ#J-6p{qI cYgJ Gٙ &I1C!8t#5}Ӱ.'4;\=揉K#/_$#+z#+i_Gr3M`0si^4 3`#Ag#/9#56.NNɵr^}?lYl3#/EUrXqQLJ}?_45aLI)@P0yxcm?-QlC=^]1*#+VV%KϰrH}!AO4(o -,k)+ȩ0>dɖ%9f $rhsA7gFcAIb02x24Pi~>N !Ǩ\l:NŖp]fVp7]!O\DNHMМD_01O>8WNQujlߤۏKFLG4OlssbI9ԥ35Q?_M䆣N5Uvo'^-lx$~BC62B0'ߎB9\JI*;.Z9A;Yr̨Lm,kl)22 ڀ5x{^c]sd\vwO!^^ `sJ(Sa~ChP =(&ZHv,4Ձy#/cy%^sGfN"_x`s0orbDGd@x==)Z/G:=z.H(O2c$iTBك5\AمI0vh"WR#/%/烆Bs@۽55M> Ŗtb@pq4Xw?"SUMcMu5fA}06N/*Y1Wqe%ZBM4{#+w@3')F#/ndIF DIuYd ~<-]$(˦n3VL77pN5H#l<4 ;v8Uֿ_ο~Ru[l b^]/#+A(q1D{ZF%&XQ m,[yUjxO1:"Qȇ5khUU*$MVFO#fǜAB`9N60xYA-qYxq=yEB *=j~̾C^:C97$MA4Ͷ}Q30 n@2g#5@>(^[ ;3v Can#//ChY"S!](~$|S83Mb#/*=q߶1ޯ'1mE` PP`9|<ʼwON#/p?>sJy4d| #+o}fgpo>H~Xl`֝ɡ@#/#5FAEF\ {11784y#Vv(wO5l,?F:<۲$EE`y3w$(8#/pDPt7:1kׅs$,ɛ٤dDI%4Ar+zMH9)3 m|{& @nV#giLTUV|%_4|{E#ӭ389qI}{ќ}AT /`+YM/Bo@]sѾ+6fY5iY[w3şroE'[uU(mlm}Tq_n+|@aז.!6V Sz\A~M_G/#/,=#+>a&8n!jX @tW?Og M~/ƤD)&IkپC#+ǐ*OPO8zOM^˕5LY'Wܤ zq1{C:óÜ>y #+>K'֘s0`ia+-!\%Dt%ES08O=uN;mU4!#Or\<#+r2 [q?o#/BG&g0g#5KRxq1Hrči)`>M7p_KA!$ۃbeXiH,dPSi1N)Y1{~iXu4pTE67x5'64^;͎ZHѢ5Gz{v&`9';ֹ'6h*eւjn]#Ta lq̊/!g…C͏3`I$ba#+$; fd""#/AD[`) ,# tkA_"+_~Lώ n~qPa6~XpY~×~;p헙?IFNl⯉8랾X&unPT_3JLg@GN,5FP>sr[g}T 1#+wT#/0ΐ x˸0Oc#5!}H9TM-!MSĎ}}dj>H石ۈN_/B#/0 9ų2؉Y]#,z{9oՃ`x2A.?RZ?ZVP* rG.wUC_P &5Wy~ӳO,ϗTX5a$$Ϗ~o?uQ։@G;dv|+,\j@ y#5P#/8E3iT-?|k<ΖfuC2/ذ$1#+KxKgA[QD*tX2=*96ev6<%q}#/c2~AHq!sՃ#/>B`HF^j#/Їߙ@K@ 4s|YIŅ%]uY$>g(?#5*;?Sl$9Aj8g$#/ E}@y.RcX $rN7;#/Mz¬9>S(!WX=#5 l?Il.*^HR _PxQuP:#5/%M4*/#Rj1?fLzBfIk+lR*|MH9S 5G#+^ + b*C)y؜QCRSZ<\U9g˖ry_#/Opl@Fx"/Mwa^1C$&AdX3-; EGWOm$XMƽ7;gdRAP#5hG/r>@YF00kUw'R Q#+ir ͋p_x\_#/_^:0#/#50@AJGZ,PB/#/~TNX{q-͗d}!?O[u= "˞˞&'%mE 8?ڡmղ)VG6l~؆޲?hS\>~#/*@ i66 <DnI7&?e3FQc_W)6Kd,S<b7ކADi=9MK8e=bO`ηx^>RyK{0cp.gգ}G#/m_0nH/xB\d{&IX?0QF:JZ2:1gS h>e]{7r8AT,&Y'\;u?ί^=o3Ƒ#/⋢9w{HAށwtqsTaE7[iD`#+|C _pa;2}$-|rwSgc PL4/DˣI|CK#/>vɹJBgPȈl #+kN{8[#/4r{E8d&4i#+f M.DħA"5\#5ӯ+QI)a :#5k#5jm~#+kѬiU!ĮZ"#1$~!e8?~ѯOP\xCz\ƸJc|ug^Q+!;‰99<[dJ U^N.EC(lvA6.@W]N'QH$m#+x#+ m9?{G]ys#m o;":|{_]_3/3xplcġZg.eސqvtN_} c>Uhza4\2WDzO „.["M#5FZR1K^JE#5Ho];r8l[s氎G S"XH9^q4eudGP0_ z>b;<_SP.Ӹ뒳 #+ QŵD<\|QG|K:m4Q Qx0G?J1a!DȒ,+w#/Y@v(:qWpD#5wmD@ԅKpodZ[ߵ1c-Pdbq!y$A;^Ot&(r%TH<9g!2G*rZTQS=:f%s8$'J?6#/,<} th&'Y#/ڶr [ @<p{?F'ߊ?P=3;ߘf,?w'CC|`DI:1/Q~P?p~^+ӥ31#cZj~16zBjheG p{5>k[v[nY s7mq-- #+{Ag4Q<gh'O(QQ~ suLXHcMQT#/{|פOPz!skOw>A/5&/rT>k#/nv٬00Yv$o?4(m}>gaX.{#+,S:#/&4`rL=4ӆhĪipr}ΤcK0}Loo"m\ybz!zO$]/wk\HWuOM eOE'(x(<Nv|%=5/#/&_$aY?y0tX_dmcm?.~ΣkEZ@(v=|jNwyxbJ` @`C#5 *Z w%F_VlK#4S CO-qlDRANʚ H~O{G>O$U#5ݷaǑCpUUUoC M$^r66~Hhcl7v0ڇ$#/0k qXɍ#/яzmc ţ"*TE5#/gIx#5tCI>2\G[dDMK$<\%50Av}/;[{dkmL,gEftԴ A4AΜa,6,U~Χ"9En=@ypv]z[DvFMfGM% ixLšYM?8K:ϟd,KF>$Z"k->Kt::NG\)Z#5T.rDJ)њ%j`03;b4iH=2=d)>Z- 4PRQ1Ujl+ 0wt^okkx`*-%eyr,YnaBLtಗ 1I0b$ ]1ޫ4I_ ml#+&lF}f\=P"wiYk4,uZ:Q}xgLJ5"gg,Pzs5%Ln)ߺTm;%O7#/l A΃49 r@ў}ǁb[ced> "~`UK?$g`]5񭅖d`xf3/vnr@Q"d 925gʽk#<E#5|J=|x8ӏUi#/ #/:zS{6M֪;z_#5#۳Dha$銛W糟b( I ?ۍC4 _yy4Ӟ98&ra][AC>ܝg/.Ot]pMP)YIg\__b!m]`ЙnđjH] J7]7D5Ahm-LSikX&FoǦ6}$4r iEZ^ŶVpvG`F_D1#/#/v,9ydxq#/78|NADiE@5,rdq=QoG!a :&Mǔ !z`|#|worQ-FPP1 ab4 FQ'a:LяXL@Smzs79"#/΂Nbp<CKWOoMX.q#k6\e;)jGZ#s8uxE3"I ,ixXMF1f#/hNjjXZ^xC"ڐ 97@û$o#/!*#/VO5PS jw^5:10d̓3 #+kDͧAUV+rڽ6}{Iy#/ hQ!=QZڻv"2S,zbGjMqʑ:ƨ؃: 3^;9B3S1 #+qGXu=˂=y"RaV'co1 SN͞RK5#5Fsॢx+՚mמ\1&(GxɡtA#+J }Kި?+DD.Mft4nnȦv3)N%,AU~9/"(%~*ik1hȕ?q/꾝2Ac1%̘-ަ7ϗ]Eg)0vMQmp Ũ'ۦNj }H^4ny>~Z,<+37tv)[L;)/!ߊ/4#/o<;%T>"#/m<`"7j_Dtn%z#+joa~id"PR'J#5@$$hXL(#/ FvA?0i{wu9'ݮ1,Ȑ-}PYԺ&F%Q%d6Ll99utnz2y'7ur_OB+gPlH#/p&8~ĞQw,cWA@7" ^Q0Z);yq;U<#@yJj`sQ_u؏;/~?7󘜑H3:+60b-tם}tTz&o\,aI mf`vnb2$$xEnXh q#+/P L)=Ha:5:8AhӴnwX)ќ[#+TW|H`L0pF,B̅T`h/I{6?{JpĽ#/ |>w%N:| \08=e:'=O{#+QIT>2Jh@ fGb1` <:=7/9#ѭIpR b#+V,Y UU?7-#+#5mKhDU{`*p\ 8{GغWjWo*}ST=[KC4a#WD $%'"hŇ ܇O#/X#+#+3^?տÝ+r~l{ߝ$at¼'ttM)~;Mpsj4~4"$q&guί}\J{C>}~PoOЉ})& x'֦%e<lڊ&Lp8#/,^G$\ɠ#5#+GjP ~B }PJ{u#+szT (Aj@L^`CP$)Az6٤C#5O'hR d!^4 #+wcVع8bbf2vlBM;$[>+peBɟ0 8"hLBI> I@5(ԇDz\(g\'ï͂`XDF ~|Q̜)ZӈD$= ,(2ȁ >@!QЫNO?Yn 1)Hm5a%buMbժ?p$VLflŹ}("I//9BaLԎ -4L̐Q!v:oyT#nGΪ$!):k)d($i#+H&3 mt[tAzE 5oU@Q#5 0Hd#/4wJoTgan#+ThVZHnnyՐ#_Q}ܰȂ Dp55A#m49A=]TeB=|| !frڧ6~ma|:ND0PXfGI`p|kqgSLGaA. #Rтbr"8Mۓ Q@zTu1wqeMzU}(_FrN#+৲!L*~<_A<#/߃GFUI6\n|}57h瘃1%$n0{bJ~oud>zYBJ8Y1?v*C'5#/G$#5v::<0U3NҪϐm>gp:/5P U&';l`^>%pղoH;y@p'GR,Ys0m&?>w07\\k,:3!sU#5 AiD{+AR5YFK#mχOz>wzx4h0< ګ֟˿3>G]9#/舾#5 ?#5i( H Iy!#+P0#+B5QR$6c6nۦs݊Ĩ`j}Ƀט@.NE!U_{[ a!:׉cTNNs_9?0&<]`,Z69NAOcslM5WU M6?ƙX:TNvi֬<-Xb+ye e5&X6h1#5{ߖU#&$E#)Ka_;#/7  (BH(Z (i)#5 i(f#5@#5b)J %%(V%"!(X)H"XbVfF BJhZZ#5IB;~?dl{K\J1g!ǤIk2eJfRfD`@=X'  ӎ& }R`n4&`m?a>6Ӗ;DIi5! }@AwY-5#+a%*B4טq:!-tCz֑pB?H܂S721gcX tw!:'*~Q_o[$/5xp/q7ɢ؊Lqӂ܇hslΎL1ˮnR$5]iX'N n3e`(O2H9i9?9Ӯ#/7G1䭹#D2& -4#/)rTСX#/WV#5PХqؓ`J780rDKHzyt#/oyT/^e#+nm! y]WK Nw1"_w3ΠfH%(2#5Puw\r4 O]J;#5Sɤ#/ )$U#+|)Mh&B"#+)Z%#/"#ʎ@~qPP\151RJA%T DJ,B|#+9+×U^߄?Ֆ"X98pOӟ@ҁJSє .Zs$(d>w~OaݱL>T#/Q#53QCsNLHeDjLb+``p}nO@4DNe>#+cIT|𻚾fc#/(c5'`Uv֩&`̌s9ACOK-{@8l~;_Ql|wQ7`ٙ~]t.,Ks%Mj$~ݝ''*t09@Ro(F_ރZy yd$RxC]#\u=.~{OlJb!O=[G6x#/{| t%%vA?s̡"sLςBC[1<=Ns%P`s;l}k6ln^85>'Ns͇j>C$;=c /j@#/9PqO#5LD4K%^DMJ)j`Ek'ta|hgLg(vh![Kw-i{n37q"  cqDQ@ PP1P%+J)('%PC{IAvc*?>XC4ov[H0X^%(͓`IK&c`QQvp[ /ҿ?R:l} l#׮ȡDoj8ϩ#/6$+:F=CF Sd&bBJH|J]/lP-(_~Or`q@K(Q`ϧ8$ƒ7 *(ɁG@g.KA@YG(GB#5Dsb($J"(Y$tM~zvԛɃsY]*p`DiH9}N'ߩ};>`{B wl͝?2'Yr6^;&;y#/bT@ܦŪe}e4#/oјהS^׳=;džy`a fӭ,^\|!xi[i!GQE Qsr\39hm\uv;Dg6Ľߺ1V}J`FՌyYaUBL&J.REII&VߋM‡'w9,GG8kae~_{F-iQ$\|O#/l9NooZÞz؀nO7&X#yyC6Ys[&=;HipFYK {Wn54Ȩe{1sF)?jB5G\R:1=IkkpzCǚcN%f}YYq şNzۖ1ޜ#+~XہUBI0Oo> ϧ>WZ }6"=OބL`g}<>4"ђ) 0v/t7f#/MJH=]=ɾ4S>Z#5bF#a[P1cT#}HZ-̉` j~cPQ#굝l*"s]k$BǶ)iߠf'R6p^4$ITĎ!wFԼ"R)KdX^gi%u<]:~L'_OrjB$h~x"O>h}<;;3]SE,g,_9"Uh5fK-sb4o s#/w5I!"~%4Tp&H>??fH"\>;)!~ Ԕ0.,963D;P[<#5icM+LۼM̆,s*Ab\bYGtfm#/\cy2Qۄcw %#5Qh!Jbi`j)06/63m#/MA{:y$h8h#/8 BN._O xˇ۪5#{@8lm- $LL)"7O8vJt~U#+v;J6;F乏@n}H^ hFRke1ՕRQ/MQE־mUGD8Pg PHKC~*H^#+ 9}#+O6kp(>>5=pC}8p>NP K!'@^#5@AT'#5@֌bPh1#5 H8'4cpΉ Dxp.rBf^8/,HPD`Q\[xn[I6b_g#+"A/{=sفѳcbCD) L`ě۵rSl2$"5i0X3yHʛAZ`7LQ߆o&)KAM|\4$ÎS D~9KC؎qVMLLQ4àlqy1^8 .NPM L?A ;m+n7sUå#5U)@  I:D(8HKfZ:\**"r~(R|533QI,{HQM4 i߅k2#,vu"əmBEv<<>DKLCz|uC_{a|-#/a#+D'ƹu ,P2c}mť㕘$D$ꋶR\F#/~K~T#+HF)B~%Lʅ#/|R*v=݆u"H+Y4tjg7 lLBb:^f_sbw'(}ra#Cu%hK"#!ZfUr78f"2b1yj$)HM%{^eViCg:V(cCtfo2P`Dq#/#ȇBP&>#/1 `(A ABn 2A4(cbOMvFф02o4Fd>ݻDFDH[*3F)?erq40W%̠ 0z_7R#+=U;@yl@D-3 U(D|z?ݽ9A(U@&AcDO#/oWi#/&Y#qI.#/1j}'xై{{a> T,#/|04vPaSKo#/0g4NsAs;ױb }Eu 5dAp#/M )RBM:tq{z#K.{ct_,F 60RN̛Tb}ߧD뉠$;~T4s3N":۲\=JuVzIm?{h!)F kp!$7:A}!}?%v=WթI*~NJc퓐{RH@r#/PК*4HҙX"% ga76ىvO!:4U43܍`.$ z2(A #51b c$E 㪷tP{}#/9uYA@U'""("#5" i8JrSC~\萌`4y;)#5D s4PAC:9K^K#/'"%q3q !hLPbduD3#5'T!>`bxÎAWLID]"8Hvyb~#:7@)!6oա67Lé0M#+f޵m+ag$ L9f Y-G0?&I. XLUNԤÙႧyH 8lgdN`.gö!P! '#+&g"j3&j^hl=&t68ƹ R›"Aؗ'1SB#67H:xaZNhj@L2#/mA#5`[4-݁fD`1^iEyFu.u|sΪLqŖy/Bg))S}Olj/l9y$FԎ!#5^w }m&mrc;&BczOAB*0{/N/057ZlA3Q  ZG#70:27i~"!k-L-PDM{:7#+6[(3 (X3 h%;[Ą2_c&)@9p^_A#/ĺFu&$nK =f8 +,uH^1$aI6;#BHpQ,q!.P&X`$JaC=I²ho*c:#5 rcV`"vAӝ'ݾfXf aLM bȩf8scH|#Ov-$$€E``rL19I!],1Iv{~7v{rm)AWY3cUW0crEq9u7G"/ƻG|Y[aXa@jZF?E97=y+8R`LI6d#*?-5N+w(k˿"#lUml!3 Zo^ ٥J˛ MR;N[HkPB>}4ÝVqJ?9F J%^⻝e;mP,Cq4NJ #/kKKmd+NCRg?Nɳʨěksa cns'уE&-/>W CDԆ or]j-L5sMD VC)9 #5DKlpX=f(I 7|0=N#+ʥZ>ad2.#Pc S,6B#5LÄCp%=|/J<6hG 4VD>M5.iGi"Y@BB0>*^#=3çzqzxfTvoyTyFñНhUUI$a!N"/^5Ƚ!it$O^v Vt[hl8/sټ"Ad+O);+4}KgmcZ>sQ/>!l{Ud8,|E9,IR,?4bw܌c1\̽C†i>1Cb0[VEZko0NiR7+ysmq9q+rosY18֌#--OX0w&$h);#5 N_S(&Cd.4VUKN_7wL)#/UwrѶtk[0׹P4<çڌJgb0v]$:N;HpcUK(vpyń[==i7bA0U.8vKcZNMӿt']khBIMJs(qMDV #/.D63f$wFkFɣb#(>s6v4#5Voz5 ]@Erl_8;r\cQQKQk-hnv/b{#5E|duo1 l36AƇ#5cT&)[8siscpx&5_Hp5P]\a#3Ah22M蒦0<1A.#+%WXWlcM;#5#5 vy΃02dDQD06^SׇjF>ln@ڧERf6'7D]gaTv;kk60\&Ɋҗp\>:-&۫l^|#/☤2獣#GX*6ؒ/Z+:BebRQ^͖*\.sK]ǫcl.&9nm8ڈm=M)j#.S^ٮnm#5mTنo]sA&ϧEoqM4Fv|L4%` e:f/F0k}IAfٍHq#e|PŔ9KGC'8mxNDEMd/#/^I{-p;h|9t#/m~6RuW80rXui20uåӮhˎ(WFH&7j!Ii2nzqDr`r(g9+$Bma؞YaZz+-W~fX nsYLm#`#;]vRK<$#+V젧w78k$m'+~8ЎP߅y[E 8c{9 &tiv5sz~>3zw8i Z8Cd䣅`D6`M{~÷.Jmc8205pk85$ZM;#/>!m| mCK8wn2$CFP$6\Ħ~5væa |uGtql1˗m} 3L0HY8b1‡uy8)9m*hle`fAZU\0;񶸓ZܷBnˆIm,tCKGvwr(9-acAɮ)Y&(3$KeZFfƜFQfUX0vM|;'30:.m86CLj1b7!3 [`R2q#:Rr3;q ٛL]҆{Y2S!1}L0lND#ehVN2aa؄!N`zQf#/<ڝsp 7CvMV#z:rBDƖS!;2C:1qvB >Q&FJ!9cn,6کqF\>-"4 2BZEvNƇ璺^M\#"2 a#5U8;HLu6(+!n "6v;]s/mU.͘ӎ!q8Ϧiyy;ķ݁uTdvQvd52:Z|psYuv[r%cPDݾzdZ>Pʪ "ޭY*a('JYlT ӪHlѡbCHЎ>!dH 0·Od÷|N `{z_L;X5`1\#5m)"Y6{yT+.*fwf *[^;jLls ؚI0r5\I3c[9+vAq#S}g1Xhn̄k}xw޳\m۟c(v~Jvo 2i%i=V|-ύQʣO58 -K1܊Niu5g[Wtj#+8Gh>P 5s64=Q<靗+ !&>!@vcRPKVճHͤ>Dss9FNJ#m4pȶck z\ 6ܭCJޏ\'nw~4ON8@$dÝCZahJho^]X; ^輣ylodO-I'$82X '6_J";dy-&Ɂl8ɇ]] )#/Y8!#/0^ f^oalT 0 #/تم(=޹:(6U&e2DeKB9 h))E]*Ӌ%[MFm^i@Փ5)ŘHh{{5Xia፬d#/t&+C=`yK&EםRyc"3ȘmNӜQt/ e]gE>a`0MKh6ox^eXc^Z8u]jH.6QcQQVDX:0DZߘ}7#+7,&qꡩy#5jjh]Xi%h.v}2v1遇jۊA A>xα5@{6BM233:jkrzql`аj9ԶaTD8m*#ΚNqc+|Qq<͜ !@fg%1Uw9x'.{rgsc8C낂t8 MQ BFqckKM)vH=(oy-cb=qTCڶb#5S]+N` f$ ͑݌K#+D4@ES`LD!H;YT"f?؁S;~;#KGhas4 " 0V'\=۹S ?ץG;$#+b;`!&%#5#+e,\Ы~u#E#5|(SPs;OjئNJßPd%sc<#/Q<:gn;䴁DJ'dү ,h)KIBBp`4ҏ$#5V@ 8Dh6 sdNN([yjw0yevpbgX8O˾6hwA) gLڠÏťoj!K!n7x;Ih'IB3:{uDg3qWAeܔ$THGfqKVS9|= @Q ӳIzD÷&SeLVLcH|jiFzUizCFA'NqNmn,m۰ 'CdfcS]x0^r#<(M9\BBR5ٝu(WdQPeiF'r6GWC'6yD⪅Xջo~{rqBSJ(s5Ayɻ=MCa4_Ŷ#/%;kl]F{aCRJKPiLlwe:: &zlP^Jžr5eLCm!j,'hy}!6coG:zer|Onz̦=u2]5ʶ} BXeÒ&Odr`0#5EV|VԚuDm[j-54D],̆1l(/sm4@" R ӏ ?4'B$#+&Ƒu/X=9e+Mj>]qRO#bGB.2\Rh Np'))6IǥI#/jllUCB#+u!ܝOFHo!#QJɎϻ'h;p[oTfl@Q##dTC` ^$"f厠2DIJ" V)"Rh(h)jIIE4D0 #/d=Z=G)zBufg2"5&a'(!0bU`l~~#5iHG+{V",z>;6,!4W#5u[ۓTcILx9x~hrqQ0:vŲ\"j__HoLhIlnaquPH$H[-M[h.4

FR#5ij[HAUB&$?ϐ.#+m$Cg0r#5#2t`bې{V8Z ?'o]?n`Z8Uf+U5ĺ#5 OAF}Fn#/A C1w'~|z`0~J= 7ia#5#5#+)EI!:Ba)HXJXddl _yT_iLX]~-7zr;S0".g}YRvNuZgmgxw/FfxÌحfRi%a=!SܾSi79/IAҦGN3ӭmX..Z3y%T>rzCִmg{~3:I+f"Lj=D=CrJ~e}"S7Ju>&Nu|>g1u&&jUICvyK+mYX|@N8R52ߴ.O =N'vLU<:xލz'OFfy[NcD7diV1K.ܥowޚ)Ol;2FLr0?:ߞ0$t&o}3>r.Klc|1P91$Cu.sNڛD>⹍$3w 83[+O\eΖQ*c?76m33(hŸ-3ˢDH[Xw3 <4ig>c#5ӡv֢9qN,0/[GMM:Lz\q^\uX戣'5jg{-`ʹfn#5lPҮ̩#ط*.u孓#5w1 >q!.:Ht&c382r {#܆Gu^<74='a.gͽuK(t#}qD犃rʒJ.H*4%>Gp>df;1oMsQ ls$C6]wvA/MtA1M\ CFūka ⓶;wX8凖<5 ŪŮA/gE5s ӛKam6!yyu>Xy)#/JGBqt4IȊڇ40.Bf9Q7P8G@v 5ލ$t*TbPR(Poזr*:F4L2:2#+#+i!r#+&ԩ($n?[|1 Td|?N1Vt('t9 ?#ü=&~򂐵ئBe`##/Y:FKW**Ŀa#/QC8xA?>w̬IJv8I!cHDgɚd}צ5Ty> ?D#/v Ϭ.Z?š@o0E%#+hЪ`L2*3&Tax"*Kx{ 9;yA1g8Yĝqꦋ|P$gƅY*Dp6.(:;ĶѩY!sM=E@9$+Ggÿ;o}=ò'(x:Hɏо93aaVXN7sy#+8,jTKb '6Q0hcs92<#/QpsӇ;#w̨M"5l4(Y^K^k(-h(h@6i/u"GWiH-lILx%@6An҇#+idgCAJ ET ?9\ϸpsު BebZ*ɜ ! ^YR}^kA؀{suI#+XLwGIe$9M$X )>}j#Hbh4emyDU|MRl#/wX4x:1u'߅A8<98\Q̇E-"'L#/4#5FN#5ZB6δHt<0F5c+MPlBn@el]cZ,c d, 1Y＀ %((aA@`BR2##+p`no8ATPL =G+(`#/E[6ƚHXpaԚkK|.AB$tz((1'8s)!#+(k%d 2sǥ@;PFlR^O()]#s( i)S,@1["b;Ä1\HDHNlkQJZ{`cP&1)mE#/DC( 8 LPDaCI< 1"'#53(.H#5:¸/zhezzɴжntwy>}}K]A+Dz01#[wBAP@>DmHވ(3#+~3Gv* hCŽm^|dd OFuACIAE#$JJL1!$S<)IHGbh22G%$#c9ҚZFoJHGC( (# 1@$.|dLJ,P4 ,KCB@R?8(b VXaHh*$_%AMMUTD4 0A#SDTIAI0TREK3 QBDĵ$-$2TE0@I C- 1%CIĄQM#/RP#/!L4ADA C24AHDP$DKH2UPPTD45LQI1A I QHD+AJ2LDPʊC#+D%$D #+T a"8HXQ!)FePCH<`F҄D%Qā#/$Q#5$(KчObպg \}8_̌5,+% ZbO&7(n+8(]Obs]`'S‰$4# .yq4})K#+DNbJP'("#+Mb#5a:&:O&N‰3]ya\xb#/HOp0conӰY]:-%$/9 S?(lw@hm>37%J8 Dmo~嶎[YPy"t;즆aKxhКjx{J@."H0 Nru}Qq#/0#+L_!ꏜ˭WF4=與Sx 4#+7?sNb#d*2An#+ܶ`̕~E-#/&Bt~GĊb"6a288uqãMjA!8d]xZ̙`'#/~@P&!)B)R F}TP@Je).d }6vsL;uq@ZjoY]zpI@f_,}Gژ悜PD#+rjw}SLIsoOiDuOiI7#/I+:1 .X*buR/x@cR% OXKI,aAN5GJ jT.U"<GҊ:|񀫳6d)%VVaR#5zt!`<Kר\R؅7l" 4Y=;td^'wS#/^nG9$r8l LGp`jk|<h"A#5#/RǘOcrzo"g@hh(G#=" M;MVgkݚ<5HRhCg#5+Ri,u#+Pk8ɽq恔}BJ?t-#+}rCA0#5@8Ĉ;@k(8Ӏu(SfJQjSIcТØBhkU*.X 66;dHz)<`  #$GB>"Y);aS#%Qrxd!uH:GR9:TM`Y1 oTaQW-7 ՐFj&M][{`l`AzƲ8 `۱t1lLZY) pM !5P4!Iʂ{(Q?du!L"V=,5dC!=}{(I"HAA-@=D;!0##+(w(xt҆:chaYzcz>ߵ]` ԼԮ9UR *b tCZ pq'b1MDF2\0#5ŨkaoZv)Y&;hDh:[4P@Ox4XַhFө<򧓙p[e|Zk]EǿEMUQmly\z5ЉnzlIέ6:sI#/h<;utHM`{HWEV"Zm њ"<ŝq*@*b^k R`[Zhf٘&F `c-:I9 M?P{#YWeB=2W {b.79L*2\Q ӼsI2D #+!5<99$S@D+R!5?7& J#5@}xЅR{L, #5 2j%J>@#5iR\TI]Æi#5#+Z#/#+q0}c<C ETSk2EBSO6u#/v/}&K1cBPMp|ER w$N2- (ZR (avB*J2pgk z#+"cuPhF;#+ܣ°K|#5|4e5Iˏw.ԉRW|&J<9o״gwhtϸހҦc"IS9s_VoUKUM'Κe5*gp;He!ԕ#/UR,o?V$#EU^q/{~"6?Ֆ|ߞ?0A~sؿ3ke|dfU|5{5¡ͨ@~s鮈Tգ2H&*%^4&A*C12#/ I#F}>134L94wlCFc 'AR5{`hc#/nKO3݂IFPceO-5hE%E=(ie89bgdp:U{ȎM8uN$h38{e8pʙ fMN5Hü!}a7>~۱x8pg{v#+:D9{vQ<.@ZZkt4!G(j "rMkA’{jXe6Pa6af}b8<BZ#+ˈP 2dpr[&]qKKE' p4MI`pT12{a>#+11$CK4Hp#G6*imPE ![a %eqS[tc>D2T1)t营5 PDDD&#+H mͱb#5TO=Ah(" t#0WACe#5G}2OG#+BGsbb{y#+!"̀Q"|g9LI<Gp0C0׹C)'lj>6#5Pgo8aVԈ 4|1Q#@mu:)1VTU#+&M$&t1LT+E/Ȅ) |_E*kUJ^8MmCBFH }[Ø%=sȝy*g31R$c- ;bth3B`X@%]+QCДDH"% T%P"D%HEBLʔE:kT4B#+i#5B4#/!HIRP5K@,E(#Db:#+,B4PSD4AET@4-#+D4%H(ZB#+JJJi)F()b"B)) N#5D$"m#+$T/ Aa3w?2ud

#+9,9Ȣnv _A5UP2xDNAR9<9$Sl>~ &L|( h)&$ ")h#5; qI^7i}GMq #+7*?M;08I #+Ɠ|}E8bJ<dyuc;#+$BF!G<]R#+:Xwck4av#hTPJʨ[C:D:jHPɇԆ)rCί7GmOoj=ۮqps`E6xN1fAHT.Wfnm(/~hxnGw 첑#+>Hl=6vPdI#/#+O~4*&#/&6֌6@DW#+|^pB+H T#/  C$Ԣ(@c _D#+D R)CTrh9pgѦ{+Zi'Oc㚸JZ4[8Kх*Ru7N%1bQPxnG1>'WrȌWHI9<<tGa|>t8yp&zr#5x|~z%Jh#+>f#5CX@ajHze vBNVqb"715dtx D**$@Ҕ#5{$ t@)Q4#/:)#5tGQƚTs !lr/,8AT@s`bb"N!b@(CCX U4DqETe1 DDF˳$L$E&)iV9P>TJUH 4FJ#5GH&)@:EГH%*$ͦ)9b"R%/jh0A;('#5#5dHD `yk#+yBEi"m{Z;ʋ дD)Wtu#+ H<5tTh8VhFS7#+ JPW,-9:D:b*1H cEhlؔ!x(u.KQ0(F;7HmϒCٲ];)ĵ=+Bd!#59{p#/TX!sT=gaP) ~-A8HdXv~>'WwN+2yzqOLC J(a LvT|j @O,ExcCe.|L+#/^gME(\j"R)9Ð@#52AG ŏn"`yy)I'Ff]iOYb(_%fR#/ƇApr#5僇Y@"m 51U??5tƏ0ChI*%Le#Sbh! )(Z>٠bĐ#+I?؀.mM#/'5c&)Z("()BO>޿i'̝xQld#/a*uJ ,1< ?v_#+(ą۸#+67c;9x'@$O#!]X)}oM<(4wG7R3VQAy)/6vwƯOفL>4aQ}t#/wLGlFz4<&lmzb` , \ bvS37܀Pi;2㏘#5`aUT~rvz;ʔaE"Xx`PL1 M/z#+ֈ$DBa'#+Cz=U-4XЇYiد\du"#+g^Si6(#b#592bMe"\#+cJ$" '.i?h~TM@#/asO9A4BahÓ6GY=|8~%Ͳ\@á-TiOA3)DNLβeU!;P΁(jH#5i)Z %*JB"F"*jib"PbJi"()aaJfJFhJZ"#5(#5i)h%FJ(#5YB jBBOySC!!A~yU T  DMA "D2EIU$HIBQT"Q44", $),TRDBU5 #/PQS4L3%$KB!15T0@@ECU*2A0PHI(R MT,h^VYf5ċe<|Sp#/Ӑ&`]܀CP Ԡ)J~3 S#+Ah@1*Bv8P; x+&Z(#+@|6!:baɂ")L)0 H9hj$%?)1PSDA#/v JRLT4)!ER4!RSP2HC*$QlBW-3iRg XOWMJq M!X̞yCAc[1l95)֣`xu{OuC#/U"`AT??r#5D=+H vcC`^:#/5}%3@DAE4ACT%)J!@CIht! ӆ >e_ϊbfC v9x)58\;q'%ga5fMO8C}4d=ybOcCaS. #+tX5$ޞ9h] (3fSTu֮C9ؒɣTP[IH$̛@#Z+RVc`a9, %?ϐMP:D6#+ɰJ#+<`B#cfn}lD!hy #0ɔ8bؙ2ݖYe#&DqS )Oo?p' ڨx~H%jOѮ,8Ur9l?DO`)HB0UVai3c4XbP*^Kސ HLSD#5j$j(bR!#5*E(DFTQP 3MTUSU!Q)uzu}Ig#$gpA-4W쟹((%:KAm#5%&`((ݽ䧭i#<;45ۭEMDd]lI?^ǮGZ(RK$!@ƃf<XƌFqeDrzX¯Z#MK{Z˭SC%#5Qgzh.cG*W{n. 7tAӭ {J"~ߩ„S4Ѣq@/`:'#5L>F;ǘp彄R; SJD$,bSE-F27zAFm_tPl_Z:VFOzgE#).1L6BLʜRB8&Q `p8c`^v]agqpxqE8#5r6V0M#/"'#+ͮQDŭ:/Ak Nc%4!*\'\);ܠ|G^s#F,MG4<\&KG$f0+Ш]jF=iZhj:8!8'fAqؒDH5jGS3)RUcwp81&D\eMwmm;IEЊlBn 1Ct\Rhls ύZkM7r~Ao,É4)5ܦ+z(#/lֳHSWBhG=6>:ӳь;ER̍jAM4q ֓4ڈ~`v#5S3IrO<L*1E&Q U#5/7Ψ*j\Mt ;7@}6 C&3#+,l wvdXpb#+°Q*z%0"8B/ڝ ƉU O C#+ۮ u~+S{G]|C#+JQzXsۻ׃0O@vR0&tJ?Gg(uq{=%iWfyQ]_,ΐ< )@"U&"RCwD:֏P>RO@ІOsM~{{"r:1'#5L@H#bhBrSSːH-$/A׸SJ~?*$J{7,OYvAAIO\08>qO&ğձ3Sv%DˬĴ&1ȊV5"h"#+׼|(m$\?%!&9zY44h8J! 8NlC#/#@P( | B9d#5TCRd(J4įkfhXQ4BzM\>vC}/N萨go bE8BTm-C~;>}?pe7\|tp,'N&+]uvpfàj,a01`"Fwq^c.qᗅ( h-OFxgǶ&.gTGƿ{`/!%?K"OPeq'kBT,ET7M%iB.0EwcjKXQPO]t|97sOAF"U;av6Lİݿ̝vmw$`6Ǫ}e0u/AV$X֓R?u`ܖϊp%㽜%/ lqTܔm>0#5#5 S1)zEr]OA@!Ar"OniPqhJH\Aj#5m{ 20. DH}Ivx 6Ԥ;g!9"!3`"C_g!?d5"RRa#+ݜ78f3{^Z<T6ayZ9hH9tkZ.,E#!tetN]#/TbhNQ:"W2 v!T@BEM#52!! ہt#/43oq+z_q7>jy=Q m|wJO1Puhk]N>@9%N\e9!9*A(ӱ ʐ#+xȆ#/0ŒiS{f#:ԒD,:ˆ(7ՏMq#+<.#5ѲƘĹ!$ //#/Ut*;{a0 xi\A藜;*EO@l_b#/'dc̢hWǸ#+?/ا)+adₓuycr$=%+1}+65S f{` \.#H[fph*;>d|wvS-Uq#5#c*.AP[#+䎗CQRh5O>BFH)Ƿovag$tACsS{An">փ'yfnt|CPS>k;A6xxx  G4v ɩ'#3sqhLO*2Pr ɨ9e|eF-VQLr˄%^pK\7!ޙ 8Z0 G9ԼpqcXE))WV杖G;3IH\I)2a$!)aWPBb4U\ h';"ߍe1LPl,ȢEClJMwDc! 1bR#+Ly'Ж4^ Ӧ0딪390S*# RRIS(g=e74z((l1ihF`h#+y/.J>O$PA11VU=el3zbpS(#5@#5~||k*֗* 3ۛLj1?rS{ @"h=8!C#5*Pvb:>|CP^]"v|NÊBj$qQѧ=NeEҶ0_wR8;vA>(aYR&(va^Hp@z>:`<#/7/i ,%z}b ʇΩ3&~Y(Lmj?!tRx:;'X結  mچFd},)>D0#+#5#5 #5)2Zck1,:њș6pt6 smqHтqЄ3`@&h@ eևt#5p%iiEhM.u:F@_'ll3kѻll#/H{AkP7#++'"cfx $F~-Ym ""A+rȅ5y/pTmऎ\(h!t"%{ 16,h5GZ[džkb4{o9B9S/ 4-8G8GJYa*љ(#4SEmLiDRfsb'aeC"7RC`#5xg/š)2{3SQ9;<#/!IS;_owtC9r6`Hz i+1qOs0>OpP6:~ |'|s5L>q&G5?(f3Y q#/Żl44|/69K%"6LoL ,e#Dpqmbν:hy ={96#+EB#fqSP#+TL9{xhI9s$/(/JvTц.TC2 33?H5L8AtWS=%eI^nv,Ǝa#5L*<]22 ,`DSZC945,1n&ƍ7xQ P\W99N)TQ"@0e) #5Y FJ\}qf"B#+XPyÆ ><%h8 Hb@%ᓁ  'H((,` $e&"X 6Ls:9節EJU :uz<<1{YhLZJ֞pdïyF]Lv\#fODET+Q<~#/So桏o陋Λrʼnyz_2i76}fӶi["=c[X4^y#/j'#+H7/f1m5תh)Ē,;鐤P'=ipoX{s7#oI0ᒎ٨fhzۤy2޳-ٱܺm=I=BWW#5gc|8|jrB(d2,םUcim HA^rm=XFǓ{Q`k#5E.rNtiVvj*:랥LC5zd#/>$4ml׭!Nl+mbŃ[\J2%t҅MgpF+bU-8L6ӕɜB;OñړZH&# O0<h7^ 2SI.6B# =҄P"o hJ? #/t `)*vG>ohj6gEp%׆V\y8\iF#+"q<rsbJ#Qꥑo~?)(#5@PiQU@:A#+u4#+tM,ETQ-4#\K3k]7CTgI 5>\xcۜC:4L aNHP>ҽ ]ڪۅe^'adJH҅$h0b@I7,DffkP9!W2-% RhvTPKlB0$NJ!JBC#+@>0 X#+fʂ=ܝb(=DdU4Da0C*C#5<+DH`D`NS ,#+'O\[ra 9TUL!&HjT1BMÊꏧ*bhŜeĆPtP$:M C5(RA#JS4$UAdUT4DEQDxءDEnFA$ECLD4-#+RAKTT4%H4(K@LKQD%0E L @PHy11ԑ[}Q_yF0#5⭀2#/ Ed{k~޲CH&mc"cMM$HS@(Ђ$F{S5]bWմ_<|:NvODu#5 }Z5],%#+3#+\282(jBH&i@p>օRЀe)A)4h4Ģf5@iW*%(T%$l#+myHiJE"0HS" 5'iH&"#+*y#GJ h?O7^itH$x_cP ss7MHhq ( !t]ۇHA$!Pً@x,[0 &#:0 H32wI'Ooy+e)eCF%l|åymE#+^n/_>)Bѓl%"V 15d"&H-9iCf0 -C */v`Y0%Wd"S} g\KAceӤaakn].c!.AZ#/hea@.#/G^nCX.L#I~J 7B0D靮Wiˎ#/TGTMҨ%!di7LQQ5FJC+X:br6Sb¹4W3p! ϲ#+6p#TbP$QM U(DR2d^Be1"y'*vUi(@Qud(І5GzMxqi NI)T#5QC>'&\LMnm|83QV#5X{Xhi"8R#/ 臼2r@nf"!ʻ>qO }M#/9+b(5bUQe0d/9KBatݚPdN+4HUu>]#/:#Vi1hm286Lfl`B[$,,`q:@ȒA=XPBd")iJ*{loCZI3(()΂k}K9W(]q8BRiL@H4/>#$a䆴C;R_[F#+#5Pa$/c#<8Z(JvF@v5Th@LQ@Ø8U0.#3tD uUK+j ?>4+x"@9#/TiE_}pm,9ɴ J(+4C ^܀ ˠp@_:>m&pF:Ei{3Уځy|Rv.T02'W}lz0jR*T8j]#/#/j~ Z!5%8C!H{CRO7@߾bjIfwhB/ 0YJo669U#/ЊBaP1"ؑ!z>zGzzU#+Ѝ#+Cj*[<1iK;# 켑K(cyJmKpU| h`o#/R:~uy)ʄv籮T#J-Њ9E"I֬]H$tqO衊Ϭ*MP1vE*aX*'U#+@Ϗ (o*cא߮&Z:fM$58!'KR.8#/`#+ l™S<`G>ӁH')b!T7-ͶG hRf֫@e`)(ҡB@XcE<1x4Lr ls)F˃IZ1? E@LC@I-D \04pTTKAkbNU4rNr99&c;PH8E>Ox J0v '(B((x 8#+9CPC!)UCP:#tO/8&#+`Б#/LJ{Y6TPyjeN>iA*#/4*hdbt).EB쯴9r0.n;$'< jaʳ4>)GxQP+}B(ҧ:~|Rsp@͖pK5܈zߋ!E"Nֈf@#5FfQ(S|zkS b!*#/jTAhEAM(|@ P ٌ6{ q:PTd$@(A{'~(DEAP!J4"1#+A)H@TH4- ''9"d/t9f<+DRS܌Q#+Lda J̏ zh>A\F$ }[1pS8`N-+LDN쏢^IH<74O 4$@ЉO$#5[hnKISQ|! % R7 Lp*Iyl#猝s$ۻҞOaBzJEh" GkLLAJQ'|߬#+ B=&uA>r^k@@|&B"Jdd8#/8J =LH@>iNT6Љ4S"L1^HJļ]CYwhEd5+f,wL6P_P#KQ}C@: e\$ I#/);#+#+d&fX$I)RX%`P`" #+ RB]E,:u#+@L S1LZbR %LMC#+ fRdP");#]kMeOTQ/x4sj#/"=8b_*=;{#/PBƌ}Qs)1& EݡA4iP`q߃0I:GKF%#5}u\/ă}v#5H $di(Bo\4:!cPW#5]c!@V","pӮx#/VePhf#/ i}!3x鐏N]Cs!l6AJ+@#5rFMcnRb'lHO#5QtBW$BBQfh rSc MP1` c1n;@dNxPR SL>Xa$.e2&U'vOm#/]:٪DDLaǗ#5i4Z v.F})Dӑkv0$#+dqz&E&'@pW^790]wJG =HaR!E E#+byxj>#USHѠE6$e mT%#+%0L@4)9"SE0b18'/x\B8TMS)G@wJP)TWK݉=ەޜN.'(ޑ@٢`t;n/U<#O>0.T!#5G79*ِVLk&t?#m?+0TaMݖ[tKROQ' ΃S q4Gjr<^X.AE!NJIa2Ť?Z*5EQ!Oϊ)L#ׂ?# J!PhђQ)O =UMJD #/.PIt#xG E)Z-h)5}tʒ(#5A˞ud@Ԓ\w#+xď'_" Z40}iS82i#/aJp>}=}LH9@$i[o0$!OΓ(6<_siY}cXkI6{ Ȕ|s%씩oTc1rj3qQILmEaDfFiS!f0Z!Fr;* (Kit N ;.I:9ŁkTcv6NJ̑d qFddM' 9&z,0m Qɹ+`*c&F~hC}$ŤB Hy?tLb0}|Wpy1fXbv{,MD>Ժ#/DL糍qh*ۈ)H:L6rɘ_o?y+Ds4[j&)&.I33c#/ms` ^sC=G#xIZߔbUf*qam,P险y) W]E4 bUʉ;,"Y7ы[ sUژ^Ӎ-9}8Sl i6s)4~cɝ(S#+:v}4*%g>F_z4 I9ٵ fmؼ.C#}U:1F6I"y|!ݐ51Ա(6x(.)U&w|1QEeST2$g첥8A1 n4ܒ1It0kH*SZ3 FX+4#58m|pwc./#+'Fֲi#c@WBpK8{! QXY-=IK6Y#/5z\w@w3oSvȅ$?IBGSb a<"!a9u_[my< aHt#5φrD#+ er\Dİ0*ၘya>'Po!ID'6&HA^;׿I>LތBR:S[.ݗ$jU,#/>q/>KY߇$eJ"H}WI38 #>)YSw܌yّg/"u{W QvV#/R,%hgU +dkzg0adh4Ge8lh hb#5<`#/)Hv}3!S䭎I^omo\l=~#畲j'1U##t{ȹܳkiۮ)FR7&XfH怏@ʙ&:7]^4TfoSCPш Q}*I#5AJH"xq"o04Vt}LԹ"7Lm}Kl 3Z#+g0ijfZQS:Ncm2R@Lҷ\4OOCYN*7#>xfIT68vZI#u*_b˭[e-BQ3RO#+ F?-g/?;=#/otl>`_Bdp54o(m#+OrJD\?:p#+QP((Z#5@(`dDi3n (>q@G}^;+55䏆#5JNP^zɠG3ǙЖY_9h<#+!|#5Q({ tMox[dENcrH,RIO(J;dBz?x Ioݪz"*TBȫ9{^=FM7ǂ="#^}#/Jn}ۃ{Nي\)0Dcs˶8#+WJ&|>31TD(5'%&9@2x ͍2>׀o<9'ttF#/FC@^# +=:!\ӡq$2OqdvՎ@%bYT0u9RFGxTSyڌ}Xr~kC\' ;/ŖYqK'Ԅ_{|7#5#/3G ߃+hc19;]2"3`ЙMjky׼v L: #ٚ!+1.'D$npn^&:Ct.gvFLB?_‰gn 6 hi>_2B&߇Lh"n3r5qx =N̬tRuCkhlo l,!k~4L1A|O^k?O ѭB@ݏb)"(F""v1D<ℒD%AZg][ZSQ5UTJS0Q!U2MMLAD%4TS%LECSQ#/ITD-AQJ~ zNĦ B|vX#+$ HC+vm+[cZzZ[ X@|R}zTy+[?[; t8rF'f(ΜQz<Ŀ]h,=Fw!FᎺȃ^}ڸkJ M8U2}A&}Z.gAc"m!4HIL?JCܣC#+ ?]5rhI#=">{dL4kL1qHc-ezZF`q0 fF#/{p8˧=%Φ-k]>M̍/Fi" 60`IR!KYl1pE3oȜ;v[&lE|I<=RiaJ ƌ#/mC:HpŰi6KPeyc?K1Y6E>#5 PC -2KcD#/\|Ln#/nSA5SA|n.NA2Ibѩ -NAC5“Z<'$ffjhh쎨$)"*aZI&")>1nb6A]c158V(O`)v_7I(H)}*9 )@8ݎqXLt] Ql놵UcZ%pDgf2ɢ`+-$HMG mV{D c9 .#T;.)y{y":B/0<ЇmPVP@뜐""8\qFgJFι L[@(Jdѿ{=P>1yRI>l>75BA$)A'%BG&qB`2`#5"()Q@ECBi#5! "(6q5L_H% ;n{LDlD.pήFRrAW!#5uטuk!#+6iބ8EN]$$lmΉdQ1 婍hve8#<7AޑLG ':Aq#/9OV+6qEQ}0y(]4%0ZS'$h")'U"|<\3T\VL6>xrrWOw1BьD!{rSLJ@L6cCX9͔EbBÑDW. aCxU mO;jtEx$8lEb#KBiy°,WcLT2Ic'[Ri-7p{!FD֤mm$kLWycm=K5iي23 :'eHQ*$!/l{[YK=wJryi9#/W3.PtJ&.#57cm0w/tĎxDʂ#/d&`#(!kd#%4OWTva&,q2Y+%XpkL'b#+6L ^#/c>ZGLsCb8a&D,+)I" ȍb6ě@ZJGp6 Fa$54dc(XVDPf#+T(D0L6?$)m4HpBBG@#/Cu0*`(& (UY7(&'Š4jo.CKA}h({ A̝DZ8c d1 {'YCy)-[$MjcfO]\>?#/Lt\\Vd4t3DѺm jR2c0bHф,P {u{#/kzzΦdAvA#5-* D"EC1:pɐ{q_ l2#)"Q0/s 5;#/_ÿ )P!KA8M#+b#/fP O#5CE1R$Joq+'}"_I9mZl1s4weWDz ]fq6#+@$He(J)dB!D(@"Adb@\(=ylcxFG#5d$n9;&ժɪhJC Q~ e᠋%FѦO`=%Ma1 aۍcWp~0NJznr-˕#5.qu8>HvZ ! .Fe32Nwls](A55Qrlp>;թ;o\tDH2zh!QHv8I;Pw=xi*Sf7Cs:?ozsx]RaO 79A'P3C6`ь: Rϧ<=W-l_f֘IB"S9+QHàddrշrBF$HgkyMQa0M$CC#m*ߟ#5&ɥpN8y4^O(|ذ'b(bITy:fMjp_VQQ^E"_VhA/#5%z4gWTsu8اO #/$xC$P֪'_D>K݃M#5};pnFq&2][њ$#5͆jϝ=m~f *UTܵbд`0WmlO@qwϋ=SR@gG˥=U*_#5Z#hzJa{pt#+$ |k~p_} _8qFh[I! q3?mXC1Muh PL|*#5P2oxOYON@/ߓJ~c͹,~nMf "Pd@ʐOwXL>Z+sN:Z$%=RCeP'~nB#5?DŽnuF9xT54/VW2=,`uM.RF(P==;+(ZAJh)#5#/kAڿc<W#5ڢ/2m 6]@Gd!t8%t<Bh}NдҘҡ1HHtCZ3#5F"^( _ȀC|}fƐ!3gV<# تK}sAzyfqz8Nϫ>s##/ $>D˂x@t(b?ȋ#+>G#+GiNrVĹ83X@i0xO_Ӄ8[tXdPkZW БJ+RhJ>#5!S)IT! &w~%d@!-߮U?IUc#/:,e ))T iy}3D#+]"HOr5I#p#`" Z="VA!0C7)i Va P9i#5iP)#5-@ Bh"tGdu '^J$)=EjS jRCB z*!"Q%IBi"(Z#+!AZb&FF#+iQz=ࢀlԨiVC©*o!_CO8C>ݞ kMp#5kv0>,l8{~2 ȢAy#+Bɷ68ޣ`%) !XA=㿹7R4 P4<>(}G}g pxv$@B)#+( |+PM"iP?$&YBHbj&" *%) *"&(Z@" iHIF#5,kaC-rAێFrÍ#/r8Fy#NG%MUP:$ ݳ''w#5]pGg\ikZEӦ;SA&Ǽ S!GZ!HauiFʠ`;Ҹs47/6!=ፘ'F{2F6C")GCMxϑ a#5HEiH"H#+) j.a&M"ˬ#+WۇBLس`;pjd$=QB | Eb#+y'A:L;mhTf;:pq$?"SܣA SIl5RyU5/0b$0$ uO;Fyre3(<.cu>\r,g>.u$]&_fM4Qjq4@$g%TL:͞Y/{\샺Lf.}-#zbM5~9%Og#/vvtmۚ$_=K&Cs!no}Ti q2J^7bs6^/ǪE1]V/d.~[Iێޗw祥!/N;|j&^f3--׏[#/\#5\o Q9뻤 ҍLvա߱+/Ŧ]8m|pv;U#/J=r MJUÎ9$'iPe//i% B dUAv=*ER h2Yԋ6.Uy ̈Ps/}~ʙUg,6>“w9B[I/VGgӊN :|򤜻>)UUra!^o}-Ovbk/YNWxzv7TTk4#5Ld=iSnMe链k/C3e_8l>[)v*{Q-.ۖ:ӵx7tKI>`JB`peZoLr8#M6.>g1l7rto>äz-ɖ;m/Y{kWdg2=mH\#5KA}(5,zװ;p/5Ì$=|3.%`#+j4;qS["&6)|.no{uf,js㉷v50 7JM3&cfbB=C%I-K k%,l?Ox8#"$iʧL<+&:|V&U+1T)A$#5ڴ}{D6q`"q3}[tk (dP²I_{7֎'#5_9hM.cdYұwΛ}FQ[(T)\XჟMQr"ʗ.5ʲmp.yTEf0eRI j1D$y֖dTHЩ.uŤ,ml=Vg]? geKywQW_~-cy+޽xzv;T,*O_c۴lL9|Uxu5 -5: *T$)&+*m#/T6s8;]gn %s1UÌpA#{S݅䯹*LҡgE1ģEN&yQy5n/I=\NajW3+m:#{'ɇ]`=+|3E1=D7p7( :g7!e#BX暹ywƉ(I3 UPV 6܍,'o0:i|0f9l(2+"c_ 5h^9=H}9b91lsCK%ϊ=y?f:P$TKA,UMvluXL8}Kӻxn~=CY#/3Jdžk&?h_Cϟm Sl(Adz>cqdDaavPt#/<6є೶t3#/vq]BGwG`##+Ѡ *Ӵ;-YXϏdt)O=qfqUdqELזGrP"o#/#ګ ׊VwHh>_ GgZ\=h:\m41G#+(g@2`q㝘xn*O/>ȸ|~ *t4H 8._W0TD:w4U•bKxJІb:U=>#/)͐8䷩vrwTmsߝ7+bD.gمqB#+54cJ,I)ʁ3ܕ[QLf6e20f{4P#+b#5HP%UTQǻV&D;St́1*ueHi& xG_/XII`^Ng)8],v.5dEdzp{qQ-Q m+=MV<^?{9QU3fp>p|\0hL`]+&\(>5S>p~/5od5af.Px SXxq=$%bf#+7v1J/6A!2~f.˱Dog|+y&v8idUCcRI}s=Hʥ6PU 䊧TP4$QY@?T{DLT LSɊ k2Q.Nt+eB!~j#5> ITVZ0L#5_'Nkh3Q9%Sj-A@˄C|Y+i'=4}g`^2<AoQ8Ç22^vf[Y'#5ޛ7+9c+Pj `X21ٲoDkJRrdSHc7(G*(G %jt8 COǘxNJP>@dqҩB(#Y#/&6SP bỨ$ʨ  qK '0!3 O%$ӝسo9S8JFR(Q]f$yWŰcŏI.9mh^ 6xc(~zU1O4Gg#/"(?\#5i#5ZU?*R#5PAJ@B-hIxFֆEQ jɈQ@&LvQٹÕ7#58Sbl#5>8[N)I:ad15ٙ\m<3$IAB+m-FqGH׮W6"aƜI&Ox);&Qz6(0RyU%Ü31&1Xݣ4A#5F̏P1"E82HD1',D" at#/Cu×*"#5i 1jI#5 h+hl!4mq}.` (0}m+1j,r3h2(uTQ6=ss0D$pVƝ`#/j$Sn],&8rN: m#/ʥNC?7]-0i8RS(JcƭV6Ѷ+UwРi֨b$mE"$)GB#:8ڬl~x\õALt':o#DD͎n&;UZű;_g{O^#+bCUxpXϯ={hk-8r퇺9O6(.MKrᱶ("1cl9(^TU;sU.܂#/Q]M#22V)7j][H1ӋEAN0};[4ރع0'y>`ot|2' ?۶PRQWȜEra')]^R"pM(1p9!A$(C#+sk$F0}Galt!>f( s%*nY9rC^#5N^]@}waÌ'Ij^=Q%1Bt0pC{؆EADAUUzmИeW&]Psɶ#/oqi{bW/^R侴u#+ u{Jc59B(-&"paF<ŝf3 3Eń0xsgSI42TDT(cXBS`zGe,6̪z{7ێAbD#/5u1ZAKa=R|?= %). (l'bb36ŪqGf0,oƤ'aَj/,*DNl>kCYPL-9+DP#/S@8B:J9Ļdi9q7#56۲.%0[a#5i#5j.H&uD\LѦ,ES #/BS=tOB~|Vf#+(xK {8<y0gK*߱EX@ LSPEAQD $@B"3(ЀЕUU$SDT! 5*I,- T$T"$2#A!AM50QMQ1A-$4RIUM%ERJQKEE4HULE@C4C?4;s=;O7 =D!NH{ C~t&ޫqcdogwv'꾱V1|3XMpp0a6BAD>( 9$Ny^ùۄ9L(e^ x!P4Aze݃x|#/Vf@m29)co=5oYӢ#+ >'F%?-Jji.[#5(5W8[auE2Ie~P>V؟ oQ$-.4}`b"-{LzzȀ"\*BiwO~s y R\bLA~83l,UYÉB>IĐ$KN "w%#]8:HoFP(1UR^ Peq!xY Aj:Ѿbl)4Ms<';X".=D%EU+uh8%!lD]')Q@?#ͰVRm"\<})#"!H8#/$Ϸi*ZLDTxzGuStL0pT_Tݦ=O K4v#1*#<{#*僄X[p4K *gybk螃%##51X@'쫩Ս"$B6#5 ruV5Z}mBm;G>+7AF9}x2oh)b#"Ur `=` /0-CBpwᄎ*vcEeU"&C([#/q%-1Az'Wy6ޗ.8?@=i%|2bRxg;Z6K}sÎW!LHC&G0q2Q#+ն+*R ,7 tN9`">M7fvL %v/~ˁI}ݟ>wF#+0~RFX#TׇJcm=ģ2i+$ 3#5{8i4/ޫMc4߬43tN-f܎@"N[fxK'٪hoL3Ffm~ $L#/9olu8zf+9ƀrShelWvl6ýg,9/qi6l]aNٳNEĈvNîq#yk܆jWN.hVql]3Q;qrb"tRN,nIgv^.2}+Ë6oN g|Ru<܇#/ab3 b܁sqCbf`qn)wXx&:0Lc}5jL lSqg|y\#/"B hGK8NiiŒUn!Ƣ\enI0h~NdQԙ+;E'%mؠ1r`XaD50K]iaˬ츉hy#e=|6aIԳ ;̐bN=*C۲\SÔԅYjӔd馪#/:9qK3vl¢8$'w2#+`@{wRU{;,n=ѸIJ4j}Gd+Dn[c`sgu\-CLBmgKs [h*7osڙ(δ(5%"ā<0u2Heiȡ:&\w}seΒZ0.wj>pՆZ٤cHh#G]Ë us}MILjbU:KC4;wwL3eT]LG+9Zmqy9{~9yءC2#+4FrBS*|>)6Pi^755YDalՌ$hNT&Gk4_uurHe 2h^liiq-5Iy#+0i7-g[[,kCykI1C}VXCZdqRD#5sTlfo13&+GU$֊1̣m1i4m]m+CL,q%Nݔg#Ĝ;7',0V"RƮi#5t|.5F7;lb*6ݻ )d6ѵk0ХIR#51`9+pPol#5LbACܭ|a`Sldj N!cmS))`q5Lds?ڗqs L[@ }yM6E#2=ZSrKy9#/@2`Pn?6emsUSvEJfv[ m/>4Gܹ5:!B/=ľ bF%)X.΃Y "^#5P%HzǸ"t @NlF5@IR2kViջP\,=(łOS 檝ޱKF)UCKcig3z 2#+%ћ`!N:=PUaYp9S.-Dʹ{|QÅ8u&Fh΃)g`|$McƧ#+96k X)]bgS#5s) ##[L93Nc'߆dޜ}և;Ps]U.#+dZ,i#/9N͌#/tP3 lͦuJq%ɝOQbKYt5/&\aelMTzV1jHIIao,(+ixeӝmr<2C.VmǦ86YpF|z&:4^&32 !jxwjiwѓ}c Jz’$wى")>wcʸd$=,kD::n݊Bi㦸Gp$peX8R<#/>!^l!†@x]p#ncƣB3(1٘%MO=:\3/Ii-B4mE`#/wm~HՁok;ٜ#/n#+qx0Id.ChWu=[S30vy|S pXx #5Cx~zQ:/b&|p#+#/bmF]C,#t{be#5: tMdيP6~.LKe*(;⯻YOzتЪ 5Gɡ1s YeK,ǧԻsg'cf#/$Y{J CL!ܳ3 sͱ7.!2hesFe`5/Zrp{;O毝G{1%' Â){c4ĦȋupX㲎|'( %COS/kOuĽL9Iޙ7:Ψ/.CShT&8ͼnM*%55$Lji6XW[d{c5STd6SM ޝ"9.7:A`LZe I"v`H0TRIC-5\7,w{g#/kj@%yTF)*;Έƹ!s^s4 (,Cvv=JDP@il41D,F#5mq3,"xhI2700 ߇LvCyjv3]:4ڣ4໮ɕ40,2JAzo|j³.E#+Mrt34: zL'qzƨVkC4|=JY9Cf80f#5*@"V룪l1:A耑L#G(K3ǝ$vHФU]lQه`} 䍋Uf M7 2\g((ި]3qs uELz&2BR t1 /zWe;Ғ:x`6p`!}dtc :vq4i'hzi1A-(ۅdwj\+ɓ)mzK^6Ȧ!#/S$Ä~xPB՘gsEU*@1+ޡBT19d! >|C_w2ıJG6D@L#+D(~O}6$pff0#5hLM=p>`mtԡrA ?&u&1E)#/¢܋q0#+lSc&#+S{q3J"P CL%~8\wx@P>D%էb_bդW2u.86$CҪ-0 a ¶v(~%0TUs#"( @QT. (Z%w)iy|F^:lE.(5{Da>DP }ξ'MBBOeӔ/!$M#PpG8>%)!W OhfX&#/9q&B's wdGLNӌ6'͈(Ln{ݎƔ1#/\X.<=9+9ȻW@TEkVfs;8)fkmc< JsF77#5#+3DD11݈x`sSzEZh8Cp1S, lUqAq0%&!2N0#5}8Ce8|."8B'cfS-5S6g^"m@ Ɩ*b ]o5Z\#/!$ 9H,IŤN3y#mNALk#/)5-)V18#5;(-r0yp#5=sKbqǖ#/.Xƈ\,MwnX@MB'st0#u̙eƓ$%dݍ10Lː󜙆22d+81,"6xnsu1bzICtq2b^L$Зc4C.JSWq`;r ةRtεmCӢ-msR,(-#+62P"6‹WaC9p:JX1N=^d:Ul`:jl\`MjcAQw1Y |q Ӡ||D2:o|#/9˖#5)n !?@񙆳 M!K-PāKXHT|KB^%C$PDr@hv%N@4*R-H֑)QHER#5B_yQ#+]#+8#+Z#/jn̎1)\<@Qw&#/(׺D6#~h:dw9bKP2"@&!FW=gz\#+=z~a;D#+v!#5g7$%`!&$"Hf Jih"b((V8`4h #YR0`)4i"CۥM`7 &6 +|$$~oOeP!.{ 0|xvHJR-PΫy~CĶlD~/h|{|b.MήUԥ]7hx"NnZ1x6K,덉*;3ʼnbLr#/YNq1I۲7aGh0ٔaHfG|E>,~T2Vj4D;,p̆R 0)A2DV?ane6lU&O( Γ`[&Ă^$Q#5tAQ:^Ǘ$_oc=gjBf(x`eb'gC9N,8 k0ԅ !>JA7AK_Xyl_NI4V^Ԉ~dU@ݕ Ah6*N=Iyh#5)Hz2dOD1#/0UEZ#5! |FCZ\#5&P#+No|чw|S5{QuLb#5I#5^}wٝU\):y+, F`":\ `{\}%ti4!}ʩ#5|9xkugh(6⑏iI LdCTQJ4)&&O|.>#5#`T#5OPq;0o6KJy9XC=_DC F= U#+pps<*'r#+ʲЕHz0{AШp$#+i(%B(±1"*y#+#+\ LUbDZ14,h+ ( LD46]TCQFj@9/8\Ի#/!pxZSg&xRwb DQRGvUAƐ(YIDHP1 0L#+4~M/5羅 d:#/H~ºkӟޗ Zϟ2 QA&a֛G#+:^#+RLR!BP PB+!#E#+J ( C@ઝخ|Pv>#5"3"/_h#5J("#5J*ZB!`$*iHuϒs#5`$)("JYH&DJ#5!P5'2HѲ=DU(J$+J)A#t'Od XdTJm9"߀_.B砂#@d@@B%4!TUTt W+H4ɀCvr!t ĸdFŶZ̓J!#+ RCʑd#5Ih@r )^wZQP #+bA;{2D(DҬvF$5}B#+( ?Jn$*saM<^_Ji+&r;l<0 8"#5\1¾rXpne ^NlDsFadM(Pdļg.IcX-J\@vltJE#/#z>DK>T |΅& Q?ixO:O)Wrk(MQ5H_=~9 ObJ")BkXĆ90FXCA%IL16hu[MM.ZRJD@PBQIT4HFؕj JJ>#/P qt=nx#50 r#?E^'S#+_lDQHq,3'q* b4ȳ݊2f`Ev#/ pcLA2.;.aPZbfU ǙıBHu 37#P9tNpo9}Am&hT5s.W#` T! #B"t#5idPP@0TCDB4H!Lotw{n8KA-6k &tp;Z@-#5S_Q#5#5AF P*ϱ rd1hK5AnÄշ-h^qG 6(dlQЩ(#5M66-co`#U$  ă8h1SɡAP@CM  Q!K$;i'*` iHM9Js390ijb&VTD#/$J@AD$@d 3GpqB1쐧<<"$:\g@J; H&&UCl&u1&"M!w>0vxl#+N}ICxOO?.EER/ {ò` .ǃz?1I3Gm_(#5˿y8pϠ.C\m #`j`0yqLۊ8Uy#/'ec )77~f6E}i>\ȅ,UxUoV.ec wԎt7 R18PLI8E ~c?O)GHMCOٻ3KdJnU9Hz #5  QbR誠O5/#+(Sɿ{ J*9#5 'N'G'd*b,#/MƮFm%Fn\ˇ!Ilc43~'& 6[+Bi-B"CTTpS#5#5A7oSo#5"s}CD?~Ѿ>)#/%؈)j2~*F3HOy%[YC+T$efP8QyHtgpQ:PECC#//zr'vIYev]fh̙'q", Cp=ޛ{QQK}Tn}yCoV#/&#+`,)3ŗ*E#+#+.zP~oUO]"4xsV#5P8#/Nx'q#/i`CHkrAP9.FSs(lS1:8r;~KXڤw?;; ?/@h{(' IF3 Ot#/d*W#5Kձ`49k>lDGyӈS$! x~E:EN/͘BCiA?zωt&s I$WIA$ 6b?B )#+~eK'sq#5DKS#5r@'-&$D# %TԒT@ZuhACO|o:W5f8u~-8q/xSZΰ''4G>D9 'tt|`mW xHz4!j(17QYxM( 9>Q%J"&*{qD ]4A6;~SI <'m^y 3dzA8 E(AMÄHv҉Nㄿ<(z"n CK Avb^P4U÷1#/Y9gj2kzUB4n&#y D"pd{LJ[4RH 5tbd̈́C!D3,yQDǨxSMQq31i]͎tt`zm#W;>TɚՉ7N,PQIZv㲃 #`2D6s&_80sm>2oպ0(5"&BR6j2"ʪQgKL*̥m[Ǜd0!LQ!TKh\F=A0Ƹ1x) ML93rf6(=mkCt (r1#+݆QU=l)0ņ=2:CZM*u6wsZ 1ng8u&a*L&*(^_oX(b%U0j۬$>Lzh VPRT"0#odV`f>__H6_kKțmI| ݟ3E[&QфAG3FU?C:6SBj 3IϟgN<8R>9<^'ᙘ)i#5b#5(F*J#+OZjti!ǂ#5N)7sæ<>_IM_!/g! |xfwS#K/a[*@txﺹd))(j#5fNN("΢`b#+#+ZV!#5;LPQ8@B#/pqv09@ _WSmُhRC1Q@m|%lHȃ>'G@^U<_Zl v̤gmydϘ o5(Lfl(قAZ~~c3.L7D8-2ULߞaЩWh-!#'R]#56=H>Ϝ0ix,}C@lfT2D5T%BK+^)vSNI9;/jֽ]#CI i}aMRRTbKjx$rSqqeC8H[oyΐ l#/u#˘u18%B~0诱͐Ӿc%rn5(8I_HT;ZSI"5."KtÉ5Ӧg H]تЕCg{XͮoBBuWdiS05~aNbS -DYQu+P;xo(3]}\#/ iYgN2T2RrAWM-#+W$#/#5SWRP% DR"h؏^׃o'!`#/Hӵ`7<}bfA d - |[pm`6#5id8ҀH. k%rxdr9 G${&Ǐ >LâCϼAڂ#5ibG)@>^p9t}X>$z"{<AV=mLTU#/D4D#/DV4`dPDMl~\~cO.Hd@Xӈ?W+w#5~w-<_G۹t4I)O?݇lIwF‹#_sSbCzA:iGb b#+RQ <9t? J)Dy<_ӥڿ8]I~xwdp(c]Y{aePmڝC>@aR'G|ɋM÷?'$=M281挴#*ty4 }Zҿw<jued!lX2`3Յgo7|6³k˪sݯYpohһxUL\e|!6;hcY=h%c(1y L/ (3"fJUtFw(UTgcI<dn1* $V#*D{,ϞPTHPןd%o 18'_N@0=7/q@xc\{w(#>#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=ZC{{R{rMށ{X˻|}ܑm۫-ۛ]͕mx1{kqd=vt]vPեv^ۗw8.[fݴ`o6|*ύgn%vl+Q]Z`@:h#>)s#>0#>)@U(D#6֮΀dV(auWn}o#6#6#6=ԗt8;gFDIJ\Δ+2Jh@HSfۺrgx:7#6eai*.}k_]ZV6ӹG-y-^R}|TYJmì S}y{S]֫Jw|U=+t*hh#>*J#6*IUk*"z=u($7nUB{_z4d:y>:PJa|#6#6#6#64#6(#6#6}ekNUKnkwsn쾷#M#==#=rݨ6tk&vk$Dq#>ZGV#6(#6J#6#>܏m@@#6#6@(dQݎ!T#6=}}ϯy^mh5@n#=E;`m#=_U[@`3'J];]w[wb5c=z7޾;y/m6vA]^J7{ϸv4vST0n{`E"]Nܮ4)ֺLU튐=d{}z {۳-ijk|)أL=ۻۺq׽veqvu\o}o}de>/첾SOnL{Ǽg^xqxx#=;e6}ۮܹwwփwμCnʕUև}׾^vz'f/w=ynkMso*ncx(j`jҋ{(95m#6#6gr#>U{#6qlPުy(fM:}x4ͷ:B]#=gE2f#6)K#>ny7bV\]uBͦ¶Q.%#6{ueeڮVnwizF6r9|^{J'J4#6feݜ-otee5܎٢;mXM#=@wl/.`#6f\ɍx#6(&* Z1jYZ. ݦ;uv5jbw@͘KnOlVM:O>n;+}8Hzfu0>u/o[>v3WڢqR޺YGZz#>#6:<@vj@aM۳Nq^5@d@vD++r9u*#>ݝp1>{`ZQPw .*](*/*ӓaݖu{v{mK*llVb[#=pyŕT 2ۣy(JWvnwYvXuҮi:ӭA2f#6ݪ{kT(k*` #6#6 N'm2.܁ݞu{oy(gF{kq^v0;۷UjԵ]\N@Sn;2׮:ۻ(({2kwj>yiOcJ .+ci3lug tUȫÏr]t}nZnF*vtyc">>4zt#6s+]q4vwVεϷs&s7l-t 瓧)E\#=پJ3Ʃ@i#=ᦄ#6 #6@i4cFjO$zSi#=4444dibPzJhB&A2#6#SL#=Q6SOIlGz4#6#6#6#6#6#6#6#6H$DMj#>SL=O'Qқʞ=F&#6#6#6#6#6#6I꒒!dbzISCP#6#6#=z@#6#6#6#6#6#6#>D#6b4A1hMO&4MSSFF4#6#64#6#6h4hA&#6M41™iQi6S4Ѧ#=#6#6#6#6o*;w7rZMo*UUnfC"ڪˤԚe.Y` P #6a҂Wb-b&**z|c8.%D(c1fg4p"DGq͏`#6SРy*`x'Јp#6?@#6↠hOD4ƃbV鬒۳fGWyi娻9{QQx]<)EyUM$ڊک+rP)iEDSDUPCKn?Rp6m%`@bTH@)@0qTLH%JiDiV$8@^#6(c#6+kn0$1cR)F0hz,Dl#6x/t#6dr`ԫ#6#6w¢)J#644* ")%+jEIiK$S))Ii"Df$2" C l,`I  (LB&`bTZf-&66-c6HThAFH665Um&Ԛed›)&b$HM$0I0T8Ԑ16(ҋe6Q1"mRĠR̔FH*,lQKXD@ 4IMh6 "Fɤd#>Y D1IFƆ&ج,I-I4cX%ьmD143cEl61e3#=VSbmIRC6PICI+E*b2 HE6(c)KE%UmmZ6BdlD2@U%`ZJMaHlfjee)M@IFi6m-MfeFPQ"F6K!HQ"XEFPP$6K2 cA b%BfI$dEl!bRYka0Ҁ)&,3D&4bK#425&4XL5$2YIY,ԒRdiM2)Pbm%I#>QZ#>$D̓6$L1l&)LR$VLI$)-m% "QH#>QђJHAlY(2lD Le6#>BbIc11LT(4$l")iHI43d& M4h S"L&c DXlm(I0D6#=!RJ#hM)FM H), 3*RƉ45K)J% ȩ "Y@djj hD`de LLAHАb eL)bʊM"jiD!$ ),bE111QSC$6",ilIi-&4llFBl3@FZ2e+D#d̓)bԩ"͊$,XLZ2F2"&QI&$қF#b!6blQ4m4#>(20)F"HHXl!)LiC%J1Y1a(HTLdEh6SFDСf$D$I,Vm5FMI5-M)SMi#a#>fDV&e3iLX ) Q h,"$T#=j4X2d%4$Y!a+AKe,f"2eK5JQE1dE)BFdIC, R]R֭fLalLLRR" " ZAaf)a"d2ZCDd* 1$#=6A( $dXF&Ch5Db4`DB&((#`Q$4D$I Td0Hb% 2LRfĆ4mk#=cM$#=ɴ)6+2_:BXT fٖefѦa[&hSiMk1bJ&M0RZY2ҁ!&&ő30Q#>ʚ(ƕElIicSM#(Ȓ)ILHbВY" *e#>fHDVf *e+,)ѩ46YJlL(ҥFV4TjZDm464ڕ۹Y1 Q JJ 4ȍhlRF23MhŔДJ24Ȱ(%$&2AE#bјIb%FڒJ`LM1CLF&C)- IJ(4 ,ƈ*(1#>Y10i a#2ƂS3Hm$)4,$3MjMjIS6"13fd#%,hZ5)cEF$P)TX3QhcFbE"R3K#6lɐdQR*6i mJ*ɖJ)cKF*DVɦZ! 3dZk)Z lk`!E Xɱ%4L!صAE0-&j֓&BmԕX[Y4jSbSEj5b**DDш֒*ѣ[Fذ2ѨU$dі15bU1#=`(Ƅj2,[ThbV-IimT5DeE%!6S5%4ͩ,I, -e,IJ[5K4)R̖ԳcmdM)djQ@Ml4ʉ6ɶk(5!؁1i(%4TT$lI0)"D6$DDI2Ԛ#I,)ITm$E%&KbADfhږfRk2Q6e@&FMbJ#=c ##6l3($1! PQB $R6#DdI#4$HJ4T,Z#6sI$L!5eHٔ(&HfiMda%65 ؚQƩ6 $j) PIEJIeLi&&H4ز*!Hc3Tl2j0HF2"IdHP1iSHdLX-I0M%X6`HikLeEb1i4EI 4E(F1I I2dC$V4k TZH(FePBLJe#fĨH4Q%$ELQ&T"DlhkhXAY4h-E+f2#A0&cm63,MQhѤ!FbMBT%U0V-*hA#=H*K e)@Ic (kc6"S%K-lkKPLE^›&M1SKE%bm%FP֒6bRMB-F5MME@ѕ$R(ġ$ J6fXb1MIHj ̦d 5L`4+3"iHJj#$Qڀ$%YJ-%Չ-X%ѱjDKh5%)M$3""c6DbPe3Z6i5lmjT(FT֋bڋET[FY5F[EbQB*fJabVEIэ&2#h2+HVMT4JF(بڥ`)*y)E(эQC1E jMIV-53Vђ# T,ĨHK$LeTF(656MRfdʖh@*d`&"2Qm2tïp(z>#=r#>WFۿ]6y?gkHA]pӿxя;OǗYZ=GfÖ3t3$vʇfOFuB4 ï^K~V=z#bZFkռycAS:Sv{WՔ 2 $̓34Sc(?꼍:34]!ZF]?%>87R&J"gk¿gAZ?y&$e#>`_ZaFVm\yO&Xu#=Zy2َ%?hk}~*4vZgJdeEN5a1UBH'IÓ#=-Y:&W)3Ɲ瘺#ҁfB~_Xu9{3#>!bj#>BrG2Wz\ػ=o!?Vȫ:)ϭ_kdC>ʛ8IGz(3WX) ›Aߞ PG0)+|@j0R#=0bGo٦ư'(Oӻa;!fu94 _(NrϕDaZujtkFޛ!r#>)F?nus_v6BQ>U/{C4+#>}9HF:Ѯ,|̠|ZVp6ϗf짇LmANj~P/j,ЛÊyB#=Auԟ+0s1S0dЛJg^}16lEE+*E?+>3Vn;1!l2 zPW砥eGmˎӟ;CO;_||~%}7;ݭU-w\\s)Β^aS'X#>dm?;w#}gC4 v@C )S= 9m|'q FG[6(PbS톍iY#>I\NfFrQ }&54g癵k3ЁQCdρu(e?$S!-`gpnv6P}Wg2٫0&8lj00XG'=rL徹6*9g%'GN-CA=H=7U9e4ڎ&'qEtTXwQGnGpWrˤ}V\DDq#~Z8gLg=%h2VauK#>uV!hcNi0c#=ab'XHO8͗Yÿ0OwL^ M?_(MaKTL7 e Y `R,PNu:-A՝YOk&,NP}Έ"|UU _o=[4WCz2Bz745$Ha)B4A8хT"O_R~!0Aj4>W zY٪5نc(Gz5T0?;-%Q_<֤D")CU\6O*YAͩQH W叵+JPnb#>JzB#=#=W0=dc=:WAi#> i]p"Ƞa)<rJ4 kkmE8 9Gb*P\1#6*<3)zWm11M})X6v%f6wesх;yc1Db aIԬ6s! &ZH#XXݬnȴpyMzf>?^=q aB^%u(xvǾbL0i r1uyntRs&o)⧎#=UTOxvxO'ڦpuRL'yfr[|tlaܘХ_zQLr1}yʌaw&k"^B+1o #=N[ :ow)#>(" RD'FEIi6ِ Q(PEl5Q(?Am7fzWpU#>ѭOGp׺45]+Qn.lD*LrhjaaInpq@QnC >L۳FgT/0P s=#|7̷jt{+#>rAn0MaFv'Ftj멘 q u(>,1QqHc%]Z#>& gdLK@`H(SK,c#6KȾu2L!Ҕgsm:QwV3Wmt@7kŵŦF~|U/C AMccT˜&b"v&Avw/Ʉa!fyrvr#>JsO؝7J(x !ݗFƲY#=,2KM1K`iՎ#=I <7RI!-M@S!qoW_-s69G MVpdخ>ac9tD.sG6:A t^i6{ç#>F0'1i#=oV*o@u#6H3(#¤=Tv*WNǭ`#( c90"2VPđڽ$~هK`Xnn#=X1fYyt>yG<c(( s_TxE.w{_BȼfaqȍUՎTGZqRwwQIzI8FZs`BJyD'Eٜc0?Kt,SXFe4/۰#>9v].SC䝤\QB%`,L}^POGx57Klf-8(KSQs_CM0-LRfwIWc$Gh6&Sq"3sfNdO`^M2_!ʽ #w,n&b|29N#bRX4bDDJ$2tEf a5\=&kuՉYȝr~yi /o%_dbU_*:烟V8p6Q턐Qْa\s0ٽD*_/*fSa%L~Mul15"lt)\64ѥ=\aˁm:;|?VQe/qKPPPQ}mU4Ĭ6+rI=JqZc#>0$FD@-n|#>~κg t?M5v4GRA2[o2yOv"g- Ԍ ~ t$ҢG)V rS*kb>T)8r,{hᯱϙAlR޳korت!(drK^T:Rm }X箳{XI+EN\z?w|08)¥{3YBa9xMU~Q3㚇[%lxY؅ IW*ˊ.|js5ڛ?;}^2|=RO+ NWvT̤IzM UN;5ą+t벰\'loI+Yƌx1 nL&苗tKpA^Ĉ!RE+C$]-*JUGV'G?GV\06u0Q~ 6g݀o|CʅoGK&-wǖgό*mtc>/Kr(! QwbPxU;K't(:E\"Y[qV?9R#=fwj]k}mܐْ3̅p2*(P]ű} Luݓ>&@vmuɔ(#=1^K@]w#=/M #>fҬJR oGxiQ^_KY Y҈>q:s3b^`9f2р\0#> QHZ GB[]NWt8I2]2q"7jeMd%$>bwxnŬZur@ai($OžW؏/]w$%T:l=JKL{*=Ov!cTd/TrMC{8\"0c"2f.{-(r -SS#>izB)}|P_ qk が8&ɒJ8fa/+3]I(3'ԓv׿qD ##6#=%)i xG%u#>Ò';ّEԕWZ>#=#6qƠnė4O1ݗuC 3@Zʈ;s/835٭Tc%o?Qa;6aU <'3-%kTo'#=(,2I);P$tfu#>:2Hwh"X{qâКԅn"EnJI$dm~TWuHS2L4luѦM4 DQQƪ8D6h;q(vrq9#65)ԙ+x }쁔@X-xYw``dLdM$ӧ6I~^5ݹNǚ2RN3U/~h-}Z9I#6 IwHiTKQ( $GGijv q|Vvjڟ+/Fݤb㲛w8}2.OU}(- *r\Psy1ŴR}:r~g#pL#69Z1g#=#="1x =߆V@ٵOk;JX}rk׬s|AlHnj 'B)]EVkT.ioYϔG/?ve;3zR_sJЗlssAdgĕD;1X3 dPLA"(>=:WW[!%G(}/XFg{NYPhdr ^}f3~/VQ̩$U#>rlqA.OcЦ.*.j?k4\xfiXk+q_* 2YHV2_2Oߞ/髪f|򡞰X 5bnΖvj}g})tj7FG]%hV{>^_Еw~."f9^ cZțk|x|yRUjE GȆ⤁r`Exb;>+ )_k}ҩ{ڔ7?FUfˋK3ōm~/v*]W}ߵK璟T?Oϣf?/<Pt!<@dψsD`ݕaڐɖ# ||wUa$дt(}rce9<ü7/0gΛ.`;&6Wyo<[.NjUM|a>w#=gshhFFs_+ݻQ`S5ĩ%Zl3 D?\:"]TV0'cUiabR63]$;LMQ#=ƣO$pVd c2%U_-% 09;|-|u߶5M/ո|a? XJ#=[sX#~r6:W] 6:OJp\ up7s#=t|1>6:]J*=fSņ Fַvw¿55qUjtdvte&sxy.6y؛#ubj8Ju<Q1o E>f8X215|9ƕQ7Y2t%IAAW/)ZS,H[b)hgWm#A;L xue{wgpUAug4+nFP͖HPƪq@s0.X/s)3xTP5ZgW[ۆk62x0rFʡ#=3 +$^Io,Uyr,&ʓQnT~ 8p!Nt6)E5vA*wGK%Y*frEdR0s}mGkT9qbEg Q2 ϡ~˘7򙑾gWu/9#6o |vvB_|$_xСe#wgǎ=Ia凲 QZ~2eXOT(#>uڠ\C:^BhPhDM;"@S#33QPrDKkeHyg)SJmsPMNuo?X䬸?(fm͡IJ.#~ŝW&+DH#>#_P*)k9mTTTnE쟛Ttja340ē!C;^6:i xfe>U-_&dC&C8)w@;&RF'(ob1*_Zs yOłZa 9C#=ZuH}%);ʹX(+}G,E2\.vJM,upɍF3zC ^+,.ml G4xú W<QvA#=/gI-#=#=be nC:*\EyyMm&ب5=#}!>2ǓUG#6@LI~_&^9NH+*- kڴ\'lXl50 JTR۫w" "68Nh]{NS9طeg/_5Y ɲS;%l)JpT*Sb!LDs0֏i+?av)Heqۧ9",7=DzYJn/B"ʪWNVp9=+ F6fafJ+rEL9hj^Fp?]sfc|.QCTPh#>nx2r`?iBZVEAj7Ax'7;?A8gf!$W*H=.4Z#>IDW(R_rYlaoI9cz6tL T0q4wð K(WU3?J8Ӧc+AVjypXx,KXH?j)2&Qb^Q_t*nJGJ{ILjSUfVFH4kgCl*4k (-}]n3xCaŸyhT \ԟCI/v"zb +N/yar?N\z40FZUM{UyA?̑]Iי=|u Dsl^J/vʩx N7|lۿ|$=/3#6>#>Fg PЎU'$$-#6I N-E=*1uITbc1.Ε!+d;e;3`叝eYJ(R=n0!t* `l-iҹl=ߤ]/\LE>̙ٴUi9mJ *a)=[r8#̣J 4nhmGV#>g};we데~Dȧ &0˧T{kWm WFؤ5ew x̵T(w\‚˳>zP`f֠'!l+Ҿ`C!F'S" i[] ,iCg`->В/,#6Ma:H.EI0誋SէNW(i(Pݖ[wr#62!M7YLlD*?&=\@b#68#6%+A?K8q.pG N~Q2N#6YL<a{iAw(!;{yiH%EGE]C&sE<[;>mDE#6PP#6$ (GQn _6Q˩G{goؾc_cS&[gl*xU`9 JC24J#IT#>Um~mWrf2|#>9Y'apg!ƒ~QAC.?-JUhzy!!UJe?g#64TGyQdOF%;UD{}u0ܞѧAhUTۺVʁt~k-(P`E؍8vRP$;;JChP#6|hݒۧO˯|Ҵݮ^Z4m[y]^^o?N;TݒR _uX]]e**S8Jf%\5;Vvvamͬme2M?72='ZC팁YɇraR\xh@*wŠhgO"<#=zt#=qKysC;krZvPvavRffw{]ZRNHA 6#6BO B ݘTEB>ҒrOn eݘC(}mYuJW#TRgjpdPCT+T'#6~JR/D *,K121#6??f]I}!5S.&NDœFA3!jN#=!᪁d4$v}+qw@Gk#=⿕<v )N4JB ?y#6pܶ"/ogX"/D#>T#67f@Á:H#>3"DTU~>3tNU9<9%DBl=U1*J }~>$WƲtV+G#62PyTl;w ~ه}1xE3/lqv+fP=W.O^Ùzz3[F #>UQ*'(V 2 .}>Φea#=c`TF;sYѡ}jg_K|< .58d_(,'Fǘ"{k=(ITdY2XJ%%$PlzuLeQg$V0=2ܨHС>2 >LlUԋʙ.< ?F |=!՝ ?Xbu(ݜn0ߕGGEDh2F*))Z ZdW9!쏌_[r!BD ,rLĔ5\O !vJ% (qyZ[aZ;Xpjpt#=iKLB:Z &[Utz"1&9ʔ7*D;y4@cKu\G@N[h#XoS.媓M(ªDzfj1\yq@GJoSϠ07Զ #6)ZHo.)Ӕ\*8$Co:I|W.ځtIa`soH=X0tPPYE+$[kg] j~ߟi8|<Cvb7Loc.~tY(o!#=-Qy)S];<|Zd#>OqqGҫf mo7A FJ8LԙHr9Σ(S;!0תi:&u"C| z^fFhQG#="A`ߢ@L@*E@ : 7p݈!A*ޭEÈꋍ4nA7PQrNT]'PYcv)i#>jA\_vkXtgdK)$Q:Cbx `ktEĔ$;{NJihؖIeSS{ ;3~**[OA.#=3v(fr=Tf,1cø T7뢁rQ_v|q.漍Qy؏q8ި*Py0yWY-&,ֱ wMLJO (uF{-KrnѺO A0/Y|[K.ӄeAv80!q]8a+̀amwqEsZ˃xPWb-"#6Ao`2L/ c#6 UP %|ʉDU#>_S''a-1g}#w!qKaGx>͛7~\\vtȶqHAM&#fFװ-lmRP Hg#Vn+FP M?~̴L,!@l̈_ۼVA|836Cᴚ?(%,X_x1_q^Cxrtwz8֋w<1tbfv.3C%$sRe~ulG=xUݥ^ }ETU]Զ8%zu2 T5Q&Ug+Q9q#> v+j*6aB愚|aOs'#6:v1SտXsp;S dT^4~4L:qyPd4_-z4~'tS_C@osڕXz 0cw2Q1H$ UP}Gg2Zaպ'w`'!#>&3N7\ F}<@VAO =9'PdLLiTfHtw`#=Id\14jy0!" .v^ !-v#6d#=1#6C1@xjl#>\g/dL8T`m|0W}C4QTM/OTU#6#6}&ϱ!Ô?`bGB䞾GZj #>=hΝ޹#=@LCdu/T0#>?UPϕgmĻvKAGu>HPA%aPCMlP E?Ԋ*)A;>>u@PUg^dGdbS?.,_AsKqxiPGs{0G_+L6ӱpS\a&% ѵn!&ׁx]Zh z$ͱ\p׆9bI$[z#>#> D% ك`SZrc?EĸEXsJ#6D3^ӦQӄ:*.,D[´ĩ;aIvRפzQDRSf]YW"_܅GQr=60*$5}JJzH&(#=lz;oj =1Cl@?L* FA#=^uJ3;.Ә#> 3#68R+s=]86s:죞ԉ~Rösn_eHTQ{|O};!#>I~ |<է#=JoD$H-X[d37,G>cBwa ,&7ƷiKRSN\Fִ9P=[I#aMN@{17I+z#w6ow#[DuBM/F+b#Ntd n當e34~j,\αA(tC #!zzi{p]Tg>+:u*1 *4aIPHQ̻,zUdW]_4CI vM<'1O "]uX&c񈙈0]д.R4~4Z}~XQN$u_q7%p]w6g˱_#6P3RZ鋣-vp7@ ^2xUBsZkwu[~qʐ|tǻHsuhg"!#E#3BGuZLpr%3 >uRt'jE&#>@F nQiGN1O?yE%/`GX@Iu]Cg#=aXIH$I3Dpx^/.~qj u"Sѹwω #> 2"n#>#=ػ;$=EJ*C >LQs0[$yF2A{ ЉD5 sչG#>P ݵ\#=E5w#>Kz5B<|eR' rNIHgxkOӏ}n6i 1?BIw}( 7_lWCTGf>9dn#b7#%C$j/(#=wHFh_ R]YiJfB8qvK߳WxdJV\%Bh%fVF|#6Szq&IF;69pD]v-,L`S_[$PR#=s\Rf^-`ѱp"NJv|Bpq%*|~3^Z.'Q+ $sxaR`tRl}zFFTHfu^ci<N َ#>P9u#=dB}arƁ<#={u,V!-ߗ#> lҥ"5;.ú;֝- ;^(X[) S;Y.]UuE1p#u ӇrQm{EB$"y-$(0&m#=s?bMՇup6tz31 PD@#fL/{r *&. ki\mDSp`OgTwjo2c3 "Pc3#>-BY.EB6 XH#@0%B5Qg=c G5V^W!8cߪ,\vNHb. bDU砠v?2撴jB0F'&Y{QH"AnvQE@\-6pȐt]c߃]f&oy_I?,G+$k#=0IV+l3EŶRAqZcNڀZׯw<ӘϽp͐or`꾉slo5 E`\jTbî_&Y{gz. (V㏎P!Fwz~(t#6NW#6!"#A]޾VX|=#^`x*T~~|F?-\T I$FC@|\)׏ 6#=@@pەaA#>oI *#>L~[Qp`cGʹ`d: (/ҹVnX_M2?M}gM\^on-z8GqSj5UU?j*;@eh2Q{OK17#=7læ"4cl./:Nr2#lrPg`eO#=AIk"o-5$Jl3>s p#6rxL>p.WegH:rp̍QGLXV(!ךw>uD|}PI y=AyU+ïٮ߆0/.Q{#>B#A?z;+w'[~NImޢ>B0#=-cx- G^?ExuEb+Sw؄:za$Dp?{R '-Em+rHuu'GPGx⾮txx4[_zB:~(:sm~:ࠀ#>@E`)0^#=2]~h^7 .ݺ1-z l_1( -s &UT)pA"ѤidU#6]pD#6bn'O0hSnclW: fEAxbSΘ1ѼLtab\9NET$Fǩ\g9#>+GX5Գri"h(/zO0,$<O#6b @l1diowY!u #6Y`\bK{//met߄ 04,Zc .7 8 Uosب/Р9W_JKxcx^gtƜ4oq]br;41X`ԚrbɵcgpG0`p~I[* IV[o̐1m@B?< }cGO#6WαOtc~:ח/lDq~x`T$_26=[4_t>8lG0+M)˟c@BJe.c\ޗCw6Pm!+7FHe9~} /KvTubk2\$QE(!#6Q~"-:9ewvPz޾$fJ\u~ey1&`0, J\9 JS Awŋ"D@[H#>feb(P=ҎSkyxr&_!w)J!L o^#=9p#>VZ#a9X9M6ؑ(9$#o0S]/O H<Y!LY'{,"hƒDv鯎4/pCUUi'r$k߹.pEKܷM+<$zIVNf^`-ëFwTEAY4Ⱥһ>b=Իk}͊|nWjvUe:d҄iB{C7N7^|ViVd|`Pv Hxjxv㦒d֮7}Ixj+J9YWJќUkG#=/%v_C1rB#s~[W}[Y#=K9)Ls)&Tv2hq'ؗQ7ə~͍b3=)䃷5M|l:3I%EM8zkP>5o#>-s\~r)'䎉cwxӆvTvQGO T/Cas$wP.#6ܰ)kYbFBN5L5xnpUBAxG0`@蹇{ymMZVVůtɮ+86##6_iPSygPC;^2N4'2ÇӾ(N;@";u2eiA;Bҹ#"?\]K,J,`C;5.n$JmFxɸvYjS#=քZ#> n5"\L0#>*j)f4ɰ#>&E%@ Fѷ3 r[[zPfb2^ʴɦT1n`עMQJ,ۚ+ʊ:;U9#˿;6o9>ac}hiFq^mMm~־#=|"6lH#6 mGhWC"kMK㽮wݢcrbtO9B }?wp@ x+!Ğ$$aiagVCs)9|='S!2<' ˉ'r}Od;1vȼ<[l!0NT&I9l|c.|Er]h [j M81.u[k%n#>DJ͙x#H~坭[Ro❢ЎM$uP9%,DQY#6ntrȄ꾱Us( ǸlKWn >lFR"}#a#)lH ?%ocUuFڸKN:&xsȉvw,ÞhKjYl՗,$ "QcBA#6[0Cʸ&~zϛgEIEKDY`ȝ5hO<N͑s_~?fZ `)%P6q-S6Hs $3Qm718@p|;8gN$By95n&֒cnTRoj#=:a,.ndm6V[Lv 4SNɍ#=2<4;pe#qfv3.5j5c ҘkWVטS3L#CA2I``[>;l5I[fț#>9$Dha{}c0A4ͪfQKD] ܣ3j:]@3 ?]ẁ` ̟ܬ;4E]M<F8BZk%%`x`#=@$&5TQKaI ?3SIREFF2#=}DxfxDAMC2~KQDTmV9BS+S}lӲg (hJQlU81'k}Y(eCm5BvhKstYZU&R1+򞾺xD1 F%{ldm21W֌Jc Y*xcX:އ[||Q3I#=jx]^d求Y<@t ﴛ&zա#6U:S>Rks`"!/w'saA?vf'Z&ITz("Rw+7cxvi+,5b+(LGtz@V"i ]D|zM[MBd6=Q Hfn5hiPI81rU1%};zbMFK%K{#=qg`VHS${hXPɐ9K(+3nҷ  #>K]]oˍ[:7 @?`}@v)A [W'tx,=$A"%Yض\Aa;#>Lԡ) .݆AϧlX){ !T[6PI'zŗ7/$rڮ,IU*k u*7:SpB.21#6CJ4R|a(˾@R)/ ӮRSM5!,Dn%sk7R7:7^:O¢O.<&>c}|d)R*eEo&܂ޮ4&BbhLGݫT=~fʨa5 <`>`bod?$<<4v/> ޠN0w#6Ir~_8- *,gGN*N=ƱUBn X$#>3o &اE,0 m2ʮuxN`Bk~4B9nz3PU FGaY_'*K#=‘pn^MpF#6Tp=^: An ‡ءtfMta&1~l94`3f~UΉ(OgKU Fi-5N)Tp(T*rzgLKm0N|n'5M`L@t_h|ae\a([#6P#=l )OOp#>!u$3s%K>&FGbN$Eń@Ldb(ٹK%D0Sj۱lfP1;P7ߖzWu)6񫚌1#=; 6za5^ Ŝ#oGFO+doP!A]V=.Rv‡uҩwmBwߗIfqw4 #=G[YmoJGX5R⠅O/$Lkٸ`ˢG/&˒lHH>]/ÿV3ոC b#>,he͡#=IP/팷{*"DT5%Qo۩JRm 6r[a9?q\|K𢌥v|)q(;+:Aگ|b#6}] w&*Dod'wI#>V:J!`!JYNh(s?aTTlQq<ҙ4#62+% ɴyYiwŐ:apX[(xT_<@qcͼbuq.2+P EY9Ȅ IcTe#>l!( #6#6^#=ƣyY11ѡs'o6#>oc7u>\K'fsät߶l_vֱA IgPSqիq#/=Dr rrm7'ͣlD-Dž_Wr,ѱ߬Pp *=?cI݂E-̰Sl+#>9#=4jO&w8`jf4#6C0~<:<4l .NWf!ڍ {kwtNRe!9PjKL Y6%fz As#=_u}R$G8?0U- (ȷ{D')∖Z e3M ?4ܠ9k˶xu>ul2Q}F߲CQmɒ\BFgXިcy? sM,#T-s_ BH[8w#6wW`l+RʷT(CO{5sق2L!d/=fPFGᓮCOpO2b^zSXjL53 Aia\nG8H$aA&1[^|#J%7pmw_ 6 U! +pLhd5R2ɷvjKEzUvLN"i.7<qKꢐ[uFݛn8EhCmX+o(7D?Dž vE#>5UsaQeEwJ#6Ue.f2XYW "lU%ݶ†G5XEd;q,kF[lWٖ}L.HOs=2rO$^ԂvdI[ b/dwq&mmZڭ!x֝VJ.6oDߒ'z *yt-c@kxi[(j8!񭮹nMsLL%XL #>[#>4ŷG,> U"z&8e*Ѻ &8gU7(=*:%mI7TF42@~#=1$6c@Nl8r&&ClN)~j'BTF1D#>O'L!a9%plH/ Xf ~tS#=f7>/ˠ}qFٮI)?ᔔ aMO@#p^/'QBhOh <+?Ax*m'#=J:]#=:Yw@TBZ;&#\R;RgN}3:~spz^ykN]#wRl1 jb~=k7.|.![Y,b U #ᒈzsZkYX ><\ռ]\t1/aQyNԜoIbU(f.+zߛ +(+@(I+&уᶢ@y"LU鑫]<қ瞠#=a|#>j-3@SOj1F:xO޶=%5rs-Ƹ(;9UD`G|R1?xЭPbmqMO{*ZOO>Q1?zڕ0d=z;yAaM06#>#=[TnD8PGZ~J8w*8#=#>ٴeyv MNfK]8. KjdpBͱEx_D-u?Iyu-mӅH?#=wGsc`]lAΖwF*3+̓18m+ yMc"Qҥg%H0#\XzϾ'C]$H:}_s\''3~T͡Jx2sn@0$AK @0hQ)kH/\55DܘQذx5`AsbFQ&;t~_yuy@<JdU[Ya)XgyjcH!q.j jDƲ.=n4p#$\WRt5[9Q(&6=œ-cm7+V@ldbF0d{~qoDfH[v8c0%rD r WOɀkK`Dj@ا%_B\ `#WL0]GɅ|^BܬW& fpoEBzZtA1j$ lN.U/ B@#R#> ,ϯ,Ü'K LZSI`5:sÎϗM3#>BbH 73`8!i-?2$/_»P'&<8-f6UulPi';SH8H^A6 HEO^1 vUCHd _#=.{qۺ}yS{xͶ3c$lײg2 g Qs凱@#6wX3ڀvIpPzD':l65NJ:, YU)*_aRTysձ#__@Y{ =VRoczd"ݯp{XWO>б(?Tbњm:b1bVo~P$Q5l9ojyliF,M53 Z xTWťl}}7a?PWHw"aw ;sU[1+ݔ7m&`CXw G!$lD=AP8'߷3{aH5]%0:'E-pqUp*1x4$vN"#>_Q["808q]52zod^Q)pCt~ě\oE *2sVW>Q1lni!fgnJ0No$D+v,SHAF)zvSyXм!zv1?íie.YI;Uvr9R,l0Q#60!$Vsb=`Ŗᙥxq:,gأzXTq!QӦ%gC!L;wZ Gb)سC_@ϚI1Pb"q epɞwHB[O/6'ȠUGUa\sO.$9#6/[ȍY*0C z&KD5A\ 8hGfYJ7mQջJkޒ41f/m&׮ybHC(!=ba;WiP˶B:}~S#=%:8b1P̿?ĆhFN}2Aрv\eHGxi˛liYFā:Hysʪ7=Cq3rQ8Zc"ͱ^jeï%,wz?cSJ 4HnSXYԌsi4Q T7Uq%_=M|䬰4ߙw(W #pz_-\@Jl ؄;#6YTTkB@+lMtǧ0_NA5{L޼T&Z[h<$4r8bqM~IR7ij U)tU!6 5,#>3׎+m @n~(cle1=1{Bo!Fk*\#6ưG/elVO#L33> %}sǫHwC3#>W]vӭ)R~T?&0WFL^c3AX2iPT{vwwZye\Z rA]oX?rq6#6nP+f̰H \!1?:| K Qޞz}YVË=:Y\X(DF"}9sӧ-0y6@a(2!ae "n.'[o- 5_Fگa`-M%ڥu4LA}!j0х0b!yt^&##=bCg7j7tN:Q~<#=22^I,#6M\58GkͱIg=?]{wΥ FNݕc^2 2 1ɐUR@AzU#=¡R˥iB%|e(ct{$%fFWt2V4Ȗdk@pƔ;([9g#6C(yk c-mu3#>p p+# zs4Ni@,2[Lk JH~T +p3:gyQyĭ40:lFIfC̈v>B^9ۻ~v 9Ȟ;a[&./(mTՄ/ql_IseXRT)h;xFDһV%դez '2#)fCJ4Pu?#жlmo(ǧ}AG#dvY :m钥#67fWrYTSG;Ɨwʽ]#=otsk@PD!.g~ Y;HZZԀK A2#>PnX^ ~!{l;~RaA0#6xrT_gMBT<ZlFՐI$b#7JW zɩ#Q -5W%77Uly_eC8#=g!c )D910iO~L),[=lX1A-8`H݂{qܤ8kէOiw3K٠#6GdAb#6̽v]~;XXYPD,0Y[#>47bq-+(>[O?{I;E 3SC+>=1̔a9bZFf"!YF#>N$|X'DUWD{3Ul<[3*I$xC}on)` #6 @ mWpn&^簥ãϨg}=a#qUURLn0ʆ#%hFP^3Ov^M@?ȢEdes f9_x8ϱcIē߯zķadǾv0z`304y`bRp`DxWp.\Yو3az`8O].קcu.m-:ޘzLA Zf]eVbV)zP(qjj{rHq`A}9۾ֺq_&MU*ftd#6}#65e/0oary%NHƠz.2hBͰȘ !ҳ0R#>D!L5”#LjȆtg8Հ4#I1G˙sq,+8v霳hyƌ(;FJkX5#=L+sBf٭bǜQDD$&vc&Sà{-S5}U.ց0n)n"O7)(jc Dlfh@J F4&]oWDaoHMg*^Ad(9% #>t\(,shVXAe(䇖"c#>r^3r:3Ƃϛla4 .dGqO&bm3Rfv9p=sVč0R#>IYHbvqĄֱY欩f#6K))t:,|~~ SԹt:N5#=PDnܲˬDM𷨙;SAB0aC&UQ'wsg`Pa (w\}$ּ)pt"jN_KwyZ^x>Dk&tm  *2*5Lw{)/TGrh$b~p_ߏnBq9NyҜ(NMxY #E?d6s]^FR\*EԈɃر0C45wΰ٬-B6ѡ_F<4  sA#=4ؿAya tzJB;f`u!r6Cx'9(|or?Q?)U4g^QsW$ɰχ=M>@4^<_qFc>$R#S|#ǿkϲ-[}vHoH 8Ltuq?6r)/XB;[NÃ% FM_ D{#<8x #6Dëy2(UJ'[)l\9#>1ݸj#JMCzf̓ w8QM!yË[-Tq(6M0h>?NjڝYic+E?'ü&e쒢 VS?%t5Ǧbۉ+"H<Q#X$pW4WBu˩):ŢU Xpwk!( 4X#?8mKU(3CC-X4* Fb#>l4I塄nw'oZ)!KG5h/YѰ[0o;-.Hf;ia>~ߟldoLLlNS:^ѲY|"XB>)4CrಙE$ .o;7gp <8}ipK*E"+dj3xm(iHA$}x(̓6&Qqk4‘R(P_ lĈ%;.@ҡoqvag;Vv#6ɔ.6䟢&xhwbwj@fj<"%wc[=:H<2fл09*wM]DF|pG%0D];HD)2qÜTRPcwcNYrBMʺeK#=AF7cH2#=`o_nOuCՈM=+Ҹհ?y{:ʬA-m}/lc$A[-C=74[k{DPKs{^UH]Hm7Vt@NKI(Wie#6=h̗%c״P`;=1C%Dx 7/R ~ʰ5AGb?+~!엍b#>nAyrg!}2ǎ]Ӝp#61SeL#=ܿT)L Ԓ=$8 L&0?ڽz#=DH'qh*Ȣ#>2cKqoWyck} CHE(`k7YYwpyv MFbA`oC-#=T@ǁÔR:=?@uzӨLvLO%}Ȕ d2!CJfUh$.* 0E{@ŏpnLZ q#t|pejӾzG.ŵ#=tFVA ؀KkRxʗER& PLW´$@xQ]#>yWvYb"Љe#Եً|TUY[cU6&r8)3Ӎhu&1#~ NWT* ]D#0%zh#6l3]2_}^BјA(Rx_L59]}#^F9 d֒'~tBDeA:pafv#>6{Wbv?@cnq< 1Fȗ4c8õs5()}ov=]ߒwXe.>y4]#>~CU砾!v !8D4KÎa6e5,Ϊl#8 @DDjGzB,|uK7q/\A9MZb픶N[ឫW9(ÄRyiholAUy*+xx_odAm*|"?<&E#{m6Фwxok2.BW\kFSlnV q}8dP]d0/v .WtO8x,Q.쾶(AhGs秗,VUR#6c'ڲpi)R $DFD8iC;ꌃ7B/A&TV$u9ah#>h؉AW52XnF١t#=/Vkna13^MVj1yOuOJU)S9%N#=kxIm ,1(9P\QWӎ$)7W"9)]-:])Os5H֩#0VF.baVu["!kRgF醘]*4 lUQj,(D2/񰧓41ƫr#=c (Onu}ELrKډXBAM;,DmFbNus]}>0®haՇmr뚩fFzsyڨG;F$-3Q%lv杄~d2<[o0g!eּuvI!#6u0}.4ܩv֬mYQ;9I]wM57@Wb}tEl\#>qS o*tG9Zrx0@j)FfZʨhA*w0/*kat=eBYIUL[` )Dfa -eL jDu0mB!o#ԨU>g}%k>\$s)za rTKA\C>L"#6ܚ(!#6elPVt0RLFTeA#=j= P(QR84 T׼ M$#=6Jv HA@h^ke`%DWM.e+4pY u3tIUh03Y#6a7л#> ]âTYJ1#==1<ۚ8rg}ˁFm~;vTl"lwi ϞTZ҉Pl6M8#=TKQRY̙a<#>m18. o "X ҥlM/|WaBT@ FJv髥kB(9RRτ!}#]ܳT(59[;U{;{k'sԳb$k$pXએ뛇((E VtZ>u(NXl'%2Cn3Jצ#=Lנct\EER v79{cd;!kb>7I(Ld]H?3Hjg0ge喌_gN$^?lj`.4)oTCo+#=ADyf_jO%0 ɟr;G'8a"2MXd\Ħ@ s.Wzr{yR#6VEN( k`-g7dž/S[t=oҡyvC#=-#=3Ǖlty7ʹOڵj]P='Bo jJ%'J>|9EZ;u;ygMVmxzlFq(ca1B61fm=⬄n+_p7(^ӳ"QYĬ\9qѝ~š-)W.>X d麒e q.kSpp#hѱm n"#6.5@6Y.T /rK&1R`? m%@vq_Gxjo;E(N8wag<ƿQϞhfv"}oᇶv5i`šXT0v3_;TwguaHe?I70ooNkQحkUC˶bt߉`Ɋ7Bj$gL>}ПF.xc]b7sv>[\"]ۘu/ļ$#>yG-v5"\^uevJ*ֹI.}Nw;,L2ubzu䨓ʸ(<@{9FB}efVY_y&1촩xraL4<'q%>u{툏,s8lFyV洵Asne3\ys98;Kug|+mmmI9C}0EUl1+.Z~]6ܿZݑ)~u#=hN<b6A (7_HƳs:!nS4chBV9ksӷHz+dppw߇V2W/Lxg&'|=k8gDjf89=m[6fx(6C3H)#(}n|Yg.zwsݑ9wsiY:(8i1yfKAic:7|B$1+;Y-Xذ4dZP\sI^2w;NwіFBfuXzippZa+Fsȩ1ZgZ՞^v~ZoOxgzbr) ezҝZоzOA2%)C+"voԵ[ގ7'MM>#=x١~neinwld&dDε|- 1D`#>Eq-fg!Qߛ3;-?RQS<Eq]s(07d9#W#>YhDnŒ2g,fsj<F+{9|v#>3]op6O.t:?Ekr:Ee*Qf;o^\0*\`ζUg0nˈ]TCsC)tc谫^7M,TiFyGsp[φLZuz0֙ṤٶPls9ٙqݠ߇'.yYq67!ڜ6IZ D«k'@yIc{*uXc pb? >ރ\;=œB5x4H#W{$p:cIX!a! "BZ/E5<M0v/D Ў3[ 0bArGxt;uY#>rfi%=gQZZm `,D:x##=t=Cp5t?zdJngͪoBB'crV!bm#>`R2II-k埍Re)<ᬲF;,~(6BW ǵ˱zv!Pס4bjT$iZo_Q|*kx#U~.I'#>4 F;,/aSKA ʈE_*JV $_*y|־jl.Haݓ(h;K=׿=19mYۻ*LmMC%:ŕO{;7i"Ζ&urh8zcgfr㒩}7#=M\G+&8RBf' ; L'1{遨Q"l4$Cf8|Q2J#.>`˾ِs'1=ЦT,H4FaFz>}Xqp0#|/(^3}3g͒aK:)(t.3f=rq#=Nvx޿7G2-(wwb-c'#0T%:цICGC7uj]gu`!-:4eT"4%шf,mS0B2:9Rz1q2E$p̪C9 ͹"z#=>N&V}l|%4#>80N;ߥiNyIc1pl̲aqBA({:`8@0 h#6#6_=D#>Ng#nC,]CxyQ;+<39C|18g[،ơE#6I U`~! NߤR;hGQbֽϨ>TN"tT5i՘cB;UEM|U4T(gd Pbᓼwz |* M<)|AߴX]﫟\,fy1#=k&|)iM<a: u\dbL#6y]D1߀wɼT{nkZYV}ES;S9.#>]F8u|<7p1{!sE|{2$/5sML?}pJ"-2uWdZ!3M-"#=^洛~(ҾW/N9c7(le67FF,(:;hwd}#$ɉ]3,V0Eߦz|U@NiXPzCZl. FFN|لJts{A 6U Tk<'[LT#2b9cpxOMyb#66ܾ@U*YUE{~3E9si :P#=-r[ B&]J&CY%  *nljH:\0XwlmʚMWF֭C壟}g>%w״sr`mT"B'CbX0 ^t:B]p#=ᣎy!A>wwa#6O#6"#68w'BDVp`xU+Pz=" PMx߭Wjwo˘j"vK7U|J1@>tk#6@sA\azp[dW#HdMBm!Į(M9ՐԇAbfts,_/Y:`Y‹ۗrϣYVM3kټopf':h6珫}_bF@>AV#6TT>N_ y#>9!(ћwoPA:T#rN Gő_XsLM;G!rvA&#6w*0 L 3~B?7)*Fp*j9Kn!M5Wj#j^Rwʇg9HgtCHo}VV\]WHŽL[óI$J{tN!}FJ \3]1$2lMF5I6dѱD(P֭$%f~Cd-#>K.Rt߻,jc Dz2|@V9>Ѩpt_'_.QmncIs'3#=Pv9ƜziI2q0>a?+YG?黲c 8f>#=!}+#>N>TXdp52{oqa)N\d}a٭_`v%H'/$^GyAOcIMFf#=  )g ›pb8aƑ `Ѭц)+c#=icv*K`aSY$RC2B# ю֧tP PF8>.͘ƌ&V#Oʈ/GJ#6:C l-#=qZ?Kg|cu9Xg0"ԭ-3X8*!X@UjEbd)$F0w`b#=;:iV[aHmi*kǸӝ9R]-9eDfMjxUR\Q7QQ"(bP+eedliQaPjG-"lN=W%qADhQ,Mb0%%{=4V#=ֈ2B"*v`j?M[ME͔`]j;@%FZ! wkKwu<5˦_ 0bn@6$̰2+3¿@5__nԗ(l0X1@ˆMP#6l`pX l!N;c{M?Ꭻh#>d%#>.}~ TD}xe?R}}@LFhl*]s>Aͩ6DͥKҲ ,I-1ˮis pyGAct~|Z #=^wc|!G TA=  X© ݽ?z<MN|򶚋n$~m~sZ땂Z=FیG(bT08#6)wDKHCD!OI^rƨ*:I9MrSy楴xw4qXIqzHC%t1hOӦ7L>gWn,i] [de{]I"U*vD3 oؠGLz{?=bj`=u|H_b%3>IW#=)??=ܮ$0,2P'i Έ#=POҌRM%y߯_guE IjL`67HN aH: #>ˇfgRfʨ{{ )!o{r(P޶,v9[`G GM^ߓ-}pӸף@~<٭z9Y!4fra#=!wzl?nUN5>AHOw閇GEV[q`;dЉN$@`nEwv#U{O5#= o>dtxGӽ3"OIA:yxmUHF|ȟ#>*i>lg Ȗ>6twC}:J3Q2U||)%‹^u*@?oz:_x5+~__dœ*'wZ}z5(=ݪ)#>MbdOEWߵlђL<^)5J(6N_#6PrlǏ=?9a>?Z odr:=DqB:џTk?JO?r%--֨VSU(CN" S~a;UԔMW|_-hɔU^C?NRdoNR@9@۷2#=c%~.(&uJEMiexl>|~2m?pdo@h'|E#6gyJs_cw:j觨,Jܺ#=lÆjm_}j7'Ԉ0RK5qHC,?)#6GPs"Tjq[ۯ[$HpI." cǗH{|nth!_D_r?O}OWL8Bx>?v5_?x_#>C{yֲt6,"_;|'Ǐ;C{z;ef?E<;"Ϸվo~Amuշ滴}(6|WMy~yNjՂis䃇g!{^>?Cӡl|dqKaꗎG#=uJ2xٗ@`U~8ӧw6l|)yq~ wn/;²0Eu+{{ᇫW|{|^\ƫ/i|:9]ÿ9y}08F^ٳN8|=d!:=^ }/yCoI2~;/D|{"=̓uK#>#=Djâޯcrw~Rr^~ wv#=11l '+W{8}t>;;".ݱVC|JD'џG.2U|Ѥy:n8pϠ)KnY޺|0fy ܫ}o8ì|cU/>5%zy,__ٿ 1OJnGGNn뚎x&3C+WKD˜uǥFUN˴Mh'tɷfw|?^tw5E˩#vmz,/^7nhyl>@1.bw+4?@9wu[v1E &ê߄<=ۻ xRgz?"njyL;.ק'i#="3!ƃ|xWo/n|:Gf86bBW?Wf?!O7~|']?y=k0þ1xOznl~B3YO>wCOM?6ϋu#=6W=JCCEڻ'|})ll2آY(]f#=,i{~Dľ) ϟ3^[S5e峃tnG7Xy|/ Kπi}2wT#>m㠊K£S#=6hGBӷǽ_V~w]9m}]JG3ڱw^ʠՋ~G|{/k =ݠ_['_OXv=<#``!~f#=#>'xyHϰ}_-n=b<+S|1N;4?6\=;ovZƘ ,n͘O`N7rF}v=}{sLTRN۟Z{! 'UA??Q;KJQ~>MV!O\"?=ewJG{~4HXO-<㿿_m߇DӘU.|%ې#=%#6E-I`Wŧ=]Y/Ox5/w>A8viw,|=n#Z:f sz P?$?? \<vtj"6|kş/x.*=?b|XxQDt嫁_7w1o?"XvyS")pWsz}7\c'}R5t:ctJ'hk|rWy=+0]È2|/)xv>|_Z\az[*Qa#yYJ)8Iiޣ6lP5Li4$P pA#>O${r1*?(#>eadmϙlޛoeL>⏶cQvY_;LR&;Yo9{0]4U'{Fz(js#=TpN4K'1|60G=t@%gg YD:kk#=0* #>paT3goT׹HulծJO#>P Agr|13Nlxs&'Aܙ<XWj؉jV 8.mX1ZdQW.3,؝Nʭrj.Z~\7.K&Sm2dⴄ_}ͨ>mLF8 {Jnx:xPxEp_$Ot-}W="i1*bD#q$ep#6r>{ʏv'P8NEj,B.1Xj)pEfH95u:Ru1- wFԎO~spwiݜKan=͚<6KȔQx?Za%x<~/?۸Lb6*J)UE#>$gR*^=$x7K?f7|WoV./I܆gpcXtn[d}#-imDJ}Q\@~'=&?9uIyo<6>|"zȐP(G_/|. 3tdR' DBDETdbf+32L>q0vt `BGU%]nnuZ&9Add9reZƮ`T2&,q{K]qq'R^ˑ̕9,f# KJ:_z_"Z]=b{VBߋ16#6RD'䇶` qx{qїZУ.#> (1)>Ӎ9UBPĒD,=44FMyR6%R.,uپ5S$ wv5Mb4{3'M~o[gd{t0JRC!pd+\axv{}9}W6{nڽf/2yGnBSrHr<}ٔm>_@ߠ{>nRx?XN9}k " 7BTpQӳU3h]Ŀ9G ǧU#63( f3ၧD?~Bc_O)LX6B #6ёt&y 'Lp/_=Bb#>"X7$Щ5)%_16tfc#=PJ" S_س1D#)2o&G#IBeNlUFAFT9,mKcieCebXQ.#>4"9aSӗl$ WPxժi[1q@Ѩg zZԇYd+CM`ZRXdheuc#=,t֣4fZj4153Jf؂Qə#=A*;\&vW7E4XBpոU  Xm3Ani0k28:X2K"6g1ZXvU5Nn21"19';疱A05&(³V#>)ҊDE?d"ake*7”i2@Lg/wL :h$GHKi_Gy,rQav>O_/ƿNoV#zϏOfA Q7H*rЈ0 h ~_Wg? e1:rHS/1HBbpcMc#=|,""O|%&kxL:)ߗgQ4Gry~#6tra@"u0Yٵ)dqmqx>[#6Cr|)ylIӊ#=ؕ cFCˏFo \!|ۗV"CD5+qӜ#>V@LNJ);w];ΣN #>$o(pRO?W:Ӱryh8׳O>e`o !PJU=',xN Aڨ#6vRnM?/ =p|u}?{bWzĊ>q~CWԴ0lѾVϑ{1 ȲX7cbpsz]#ާuuiyI!EejgXa6n=2#=م0*%Py1ZMyy>:+vQ' +tɌlS>zQz4wznBcIF`c >d%U0iFe:`\xȆ4mކ"#6ٮlcѝf<Fy'R5@,MǦd,Sc+Ɣ]&#>`# g̯n6t.O={\|\Tn+pI~]]/ۚ]3c*]!4@+'T(,#'{+2{ԫCFQI y].]'urLw\˧';7.F6j 䑸*3)&ef[=鷾0&(ђWZK0+#=MQ|}:t-gNGJ´#tBA8e6S1Q0p"c Dr$sE%p&wq&JqS!EPdD1^ь⍹j8%'Y R53uϮK#>RTl[Hr7aWn󍅑+^k F#=chf5#>#>*j0Q@Hri0,νݝ\ok ʊuL(ISs402 Gԏ*(]4#w7$~J#sɌ0:)6wÛȰǮ(Q~% W9eB6Fsgzy%pDC0`JcIFn<07gu3X%nt(vd‰TL4M*"6T6ߚmWTb֥Wo7#aZ$„DcUCX_)5Ù#>xפמs!`@\U.M-[bQbO}XgUN[5LO)?YYD$(cW|(vBwppf3Iv/-OG$ 9Bc j ¾yJ=DW:Y#6&LO̖yXaˎӊdw<䖯$g~9#6p0[NGa0 XiHK{ÆVdPt#6ff3YЛ|r#:7u$HJ!#6$'@bQ&cxf)\x<<ɹٛSGpJeLIF007MxU9N{tʏBDS}&Φ#6AkV 5RKi>x4 #>HB1Whwգ 9UiXp:lpp!+a[;B'qt^х9GJF! wuE8Eećs9m6MAe"c],i e2%jf܃-˦q|[7t׌;ӄ};JӠ>,VR/gNPɗf5_HDz^N45-3-rNq`me`b7'O} 8(;Ƨ(x'&jOnqcT5UF m _JLVDoJ߅䊰8+6c~<)4iuC;'}K#6>şD2i'soFqH`3@/@*ytmV#=:$`#=*#=;G)}w=O@r yBsP(Fm7?$v*<`$zvb6{q6VuOE,Pu)Qh r\!h FIaxldLpN#!u}ㆆ"іrz|`ixlKٲސomc>mR]C ћF WݮˊC,t#>JF{vj?u_І4h}E6G#6,g#Ulkn65$ƶ#=6Q4flv͒}19y \Ǔ7" -It~ї߾="#=*AI@9\{XG#6$gn}vLTD!QW Zaӟ`X5K'BCBq#C.5#62 yJj#_fԃ<;^ngoػ<PB;$+W葨8z)5Zȑ'j蜔z8NG>ALK3#= 7l׋&qD`Ș|́D853*%zO C|FXQ5́@_Đ;L}9Gq9d[nh{wYӂu0ljdlBm{uI^̛z b̌I:Wf˓֛U=iaK=iSG2Du'3eWMAx7&"k1՟ປ$YwN4=-#o qf;7јg'&*90^\ҧ ~β2@eiwneqCqh] w؅bt8 1LE bϛɡ?o4q{-ک6bIȶW#yϔ|sF h¸*li@6ɣ']qe L9'UB"޴fM;╏-A74{~G8(@ pp#>Ce5;O[ viaWP)M㱤cDi7h`P#-ơXT8ggZLNTPcnIMzԽ,ԅcZ>ߢzgΟm,_3)B [2O Pku!>94L`iE9W`*_՗]U[r\Hf垄8OpΈˉ\'*#>#>qy8Nmeh #6sT\D) ?Kd)#=Jd}wru#=D환s"Xf0^?">5?5JF]z>Dp:j1]Se#9HFV_KzžO 7=\ dƉY*ѬImQ} jCW&sfPw6KxatLr|?r\4w0Fʟ,iol+7,c,YSe66q# }<e_Gܻ\BV*ZfpHNbKAޮ.!L6K cS;tWfr'Lғa碇gF{l!H ĸn?*OaCtkf mH*eE+sRq۹ϯr@CC hI=$^@݂o.)8".KU6bʃc7U `CĶg\>2H0*jQoBYkCö$e5 1͞;qS'ouQ6ÍAU"IР\죾a ;2THIÎc`ф:Q\7ƶpQax#vM{V[m?:4v7Qߒ,܎{VݦEc#=УPX>sƸu- qIT fOEėM!m:[U":я1;oB!|w6(:R_gD/b(y#>R _fqp`I5)>MɭNx#=*!r#>!n{4'_YLDXδkF-UEG% b)LoOdE:ɼ‰@d cfF!䠿QqkNcօsԈIG#6= 5~Ѿgn B;MIL^A٭w]Tr2 _4NJ 8ru+JU͡sɚũE{1o:ת0ulvlws7DL^gu[Ŵr rm3+kd.1ƣcZK-,q> )J zQtx6klrʟDRYtŤ@ܥaNc0#= ĴZo{tۼ=+Q! !&+F"],/6bham֔sM0$~hV~6 lwg,,.X65tR3.W$%WS{bY)t%ٍzSZrAu\!R7nAB9ol$hCWJwrʨ0xGD*o_8@BŸp;oφ#68 0@/Y0^0J8D\|v?)nu0q͗-ޫѿǞw`-V<wkW&k2/ˡa nfw?(/`2/tjZ7kbDu_]H,}.FSV#>:߿MF;cy!/0&̩rBr8B92Z^@9ΙF#6ыv`[.n=W̵+OU#=/a,MWs3Xa2Pʼnɢeezɏ.M]6n}S#>T#74wzpXZ7Ea80ݜpC(?? fܙ2&2)p#XЉ^uŃ^#lJV"NB1|FrةT\i vHI7xud4D(WX$ y^nze=>[LGUsЉk ,'դR|BDg#SwrO I@JR3gbIOBqYٗK{,bpvu1ۼbvᶰI{Xb_gY׬J;-*r)N&g æV~Sb'HSi67?n6i~s̈́4hM&SX~{CP/`t ,! z`mPpkIdDX}uc/g|C/2>dcoT:PE|cѵO>2TKko\qhY^#=rc5%1n$j$N NUj T'>.T #>g$g&*jW- ɪf\#>8C.RoTl3L) @候[fVB{)#6'>  /in=1#>pV#oVB6QR*<ݛ/DE7am<(AAVjZq7qXio[+ŀɐJ.,Tػ|eVؗ7P EUStBCz}o; iA+5Fh#fR9>ߖ"J?qg+͟UUH8eV̍\8Y$xD6& c1d`aЀ!ĩ Ǧƕ[ (O<Uo`-tGzOԦrրuAr>τk#6L#=oa6 ##>0^ti{pRp{Dm%\c΅DH5pyJq+u}pa|%,; d**Kg,@wV0x^}yLEi{Bc6Y#۳+p!XД#>7ǰ>#=fGQ^yePkI\#=e)㉶v>}X"Ԯ#=#cQ9$iJH_c*d cwE#=9iX.xs;y0P1nΗ] 4HJ3qt6<G&oiZn(Wc\H䰼xs7i z4`2]#6N,gj.ң$z"-3}[ʥ#6 xo^~\e.e-'l|?fw]ka2cI3jJ6.+@4-$uA/ؼSZɗ{ #w !uC|!h?mg]^ :$qT.+eMUmՆ/o]e~y,>}:v$B9r9W!a#>dCݐYwxsO>#!ш~v.#=cHHn}pmKPX_@(*"zj">wM!LFQ#6qSx px~\B,BS"<#61 5,\bt`#6/s2fn(?+gzlWH=sf's~lڍb~f6 s( V^G`#6ޞҞ?o׎;:fA׫pB`#6LΧ韰ay"U≗͓JIĜ#6RrX3 #>%~ۅ~e@N6Gm;:WBCHD2E;#>e}}Ǩ_ xKBw`#iD\J2̻ؒv #ɝAUͻsSv#}RG?>*g`mvy= sP$ >^ATSqmD9B^Ȏ^eT]ʊ|{3h>Eת2S+`ޢPXI}#>=GL˱QoK#=0_ڣpw#6őQK%C:ymaS:tr%Sv8cfjKma'#62 .!y\qiP0NJ9UϢ DGtԏ=Wy#=HXI俷sݲ2k#g-Pa=#T-A:=WF :G="𤈑G.:?w1ߊf.YcLDG3H?ESߙAYw+z_vl{O0˘*k|CieKZ_#>uu\Q!QuCA1#>v4Z`yKrv,X:]]&LCTeo̴^ʂR@%"UM;|b'?F8 }r2"ݜ G޳,;#>R}|Իlm@P}#6jƥB }x=|$k]lMt8)gz(}ZG:cx<^!%nxQ=CW#>J'#>v${il|#>8Sw9Ro @b#6]=Bn$´D=?E{18>򖐷|4*m+#>CׇGbbxȉTԠy.w!n"P蘾O_ Xb#hu~q^u6GnQ~<n#.H}H#=ý ݐC=z;'D+nաDy|f&g gK,gf&v (GP(orrqw,DSP{O#>9#=%v6+J `JlRXEE=#53ջny ɶRd$#e[*zC<+YBZ^SehB0B8R&QK¢[)eox߾s[e[_Mci|Ƅ8cC]Ҽ9em(,Cy^GlVf;W>?(w[R#+nBi@b}jdj6A;;x5ReQPҍ76i+#>"#+Tg!#Fk>j(JE2|tyS<;]%Ш&@.;ƌ&w!~߃ZdvsV<#]?#6E eXprb"2FoM;7R蟂ǀ@#>'0z#H*qNKJ#ԩڅy(P3<[[))6ޕop#>Suy!= m{b٪`΀C&6V~ cxǜJვ=r9 7V#=ORXy-1럞3CR|cMAp#6'f >`rʺpvFivLA#=F!0#=+0{vN0K!Wg0JU,+C~#0bdpզitue^Â\zk\%b :sFOx)|4n1?}6ttnQJnV{"5CgY+ಣC}`"u٨9ݰkZA!tQB#=H )U #jL]t(;C3[1)PƵBOZ'`I s2%K~T{ b8x%@O&s댪't9q9sA2DŽXyєiΞV6QeFmq3SZacl߄3[#>aN{bgd>Uw$DO<g%#6$,C`"~芈pA@˄z~xYDxY C#=P^N_ePˏJibdlUIֈgt$(Tv -6K) qۍm5׎-d>G<{躰$j+HOU "Z*#>#v{u;=@"WfO~H78_ټ{IZ!"KMom)3Y5TA~a?h Gw>z8Vk8tSPć.Au=?ΩFnHNU/ݩ$1B7EXca" B#=*"j@sݶx^ߓi9T~*$B2^}_nRV*)Z8T}bh=u.d&AU4w v>BY,%'X 01Eh(ĆIӮM9R;HlC9L͠?d‘::bUudX@EEWS%?Oo{6Hsi9fbRe~.z?SקDƢ(4@o'oÂE:G,z.EDذl]dhiaR|25[giÐR}*#= k OS67#>8'd:kl;0i74>6oR>3ܗ.l4nd!6 Q_g3߲k3y6{l''K~{  HGh2zG_Owه\JqP#ًSPb1X8(d#6uR̴C)SX((ڼ?ē(OQDaە䟂̆#6~Jp!qi5T)/rG/T"Pk3| Rr$VB0 ܇s&}DǏq?c'Wľ}d*AصAb#>aX* AŅLG#>ČT dAvVkblvqe?VsGTI`u*z rib*rW݈zaH~'G#62Vb7A#>9ǫ.=/[شch&wuH<[`vǵ'pڗJ;o{BkWɶ{0Uƽ\rEЪ7ǫ^Xw$$6I7z3Tn(0b0g6PB^5%PL#>h L:bg<^"?Zn(.A#6G$9n-|<A#ED@*ҋX (T7l{GJ*\>9bs#=0n7T',`/3upwOyI#>UOGk#>,Y 2/ޡ/ *(=֩j=l-VwZ`#>9,7\wQ> b Lxno(/c#604+qYS)#60 %<`ߣ1wnY=^ *g0 ( Vΐ N}:\h%#G$#>Q, ;|PE(w?|37qv#6y 59:chֲ{#魪fNy^˩u$! E6^6@ZBWf[}$ #6|?Iǝv=&L_aT*Q0E}K8PGreXQ| 2k$wBU}n$a^rqh0 ߄e7qPvᖨkzp@g7āY5ˮYHZN0M-feYL0#mڦ1HM =@FL@m"P|c%' e[8V7p@,nWq[PŇ#>g O,ș~jJl+}n9=US }7dAIG @*L$i^PSM#6~ҳGW D{4#=s$x8,.~89cPAm*<P>| ߄#=|m;À[$x #6xXD=9pb'Jw Dž;#=6(0S[ &=^I;s rBQT)LFsk(œ>}Jn@KRnE)"n0UDr}dA )NC|h#P 0{ӄb>#>KNt&kC&YL}qB#=~D{-QvVl_f3P2MboDO7}vdB9D@"H`)ᱧ99 Y1T#>` kw-ܲ} pz`!->,"ќ[GXϴ(у'Q#=q_kaNdLl= e_l,|Y!s%#>\S,1$\-v?(q8v]X}ڡr=U0ޏ)Tyr0))-Nmp(v8q<,##>aaߩ!~:S:MT&aouϬ{&th`,x(%l2&b?WV:Acq$41K[u%oRjv^6wЈz^dP+6 (y^u1T`"v8ԖP]/Jbީ#!g,,q^ۖtm1ǶI "'STe]5S >uFPF#=m5S~'5$ܠE T^|D}OJ?FoTSX3?#=`wJnhNp  E^J\H銹I\n!$>ڀ]G~zٔіEGU &Y)ՓASqY嫾g]b Ysx!:+e}#=o aHe`H;les#=#>cAp<ݒQIL'뢅NUA=V#=,vB_F:=Aw#>%n}^67 vJŔ/t7֐5jo B.){+$#63ɽ]O #=5e2tFD1Djqz6F@\2ih6Ҷi#=\ -$( K`-H`s_PA#xp>#='҄~wǙJ#>=5Ucw@(;tw1܊@GrI2k sJU = xo7(_aHFnjH=dF4c0?(#6,nHK~?4T}}EUT d?\8(QBWFX܀󳘟0/ 5@}b@|!ҐE*>ahYJIoG! x#AS1zhȪ_0ޔC~/#>O=LŲDř~5tQH?/ ",RH#>^#>#>ݝG!BEpo)tet0cȤ$(;xBDI2ۨ#6[x_H*U^C*͐zw #6=z(BRNgHبXq’2;`($(,?Hiُ$J}5!Bl;NAkɛ~VcMb+}hJM>=v6 oV}ԟFd02jɾޒ 671gg4o1ݭf\2Vvjk_=Q%`;P+.Dt7UFD9 ɬ3Σm,k5WkWJyqu\8dcq77-̻Ba$q/lnOzCZ^w3pJ-qpf>]Т#~0ٕN>9z5=/z";ENSPkncIVFZ2kzDutYTƔg!OO[s8Yw/m:nj֢4R(7戌Vt߄l_;pܱ(u%t"6B%~sZ uoa+qּD.[{a~T̮Gmϖ50OL1|("w{l,;[LC&zFZϣ!jkv_?_g?P#6#6G /EU0+ ]on[ˠ]uï2uWRaa$8j50~?X'ӡHhNt/4Α\WfptFV#6U4#>!)LC)܁h'fEpDlBu>#6Yq8:@!AwJ;2H _FaH pNhG?#='wтy ni)C>6R#6^:J'N0@^/O5.$P P6飢| àvsW AB4tAbH<J(rn#=,7(6lUC3Yg#W.'?ЋWϏg7rӽS@&љ(D*,R3ҵ>91%G泹 1_@㩁4 7vD3kӧɌepl^ZniuҥRq;kf#GcE>.=x(Y}jEe`g?Z_eUđƃ>:$Vph\^q$$s8B#=&HcJc@.rav[;&m T C4-]~C0]qɿztMy9"#=FU"h?;h`} zqG{+f'6e?`hmd!뗸YZ[yN/ 1EG";fC);˯eS,7RNx:$$41#=΋tv1]5 Kc:B\'<;֒cӚS~Nyo'#6xxY)>d>[hL}w<#=PytqBPDHf A2@35$CyxIN.9 m$>La AѨ?fiT<"tB"H9!zt!''l,ntod 8DR1 koYVXP~ckb,HZp$(gslǬï*R@!H0t<c҇g?F3"""2v`Cͤ' PSDx:')]b (J0ݲc|l>$v# q!0$AeODTP%tB(z_fͶ#moA90B`hGŸmSR5amՐIl'/`DܫAu!$Y5 ;! f)a#6Dl|.Xz#>m28ʥRm%:D$Q,8&d]jPLdX<6ơ>PBK͞s NON0GY[37ѳ-ŭna"wn27b)"SMk 4Y9#6#>u)jCvSat@;!-x `RJ#6M''AīC3oMDdPQ$1#=DT^eNwky$ҥ%#>UQHkbxǮQ1vx?h掎b< h()f]ˡ:S82]*x| bdO0qsY4ޣQ=TWr#@A^;̇#^5&eazL`*S#6o]Ag-M"qc0 ,Ozd bISwTHa$f(^áyCuuQx8"ZB#=PaF1XZ#=IXIJS̈ɶP2$vhjVc'YF5gt;wo1X{i#t|xp$;uifb#hxD"HR!B#6\qXqd0m}egcv |OWzc#6fQ>3i__Ý#6+d:j/F#> js֍|̚Qp|vC5w/v{#=l|jNF9uE#>[$URؒƥ mdP`3FmGNIՎϽ7fc rQY[b*;>EoN硂yg#67)3ھe.]mx;Jy&̦\َ2Mdl[,tXoWnjs^C:\iOjADNnM@/F@]KvK sTUc&iK#6VI3Ň9v'wKSVҌlA#=;_O./H3=*nC4{s#6j)O q87jqCPPg#6co0~ a@>u#=pq˚44#6'0^Ev@l^\;&ѧT&No!m^F1㒏w_>I (JB+,6]鈩iB1:08f϶5D9#>UdEd(+pi1bW%`Ym#>D9h78N!3u/CX^=~ #bk$}D݄I.#=mZwTnHi`ؑrrpmVAd"Qp$.VAX:c 916#>@LG!2"`uk>=_ QNq7ס3xL Ϥh巹`%@p7g}"L*m}2vdNd7*ߪb%DwR1pk+[R͸::-sWg,:{fG*Y|a|\od4-S6Z?_'(7a^c=|ªԏ?rsad L}nv=!<IGsk9Fv9[HyǓמE{cy,I.#>,ب Z(b T(;/frɖnh#=v@vDyu 0.%KT{PT0'tT?m %Lj,FےDF#=B|6|Prߡ}Xl! tz`Է^zv4@{<"I0|NQߣrx)Ry)p#>CP`h!~!F=r tf f`\*C3;SRLAHEwMMmf}x+6Jys Oo]$t o}A#> 0>M!>#>FC!=C GGxEfbaâ0c-I2'woS a>wPw#>~ ar zz@w_Ts<#=S(\UI^xS5 #6:0EAqRELa171#6gEd`QבWΌe<̏kaPCk%73d~Pr-+#=z@k0#6[ J1-bz#>fY-G:0OD.#;Agzk9՟LeŐs#6?e<dA~~<bX#6oB )#6%׿d,/`cj%ApG= #=n?}Oѹr&iE&nOZ͔8!؊#>s,Ez}=BYEnGvKvBo=5#6Gs#6GAG֌~"ne;{ bpu]2rDc]>LA<~d[0t:T3* {r#6#6mN؃#=Uyͯ-\#6t;Sg׾ˢE D(#>TD(#6`c#$$syb-x< /z\:rs<]r{k^ä:Sӽ]L#=)$PD"5e`X~yG#6O}lz٩GO>b??O$d0G?WSA!$.E MI₄)NƌYC_;3\ݖ0 "F.CVTHK/#6XB}O]!Ss^;~%$tpeMGhѐYwL#6j;:H8 E?;[×!EGޏƿyG?P/T·Iܴk;=|+ /ԬVpxuy{#6A*#6ROh#6(#6 EI < rB#6J %#>vz]b2䔬#6jptD^G+J8ÞH}[_qG^#B6&_f E[4?B#yJxv hG#6~_>NP?I) t߼V}EkYހnY*JA\ǕUm U:?wFuIԏ~T^^3|+7u71A__uYm\>JG#>#W)  b98 u,m>Gr'^àZ'MWazhWdh~C?#=ǎ2 V:" #6 hr40~<ۭmSڰɸ  I˪ 2 >',n8NbGr$2zxqFI#>>^3;#6t wÂ'L|Ҋ4(?k~~~9톖!f Leh&"HӤ%rݖ#6Ò_?o!oo*H"~5!>'~#=ZOtbhgo6! 5R/InɥODY??8xŌ@BH?n@^Y_t{F#[-tw݂'d+վr0B?Y ;l?eHd6+a7|A6УwRQӚ#6rOO|S{'17H (`rg`è{5QYaWe>UG"YQ@"|Y2/-녗!8 d>sPx16JN C҂CZ1BO M~a3#: u4- 9_Hjʛ5վAC#6>qs#>>Wcڲ{ÓQbI8@0^W\~S,c^W]q@4#z~AI+b~?H(RAu@<Bn[#6P+#6S?3#=s>Gg`0|0e^EAH/#q)O##6.(=)'6:g~?M?9#;@A.Up30z/N;"| {>DAh4[̽7PH ,1H̸2P8?:tJ'R~WڞRYrAl < $#Bݬ4DUӚN:Aٳ]r3Lin:$}|= @$?NɾpO-{BT/lf2fB(DD4!$FXi3:lDHC]>Xdޡ"u3#6yI;"Oۻ6W>1x=ʃk#;/3D0qOJ/~Yr=p7? #= #L^^i횋)&o\VBw6¾1j̰ȧ{?&?|pN$TRxrlO!Vwh`6xm#=~D,h0z?gL:c3^;"q|u {`cMciV)f-Y#>$$z{Xcvh޾Z~#636 L ݦw,+dSvƋyp85cy#=EUI$Q &3]&LEsmnH=eCn@pJTd e]jn*",` Cfr $I^S!m0J]p#6owR{b7*IGRQA n~Q woGA2v#v C@@492#Ⱅ~xM3"B ܾ̄k1Ěִ:xC|.;5}Gx=cRcG3 J4ߙVAM9~~J΂k_Gw"4$#=CUN$6d (3MQ*6T?Ek~#6@EEu7;C_`duޟԟ_ɿ:5 iIoQٽ;`BB)J&cA0$#>g3IҽSAǁS=tDHn`MD<#=u].A.5V̆Ffo{M׸0IWDzjI}8=7?wed{c>ғ (_+gtGpx*MyJ.U9 T?DH=( 8d=15>A-u-#6 \zMX*H<{ha#6{M.%p3? uQU}C~N.^ jaX".+; ElnzB0"pf&fz17?'xeDx>3X{4n!'Ⱥ!13$dE)R&岴{!ה~*F>n?j"|8bFOƑ f$*`3,F#ñrM+"m9>?H޶o'.E@Ԏ0<{&E#>VRfjPXҲ-zp`J##>:۞5Bv3~HvP:8G {#=!5XѬW`JR EzW*ht.C ,H1\rvWpylt".QV~sGŬRg `9oulKAȷa牆!4V)CbW9+2\GPv>w7WL߾G'nWMpծ ګ'PNcu4Ԋ"lf`>~ / v\ h ^̀Q)Kք2⡄AʜfyMAC3fu`jiaOmPPQ(Q3Wj4D@_KɌUUi]sY_A_#>/d/I̬L#=#=/5vztƹlY#>b}\g.7Yͥ$2f%ty)G;<?Ve]?<0oVxa,sq§:gM/S.!6I#>OÍUb#6>JAjŽ[BB: < Ӄ#-DYifN`[zӟѫ"db.~=Μ: (.j%Vi2t${?UY^#=#c#6#=g\ Qx[@2*8-AAU)@)ccA#>P4s#>fPO'ݚuHvCkR-'kߚ( !qFwa콼\,JPLSVAiR(NXgQR42F9V.3sM$W9+T#6hVqҒ5+J6^kmgpä3#>,*zW1.Pw#>XSay_ L~OzKpBA#K#6-݃7g۳/Pt9óEv/`"4N!w8CEGOaJ2#=uD&=xw$!b`țHذ K?~ûEgGC ^f216F}χ5cNy#7!u'GlwÆfr#=3|f_49>;Β^,Xs,dHz1z{0#=fJIdmT`4#D@<2ϑ1OuPPʕ5b [w1 蓿Drfߌ6r䄼'Mi;JJt #=DC^wD>?t{t"Ba-.P#6str|ö_vP.N(ɶ>ݙBV2Ӕ)zIxt; N [fm 9:Nc/`' @-ճMUɴ‹tJ)%.0q+D@1%z@rV7tB6a*;)l951eoX@!1ე% TC]%'E#>TnAX7 n@^\#=xTTlqs+_oߡqqcãy !0WS "tmѝ~ _S\,A H*/J#Ζ@}?jo_?<01#ڽq~}TYWj{vMɌJ|㙏wcXwˇF6=:#(u}wWG7X8Fp, #SYT+"7kă'2~/WA ɤGJ%=U%Ao/L#>.Y[#Flo}[|ړhj^(T=AGh=L8USZMb&(1:0ǑG@_~њO+J6ۦu$uoimNNPj$ ?tH*]&@{mPЭ4'@#6AHÒ#6@]4#6j@BhSP l""#6 @x}>QO(ur#8&QR3#:ouI \,?|2zfd>Bf!~m@"Cgh#6dd zM8D&='yA($ y\yp>zĀ 1AN1W~/?,5+xZϏ#>cҤ`IS1$fh´Vm%O A?^`#>uزiӡ}oW$HBI2 @sο2IIR#>W\`-HӰˑ&Akf0;N=B y@)H i}y+^8j$iU6IձAȹ WSc+9W;/ {@eŁ dy$#6ȡјVz.z_{|Ttr5̌Ti$!A"g۫7k1v]0ɠ#>D =\UlZқtrSuT%#>*F^d{}9F2Xv#r3Z8bO7XJ%"#q=\8hR N6䀄z4 {S*Jύ"@ Ѕ|\Ib9bn#6ɭ^#=n 1]H *DĢe HZ*֬@S$#6;)#6:a "\IVHϟ.A+BNEP2B`@RWR(J@WSrÕ 8WĠ: yD#>(&#>#}?#6#=] }%zT m&L,%IpXW4 6)EP-!YP8]0kW_:P<|DfXoF6 @{&ؒQZd6kc8[AaLJ੒^,MC*/]2ekU "wp ?cU9㑍1:f 8J֪|oT$љ+-4Q0Rjjw8#OYЕT(v3#6>tHWmNZ |`,#=J?y}g|x`bݥHe "0S@я"XZj˪?z4x4RnG탶=\QmKZC~8DJy"q.Zdqqu#>{X#=ߛ.KAUׇ+l6=.,Q'7$=9:e,íiwCf{sqG'&F!VI;^!#>x]rH7:Mo!h0Y,Sq(G\n{,, gP-l!:2Q#|XQvd#=CUnЈOޱǫ/N =-ǡ9=kM`"acg/,D x{;V`hC%cXhR-DĐ{ ߌkvDqfꝳ̄~D]vfAT57L(ӥ9r߷~wpt. 0ӫr ;!g8s^^(͗y ¼0֣acBVw8 Ԣ!BgqB#6]pg}?#=3o|~p?}9G6b#607 Di$#=ߏy!OWI.^.ULb Pzχ 2''5#>? _#6?ɴCtN}?:k(ߏ^~Pz34sȝ _WsIi?zIrIyKoT2Dwu#6LE?ǕPp;<|"Z^?SiӬ8)aQI^YrQ{{@oA=L}& H?=.GQI܂|#6^b||㉷^G~ߐmԑhYĒJ [) ?aؤo'uص]FvqeĈDZ~F~|Bρ/J}/6#6~+ ވb}rO,^w;:&OىC'y6! VI4#6KS'oz1xI~=#6S0܃þ=ib#>#6Dȕ`(<1.ȠhdX%#6<6G3@HQeQQ54;6',|@o{ʼnoUC¡HXb~)m0Bczp|+#6Yȫ_3OV7`Ch!-cDh 99BĝC@s=orx tqоt |7 GB롑IHm%6#}k/ | UˬܩE"I OJ{ ~wExx{pInWfJgz͒v"R.vL=>1/-=#x~8Jci#>4a-G4CKh۰/zm)Ap zc mpHѱuMX,%ZK6D1!0E#6L#= 'BBNےDysh8(,4t (0XKՀ~CR?=7n np̂Jd!CGߙno ࢧd1sG(#>} /};~p88;z+SEӃ[6p,Wf2 #N-biTK7[ѡ$WO"3Fdz25g;g<#ڛqH(a9CA-eQ LߎDI2kr~GiKdfDy#<a:5H?2OzwyN.,ѠżRE$B+*ҳGZG?x w~d)J?>_ʝoS'Mk#6 $4wOjRvGG<OhB0/3+=: Z4{=^W$ 7uc,)"ϢƧyoNtzH0}f<'.pzLU&7A&AK-pvJMcZ-r@^yֲeDICTBO#g dm!Ni \J#dymU*Ub.opѹ7aMG0WH7Ccx#>Q~Kld^3N 7BA*PTR2^U&QPr֙k{nJCE5`& *?9~Ώ<1nKD)GON_McYc?qEzbG_.\#6%#6%T~}1ޟj8P}L̟ٵ[@PVl`c1Řu̇^8)N35T?0ဟN'3#=֢dwL){GZkmc%ˋL<n)fi22c @; X~P+x˂B;;1#=YPͺԒ73uk2PqIUkUՍ1y.IfuQfX5ZmXbۚY1XQ@df50U#=D1wGIOĸ#fpsSmh9y%aŔ8aMCjpBe:dNYw50` IxhFL,mw8Jff3< h3dX#=//k~^w萨N|qӱ>o\͠<.8fmDIaTDEl.!+1b' ??#=Hm9@YxOgiOf}`Q,>!2f"3xr86 v6"7Vlv0pQ#=oĩѕ4O[9:HS?ɤ1#m'27YN#_?~猃[1|*dkorwT~MaNZ*Pt!=Gu, `HV(mF$EnrzsāfBJD)9h .p@j?<9o=<\oAo%pv$8#6?8'kI#=ə}#=w8pr9Jv 6bB ǨSiIBD'G`?XD˼'m9~ҐG0;;$ι H[>!}^ OE6',>JH{:ǻ3;G;߉TGDvZ%읍GevpOL9 9=aqA'(R?׫o5=Fbq=ig˚ʷ;Ϫ0qm2$ظ~ӳ7:tKI!#(1B糵~9Yx?8qva'Inr~W19"E'h׃Dzwմ^Sx0EG(E F:߼|;_AϦ/#uY*Mo{= G}#0m=֐lr(U5\*e*χsbXjVg%OE%\[LԌK1sa#6(MQ<NI67p H,#>-1xS lT1*U7`HϲzR傋bZ](|At}o#=~?L^y H v鄨aB0RXh|X1CD=Oj=C;QY?]~;7M ^@"#|wM\!sJ6S0E,9|p~J?DM\F IdÝ_z^G99Ĕ?6ֳ[L[H2xʡ,jC`/LKsdu#6@3 ~#6+#6ւ$kKiKDL:Q"zҧp!0#>̨=dUP4?T/:\C ʠ@%Y{! AIbLWf <oDA"#6%PPV 0?;01JC½}.s9ZtH?N aлt#=?:xG#4<8#6+9N jPIk`%11O\v 4lyLm(H"1řt=$$#=wj*W!q9ISY<ӃuGJ*^z0־/htPt3x#~`5u'LEtB yh<`k3#6@+~rxv}C~YKϧ-韡WfΡFHpēҽ ?+>TCu#>,'#>X@OOdӁ/!!~IPPd#6į|P*+"zwf}AILJ 4]t، !|BG{O}0X قAti602$S|W=|=Ajv#>Bs&x}ie~H{I4sϞvCtwQ}NjiPO; kL%'|>P=U?>Ss&Q=u+|H'/h>71JwP&ӃywA9#=d='#)Sb%#=-U+%";45.?CO,q(#6ៅ$W]B6 %7\rU@VBP?>͏y<|tMjb/Q/3msn?P1m`Li\ރ#Gl}ZܨfJCdxpV[+rj(hQrOꊸ}&FKl1!I? x@ Ě1nCc#>ƌlGe)T#=Mƃ`GѐKؐ#>*&wqO"z,bWM={~{t=r1tw iT#6N"ж?PMixFބ ސtGԁ.CHtČH2a #6#>ծP8Z+Uc$  [0f;!dBPr<ϬIepb1tذۻۙ t*xNla|`<3LL?ڱ,;W~EWkkX&XL~?Oǖ"V+&*X[%L+ajh$3^d`5QeTgM%[x'0`?_q3C`-RӼQ`ſ"cNSފn}.SVxui߬Gn앩^?pgҳԫAש6_e\X-S/.xp @t5J#p.8"ݫ8lcOY'i6Fog+gN:k v~D>NSh[~#B.\S#>P;i9χ2l>}De"s6A:AJo`. ,ާ\I<fpwz,!͊!~B<vUx}S:$?['myi$)#6|8LT4_QH~^q#=oI:9.ñ@O0IIHdb'񅏡=z{S#6bC@*K1#>+E7cyK#6Pp:x^:95.OO˸"Wb(z>g ;̼, i>?Ws3I$>~٣4>_hlkX")r'|11>G)ݧ#6}T➫YOU5+Cڊ&,=ٓ @'XF A#6lK$,b$} 4fS4-'H$%44g!x =i՘ғ8 HaL!M4  M({nCesBh&n{`ec˴KWa4bsY5/m{s?nzi&hnFy&[£G1V o[@&r5cP 2aE9л4Ԓ;`8þΤu #C6b}|.9o`p~'x }UT$0=ֿ">6l+쏈|Iu#=>Zq.;, _~'q=Oy糉O?.^.0>أX.A#6E,~EvSFfmd@0?Ш}9#L1Q$37a7`1m#{X =SP?#6K 2,'1ϯ_g?t؆SU(_Q8]IJ:r45a#Y%9'a#6db@ֆ`X B#6&s#=d)0R5AK~F&)ڞ?hx`b>59Xմ!{t7rL&[.K"ƆX䌌lfbm24ݐle+8pP7YCA)fZ5JE:T#=J#>Z/~pg-q"pB@' 8xE|сSj[wuaULƼFn[}_2}vr6$1|AG#6lU? 8Y:xWߌ?#(122ۗM5B#=@#6lm$#6FJW>Vu@n3/=IZ)8KAO0y#=ljǪ: 3#{;KY|#k`;>1@u~r5pI;BhGW a`C(jT)R+#6hV&2p6`?π$jdfAr ۶_yn8iayͳ2KׇтT حaM4uBNMOM#6?_j#=zWsGp#>Xʈg]/_1M}?n@Th]6ث2$CrD̫l;BhK]gtl)qqwo<#%8Ns%VaJiZl=BG:[fu&i ȩ'r$%N*:~ XQ;~gEʬ]c8#=;a#q /u(L; c^t&MPxm|_6B<4-*Ct'\9Gu—W$#6#=\@X(o~R%" Gǀ,>\G,U3]17Oj,XĉH[ѿ{ #=5ϙ!g˿&Fp=R#=}Gci'|8#=猟I)cY^sLԊ}?QdO~ŧtuFkt07l $96Uͭʊ}#=zkR؂/Aoj%T1M`0900V XAjjnsMY.o_Ej(Q,L -٪i#V '&#6+:#SqNC5E*%;aDȘ[Fߣ[|ɢ`q[)ZЫ#^l-LQȏ@poFdX0:q2cp@&vH^W_yN--tiɆQ|)#>]Ӑ^,cq_eBOʰo5UeUYeA_6Ƹugi#=#>UT"Ϳ= X5tv#=Y:d2䰛#>Dse(/IjmX}aYWGQእ ;=Lλ/#=axoE/^ #=)>!e`T)j5b #>\)/dr̠EТf"8B KM;?G왟Jrnc_w#>E'zY()G X!47͜E}a?#=g#6}]7G}~a}E#=2ѽNfyZ6,c3ߗd;&$ɨBigۊj͘?UJnTҟ8qF;֧ŚbFBHem w=8 8 ,Rz˸~a}VYYR#6'JFDбL$y=݇&9C'(Yb&́idU 3LM;olqaQ@[}US~8@P0P|;يdCɑA!#6p3M=3(6G:@l"t%8c;[zQVæ }^am۴ke"ʿ% ?nZzEbVCƅ!|8§Cs`0%a%'x;[xuf:{{'EW~苲ۆ ]QQ:?נ 8 脭ȃe}[OwۓG8>O" Z0t`IH[9͊`N חn5-m <ۭS]=pKhf}qRTWL5%=Y#6-_@Q92!*b+(6w#v#z?dGe#6#>9l#6z/ uUfbf!\eDL +匴h%.d93)ekscigKBnfG8#5{#F*GFB%$]0#>*R:3ɣ-u3ۊ_sg r Fl}Y"]s$$!3EcH!BRvW>ݷmP< Y@F#W#jsi\E9Ƀ2*ZtK2SQV&"s(" roJ8+9Eת\XlW;^ch샿[lčz-ښ*:2ܲL6#>" _N@?#=TyNǑL'l|`1Yo4O]ШVPavNjBm߇@"xé%'tr,7vy7KX^[lXhsMsX7Yc9ˋ{ '޴|+:,k\/Tqb߇$ D;idMt2p5јhႇ\/cWQccŻv*c]yT`Sgɐjn)KEJuV<Pz<W#63ڑ@} 9D^V96ޔAS@U'^e ~+/>栰S܄h= /b<}F8LM(FXOKW'߽ H@ò_@#wL%ٱP%T(3T".Y9s)<0"LdEzKAPA:D@>q^ŪUH#stPt#*ʙ' #vח$(H9*~S\Ep?3$$O]#P?G?aF^`tGOho#="[A ozd XPTcz?*l([״g]l:]f#6#6=.*>~Y"(nD$rGr@;Q$b3A(p-S *m7V?MW5C!rN@:ubW;MgKx`܍ nr A?M#,җnlF:?#$}8r #E{fV!l#>Dt7w=dp|t#0O~gKIpwR1#6zP,'#6sMfv\0M(0R̮RO$z3+,Ens#>,y?l{ k-H7ZDzuS{@. ߚFCIqꖕY2D>a#>Oׁ$%? iӝ"EI},߬:πPP{CaC'o$Gw>A/5 EP&\н|.pjsm$0fq=OF*!~#׀{1zyi#>[ُKː3Hz#>!> hs0pr8$#lE•`je*!HO2`ߕR?R[?Q.F)p?,đS CAgQ7~mMAK-E8a4z=% >Jwm9I!sCqQUV;'Z#=AFÏ@|Vx)Ъ];O9y3QHbPn'B~;fm!H s!vliDV@)PBBC>dKjKIQ) }}ܓR!p5 +aJ#>Q8t:=yC$DmH0r4%oVbC =!oXr!&I #=^x(NU"#>oH`h#=`١nA\,24Q'ͪդG)2@Tсݨ{v'0l q?O`UIdQ#6;sm p+it+c9*Pyh=F%#=b)#rO=#=x=nSi001 (yG祠i#=w-  =CaNDjdda1fs.xMY90مbEb*3o`˖ 0pɇwWQrulv`ysX0o|]g-d9 Mr8Û8Zk^IåZnƍ, Tp6c*QouhΜYkhQbJF'&@sFEyZ &T`>~9@`Mr(;Y8T;vxrJjgv{W7 g¿'>>oc|=>GUl z9j']kI~}{ LxT>Hv%<D9V.qox#5Gz[Z|?LEVd C~G1}z:aZ }w)Jt!fXeoI(5HbG``AtM.xOrN{w#6q`DQ`0|pʫv`B+hqe'uǔbQcPr%N6B_IX")蝓}[;&;ŘCs:+5#6bU]bxD.x+}%N 4^|_hfkН|:끖4Al|ՋD(`*<.Eݣӆ`{z׹~l/oZvOzyG{c條* F3:UUl{'c`4P߻Cأ1d;H#=Uy/t6'Ep*Ҧ,. Ӯ܍a vSa"jH G6Hr'["Nyn\h+c',_md9Z.ueEP"Aդ90k2JUb]^ #6o>`Hf*t=jHCPk$xchrd^Rk1t7ݖ ȏGX~M1i`S(yC;Jxax-baZz+1kn #6ObDCS(י޷9$놆H??֍Y9@k\2`$~lU&N#=q4&O4/Be^#%V @;qjER8_䩴-VEZ_Kxc ʢ.+gpv1D|eVV;p(,ro1U) *(~v|ndxAɾdžB]-=":#>t.ûҲ)&vf6c9&Ȉkoy0 >`r`(vCjD72~ZdN!xHh#6E@y,WhS|-! ۡ>LFoG4Mc8y'x I6ȅb0x3=%ToA݆'Xr;'0ܸ=v_b.f)*M),WJ7F̭7E~X%0mHQ} 3\1AŒBr89L=Lt 3S/YU=Bx͇ţXcz~{{2&=ش%Q֙>~$BӴ{1Kz9bi Rc"NاБ4;hi=7#=hĈenE#>Y3*ַ9X hHT4D)27EȇQ!=QDam]A\!I~Lw|aL|:up4 N.9N#6:$R4Ǹ8=i|Á=A 8!9;¢ьuk?.Z̀D^'#>|c̡۝ Z~ySiZ[}ȣN8%fؘFL#=(tIx7\SPL*T!7SB^O#=8M~n+.W~3;E)ĥ? *B t #A#>ayd8AG]>|Mb&&3tT=$&`S7͡q2)u'cYtW/8_yGHnWWRz{0!lpa'a^l *x3a>@  Jh@4P]\C쥔|O-g]@'}eq(#=Q}JzW˙U+Ed؅}KOsy%I3سӼ/䚞gXU"COyXV~#Ehy`T8e9Ӄ#>PTV4jp(fEwX1bri#==i&m{=4h4`@2pH<ޢ_I|/_g7A! D60P II9==Y?'_ u,?z?ȬUO&u?0Px,chc()M%G=m Q E51뜗9HFXYґ463RȑeOb_sB''3#=˯3&FǗQmT[>ر ZT(4PJ$&i}h#6#6{dMJzbn2K2PŇѡ1_LQ#>4CIё2Xr#6@T<}?VD絚~]Lrr]gqbF&E݈ه`P' ?pB!3WAaMyͨZ vQPi"Ԍ7,@BPn&0e%(%B߂Đ(-@4|Y>Z'z ɁC+-$!ϩ¶8ܑPؘ eFS[#6)J]ԡCBb*@||#6G$euO!_AB^2 ppo3#>d H$ CvFv0y[^r,ָ""C!`#ZY%R*(w LLaS!#> o[2dܾ1_mC>c&R.0 Wa8I#6G#pcD|W4#=>ɴVi 2+pBV(:pǃY1 ;gpo,RA!` {dzϝ)771PQ?\<ɰN#6PˮçB5H,gRA%s躆>T\JFTF.J 9xQȇ=7Bf4E/-#6#6.#69!Ou5#>B*rm|bjB}tK'QQbJT#6wwTCC̣ BM-s$PPY{>g$ހ#>@t#=2uD>R* 4-|}mRҨ#6@2P$C @!H JgCKQtTS!҈![AoL&$ʓsXZ)-W"*#= („(]p(3" JB3%uI()PpmG̍&\k0AMBd#|]dBDЈc:#j$ #6ɴ.i5%)U7r+f*-v%*k\pi5X}"jي.&fEY:NS*{'@&,|PXCnH[y5(/]QT qIAf1;I#>@uw, Tk!D:$j#6^bWEZP u}W>ׯo+%]N(h# } "%-VZإ[U+D.J\uDJ#6Z#6)Epp|* ؾ=/g"&hR0=Bg$H(|O#6+@O(fєS#=Tg!tqVЇ,݆dt:[͞Ng& Xk(%5.-%ًCQcژ2]!G4tI-b+_,bmt9@?[T.DO)WZ"^߼X{hD/FdT~FAv/DOÊ^3vzf$#=N/p,IzC>6Q.CV*27P!DPkX}0+Lci4p`HF/vSC*ybqA5RuOg\8~#>?d#>H'rS8;>.8ߕq0!90CA^sYؒGv)ޓABаDB3ɳ0??Ҋ@~h2b ?njCX˜w1#)zClt{h[NmUL?BZJ 5?i J 灌~8gQƉ!j"d8ӌdHDjdPJK\hbl~jod̐#=Ut2 vDuu4Dzz 1U(ZU#:E #6 ^gOZ֠6&<l#=Df\iR{rՇo}ۖW/цcleN"Eh N6]뺈k{<7q$ЛCHDcm(:#u!fU(bHDElZjY4j-)Q)mFlVd͚-dRil[43J+IѶMchS[5 iQM4£ $!C*%4--,a|Eht[y|?{Oqd,A_mKT3%Efcj5B6mHC}*~\(֗eNi {-9`7IoL76G`& hQR iWw<)rYFa WHCX!#=ϡ5‡sP2OSz0lg = 'O:J8#=# zG !¨!98(ZVwSa[TyĂ\ܓW#>rf&̨cv&Nd?:u:;1ˮ[-1{xgq#6X%mlB0ki#= ..mb g6`cm#>-g,b8q\`Ia M<Z4;ǧdlq-֧@iYCgc*v =ꖄ@d)B!$@P Xd#6IG, #6@#=^_ Awx|jAd٨(|OHqbuszO}[) $P_A:o yLIb TyHDUO.C?g\=]G pX_g_#ʝǙX>P̶A]#=N!Q5Ad/\?ןZ޽*Pp y_ªfa?أhCiٖ'jv2XڦpTBZXH~I r 5^\@:PuꈻBO)O`!="'NP4jUh5P>gko]=!u?)!R y뒯{EO༄)\_.0 `E8HSVՇA٩=)1X>p󜅀ҹGvPBH>B9ӯlT; 2#‡棲RG`g`qO`NP* XL@s|Wti&I*.T~-$CATUH4I7p|P$HQ@~FsnIgt6˸e qhcl18$o7+03 r̙y,fޱVǝ2i6D;K?(J#= R%ViKIѴV` S|='#6֐31r12FHQ>r|wvGsdW;v=HEv#62C#UY^Y/lP-Ec#EkYB}y$v҈IM=l ۹#6~P^-'t.CAAd|"U7(r&БYnmbڋ`E\B1*Ue1դ n\-;#>5przWRc!`c#/|yB*gk%հ^[6xW1<ɖNm|xf8Lq]3bE If4߷[#k8A#}/f|>v"Z$z= Eh`}L>EW8F)B"aUD\/˫ݱ%] =wí;"@ϐjXQcj/|afcwDt#s#=\0Ȝ@#=Ѕs *2#6<#=0$Ɗl shN"S3[='B=5%DTy4=;W`⩯| ?#k+mD/1>'Cx3.>h~G q5CzL;@r@|ϧ;d_s8G^P! "煳Ǧ^qr>ĕԔ ,2r%!ј7Mr"@H Ŭvȓ1Vq>aέ#>`?C7\M놑3!ĵ$fFK/#6pl8g+b/}=7"vīIo/w<وрN׬ G՘RDx;~>_UIzrzOͪbO#6K4|HEs3R+R8ݖ:.=#pNox^#b n}{*!(0q1;#>UTCs›q#=:g'đ,Asj?\)\`44UM߰q@hSh 0 kdbD0CR>^fA?gRl!w#>5H^RDIM!$Ehjϵ}j#=I%l~t:{5Azun?c/=2{!OvzO`D]cjTſn}Y`xZgclyI.m,g,>D+h5HW~lLScU6:aƱ$$So2$f@LL1MSfH 'H_(,\{:{O#=uɑdDh)y^;{6jؐ}7X̠̔vG&CrR]kMV.ڹY[].lɕق )cwOr޽1^pN%ܑ8Q#>F8p'D.,;!f8$mAz&db& B1D&B(En`r!94EVcdӉ>߉E2Ƣ&(D5hWP% v?~:hG/-e{ 9?kevȰ#>1d#=nqEqkOPȍ9:4J#=(ŖX[Uɧty8VJO)uև*{h}+뺾E3HTiGAet{Ɛ҆]D`Qk o9mھ6F;\$(RhFIn ;4NVakhըniݫ&.'!XO]HjvF.pI4,JaKOeC?:I=k\j1Q6hOFf1VQj8Q4àrxx}q%NMz U#>xU L[aJE3dxfc'U#=[)5(0$_YBx<!l0ډB0R[Hu'0qjudcQCbh=Gu"{\]moZ`p)Bg.&u>y쟇Bq%IsqKKѹ޼(O71|Le ;{Kg ɺ}I]JutS!#>b%"[!IwA8++ƻ4XN!"fiszC~3Se]) #6r#=5&ڕ#$?I hRAg`m;6xw|;U,tof#=Z#g29;_WM0B #=h0KKqDẝC1lGq#=t4oQl{(| tGMHrK!?V\xj+=Ȓ#>RIzg|wQ*.Ea.3SRD$6|p h:t6U#6Ls2cb`&aJ !M`?*C|"$IăI;#QqQ`Č0ZFS'8|nغ1,ZwJ L'@sÉ )hdbA \|=TQy ͚՘C #>X5Jw`;B盹i0f2#>TO24[j8p12Y.lMddR #6 I(su .F2lOa(#67Z 1%)egnK0r;G]~jx0ZSXP#>0>/ 5a(Ap780O ONmǣ 6W˸=!#6.qw롛ZF1 Hs!r׌8zWd%{X' $yar2v,z8#xu) s yOA' uٹg&͇'d3΂CL<$~C3``:|n P}`oA=sٸ=fC3? '%*Sq5pPA l8[.E>JDћcF1!m{n#= I%}:0;Uf`9bR *)_L'tjPnOBw4J) >$"L^_>^Y}O?NB#=nvCmA&t/u=z\v&168.|I')"U_Y=Fԥ1 ~jZb^Aѭ]jII#6 #F%H($asᐾ&lM/gox٥t0D:=$@ |@ݢ_466dP#ƌI<ƫ(.ˎ#6},*3?h#>!o9JT ƨq(ǻ!.|\>*(!ZPJj58`M05W/1=0vɊbS$@hg`,P&]^MFka)XA/̣3kF5^\X%DJij?&oBVaL#H-X~%ΰm0w8,;:\W(̵v,RplFYЋ)c5IC7R 4K; CU&a KZ&Esa4p(5#=93ѬލPj7+_#yw^WZ]]n0#>Xa  %䋒"2TUNFIwYįJfF! 7~^`(A(4Ҋ&4U7#=$Y\q@vCCtN J(d\5PIX\0_eka56yĊ|EHa˜F LZa#U`f4M[dq"#>д4XD PІXa PU"l5Ex557n|T,ޥ#6ڎ Ua`#=Y4*+ S|8,ݡ[V7blU&1yp#1@Dv,:.&$*`bqYE#~VG8$E0 1N1h<lVhػ:aLL j+fgmٮ֙\+{:l>yX40|g*ED6qaaWa7dC L}$2AlRV#=0cIˌCV2r8j4ƣŒ!ƫ *S!+'ۺu}MřxkCJ&QxZ\*6{#>-ʌx.M2D{k/\h%2]<]IAxV_]KZzN8_Cu{yNgZ݉#=5঻ ^9/yPz{_rPygayC;uȕAJ֍G͟ݿqå"9e[[zJS!`A$&$ b&Bq]z i0]̍'ˣ=~lAi@`.v1!;nis@ݛsA2L7#=#=Hnz SRtey1R,80аtxu*Z,WcrF0S/P,TLAC-hB5!+P(8!BX,Lp8vͣr݊q}uѪee" uiG8XUw[B9 45!uiv5"t2D׆Mɶ낅T,I.^ lBQ4 s؛6rB* galK듳HoN k^<l]PʰmtikYM&@2&\~ *KhgM#=ǐo(X34!V3;dъ')@+:{M>_#ɢbc3#(=X1;p+F 'vC&kgI:'& 0L@,ߪA7S۷>b#= #6P0a L`v{'(J4.͆ܐ؁> l;&&3JT@QA#u#6\%؁ :O4#=4rh Z.D@@#=r.FA 0=p(jd!]ó!SJpLDeÎbdBc >z67~S_X˪Y^y!%/-g(uPdc\3$&&5dGx+^CMVp]o04Gz98Y3-{p_!UL4QU꬛Q[j_=w%uHb+ڼȆYX݊2D[Z嶹jkJ^3ky&LJ\[m㴕ۮz4M#h+@)BRЪ;2rnQ#6"#65+C&A0\$"P2T㼓?>ObOT/x O ʘ<M]t6bt@# R`1C kw~{rjR&c(˪9^^3(G`LݣdLpag$ؚmsO3#> "7צӈm#=DĨ' B|#6_ 챡mu|-*3kQHի0X5cc`:B+.+fb#p@ƌp0#tNfa.;Tq ȁK3T B%,8QEdfݧ7TF$mlF,Hm*XHhaZOgcFv>iܠϐP_QQ&OI4U=eNuvw|K:hbep"ȌOPfI}L8#d#6~qO<:' 'xf#6?_7:y&k/nN}ѓ!30?`!j Tr4 *:CKq3;*~XƲAp#6:J2@{s79x~ui~4UTFrCе/ܴ0V\r_lEZ#>&*T`gDfGϳKCW*f=J'ȯl1OuG4yT,gX6\CmU|Z7Tfڅj@Z(M'ys4kGls`) 6iRD6opXMCy`r}{BoYst+r䁩;HmSi:ВE\4?e GHr,_ TatPA#=X .{>O 퍚s>70#Ì`okՌ}%{_l]^UgK!6>$:2'6jL6I頩]0r{dWìc 3Cy#Ui!{aVW#6&GH=#=΁k*vTE88Yd8%ZFp_"=U:ym(HAa+0@(` 9ǸIz @Gp;yC:WRK9q\cN#=XQH#>"6v:&ռ7H.#6w;<ϧAy D1p} g}15/7Dőaڹ#R$m8bʪqX() EڂG"RAi>+&q%T<(jE6[V̊5m~;esnQxi<;γ/!:{1ޮR;(t b/iR8*Rz yy_?T*1fN#>*\5;͋+;2ܩL^P"=cWb;>{*dLP]<0Viygtk1;O^,+frATeTEpYwh:"{˰47 5[RJ;O;_mfQp,p\C9Orfd5HYZ+{fTjՒXQO*#>1hÿ<"L]S$ܲǕs(s+-;4D+9u!sӢPmRTIChJJuV[N`52!#>`pɍ`n*`oFT<%Twȝ.L`5p2TLSqid5v;vw;_7kkpx&}g/!OE yr Ȍ$t&|h-g,*cSłK$ ZD&@\w"Ǿ2slt<кȁFSa""`(s]_:܈΍ ֩lDžN[#6ѬHqMIV'#>PISVzFN2u=M[@<lXh8p˺#5'XF8>"T#>[.K=N81Z2 C@갌60x~cț}☤2:9bLcẁ'pUnF-#>ǚk;aj{ p`Vs"$Ƹ\xr3cNvv~P)Wn^Ƈ:ti2B隄92UfN5qtvRrC&c\f %Co.ь԰˧9]lx_^G(`ĈRRܐ|#=L(hFFZ,2{<#=〸hߗt?\vHi]jT♪%! 2C\<["\c<"4uA;x Ingbzdo#>|B'DsQH7&kbX twE?#6c,b9CƠ|X:ņ)#6Î]IP/~.xpP[-C]avhAjsVCri" 2!%3-~l-7( gR;7pCn]p_mJr4aQmJcOnSE%Ş,6F0@κU\OݬǞy)lBo I8dwysvJ5f53.VI(+ o"',yvgTHAj 9]C`j{@a/cd^l(qhc6mOh]p3i,RX0Eq CRfS160}JCl%NplҦ>%(g㇣W=8aӌBc7tԻйe0?"z~)`Nm4s.2wvdӮKDi:bnP 3?SݮD{#6pJӤx3$00spH@.wOxa뀄fp@tD!Cq^jБ(p7 {\ﱳ/F{dUM62BZEwv4EvchݾvCلa-#p`5ƭRVDc6vK%ʔp)#6|̇ h{⇀,959fҳmZf oKrj#=2iԳTI"fS1 Ի \NV@ِ 5^~+6ϴG.:Tt"\fG KkhghI87Ps49sPZbp"#}:NgL]&pQQG]GYs5f-$/%IٮW 3#=r2j^#\oƫYGo䌫F:sZNqw㜫 6OsE1Xn,]]˹E5{;.q8g93%.(P%evsHȧ]g`=CN΄"8h=RYA' laid8yǤSG|ozM #.*.2[QhI;pkg'Y7aCD݉liK}(pv}qp#iHվ[%NP>%gNrwtiH$ņK=0u&%N.f&NF#6am["C#="5uJ$bUԨ4D0T09md 3"yRaaT4I,{)r*fz:;EWos7Cc4)q{V)+wkG-lHK>2TܗS1=p Z9eMˡ8Ǚk*hJv7YކKx3)&(E#>>rV]sʋi8외k8/bl"Vsh12#="Gn0hsvm ͜fH& jrK@\sE/!zݡDHTTVYډ&3*\[p!IYx}?X &FTqOENMpLR!߲mĦj؇^-2tЅ1Lvh;mPG4AɈcf&"s*"CRɐ&UPYsլ)?4mh&;0 ;ꐅr92x4eeIze ,tECfVtK&c,#>SmA騮L2P6Yɨ%=MeiJA[;l[2ptxVNYs\BN:VPbja#>C%;X#=La;j%76hOwZR%D,J3OsIeVa9Ӯm5電2ܵǝƣ0m2#>\tfEDM=U}E\h@"]>rQ2e;2\%gT׉3(R:8yD&GQ:#=WVO(Xn;αv#=aZv~QwG)](N@Bvy By9GJx Č#=3Ոftun1xLHs!,KmDm L1$Ƭ? qlڧ"i5N1땴9NM[x%#>#6&o ȢQUHCnI #>MKIO^}&iC.Ԉ4G0d|[Oб&n5ݳOxdRJ$rݿ$Crjg@wI*3vZKr͐#>ö.{h>6eDt258: 9|爃mfZjO^oEi3joLx*EN=fƫQPn\I,ʪÇc^}<O(Jj;A*+ivPmލ,tP*ON;7FP%ؒ[xhay?"엑ԃ34 wtOѯ쑷2#=̈́J1:#ĀxI.6)i7:WX,aդVzԱ}44ًdRva;Q(sanZF.A5ϕM )2 Crt̲"FM腰]WeT )clMf f6f({0qj90lm uqEP4#=لIL ](e6aiƎ؞VXQɨɥۘP v]٪; Nә4Ο2QK-勔d2MK2J`l# &wMXѸh@" fea<4kaKD:M sPԄAi?0O 80G4R"MCrBNUBJ uZ#6䋌SWHb2jj2o,#6:ve0查zqm4ax;k[rn0Sk`eC$v}4cdپG\EŃ#3pbCBð:ŖЎcB0F6 hhT<01\8yXYg[=\"Q0vy@Tb҅[Zkbyml2k#o]I?8K;IUωh#u"tvnL%#>3rapx;RFf#> YV"˙%g(o#6;1oiR^[Hh4L9eeTtfmM8$#=ylhcY hh4GuMi`hxȟDDu#,BdNA^rͰ'C,3421n #61+p8#=M(LCY&Isy*8Q{IH9lC#=@4NN&fm3Pr8Xm#6'7 i3sX؏Gr7l֑M8yE"dS7P4=A4 C/Sv'O7s  ߐv*1+Z񾍈w"{@NX!vJ(!e8k}v;M#>x5o~V-5m`S!"U0Q)Q,N)84rCs7)J7)r8Yu#>Q1b+11`R#6;dd.];j @ +@DVcpuPG Z0 +% x":r`P#6Hha#=0)]yp];LX]sfXcj,Fc>K~ ̛X{ܹbe+TKq-&\js-*Xb7+R $#>Ň+(y;[ҷa؀YS;THGfz#myw#=d0!$C!-; q7b.or9ُp91![^V=&bV'8prtNo|{O׆O2a3}4^$x0^}w~G^V{={5Ays%rtdw<6&~&bbd'eď Lf+cq]ZUo>'pi4#=[I0տPE5AadOiY]u NM]!CIjƞ.^%%)Ӥ=FlzwDDQ2Eg,tPC3IżxRZ|3:O37ғ"!NLF׏-xvgHu3ߺDs#6N8;M@:SPϡJkN%Kdʘ+^`x*I)<+aTTE5Fd0=HbL'6(0ㅔ!L #TQѩVZJD{D3bsAhcJ Uʖp{O)@b $/wRxGNؽ>>=JpNsG[^O=;cG'#Z)QBR"lhNN69Uad#6X1f/wyC$N缑4c==ູ CCp@?ԟ#%FH'F,,1T:saDi~^! I@4+!z~pCAžL/t؉Co#6Ox˝C%74ߚ"<)MMTlh%(z1J08!/YW?1 Џ~ˉ*<ЛU#6Z:Tl?2p?wAJZPyϰ㈔o;ϐl.b@KI#>!gB4 圻`0#>Do36ቒl۽Pp#==N#6ֲYs̆*pF#>Y:ȳ5sRxUH]{55b|; Kzl:h6:IsIS+AzA݋^:Bs%=1Xڀ^ä KE`F~fņUD(d5o0 񵿧ٳziUPE}OX4$TmlDXc`ē2#TC@dp)HbkX?r| gq΄#>$_)@2 C>}HX3MDr}H+\y#KCX1Al*Bq⭚4`QW!b Pmp6k 5r2D`e391FD"63d0J<, cd8W1H@=-4͔W$hn9L0*K`UYYYs!D#=IGbi<□6W7@3!DQ#G; X@b%UCMAQSGD@ā#>[w@Ĭ#=,*eˢGΣA!:7+iUͪ*|s5-vV)kxf#= H`X݌Qkﻑ}{Ҕ% L0ݣqTVc%1T)TBX ci{iэC-m濾s a6K/a +xCPDA\Xo6:ˣ2#=ѓPuMT@b2356y*jLOT[.9/TC#>|cL>8muF;"("0dITl*!1#>0S 'XQ̅#=d:%DDQ#P芣KOBO$pGJ=Ҡb .239$E-BAmpEi#>:Tqb0"]Lñ1e\yme&/00lUjMw,PݡfbnmJ¾9XMZ{?>\2fr5^{&d*[ngytfSeZkLX?#6W 2 #6`}CMw|Bpd}@ m~mi-&d#6 OUS$3=@.:8K2uf\x? BlX]_bQqj}UcPg4E6Bo59rThc9Ej%8Nw8?MeL0" [BNa]Mp|x!0핥VdRJբFXY6ȝo`)HXJXČ+D@/S?7sߖ3.e7:s 3YޤnPooxxřfbgvTN'2pblOËo\r'ȣknJbkP+҄\2~K:E8c5fWM˞9=lNqڙyu(t\@\A_|wي5[v"yI\uu|7FNu|y] iƠ5]a|=ƫXj$HNܸv=&4lc}IU^#>^Z0}甥5GqpnT"HX-F 'gXRE<㼼knDz<+jpɊþ{=F'OFdV1ʊ"ʛ]*_6}b8}aJ`fH積vfBP2 ,Z#>窱_cfl ;j襡]X*&$TC"I{D.4qf106%ڱ16KshIy 6N&/>s~߈0ds0!$ҥs\b%a驻#>9ǜNNwX>xƹ8A-4 Q15<\ժ%^ӘADUv\WQ9h[Y* )CvJ'2HV=qnCKH9֠s;F QE7TlP0#=B8%iY{Um aMT\b.zeM\I)ih>ZYFmDe#<|v6T89Q[Le̐5S>K|$JeJ5sI?KcK^s4:Ykp&[ןce^A6J4m˔JXb]89ظTYi2JSFx#NxeE"tVVU˙c k\Jc-Yx:)ٷ.mV3Lbâpя)#>,&Lr0cSL4z2]fq7rm4w8Z! [GBwzc,l:m<3d4|{WEB& <#g2>ԡ+{-rͼ6Qr̳j[XCҒ JRlS԰ u/-k^Ԕl[#74iK eMET)X\a#=zlsup+I1% 4 4=)0bx(t9R8R bd#6{xNfgR{M,DfT"T?iZVD)6pؕR"R1H-:t;u#=85E&֩v]ݣmv]I-HsE-*qlQVVa#6mM VcB`&̎HvKtj1ͳSD. %D=G #=#=]$B2K` c"D!RKM`՛$&] $K!pϻWSQ+%X,kLՅB(E~$4guN<*)%LaBGAX*"f@Xib}ERPW3 ,M) JT5r"3|9iR#>vI`"#=#J4 RR4Q15I"#=1}gJQO0HfQd 00=E_4؟J)01:k@rj<ٰHy{1Ԑg g}PPY0QS!Jxχ;'Qun-LԆ{2c_~ u au'[jauҙ&*Мa#6MF@񉓄L]5T=~NXYkD}/J!5#=bJYJR@m]'(jtmބ*$x @p飆L 7d%NB(16ɷ#UT&2sx !I|iID0NDźN-34jVHdAB4HpYBodLB/9: 'i\3cy{ípxD"#&?ݹ$רͅKڽ(;zIȖz$S#=Gb#P`(sfdx@-XS1Vƌ@ 3[̆術^+e{ҭpÒw%#>ckFPւSZ46I8@=ؖWD<[M4 ֬CJULjp1FDdRH'螟W>kX1q{7Kbg?~%BG\!yfHisv Gw$iʇ#>P#6n5>Y tmvxw3o;J51;-J"O6B%c3'Xw8M(Rk0xp2x0MKd/ju{׳r&nOz^.oj^k^s'-AfbLgQK9K7iv^]q+ {u~KKbɹ0 _FÖOLx!$ ap4D[[+Zhur{k&8Fcv '{51dj\bkVtՠ.ن3I0HHӪ1͎yJ\ NYhTP0[޽VUmLܪvӻP]zR9=Ճl#"pJ0$vX(KJ;wiZ ab05@rYi4Ihȱb\L+6ލƑpFƤtnqqj67qLE(S%%uc,]̋ vEEMzTkgyv'mSm%0o%{"/>ukګ,+I #6(~wO9nbDY$G &#=S#6M#=(Iudм=Փj:q`5SQ>0'{xU4AF`1FnYltYjkjSPkvE̚D53#K!5$LCT8)Iipdı+Ō,%zf&`b+-#6R"Ug. }" 'H#6!r#6.IdYif46ɱbң[,ie*#6+,0,JIE)n#>)+,5IY4J, (4fYM 6LEI1f2PTR֓e6-RF4 (#=ETe RM)̤hPjH֔CY*6hPdBT̤S$&mF5D&5)ZLfڬV)--B(@TE(6J[fD" J)LҢu1)TJZE#>$4K :,AؗS#=.@g [idԞF#>5E>o #6x'61CVpP2v^ OBx#6&4=('~(iy4,|gM >z(4 ~P$e>+tpGMA;bs:û|SfGdTzn.a0 C$NV_X1t)F<\J:m Pk!J"tvȃ*̐J#66/)#YZXe~P9PHS)1H֦I!`4)kU!󾿶8hӮ83ˆ Z  3@t#='jP$w$}(#6'=xs'_àvmeH[^#=:pQ5T`Nlty@: O90k:m@u'p.M&X$>ic&zԩyT|i.Jq̝!e,PtSqődG)I9B>fwl.=9OSd30fϷ#6|4i3]7+C3 $~#6S$)JU SE' 2ܠ=!M@)IwD *"'RϺiectfBfHLj@A;: d"a[Qt#=( >#6B5bYUT) z#6lTj5T_-_j]!2H@A#=0i$%P%#6 "oV(}}+@Xfhρl٦Dr`~qX\px$7$DW5.wxa:'3`f1 %#6}'r$ WSjTLwY#!Ck=ׯ!=҇JX#68#An=f3m=/w!8Chu~D:rwM 䋐j J!rY*Jm+.mY$0T̄#6w`pZOnBB@QC ,Tpo8o#>ݦAf7TW:<"F7=nxZV(o8ybC658` 7oj' ?;.HXL+y`>v#= @mW|Q hnȅ@=f$at*LXnY#6HRdF&G|7Ka: ntSIIM([I4&Y.seb%ىV&F mYNXu@9ȆC!Pݘ#6#=#=룾]8rRnqjsƍ@xBa'9<$R8Q!Y4m;!n.)ԧh:I1N#=l2hoY)31EPhFө='Ts"Y;aS1xMqwD/2M؟Hs/,B]ͼ(eS@$AQFy}_]9EڌZ6YFThF2೷*F,xje27P:Pc4nĀ2 #6Hpd/9X`2EbYTXb5ЍGF&#hɮ-*.4=oTtr"HlF{uhSeD#6< 뵦6zId3.vh>T; ZT.UGeEӤXBh#=L^|4 ԽC,/T#=Cw1*#6.$έo3Vݤ#=`|VtjHvUb*M0{w`" O#_g%gکLjUǧw#6=`wok뺒!1k#=r;]QW5ѶV"l_>\oFΧPρLf$oKI1!=g*4?ύl-Kr8fTFs"|!)dRFyD;1$"b\(E5E̅$!#6սغ[ e`J2L3" *J#=2hgiˤe8Dl0W#˧4 @u0vǑj8BT~e2bcϧ?/GhPD#>Q8 KbC#='`Rά8i!ސRRlάۛRSI3e2ӚLS!e!'*JR,?/@?HW C%eIB8#>#>>89lh;IKF?88A3~M#>e&}:p'fT=H׶E&:+{:4$QP(іu553 I8(Hà ̘R,?'Pj`+b D9w΋;*rJ_ΤK7Ӕ5ϋj{rGŎ4f"Zq= mXOz]wy^mnH6{ڽkӒj04(b3w3+U|O.su餛>< 1EQ5B#6՘hшJk ycoD?Y'N8C{~Ǐr=#6_bLP7D6yWxӏ^>2ȲealVXX̴,X3#=*!vˠC6Qӟk#6ml; #=#>S$ kg˝~lr[u˻ dE݁;15^^B̆lV~ܲ]bwrtNd9#=F6i]9l4fs;Hj̠vɱ@s@:_#=KUXڿiHld3mmtMSfJ9\fw]4mE[WXbp#r'r  X p)eU*#=@v{/v̻05HYZ*=Hp)~F}}}ò~}]E] &Q:HL<LS{ F%!#><\2#6rKN"版>("z ]%93;wC3LT%&uCinWR)DBFrhwq6\4I6ƨմ[DVƂ]mIhjC[ERrŮsTTlTF6Ѫbѭ;]*i*쵸mȺUF;WsM+ṶmZƭrQѝulh`Mkܶ(K]i5mEjM 樍Z4s5mƍW6*$*VI5#=F]7wX܋rJhi';k; #6ף^^辣ױ@2qZC | + %k\&|# QI4$6ŴU[+lcCܕ4@'bs.r]juV}LSsޟhlLo-i`BD/zP}+p5r M3h"5Ȯ Qf8n M)$x8bbFcciR!ed51*J42lh#=)2tr>:Z;i;;D`}Ƞ:њNwu~MRɬD6 уX2IJF)i#=!j7Ӝ:p@u$?AYn#6u$VNb88@`6p0v54ق&C#6A{Mz=ůX 杠">ā%G{ _ GH#=xOa Dj6Kx9$Nĉޫ-Wds#> T2?} r:ب)G=X`PHy6AOG1xh'F0Zr#=s,7۶NA°,H=NR{Q/Nvo( 5DpiG~`=p)fx=Sӭ!CrWg'Q4pDQ49q!>#6TWb|apVZPC# LU %P#6GI\$ a;@auڵoUbhCAp#lLLpIl>m']Q699yOvVA$5:C7*_P=8Ch2<_rLڛX%P=N?{?f&ױ]_jjBpi#60wO}GQHy?#=/]D7}OG&K"#6#61~f#>Ci09s{3%?88'p:';TTc嗬A|FIl?i۷u tRxM AO6i_#/c_V9!D8C#>R}ԟ &ք0nAD[ #=/l;?/v#=瞤<,G}dapWOˉ@`Qo4 16F{GQ Hb{4.X! 5V)*ED}iEd&(A/w)qC QM>iIwN_= *q}bn4޵czmMZ$Q(9F#=N(Q"+pUS a0B_364]dnV#>F*wj*Kb[^E\op%Z GXB-kvcB#=̅JDEdg S5Jۙ(t# 5#)M1Fme3#=[T+OoDbr#C؛VDD2FL!w:vGQX#>q"٨V68ԍ@|3n'#=EaUKxa7;)%5(jK(j(("qxp햺NC}0ZG927ضcd*Jז*ZRRe3m[M(ڌ0\x`m4i4I)+>WNޱEa#>Ye.zQeCMnLIcd7nuPX5#=!H#>4 -4(sFU&!eDQ#=R\VN*-FލD)Y!#M'CL7q*#`p m#,KAMD %ߞdakqWD)#65:r1CTMkQ#=`bֳ_@at_]\lJ(k!ma_%.Q'bp K 1#*pdJYV+iDˈH -ːi.LR~2&t#=Ȑ;^d?wC+5AU'pA>IR= PI,aBtJB#>D#=L@0RT[V7$nt]bNYFbD*qIr*ldC[$]esjrޛ&4Ir@ѬS[Q*R" a!6dҒ}c,TF.E`R#><ܶ5Dl\66܅2TLrI!R@JOm4/n䢀#>D#>Ci48 ya(D1""6*؇ 3щ?^skP* &`d;}'^Zv(#\v(oIwQy "-;ިx`5QhFr)>pJkTD$O(?=;ac4|ą}#6!#=ɴR `h<3k{@ߋqCrQSg` ",&l:_L۰sÚ٣QlKk\ *ZRq KC⨡UWvF9#6 $t"#=!&F:[n|~mͫK)rWaXĂz= &Y-HNbK#>M;\&LDUUFG@~?O͸(tj ","9*w_qS0攞#6p1?7BU|HUKUO[0ɒH0(qgqdZo7H32LTҔƋc"~ߠИ$$#6O怠RWLIE5/߸EUQE&a* `,p^[})#r_K|? \VDr#=cS _.TʐÎI$>< y#6_,Ux|Quo|mg1H]:I&G?u.x_<,pX1NA7p=H7멾3.R_eJJCr'~6ޑ3umTvSLGNlguOzOI[3Tn1` 96r+ }NAv#=CAZų7$x_41TqԖ3vg-/XS#>B R靻i]Y&-3=n7m]f5\*7~t 0O!fۼ4oZ;ex_Be,5dC\y,-zK8}rg{ LtQBD!S+q\kYȩz1A㒉F1uqX*yqLXk0۽oXbPWD,b|pa@e`D;bobH;Fhyf7`o"wltv+핰ՅӋ). H3>Ep1>|+?ª/) *8ʊ# #=O:C}e鹌{P=$+ea#6}(R}*bMR!q#"̹Ͱ#p}#><9V2.#=QJr`v ?.EGf$( AzPb4Kdn7S5#T#=%5dz4LD2P5k}?,MnRI9\"CBT("`8Ko zqھ!P>nj#6 Y@"#6ߔ>Vd׳c2obZSAOD;q #Wa 510clP#=6cJB!+*$(IIUE4m-*&1FfcmFĔZ5ѱbb4a(Vٵ&QQ1FҸ,Y-+5f#vbVVZ$fjɑ3f5FfAkRCB 2B2iyq%"E!eKBeC&b5 4Hڢ0RVI(4QKe4Z AU-$^|aF!C1DeJQ ~;fx\NbQG#>#="fl,#=0%Un*UQ[Q-| tPʇD9”E(XC#>R! #6^oFͮZhD)b|ÝQ-Dm#>. srK(SCGrvLfD65JDyshUSvkJhƥHȔɟ:ZFg!B8KZai#>ލeM@\슛M}Wz噯;{"ᓈ?N}L\&#5!Q&#>u6769C *)$~=/9'.0R!)@_"|W+>7}(Yzt쿏ƬL63h&eb,ci(hUIbU¯M^j29.`dM&)'@׾OD6k?(PA;Ha`/ԣ34#6sYt_uݙ8BP arU-Fd2 ?;ORI9'8C02MPr5mVKsԁ8|4:i!Y ͊;*$eAh$Л K10[@;G9@t'g{syɒd!w`#6{TI^#mvSJxnT|;zܟ$AM__.A`9n\דtH8!_w<|r7>1}M8(R1&   sfMȹ=3oR !(ccu9A4D[T9H QG%J!J?勉nT#>D5 `hb#6O8a{0C#>6mR`Zٓ #7Jau?_spU T8?s0gnx8.o$(~?&~R~?#=#%Z?++5`k#=4ͫZa@E/zGf, DML1+` Phm P֖V~!Jj-3*0 05 O婞TxF8qU;l$1h%yR'4 ,z`׉x}N$N<H0m7q o4JVZXT(יI7!ǻW>\1Gx+i!7FD0þ5̇u<h,"rOuj0d04T,5fA#11R@h_c4TFrX8xbCݕ=ur6]WUPpf#>cURx2z3kWZ`2:Ma`$Tqos65avIxFh IdKjaXHL*!#m4GW֌bӢJFLAN6woR}9q n7MzIL(1&ZYb=۶ktPɎ6 -t==@j4Є \U=j54#>Qy[vG&pƀO tl^֣L$vEB#>BB46H0V{:DH$K58D9%wgvۭK.I8Oo E#$ h %104f!&2@$ `b `m̂mFD!9?bE0PthśIJwknlZskO!;0$(P;P;Pl(9M&=G'Kt@`vSKE3LBM.D I'#+S$rji`?|U|]?i#>p=YcOG ԪgE##> w>b'N z衁x04HF!MSFEj11;*&;B(bC w*jw^_|`5}4%67"Co!{zn< >E[%.\66+f$JO`a.Kd!5#646 s{ab^]Цz-{3&=t$5_. *EZ'Ѐ]G}VRO?V1̱7?#Wi lr5 `c NLĜ4fM8bck1ՖNhkoFj-QRwQhqXHPqxʐs0c7HՃ8t(<aCz*c-ʹjA9htOO#\LI(=7ykAԚiI\2#=!bE=P#> 9 cbQPԌz4ۉq)M#=Y"3#)I"9,#6g|DNKi::>_rr6LK}/pEѵEU-d )je֔WM(pM#=#RҰor:mdq|tq,É%̊'=Qx~#>kb[idLFLFJu3w6Ýķ#=@cTǩ3gns-L`U&h:#>;KIF9EpkڙbK(u&Бcm,ASUj~H/( V UW*ddlH{O?O JƘv +^H-jҮ%A1t&I%0= ,0 ,p;ȇo~@}`⏂/#>Q"@Nϕ_wypoL7z}3P79ѐC#=Y9!,džPz'R5)@"#6(a%)A8}x^OHNHzU#=:0 v`-G^56wgk]D#>fAA  ##@A  r'#=jTRNfPHkYG!_#>!ˡ>JĐ0N=o}d~tF}s'NπMôE4XDBA586JS@@'{Zex$#m3%p*h֏+WP-$mIojۥ-@%%VJAH#6rHS BQ@JMVKQU|Ii29e[آY͔_Ei@pxb,{v}z83ɀY17dxBc6m$&7[(C+#n:C*K$>QHf@b#>:Vr]5q M*cs=*QiqB8e#=RQ{벭ApZ!q3L0tEuɹEQf(pᷖ܈hb}yND5ձ)u `J&`ӊTf5DFU1 @PTل!9exR"( 8+9%uv!imY `qhl cF uLjA V KAdh`FG\1UaHcW 6,f]@DWP#>ґ+̺w`knl1nZ%}*<Y7ݷIb(Qt˻N\֮dw;}uռ5wv{(X31J@0r 1 9G)6#64w_dd&I&">]łS윫r~(@I0Ť#6!ByNb ۷k3b0;D:oH#;h9[C/G.mrc$+Mc2Z9ݷQyN7ZG -P4Oo&i49erǩե .ؚ3x̕.o O2G8ƽrTIN@!tOQ6#6gy;ixñcxᏟdS^À& 2,i;ף 8U.Va5~rl|/%I -4c~2o\!U7;LknG|H݇Ӡ*J=tPz~&%:4>83tC0!C #6UUKK4tZC{`#=aĽCm˝U!恊TYCmp`n8q?7ey?i~PeDF|ZNӿhtfple0놦ɗ劇&CNz%kl9ǂA*AxB ʴ"unMƧr;_O{ڵCƷ5ipLpb`#>!?$@#>-% HЕI@U63[{Ji "JA"ՎC#: 9EhSL"hS"⡹C#=6؎5AƎ9ϧr:#># yFw<Ϝ[LCI>%p=~dDVx+cT.DLh~DS!?o }װڹfjrvS>h@-)[SL@I mPT@(8y sR{(>}s%A;AEVDRH#>7Eau.yJݛ֑%baaC HfB.88&ѡt`Y"bI,D+@#>#64^JLZHR-*4#>Cba,{#>/n|7ӯ);KG4 EφjFQt3#>a @"z\;= cl]_2bԜO^g^xWժT*3g`\-mn wMSZiFJM&U$#=m3=4#6!DH'#= !ngOZ(xK{ t )<~}z@v{~_<ٙO8C()I g(P#6%St_S><~Իf7Z? ^476MΆj)i3:fوl%nwK#>10~71É+8up`Øiբ̅+p;r铞!יј$vTI(UvZ?G:&R?.$v#> ,HQ##>XU>ؽ>*C6Ze(bEw` -Qf9`d&n3Xp+2Z"#=X#61SF8B h7E-fsRf`G!#Ř !"c6*5{v隣mXDSVgPe3Y:z7dr#>-DD#>e"*>004scJKk55{ܪMha5)W=.Oq߲qNdl3*?M?^b<?uy_ +Or#>v5#…K;(bмad<&CZOnD#;dā<@3wiV2pP3!8r6$rD6 ʣI6#6~#6;tXUMB,$>9'h+,D؄1.#=.8e~ɲvQ́v=*@Ey#>ρ0;fadyaܼu1W,fUmq2S{o~eL1Ԥ? _oJd:WT:oszxG"@}1=)j{#>pp6%GƏh%%{*5ڛNR=۰@<@42 U`5_g\~dP d2P)4y%A5@I~1~ŒѪ65DkJ>\a'=0j\F޻6Hdj!A7^o-~5km(%EXmyUZyYL־+&5hQ5At#604@:F$G#6oV ISotO؟O13[ٛfdf&ez461 M64jU`!2(h*)Y:ѦWZ6XQ#=SK*@=lOV'C5 !]ȁШ0*aF@ϑCuQ*H$ #>_du.ö6LQlz 08&3o4(GQA;d!uͅ**e$LDI-Y'DN}>b3@D02c?#>#6aR#|tx}>~AW>[#6Ы|Cb:}G!61z8(Ӟ'te2w锸?3Q@S׵<(A9~jFD~HPo &?;p=S*t5!G%<S#CJQnͫG4ǚpbPfaHM4ha$裌Eiv:L;}5rs`6v ^C*!߃jTVM)Tq|[fvFbmMYʂp|'7) ;K!L9HݠTW3jp4$H#=C"?`≠EH#6i4ahMcr8%J:mXQ6DBI!{FYjj*LcºU$lވϊЍ\+DBBljg^ܟ{#>W/i%1yj:}xHk~U&#BCQr`(& $^&2d-kZ57+8aNq GryQFimŷ_mLQ:|y~a"].@)ٟ#6#>htȉRWؽrsa4+> әEjvUW׳b\7O k$#6?#6`9g;z3@"q۰s tkuT@#=O }!2a#aJ=fT7G|@q b[\"4IE8ƓX0R4@,&-sc,pI$?[#=4™jjLC#=FEb˃Uv/f+ڋXhbu܍'+lzzSwnݼ`+w #=}H`%"h14ce{O)o$3٦ c!T~̚<: FDݽ΍o:sTHi*.Eظ#I,xB . dhu0fɐ.1i4~;62iH1l`s12XaN=(Bj]ee/0gRYDAWh[&7,Ȇgw/5̛MKfcDؖl6b4S7ddAB# %l! $0UF44c&XX#> IPFƋAJPѓQA#>ӻ֕ܫf.!R2h`x;T$,@nՃ#$+H2FKdef11 e" L4CjYE ܃1dk(/C@8#>*"sއhE6|&3^YYW.xDdgQL'#=:bF2Uyx?2A$Im=2ޔ#(`AT ichh!5zDŽ#>Wk.c$ӍF_,dF"c(boWab@؆6@i0m5""iLl9hʜ5AE#>6LԸ\d43o̘ hBƊp+8ԯzGVjfQ`JԦSbыi$534,24 C#>՘MWKJf ƘHɼ̃jBD#6o xVHW(*M L<2ɐB#=$ .;#6H2G&4T5d!Qkn,{뛁#=7q4F6c1Q4ApY @ܮ#=$Lb7ehDFQ#=euceG#>(c&j%8x.TMb9&Q&#>ŽuhEǓ.<X@i>qjPْQPLՏOy#i #HN-kZQA=6ckU7o0j#Z`8N4|Rkaȹ`aKtݭ{{oz#=3B,#>:U)a\hM_u逸urKgήm̒ʠkJn#=FQHGK 6ɸ,hG*`G <´Z,ly!0Ʃl44b1#τ5&BcKqs'HDN8[l:,lPmDn}:ޑ^<213w"X *t!Fxh#&j#==Ohw ҭ-j#>P=#=a#Y4ZJdΈCM5`<&ژ@FJ3BIda[BNd3$/(/JwTɋ2v"s(>a$s$rXzt\Rx`N2?#=+/ҹݎQaZoUVȖX:$#=jqn1iDkdXb[8/#660V|sV2Gd#6U0qr 6XZgףGGP&tecK r@|;e90` 1%Pi#>Nၠ&;MRQXц2i0lXlD:q#>,dR #=4y7C4Kmo<3%$ PMw֏&̔I^+XTR_j *pdjIid{ъh3{M3&^\2Q؛ǪJQ͑ 5B) bb-).jK5LӃAQQ$7QL~e>#=FBђ(d5#=jU!s$ 06РhUhSx62FK{"f2˯amJhHI Z#G9--'9"\t@Dҗgz1UY #>VbŃ[\J2%tBƗԉ+5'vVtu70HPKX7[J85?Es&@h՜qU='}=z#>GˌK/w#=dE ҋODFY^ #=a=b>`#=wbFf!BRT&8k6=QBm٢"25{-cHQt],0GU "b(A1IHP$#=O#6(@#>C9Jl#6"Z2hhєa+N乶ǻzIjȎj)!N'w#@" `_0JV!)bV@Td1{}}&˿ݩ@8?јGt>GP+0'q^/Q $#6H(d D3F@0EAm#6&I&h?g'm*0*%&FA(VAc9 ĊjP]M#>XL46QE[;U^"(%ThB01 #> PdQ8e!RU-Ayڻ]8~:39 ܜ9IpLUz ,qjȑG#rG#n #6k՗aH~w&#=hHFQ#6tC#~~|')(31ĕDáSF(j}֝Δ#zCiGp#TD-]&=&;Uw8rW %)1Pǰ"GHx(&?RqP?&3x?KϏɲ(Ͻϑ,MD2y2(-m\aCM}ɤȡ82L4w~zh>UBW=a"#>Hmpd $Vea!ju㭧H)wauW61oFjݸAZ{65E8lDLg,\ә.LR<ǬL>>^7Х`ݙ͡$jl9lOR0g q=\ۥ/OhB6D0|j9#>4^Veg;-$#=2CEC5#=x09y4/"_>e\|w$AkqƝԑɽ|yCJw@QfӒcT}yvtl*Ő|`]G`"?i־#AEA7=P+DFAm&Qm;S!%WYH0-dʻ8#YЄ@ PJtQw'Y|\2wׄAb&oŽ$=L<̀()#6xbsHU_Y.#6mc|c>d$ppj!p*78K 2W.eܪa2&1oPEO$MqmLjإ=hr+!idC1PZrZw V#6#=HlWS9#=f]6~PXѠcdq`l.2d͛O p0ѮτIE(4 U"$r,QD!JRQmѶUm,&(#=#=j[3wl[+۔{]OqanٖiLtLm*aXŝPd@BPC9W[xkF&*A,r\RKIXA+Jʀݯs kMtEeҩq-##>rM#W@3A"BX9PCQӲ#=1P&X*#@/$$#=CAWLM JX~7\`#=ԲRwUʢt&&9{BMO+Þq¸t|񎰄b_ѭT+5n2a oړPnܝUL9ws'>LxK'Ё@DSt$I˿6< GpJsE'(yF9Ahy PpzxR\PjfWb -PP<)ڪGd!AIu3R74!EUJv۴#6wD-8c\~n#=(Q;$k̅D(w*&R4@L Ӵ%!\ɫ38=-#1*0]i=J8*@lmvɭZQ Ӧ3ttVzOh<2mv$ZF<ێXpvFy& huZݚߖkP8[c#6JK ƿTS3fqtk+HblN`D#6S-:7JAuW#=$T#=0l #=B遰̨T B@8}^H 6E`1"E䄒ƘJ'd7w mqTz#>3+qWfD2aI#6ٕbs)BSK2DW]}dzK#6ySrC !un͵]m`S)Lc*y9?#68TP%0DT6!˲p>ωOB烋 2\m]=e]ʜ`(LO`rb1#dD"J(TK+I:+-nk4\ENEz2sZu\W&/v^swN|beu\e2RK FRiHQ'УԘ#>!# (jb@K{ %g44h>}j|r*^Ө҈?#>~ep:t0y$#6(zNJHTz3GԔzg?D -1D̀M)JP',{O{S)4>Ѩ "EN0bHhct(74~oR(@2ECA~W-$R[Z*֊cccZ)شbVRRm}l0@r5N@d.&p.EH]k F-&˫4ۮ#>WV)L;$;ܺ]^tKN\hN19n2eH:}z4@d0t"H NDjZgŷ;M@x+2#>C$2j^@U DE݄Z\J _>fHKҍ#Mޕ=@RPD6b<4&LQ6#>:A +#6ܾ#> r6w@#=vR_2)sA4ᝤ#=`\<@{bbᠽTW9֑JyltpubP#>]691`T`@iu( o1b sC*I_bu8j)6BFkJ)-k"($  2*4߭ ,|8W2R~O"QSE%H^Cjh@A zѓMWjf@&6#><,.( aTT Kr*g5}G-+ |bx ;SZaFke0?q@thHT\JȓcfyU4IVĪED [v7YeIS\iLC |͍ Ih/iw|6۱k"`hd6"*1ʘfN$P#nܐ0Z9R^#FJ [.6lh@4PD\L'fяU$YKbEuҿ#= #i"\#@U'ŚX#=CA AJ'F"o G>վ)$bI9L1{|ބ'\FmwQ-1!  C!*)kwučR#Q]"."Q4iP`q#6jD,;#qYK@W6#>rϻYJ`$#>`g-irdf2;gy @SR,Gb3#6hjǵ ($K蝙#=ћOLzuC$idiQZVO0m6%&"vdBQ"#6.@%"#>+QcbJN? 38#GG{-ECbl(CN\R)~ $ p<)yAU'y~Ó]]:٪DDDӢ tm6 #=EB1ǟ, *+NFnkvdDa10jz \N6DRbwwu?}{|w_.#6 fYWʧ㴵UdYDG*%oH(EAH.;b#tМD@f ~2uU?d"rv4f)ܺu7͹܈d$]0N#=Wo2X%#=$ST)B.#6D)D}>KXx9Ȋ6tܞVR~Y JiڇF#>\#6}ӕ mFYCu؛{ܠ)ӳo:V拀Y94iSw[ ѹ 5)we3/#<zXNz#=, It!7RN@ʤ$ @d'O92#6ROx~iDQ@TUx 9h{3BㅂqjA(Ur1!FC!`yGDRCxMBШX!Ih`ڥi >kSis02dއv5 B(PP#!.t857!6雴~=H¢ui\*BY&2R%a ' a c3L#>OA}h H?4y#[o$4Hd3$'YRE~-?YhauW5c&6[a5>]M']*[4?+iV>I+et EYkXt%m3OELд(QN{Й3݇!E%(Am2~&tV lgRc,ETq,H{n?i#=P9r1!9 A :l19c~?IY;2LOכ'vNP(#=o&Ѐ'͋T+;6b\d)l #G$ϠQC"2ʱ6Ȋz2 #=OwN0>W-qj$!cJ{Lgu#=cyٜf{i1gJL2)Y7\zlo-Oҧ9m]]5'A荁߿f`󇹨.FԉEUe5RΆELpή@=Ve91T׃wdaaÆN]~Qy#>Lx3 LﴶMYgG{s#>Q̾[$SYpdpHLu'oEqVHHJ]SW+9-LoN v8f^;T?ѨϐҤF8?zzr#6VBtҠ%DE6b#`W\x4UJd!Hax"rrywI661& ٯ"8[.!3jp'Fr`pm/ChyxCo0."Erㆪwug$;v#='\"w!C0~r{#=) ^&v0=tېHa#d%#6c#i9 'fdcz>OLTR ON0bbM"} Cқ1g^>J6@1#6‡nE3Wr#>P#=r9rqB#还671*%32V@a"$ O %۬`~Acڮ'.V8:/q'kTeCwtO+[!Q}i0!%k3ޓYáFHNӓ#悌#=܂LTf֋UZ%UVuV|}8$l/H1qr$l(1+V[jj5QZh5am=OG|ղj'ddȝj7[ρ3\m9 {2݅nAVZM&.+!c#=[J*jGyR2 S)6\SӈVIQ2)TJri$$X}fSu)Cws~~Hw7c4;rF#1큙/c:Y9[B{])27xl[ eZbw0VJ|$J ǒ{of\-K,8O#6!=)$X,y"r8\YW#6>+C>$ wUÃlf`#6*[ц>G~z  tTm!󂓖b~xyȣDTw41 z3f:Dn71JqT(;AA|3% Q"pK`J#>#6bwE~?{W]d#'$c3>#!'N#=vk,b|0WoJL$1#=NĐ䇑(М`O y[ \#6RP`#(Dz%S%" KY-` (Hpw\WW~Hr/*ܰ;O72eKOB{Nl0q]@Dc-#>b+  8QwxsYOQKau/GhwZ#>+1^oT2s?/^(L+#QJB5 1ʕCU|N\Rkz5"NP?=gN\ߺ2K#>; L-7Tĵč.qt83#>ތ/X[/Xq13#>[B;#=b[Q Ii:GS6-!nj g:|}ug_%Xb;kL@k"D#zDz rh&&HL $#=y '!!xălדI8jBMUL72E5lb037+2_͂N޴;CA#6=ܗP?gl薑Mh0C8p82"cs @tȊ 3xi2⾝YPuHHG6`P@:">%Qaø0_|4CI@y K[.#='C,Phv.Zh9Z0d#=j(,#=#=4?B~qyG 0?{9Anc܁c_͜kz0p1i*p6nP6]̪_oײIԠ;#!!Y`ȇǏWFܨ:u㑓aB̈*1 I1B2DR7(nh6k̍M $0$>1~=X̨όɜ'a |#>)WCw #>sgicw[ϩV"|{0VPJU}<8D>&I}b׺N|j݌i#>"W.M̙)7p$"iB`Jݘ) "iD#=4wj,?.f#6&љD#>kݸƥ4MQ-IcC)2LCaDISl5TE3R{ax#6DQ&4[܄[Mvs&_rB[UvlL66-""$-=-5-$QI$xALF;gOwu{zH48৘Sb08l6Wxa<*J%kQ`}= s#>_,K{$V9 n# ]Dr#='& &LQ?]aA2LKYϱPZ_-r;ۆp}R'rJD"QZza$M\9#6#6cLy-.93Zbue qUtev8Ef㑓,6T{p8CK5yI9 s44Dm۶`#=C$BDGHXLd2ls bAAwup;oKk(#>3tXFQ,Yb ha Ԁ`UA6rؐ4m&B)wfRF6 lq_4m٫0PBב3ǬeٟCtkM>Ƃj&>b qb@D!zn.NBWZljjC UY,}!DAk{; *i(jGCjMF&ZƔF#=2B #Ljd.`UG#=15rY)  Muz(J#>^sъ )@ uS/[EvsrmcI9Dcp #=˼n^y1X!ˌeZ iE4N=e)p_sv#=4'#6b J4#=4־׻jnZ4h~פj/{Mةn⍜ :g I)u#6ژn~+j5aZIᜰ2@%q~ҢI4Ҿ-˨^綥)u7줢*$HY$;Dд$" z`6]֒A.& \74AbaJ%fs:Z+slI-I&PޗlHɂD"z9w_Ti%ZfQGښ2!R zMO{lF#g MAz"XV,Ʀ(\ѥ!-c87=\TX(in28A,e$4dLSlnL4F⧍n٩bn1br޽|OUqh luH*V!(۱`)),p5zB,T1(Gyz,dqjM=%f8\=p&kmȡ#l&4\BB*DDB)#>DS5Vf+o&8-f8768#Nq&h(qiUijXf\K L.\n\4'ꉨ#>L$&#݈c@dC#CkX5r$Ŏ&`\d.MVx#!Z0j-3 gjJpl|FK08a"Zd鸆Ye#$f@+ ._, 9C[Nё[:&5[ D("ȋ6H*FkL5d5*4D+t8#6jq#=1CjkvUXm6A։H[L#>Hҳ:M 2;;\A-&@e4Tk'*_.5^%NhHƎ7d]wbd&vSF/XfC\8H'&dUDFZLLf鷘0ŭFq61D44JTmHq6-M"LBb( eS*%%9r1GD4)P٣w!@^䅡@ڶ YMl8΃d$)N(PDbcFhw$E:?Ć T̰'+w#=ד#=QFwWKS'ˑݱqM~#6_ܩ{|#6ՓkQV++*ҔmF#[Zť5U#=iLԕEo^;M!#6JDEBp*ҏ؟tbT|}m~NʹIv#6v)%=ZzvPddHR @PbHmW.؊*W RBR%G?)͋!ȯ`.Iݤ/#=K1zH/fWDr {9#6z=,ѭe2%D* 2B0 P.PuKӏ!o&_~uyӁgY[FjŠ#ׇF"sk4=86.Zŋ `Q;L*J0wsE0 :&&K F$UBj Zܯ7/sudVܹnSĆeח-_Jpb"$LLqblc!#>l`DiVRVJ/+KIrZr)"hw rhaS@$4:̡ŋ7\pdG#6m إ]A c? fHU˕!TJ)kI)tu20h(P '3HgM\117  1hV^g0j?ItB##5'!K[#Vʰ%yj$} QM$$A\š$l(!z߇ PuNLLdPmGւ}f Q4T1# h[ʲD6̨M'lvQ8 "/ΙVhAP Og_OԒ#6!䧊Fcv;:=qK򾖉JXY 53&09B;%W&,K[C&r][њ"_#=w+Msfx1.\d fs(4$I(c#6/ۯs}A~fSw>\ʓw9`l1e>K*c/Uz֒~ϗ>p?9W8_$-}sEp~Paun_@r뻓5GvZ,J#62A30 %ϯ'F~R2"`C-"#6=ANP2f= LSݚ#@d}2jShn7m<+Nl*Pd:=!0@@>FH Ow}xco{ TI#>J8|= H;&83=ĦD>x ⛁ݼ̓))oۙCj!2ӹC;-]̽6l|TrB)#6IsTZMQord#JNVe,vsW-dn5z-&UrJj7/*I&]{njD.GrY@#-Fm&w-9RkX+L=Eޠbm^$SM`s@/>p>g1 BvSn@=ϴ ꆔ#> K4#>(TC@@ wKA#60?cuЏڀ~GPjt#0R`:]2r{,;&H #6^[iI5*XR@SS#ITAҠz?w/3}g~RWCIyK8#=nf,qTr\@2ByfSт32Lmf$U] B$ee(L#>B@q"H%4)-B {{7Aj`9EBsWnEX/܏?Rޙe^io5*MTHTkUV6ږljMXXZmƦUcbL-ʬi=ޟ{"iQ&t#6#6z#Zϳ'|-ϥMIH95#>#>Z"Hp . ]$F-A|h ;Wn$8 N|%!$ '<%񪑡JFꜢS=gq'߂OtwJ+BA#6C'D6"iZڿSii&̘jLe#> -ihj*hmj-)Adʤb4R LٖثEH`;#6ư#=6f\iXWcpT((FVF'HݪETX5%B!1 [yW|v2а#ƒ4B2ԌFWٳ#=#`3*A jIK#=‘ŘcWVDf1vWXӤ&2qRIXƚm!!E*Q$ti QcM&FF(Uci1QYlf!F"=ֺV붅,˦URƴ:EPB"@9(LJ%dQ̱s1rQ2 1rx#JKKRF9Yb%If&4F4s 3#=ـ #6 " Б)JRR4.mYf>5(`٘X]PslǍC@~6̏{PZI_3J)O ED%_g#>72.ɆZȡP(\/3}` `8T!vɤb'NM6JMmDVd(EG7\'ShJfs`a*`{d I.+(w>Z+ uOq5N#=!e ~ ̺ږrL"IUSwfM*|;K$uGʛ|uG1q40vࡋQDʮ=Us&Aalݝ_uaq0[3gFta## aFSÎgASY8"@#>sPsN/C ,MnBfX/wOOҍ8l)ý#%91ݪNY|r<Ĺp#KOG I$+>c"ORS;:8nz9JOwR:_ô>Kd,vfATfUJ-IÝN!K{+3}%+yݣ^zRuǭ<M6bFx2l!|LHf `26[ \:8{1`Y\g*OTrwMGDjl]~Ql8rw8Ǥ`Fޘ,Fq#>uUd/_Zk2mg2=o>^y)uOE]~{3s#߄|=QW+~",;c0(#=#6j1(4W|Ķ4Dp&5.l 'G r&ݛ΄BVmӎ&eT7.0x#=M5m//3Y,o{+&oMoܔV'PcX))-~[#=#=U([UŹtקPJS:xCWy%'YiuЗDO#=㳜2CW}Q=bQjطĘ҆翇q;&AIJK3-EDѧ7n]2UYڪͳxj+RX-+-qst#= ;a4!"6WSǢ _|֫ %QdefLpЄRiD! Ls(TT[1+^XG0:ŗڙ.)_KP)]ߺ߬D:4\4@foG8H:[rQb-KsV9 ^;}X \\V^Ok )NؕzYlL4ưmf@$ޘGbj@jEB]SfDEgA<_#=s:0Om<#(Z{NōO_giy{猂KTH3]&:2arzxgu?51KgXg)Cq!n+r0MKzC^&'성6w9V{<_O5*&N Tbnjg?xjG&t;pyXj_Sd$E\m(0IohTJ8C$XAv霧nƫ=3m>f-LVbLy5u)~ & jFy85u,}L5V]5oRr\%{{@.ö*E$>Pd5Ln!Ux1kSQ'4'Ib皒F5if{d|/tc+&썓K8rmw7rCdNS"$2˽ `;}x뾘%#>%7 ٯ+25/'o}] yK{#>bQ1H] ŘjUq-5᛻֙vv7f.x\;N<6u>5zKٞ%b99)L`YݎK:6PBff>hfNoPȏ';>8AN:`w"JcA0?oL!fOVy%,V!^!O#6osϮhZZ:`̆+vk*@:VB#>SJ]%o^DK5 )LpDB׵{FP|SM2r(( $q tN6)Ai2M$n;eAKQH\ܥm uxm#>5d#>>y'[x>t2cKò(sOzE"a1.C`LzTgAA CJ,nfq1Ì_5q`f6>mXSҢ+8#7<sL򍾷kc2I3AFaCiS7dErk#>:2lQADw}d-2֖ D#>\ !;]վuxv(r^x, :ii0[fмޥv$l 'nDaT[@Z8X ѝ4דMkOjx&w2EжCR*ȣu.`֫])_h5L[jj2ϔNubzn.S5ٻvśʁ*ꔓEeWкQbPB{o #=X&D޺y /F:s1*J1$є_#% !~6m7rv;joB@(p4Ń팥[o_,Q]1 ΛN`o=AzlX&+D}mYb1+Av&OzǯٜQ#>3}r5NOG0d5a1<^ؽu;W.翙?;H 'qy_f4Bd g`t3`1Hl:a,nB87Km(<212% HKZY=kx|ZpK|7K CXvz2Ɛ|Ŋj+_Pu;×>BucNP@[ncPwZ/5I'B ӛ(8Nr|6UMwj6vְr+KVr@<0#6 TYP Nލ/GYǫszwT07A{##=O1m>63"# 1;P2EsYijm:2w]:&=s0,#=܎܊dBP+ãYӮF͆T$&v\A|˪r|adRɅ!bh :֋-MT<*EH" 1k#=W/^+uƍsRMY_>mi|'biJ#>#6=h.:ES4g{d1ҧ0=Bz=9>]}(߹Bj$ &C`vv:3ұyAb|_껝cxm3#6+,r%_jdV'1l0P0a=L#>!2"`xc1`#6#=8Ua-ӝ֪,9Jp!|Hҁ|&Sŧp<9n@I0K 21'YUβ¹Y ̠93fnOj4ETy2PA~5#>/lb؎,v]Q>" LH E9s.I|X#6#>bq8l* FPC^SdC{ݎE,Jj47#=$nlϏFl\xC<0bTBsOXySּ&Eo[ٕ"d;p/x˯$ V#>t*ֵf:|#q#"/F ^_l[h|^L )+k&IEM*[kl ɡ V]*co ųr'b\5I3daL3\ "lQ fA@Bu{c&c`#6:' }#6^#6#= Qi͇PoQgmBx\ >=WFў:¡xc<9ȰZx*%*"T"..)1a<9fQOdH\ޫ9=&RJMYeIBh W;l?(}}<F3ff)8#=SUhX ⣊(D4D,Ym$eB:Dv~̍h2.aZ`WD0̹]N=ރG؇pq :*9Sو|E64oꌬC(ףlejE^jf͒ r3'0aZPFn7pډF6eƒv[,jB6Dr#xٹZ/+6JϾ'T{Ì0&w'A+yJ G!CR#6\(SPԀb#=649ʎXqe#6QQ*3 Xa`$>7t4}WC&imE]ACw-np\ȇ1ŅXr#>,'yܚbn#=pp!z3wAU:}bcA_߄aJE,#>j̵jE(ASFf3_:ˬspwYYˎKztn?ҽ'{׆z:kw)+: cb9#=#="#= juA/э1$8Jn4yr2A~mu._[g9-\37Pv"#>QSB2ZB̆&m#=kc.&G2)t4`dIR,p6BϽ&LMr'5vf'2gtp(FjRkdF%7ːzv:#=**/#=4]ī΢Ѣ" J2&F<6{wJ16.X.ŕIBq6`܌Nl5,qԶ#6QQG%Nf*7%Out9%,CLgC XdQ@UiRl{0D$U+0kXhcPL8) =51MF[,d#=NP ڴ#uCcR!nsil$ahݺa9u.A+Tv"NZ:#6-fH,j5tbvuqXٲs'2@j( cN{C7_g02Hd5<}ꮍ`^iӤm(H,YMimB#=x7$ A#CVAy/,޻R].`8f&c4EKsyD=k U[[]F7HKRyH`HGj][H1҃TbYE;Ah:5:0>?#ȻfekO'm[4hO4\BKd+-OJ{DT?IvF+#>@ԲC!tF`xz1 20Dv]e7JQ 2܄7uԃd}DEsSvin.{K[VsFzuS_g 9yDyJ&*Ctuw[g'޹rm«ܾׯ=eO #>D8`Ģ*!<9KS=_"c{d5z_HnŒzŞU,q#0>H0s^6aLPPNc:1Bσ{&&Z%1M*q^vh%@TFUVQ<%|L=V=Ә\j!'f^с>2<}s20 7#=8͘YW1$4uGך|p5k灔[;pWk'#FdosEC^dRAޖ,^[DR@G#=ډF$ƕ&֥JmdfS!0ԊdM{(̝'w7uWNrٹ]P#=.$I#6#>6b@~φ/g`˟F~ƍ1UʺfC 0JD-26jŒZ#6بD#=MHJfl*kHU53MЦ0#Ȧ4b(ő Ƅk(A$S(Rɑ6&14嵷e#6!+{ϟ~]rG'/'>w}!?WH;5mt`Ja.sf0u{[ߗsaΊ (]2;}ڧF{Yز/2|}sHS&A0nt&+#=c )iS0&r.?UM`:C6g:#6?W}A=p>;O2dPlY%I/ǯ="Htb y>J#>W.bK+&Ueeb2ɱkS%$]ewt;櫉`dYEDXM1iMDQhӺ"] ڢfoޕuQ*(F@K#60RkW(PI MlC`URcTr4#=FiWk{&d|[%کPhw(ܱ @ӍRun/\!j0M&!lXE&JcaHJQR3#= #=O}i]/U$*"cWpbhJfǥ2ףCYH"Zq1m [ƼޚL$ĝBJ]A[le5aU\h6<ǃyP[70+[2coњLXYYZA2#>8OlLeoQ"!I )HB0 Bi=۷s+uS!s |`[HIĀFnE7kPbnNLXL#oT\4"34 d`Q)2!tlׂhe43e0X0C*ݽ_fݓvtݷKX61rQ9"!N0Eo1'*㎂W"{9L O#>b xN@$ѶAL9E[)+"L@A{x$ gp#6{/dm r=I$x*jșNq#G`׈W@bVQxgG$P⇿Ʋ2\܁VC`UBidݬg|A15ƌ1﷿c|J+{rWHv0zm?O6./9LNUN4|3BR}B/h=6 &]8wDA1$92퇌pI}?iWПf tv#`qWڞ״vp{9s5O#>}51~,M!J3+KZ2#6TNv9Ʒ,`:\`i@<ӽG0[LO zr96k&ZPOK#=>fÎ.Br H~dtv _N1_FAJD2Xr ;3Dvۙ'b?pG'X$9@˅>y~/h#6xk bM%@cm>d#=F r#>#۽ U&pϪSOV:m|826j2bwI<#=I::{2#^uv6s1/>/؟OTV0rݿh~N)ǡ3mZV)fpΕSɓ$=T"$#`65]#=jaշ,<)ɹ6l`k*BApg3 "jI85*%konF]iJ^zfVޣvI㢗(t0nnhB̮Fqivد#d_#=<9|Rv{\L,AqSywHƀ4A9;!v񮳵& e8_#>`֎n魛R`d-ʜP_ӎVtG+ bGNEƄE4#;9=6 Zpo!ơ]8aIxSN!I9aPaB"BZiɶȮ1y#Aˋgvjf>Tia*xl; WMp:r;":rЭ|y5((EKg6&o)!6l'b.#=F'M5Y qevWD7EMp#`gURHf͞V$&K`@-fzK'bZ?{;b)3_,lʋ3V8ld$Eq/#>DZhst.8Pgn4Tu#=9|WglP>u\cuETwyӘVwLgj;u'Pgc2 b"G9B%θC{\=pY!@7@'K^cgC1 ćLF&3J8"ӦfYFTC1%-NL4w Av7)rW#6#=oPd<#6e djضBI@Є M10cC|/<;f[ӳferQV mdvdv4II#6}\G!2['s/5>5/kt^R9flnxJs>e©f ˺рQ{g8+"EOo"ɷ ޽bD֓Mq1˰g\,pӢJX[EtUݶb]q>|^X]U.z&6糽I8j]Wq6!9mߗx`2ɘHI$\Brl5؁Jo7f~k{Dڱ+,̌ޞ`2He6iBu-qhT}[La%hs,8EXܞy!V4LbCmhlXd,d9ţrX3a)HД2R0SL.m޵osZku2v~z~Au#=9B$t[ H#6VCaaA$q;SŽjhehbi3iζ<teky\R@Ӹf޴qs8zF24R#6v L2t}gs,N\L"=Ⴣ7)Ou!y\i hJ#>g Ս5L#*։u$@f)Ҝ,3*"iJU$֊䉬̣ڊ%b\)1LE[5I+|mrLFuvec2VH uͯ?ϘFmD cD5#(..[Ļ{d\Dقw7v &԰B'UH(V+dNq&+[#=r:OB yMcp(ŷF('yɆ8-Q٪#9Ʈ)pZY0$%˞P@ v~^SNBQIY$T@W*vPvp tYiC&A:B~?o>i܅PWjN gۍǑxC&ct"Pu\ˋ%ICf^~"yX4\*RE9'(B2#6dvüG!#6XiZ6nhzFU4ZT^RMwSJ:zjb)aIF6#=efd7qoT5LC+@^P&Pm+F\≸ĎG*#>qX Dݛ|Q: ΜPPhή^< 411cqa6٦[8LLq&)MUÞ35CvL4#=!KJ0A56 VONqg%ikU&i``0m k).1l:Ӝw#75Щ Éʃ9*ܦIg0ٛL'TC&vQ1Xqn1z2$>YxPr-jM)R!1tV2qqe6"& ; KBfxf˅7:#*uU^9\ &mxc\\ɴ$=,kRufuXYv(Xr`wxSjuXָ1I]82,qf#6M:.x-ʊ<- yu,VE#Q\QC!Ksmy6lݎU-),zC#=&#NFh@D&_N/؍<<=o#aݠ37Id.CM _Lo>tSË 3#P|\kTf'%18XR^mNQL3! #=Ph[Us'BMAִMQQ@RU&&L@Ľg$#hB56b:v;넕\PG1t5@1C:xlEmcl)ЖPu#=yjkM1#^p#>Y~11ns{vb! W,Kw=[B u,XNC3]ʠ.#=K֨89N%8)5|EϺ1},:V̀?:F5GۓCE~ sARt=P.׌me|ġ!&ˆG]U\)rֹCF** E) EzA $)]89W*,YD3@w 8TLeasBCm"$bi@:zJC#>̲d":CnH Ii@C&(َlpgV`m#=P #6d˽.ð,;ۉX٭HiH#=dXT76VoMך"X+a&0Nx)G.8#=` B0l([f,IMiMZ~2e:w͗Nth+lW&0#%)b|@igI~o>yPEA0!6A(*/H؃+Hn&2d]pEB@_d29r9!=O+1@]#/0]b=Pp7'Kw*bҁ*b`i)#>mءh܆p2wDYMq!X&\G(8}x5%NkK!8ċ%E+X6LYhQç^_mlE.%4,m CzCmaaMvG2* 񄝀'dY#6#>~lJ<`M!v50RozEBEJl',V6CՕ4₃@^r# >2epF##6`#=7'zo!$|u(jfXY Ic"]ʮ__^|N87nwn.ݺe/-[Z4;z*5L2_M UHR@ǁ!lLhV#$2(T ӄmPP"SDá4I#><#6.Ϛu=T!D5&bq5̼E@*ú5qӜ& T95d8Ѫȵd}ۼ' 77o´ck6l[y+8kH(-RF4 h:ɞOPQi9 D%#=/"͑߾1C.f 9d 0~])_Jʼ#>|M{ύBsm439)(Re3ИJ鬡|6ޜ8pŪz9j`S5#=2hla#==S[ko#>"!dL:lI#=0fXC,"E9h<|@l4zM\$M#='A0p0e9ضF4cqV[OgxC-IA@*nV`q*pP|ﶵWd2wq?'݋·8BqizSd#4C跊~=`"{I/~#>}?Q?ٓq]z,`#=IiL5lcaj"-np9J-8v`38#>i(`$jc<NN0>7 P' 3P;]˒@$#6i@"ɢKy1!i4,qa4Eģ#6J#6) 2(bM~g-jKk @Ȕb#6()US #=CH8DH+C!#=T m%H#6C |C ( R1ujiE@Q ]#@#=(C%tH@|@5 z#6 {#>X0Whƥ؛&]beHi{Hh܃b8F!#HCЅwyҹS9ڻn0aZEZX3% i9\eDHCC+"`hdY#>a#l< r;K(L4;ٖTi4J "M2`T#=A1GMktۺ;Nrz|Q䃃X9Na-M7FS+X=m1P;J,3,36h9rϘvWWHFF:cێf<]@u"?ptr6 AřƲl2&q#>fU?i-A%Q?T%5wn.|@^ H5}{!=I2Srϗ/VLq~9UzIՐH7h( BYkr('è-'vPf{Se#=&S/#=_ 5p:NZ7}'Ƞg{ l#6>ؠԺɠo@DШn&(s9 JzVs\̑i4ؒ i#> "©a ~vK"a2] UO=`'~_P@Mv&~Fjt[lͦU&eLS,MYR*U*kmk^r a|;́ity HuiSC*'̇H~po]qǯ;$(B_~כkŠJX5Q(_~#󝾒ro|~!xМnE? ~퀏2@G܉8N|Q#c=nb#UXU3#.΂tvBj+D3 M%4ʂ#=sCc,['Xp$ON'$!!DziSûWxi*LE(&Ȏ9FY#kinw%UST1]"!cunur^*jX áK92#6DIFsO>AR=:d^;*ym<*2!<<[q1I2df;tхY௘5vb>,?/HFγ[sZvbLf=hU~,m3i#6I5#>kt:Mݷ$iԀa'j٘Hfs1(*t.H#>#=w<%XE  JNZI| ԛXq:ăSoJ&21b08#Jw{GG4|>ǩ}xk-)@,H% :ƈAÐ_ExQD92 #6'IϨ{}̄SQ$QuTd=D)YJإ!#[rضԆXRX3r_Td/(ER%@GB!xv 4~av/0}#6#`D#>2 cDS/3 z6VPyX{3 `łX!2L,Tf}9yh^@ UHHS]CM.8*1̋Gb26f~^!P( I#=`EP!۳}?w'LK2e@w}>^A|}GN@kJyY H"0=DK>H`ffV29WA= #BuLU(7C~+BU!#=)9hMtH'#>0+AR#6P~C$Q]IGۓ`c;uR`dMD20zYJb$Ǧy#J`\}7[wFW XeC&i-6%\Q4H٫q0XUmRHP)3(HT=_+DMGG0>@=qO_A_l|< 2b&#dx"=$f=Qh35ml(B%UBd"$DRl#6|\ha4ڪجQ,**fTm,dT" %0Ƒ2:{?q("`64lfhIbdڍ$R&@aJMibX_}_*Ti"@DX%#O!|aPqvI_6+JSP`<.jh#6 }ҝ#vX }Ļٯ{Q}B)4qNK`@rhH2cWs :0*rDޝCFøu-#>'})&c#6vzW=A.~U{z% Cy#>S%0R#>hb+BQ$huԜ<88ԿN4v0H#S6 -#2*%9uN(w!a1``~XOAFHa`5,2 AM{#6J>8U K֥k+^zk̳1hXc$ҨJ2T°XDsDKF5#6#6#J=2j(!h@|#6; (D2D(DҬz (}DiI$z$Ó#=|S-)> 3)=Aϔ{-eoaO9 %b(pa@q!\xg[t Inՙef)ݚhʖPHd%LKrIh7lN_\CLr "2XCG>&LJSy/Ap?Z v>#>(VDC!}!#6LK11ڏԶ*7mXήͫۗ^r*$LB ƣd!!t;=1!b1Uۭ#6R/8ŎDQ}0,&#=#=i$L;(I fAX$!2$NLAJ}lAn &qRϩ̱ES{!_xG|ssuN\%XGvm'?ʇ hU=5B"BS4[Ѷ YB*Э"2@?۳W{өP$"UW39Xqh1-#`qos#= I0LIC0@?3:VBtds\7ǪZ|Dh>t̟rs'8m#>Dc}wi)~}kBJ6D?i>R,&'xw0I>,I~w|{'ABTXO|Qab`߫$$`j-K`&Ұ~1j%:ҤF p'vv7'BMN&bGMyxpɥNJb1E #$&l+Rҍdb{41;#NIw}:mE-44 ҉APjGZj#>$HR#G3#=(é 2>pk5{3Wjkupav1N'Z#=qoRǍZh!JD T\-"n\$YvXZQo Ad=%#>y[VP[1zF=>_U2aC~>K, n1oF0\Đ#=#6`cd9zzz2RF#3~iiW` ̢eB~x%E ,Re'u0Ml <cJ`2'GGaϾ[7*WCNoozJF?q" 1†$Qui9fvLîO.b4RrT|N5׀" tIL!}X +bjbm(2z|(F|1#64)uJ2d#6n Cgѽiq#=F<#6EVVT$8B0ۨ%u#>~b!-42Lۤ0T)S_[_&ؓTRm(9g=+bK͖#Ics#6J"s3_/|#>Lgx`#Cf}#!@<wfp?'*CPR@uO5Э-i 8$9@(DѴi]CjL I*:H9>#=?w;&ڭh0W_׊#=zU ˙3)JOُûy<9wH$QDyNg)pg{\"h6ㇱ#=/a!|3%2FxtRQeQa!܀(acT:@U<+ɫ߾Rb8'=U#6hNωq,JE1V"x)UT3S 2yM"r =CžO]Cbz()cJ͑oڽp܃cjnHT&ǚNߚ8Llb{8P(Jzrӗ\2mɀR81,`kF̱(gu Hug]f\l/QT#>ϛM]96쬶 X xIhSUu[Ǭ8k9  q ^)*F(`22ΰbnYtscMr4lrjVA4&6Bᵃ˶[ L=aBr%!ԃ ବ`Nd҇EA#=LŐJ,C\nt_[z)<3@!39eCľ$ϙEm7[&QѢof%G/ O> (qȊ-@hrTzujDHYE#;fã̼33>0!CO?a1vJT#6a\wӡ@Aa&lFٲUm0?/+2St'"YsĆ0 HXoqbXEtGTӝ~fPm)#=0eh۩gO]@P(HE24} 8E=w}z#=y{ [Ld€+*-754`ԀœD&VMPZj*bifJHAb"Ϟ#>4% a BXC N/B25kF!I 1#=Eq}m%pB8AA/#6XoI ELA$e#>4Cm1UVfWɥU5@YB<5ve9:pVaN82Tdxq/d9Zn_G66\!.2XWghL4 5zp#=,sXpzZ/Ge{ ӏ$΍3!toCU-3 k0US)6D닳WdaCkwcȎ7Ħ!!rJ5 *9w_(5LaDW=-ϸkQz{jv#> $-٣^Zɂ*(<^k\SBnPtCB %R: ^&f6#> jF,O2S" ,aP?C!޹F8|}#>p#6_ejd3݂gLGMxtĢ>=Iϛ8}!p2^ƠzȒJM@5? *ށ_Js1QB!P' _'{አ%( 0Bx&>]Ԏbٔ)ȣ0#2I8ҡo@709#6#>bY`)]Gӏ@u7h@~HH*bKXJ-#>уA^&J>oާ8C4'#>}p #6@#>_۴ZTP/|{,K Poޝ+{\觎1S>Q OdëX?0 3J^1_Rv@D[W fyd 2BV~ER/% ו.IORk8+Q Ywsm3} ?˶V2CD׫J,3Wܔ6G`ϟlE 1ܻЫ@$5 BAQGʈhw}SG!ׯ4Οx]bB^?+ n\y^@{{һ8=d:G*` ;7m \ouUd9b? ;A1=?81@_HfL`O ,=?#=#6-9R5j8 KH"(H%lK#6 #<== diff --git a/waf.bat b/waf.bat index db5e1d46..f468505c 100644 --- a/waf.bat +++ b/waf.bat @@ -25,7 +25,7 @@ Setlocal EnableDelayedExpansion set PYTHON_DIR_OK=FALSE set REGPATH= -for %%i in (3.10 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7 2.6 2.5) do ( +for %%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 ( for %%j in (HKCU HKLM) do ( for %%k in (SOFTWARE\Wow6432Node SOFTWARE) do ( for %%l in (Python\PythonCore IronPython) do ( diff --git a/wscript b/wscript index c3219d58..da609f47 100644 --- a/wscript +++ b/wscript @@ -2,17 +2,36 @@ # encoding: utf-8 # a1batross, mittorn, 2018 -from __future__ import print_function -from waflib import Logs, Context, Configure +from waflib import Build, Context, Logs, TaskGen +from waflib.Tools import waf_unit_test, c_tests import sys import os VERSION = '0.99' APPNAME = 'xash3d-fwgs' top = '.' +default_prefix = '/' # Waf uses it to set default prefix Context.Context.line_just = 55 # should fit for everything on 80x26 +c_tests.LARGE_FRAGMENT='''#include +int check[sizeof(off_t) >= 8 ? 1 : -1]; int main(void) { return 0; }''' + +@TaskGen.feature('cshlib', 'cxxshlib', 'fcshlib') +@TaskGen.before_method('apply_implib') +def remove_implib_install(self): + if not getattr(self, 'install_path_implib', None): + self.install_path_implib = None + +@TaskGen.feature('cprogram', 'cxxprogram') +@TaskGen.before_method('apply_flags_msvc') +def apply_subsystem_msvc(self): + if getattr(self, 'subsystem', None): + return # have custom subsystem + + if 'test' in self.features: + self.subsystem = self.env.CONSOLE_SUBSYSTEM + class Subproject: def __init__(self, name, fnFilter = None): self.name = name @@ -30,93 +49,148 @@ class Subproject: return True +class RefDll: + def __init__(self, name, default, key = None): + self.name = name + self.default = default + self.dest = key if key else name.upper() + + def register_option(self, opt): + kw = dict() + if self.default: + act = 'disable' + kw['action'] = 'store_false' + else: + act = 'enable' + kw['action'] = 'store_true' + + key = '--%s-%s' % (act, self.name) + + kw['dest'] = self.dest + kw['default'] = self.default + kw['help'] = '%s %s renderer [default: %%(default)s]' % (act, self.name) + + opt.add_option(key, **kw) + + def register_env(self, env, opts, force): + env[self.dest] = force or opts.__dict__[self.dest] + + def register_define(self, conf): + conf.define_cond('XASH_REF_%s_ENABLED' % self.dest, conf.env[self.dest]) + SUBDIRS = [ # always configured and built Subproject('public'), Subproject('filesystem'), - Subproject('engine'), Subproject('stub/server'), Subproject('dllemu'), + Subproject('3rdparty/libbacktrace'), # disable only by engine feature, makes no sense to even parse subprojects in dedicated mode - Subproject('3rdparty/extras', lambda x: not x.env.DEDICATED and x.env.DEST_OS != 'android'), - Subproject('3rdparty/nanogl', lambda x: not x.env.DEDICATED and x.env.NANOGL), - Subproject('3rdparty/gl-wes-v2', lambda x: not x.env.DEDICATED and x.env.GLWES), - Subproject('3rdparty/gl4es', lambda x: not x.env.DEDICATED and x.env.GL4ES), - Subproject('ref/gl', lambda x: not x.env.DEDICATED and (x.env.GL or x.env.NANOGL or x.env.GLWES or x.env.GL4ES)), - Subproject('ref/soft', lambda x: not x.env.DEDICATED and x.env.SOFT), - Subproject('3rdparty/mainui', lambda x: not x.env.DEDICATED), - Subproject('3rdparty/vgui_support', lambda x: not x.env.DEDICATED), - Subproject('stub/client', lambda x: not x.env.DEDICATED), - Subproject('game_launch', lambda x: not x.env.SINGLE_BINARY and x.env.DEST_OS != 'android'), - - # disable only by external dependency presense - Subproject('3rdparty/opus', lambda x: not x.env.HAVE_SYSTEM_OPUS and not x.env.DEDICATED), + Subproject('3rdparty/extras', lambda x: x.env.CLIENT and x.env.DEST_OS != 'android'), + Subproject('3rdparty/nanogl', lambda x: x.env.CLIENT and x.env.NANOGL), + Subproject('3rdparty/gl-wes-v2', lambda x: x.env.CLIENT and x.env.GLWES), + Subproject('3rdparty/gl4es', lambda x: x.env.CLIENT and x.env.GL4ES), + Subproject('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)), + Subproject('ref/soft', lambda x: x.env.CLIENT and x.env.SOFT), + Subproject('ref/null', lambda x: x.env.CLIENT and x.env.NULL), + Subproject('3rdparty/bzip2', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_BZ2), + Subproject('3rdparty/opus', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OPUS), + Subproject('3rdparty/libogg', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OGG), + Subproject('3rdparty/vorbis', lambda x: x.env.CLIENT and (not x.env.HAVE_SYSTEM_VORBIS or not x.env.HAVE_SYSTEM_VORBISFILE)), + Subproject('3rdparty/opusfile', lambda x: x.env.CLIENT and not x.env.HAVE_SYSTEM_OPUSFILE), + Subproject('3rdparty/maintui', lambda x: x.env.CLIENT and x.env.TUI), + Subproject('3rdparty/mainui', lambda x: x.env.CLIENT), + Subproject('3rdparty/vgui_support', lambda x: x.env.CLIENT), + Subproject('3rdparty/MultiEmulator',lambda x: x.env.CLIENT), +# Subproject('3rdparty/freevgui', lambda x: x.env.CLIENT), + Subproject('stub/client', lambda x: x.env.CLIENT), + Subproject('game_launch', lambda x: x.env.LAUNCHER), + Subproject('engine'), # keep latest for static linking # enabled optionally Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS), + Subproject('utils/xar', lambda x: x.env.ENABLE_UTILS and x.env.ENABLE_XAR), Subproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER), + + # enabled on PSVita only + Subproject('ref/gl/vgl_shim', lambda x: x.env.DEST_OS == 'psvita'), +] + +REFDLLS = [ + RefDll('soft', True), + RefDll('gl', True), + RefDll('gles1', False, 'NANOGL'), + RefDll('gles2', False, 'GLWES'), + RefDll('gl4es', False), + RefDll('gles3compat', False, 'GLES3COMPAT'), + RefDll('null', False), ] def options(opt): + opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msvs subproject ninja') + grp = opt.add_option_group('Common options') grp.add_option('-d', '--dedicated', action = 'store_true', dest = 'DEDICATED', default = False, - help = 'build Xash Dedicated Server [default: %default]') + help = 'only build Xash Dedicated Server [default: %(default)s]') + + grp.add_option('--enable-dedicated', action = 'store_true', dest = 'ENABLE_DEDICATED', default = False, + help = 'enable building Xash Dedicated Server alongside client [default: %(default)s]') + + grp.add_option('--enable-tui', action = 'store_true', dest = 'ENABLE_TUI', default = False, + help = 'enable TUI main menu [default: %(default)s]') grp.add_option('--gamedir', action = 'store', dest = 'GAMEDIR', default = 'valve', - help = 'engine default game directory [default: %default]') - - grp.add_option('--single-binary', action = 'store_true', dest = 'SINGLE_BINARY', default = False, - help = 'build single "xash" binary (always enabled for dedicated) [default: %default]') + help = 'engine default (base) game directory [default: %(default)s]') grp.add_option('-8', '--64bits', action = 'store_true', dest = 'ALLOW64', default = False, - help = 'allow targetting 64-bit engine(Linux/Windows/OSX x86 only) [default: %default]') + help = 'allow targetting 64-bit engine(Linux/Windows only) [default: %(default)s]') + + grp.add_option('-4', '--32bits', action = 'store_true', dest = 'FORCE32', default = False, + help = 'force targetting 32-bit engine, usually unneeded [default: %(default)s]') grp.add_option('-P', '--enable-packaging', action = 'store_true', dest = 'PACKAGING', default = False, - help = 'respect prefix option, useful for packaging for various operating systems [default: %default]') + help = 'respect prefix option, useful for packaging for various operating systems [default: %(default)s]') grp.add_option('--enable-bundled-deps', action = 'store_true', dest = 'BUILD_BUNDLED_DEPS', default = False, help = 'prefer to build bundled dependencies (like opus) instead of relying on system provided') - grp.add_option('--enable-bsp2', action = 'store_true', dest = 'SUPPORT_BSP2_FORMAT', default = False, - help = 'build engine and renderers with BSP2 map support(recommended for Quake, breaks compatibility!) [default: %default]') + grp.add_option('--enable-hl25-extended-structs', action = 'store_true', dest = 'SUPPORT_HL25_EXTENDED_STRUCTS', default = False, + help = 'build engine and renderers with HL25 extended structs compatibility (might be required for some mods) [default: %(default)s]') - grp.add_option('--low-memory-mode', action = 'store', dest = 'LOW_MEMORY', default = 0, type = 'int', + grp.add_option('--low-memory-mode', action = 'store', dest = 'LOW_MEMORY', default = 0, type = int, help = 'enable low memory mode (only for devices have <128 ram)') grp.add_option('--disable-werror', action = 'store_true', dest = 'DISABLE_WERROR', default = False, help = 'disable compilation abort on warning') + grp.add_option('--enable-tests', action = 'store_true', dest = 'TESTS', default = False, + help = 'enable building standalone tests (does not enable engine tests!) [default: %(default)s]') + + # a1ba: special option for me + grp.add_option('--debug-all-servers', action='store_true', dest='ALL_SERVERS', default=False, help='') + grp.add_option('--enable-msvcdeps', action='store_true', dest='MSVCDEPS', default=False, help='') + grp.add_option('--enable-wafcache', action='store_true', dest='WAFCACHE', default=False, help='') + grp = opt.add_option_group('Renderers options') grp.add_option('--enable-all-renderers', action='store_true', dest='ALL_RENDERERS', default=False, - help = 'enable all renderers supported by Xash3D FWGS [default: %default]') + help = 'enable all renderers supported by Xash3D FWGS [default: %(default)s]') - grp.add_option('--enable-gles1', action='store_true', dest='NANOGL', default=False, - help = 'enable gles1 renderer [default: %default]') - - grp.add_option('--enable-gles2', action='store_true', dest='GLWES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--enable-gl4es', action='store_true', dest='GL4ES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--disable-gl', action='store_false', dest='GL', default=True, - help = 'disable opengl renderer [default: %default]') - - grp.add_option('--disable-soft', action='store_false', dest='SOFT', default=True, - help = 'disable opengl renderer [default: %default]') + for dll in REFDLLS: + dll.register_option(grp) grp = opt.add_option_group('Utilities options') grp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False, - help = 'enable building various development utilities [default: %default]') + help = 'enable building various development utilities [default: %(default)s]') + + grp.add_option('--enable-xar', action = 'store_true', dest = 'ENABLE_XAR', default = False, + help = 'enable building Xash ARchiver (experimental) [default: %(default)s]') grp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False, - help = 'enable building libFuzzer runner [default: %default]' ) - - opt.load('compiler_optimizations subproject') + help = 'enable building libFuzzer runner [default: %(default)s]' ) for i in SUBDIRS: if not i.is_exists(opt): @@ -124,18 +198,32 @@ def options(opt): opt.add_subproject(i.name) - opt.load('xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs') - if sys.platform == 'win32': - opt.load('msvc') - opt.load('reconfigure') - def configure(conf): conf.load('fwgslib reconfigure compiler_optimizations') - conf.env.MSVC_TARGETS = ['x86' if not conf.options.ALLOW64 else 'x64'] + if conf.options.ALLOW64: + conf.env.MSVC_TARGETS = ['x64'] + elif sys.maxsize > 2 ** 32 and not conf.options.MSVC_WINE: + conf.env.MSVC_TARGETS = ['amd64_x86', 'x86'] + else: + conf.env.MSVC_TARGETS = ['x86'] # Load compilers early conf.load('xshlib xcompile compiler_c compiler_cxx') + if not conf.options.WAFCACHE: + conf.load('gccdeps') + + if conf.options.MSVCDEPS: + conf.load('msvcdeps') + + conf.env.WAFCACHE = conf.options.WAFCACHE + + if conf.options.NSWITCH: + conf.load('nswitch') + + if conf.options.PSVITA: + conf.load('psvita') + # HACKHACK: override msvc DEST_CPU value by something that we understand if conf.env.DEST_CPU == 'amd64': conf.env.DEST_CPU = 'x86_64' @@ -143,157 +231,236 @@ def configure(conf): if conf.env.COMPILER_CC == 'msvc': conf.load('msvc_pdb') - conf.load('msvs msdev subproject gitversion clang_compilation_database strip_on_install waf_unit_test enforce_pic') + conf.load('msvs subproject clang_compilation_database strip_on_install waf_unit_test enforce_pic force_32bit ninja') - # Force XP compatibility, all build targets should add subsystem=bld.env.MSVC_SUBSYSTEM - if conf.env.MSVC_TARGETS[0] == 'x86': - conf.env.MSVC_SUBSYSTEM = 'WINDOWS,5.01' - conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE,5.01' - else: - conf.env.MSVC_SUBSYSTEM = 'WINDOWS' - conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE' + conf.env.MSVC_SUBSYSTEM = 'WINDOWS' + conf.env.CONSOLE_SUBSYSTEM = 'CONSOLE' - enforce_pic = True # modern defaults + # Windows XP compatibility + if conf.env.MSVC_TARGETS[0] == 'amd64_x86' or conf.env.MSVC_TARGETS[0] == 'x86': + conf.env.MSVC_SUBSYSTEM += ',5.01' + conf.env.CONSOLE_SUBSYSTEM += ',5.01' - # modify options dictionary early + # Set default options for some platforms if conf.env.DEST_OS == 'android': - conf.options.NO_VGUI= True # skip vgui - conf.options.NANOGL = True - conf.options.GLWES = True - conf.options.GL4ES = True - conf.options.GL = False + conf.options.NANOGL = True + conf.options.GLWES = False # deprecated + conf.options.GL4ES = True + conf.options.GLES3COMPAT = True + conf.options.GL = False elif conf.env.MAGX: - conf.options.USE_SELECT = True conf.options.SDL12 = True - conf.options.NO_VGUI = True conf.options.GL = False conf.options.LOW_MEMORY = 1 - conf.options.SINGLE_BINARY = True - conf.options.NO_ASYNC_RESOLVE = True - conf.define('XASH_SDLMAIN', 1) enforce_pic = False - elif conf.env.DEST_OS == 'dos': - conf.options.SINGLE_BINARY = True - - if conf.env.STATIC_LINKING: - enforce_pic = False # PIC may break full static builds + elif conf.env.DEST_OS == 'emscripten': + conf.options.BUILD_BUNDLED_DEPS = True + conf.options.GLES3COMPAT = True + conf.options.GL = False + # psvita needs -fPIC set manually and static builds are incompatible with -fPIC + enforce_pic = conf.env.DEST_OS != 'psvita' and not conf.env.STATIC_LINKING conf.check_pic(enforce_pic) - # We restrict 64-bit builds ONLY for Win/Linux/OSX running on Intel architecture + # NOTE: We restrict 64-bit builds ONLY for Win/Linux running on Intel architecture # Because compatibility with original GoldSrc - if conf.env.DEST_OS in ['win32', 'linux', 'darwin'] and conf.env.DEST_CPU == 'x86_64': - conf.env.BIT32_MANDATORY = not conf.options.ALLOW64 - if conf.env.BIT32_MANDATORY: - Logs.info('WARNING: will build engine for 32-bit target') + # NOTE: Since modern OSX (since Catalina) don't support 32-bit applications, there is no point + # to restrict them to 32-bit engine, despite GoldSrc is still officially supported. + # There is now `-4` (or `--32bits`) configure flag for those + # who want to specifically build engine for 32-bit + if conf.env.DEST_OS in ['win32', 'linux'] and conf.env.DEST_CPU == 'x86_64': + force_32bit = not conf.options.ALLOW64 else: - conf.env.BIT32_MANDATORY = False + force_32bit = conf.options.FORCE32 - conf.load('force_32bit') - - compiler_optional_flags = [ -# '-Wall', '-Wextra', '-Wpedantic', - '-fdiagnostics-color=always', - '-Werror=return-type', - '-Werror=parentheses', - '-Werror=vla', - '-Werror=tautological-compare', - '-Werror=duplicated-cond', - '-Werror=bool-compare', - '-Werror=bool-operation', - '-Wcast-align', - '-Werror=cast-align=strict', # =strict is for GCC >=8 - '-Werror=packed', - '-Werror=packed-not-aligned', - '-Wuninitialized', # older GCC versions have -Wmaybe-uninitialized enabled by this switch, which is not accurate - # so just warn, not error - '-Winit-self', - '-Werror=implicit-fallthrough=2', # clang incompatible without "=2" - '-Werror=logical-op', - '-Werror=write-strings', -# '-Werror=format=2', -# '-Wdouble-promotion', # disable warning flood - '-Wstrict-aliasing', - '-Wmisleading-indentation', - ] - - c_compiler_optional_flags = [ - '-Werror=incompatible-pointer-types', - '-Werror=implicit-function-declaration', - '-Werror=int-conversion', - '-Werror=implicit-int', - '-Werror=strict-prototypes', - '-Werror=old-style-declaration', - '-Werror=old-style-definition', - '-Werror=declaration-after-statement', - '-Werror=enum-conversion', - '-Werror=jump-misses-init', - '-Werror=strict-prototypes', -# '-Werror=nested-externs', - '-fnonconst-initializers' # owcc - ] + if force_32bit: + conf.force_32bit() cflags, linkflags = conf.get_optimization_flags() + cxxflags = list(cflags) # optimization flags are common between C and C++ but we need a copy - # And here C++ flags starts to be treated separately - cxxflags = list(cflags) - if conf.env.COMPILER_CC != 'msvc' and not conf.options.DISABLE_WERROR: - conf.check_cc(cflags=cflags, linkflags=linkflags, msg='Checking for required C flags') - conf.check_cxx(cxxflags=cflags, linkflags=linkflags, msg='Checking for required C++ flags') + # on the Switch, allow undefined symbols by default, which is needed for libsolder to work + # we'll specifically disallow them for the engine executable + # additionally, shared libs are linked without standard libs, we'll add those back in the engine wscript + if conf.env.DEST_OS == 'nswitch': + linkflags.remove('-Wl,--no-undefined') + conf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-nostartfiles']) + conf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-nostartfiles']) + # same on the vita + elif conf.env.DEST_OS == 'psvita': + conf.env.append_unique('CFLAGS_cshlib', ['-fPIC']) + conf.env.append_unique('CXXFLAGS_cxxshlib', ['-fPIC', '-fno-use-cxa-atexit']) + conf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all']) + conf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all']) + # check if we need to use irix linkflags + elif conf.env.DEST_OS == 'irix' and conf.env.COMPILER_CC == 'gcc': + linkflags.remove('-Wl,--no-undefined') + linkflags.append('-Wl,--unresolved-symbols=ignore-all') + # check if we're in a sgug environment + if 'sgug' in os.environ['LD_LIBRARYN32_PATH']: + linkflags.append('-lc') + elif conf.env.SAILFISH in ['aurora', 'sailfish']: + # TODO: enable XASH_MOBILE_PLATFORM + conf.define('XASH_SAILFISH', 1) + if conf.env.SAILFISH == 'aurora': + conf.define('XASH_AURORAOS', 1) - conf.env.append_unique('CFLAGS', cflags) - conf.env.append_unique('CXXFLAGS', cxxflags) - conf.env.append_unique('LINKFLAGS', linkflags) + # Do not warn us about bug in SDL_Audio headers + conf.env.append_unique('CFLAGS', ['-Wno-attributes']) + conf.env.append_unique('CXXFLAGS', ['-Wno-attributes']) - cxxflags += conf.filter_cxxflags(compiler_optional_flags, cflags) - cflags += conf.filter_cflags(compiler_optional_flags + c_compiler_optional_flags, cflags) + conf.check_cc(cflags=cflags, linkflags=linkflags, msg='Checking for required C flags') + conf.check_cxx(cxxflags=cxxflags, linkflags=linkflags, msg='Checking for required C++ flags') conf.env.append_unique('CFLAGS', cflags) conf.env.append_unique('CXXFLAGS', cxxflags) conf.env.append_unique('LINKFLAGS', linkflags) - # check if we can use C99 stdint - if conf.check_cc(header_name='stdint.h', mandatory=False): - # use system - conf.define('STDINT_H', 'stdint.h') - else: - # include portable stdint by Paul Hsich - conf.define('STDINT_H', 'pstdint.h') + if conf.env.COMPILER_CC != 'msvc': + opt_flags = [ + # '-Wall', '-Wextra', '-Wpedantic', + '-fdiagnostics-color=always', + # stable diagnostics, forced to error, sorted + '-Werror=alloc-size', + '-Werror=bool-compare', + '-Werror=bool-operation', + # '-Werror=cast-align=strict', + '-Werror=duplicated-cond', + '-Werror=format=2', + '-Werror=free-nonheap-object', + '-Werror=implicit-fallthrough=2', + '-Werror=logical-op', + '-Werror=nonnull', + '-Werror=packed', + '-Werror=packed-not-aligned', + '-Werror=parentheses', + '-Werror=return-type', + '-Werror=sequence-point', + '-Werror=sizeof-pointer-memaccess', + '-Werror=sizeof-array-div', + '-Werror=sizeof-pointer-div', + '-Werror=strict-aliasing', + '-Werror=string-compare', + '-Werror=tautological-compare', + '-Werror=use-after-free=3', + '-Werror=vla', + '-Werror=write-strings', + + # unstable diagnostics, may cause false positives + '-Walloc-zero', + '-Winit-self', + '-Wmisleading-indentation', + '-Wmismatched-dealloc', + '-Wstringop-overflow', + '-Wunintialized', + '-Wno-error=format-nonliteral', + + # disabled, flood + # '-Wdouble-promotion', + + '-Wunused-function', + '-Wunused-variable', + '-Wunused-but-set-variable', + ] + + if conf.env.COMPILER_CC == 'clang': + opt_flags += [ + '-Werror=unsequenced', # clang's version of -Werror=sequence-point + ] + + opt_cflags = [ + '-Werror=declaration-after-statement', + '-Werror=enum-conversion', + '-Wno-error=enum-float-conversion', # need this for cvars + '-Werror=implicit-int', + '-Werror=implicit-function-declaration', + '-Werror=incompatible-pointer-types', + '-Werror=int-conversion', + '-Werror=jump-misses-init', + '-Werror=old-style-declaration', + '-Werror=old-style-definition', + '-Werror=strict-prototypes', + '-fnonconst-initializers', # owcc + '-Wmissing-prototypes', # not an error yet + ] + + opt_cxxflags = [] # TODO: + + if conf.options.DISABLE_WERROR: + opt_flags = [] + opt_cflags = ['-Werror=implicit-function-declaration'] + opt_cxxflags = [] + + conf.env.CFLAGS_werror = conf.filter_cflags(opt_flags + opt_cflags, cflags) + conf.env.CXXFLAGS_werror = conf.filter_cxxflags(opt_flags + opt_cxxflags, cxxflags) + + conf.env.TESTS = conf.options.TESTS conf.env.ENABLE_UTILS = conf.options.ENABLE_UTILS + conf.env.ENABLE_XAR = conf.options.ENABLE_XAR conf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER - conf.env.DEDICATED = conf.options.DEDICATED - conf.env.SINGLE_BINARY = conf.options.SINGLE_BINARY or conf.env.DEDICATED - conf.env.NANOGL = conf.options.NANOGL or conf.options.ALL_RENDERERS - conf.env.GLWES = conf.options.GLWES or conf.options.ALL_RENDERERS - conf.env.GL4ES = conf.options.GL4ES or conf.options.ALL_RENDERERS - conf.env.GL = conf.options.GL or conf.options.ALL_RENDERERS - conf.env.SOFT = conf.options.SOFT or conf.options.ALL_RENDERERS + if not conf.options.DEDICATED: + conf.env.SERVER = conf.options.ENABLE_DEDICATED + conf.env.CLIENT = True + conf.env.LAUNCHER = conf.env.DEST_OS not in ['android', 'nswitch', 'psvita', 'dos', 'emscripten'] and not conf.env.MAGX and not conf.env.STATIC_LINKING + else: + conf.env.SERVER = True + conf.env.CLIENT = False + conf.env.LAUNCHER = False + + conf.env.TUI = conf.options.ENABLE_TUI + + conf.define_cond('SUPPORT_HL25_EXTENDED_STRUCTS', conf.options.SUPPORT_HL25_EXTENDED_STRUCTS) + + if conf.env.SAILFISH == 'aurora': + conf.env.DEFAULT_RPATH = '/usr/share/su.xash.Engine/lib' + elif conf.env.DEST_OS == 'darwin': + conf.env.DEFAULT_RPATH = '@loader_path' + elif conf.env.DEST_OS == 'openbsd': + # OpenBSD requires -z origin to enable $ORIGIN expansion in RPATH + conf.env.RPATH_ST = '-Wl,-z,origin,-rpath,%s' + conf.env.DEFAULT_RPATH = '$ORIGIN' + elif conf.env.DEST_OS in ['nswitch', 'psvita']: + conf.env.DEFAULT_RPATH = None + else: + conf.env.DEFAULT_RPATH = '$ORIGIN' + + setattr(conf, 'refdlls', REFDLLS) + + for refdll in REFDLLS: + refdll.register_env(conf.env, conf.options, conf.options.ALL_RENDERERS) conf.env.GAMEDIR = conf.options.GAMEDIR conf.define('XASH_GAMEDIR', conf.options.GAMEDIR) + conf.define_cond('XASH_ALL_SERVERS', conf.options.ALL_SERVERS) - if conf.env.DEST_OS != 'win32': - conf.check_cc(lib='dl', mandatory=False) + if conf.env.DEST_OS == 'nswitch': + conf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER') + if conf.env.HAVE_SOLDER and conf.env.LIB_SOLDER and conf.options.BUILD_TYPE == 'debug': + conf.env.LIB_SOLDER[0] += 'd' # load libsolderd in debug mode + conf.check_cc(lib='m') + elif conf.env.DEST_OS == 'psvita': + conf.check_cc(lib='vrtld') + conf.check_cc(lib='m') + elif conf.env.DEST_OS == 'android': + # maybe there is some better check? + if conf.find_program('termux-info', mandatory=False): + conf.env.TERMUX = True + conf.define('__TERMUX__', 1) - if not conf.env.LIB_M: # HACK: already added in xcompile! + conf.check_cc(lib='dl') + conf.check_cc(lib='log') + if not conf.options.ANDROID_OPTS: + # if we're compiling on device itself conf.check_cc(lib='m') - else: + # otherwise LIB_M is defined by xcompile (as it might be libm_hard, depending on NDK configuration) + elif conf.env.DEST_OS == 'win32': # Common Win32 libraries # Don't check them more than once, to save time # Usually, they are always available # but we need them in uselib - a = [ - 'user32', - 'shell32', - 'gdi32', - 'advapi32', - 'dbghelp', - 'psapi', - 'ws2_32' - ] - + a = [ 'user32', 'shell32', 'gdi32', 'advapi32', 'dbghelp', 'psapi', 'ws2_32' ] if conf.env.COMPILER_CC == 'msvc': for i in a: conf.start_msg('Checking for MSVC library') @@ -302,75 +469,68 @@ def configure(conf): else: for i in a: conf.check_cc(lib = i) - - # check if we can use C99 tgmath - if conf.check_cc(header_name='tgmath.h', mandatory=False): - if conf.env.COMPILER_CC == 'msvc': - conf.define('_CRT_SILENCE_NONCONFORMING_TGMATH_H', 1) - tgmath_usable = conf.check_cc(fragment='''#include - const float val = 2, val2 = 3; - int main(void){ return (int)(-asin(val) + cos(val2)); }''', - msg='Checking if tgmath.h is usable', mandatory=False, use='M') - conf.define_cond('HAVE_TGMATH_H', tgmath_usable) else: - conf.undefine('HAVE_TGMATH_H') + conf.check_cc(lib='dl', mandatory = False) + conf.check_cc(lib='m') + # set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes - if conf.env.DEST_OS != 'win32' and conf.env.DEST_SIZEOF_VOID_P == 4: - # check was borrowed from libarchive source code - file_offset_bits_usable = conf.check_cc(fragment=''' -#define _FILE_OFFSET_BITS 64 -#include -#define KB ((off_t)1024) -#define MB ((off_t)1024 * KB) -#define GB ((off_t)1024 * MB) -#define TB ((off_t)1024 * GB) -int t2[(((64 * GB -1) % 671088649) == 268434537) - && (((TB - (64 * GB -1) + 255) % 1792151290) == 305159546)? 1: -1]; -int main(void) { return 0; }''', - msg='Checking if _FILE_OFFSET_BITS can be defined to 64', mandatory=False) - if file_offset_bits_usable: - conf.define('_FILE_OFFSET_BITS', 64) - else: conf.undefine('_FILE_OFFSET_BITS') - - if conf.env.DEST_OS != 'win32': - strcasestr_frag = '''#include -int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }''' - - if conf.check_cc(msg='Checking for strcasestr', mandatory=False, fragment=strcasestr_frag): - conf.define('HAVE_STRCASESTR', 1) - elif conf.check_cc(msg='... with _GNU_SOURCE?', mandatory=False, fragment=strcasestr_frag, defines='_GNU_SOURCE=1'): - conf.define('_GNU_SOURCE', 1) - conf.define('HAVE_STRCASESTR', 1) - - # check if we can use alloca.h or malloc.h - if conf.check_cc(header_name='alloca.h', mandatory=False): - conf.define('ALLOCA_H', 'alloca.h') - elif conf.check_cc(header_name='malloc.h', mandatory=False): - conf.define('ALLOCA_H', 'malloc.h') + # must be set globally as it changes ABI + if conf.env.DEST_OS == 'android' and conf.env.DEST_SIZEOF_VOID_P == 4: + # Android in 32-bit mode don't have good enough large file support + # with our native API level + # https://android.googlesource.com/platform/bionic/+/HEAD/docs/32-bit-abi.md + pass + elif conf.env.DEST_OS == 'psvita': + # PSVita don't have large file support at all + pass + else: + # try to guess how to support large files + conf.check_large_file(compiler = 'c', execute = False) # indicate if we are packaging for Linux/BSD if conf.options.PACKAGING: - conf.env.LIBDIR = conf.env.BINDIR = '${PREFIX}/lib/xash3d' - conf.env.SHAREDIR = '${PREFIX}/share/xash3d' + conf.env.PREFIX = conf.options.prefix + if conf.env.SAILFISH == "aurora": + conf.env.SHAREDIR = '${PREFIX}/share/su.xash.Engine/rodir' + elif conf.env.SAILFISH == "sailfish": + conf.env.SHAREDIR = '${PREFIX}/share/harbour-xash3d-fwgs/rodir' + else: + conf.env.SHAREDIR = '${PREFIX}/share/xash3d' + conf.env.LIBDIR += '/xash3d' else: - if sys.platform != 'win32' and not conf.env.DEST_OS == 'android': - conf.env.PREFIX = '/' - conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX if not conf.options.BUILD_BUNDLED_DEPS: - # check if we can use system opus - conf.define('CUSTOM_MODES', 1) + # there was a check for system libbacktrace but we can't be sure if it supports fileline or not + # therefore, always build libbacktrace ourselves - # try to link with export that only exists with CUSTOM_MODES defined - if conf.check_pkg('opus', 'opus', '''#include -int main(void){ return !opus_custom_encoder_init(0, 0, 0); }''', fatal = False): - conf.env.HAVE_SYSTEM_OPUS = True - else: - conf.undefine('CUSTOM_MODES') + if conf.env.CLIENT: + for i in ('ogg','opusfile','vorbis','vorbisfile'): + if conf.check_cfg(package=i, uselib_store=i, args='--cflags --libs', mandatory=False): + conf.env['HAVE_SYSTEM_%s' % i.upper()] = True + + if conf.env.HAVE_SYSTEM_OPUSFILE: + frag='''#include +int main(int argc, char **argv) { return opus_tagcompare(argv[0], argv[1]); }''' + + conf.env.HAVE_SYSTEM_OPUSFILE = conf.check_cc(msg='Checking for libopusfile sanity', use='opusfile werror', fragment=frag, mandatory=False) + + # search for opus 1.4 only, it has fixes for custom modes + # 1.5 breaks custom modes: https://github.com/xiph/opus/issues/374 + if conf.check_cfg(package='opus', uselib_store='opus', args='opus = 1.4 --cflags --libs', mandatory=False): + # now try to link with export that only exists with CUSTOM_MODES defined + frag='''#include +int main(void) { return !opus_custom_encoder_init((OpusCustomEncoder *)1, (const OpusCustomMode *)1, 1); }''' + + conf.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) + + # search for bzip2 + BZIP2_CHECK='''#include +int main(void) { return (int)BZ2_bzlibVersion(); }''' + + conf.env.HAVE_SYSTEM_BZ2 = conf.check_cc(lib='bz2', fragment=BZIP2_CHECK, uselib_store='bzip2', mandatory=False) - conf.define('XASH_BUILD_COMMIT', conf.env.GIT_VERSION if conf.env.GIT_VERSION else 'notset') conf.define('XASH_LOW_MEMORY', conf.options.LOW_MEMORY) for i in SUBDIRS: @@ -380,6 +540,18 @@ int main(void){ return !opus_custom_encoder_init(0, 0, 0); }''', fatal = False): conf.add_subproject(i.name) def build(bld): + if bld.env.WAFCACHE: + bld.load('wafcache') + + # guard rails to not let install to root + if bld.is_install and not bld.options.PACKAGING and not bld.options.destdir: + bld.fatal('Set the install destination directory using --destdir option') + + # don't clean QtCreator files and reconfigure saved options + bld.clean_files = bld.bldnode.ant_glob('**', + excl='*.user configuration.py .lock* *conf_check_*/** config.log 3rdparty/libbacktrace/*.h %s/*' % Build.CACHE_DIR, + quiet=True, generator=True) + bld.load('xshlib') for i in SUBDIRS: @@ -387,3 +559,7 @@ def build(bld): continue bld.add_subproject(i.name) + + if bld.env.TESTS: + bld.add_post_fun(waf_unit_test.summary) + bld.add_post_fun(waf_unit_test.set_exit_code)