diff --git a/CMakeLists.txt b/CMakeLists.txt index 89e3cb0..0099a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,15 +89,17 @@ set(PROJECT_SOURCES 3rdparty/qrcodegen.cpp 3rdparty/QtExtKeySequenceEdit.cpp - qv2ray/ui/LogHighlighter.cpp - qv2ray/ui/QvAutoCompleteTextEdit.cpp - qv2ray/utils/HTTPRequestHelper.cpp - qv2ray/components/proxy/QvProxyConfigurator.cpp - qv2ray/ui/widgets/common/QJsonModel.cpp + qv2ray/v2/ui/LogHighlighter.cpp + qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp + qv2ray/v2/utils/HTTPRequestHelper.cpp + qv2ray/v2/components/proxy/QvProxyConfigurator.cpp + qv2ray/v2/ui/widgets/common/QJsonModel.cpp + qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp + qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp + qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui - qv2ray/ui/widgets/editors/w_JsonEditor.cpp - qv2ray/ui/widgets/editors/w_JsonEditor.hpp - qv2ray/ui/widgets/editors/w_JsonEditor.ui + qv2ray/v3/components/GeositeReader/GeositeReader.cpp + qv2ray/v3/components/GeositeReader/picoproto.cpp rpc/gRPC.cpp diff --git a/qv2ray/components/proxy/QvProxyConfigurator.cpp b/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp similarity index 100% rename from qv2ray/components/proxy/QvProxyConfigurator.cpp rename to qv2ray/v2/components/proxy/QvProxyConfigurator.cpp diff --git a/qv2ray/components/proxy/QvProxyConfigurator.hpp b/qv2ray/v2/components/proxy/QvProxyConfigurator.hpp similarity index 100% rename from qv2ray/components/proxy/QvProxyConfigurator.hpp rename to qv2ray/v2/components/proxy/QvProxyConfigurator.hpp diff --git a/qv2ray/ui/LogHighlighter.cpp b/qv2ray/v2/ui/LogHighlighter.cpp similarity index 100% rename from qv2ray/ui/LogHighlighter.cpp rename to qv2ray/v2/ui/LogHighlighter.cpp diff --git a/qv2ray/ui/LogHighlighter.hpp b/qv2ray/v2/ui/LogHighlighter.hpp similarity index 100% rename from qv2ray/ui/LogHighlighter.hpp rename to qv2ray/v2/ui/LogHighlighter.hpp diff --git a/qv2ray/ui/QvAutoCompleteTextEdit.cpp b/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp similarity index 100% rename from qv2ray/ui/QvAutoCompleteTextEdit.cpp rename to qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp diff --git a/qv2ray/ui/QvAutoCompleteTextEdit.hpp b/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp similarity index 100% rename from qv2ray/ui/QvAutoCompleteTextEdit.hpp rename to qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp diff --git a/qv2ray/ui/widgets/common/QJsonModel.cpp b/qv2ray/v2/ui/widgets/common/QJsonModel.cpp similarity index 100% rename from qv2ray/ui/widgets/common/QJsonModel.cpp rename to qv2ray/v2/ui/widgets/common/QJsonModel.cpp diff --git a/qv2ray/ui/widgets/common/QJsonModel.hpp b/qv2ray/v2/ui/widgets/common/QJsonModel.hpp similarity index 100% rename from qv2ray/ui/widgets/common/QJsonModel.hpp rename to qv2ray/v2/ui/widgets/common/QJsonModel.hpp diff --git a/qv2ray/ui/widgets/editors/w_JsonEditor.cpp b/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp similarity index 100% rename from qv2ray/ui/widgets/editors/w_JsonEditor.cpp rename to qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp diff --git a/qv2ray/ui/widgets/editors/w_JsonEditor.hpp b/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp similarity index 90% rename from qv2ray/ui/widgets/editors/w_JsonEditor.hpp rename to qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp index 2f216dd..42a5364 100644 --- a/qv2ray/ui/widgets/editors/w_JsonEditor.hpp +++ b/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp @@ -1,7 +1,7 @@ #pragma once #include "qv2ray/wrapper.hpp" -#include "qv2ray/ui/widgets/common/QJsonModel.hpp" +#include "qv2ray/v2/ui/widgets/common/QJsonModel.hpp" #include "ui_w_JsonEditor.h" diff --git a/qv2ray/ui/widgets/editors/w_JsonEditor.ui b/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui similarity index 100% rename from qv2ray/ui/widgets/editors/w_JsonEditor.ui rename to qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui diff --git a/qv2ray/utils/HTTPRequestHelper.cpp b/qv2ray/v2/utils/HTTPRequestHelper.cpp similarity index 100% rename from qv2ray/utils/HTTPRequestHelper.cpp rename to qv2ray/v2/utils/HTTPRequestHelper.cpp diff --git a/qv2ray/utils/HTTPRequestHelper.hpp b/qv2ray/v2/utils/HTTPRequestHelper.hpp similarity index 100% rename from qv2ray/utils/HTTPRequestHelper.hpp rename to qv2ray/v2/utils/HTTPRequestHelper.hpp diff --git a/qv2ray/v3/components/GeositeReader/GeositeReader.cpp b/qv2ray/v3/components/GeositeReader/GeositeReader.cpp new file mode 100644 index 0000000..5825ff6 --- /dev/null +++ b/qv2ray/v3/components/GeositeReader/GeositeReader.cpp @@ -0,0 +1,42 @@ +#include "GeositeReader.hpp" + +#include "qv2ray/wrapper.hpp" +#include "picoproto.hpp" + +#include +#include + +namespace Qv2ray::components::GeositeReader { + QMap GeositeEntries; + + QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache) { + if (GeositeEntries.contains(filepath) && allowCache) + return GeositeEntries.value(filepath); + + QStringList list; + qInfo() << "Reading geosites from:" << filepath; + QFile f(filepath); + bool opened = f.open(QFile::OpenModeFlag::ReadOnly); + + if (!opened) { + qInfo() << "File cannot be opened:" << filepath; + return list; + } + + const auto content = f.readAll(); + f.close(); + { + picoproto::Message root; + root.ParseFromBytes((unsigned char *) content.data(), content.size()); + + list.reserve(root.GetMessageArray(1).size()); + for (const auto &geosite: root.GetMessageArray(1)) + list << QString::fromStdString(geosite->GetString(1)); + } + + qInfo() << "Loaded" << list.count() << "geosite entries from data file."; + list.sort(); + GeositeEntries[filepath] = list; + return list; + } +} // namespace Qv2ray::components::geosite diff --git a/qv2ray/v3/components/GeositeReader/GeositeReader.hpp b/qv2ray/v3/components/GeositeReader/GeositeReader.hpp new file mode 100644 index 0000000..8bafd6d --- /dev/null +++ b/qv2ray/v3/components/GeositeReader/GeositeReader.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace Qv2ray::components::GeositeReader { + QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache = true); +} // namespace Qv2ray::components::GeositeReader diff --git a/qv2ray/v3/components/GeositeReader/picoproto.cpp b/qv2ray/v3/components/GeositeReader/picoproto.cpp new file mode 100644 index 0000000..d62a9d7 --- /dev/null +++ b/qv2ray/v3/components/GeositeReader/picoproto.cpp @@ -0,0 +1,664 @@ +/* Copyright 2016 Pete Warden. All Rights Reserved. + + 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. + ==============================================================================*/ + +#include "picoproto.hpp" + +namespace picoproto +{ + + namespace + { + + // To keep the dependencies down, here's a local copy of the widespread bit_cast + // operator. This is necessary because in practice weird things can happen if + // you just try to use reinterpret_cast. + template + inline Dest bit_cast(const Source &source) + { + static_assert(sizeof(Dest) == sizeof(Source), "Sizes do not match"); + Dest dest; + memcpy(&dest, &source, sizeof(dest)); + return dest; + } + + // These are defined in: + // https://developers.google.com/protocol-buffers/docs/encoding + enum WireType + { + WIRETYPE_VARINT = 0, + WIRETYPE_64BIT = 1, + WIRETYPE_LENGTH_DELIMITED = 2, + WIRETYPE_GROUP_START = 3, + WIRETYPE_GROUP_END = 4, + WIRETYPE_32BIT = 5, + }; + + // Pull bytes from the stream, updating the state. + bool ConsumeBytes(uint8_t **current, size_t how_many, size_t *remaining) + { + if (how_many > *remaining) + { + PP_LOG(ERROR) << "ReadBytes overrun!"; + return false; + } + *current += how_many; + *remaining -= how_many; + return true; + } + + // Grabs a particular type from the byte stream. + template + T ReadFromBytes(uint8_t **current, size_t *remaining) + { + PP_CHECK(ConsumeBytes(current, sizeof(T), remaining)); + const T result = *(bit_cast(*current - sizeof(T))); + return result; + } + + uint64_t ReadVarInt(uint8_t **current, size_t *remaining) + { + uint64_t result = 0; + bool keep_going; + int shift = 0; + do + { + const uint8_t next_number = ReadFromBytes(current, remaining); + keep_going = (next_number >= 128); + result += (uint64_t)(next_number & 0x7f) << shift; + shift += 7; + } while (keep_going); + return result; + } + + void ReadWireTypeAndFieldNumber(uint8_t **current, size_t *remaining, uint8_t *wire_type, uint32_t *field_number) + { + uint64_t wire_type_and_field_number = ReadVarInt(current, remaining); + *wire_type = wire_type_and_field_number & 0x07; + *field_number = wire_type_and_field_number >> 3; + } + + } // namespace + + std::string FieldTypeDebugString(enum FieldType type) + { + switch (type) + { + case FIELD_UNSET: return "UNSET"; break; + case FIELD_UINT32: return "UINT32"; break; + case FIELD_UINT64: return "UINT64"; break; + case FIELD_BYTES: return "BYTES"; break; + default: return "Unknown field type"; break; + } + return "Should never get here"; + } + + Field::Field(FieldType type, bool owns_data) : type(type), owns_data(owns_data) + { + cached_messages = nullptr; + switch (type) + { + case FIELD_UINT32: + { + value.v_uint32 = new std::vector(); + } + break; + case FIELD_UINT64: + { + value.v_uint64 = new std::vector(); + } + break; + case FIELD_BYTES: + { + value.v_bytes = new std::vector>(); + cached_messages = new std::vector(); + } + break; + default: + { + PP_LOG(ERROR) << "Bad field type when constructing field: " << type; + } + break; + } + } + + Field::Field(const Field &other) : type(other.type), owns_data(other.owns_data) + { + switch (type) + { + case FIELD_UINT32: + { + value.v_uint32 = new std::vector(*other.value.v_uint32); + } + break; + case FIELD_UINT64: + { + value.v_uint64 = new std::vector(*other.value.v_uint64); + } + break; + case FIELD_BYTES: + { + if (owns_data) + { + value.v_bytes = new std::vector>(); + for (std::pair data_info : *other.value.v_bytes) + { + uint8_t *new_data = new uint8_t[data_info.second]; + std::copy_n(data_info.first, data_info.second, new_data); + value.v_bytes->push_back({ new_data, data_info.second }); + } + } + else + { + value.v_bytes = new std::vector>(*other.value.v_bytes); + } + cached_messages = new std::vector(); + cached_messages->reserve(other.cached_messages->size()); + for (Message *other_cached_message : *other.cached_messages) + { + Message *cached_message; + if (other_cached_message) + { + cached_message = new Message(*other_cached_message); + } + else + { + cached_message = nullptr; + } + cached_messages->push_back(cached_message); + } + } + break; + default: + { + PP_LOG(ERROR) << "Bad field type when constructing field: " << type; + } + break; + } + } + + Field::~Field() + { + switch (type) + { + case FIELD_UINT32: delete value.v_uint32; break; + case FIELD_UINT64: delete value.v_uint64; break; + case FIELD_BYTES: + { + if (owns_data) + for (std::pair data_info : *value.v_bytes) + delete[] data_info.first; + delete value.v_bytes; + + for (Message *cached_message : *cached_messages) + if (cached_message) + delete cached_message; + delete cached_messages; + break; + } + default: PP_LOG(ERROR) << "Bad field type when destroying field: " << type; break; + } + } + + Message::Message() : Message(true){}; + + Message::Message(bool copy_arrays) : copy_arrays(copy_arrays){}; + + Message::Message(const Message &other) : field_map(other.field_map), fields(other.fields), copy_arrays(other.copy_arrays){}; + + Message::~Message(){}; + + bool Message::ParseFromBytes(uint8_t *bytes, size_t bytes_size) + { + uint8_t *current = bytes; + size_t remaining = bytes_size; + while (remaining > 0) + { + uint8_t wire_type; + uint32_t field_number; + ReadWireTypeAndFieldNumber(¤t, &remaining, &wire_type, &field_number); + switch (wire_type) + { + case WIRETYPE_VARINT: + { + Field *field = AddField(field_number, FIELD_UINT64); + const uint64_t varint = ReadVarInt(¤t, &remaining); + field->value.v_uint64->push_back(varint); + break; + } + case WIRETYPE_64BIT: + { + Field *field = AddField(field_number, FIELD_UINT64); + const uint64_t value = ReadFromBytes(¤t, &remaining); + field->value.v_uint64->push_back(value); + break; + } + case WIRETYPE_LENGTH_DELIMITED: + { + Field *field = AddField(field_number, FIELD_BYTES); + const uint64_t size = ReadVarInt(¤t, &remaining); + uint8_t *data; + if (copy_arrays) + { + data = new uint8_t[size]; + std::copy_n(current, size, data); + field->owns_data = true; + } + else + { + data = current; + field->owns_data = false; + } + field->value.v_bytes->push_back({ data, size }); + field->cached_messages->push_back(nullptr); + current += size; + remaining -= size; + break; + } + case WIRETYPE_GROUP_START: + { + PP_LOG(INFO) << field_number << ": GROUPSTART" << std::endl; + PP_LOG(ERROR) << "Unhandled wire type encountered"; + break; + } + case WIRETYPE_GROUP_END: + { + PP_LOG(INFO) << field_number << ": GROUPEND" << std::endl; + PP_LOG(ERROR) << "Unhandled wire type encountered"; + break; + } + case WIRETYPE_32BIT: + { + Field *field = AddField(field_number, FIELD_UINT32); + const uint32_t value = ReadFromBytes(¤t, &remaining); + field->value.v_uint32->push_back(value); + break; + } + default: + { + PP_LOG(ERROR) << "Unknown wire type encountered: " << static_cast(wire_type) << " at offset" << (bytes_size - remaining); + return false; + break; + } + } + } + return true; + } + + Field *Message::AddField(int32_t number, enum FieldType type) + { + Field *field = GetField(number); + if (!field) + { + fields.push_back(Field(type, copy_arrays)); + field = &fields.back(); + field_map.insert({ number, fields.size() - 1 }); + } + return field; + } + + Field *Message::GetField(int32_t number) + { + if (field_map.count(number) == 0) + return nullptr; + return &(fields[field_map[number]]); + } + + Field *Message::GetFieldAndCheckType(int32_t number, enum FieldType type) + { + Field *field = GetField(number); + PP_CHECK(field) << "No field for " << number; + PP_CHECK(field->type == type) << "For field " << number << " wanted type " << FieldTypeDebugString(type) << " but found " << FieldTypeDebugString(field->type); + return field; + } + + int32_t Message::GetInt32(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_UINT32); + uint32_t first_value = (*(field->value.v_uint32))[0]; + int32_t zig_zag_decoded = static_cast((first_value >> 1) ^ (-(first_value & 1))); + return zig_zag_decoded; + } + + int64_t Message::GetInt64(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_UINT64); + uint64_t first_value = (*(field->value.v_uint64))[0]; + int64_t zig_zag_decoded = static_cast((first_value >> 1) ^ (-(first_value & 1))); + return zig_zag_decoded; + } + + uint32_t Message::GetUInt32(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_UINT32); + uint32_t first_value = (*(field->value.v_uint32))[0]; + return first_value; + } + + uint64_t Message::GetUInt64(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_UINT64); + uint64_t first_value = (*(field->value.v_uint64))[0]; + return first_value; + } + + int64_t Message::GetInt(int32_t number) + { + Field *field = GetField(number); + PP_CHECK(field) << "No field for " << number; + PP_CHECK((field->type == FIELD_UINT32) || (field->type == FIELD_UINT64)) + << "For field " << number << " wanted integer type but found " << FieldTypeDebugString(field->type); + switch (field->type) + { + case FIELD_UINT32: return GetInt32(number); break; + case FIELD_UINT64: return GetInt64(number); break; + default: + { + // Should never get here. + } + break; + } + // Should never get here. + return 0; + } + + bool Message::GetBool(int32_t number) + { + return (GetInt(number) != 0); + } + + float Message::GetFloat(int32_t number) + { + uint32_t int_value = GetUInt32(number); + float float_value = *(bit_cast(&int_value)); + return float_value; + } + + double Message::GetDouble(int32_t number) + { + uint64_t int_value = GetUInt64(number); + return *(bit_cast(&int_value)); + } + + std::pair Message::GetBytes(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_BYTES); + std::pair first_value = (*(field->value.v_bytes))[0]; + return first_value; + } + + std::string Message::GetString(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_BYTES); + std::pair first_value = (*(field->value.v_bytes))[0]; + std::string result(first_value.first, first_value.first + first_value.second); + return result; + } + + Message *Message::GetMessage(int32_t number) + { + Field *field = GetFieldAndCheckType(number, FIELD_BYTES); + Message *cached_message = field->cached_messages->at(0); + if (!cached_message) + { + std::pair first_value = (*(field->value.v_bytes))[0]; + cached_message = new Message(copy_arrays); + cached_message->ParseFromBytes(first_value.first, first_value.second); + field->cached_messages->at(0) = cached_message; + } + return cached_message; + } + + std::vector Message::GetInt32Array(int32_t number) + { + std::vector raw_array = GetUInt64Array(number); + std::vector result; + result.reserve(raw_array.size()); + for (uint64_t raw_value : raw_array) + { + int32_t zig_zag_decoded = static_cast((raw_value >> 1) ^ (-(raw_value & 1))); + result.push_back(zig_zag_decoded); + } + return result; + } + + std::vector Message::GetInt64Array(int32_t number) + { + std::vector raw_array = GetUInt64Array(number); + std::vector result; + result.reserve(raw_array.size()); + for (uint64_t raw_value : raw_array) + { + int64_t zig_zag_decoded = static_cast((raw_value >> 1) ^ (-(raw_value & 1))); + result.push_back(zig_zag_decoded); + } + return result; + } + + std::vector Message::GetUInt32Array(int32_t number) + { + std::vector raw_array = GetUInt64Array(number); + std::vector result; + result.reserve(raw_array.size()); + for (uint64_t raw_value : raw_array) + { + result.push_back(static_cast(raw_value)); + } + return result; + } + + std::vector Message::GetUInt64Array(int32_t number) + { + std::vector result; + Field *field = GetField(number); + if (!field) + { + return result; + } + if (field->type == FIELD_UINT64) + { + result.reserve(field->value.v_uint64->size()); + for (uint64_t value : *field->value.v_uint64) + { + result.push_back(static_cast(value)); + } + } + else if (field->type == FIELD_UINT32) + { + result.reserve(field->value.v_uint32->size()); + for (uint32_t value : *field->value.v_uint32) + { + result.push_back(static_cast(value)); + } + } + else if (field->type == FIELD_BYTES) + { + for (std::pair data_info : *field->value.v_bytes) + { + uint8_t *current = data_info.first; + size_t remaining = data_info.second; + while (remaining > 0) + { + const uint64_t varint = ReadVarInt(¤t, &remaining); + result.push_back(static_cast(varint)); + } + } + } + else + { + PP_LOG(ERROR) << "Expected field type UINT32, UINT64, or BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + + std::vector Message::GetBoolArray(int32_t number) + { + std::vector raw_array = GetUInt64Array(number); + std::vector result; + result.reserve(raw_array.size()); + for (uint64_t raw_value : raw_array) + { + result.push_back(raw_value != 0); + } + return result; + } + + std::vector Message::GetFloatArray(int32_t number) + { + std::vector result; + Field *field = GetField(number); + if (!field) + { + return result; + } + if (field->type == FIELD_UINT32) + { + result.reserve(field->value.v_uint32->size()); + for (uint32_t value : *field->value.v_uint32) + { + result.push_back(bit_cast(value)); + } + } + else if (field->type == FIELD_BYTES) + { + for (std::pair data_info : *field->value.v_bytes) + { + uint8_t *current = data_info.first; + size_t remaining = data_info.second; + while (remaining > 0) + { + const uint64_t varint = ReadVarInt(¤t, &remaining); + const uint32_t varint32 = static_cast(varint & 0xffffffff); + result.push_back(bit_cast(varint32)); + } + } + } + else + { + PP_LOG(ERROR) << "Expected field type UINT32 or BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + + std::vector Message::GetDoubleArray(int32_t number) + { + std::vector result; + Field *field = GetField(number); + if (!field) + { + return result; + } + if (field->type == FIELD_UINT64) + { + result.reserve(field->value.v_uint64->size()); + for (uint64_t value : *field->value.v_uint64) + { + result.push_back(bit_cast(value)); + } + } + else if (field->type == FIELD_BYTES) + { + for (std::pair data_info : *field->value.v_bytes) + { + uint8_t *current = data_info.first; + size_t remaining = data_info.second; + while (remaining > 0) + { + const uint64_t varint = ReadVarInt(¤t, &remaining); + result.push_back(bit_cast(varint)); + } + } + } + else + { + PP_LOG(ERROR) << "Expected field type UINT64 or BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + + std::vector> Message::GetByteArray(int32_t number) + { + std::vector> result; + Field *field = GetField(number); + if (!field) + { + return result; + } + if (field->type == FIELD_BYTES) + { + result.reserve(field->value.v_bytes->size()); + for (std::pair data_info : *field->value.v_bytes) + { + result.push_back(data_info); + } + } + else + { + PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + + std::vector Message::GetStringArray(int32_t number) + { + std::vector result; + Field *field = GetField(number); + if (!field) + return result; + if (field->type == FIELD_BYTES) + { + result.reserve(field->value.v_bytes->size()); + for (std::pair data_info : *field->value.v_bytes) + { + result.push_back(std::string(data_info.first, data_info.first + data_info.second)); + } + } + else + { + PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + + std::vector Message::GetMessageArray(int32_t number) + { + std::vector result; + Field *field = GetField(number); + if (!field) + return result; + + if (field->type == FIELD_BYTES) + { + result.reserve(field->value.v_bytes->size()); + for (size_t i = 0; i < field->value.v_bytes->size(); ++i) + { + Message *cached_message = field->cached_messages->at(i); + if (!cached_message) + { + std::pair value = field->value.v_bytes->at(i); + cached_message = new Message(copy_arrays); + cached_message->ParseFromBytes(value.first, value.second); + field->cached_messages->at(i) = cached_message; + } + result.push_back(cached_message); + } + } + else + { + PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); + } + return result; + } + +} // namespace picoproto diff --git a/qv2ray/v3/components/GeositeReader/picoproto.hpp b/qv2ray/v3/components/GeositeReader/picoproto.hpp new file mode 100644 index 0000000..98cd305 --- /dev/null +++ b/qv2ray/v3/components/GeositeReader/picoproto.hpp @@ -0,0 +1,213 @@ +/* Copyright 2016 Pete Warden. All Rights Reserved. + + 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. + ==============================================================================*/ + +/* + See the README for full details, but this module lets you read in protobuf + encoded files with a minimal code footprint. + + It doesn't create classes for each kind of message it encounters, it just has a + single Message interface that lets you access the members of the protobuf as a + key/value store. This loses a lot of the convenience of type-checked classes, + but it does mean that very little code is needed to access data from files. + + As a simple example, if you had read a `bytes_size` long file into `bytes` that + contained a TensorFlow GraphDef proto: + + Message graph_def; + graph_def.ParseFromBytes(bytes, bytes_size); + + You can then access the different fields of the GraphDef using the field + numbers assigned in the .proto file: + + std::vector nodes = graph_def.GetMessageArray(1); + + One big difference between this minimal approach and normal protobufs is that + the calling code has to already know the field number and type of any members + it's trying to access. Here I know that the `node` field is number 1, and that + it should contain a repeated list of NodeDefs. Since they are not primitive + types like numbers or strings, they are accessed as an array of Messages. + + Here are the design goals of this module: + - Keep the code size tiny (single-digit kilobytes on most platforms). + - Minimize memory usage (for example allow in-place references to byte data). + - Provide a simple, readable implementation that can be ported easily. + - Deserialize all saved protobuf files into a usable representation. + - No dependencies other than the standard C++ library. + - No build-time support (e.g. protoc) required. + + Here's what it's explicitly not offering: + - Providing a readable and transparent way of accessing serialized data. + - Saving out data to protobuf format. + +*/ + +#ifndef INCLUDE_PICOPROTO_H +#define INCLUDE_PICOPROTO_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// To keep dependencies minimal, some bare-bones macros to make logging easier. +#define PP_LOG(X) PP_LOG_##X +#define PP_LOG_INFO std::cerr << __FILE__ << ":" << __LINE__ << " - INFO: " +#define PP_LOG_WARN std::cerr << __FILE__ << ":" << __LINE__ << " - WARN: " +#define PP_LOG_ERROR std::cerr << __FILE__ << ":" << __LINE__ << " - ERROR: " +#define PP_CHECK(X) \ + if (!(X)) \ + PP_LOG(ERROR) << "PP_CHECK(" << #X << ") failed. " + +namespace picoproto +{ + + // These roughly correspond to the wire types used to save data in protobuf + // files. The best reference to understand the full format is: + // https://developers.google.com/protocol-buffers/docs/encoding + // Because we don't know the bit-depth of VarInts, they're always stored as + // uint64 values, which is why there's no specific type for them. + enum FieldType + { + FIELD_UNSET, + FIELD_UINT32, + FIELD_UINT64, + FIELD_BYTES, + }; + + // Gives a readable name for the field type for logging purposes. + std::string FieldTypeDebugString(enum FieldType type); + + // Forward declare the main message class, since fields can contain them. + class Message; + + // Fields are the building blocks of messages. They contain the values for each + // data member, and handle all the allocation and deallocation of storage. + // It's unlikely you'll want to access this class directly, since you'll + // normally want to use Message below to pull typed values. + class Field + { + public: + // You need to specify the type of a Field on creation, so that the right + // storage can be set up for the values. You also need to indicate whether the + // underlying memory will be around for the lifetime of the message (in which + // case no copies are needed) or whether the class should make copies and take + // ownership in case the data goes away. + Field(FieldType type, bool owns_data); + Field(const Field &other); + ~Field(); + + enum FieldType type; + // I know, this isn't very OOP, but the simplicity of keeping track of a type + // and deciding how to initialize and access the data based on that persuaded + // me this was the best approach. The `value` member contains whatever data + // the field should be holding. + union + { + std::vector *v_uint32; + std::vector *v_uint64; + std::vector> *v_bytes; + } value; + // One of the drawbacks of not requiring .proto files ahead of time is that I + // don't know if a length-delimited field contains raw bytes, strings, or + // sub-messages. The only time we know that a field should be interpreted as a + // message is when client code requests it in that form. Because parsing can + // be costly, here we cache the results of any such calls for subsequent + // accesses. + std::vector *cached_messages; + // If this is set, then the object will allocate its own storage for + // length-delimited values, and copy from the input stream. If you know the + // underlying data will be around for the lifetime of the message, you can + // save memory and copies by leaving this as false. + bool owns_data; + }; + + // The main interface for loading and accessing serialized protobuf data. + class Message + { + public: + // If you're not sure about the lifetime of any binary data you're reading + // from, just call this default constructor. + Message(); + // In the case when you're sure the lifetime of the byte stream you'll be + // decoding is longer than the lifetime of the message, you can set + // copy_arrays to false. This is especially useful if you have a memory + // mapped file to read from containing large binary blobs, since you'll skip + // a lot of copying and extra allocation. + Message(bool copy_arrays); + Message(const Message &other); + ~Message(); + + // Populates fields with all of the data from this stream of bytes. + // You can call this repeatedly with new messages, and the results will be + // merged together. + bool ParseFromBytes(uint8_t *binary, size_t binary_size); + + // These are the accessor functions if you're expecting exactly one value in a + // field. As discussed above, the burden is on the client code to know the + // field number and type of each member it's trying to access, and so pick the + // correct accessor function. + // If the field isn't present, this will raise an error, so if it's optional + // you should use the array accessors below. + int32_t GetInt32(int32_t number); + int64_t GetInt64(int32_t number); + uint32_t GetUInt32(int32_t number); + uint64_t GetUInt64(int32_t number); + int64_t GetInt(int32_t number); + bool GetBool(int32_t number); + float GetFloat(int32_t number); + double GetDouble(int32_t number); + std::pair GetBytes(int32_t number); + std::string GetString(int32_t number); + Message *GetMessage(int32_t number); + + // If you're not sure if a value will be present, or if it is repeated, you + // should call these array functions. If no such field has been seen, then the + // result will be an empty vector, otherwise you'll get back one or more + // entries. + std::vector GetInt32Array(int32_t number); + std::vector GetInt64Array(int32_t number); + std::vector GetUInt32Array(int32_t number); + std::vector GetUInt64Array(int32_t number); + std::vector GetBoolArray(int32_t number); + std::vector GetFloatArray(int32_t number); + std::vector GetDoubleArray(int32_t number); + std::vector> GetByteArray(int32_t number); + std::vector GetStringArray(int32_t number); + std::vector GetMessageArray(int32_t number); + + // It's unlikely you'll want to access fields directly, but here's an escape + // hatch in case you do have to manipulate them more directly. + Field *GetField(int32_t number); + + private: + // Inserts a new field, updating all the internal data structures. + Field *AddField(int32_t number, enum FieldType type); + + Field *GetFieldAndCheckType(int32_t number, enum FieldType type); + + // Maps from a field number to an index in the `fields` vector. + std::map field_map; + // The core list of fields that have been parsed. + std::vector fields; + bool copy_arrays; + }; + +} // namespace picoproto + +#endif // INCLUDE_PICOPROTO_H diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp index 45cecb3..316d062 100644 --- a/sub/GroupUpdater.cpp +++ b/sub/GroupUpdater.cpp @@ -1,4 +1,4 @@ -#include "qv2ray/utils/HTTPRequestHelper.hpp" +#include "qv2ray/v2/utils/HTTPRequestHelper.hpp" #include "db/Database.hpp" #include "db/ProfileFilter.hpp" diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index 8f94e61..29e7db4 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -192,7 +192,7 @@ Copy profile share links - 复制所有配置的分享链接 + 复制分组内配置的分享链接 Copied @@ -200,7 +200,7 @@ Copy profile share links (Nekoray) - 复制所有配置的分享链接 (Nekoray) + 复制分组内配置的分享链接 (Nekoray) @@ -293,7 +293,7 @@ DialogHotkey - Hot key + Hotkey 热键 @@ -745,8 +745,8 @@ 停止 - Routes - 路由 + Routing VPN Settings + 路由 VPN 设置 Add profile from clipboard @@ -924,7 +924,7 @@ End: %2 Remove Unavailable - 删除不可用 + 删除不可用的配置 Settings @@ -959,8 +959,8 @@ End: %2 删除 [ Delete ] - Hot key - 热键 + Hotkey Settings + 热键设置 QR Code and link @@ -987,12 +987,12 @@ End: %2 加载路由规则并应用: %1 - Copy links of selected - 复制选中的分享链接 + Copy links of selected [ Ctrl+C ] + 批量复制选中项目的分享链接 [ Ctrl+C ] Copied %1 item(s) - 复制了%1 个项目 + 复制了 %1 个项目 New profile from clipboard @@ -1012,7 +1012,7 @@ End: %2 Delete Repeat - 删除重复 + 删除重复的配置 Select All diff --git a/ui/dialog_basic_settings.cpp b/ui/dialog_basic_settings.cpp index 238213f..2f44068 100644 --- a/ui/dialog_basic_settings.cpp +++ b/ui/dialog_basic_settings.cpp @@ -1,7 +1,7 @@ #include "dialog_basic_settings.h" #include "ui_dialog_basic_settings.h" -#include "qv2ray/ui/widgets/editors/w_JsonEditor.hpp" +#include "qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "ui/ThemeManager.hpp" #include "main/GuiUtils.hpp" #include "main/NekoRay.hpp" diff --git a/ui/dialog_hotkey.ui b/ui/dialog_hotkey.ui index ba055b0..d63ed4e 100644 --- a/ui/dialog_hotkey.ui +++ b/ui/dialog_hotkey.ui @@ -11,7 +11,7 @@ - Hot key + Hotkey diff --git a/ui/dialog_manage_routes.cpp b/ui/dialog_manage_routes.cpp index 5e1e24c..615aafb 100644 --- a/ui/dialog_manage_routes.cpp +++ b/ui/dialog_manage_routes.cpp @@ -1,7 +1,8 @@ #include "dialog_manage_routes.h" #include "ui_dialog_manage_routes.h" -#include "qv2ray/ui/widgets/editors/w_JsonEditor.hpp" +#include "qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" +#include "qv2ray/v3/components/GeositeReader/GeositeReader.hpp" #include "main/GuiUtils.hpp" #include @@ -59,14 +60,23 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : builtInSchemesMenu->addActions(this->getBuiltInSchemes()); ui->preset->setMenu(builtInSchemesMenu); + QString geoipFn = QApplication::applicationDirPath() + "/geoip.dat"; + QString geositeFn = QApplication::applicationDirPath() + +"/geosite.dat"; + if (!NekoRay::dataStore->v2ray_asset_dir.isEmpty()) { + geoipFn = NekoRay::dataStore->v2ray_asset_dir + "/geoip.dat"; + geositeFn = NekoRay::dataStore->v2ray_asset_dir + "/geosite.dat"; + } // - directDomainTxt = new AutoCompleteTextEdit("geosite", {}, this); - proxyDomainTxt = new AutoCompleteTextEdit("geosite", {}, this); - blockDomainTxt = new AutoCompleteTextEdit("geosite", {}, this); + const auto sourceStringsDomain = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geoipFn); + directDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); + proxyDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); + blockDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); // - directIPTxt = new AutoCompleteTextEdit("geoip", {}, this); - proxyIPTxt = new AutoCompleteTextEdit("geoip", {}, this); - blockIPTxt = new AutoCompleteTextEdit("geoip", {}, this); + const auto sourceStringsIP = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geositeFn); + qDebug() << sourceStringsIP; + directIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); + proxyIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); + blockIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); // ui->directTxtLayout->addWidget(directDomainTxt, 0, 0); ui->proxyTxtLayout->addWidget(proxyDomainTxt, 0, 0); diff --git a/ui/dialog_manage_routes.h b/ui/dialog_manage_routes.h index a07eb5e..cb09531 100644 --- a/ui/dialog_manage_routes.h +++ b/ui/dialog_manage_routes.h @@ -3,7 +3,7 @@ #include #include -#include "qv2ray/ui/QvAutoCompleteTextEdit.hpp" +#include "qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp" #include "main/NekoRay.hpp" QT_BEGIN_NAMESPACE diff --git a/ui/edit/dialog_edit_group.ui b/ui/edit/dialog_edit_group.ui index d74d021..3e581cb 100644 --- a/ui/edit/dialog_edit_group.ui +++ b/ui/edit/dialog_edit_group.ui @@ -119,7 +119,7 @@ - + diff --git a/ui/edit/dialog_edit_profile.cpp b/ui/edit/dialog_edit_profile.cpp index 601b5fb..cbaa44b 100644 --- a/ui/edit/dialog_edit_profile.cpp +++ b/ui/edit/dialog_edit_profile.cpp @@ -10,7 +10,8 @@ #include "ui/edit/edit_custom.h" #include "fmt/includes.h" -#include "qv2ray/ui/widgets/editors/w_JsonEditor.hpp" + +#include "qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "main/GuiUtils.hpp" #include diff --git a/ui/edit/edit_custom.cpp b/ui/edit/edit_custom.cpp index 63a7781..c87876f 100644 --- a/ui/edit/edit_custom.cpp +++ b/ui/edit/edit_custom.cpp @@ -1,7 +1,7 @@ #include "edit_custom.h" #include "ui_edit_custom.h" -#include "qv2ray/ui/widgets/editors/w_JsonEditor.hpp" +#include "qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "fmt/CustomBean.hpp" EditCustom::EditCustom(QWidget *parent) : diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 6be63eb..7f1cd96 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -16,12 +16,12 @@ #include "3rdparty/qrcodegen.hpp" #include "3rdparty/VT100Parser.hpp" -#include "qv2ray/ui/LogHighlighter.hpp" +#include "qv2ray/v2/ui/LogHighlighter.hpp" #ifndef NKR_NO_EXTERNAL #include "3rdparty/ZxingQtReader.hpp" -#include "qv2ray/components/proxy/QvProxyConfigurator.hpp" +#include "qv2ray/v2/components/proxy/QvProxyConfigurator.hpp" #endif @@ -254,6 +254,7 @@ MainWindow::MainWindow(QWidget *parent) // ui->menu_program_preference->addActions(ui->menu_preferences->actions()); connect(ui->menu_add_from_clipboard2, &QAction::triggered, ui->menu_add_from_clipboard, &QAction::trigger); + connect(shortcut_ctrl_c, &QShortcut::activated, ui->menu_copy_links, &QAction::trigger); connect(shortcut_ctrl_v, &QShortcut::activated, ui->menu_add_from_clipboard, &QAction::trigger); // connect(ui->menu_program, &QMenu::aboutToShow, this, [=]() { @@ -938,6 +939,7 @@ void MainWindow::on_menu_copy_links_triggered() { for (const auto &ent: ents) { links += ent->bean->ToShareLink(); } + if (links.length() == 0) return; QApplication::clipboard()->setText(links.join("\n")); MessageBoxInfo("NekoRay", tr("Copied %1 item(s)").arg(links.length())); } diff --git a/ui/mainwindow.h b/ui/mainwindow.h index f6c3a7d..2e4c18d 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -110,6 +110,7 @@ private: Ui::MainWindow *ui; QSystemTrayIcon *tray; QShortcut *shortcut_ctrl_f = new QShortcut(QKeySequence("Ctrl+F"), this); + QShortcut *shortcut_ctrl_c = new QShortcut(QKeySequence("Ctrl+C"), this); QShortcut *shortcut_ctrl_v = new QShortcut(QKeySequence("Ctrl+V"), this); QShortcut *shortcut_esc = new QShortcut(QKeySequence("Esc"), this); // diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 6cebde4..8dec535 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -498,8 +498,8 @@ Preferences - + @@ -582,7 +582,7 @@ - Routes + Routing VPN Settings @@ -715,7 +715,7 @@ - Hot key + Hotkey Settings @@ -746,7 +746,7 @@ - Copy links of selected + Copy links of selected [ Ctrl+C ]