diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/crypto/crypto.cpp | 47 | ||||
-rw-r--r-- | core/crypto/crypto.h | 23 | ||||
-rw-r--r-- | core/input/input.cpp | 2 | ||||
-rw-r--r-- | core/input/input_event.cpp | 235 | ||||
-rw-r--r-- | core/input/input_event.h | 16 | ||||
-rw-r--r-- | core/register_core_types.cpp | 1 | ||||
-rw-r--r-- | core/variant/variant_op.cpp | 2 |
7 files changed, 279 insertions, 47 deletions
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index d12108bca0..33ba0ba704 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -65,6 +65,22 @@ void X509Certificate::_bind_methods() { ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load); } +/// HMACContext + +void HMACContext::_bind_methods() { + ClassDB::bind_method(D_METHOD("start", "hash_type", "key"), &HMACContext::start); + ClassDB::bind_method(D_METHOD("update", "data"), &HMACContext::update); + ClassDB::bind_method(D_METHOD("finish"), &HMACContext::finish); +} + +HMACContext *(*HMACContext::_create)() = nullptr; +HMACContext *HMACContext::create() { + if (_create) { + return _create(); + } + ERR_FAIL_V_MSG(nullptr, "HMACContext is not available when the mbedtls module is disabled."); +} + /// Crypto void (*Crypto::_load_default_certificates)(String p_path) = nullptr; @@ -82,6 +98,35 @@ void Crypto::load_default_certificates(String p_path) { } } +PackedByteArray Crypto::hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg) { + Ref<HMACContext> ctx = Ref<HMACContext>(HMACContext::create()); + ERR_FAIL_COND_V_MSG(ctx.is_null(), PackedByteArray(), "HMAC is not available witout mbedtls module."); + Error err = ctx->start(p_hash_type, p_key); + ERR_FAIL_COND_V(err != OK, PackedByteArray()); + err = ctx->update(p_msg); + ERR_FAIL_COND_V(err != OK, PackedByteArray()); + return ctx->finish(); +} + +// Compares two HMACS for equality without leaking timing information in order to prevent timing attakcs. +// @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy +bool Crypto::constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received) { + const uint8_t *t = p_trusted.ptr(); + const uint8_t *r = p_received.ptr(); + int tlen = p_trusted.size(); + int rlen = p_received.size(); + // If the lengths are different then nothing else matters. + if (tlen != rlen) { + return false; + } + + uint8_t v = 0; + for (int i = 0; i < tlen; i++) { + v |= t[i] ^ r[i]; + } + return v == 0; +} + void Crypto::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_random_bytes", "size"), &Crypto::generate_random_bytes); ClassDB::bind_method(D_METHOD("generate_rsa", "size"), &Crypto::generate_rsa); @@ -90,6 +135,8 @@ void Crypto::_bind_methods() { ClassDB::bind_method(D_METHOD("verify", "hash_type", "hash", "signature", "key"), &Crypto::verify); ClassDB::bind_method(D_METHOD("encrypt", "key", "plaintext"), &Crypto::encrypt); ClassDB::bind_method(D_METHOD("decrypt", "key", "ciphertext"), &Crypto::decrypt); + ClassDB::bind_method(D_METHOD("hmac_digest", "hash_type", "key", "msg"), &Crypto::hmac_digest); + ClassDB::bind_method(D_METHOD("constant_time_compare", "trusted", "received"), &Crypto::constant_time_compare); } /// Resource loader/saver diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index 8325f043bf..5cacddaea0 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -67,6 +67,23 @@ public: virtual Error save(String p_path) = 0; }; +class HMACContext : public Reference { + GDCLASS(HMACContext, Reference); + +protected: + static void _bind_methods(); + static HMACContext *(*_create)(); + +public: + static HMACContext *create(); + + virtual Error start(HashingContext::HashType p_hash_type, PackedByteArray p_key) = 0; + virtual Error update(PackedByteArray p_data) = 0; + virtual PackedByteArray finish() = 0; + + HMACContext() {} +}; + class Crypto : public Reference { GDCLASS(Crypto, Reference); @@ -88,6 +105,12 @@ public: virtual Vector<uint8_t> encrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_plaintext) = 0; virtual Vector<uint8_t> decrypt(Ref<CryptoKey> p_key, Vector<uint8_t> p_ciphertext) = 0; + PackedByteArray hmac_digest(HashingContext::HashType p_hash_type, PackedByteArray p_key, PackedByteArray p_msg); + + // Compares two PackedByteArrays for equality without leaking timing information in order to prevent timing attacks. + // @see: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy + bool constant_time_compare(PackedByteArray p_trusted, PackedByteArray p_received); + Crypto() {} }; diff --git a/core/input/input.cpp b/core/input/input.cpp index 00a7e63a73..153656e44a 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -1211,7 +1211,7 @@ void Input::parse_mapping(String p_mapping) { ERR_CONTINUE_MSG(output.length() < 1 || input.length() < 2, String(entry[idx] + "\nInvalid device mapping entry: " + entry[idx])); - if (output == "platform") { + if (output == "platform" || output == "hint") { continue; } diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 41bc5e84b0..e04e642f6b 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -84,10 +84,6 @@ Ref<InputEvent> InputEvent::xformed_by(const Transform2D &p_xform, const Vector2 return Ref<InputEvent>((InputEvent *)this); } -String InputEvent::as_text() const { - return String(); -} - bool InputEvent::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { return false; } @@ -198,6 +194,33 @@ void InputEventWithModifiers::set_modifiers_from_event(const InputEventWithModif set_metakey(event->get_metakey()); } +String InputEventWithModifiers::as_text() const { + Vector<String> mod_names; + + if (get_control()) { + mod_names.push_back(find_keycode_name(KEY_CONTROL)); + } + if (get_shift()) { + mod_names.push_back(find_keycode_name(KEY_SHIFT)); + } + if (get_alt()) { + mod_names.push_back(find_keycode_name(KEY_ALT)); + } + if (get_metakey()) { + mod_names.push_back(find_keycode_name(KEY_META)); + } + + if (!mod_names.empty()) { + return String("+").join(mod_names); + } else { + return "None"; + } +} + +String InputEventWithModifiers::to_string() { + return as_text(); +} + void InputEventWithModifiers::_bind_methods() { ClassDB::bind_method(D_METHOD("set_store_command", "enable"), &InputEventWithModifiers::set_store_command); ClassDB::bind_method(D_METHOD("is_storing_command"), &InputEventWithModifiers::is_storing_command); @@ -326,24 +349,31 @@ uint32_t InputEventKey::get_physical_keycode_with_modifiers() const { } String InputEventKey::as_text() const { - String kc = keycode_get_string(keycode); + String kc; + + if (keycode == 0) { + kc = keycode_get_string(physical_keycode) + " (" + RTR("Physical") + ")"; + } else { + kc = keycode_get_string(keycode); + } + if (kc == String()) { return kc; } - if (get_metakey()) { - kc = find_keycode_name(KEY_META) + ("+" + kc); - } - if (get_alt()) { - kc = find_keycode_name(KEY_ALT) + ("+" + kc); - } - if (get_shift()) { - kc = find_keycode_name(KEY_SHIFT) + ("+" + kc); - } - if (get_control()) { - kc = find_keycode_name(KEY_CONTROL) + ("+" + kc); + String mods_text = InputEventWithModifiers::as_text(); + return mods_text == "" ? kc : mods_text + "+" + kc; +} + +String InputEventKey::to_string() { + String p = is_pressed() ? "true" : "false"; + String e = is_echo() ? "true" : "false"; + + if (keycode == 0) { + return vformat("InputEventKey: keycode=%s mods=%s physical=%s pressed=%s echo=%s", itos(physical_keycode) + " " + keycode_get_string(physical_keycode), InputEventWithModifiers::as_text(), "true", p, e); + } else { + return vformat("InputEventKey: keycode=%s mods=%s physical=%s pressed=%s echo=%s", itos(keycode) + " " + keycode_get_string(keycode), InputEventWithModifiers::as_text(), "false", p, e); } - return kc; } bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool *p_pressed, float *p_strength, float *p_raw_strength, float p_deadzone) const { @@ -536,41 +566,74 @@ bool InputEventMouseButton::action_match(const Ref<InputEvent> &p_event, bool *p return match; } +static const char *_mouse_button_descriptions[9] = { + TTRC("Left Mouse Button"), + TTRC("Right Mouse Button"), + TTRC("Middle Mouse Button"), + TTRC("Mouse Wheel Up"), + TTRC("Mouse Wheel Down"), + TTRC("Mouse Wheel Left"), + TTRC("Mouse Wheel Right"), + TTRC("Mouse Thumb Button 1"), + TTRC("Mouse Thumb Button 2") +}; + String InputEventMouseButton::as_text() const { - String button_index_string = ""; - switch (get_button_index()) { + // Modifiers + String mods_text = InputEventWithModifiers::as_text(); + String full_string = mods_text == "" ? "" : mods_text + "+"; + + // Button + int idx = get_button_index(); + switch (idx) { case BUTTON_LEFT: - button_index_string = "BUTTON_LEFT"; - break; case BUTTON_RIGHT: - button_index_string = "BUTTON_RIGHT"; - break; case BUTTON_MIDDLE: - button_index_string = "BUTTON_MIDDLE"; - break; case BUTTON_WHEEL_UP: - button_index_string = "BUTTON_WHEEL_UP"; - break; case BUTTON_WHEEL_DOWN: - button_index_string = "BUTTON_WHEEL_DOWN"; - break; case BUTTON_WHEEL_LEFT: - button_index_string = "BUTTON_WHEEL_LEFT"; - break; case BUTTON_WHEEL_RIGHT: - button_index_string = "BUTTON_WHEEL_RIGHT"; - break; case BUTTON_XBUTTON1: - button_index_string = "BUTTON_XBUTTON1"; + case BUTTON_XBUTTON2: + full_string += RTR(_mouse_button_descriptions[idx - 1]); // button index starts from 1, array index starts from 0, so subtract 1 + break; + default: + full_string += RTR("Button") + " #" + itos(idx); break; + } + + // Double Click + if (doubleclick) { + full_string += " (" + RTR("Double Click") + ")"; + } + + return full_string; +} + +String InputEventMouseButton::to_string() { + String p = is_pressed() ? "true" : "false"; + String d = doubleclick ? "true" : "false"; + + int idx = get_button_index(); + String button_string = itos(idx); + + switch (idx) { + case BUTTON_LEFT: + case BUTTON_RIGHT: + case BUTTON_MIDDLE: + case BUTTON_WHEEL_UP: + case BUTTON_WHEEL_DOWN: + case BUTTON_WHEEL_LEFT: + case BUTTON_WHEEL_RIGHT: + case BUTTON_XBUTTON1: case BUTTON_XBUTTON2: - button_index_string = "BUTTON_XBUTTON2"; + button_string += " (" + RTR(_mouse_button_descriptions[idx - 1]) + ")"; // button index starts from 1, array index starts from 0, so subtract 1 break; default: - button_index_string = itos(get_button_index()); break; } - return "InputEventMouseButton : button_index=" + button_index_string + ", pressed=" + (pressed ? "true" : "false") + ", position=(" + String(get_position()) + "), button_mask=" + itos(get_button_mask()) + ", doubleclick=" + (doubleclick ? "true" : "false"); + + return vformat("InputEventMouseButton: button_index=%s pressed=%s position=(%s) button_mask=%s doubleclick=%s", button_index, p, String(get_position()), itos(get_button_mask()), d); } void InputEventMouseButton::_bind_methods() { @@ -653,27 +716,32 @@ Ref<InputEvent> InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co } String InputEventMouseMotion::as_text() const { - String button_mask_string = ""; + return vformat(RTR("Mouse motion at position (%s) with speed (%s)"), String(get_position()), String(get_speed())); +} + +String InputEventMouseMotion::to_string() { + int button_mask = get_button_mask(); + String button_mask_string = itos(button_mask); switch (get_button_mask()) { case BUTTON_MASK_LEFT: - button_mask_string = "BUTTON_MASK_LEFT"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_LEFT - 1]) + ")"; break; case BUTTON_MASK_MIDDLE: - button_mask_string = "BUTTON_MASK_MIDDLE"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_MIDDLE - 1]) + ")"; break; case BUTTON_MASK_RIGHT: - button_mask_string = "BUTTON_MASK_RIGHT"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_RIGHT - 1]) + ")"; break; case BUTTON_MASK_XBUTTON1: - button_mask_string = "BUTTON_MASK_XBUTTON1"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_XBUTTON1 - 1]) + ")"; break; case BUTTON_MASK_XBUTTON2: - button_mask_string = "BUTTON_MASK_XBUTTON2"; + button_mask_string += " (" + RTR(_mouse_button_descriptions[BUTTON_XBUTTON2 - 1]) + ")"; break; default: - button_mask_string = itos(get_button_mask()); break; } + return "InputEventMouseMotion : button_mask=" + button_mask_string + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + "), pressure=(" + rtos(get_pressure()) + "), tilt=(" + String(get_tilt()) + ")"; } @@ -796,7 +864,26 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool * return match; } +static const char *_joy_axis_descriptions[JOY_AXIS_MAX] = { + TTRC("Left Stick X-Axis, Joystick 0 X-Axis"), + TTRC("Left Stick Y-Axis, Joystick 0 Y-Axis"), + TTRC("Right Stick X-Axis, Joystick 1 X-Axis"), + TTRC("Right Stick Y-Axis, Joystick 1 Y-Axis"), + TTRC("Joystick 2 X-Axis, Left Trigger, Sony L2, Xbox LT"), + TTRC("Joystick 2 Y-Axis, Right Trigger, Sony R2, Xbox RT"), + TTRC("Joystick 3 X-Axis"), + TTRC("Joystick 3 Y-Axis"), + TTRC("Joystick 4 X-Axis"), + TTRC("Joystick 4 Y-Axis"), +}; + String InputEventJoypadMotion::as_text() const { + String desc = axis < JOY_AXIS_MAX ? RTR(_joy_axis_descriptions[axis]) : TTR("Unknown Joypad Axis"); + + return vformat(TTR("Joypad Motion on Axis %s (%s) with Value %s"), itos(axis), desc, String(Variant(axis_value))); +} + +String InputEventJoypadMotion::to_string() { return "InputEventJoypadMotion : axis=" + itos(axis) + ", axis_value=" + String(Variant(axis_value)); } @@ -869,7 +956,39 @@ bool InputEventJoypadButton::shortcut_match(const Ref<InputEvent> &p_event) cons return button_index == button->button_index; } +static const char *_joy_button_descriptions[JOY_BUTTON_SDL_MAX] = { + TTRC("Bottom Action, Sony Cross, Xbox A, Nintendo B"), + TTRC("Right Action, Sony Circle, Xbox B, Nintendo A"), + TTRC("Left Action, Sony Square, Xbox X, Nintendo Y"), + TTRC("Top Action, Sony Triangle, Xbox Y, Nintendo X"), + TTRC("Back, Sony Select, Xbox Back, Nintendo -"), + TTRC("Guide, Sony PS, Xbox Home"), + TTRC("Start, Nintendo +"), + TTRC("Left Stick, Sony L3, Xbox L/LS"), + TTRC("Right Stick, Sony R3, Xbox R/RS"), + TTRC("Left Shoulder, Sony L1, Xbox LB"), + TTRC("Right Shoulder, Sony R1, Xbox RB"), + TTRC("D-pad Up"), + TTRC("D-pad Down"), + TTRC("D-pad Left"), + TTRC("D-pad Right"), +}; + String InputEventJoypadButton::as_text() const { + String text = "Joypad Button " + itos(button_index); + + if (button_index < JOY_BUTTON_SDL_MAX) { + text += vformat(" (%s)", _joy_button_descriptions[button_index]); + } + + if (pressure != 0) { + text += ", Pressure:" + String(Variant(pressure)); + } + + return text; +} + +String InputEventJoypadButton::to_string() { return "InputEventJoypadButton : button_index=" + itos(button_index) + ", pressed=" + (pressed ? "true" : "false") + ", pressure=" + String(Variant(pressure)); } @@ -927,6 +1046,12 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co } String InputEventScreenTouch::as_text() const { + String status = pressed ? RTR("touched") : RTR("released"); + + return vformat(RTR("Screen %s at (%s) with %s touch points"), status, String(get_position()), itos(index)); +} + +String InputEventScreenTouch::to_string() { return "InputEventScreenTouch : index=" + itos(index) + ", pressed=" + (pressed ? "true" : "false") + ", position=(" + String(get_position()) + ")"; } @@ -996,6 +1121,10 @@ Ref<InputEvent> InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con } String InputEventScreenDrag::as_text() const { + return vformat(RTR("Screen dragged with %s touch points at position (%s) with speed of (%s)"), itos(index), String(get_position()), String(get_speed())); +} + +String InputEventScreenDrag::to_string() { return "InputEventScreenDrag : index=" + itos(index) + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + ")"; } @@ -1079,6 +1208,10 @@ bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool *p_pres } String InputEventAction::as_text() const { + return vformat(RTR("Input Action %s was %s"), action, pressed ? "pressed" : "released"); +} + +String InputEventAction::to_string() { return "InputEventAction : action=" + action + ", pressed=(" + (pressed ? "true" : "false"); } @@ -1142,6 +1275,10 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, } String InputEventMagnifyGesture::as_text() const { + return vformat(RTR("Magnify Gesture at (%s) with factor %s"), String(get_position()), rtos(get_factor())); +} + +String InputEventMagnifyGesture::to_string() { return "InputEventMagnifyGesture : factor=" + rtos(get_factor()) + ", position=(" + String(get_position()) + ")"; } @@ -1178,6 +1315,10 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con } String InputEventPanGesture::as_text() const { + return vformat(RTR("Pan Gesture at (%s) with delta (%s)"), String(get_position()), String(get_delta())); +} + +String InputEventPanGesture::to_string() { return "InputEventPanGesture : delta=(" + String(get_delta()) + "), position=(" + String(get_position()) + ")"; } @@ -1255,7 +1396,11 @@ int InputEventMIDI::get_controller_value() const { } String InputEventMIDI::as_text() const { - return "InputEventMIDI : channel=(" + itos(get_channel()) + "), message=(" + itos(get_message()) + ")"; + return vformat(RTR("MIDI Input on Channel=%s Message=%s"), itos(channel), itos(message)); +} + +String InputEventMIDI::to_string() { + return vformat("InputEvenMIDI: channel=%s message=%s pitch=%s velocity=%s pressure=%s", itos(channel), itos(message), itos(pitch), itos(velocity), itos(pressure)); } void InputEventMIDI::_bind_methods() { diff --git a/core/input/input_event.h b/core/input/input_event.h index 0eae3a2bbe..6a71a24c8b 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -132,7 +132,7 @@ public: virtual bool is_pressed() const; virtual bool is_echo() const; - virtual String as_text() const; + virtual String as_text() const = 0; virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const; @@ -207,6 +207,9 @@ public: void set_modifiers_from_event(const InputEventWithModifiers *event); + virtual String as_text() const override; + virtual String to_string() override; + InputEventWithModifiers() {} }; @@ -249,6 +252,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventKey() {} }; @@ -306,6 +310,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventMouseButton() {} }; @@ -336,6 +341,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; virtual bool accumulate(const Ref<InputEvent> &p_event) override; @@ -363,6 +369,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventJoypadMotion() {} }; @@ -391,6 +398,7 @@ public: virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventJoypadButton() {} }; @@ -416,6 +424,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventScreenTouch() {} }; @@ -445,6 +454,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventScreenDrag() {} }; @@ -476,6 +486,7 @@ public: virtual bool shortcut_match(const Ref<InputEvent> &p_event) const override; virtual bool is_action_type() const override { return true; } virtual String as_text() const override; + virtual String to_string() override; InputEventAction() {} }; @@ -506,6 +517,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventMagnifyGesture() {} }; @@ -523,6 +535,7 @@ public: virtual Ref<InputEvent> xformed_by(const Transform2D &p_xform, const Vector2 &p_local_ofs = Vector2()) const override; virtual String as_text() const override; + virtual String to_string() override; InputEventPanGesture() {} }; @@ -568,6 +581,7 @@ public: int get_controller_value() const; virtual String as_text() const override; + virtual String to_string() override; InputEventMIDI() {} }; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 7e32f215e7..9883ce12a0 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -168,6 +168,7 @@ void register_core_types() { ClassDB::register_class<AESContext>(); ClassDB::register_custom_instance_class<X509Certificate>(); ClassDB::register_custom_instance_class<CryptoKey>(); + ClassDB::register_custom_instance_class<HMACContext>(); ClassDB::register_custom_instance_class<Crypto>(); ClassDB::register_custom_instance_class<StreamPeerSSL>(); diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index 07b024ecb4..df29ec7b63 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -1399,6 +1399,8 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorSub<Vector2i, Vector2i, Vector2i>>(Variant::OP_SUBTRACT, Variant::VECTOR2I, Variant::VECTOR2I); register_op<OperatorEvaluatorSub<Vector3, Vector3, Vector3>>(Variant::OP_SUBTRACT, Variant::VECTOR3, Variant::VECTOR3); register_op<OperatorEvaluatorSub<Vector3i, Vector3i, Vector3i>>(Variant::OP_SUBTRACT, Variant::VECTOR3I, Variant::VECTOR3I); + register_op<OperatorEvaluatorSub<Quat, Quat, Quat>>(Variant::OP_SUBTRACT, Variant::QUAT, Variant::QUAT); + register_op<OperatorEvaluatorSub<Color, Color, Color>>(Variant::OP_SUBTRACT, Variant::COLOR, Variant::COLOR); register_op<OperatorEvaluatorMul<int64_t, int64_t, int64_t>>(Variant::OP_MULTIPLY, Variant::INT, Variant::INT); register_op<OperatorEvaluatorMul<double, int64_t, double>>(Variant::OP_MULTIPLY, Variant::INT, Variant::FLOAT); |