mirror of
https://github.com/MatsuriDayo/nekoray.git
synced 2025-12-24 10:33:18 +08:00
你妈死了
This commit is contained in:
parent
adef6cd4af
commit
c4840c5b69
1
3rdparty/QHotkey
vendored
1
3rdparty/QHotkey
vendored
@ -1 +0,0 @@
|
||||
Subproject commit 52e25acf221e5ac86ce648f6922620fb2d6a7121
|
||||
39
3rdparty/QThreadCreateThread.hpp
vendored
39
3rdparty/QThreadCreateThread.hpp
vendored
@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <QThread>
|
||||
|
||||
// FOR OLD QT
|
||||
|
||||
class QThreadCreateThread : public QThread {
|
||||
public:
|
||||
explicit QThreadCreateThread(std::future<void> &&future)
|
||||
: m_future(std::move(future)) {
|
||||
// deleteLater
|
||||
connect(this, &QThread::finished, this, &QThread::deleteLater);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override {
|
||||
m_future.get();
|
||||
}
|
||||
|
||||
std::future<void> m_future;
|
||||
};
|
||||
|
||||
inline QThread *createThreadImpl(std::future<void> &&future) {
|
||||
return new QThreadCreateThread(std::move(future));
|
||||
}
|
||||
|
||||
template<typename Function, typename... Args>
|
||||
QThread *createQThread(Function &&f, Args &&... args) {
|
||||
using DecayedFunction = typename std::decay<Function>::type;
|
||||
auto threadFunction =
|
||||
[f = static_cast<DecayedFunction>(std::forward<Function>(f))](auto &&... largs) mutable -> void {
|
||||
(void) std::invoke(std::move(f), std::forward<decltype(largs)>(largs)...);
|
||||
};
|
||||
|
||||
return createThreadImpl(std::async(std::launch::deferred,
|
||||
std::move(threadFunction),
|
||||
std::forward<Args>(args)...));
|
||||
}
|
||||
22
3rdparty/QtExtKeySequenceEdit.cpp
vendored
22
3rdparty/QtExtKeySequenceEdit.cpp
vendored
@ -1,22 +0,0 @@
|
||||
#include "QtExtKeySequenceEdit.h"
|
||||
|
||||
QtExtKeySequenceEdit::QtExtKeySequenceEdit(QWidget *parent)
|
||||
: QKeySequenceEdit(parent) {
|
||||
}
|
||||
|
||||
QtExtKeySequenceEdit::~QtExtKeySequenceEdit() {
|
||||
}
|
||||
|
||||
void QtExtKeySequenceEdit::keyPressEvent(QKeyEvent *pEvent) {
|
||||
QKeySequenceEdit::keyPressEvent(pEvent);
|
||||
|
||||
QKeySequence keySeq = keySequence();
|
||||
if (keySeq.count() <= 0) {
|
||||
return;
|
||||
}
|
||||
int key = keySeq[0];
|
||||
if (key == Qt::Key_Backspace || key == Qt::Key_Delete) {
|
||||
key = 0;
|
||||
}
|
||||
setKeySequence(key);
|
||||
}
|
||||
11
3rdparty/QtExtKeySequenceEdit.h
vendored
11
3rdparty/QtExtKeySequenceEdit.h
vendored
@ -1,11 +0,0 @@
|
||||
#include <QKeySequenceEdit>
|
||||
|
||||
class QtExtKeySequenceEdit : public QKeySequenceEdit {
|
||||
public:
|
||||
QtExtKeySequenceEdit(QWidget *parent);
|
||||
|
||||
~QtExtKeySequenceEdit();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *pEvent);
|
||||
};
|
||||
98
3rdparty/RunGuard.hpp
vendored
98
3rdparty/RunGuard.hpp
vendored
@ -1,98 +0,0 @@
|
||||
#ifndef RUNGUARD_H
|
||||
#define RUNGUARD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
class RunGuard {
|
||||
public:
|
||||
RunGuard(const QString &key);
|
||||
|
||||
~RunGuard();
|
||||
|
||||
bool isAnotherRunning(quint64 *data_out);
|
||||
|
||||
bool tryToRun(quint64 *data_in);
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
const QString key;
|
||||
const QString memLockKey;
|
||||
const QString sharedmemKey;
|
||||
|
||||
QSharedMemory sharedMem;
|
||||
QSystemSemaphore memLock;
|
||||
|
||||
Q_DISABLE_COPY(RunGuard)
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
QString generateKeyHash(const QString &key, const QString &salt) {
|
||||
QByteArray data;
|
||||
|
||||
data.append(key.toUtf8());
|
||||
data.append(salt.toUtf8());
|
||||
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RunGuard::RunGuard(const QString &key)
|
||||
: key(key), memLockKey(generateKeyHash(key, "_memLockKey")), sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) {
|
||||
memLock.acquire();
|
||||
{
|
||||
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
|
||||
fix.attach();
|
||||
}
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
RunGuard::~RunGuard() {
|
||||
release();
|
||||
}
|
||||
|
||||
bool RunGuard::isAnotherRunning(quint64 *data_out) {
|
||||
if (sharedMem.isAttached())
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool isRunning = sharedMem.attach();
|
||||
if (isRunning) {
|
||||
if (data_out != nullptr) {
|
||||
memcpy(data_out, sharedMem.data(), sizeof(quint64));
|
||||
}
|
||||
sharedMem.detach();
|
||||
}
|
||||
memLock.release();
|
||||
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
bool RunGuard::tryToRun(quint64 *data_in) {
|
||||
memLock.acquire();
|
||||
const bool result = sharedMem.create(sizeof(quint64));
|
||||
if (result) memcpy(sharedMem.data(), data_in, sizeof(quint64));
|
||||
memLock.release();
|
||||
|
||||
if (!result) {
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RunGuard::release() {
|
||||
memLock.acquire();
|
||||
if (sharedMem.isAttached())
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
#endif // RUNGUARD_H
|
||||
22
3rdparty/VT100Parser.hpp
vendored
22
3rdparty/VT100Parser.hpp
vendored
@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
inline QString cleanVT100String(const QString &in) {
|
||||
QString out;
|
||||
bool in_033 = false;
|
||||
for (auto &&chr: in) {
|
||||
if (chr == '\033') {
|
||||
in_033 = true;
|
||||
continue;
|
||||
}
|
||||
if (in_033) {
|
||||
if (chr == 'm') {
|
||||
in_033 = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
out += chr;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
105
3rdparty/WinCommander.cpp
vendored
105
3rdparty/WinCommander.cpp
vendored
@ -1,105 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)
|
||||
** Contact: code@updatenode.com
|
||||
**
|
||||
** This file is part of the UpdateNode Client.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial UpdateNode license may use this file
|
||||
** under the terms of the the Apache License, Version 2.0
|
||||
** Full license description file: LICENSE.COM
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation. Please review the following information to ensure the
|
||||
** GNU General Public License version 3.0 requirements will be met:
|
||||
** http://www.gnu.org/copyleft/gpl.html.
|
||||
** Full license description file: LICENSE.GPL
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "WinCommander.hpp"
|
||||
|
||||
#include <QSysInfo>
|
||||
#include <QDir>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <sddl.h>
|
||||
#define MAX_KEY_LENGTH 255
|
||||
#define MAX_VALUE_NAME 16383
|
||||
#endif
|
||||
|
||||
|
||||
/*!
|
||||
Executes a command elevated specified by \apath , using paramters \aparameters.
|
||||
\n
|
||||
Parameter /aaWait decides if the function should return immediatelly after it's\n
|
||||
execution or wait for the exit of the launched process
|
||||
\n
|
||||
Returns the return value of the executed command
|
||||
*/
|
||||
uint WinCommander::runProcessElevated(const QString &path,
|
||||
const QStringList ¶meters,
|
||||
const QString &workingDir,
|
||||
int nShow, bool aWait) {
|
||||
uint result = 0;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString params;
|
||||
HWND hwnd = NULL;
|
||||
LPCTSTR pszPath = (LPCTSTR)path.utf16();
|
||||
foreach(QString item, parameters)
|
||||
params += "\"" + item + "\" ";
|
||||
|
||||
LPCTSTR pszParameters = (LPCTSTR)params.utf16();
|
||||
QString dir;
|
||||
if (workingDir.count() == 0)
|
||||
dir = QDir::toNativeSeparators(QDir::currentPath());
|
||||
else
|
||||
dir = QDir::toNativeSeparators(workingDir);
|
||||
LPCTSTR pszDirectory = (LPCTSTR)dir.utf16();
|
||||
|
||||
SHELLEXECUTEINFO shex;
|
||||
DWORD dwCode = 0;
|
||||
|
||||
ZeroMemory(&shex, sizeof(shex));
|
||||
|
||||
shex.cbSize = sizeof(shex);
|
||||
shex.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shex.hwnd = hwnd;
|
||||
shex.lpVerb = TEXT("runas");
|
||||
shex.lpFile = pszPath;
|
||||
shex.lpParameters = pszParameters;
|
||||
shex.lpDirectory = pszDirectory;
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
||||
shex.nShow = nShow;
|
||||
|
||||
ShellExecuteEx(&shex);
|
||||
if (shex.hProcess)
|
||||
{
|
||||
if(aWait)
|
||||
{
|
||||
WaitForSingleObject(shex.hProcess, INFINITE );
|
||||
GetExitCodeProcess(shex.hProcess, &dwCode);
|
||||
}
|
||||
CloseHandle (shex.hProcess) ;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
result = (uint)dwCode;
|
||||
#else
|
||||
Q_UNUSED(path);
|
||||
Q_UNUSED(parameters);
|
||||
Q_UNUSED(workingDir);
|
||||
Q_UNUSED(aWait);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
41
3rdparty/WinCommander.hpp
vendored
41
3rdparty/WinCommander.hpp
vendored
@ -1,41 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)
|
||||
** Contact: code@updatenode.com
|
||||
**
|
||||
** This file is part of the UpdateNode Client.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial UpdateNode license may use this file
|
||||
** under the terms of the the Apache License, Version 2.0
|
||||
** Full license description file: LICENSE.COM
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation. Please review the following information to ensure the
|
||||
** GNU General Public License version 3.0 requirements will be met:
|
||||
** http://www.gnu.org/copyleft/gpl.html.
|
||||
** Full license description file: LICENSE.GPL
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef WINCOMMANDER_H
|
||||
#define WINCOMMANDER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class WinCommander {
|
||||
public:
|
||||
static const int SW_HIDE = 0;
|
||||
static const int SW_NORMAL = 1;
|
||||
static const int SW_SHOWMINIMIZED = 2;
|
||||
|
||||
static uint runProcessElevated(const QString &path,
|
||||
const QStringList ¶meters = QStringList(),
|
||||
const QString &workingDir = QString(),
|
||||
int nShow = SW_SHOWMINIMIZED, bool aWait = true);
|
||||
};
|
||||
|
||||
#endif // WINCOMMANDER_H
|
||||
454
3rdparty/ZxingQtReader.hpp
vendored
454
3rdparty/ZxingQtReader.hpp
vendored
@ -1,454 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXing/ReadBarcode.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
#include "qscopeguard.h"
|
||||
#else
|
||||
#include <QScopeGuard>
|
||||
#endif
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QAbstractVideoFilter>
|
||||
#else
|
||||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
#endif
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
|
||||
// This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like.
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
//TODO: find a better way to export these enums to QML than to duplicate their definition
|
||||
// #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml
|
||||
#ifdef QT_QML_LIB
|
||||
enum class BarcodeFormat
|
||||
{
|
||||
None = 0, ///< Used as a return value if no valid barcode has been detected
|
||||
Aztec = (1 << 0), ///< Aztec
|
||||
Codabar = (1 << 1), ///< Codabar
|
||||
Code39 = (1 << 2), ///< Code39
|
||||
Code93 = (1 << 3), ///< Code93
|
||||
Code128 = (1 << 4), ///< Code128
|
||||
DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14
|
||||
DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED
|
||||
DataMatrix = (1 << 7), ///< DataMatrix
|
||||
EAN8 = (1 << 8), ///< EAN-8
|
||||
EAN13 = (1 << 9), ///< EAN-13
|
||||
ITF = (1 << 10), ///< ITF (Interleaved Two of Five)
|
||||
MaxiCode = (1 << 11), ///< MaxiCode
|
||||
PDF417 = (1 << 12), ///< PDF417 or
|
||||
QRCode = (1 << 13), ///< QR Code
|
||||
UPCA = (1 << 14), ///< UPC-A
|
||||
UPCE = (1 << 15), ///< UPC-E
|
||||
MicroQRCode = (1 << 16), ///< Micro QR Code
|
||||
|
||||
LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE,
|
||||
MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode,
|
||||
};
|
||||
|
||||
enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI };
|
||||
|
||||
#else
|
||||
using ZXing::BarcodeFormat;
|
||||
using ZXing::ContentType;
|
||||
#endif
|
||||
|
||||
using ZXing::DecodeHints;
|
||||
using ZXing::Binarizer;
|
||||
using ZXing::BarcodeFormats;
|
||||
|
||||
Q_ENUM_NS(BarcodeFormat)
|
||||
Q_ENUM_NS(ContentType)
|
||||
|
||||
template<typename T, typename = decltype(ZXing::ToString(T()))>
|
||||
QDebug operator<<(QDebug dbg, const T& v)
|
||||
{
|
||||
return dbg.noquote() << QString::fromStdString(ToString(v));
|
||||
}
|
||||
|
||||
class Position : public ZXing::Quadrilateral<QPoint>
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(QPoint topLeft READ topLeft)
|
||||
Q_PROPERTY(QPoint topRight READ topRight)
|
||||
Q_PROPERTY(QPoint bottomRight READ bottomRight)
|
||||
Q_PROPERTY(QPoint bottomLeft READ bottomLeft)
|
||||
|
||||
using Base = ZXing::Quadrilateral<QPoint>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
class Result : private ZXing::Result
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(BarcodeFormat format READ format)
|
||||
Q_PROPERTY(QString formatName READ formatName)
|
||||
Q_PROPERTY(QString text READ text)
|
||||
Q_PROPERTY(QByteArray bytes READ bytes)
|
||||
Q_PROPERTY(bool isValid READ isValid)
|
||||
Q_PROPERTY(ContentType contentType READ contentType)
|
||||
Q_PROPERTY(Position position READ position)
|
||||
|
||||
QString _text;
|
||||
QByteArray _bytes;
|
||||
Position _position;
|
||||
|
||||
public:
|
||||
Result() = default; // required for qmetatype machinery
|
||||
|
||||
explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) {
|
||||
_text = QString::fromStdString(ZXing::Result::text());
|
||||
_bytes = QByteArray(reinterpret_cast<const char*>(ZXing::Result::bytes().data()), Size(ZXing::Result::bytes()));
|
||||
auto& pos = ZXing::Result::position();
|
||||
auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); };
|
||||
_position = {qp(0), qp(1), qp(2), qp(3)};
|
||||
}
|
||||
|
||||
using ZXing::Result::isValid;
|
||||
|
||||
BarcodeFormat format() const { return static_cast<BarcodeFormat>(ZXing::Result::format()); }
|
||||
ContentType contentType() const { return static_cast<ContentType>(ZXing::Result::contentType()); }
|
||||
QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); }
|
||||
const QString& text() const { return _text; }
|
||||
const QByteArray& bytes() const { return _bytes; }
|
||||
const Position& position() const { return _position; }
|
||||
|
||||
// For debugging/development
|
||||
int runTime = 0;
|
||||
Q_PROPERTY(int runTime MEMBER runTime)
|
||||
};
|
||||
|
||||
inline QList<Result> QListResults(ZXing::Results&& zxres)
|
||||
{
|
||||
QList<Result> res;
|
||||
for (auto&& r : zxres)
|
||||
res.push_back(Result(std::move(r)));
|
||||
return res;
|
||||
}
|
||||
|
||||
inline QList<Result> ReadBarcodes(const QImage& img, const DecodeHints& hints = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
auto ImgFmtFromQImg = [](const QImage& img) {
|
||||
switch (img.format()) {
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_RGB32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
return ImageFormat::BGRX;
|
||||
#else
|
||||
return ImageFormat::XRGB;
|
||||
#endif
|
||||
case QImage::Format_RGB888: return ImageFormat::RGB;
|
||||
case QImage::Format_RGBX8888:
|
||||
case QImage::Format_RGBA8888: return ImageFormat::RGBX;
|
||||
case QImage::Format_Grayscale8: return ImageFormat::Lum;
|
||||
default: return ImageFormat::None;
|
||||
}
|
||||
};
|
||||
|
||||
auto exec = [&](const QImage& img) {
|
||||
return QListResults(ZXing::ReadBarcodes(
|
||||
{img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast<int>(img.bytesPerLine())}, hints));
|
||||
};
|
||||
|
||||
return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img);
|
||||
}
|
||||
|
||||
inline Result ReadBarcode(const QImage& img, const DecodeHints& hints = {})
|
||||
{
|
||||
auto res = ReadBarcodes(img, DecodeHints(hints).setMaxNumberOfSymbols(1));
|
||||
return !res.isEmpty() ? res.takeFirst() : Result();
|
||||
}
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
inline QList<Result> ReadBarcodes(const QVideoFrame& frame, const DecodeHints& hints = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
ImageFormat fmt = ImageFormat::None;
|
||||
int pixStride = 0;
|
||||
int pixOffset = 0;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#define FORMAT(F5, F6) QVideoFrame::Format_##F5
|
||||
#define FIRST_PLANE
|
||||
#else
|
||||
#define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6
|
||||
#define FIRST_PLANE 0
|
||||
#endif
|
||||
|
||||
switch (frame.pixelFormat()) {
|
||||
case FORMAT(ARGB32, ARGB8888):
|
||||
case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied):
|
||||
case FORMAT(RGB32, RGBX8888):
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::BGRX;
|
||||
#else
|
||||
fmt = ImageFormat::XRGB;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case FORMAT(BGRA32, BGRA8888):
|
||||
case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied):
|
||||
case FORMAT(BGR32, BGRX8888):
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::RGBX;
|
||||
#else
|
||||
fmt = ImageFormat::XBGR;
|
||||
#endif
|
||||
break;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
case QVideoFrame::Format_RGB24: fmt = ImageFormat::RGB; break;
|
||||
case QVideoFrame::Format_BGR24: fmt = ImageFormat::BGR; break;
|
||||
case QVideoFrame::Format_YUV444: fmt = ImageFormat::Lum, pixStride = 3; break;
|
||||
#else
|
||||
case QVideoFrameFormat::Format_P010:
|
||||
case QVideoFrameFormat::Format_P016: fmt = ImageFormat::Lum, pixStride = 1; break;
|
||||
#endif
|
||||
|
||||
case FORMAT(AYUV444, AYUV):
|
||||
case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied):
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 3;
|
||||
#else
|
||||
fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case FORMAT(YUV420P, YUV420P):
|
||||
case FORMAT(NV12, NV12):
|
||||
case FORMAT(NV21, NV21):
|
||||
case FORMAT(IMC1, IMC1):
|
||||
case FORMAT(IMC2, IMC2):
|
||||
case FORMAT(IMC3, IMC3):
|
||||
case FORMAT(IMC4, IMC4):
|
||||
case FORMAT(YV12, YV12): fmt = ImageFormat::Lum; break;
|
||||
case FORMAT(UYVY, UYVY): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
case FORMAT(YUYV, YUYV): fmt = ImageFormat::Lum, pixStride = 2; break;
|
||||
|
||||
case FORMAT(Y8, Y8): fmt = ImageFormat::Lum; break;
|
||||
case FORMAT(Y16, Y16): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
||||
case FORMAT(ABGR32, ABGR8888):
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::RGBX;
|
||||
#else
|
||||
fmt = ImageFormat::XBGR;
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
case FORMAT(YUV422P, YUV422P): fmt = ImageFormat::Lum; break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (fmt != ImageFormat::None) {
|
||||
auto img = frame; // shallow copy just get access to non-const map() function
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
if (!img.isValid() || !img.map(QAbstractVideoBuffer::ReadOnly)){
|
||||
#else
|
||||
if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){
|
||||
#endif
|
||||
qWarning() << "invalid QVideoFrame: could not map memory";
|
||||
return {};
|
||||
}
|
||||
QScopeGuard unmap([&] { img.unmap(); });
|
||||
|
||||
return QListResults(ZXing::ReadBarcodes(
|
||||
{img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, hints));
|
||||
}
|
||||
else {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
if (QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()) != QImage::Format_Invalid) {
|
||||
qWarning() << "unsupported QVideoFrame::pixelFormat";
|
||||
return {};
|
||||
}
|
||||
auto qimg = frame.image();
|
||||
#else
|
||||
auto qimg = frame.toImage();
|
||||
#endif
|
||||
if (qimg.format() != QImage::Format_Invalid)
|
||||
return ReadBarcodes(qimg, hints);
|
||||
qWarning() << "failed to convert QVideoFrame to QImage";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline Result ReadBarcode(const QVideoFrame& frame, const DecodeHints& hints = {})
|
||||
{
|
||||
auto res = ReadBarcodes(frame, DecodeHints(hints).setMaxNumberOfSymbols(1));
|
||||
return !res.isEmpty() ? res.takeFirst() : Result();
|
||||
}
|
||||
|
||||
#define ZQ_PROPERTY(Type, name, setter) \
|
||||
public: \
|
||||
Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \
|
||||
Type name() const noexcept { return DecodeHints::name(); } \
|
||||
Q_SLOT void setter(const Type& newVal) \
|
||||
{ \
|
||||
if (name() != newVal) { \
|
||||
DecodeHints::setter(newVal); \
|
||||
emit name##Changed(); \
|
||||
} \
|
||||
} \
|
||||
Q_SIGNAL void name##Changed();
|
||||
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
class BarcodeReader : public QAbstractVideoFilter, private DecodeHints
|
||||
#else
|
||||
class BarcodeReader : public QObject, private DecodeHints
|
||||
#endif
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {}
|
||||
#else
|
||||
BarcodeReader(QObject* parent = nullptr) : QObject(parent) {}
|
||||
#endif
|
||||
|
||||
// TODO: find out how to properly expose QFlags to QML
|
||||
// simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats)
|
||||
// results in the runtime error "can't assign int to formats"
|
||||
Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged)
|
||||
int formats() const noexcept
|
||||
{
|
||||
auto fmts = DecodeHints::formats();
|
||||
return *reinterpret_cast<int*>(&fmts);
|
||||
}
|
||||
Q_SLOT void setFormats(int newVal)
|
||||
{
|
||||
if (formats() != newVal) {
|
||||
DecodeHints::setFormats(static_cast<ZXing::BarcodeFormat>(newVal));
|
||||
emit formatsChanged();
|
||||
qDebug() << DecodeHints::formats();
|
||||
}
|
||||
}
|
||||
Q_SIGNAL void formatsChanged();
|
||||
|
||||
ZQ_PROPERTY(bool, tryRotate, setTryRotate)
|
||||
ZQ_PROPERTY(bool, tryHarder, setTryHarder)
|
||||
ZQ_PROPERTY(bool, tryDownscale, setTryDownscale)
|
||||
|
||||
public slots:
|
||||
ZXingQt::Result process(const QVideoFrame& image)
|
||||
{
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
|
||||
auto res = ReadBarcode(image, *this);
|
||||
|
||||
res.runTime = t.elapsed();
|
||||
|
||||
emit newResult(res);
|
||||
if (res.isValid())
|
||||
emit foundBarcode(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
signals:
|
||||
void newResult(ZXingQt::Result result);
|
||||
void foundBarcode(ZXingQt::Result result);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
public:
|
||||
QVideoFilterRunnable *createFilterRunnable() override;
|
||||
#else
|
||||
private:
|
||||
QVideoSink *_sink = nullptr;
|
||||
|
||||
public:
|
||||
void setVideoSink(QVideoSink* sink) {
|
||||
if (_sink == sink)
|
||||
return;
|
||||
|
||||
if (_sink)
|
||||
disconnect(_sink, nullptr, this, nullptr);
|
||||
|
||||
_sink = sink;
|
||||
connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process);
|
||||
}
|
||||
Q_PROPERTY(QVideoSink* videoSink WRITE setVideoSink)
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
class VideoFilterRunnable : public QVideoFilterRunnable
|
||||
{
|
||||
BarcodeReader* _filter = nullptr;
|
||||
|
||||
public:
|
||||
explicit VideoFilterRunnable(BarcodeReader* filter) : _filter(filter) {}
|
||||
|
||||
QVideoFrame run(QVideoFrame* input, const QVideoSurfaceFormat& /*surfaceFormat*/, RunFlags /*flags*/) override
|
||||
{
|
||||
_filter->process(*input);
|
||||
return *input;
|
||||
}
|
||||
};
|
||||
|
||||
inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable()
|
||||
{
|
||||
return new VideoFilterRunnable(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // QT_MULTIMEDIA_LIB
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE(ZXingQt::Position)
|
||||
Q_DECLARE_METATYPE(ZXingQt::Result)
|
||||
|
||||
#ifdef QT_QML_LIB
|
||||
|
||||
#include <QQmlEngine>
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
inline void registerQmlAndMetaTypes()
|
||||
{
|
||||
qRegisterMetaType<ZXingQt::BarcodeFormat>("BarcodeFormat");
|
||||
qRegisterMetaType<ZXingQt::ContentType>("ContentType");
|
||||
|
||||
// supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name
|
||||
// but then the qml side complains about "unregistered type"
|
||||
qRegisterMetaType<ZXingQt::Position>("Position");
|
||||
qRegisterMetaType<ZXingQt::Result>("Result");
|
||||
|
||||
qmlRegisterUncreatableMetaObject(
|
||||
ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only");
|
||||
qmlRegisterType<ZXingQt::BarcodeReader>("ZXing", 1, 0, "BarcodeReader");
|
||||
}
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
#endif // QT_QML_LIB
|
||||
92
3rdparty/base64.cpp
vendored
92
3rdparty/base64.cpp
vendored
@ -1,92 +0,0 @@
|
||||
#include "base64.h"
|
||||
|
||||
#ifndef qsizetype
|
||||
#define qsizetype size_t
|
||||
#endif
|
||||
|
||||
namespace Qt515Base64 {
|
||||
namespace {
|
||||
struct fromBase64_helper_result {
|
||||
qsizetype decodedLength;
|
||||
Base64DecodingStatus status;
|
||||
};
|
||||
|
||||
fromBase64_helper_result fromBase64_helper(const char *input, qsizetype inputSize,
|
||||
char *output /* may alias input */,
|
||||
Base64Options options) {
|
||||
fromBase64_helper_result result{0, Base64DecodingStatus::Ok};
|
||||
|
||||
unsigned int buf = 0;
|
||||
int nbits = 0;
|
||||
|
||||
qsizetype offset = 0;
|
||||
for (qsizetype i = 0; i < inputSize; ++i) {
|
||||
int ch = input[i];
|
||||
int d;
|
||||
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
d = ch - 'A';
|
||||
} else if (ch >= 'a' && ch <= 'z') {
|
||||
d = ch - 'a' + 26;
|
||||
} else if (ch >= '0' && ch <= '9') {
|
||||
d = ch - '0' + 52;
|
||||
} else if (ch == '+' && (options & Base64UrlEncoding) == 0) {
|
||||
d = 62;
|
||||
} else if (ch == '-' && (options & Base64UrlEncoding) != 0) {
|
||||
d = 62;
|
||||
} else if (ch == '/' && (options & Base64UrlEncoding) == 0) {
|
||||
d = 63;
|
||||
} else if (ch == '_' && (options & Base64UrlEncoding) != 0) {
|
||||
d = 63;
|
||||
} else {
|
||||
if (options & AbortOnBase64DecodingErrors) {
|
||||
if (ch == '=') {
|
||||
// can have 1 or 2 '=' signs, in both cases padding base64Size to
|
||||
// a multiple of 4. Any other case is illegal.
|
||||
if ((inputSize % 4) != 0) {
|
||||
result.status = Base64DecodingStatus::IllegalInputLength;
|
||||
return result;
|
||||
} else if ((i == inputSize - 1) ||
|
||||
(i == inputSize - 2 && input[++i] == '=')) {
|
||||
d = -1; // ... and exit the loop, normally
|
||||
} else {
|
||||
result.status = Base64DecodingStatus::IllegalPadding;
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
result.status = Base64DecodingStatus::IllegalCharacter;
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
d = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (d != -1) {
|
||||
buf = (buf << 6) | d;
|
||||
nbits += 6;
|
||||
if (nbits >= 8) {
|
||||
nbits -= 8;
|
||||
Q_ASSERT(offset < i);
|
||||
output[offset++] = buf >> nbits;
|
||||
buf &= (1 << nbits) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.decodedLength = offset;
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options) {
|
||||
const auto base64Size = base64.size();
|
||||
QByteArray result((base64Size * 3) / 4, Qt::Uninitialized);
|
||||
const auto base64result = fromBase64_helper(base64.data(),
|
||||
base64Size,
|
||||
const_cast<char *>(result.constData()),
|
||||
options);
|
||||
result.truncate(int(base64result.decodedLength));
|
||||
return {std::move(result), base64result.status};
|
||||
}
|
||||
} // namespace Qt515Base64
|
||||
47
3rdparty/base64.h
vendored
47
3rdparty/base64.h
vendored
@ -1,47 +0,0 @@
|
||||
#include <QByteArray>
|
||||
|
||||
namespace Qt515Base64 {
|
||||
enum Base64Option {
|
||||
Base64Encoding = 0,
|
||||
Base64UrlEncoding = 1,
|
||||
|
||||
KeepTrailingEquals = 0,
|
||||
OmitTrailingEquals = 2,
|
||||
|
||||
IgnoreBase64DecodingErrors = 0,
|
||||
AbortOnBase64DecodingErrors = 4,
|
||||
};
|
||||
Q_DECLARE_FLAGS(Base64Options, Base64Option)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Base64Options)
|
||||
|
||||
enum class Base64DecodingStatus {
|
||||
Ok,
|
||||
IllegalInputLength,
|
||||
IllegalCharacter,
|
||||
IllegalPadding,
|
||||
};
|
||||
|
||||
class FromBase64Result {
|
||||
public:
|
||||
QByteArray decoded;
|
||||
Base64DecodingStatus decodingStatus;
|
||||
|
||||
void swap(FromBase64Result &other) noexcept {
|
||||
qSwap(decoded, other.decoded);
|
||||
qSwap(decodingStatus, other.decodingStatus);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return decodingStatus == Base64DecodingStatus::Ok; }
|
||||
|
||||
#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(Q_QDOC)
|
||||
QByteArray &operator*() &noexcept { return decoded; }
|
||||
const QByteArray &operator*() const &noexcept { return decoded; }
|
||||
QByteArray &&operator*() &&noexcept { return std::move(decoded); }
|
||||
#else
|
||||
QByteArray &operator*() noexcept { return decoded; }
|
||||
const QByteArray &operator*() const noexcept { return decoded; }
|
||||
#endif
|
||||
};
|
||||
|
||||
FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options);
|
||||
} // namespace Qt515Base64
|
||||
11
3rdparty/fix_old_qt.h
vendored
11
3rdparty/fix_old_qt.h
vendored
@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
|
||||
inline QString qEnvironmentVariable(const char *varName) {
|
||||
return qgetenv(varName);
|
||||
}
|
||||
|
||||
#endif
|
||||
830
3rdparty/qrcodegen.cpp
vendored
830
3rdparty/qrcodegen.cpp
vendored
@ -1,830 +0,0 @@
|
||||
/*
|
||||
* QR Code generator library (C++)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
using std::int8_t;
|
||||
using std::uint8_t;
|
||||
using std::size_t;
|
||||
using std::vector;
|
||||
|
||||
|
||||
namespace qrcodegen {
|
||||
|
||||
/*---- Class QrSegment ----*/
|
||||
|
||||
QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) :
|
||||
modeBits(mode) {
|
||||
numBitsCharCount[0] = cc0;
|
||||
numBitsCharCount[1] = cc1;
|
||||
numBitsCharCount[2] = cc2;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::Mode::getModeBits() const {
|
||||
return modeBits;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::Mode::numCharCountBits(int ver) const {
|
||||
return numBitsCharCount[(ver + 7) / 17];
|
||||
}
|
||||
|
||||
|
||||
const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14);
|
||||
const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13);
|
||||
const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16);
|
||||
const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12);
|
||||
const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0);
|
||||
|
||||
|
||||
QrSegment QrSegment::makeBytes(const vector<uint8_t> &data) {
|
||||
if (data.size() > static_cast<unsigned int>(INT_MAX))
|
||||
throw std::length_error("Data too long");
|
||||
BitBuffer bb;
|
||||
for (uint8_t b : data)
|
||||
bb.appendBits(b, 8);
|
||||
return QrSegment(Mode::BYTE, static_cast<int>(data.size()), std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeNumeric(const char *digits) {
|
||||
BitBuffer bb;
|
||||
int accumData = 0;
|
||||
int accumCount = 0;
|
||||
int charCount = 0;
|
||||
for (; *digits != '\0'; digits++, charCount++) {
|
||||
char c = *digits;
|
||||
if (c < '0' || c > '9')
|
||||
throw std::domain_error("String contains non-numeric characters");
|
||||
accumData = accumData * 10 + (c - '0');
|
||||
accumCount++;
|
||||
if (accumCount == 3) {
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 10);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
if (accumCount > 0) // 1 or 2 digits remaining
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), accumCount * 3 + 1);
|
||||
return QrSegment(Mode::NUMERIC, charCount, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeAlphanumeric(const char *text) {
|
||||
BitBuffer bb;
|
||||
int accumData = 0;
|
||||
int accumCount = 0;
|
||||
int charCount = 0;
|
||||
for (; *text != '\0'; text++, charCount++) {
|
||||
const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text);
|
||||
if (temp == nullptr)
|
||||
throw std::domain_error("String contains unencodable characters in alphanumeric mode");
|
||||
accumData = accumData * 45 + static_cast<int>(temp - ALPHANUMERIC_CHARSET);
|
||||
accumCount++;
|
||||
if (accumCount == 2) {
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 11);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
if (accumCount > 0) // 1 character remaining
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 6);
|
||||
return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
vector<QrSegment> QrSegment::makeSegments(const char *text) {
|
||||
// Select the most efficient segment encoding automatically
|
||||
vector<QrSegment> result;
|
||||
if (*text == '\0'); // Leave result empty
|
||||
else if (isNumeric(text))
|
||||
result.push_back(makeNumeric(text));
|
||||
else if (isAlphanumeric(text))
|
||||
result.push_back(makeAlphanumeric(text));
|
||||
else {
|
||||
vector<uint8_t> bytes;
|
||||
for (; *text != '\0'; text++)
|
||||
bytes.push_back(static_cast<uint8_t>(*text));
|
||||
result.push_back(makeBytes(bytes));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeEci(long assignVal) {
|
||||
BitBuffer bb;
|
||||
if (assignVal < 0)
|
||||
throw std::domain_error("ECI assignment value out of range");
|
||||
else if (assignVal < (1 << 7))
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 8);
|
||||
else if (assignVal < (1 << 14)) {
|
||||
bb.appendBits(2, 2);
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 14);
|
||||
} else if (assignVal < 1000000L) {
|
||||
bb.appendBits(6, 3);
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 21);
|
||||
} else
|
||||
throw std::domain_error("ECI assignment value out of range");
|
||||
return QrSegment(Mode::ECI, 0, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment::QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt) :
|
||||
mode(&md),
|
||||
numChars(numCh),
|
||||
data(dt) {
|
||||
if (numCh < 0)
|
||||
throw std::domain_error("Invalid value");
|
||||
}
|
||||
|
||||
|
||||
QrSegment::QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt) :
|
||||
mode(&md),
|
||||
numChars(numCh),
|
||||
data(std::move(dt)) {
|
||||
if (numCh < 0)
|
||||
throw std::domain_error("Invalid value");
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::getTotalBits(const vector<QrSegment> &segs, int version) {
|
||||
int result = 0;
|
||||
for (const QrSegment &seg : segs) {
|
||||
int ccbits = seg.mode->numCharCountBits(version);
|
||||
if (seg.numChars >= (1L << ccbits))
|
||||
return -1; // The segment's length doesn't fit the field's bit width
|
||||
if (4 + ccbits > INT_MAX - result)
|
||||
return -1; // The sum will overflow an int type
|
||||
result += 4 + ccbits;
|
||||
if (seg.data.size() > static_cast<unsigned int>(INT_MAX - result))
|
||||
return -1; // The sum will overflow an int type
|
||||
result += static_cast<int>(seg.data.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool QrSegment::isNumeric(const char *text) {
|
||||
for (; *text != '\0'; text++) {
|
||||
char c = *text;
|
||||
if (c < '0' || c > '9')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool QrSegment::isAlphanumeric(const char *text) {
|
||||
for (; *text != '\0'; text++) {
|
||||
if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const QrSegment::Mode &QrSegment::getMode() const {
|
||||
return *mode;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::getNumChars() const {
|
||||
return numChars;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<bool> &QrSegment::getData() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
|
||||
|
||||
|
||||
|
||||
/*---- Class QrCode ----*/
|
||||
|
||||
int QrCode::getFormatBits(Ecc ecl) {
|
||||
switch (ecl) {
|
||||
case Ecc::LOW : return 1;
|
||||
case Ecc::MEDIUM : return 0;
|
||||
case Ecc::QUARTILE: return 3;
|
||||
case Ecc::HIGH : return 2;
|
||||
default: throw std::logic_error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeText(const char *text, Ecc ecl) {
|
||||
vector<QrSegment> segs = QrSegment::makeSegments(text);
|
||||
return encodeSegments(segs, ecl);
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeBinary(const vector<uint8_t> &data, Ecc ecl) {
|
||||
vector<QrSegment> segs{QrSegment::makeBytes(data)};
|
||||
return encodeSegments(segs, ecl);
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeSegments(const vector<QrSegment> &segs, Ecc ecl,
|
||||
int minVersion, int maxVersion, int mask, bool boostEcl) {
|
||||
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
|
||||
throw std::invalid_argument("Invalid value");
|
||||
|
||||
// Find the minimal version number to use
|
||||
int version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
dataUsedBits = QrSegment::getTotalBits(segs, version);
|
||||
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
|
||||
break; // This version number is found to be suitable
|
||||
if (version >= maxVersion) { // All versions in the range could not fit the given data
|
||||
std::ostringstream sb;
|
||||
if (dataUsedBits == -1)
|
||||
sb << "Segment too long";
|
||||
else {
|
||||
sb << "Data length = " << dataUsedBits << " bits, ";
|
||||
sb << "Max capacity = " << dataCapacityBits << " bits";
|
||||
}
|
||||
throw data_too_long(sb.str());
|
||||
}
|
||||
}
|
||||
assert(dataUsedBits != -1);
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high
|
||||
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
|
||||
ecl = newEcl;
|
||||
}
|
||||
|
||||
// Concatenate all segments to create the data bit string
|
||||
BitBuffer bb;
|
||||
for (const QrSegment &seg : segs) {
|
||||
bb.appendBits(static_cast<uint32_t>(seg.getMode().getModeBits()), 4);
|
||||
bb.appendBits(static_cast<uint32_t>(seg.getNumChars()), seg.getMode().numCharCountBits(version));
|
||||
bb.insert(bb.end(), seg.getData().begin(), seg.getData().end());
|
||||
}
|
||||
assert(bb.size() == static_cast<unsigned int>(dataUsedBits));
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
size_t dataCapacityBits = static_cast<size_t>(getNumDataCodewords(version, ecl)) * 8;
|
||||
assert(bb.size() <= dataCapacityBits);
|
||||
bb.appendBits(0, std::min(4, static_cast<int>(dataCapacityBits - bb.size())));
|
||||
bb.appendBits(0, (8 - static_cast<int>(bb.size() % 8)) % 8);
|
||||
assert(bb.size() % 8 == 0);
|
||||
|
||||
// Pad with alternating bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
bb.appendBits(padByte, 8);
|
||||
|
||||
// Pack bits into bytes in big endian
|
||||
vector<uint8_t> dataCodewords(bb.size() / 8);
|
||||
for (size_t i = 0; i < bb.size(); i++)
|
||||
dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7));
|
||||
|
||||
// Create the QR Code object
|
||||
return QrCode(version, ecl, dataCodewords, mask);
|
||||
}
|
||||
|
||||
|
||||
QrCode::QrCode(int ver, Ecc ecl, const vector<uint8_t> &dataCodewords, int msk) :
|
||||
// Initialize fields and check arguments
|
||||
version(ver),
|
||||
errorCorrectionLevel(ecl) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw std::domain_error("Version value out of range");
|
||||
if (msk < -1 || msk > 7)
|
||||
throw std::domain_error("Mask value out of range");
|
||||
size = ver * 4 + 17;
|
||||
size_t sz = static_cast<size_t>(size);
|
||||
modules = vector<vector<bool> >(sz, vector<bool>(sz)); // Initially all light
|
||||
isFunction = vector<vector<bool> >(sz, vector<bool>(sz));
|
||||
|
||||
// Compute ECC, draw modules
|
||||
drawFunctionPatterns();
|
||||
const vector<uint8_t> allCodewords = addEccAndInterleave(dataCodewords);
|
||||
drawCodewords(allCodewords);
|
||||
|
||||
// Do masking
|
||||
if (msk == -1) { // Automatically choose best mask
|
||||
long minPenalty = LONG_MAX;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
applyMask(i);
|
||||
drawFormatBits(i);
|
||||
long penalty = getPenaltyScore();
|
||||
if (penalty < minPenalty) {
|
||||
msk = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(i); // Undoes the mask due to XOR
|
||||
}
|
||||
}
|
||||
assert(0 <= msk && msk <= 7);
|
||||
mask = msk;
|
||||
applyMask(msk); // Apply the final choice of mask
|
||||
drawFormatBits(msk); // Overwrite old format bits
|
||||
|
||||
isFunction.clear();
|
||||
isFunction.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
QrCode::Ecc QrCode::getErrorCorrectionLevel() const {
|
||||
return errorCorrectionLevel;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getMask() const {
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::getModule(int x, int y) const {
|
||||
return 0 <= x && x < size && 0 <= y && y < size && module(x, y);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFunctionPatterns() {
|
||||
// Draw horizontal and vertical timing patterns
|
||||
for (int i = 0; i < size; i++) {
|
||||
setFunctionModule(6, i, i % 2 == 0);
|
||||
setFunctionModule(i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(3, 3);
|
||||
drawFinderPattern(size - 4, 3);
|
||||
drawFinderPattern(3, size - 4);
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
const vector<int> alignPatPos = getAlignmentPatternPositions();
|
||||
size_t numAlign = alignPatPos.size();
|
||||
for (size_t i = 0; i < numAlign; i++) {
|
||||
for (size_t j = 0; j < numAlign; j++) {
|
||||
// Don't draw on the three finder corners
|
||||
if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))
|
||||
drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion();
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFormatBits(int msk) {
|
||||
// Calculate error correction code and pack bits
|
||||
int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3
|
||||
int rem = data;
|
||||
for (int i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
int bits = (data << 10 | rem) ^ 0x5412; // uint15
|
||||
assert(bits >> 15 == 0);
|
||||
|
||||
// Draw first copy
|
||||
for (int i = 0; i <= 5; i++)
|
||||
setFunctionModule(8, i, getBit(bits, i));
|
||||
setFunctionModule(8, 7, getBit(bits, 6));
|
||||
setFunctionModule(8, 8, getBit(bits, 7));
|
||||
setFunctionModule(7, 8, getBit(bits, 8));
|
||||
for (int i = 9; i < 15; i++)
|
||||
setFunctionModule(14 - i, 8, getBit(bits, i));
|
||||
|
||||
// Draw second copy
|
||||
for (int i = 0; i < 8; i++)
|
||||
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
||||
for (int i = 8; i < 15; i++)
|
||||
setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
||||
setFunctionModule(8, size - 8, true); // Always dark
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawVersion() {
|
||||
if (version < 7)
|
||||
return;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
int rem = version; // version is uint6, in the range [7, 40]
|
||||
for (int i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
long bits = static_cast<long>(version) << 12 | rem; // uint18
|
||||
assert(bits >> 18 == 0);
|
||||
|
||||
// Draw two copies
|
||||
for (int i = 0; i < 18; i++) {
|
||||
bool bit = getBit(bits, i);
|
||||
int a = size - 11 + i % 3;
|
||||
int b = i / 3;
|
||||
setFunctionModule(a, b, bit);
|
||||
setFunctionModule(b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFinderPattern(int x, int y) {
|
||||
for (int dy = -4; dy <= 4; dy++) {
|
||||
for (int dx = -4; dx <= 4; dx++) {
|
||||
int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm
|
||||
int xx = x + dx, yy = y + dy;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size)
|
||||
setFunctionModule(xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawAlignmentPattern(int x, int y) {
|
||||
for (int dy = -2; dy <= 2; dy++) {
|
||||
for (int dx = -2; dx <= 2; dx++)
|
||||
setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::setFunctionModule(int x, int y, bool isDark) {
|
||||
size_t ux = static_cast<size_t>(x);
|
||||
size_t uy = static_cast<size_t>(y);
|
||||
modules .at(uy).at(ux) = isDark;
|
||||
isFunction.at(uy).at(ux) = true;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::module(int x, int y) const {
|
||||
return modules.at(static_cast<size_t>(y)).at(static_cast<size_t>(x));
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::addEccAndInterleave(const vector<uint8_t> &data) const {
|
||||
if (data.size() != static_cast<unsigned int>(getNumDataCodewords(version, errorCorrectionLevel)))
|
||||
throw std::invalid_argument("Invalid argument");
|
||||
|
||||
// Calculate parameter numbers
|
||||
int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(errorCorrectionLevel)][version];
|
||||
int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast<int>(errorCorrectionLevel)][version];
|
||||
int rawCodewords = getNumRawDataModules(version) / 8;
|
||||
int numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
int shortBlockLen = rawCodewords / numBlocks;
|
||||
|
||||
// Split data into blocks and append ECC to each block
|
||||
vector<vector<uint8_t> > blocks;
|
||||
const vector<uint8_t> rsDiv = reedSolomonComputeDivisor(blockEccLen);
|
||||
for (int i = 0, k = 0; i < numBlocks; i++) {
|
||||
vector<uint8_t> dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)));
|
||||
k += static_cast<int>(dat.size());
|
||||
const vector<uint8_t> ecc = reedSolomonComputeRemainder(dat, rsDiv);
|
||||
if (i < numShortBlocks)
|
||||
dat.push_back(0);
|
||||
dat.insert(dat.end(), ecc.cbegin(), ecc.cend());
|
||||
blocks.push_back(std::move(dat));
|
||||
}
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
vector<uint8_t> result;
|
||||
for (size_t i = 0; i < blocks.at(0).size(); i++) {
|
||||
for (size_t j = 0; j < blocks.size(); j++) {
|
||||
// Skip the padding byte in short blocks
|
||||
if (i != static_cast<unsigned int>(shortBlockLen - blockEccLen) || j >= static_cast<unsigned int>(numShortBlocks))
|
||||
result.push_back(blocks.at(j).at(i));
|
||||
}
|
||||
}
|
||||
assert(result.size() == static_cast<unsigned int>(rawCodewords));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawCodewords(const vector<uint8_t> &data) {
|
||||
if (data.size() != static_cast<unsigned int>(getNumRawDataModules(version) / 8))
|
||||
throw std::invalid_argument("Invalid argument");
|
||||
|
||||
size_t i = 0; // Bit index into the data
|
||||
// Do the funny zigzag scan
|
||||
for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6)
|
||||
right = 5;
|
||||
for (int vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
size_t x = static_cast<size_t>(right - j); // Actual x coordinate
|
||||
bool upward = ((right + 1) & 2) == 0;
|
||||
size_t y = static_cast<size_t>(upward ? size - 1 - vert : vert); // Actual y coordinate
|
||||
if (!isFunction.at(y).at(x) && i < data.size() * 8) {
|
||||
modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast<int>(i & 7));
|
||||
i++;
|
||||
}
|
||||
// If this QR Code has any remainder bits (0 to 7), they were assigned as
|
||||
// 0/false/light by the constructor and are left unchanged by this method
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(i == data.size() * 8);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::applyMask(int msk) {
|
||||
if (msk < 0 || msk > 7)
|
||||
throw std::domain_error("Mask value out of range");
|
||||
size_t sz = static_cast<size_t>(size);
|
||||
for (size_t y = 0; y < sz; y++) {
|
||||
for (size_t x = 0; x < sz; x++) {
|
||||
bool invert;
|
||||
switch (msk) {
|
||||
case 0: invert = (x + y) % 2 == 0; break;
|
||||
case 1: invert = y % 2 == 0; break;
|
||||
case 2: invert = x % 3 == 0; break;
|
||||
case 3: invert = (x + y) % 3 == 0; break;
|
||||
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||
default: throw std::logic_error("Unreachable");
|
||||
}
|
||||
modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
long QrCode::getPenaltyScore() const {
|
||||
long result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
for (int y = 0; y < size; y++) {
|
||||
bool runColor = false;
|
||||
int runX = 0;
|
||||
std::array<int,7> runHistory = {};
|
||||
for (int x = 0; x < size; x++) {
|
||||
if (module(x, y) == runColor) {
|
||||
runX++;
|
||||
if (runX == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runX, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = module(x, y);
|
||||
runX = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;
|
||||
}
|
||||
// Adjacent modules in column having same color, and finder-like patterns
|
||||
for (int x = 0; x < size; x++) {
|
||||
bool runColor = false;
|
||||
int runY = 0;
|
||||
std::array<int,7> runHistory = {};
|
||||
for (int y = 0; y < size; y++) {
|
||||
if (module(x, y) == runColor) {
|
||||
runY++;
|
||||
if (runY == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runY, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = module(x, y);
|
||||
runY = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (int y = 0; y < size - 1; y++) {
|
||||
for (int x = 0; x < size - 1; x++) {
|
||||
bool color = module(x, y);
|
||||
if ( color == module(x + 1, y) &&
|
||||
color == module(x, y + 1) &&
|
||||
color == module(x + 1, y + 1))
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of dark and light modules
|
||||
int dark = 0;
|
||||
for (const vector<bool> &row : modules) {
|
||||
for (bool color : row) {
|
||||
if (color)
|
||||
dark++;
|
||||
}
|
||||
}
|
||||
int total = size * size; // Note that size is odd, so dark/total != 1/2
|
||||
// Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
int k = static_cast<int>((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1;
|
||||
assert(0 <= k && k <= 9);
|
||||
result += k * PENALTY_N4;
|
||||
assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
vector<int> QrCode::getAlignmentPatternPositions() const {
|
||||
if (version == 1)
|
||||
return vector<int>();
|
||||
else {
|
||||
int numAlign = version / 7 + 2;
|
||||
int step = (version == 32) ? 26 :
|
||||
(version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
|
||||
vector<int> result;
|
||||
for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step)
|
||||
result.insert(result.begin(), pos);
|
||||
result.insert(result.begin(), 6);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getNumRawDataModules(int ver) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw std::domain_error("Version number out of range");
|
||||
int result = (16 * ver + 128) * ver + 64;
|
||||
if (ver >= 2) {
|
||||
int numAlign = ver / 7 + 2;
|
||||
result -= (25 * numAlign - 10) * numAlign - 55;
|
||||
if (ver >= 7)
|
||||
result -= 36;
|
||||
}
|
||||
assert(208 <= result && result <= 29648);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getNumDataCodewords(int ver, Ecc ecl) {
|
||||
return getNumRawDataModules(ver) / 8
|
||||
- ECC_CODEWORDS_PER_BLOCK [static_cast<int>(ecl)][ver]
|
||||
* NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(ecl)][ver];
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::reedSolomonComputeDivisor(int degree) {
|
||||
if (degree < 1 || degree > 255)
|
||||
throw std::domain_error("Degree out of range");
|
||||
// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.
|
||||
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
|
||||
vector<uint8_t> result(static_cast<size_t>(degree));
|
||||
result.at(result.size() - 1) = 1; // Start off with the monomial x^0
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// and drop the highest monomial term which is always 1x^degree.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
uint8_t root = 1;
|
||||
for (int i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (size_t j = 0; j < result.size(); j++) {
|
||||
result.at(j) = reedSolomonMultiply(result.at(j), root);
|
||||
if (j + 1 < result.size())
|
||||
result.at(j) ^= result.at(j + 1);
|
||||
}
|
||||
root = reedSolomonMultiply(root, 0x02);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::reedSolomonComputeRemainder(const vector<uint8_t> &data, const vector<uint8_t> &divisor) {
|
||||
vector<uint8_t> result(divisor.size());
|
||||
for (uint8_t b : data) { // Polynomial division
|
||||
uint8_t factor = b ^ result.at(0);
|
||||
result.erase(result.begin());
|
||||
result.push_back(0);
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) {
|
||||
// Russian peasant multiplication
|
||||
int z = 0;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||
z ^= ((y >> i) & 1) * x;
|
||||
}
|
||||
assert(z >> 8 == 0);
|
||||
return static_cast<uint8_t>(z);
|
||||
}
|
||||
|
||||
|
||||
int QrCode::finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const {
|
||||
int n = runHistory.at(1);
|
||||
assert(n <= size * 3);
|
||||
bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n;
|
||||
return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0)
|
||||
+ (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const {
|
||||
if (currentRunColor) { // Terminate dark run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
currentRunLength = 0;
|
||||
}
|
||||
currentRunLength += size; // Add light border to final run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
return finderPenaltyCountPatterns(runHistory);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const {
|
||||
if (runHistory.at(0) == 0)
|
||||
currentRunLength += size; // Add light border to initial run
|
||||
std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end());
|
||||
runHistory.at(0) = currentRunLength;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::getBit(long x, int i) {
|
||||
return ((x >> i) & 1) != 0;
|
||||
}
|
||||
|
||||
|
||||
/*---- Tables of constants ----*/
|
||||
|
||||
const int QrCode::PENALTY_N1 = 3;
|
||||
const int QrCode::PENALTY_N2 = 3;
|
||||
const int QrCode::PENALTY_N3 = 40;
|
||||
const int QrCode::PENALTY_N4 = 10;
|
||||
|
||||
|
||||
const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//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 Error correction level
|
||||
{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
|
||||
{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
|
||||
{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
|
||||
{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
|
||||
};
|
||||
|
||||
const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//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 Error correction level
|
||||
{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
};
|
||||
|
||||
|
||||
data_too_long::data_too_long(const std::string &msg) :
|
||||
std::length_error(msg) {}
|
||||
|
||||
|
||||
|
||||
/*---- Class BitBuffer ----*/
|
||||
|
||||
BitBuffer::BitBuffer()
|
||||
: std::vector<bool>() {}
|
||||
|
||||
|
||||
void BitBuffer::appendBits(std::uint32_t val, int len) {
|
||||
if (len < 0 || len > 31 || val >> len != 0)
|
||||
throw std::domain_error("Value out of range");
|
||||
for (int i = len - 1; i >= 0; i--) // Append bit by bit
|
||||
this->push_back(((val >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
}
|
||||
549
3rdparty/qrcodegen.hpp
vendored
549
3rdparty/qrcodegen.hpp
vendored
@ -1,549 +0,0 @@
|
||||
/*
|
||||
* QR Code generator library (C++)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace qrcodegen {
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* Instances of this class are immutable.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a static factory function such as QrSegment::makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and call the QrSegment() constructor with appropriate values.
|
||||
* This segment class imposes no length restrictions, but QR Codes have restrictions.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
*/
|
||||
class QrSegment final {
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted. Immutable.
|
||||
*/
|
||||
public: class Mode final {
|
||||
|
||||
/*-- Constants --*/
|
||||
|
||||
public: static const Mode NUMERIC;
|
||||
public: static const Mode ALPHANUMERIC;
|
||||
public: static const Mode BYTE;
|
||||
public: static const Mode KANJI;
|
||||
public: static const Mode ECI;
|
||||
|
||||
|
||||
/*-- Fields --*/
|
||||
|
||||
// The mode indicator bits, which is a uint4 value (range 0 to 15).
|
||||
private: int modeBits;
|
||||
|
||||
// Number of character count bits for three different version ranges.
|
||||
private: int numBitsCharCount[3];
|
||||
|
||||
|
||||
/*-- Constructor --*/
|
||||
|
||||
private: Mode(int mode, int cc0, int cc1, int cc2);
|
||||
|
||||
|
||||
/*-- Methods --*/
|
||||
|
||||
/*
|
||||
* (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15).
|
||||
*/
|
||||
public: int getModeBits() const;
|
||||
|
||||
/*
|
||||
* (Package-private) Returns the bit width of the character count field for a segment in
|
||||
* this mode in a QR Code at the given version number. The result is in the range [0, 16].
|
||||
*/
|
||||
public: int numCharCountBits(int ver) const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte vectors are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
public: static QrSegment makeBytes(const std::vector<std::uint8_t> &data);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
public: static QrSegment makeNumeric(const char *digits);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
public: static QrSegment makeAlphanumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a list of zero or more segments to represent the given text string. The result
|
||||
* may use various segment modes and switch modes to optimize the length of the bit stream.
|
||||
*/
|
||||
public: static std::vector<QrSegment> makeSegments(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
public: static QrSegment makeEci(long assignVal);
|
||||
|
||||
|
||||
/*---- Public static helper functions ----*/
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in numeric mode.
|
||||
* A string is encodable iff each character is in the range 0 to 9.
|
||||
*/
|
||||
public: static bool isNumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
|
||||
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
public: static bool isAlphanumeric(const char *text);
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
/* The mode indicator of this segment. Accessed through getMode(). */
|
||||
private: const Mode *mode;
|
||||
|
||||
/* The length of this segment's unencoded data. Measured in characters for
|
||||
* numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
* Always zero or positive. Not the same as the data's bit length.
|
||||
* Accessed through getNumChars(). */
|
||||
private: int numChars;
|
||||
|
||||
/* The data bits of this segment. Accessed through getData(). */
|
||||
private: std::vector<bool> data;
|
||||
|
||||
|
||||
/*---- Constructors (low level) ----*/
|
||||
|
||||
/*
|
||||
* Creates a new QR Code segment with the given attributes and data.
|
||||
* The character count (numCh) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is copied and stored.
|
||||
*/
|
||||
public: QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt);
|
||||
|
||||
|
||||
/*
|
||||
* Creates a new QR Code segment with the given parameters and data.
|
||||
* The character count (numCh) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is moved and stored.
|
||||
*/
|
||||
public: QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt);
|
||||
|
||||
|
||||
/*---- Methods ----*/
|
||||
|
||||
/*
|
||||
* Returns the mode field of this segment.
|
||||
*/
|
||||
public: const Mode &getMode() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the character count field of this segment.
|
||||
*/
|
||||
public: int getNumChars() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the data bits of this segment.
|
||||
*/
|
||||
public: const std::vector<bool> &getData() const;
|
||||
|
||||
|
||||
// (Package-private) Calculates the number of bits needed to encode the given segments at
|
||||
// the given version. Returns a non-negative number if successful. Otherwise returns -1 if a
|
||||
// segment has too many characters to fit its length field, or the total bits exceeds INT_MAX.
|
||||
public: static int getTotalBits(const std::vector<QrSegment> &segs, int version);
|
||||
|
||||
|
||||
/*---- Private constant ----*/
|
||||
|
||||
/* The set of all legal characters in alphanumeric mode, where
|
||||
* each character value maps to the index in the string. */
|
||||
private: static const char *ALPHANUMERIC_CHARSET;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A QR Code symbol, which is a type of two-dimension barcode.
|
||||
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
|
||||
* Instances of this class represent an immutable square grid of dark and light cells.
|
||||
* The class provides static factory functions to create a QR Code from text or binary data.
|
||||
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
|
||||
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
|
||||
*
|
||||
* Ways to create a QR Code object:
|
||||
* - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary().
|
||||
* - Mid level: Custom-make the list of segments and call QrCode::encodeSegments().
|
||||
* - Low level: Custom-make the array of data codeword bytes (including
|
||||
* segment headers and final padding, excluding error correction codewords),
|
||||
* supply the appropriate version number, and call the QrCode() constructor.
|
||||
* (Note that all ways require supplying the desired error correction level.)
|
||||
*/
|
||||
class QrCode final {
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol.
|
||||
*/
|
||||
public: enum class Ecc {
|
||||
LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
|
||||
MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
|
||||
QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
|
||||
HIGH , // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
// Returns a value in the range 0 to 3 (unsigned 2-bit integer).
|
||||
private: static int getFormatBits(Ecc ecl);
|
||||
|
||||
|
||||
|
||||
/*---- Static factory functions (high level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given Unicode text string at the given error correction level.
|
||||
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer
|
||||
* UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible
|
||||
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than
|
||||
* the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
public: static QrCode encodeText(const char *text, Ecc ecl);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given binary data at the given error correction level.
|
||||
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
|
||||
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
public: static QrCode encodeBinary(const std::vector<std::uint8_t> &data, Ecc ecl);
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given segments with the given encoding parameters.
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is encodeText() and encodeBinary().
|
||||
*/
|
||||
public: static QrCode encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,
|
||||
int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
// Immutable scalar parameters:
|
||||
|
||||
/* The version number of this QR Code, which is between 1 and 40 (inclusive).
|
||||
* This determines the size of this barcode. */
|
||||
private: int version;
|
||||
|
||||
/* The width and height of this QR Code, measured in modules, between
|
||||
* 21 and 177 (inclusive). This is equal to version * 4 + 17. */
|
||||
private: int size;
|
||||
|
||||
/* The error correction level used in this QR Code. */
|
||||
private: Ecc errorCorrectionLevel;
|
||||
|
||||
/* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
|
||||
* Even if a QR Code is created with automatic masking requested (mask = -1),
|
||||
* the resulting object still has a mask value between 0 and 7. */
|
||||
private: int mask;
|
||||
|
||||
// Private grids of modules/pixels, with dimensions of size*size:
|
||||
|
||||
// The modules of this QR Code (false = light, true = dark).
|
||||
// Immutable after constructor finishes. Accessed through getModule().
|
||||
private: std::vector<std::vector<bool> > modules;
|
||||
|
||||
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
|
||||
private: std::vector<std::vector<bool> > isFunction;
|
||||
|
||||
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
/*
|
||||
* Creates a new QR Code with the given version number,
|
||||
* error correction level, data codeword bytes, and mask number.
|
||||
* This is a low-level API that most users should not use directly.
|
||||
* A mid-level API is the encodeSegments() function.
|
||||
*/
|
||||
public: QrCode(int ver, Ecc ecl, const std::vector<std::uint8_t> &dataCodewords, int msk);
|
||||
|
||||
|
||||
|
||||
/*---- Public instance methods ----*/
|
||||
|
||||
/*
|
||||
* Returns this QR Code's version, in the range [1, 40].
|
||||
*/
|
||||
public: int getVersion() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's size, in the range [21, 177].
|
||||
*/
|
||||
public: int getSize() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's error correction level.
|
||||
*/
|
||||
public: Ecc getErrorCorrectionLevel() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's mask, in the range [0, 7].
|
||||
*/
|
||||
public: int getMask() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
* for light or true for dark. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the given coordinates are out of bounds, then false (light) is returned.
|
||||
*/
|
||||
public: bool getModule(int x, int y) const;
|
||||
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Drawing function modules ----*/
|
||||
|
||||
// Reads this object's version field, and draws and marks all function modules.
|
||||
private: void drawFunctionPatterns();
|
||||
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
private: void drawFormatBits(int msk);
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field, iff 7 <= version <= 40.
|
||||
private: void drawVersion();
|
||||
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator,
|
||||
// with the center module at (x, y). Modules can be out of bounds.
|
||||
private: void drawFinderPattern(int x, int y);
|
||||
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module
|
||||
// at (x, y). All modules must be in bounds.
|
||||
private: void drawAlignmentPattern(int x, int y);
|
||||
|
||||
|
||||
// Sets the color of a module and marks it as a function module.
|
||||
// Only used by the constructor. Coordinates must be in bounds.
|
||||
private: void setFunctionModule(int x, int y, bool isDark);
|
||||
|
||||
|
||||
// Returns the color of the module at the given coordinates, which must be in range.
|
||||
private: bool module(int x, int y) const;
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Codewords and masking ----*/
|
||||
|
||||
// Returns a new byte string representing the given data with the appropriate error correction
|
||||
// codewords appended to it, based on this object's version and error correction level.
|
||||
private: std::vector<std::uint8_t> addEccAndInterleave(const std::vector<std::uint8_t> &data) const;
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code. Function modules need to be marked off before this is called.
|
||||
private: void drawCodewords(const std::vector<std::uint8_t> &data);
|
||||
|
||||
|
||||
// XORs the codeword modules in this QR Code with the given mask pattern.
|
||||
// The function modules must be marked and the codeword bits must be drawn
|
||||
// before masking. Due to the arithmetic of XOR, calling applyMask() with
|
||||
// the same mask value a second time will undo the mask. A final well-formed
|
||||
// QR Code needs exactly one (not zero, two, etc.) mask applied.
|
||||
private: void applyMask(int msk);
|
||||
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
private: long getPenaltyScore() const;
|
||||
|
||||
|
||||
|
||||
/*---- Private helper functions ----*/
|
||||
|
||||
// Returns an ascending list of positions of alignment patterns for this version number.
|
||||
// Each position is in the range [0,177), and are used on both the x and y axes.
|
||||
// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
|
||||
private: std::vector<int> getAlignmentPatternPositions() const;
|
||||
|
||||
|
||||
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
||||
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
||||
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
|
||||
private: static int getNumRawDataModules(int ver);
|
||||
|
||||
|
||||
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
||||
// QR Code of the given version number and error correction level, with remainder bits discarded.
|
||||
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
|
||||
private: static int getNumDataCodewords(int ver, Ecc ecl);
|
||||
|
||||
|
||||
// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
|
||||
// implemented as a lookup table over all possible parameter values, instead of as an algorithm.
|
||||
private: static std::vector<std::uint8_t> reedSolomonComputeDivisor(int degree);
|
||||
|
||||
|
||||
// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.
|
||||
private: static std::vector<std::uint8_t> reedSolomonComputeRemainder(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &divisor);
|
||||
|
||||
|
||||
// Returns the product of the two given field elements modulo GF(2^8/0x11D).
|
||||
// All inputs are valid. This could be implemented as a 256*256 lookup table.
|
||||
private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y);
|
||||
|
||||
|
||||
// Can only be called immediately after a light run is added, and
|
||||
// returns either 0, 1, or 2. A helper function for getPenaltyScore().
|
||||
private: int finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().
|
||||
private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
|
||||
private: void finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Returns true iff the i'th bit of x is set to 1.
|
||||
private: static bool getBit(long x, int i);
|
||||
|
||||
|
||||
/*---- Constants and tables ----*/
|
||||
|
||||
// The minimum version number supported in the QR Code Model 2 standard.
|
||||
public: static constexpr int MIN_VERSION = 1;
|
||||
|
||||
// The maximum version number supported in the QR Code Model 2 standard.
|
||||
public: static constexpr int MAX_VERSION = 40;
|
||||
|
||||
|
||||
// For use in getPenaltyScore(), when evaluating which mask is best.
|
||||
private: static const int PENALTY_N1;
|
||||
private: static const int PENALTY_N2;
|
||||
private: static const int PENALTY_N3;
|
||||
private: static const int PENALTY_N4;
|
||||
|
||||
|
||||
private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41];
|
||||
private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41];
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Public exception class ----*/
|
||||
|
||||
/*
|
||||
* Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include:
|
||||
* - Decrease the error correction level if it was greater than Ecc::LOW.
|
||||
* - If the encodeSegments() function was called with a maxVersion argument, then increase
|
||||
* it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other
|
||||
* factory functions because they search all versions up to QrCode::MAX_VERSION.)
|
||||
* - Split the text data into better or optimal segments in order to reduce the number of bits required.
|
||||
* - Change the text or binary data to be shorter.
|
||||
* - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).
|
||||
* - Propagate the error upward to the caller/user.
|
||||
*/
|
||||
class data_too_long : public std::length_error {
|
||||
|
||||
public: explicit data_too_long(const std::string &msg);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.
|
||||
*/
|
||||
class BitBuffer final : public std::vector<bool> {
|
||||
|
||||
/*---- Constructor ----*/
|
||||
|
||||
// Creates an empty bit buffer (length 0).
|
||||
public: BitBuffer();
|
||||
|
||||
|
||||
|
||||
/*---- Method ----*/
|
||||
|
||||
// Appends the given number of low-order bits of the given value
|
||||
// to this buffer. Requires 0 <= len <= 31 and val < 2^len.
|
||||
public: void appendBits(std::uint32_t val, int len);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
96
3rdparty/qscopeguard.h
vendored
96
3rdparty/qscopeguard.h
vendored
@ -1,96 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sérgio Martins <sergio.martins@kdab.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QSCOPEGUARD_H
|
||||
#define QSCOPEGUARD_H
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
||||
template <typename F> class QScopeGuard;
|
||||
template <typename F> QScopeGuard<F> qScopeGuard(F f);
|
||||
|
||||
template <typename F>
|
||||
class QScopeGuard
|
||||
{
|
||||
public:
|
||||
QScopeGuard(QScopeGuard &&other) Q_DECL_NOEXCEPT
|
||||
: m_func(std::move(other.m_func))
|
||||
, m_invoke(other.m_invoke)
|
||||
{
|
||||
other.dismiss();
|
||||
}
|
||||
|
||||
~QScopeGuard()
|
||||
{
|
||||
if (m_invoke)
|
||||
m_func();
|
||||
}
|
||||
|
||||
void dismiss() Q_DECL_NOEXCEPT
|
||||
{
|
||||
m_invoke = false;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit QScopeGuard(F f) Q_DECL_NOEXCEPT
|
||||
: m_func(std::move(f))
|
||||
{
|
||||
}
|
||||
|
||||
Q_DISABLE_COPY(QScopeGuard)
|
||||
|
||||
F m_func;
|
||||
bool m_invoke = true;
|
||||
friend QScopeGuard qScopeGuard<F>(F);
|
||||
};
|
||||
|
||||
|
||||
template <typename F>
|
||||
QScopeGuard<F> qScopeGuard(F f)
|
||||
{
|
||||
return QScopeGuard<F>(std::move(f));
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QSCOPEGUARD_H
|
||||
@ -1,440 +0,0 @@
|
||||
#include "QvProxyConfigurator.hpp"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
//
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
//
|
||||
#include <wininet.h>
|
||||
#include <ras.h>
|
||||
#include <raserror.h>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QProcess>
|
||||
|
||||
#include "3rdparty/fix_old_qt.h"
|
||||
#include "3rdparty/qv2ray/wrapper.hpp"
|
||||
#include "fmt/Preset.hpp"
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
#define QV_MODULE_NAME "SystemProxy"
|
||||
|
||||
#define QSTRN(num) QString::number(num)
|
||||
|
||||
namespace Qv2ray::components::proxy {
|
||||
|
||||
using ProcessArgument = QPair<QString, QStringList>;
|
||||
#ifdef Q_OS_MACOS
|
||||
QStringList macOSgetNetworkServices() {
|
||||
QProcess p;
|
||||
p.setProgram("/usr/sbin/networksetup");
|
||||
p.setArguments(QStringList{"-listallnetworkservices"});
|
||||
p.start();
|
||||
p.waitForStarted();
|
||||
p.waitForFinished();
|
||||
LOG(p.errorString());
|
||||
auto str = p.readAllStandardOutput();
|
||||
auto lines = SplitLines(str);
|
||||
QStringList result;
|
||||
|
||||
// Start from 1 since first line is unneeded.
|
||||
for (auto i = 1; i < lines.count(); i++) {
|
||||
// * means disabled.
|
||||
if (!lines[i].contains("*")) {
|
||||
result << lines[i];
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Found " + QSTRN(result.size()) + " network services: " + result.join(";"));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
#define NO_CONST(expr) const_cast<wchar_t *>(expr)
|
||||
// static auto DEFAULT_CONNECTION_NAME =
|
||||
// NO_CONST(L"DefaultConnectionSettings");
|
||||
///
|
||||
/// INTERNAL FUNCTION
|
||||
bool __QueryProxyOptions() {
|
||||
INTERNET_PER_CONN_OPTION_LIST List;
|
||||
INTERNET_PER_CONN_OPTION Option[5];
|
||||
//
|
||||
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
|
||||
Option[2].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
|
||||
//
|
||||
List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
List.pszConnection = nullptr; // NO_CONST(DEFAULT_CONNECTION_NAME);
|
||||
List.dwOptionCount = 5;
|
||||
List.dwOptionError = 0;
|
||||
List.pOptions = Option;
|
||||
|
||||
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
|
||||
LOG("InternetQueryOption failed, GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
LOG("System default proxy info:");
|
||||
|
||||
if (Option[0].Value.pszValue != nullptr) {
|
||||
LOG(QString::fromWCharArray(Option[0].Value.pszValue));
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) {
|
||||
LOG("PROXY_TYPE_AUTO_PROXY_URL");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) {
|
||||
LOG("PROXY_TYPE_AUTO_DETECT");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) {
|
||||
LOG("PROXY_TYPE_DIRECT");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) {
|
||||
LOG("PROXY_TYPE_PROXY");
|
||||
}
|
||||
|
||||
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
|
||||
LOG("InternetQueryOption failed,GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
if (Option[4].Value.pszValue != nullptr) {
|
||||
LOG(QString::fromStdWString(Option[4].Value.pszValue));
|
||||
}
|
||||
|
||||
INTERNET_VERSION_INFO Version;
|
||||
nSize = sizeof(INTERNET_VERSION_INFO);
|
||||
InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize);
|
||||
|
||||
if (Option[0].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[0].Value.pszValue);
|
||||
}
|
||||
|
||||
if (Option[3].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[3].Value.pszValue);
|
||||
}
|
||||
|
||||
if (Option[4].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[4].Value.pszValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC) {
|
||||
INTERNET_PER_CONN_OPTION_LIST list;
|
||||
DWORD dwBufSize = sizeof(list);
|
||||
// Fill the list structure.
|
||||
list.dwSize = sizeof(list);
|
||||
// NULL == LAN, otherwise connectoid name.
|
||||
list.pszConnection = nullptr;
|
||||
|
||||
if (nullptr == proxy_full_addr) {
|
||||
LOG("Clearing system proxy");
|
||||
//
|
||||
list.dwOptionCount = 1;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[1];
|
||||
|
||||
// Ensure that the memory was allocated.
|
||||
if (nullptr == list.pOptions) {
|
||||
// Return if the memory wasn't allocated.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
|
||||
} else if (isPAC) {
|
||||
LOG("Setting system proxy for PAC");
|
||||
//
|
||||
list.dwOptionCount = 2;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
|
||||
|
||||
if (nullptr == list.pOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;
|
||||
// Set proxy name.
|
||||
list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
list.pOptions[1].Value.pszValue = proxy_full_addr;
|
||||
} else {
|
||||
LOG("Setting system proxy for Global Proxy");
|
||||
//
|
||||
list.dwOptionCount = 2;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
|
||||
|
||||
if (nullptr == list.pOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
|
||||
// Set proxy name.
|
||||
list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
|
||||
list.pOptions[1].Value.pszValue = proxy_full_addr;
|
||||
// Set proxy override.
|
||||
// list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
// auto localhost = L"localhost";
|
||||
// list.pOptions[2].Value.pszValue = NO_CONST(localhost);
|
||||
}
|
||||
|
||||
// Set proxy for LAN.
|
||||
if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {
|
||||
LOG("InternetSetOption failed for LAN, GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
RASENTRYNAME entry;
|
||||
entry.dwSize = sizeof(entry);
|
||||
std::vector<RASENTRYNAME> entries;
|
||||
DWORD size = sizeof(entry), count;
|
||||
LPRASENTRYNAME entryAddr = &entry;
|
||||
auto ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);
|
||||
if (ERROR_BUFFER_TOO_SMALL == ret) {
|
||||
entries.resize(count);
|
||||
entries[0].dwSize = sizeof(RASENTRYNAME);
|
||||
entryAddr = entries.data();
|
||||
ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);
|
||||
}
|
||||
if (ERROR_SUCCESS != ret) {
|
||||
LOG("Failed to list entry names");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set proxy for each connectoid.
|
||||
for (DWORD i = 0; i < count; ++i) {
|
||||
list.pszConnection = entryAddr[i].szEntryName;
|
||||
if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {
|
||||
LOG("InternetSetOption failed for connectoid " + QString::fromWCharArray(list.pszConnection) + ", GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
delete[] list.pOptions;
|
||||
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
|
||||
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void SetSystemProxy(int httpPort, int socksPort) {
|
||||
const QString &address = "127.0.0.1";
|
||||
bool hasHTTP = (httpPort > 0 && httpPort < 65536);
|
||||
bool hasSOCKS = (socksPort > 0 && socksPort < 65536);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (!hasHTTP) {
|
||||
LOG("Nothing?");
|
||||
return;
|
||||
} else {
|
||||
LOG("Qv2ray will set system proxy to use HTTP");
|
||||
}
|
||||
#else
|
||||
if (!hasHTTP && !hasSOCKS) {
|
||||
LOG("Nothing?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasHTTP) {
|
||||
LOG("Qv2ray will set system proxy to use HTTP");
|
||||
}
|
||||
|
||||
if (hasSOCKS) {
|
||||
LOG("Qv2ray will set system proxy to use SOCKS");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString str = NekoGui::dataStore->system_proxy_format;
|
||||
if (str.isEmpty()) str = Preset::Windows::system_proxy_format[0];
|
||||
str = str.replace("{ip}", address)
|
||||
.replace("{http_port}", Int2String(httpPort))
|
||||
.replace("{socks_port}", Int2String(socksPort));
|
||||
//
|
||||
LOG("Windows proxy string: " + str);
|
||||
auto proxyStrW = new WCHAR[str.length() + 1];
|
||||
wcscpy(proxyStrW, str.toStdWString().c_str());
|
||||
//
|
||||
__QueryProxyOptions();
|
||||
|
||||
if (!__SetProxyOptions(proxyStrW, false)) {
|
||||
LOG("Failed to set proxy.");
|
||||
}
|
||||
|
||||
__QueryProxyOptions();
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<ProcessArgument> actions;
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}};
|
||||
//
|
||||
bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" ||
|
||||
qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma";
|
||||
const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
|
||||
//
|
||||
// Configure HTTP Proxies for HTTP, FTP and HTTPS
|
||||
if (hasHTTP) {
|
||||
// iterate over protocols...
|
||||
for (const auto &protocol: QStringList{"http", "ftp", "https"}) {
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy." + protocol, "host", address}};
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy." + protocol, "port", QSTRN(httpPort)}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", protocol + "Proxy", //
|
||||
"http://" + address + " " + QSTRN(httpPort)}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure SOCKS5 Proxies
|
||||
if (hasSOCKS) {
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy.socks", "host", address}};
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy.socks", "port", QSTRN(socksPort)}};
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "socksProxy", //
|
||||
"socks://" + address + " " + QSTRN(socksPort)}};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Setting Proxy Mode to Manual
|
||||
{
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "ProxyType", "1"}};
|
||||
}
|
||||
}
|
||||
|
||||
// Notify kioslaves to reload system proxy configuration.
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"dbus-send",
|
||||
{"--type=signal", "/KIO/Scheduler", //
|
||||
"org.kde.KIO.Scheduler.reparseSlaveConfiguration", //
|
||||
"string:''"}};
|
||||
}
|
||||
// Execute them all!
|
||||
//
|
||||
// note: do not use std::all_of / any_of / none_of,
|
||||
// because those are short-circuit and cannot guarantee atomicity.
|
||||
QList<bool> results;
|
||||
for (const auto &action: actions) {
|
||||
// execute and get the code
|
||||
const auto returnCode = QProcess::execute(action.first, action.second);
|
||||
// print out the commands and result codes
|
||||
DEBUG(QStringLiteral("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";")));
|
||||
// give the code back
|
||||
results << (returnCode == QProcess::NormalExit);
|
||||
}
|
||||
|
||||
if (results.count(true) != actions.size()) {
|
||||
LOG("Something wrong when setting proxies.");
|
||||
}
|
||||
#else
|
||||
|
||||
for (const auto &service: macOSgetNetworkServices()) {
|
||||
LOG("Setting proxy for interface: " + service);
|
||||
if (hasHTTP) {
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxy", service, address, QSTRN(httpPort)});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxy", service, address, QSTRN(httpPort)});
|
||||
}
|
||||
|
||||
if (hasSOCKS) {
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxy", service, address, QSTRN(socksPort)});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClearSystemProxy() {
|
||||
LOG("Clearing System Proxy");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (!__SetProxyOptions(nullptr, false)) {
|
||||
LOG("Failed to clear proxy.");
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<ProcessArgument> actions;
|
||||
const bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" ||
|
||||
qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma";
|
||||
const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
|
||||
// Setting System Proxy Mode to: None
|
||||
{
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "none"}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configRoot + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "ProxyType", "0"}};
|
||||
}
|
||||
}
|
||||
|
||||
// Notify kioslaves to reload system proxy configuration.
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"dbus-send",
|
||||
{"--type=signal", "/KIO/Scheduler", //
|
||||
"org.kde.KIO.Scheduler.reparseSlaveConfiguration", //
|
||||
"string:''"}};
|
||||
}
|
||||
|
||||
// Execute the Actions
|
||||
for (const auto &action: actions) {
|
||||
// execute and get the code
|
||||
const auto returnCode = QProcess::execute(action.first, action.second);
|
||||
// print out the commands and result codes
|
||||
DEBUG(QStringLiteral("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";")));
|
||||
}
|
||||
|
||||
#else
|
||||
for (const auto &service: macOSgetNetworkServices()) {
|
||||
LOG("Clearing proxy for interface: " + service);
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setautoproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "off"});
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
} // namespace Qv2ray::components::proxy
|
||||
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
//
|
||||
namespace Qv2ray::components::proxy {
|
||||
void ClearSystemProxy();
|
||||
void SetSystemProxy(int http_port, int socks_port);
|
||||
} // namespace Qv2ray::components::proxy
|
||||
|
||||
using namespace Qv2ray::components;
|
||||
using namespace Qv2ray::components::proxy;
|
||||
157
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp
vendored
157
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp
vendored
@ -1,157 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** BSD License Usage
|
||||
** Alternatively, you may use this file under the terms of the BSD license
|
||||
** as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * 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.
|
||||
** * Neither the name of The Qt Company Ltd 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 COPYRIGHT HOLDERS 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 COPYRIGHT
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "QvAutoCompleteTextEdit.hpp"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QCompleter>
|
||||
#include <QKeyEvent>
|
||||
#include <QModelIndex>
|
||||
#include <QScrollBar>
|
||||
#include <QStringListModel>
|
||||
#include <QToolTip>
|
||||
#include <QtDebug>
|
||||
|
||||
namespace Qv2ray::ui::widgets {
|
||||
AutoCompleteTextEdit::AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent) : QPlainTextEdit(parent) {
|
||||
this->prefix = prefix;
|
||||
this->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||
c = new QCompleter(this);
|
||||
c->setModel(new QStringListModel(sourceStrings, c));
|
||||
c->setWidget(this);
|
||||
c->setCompletionMode(QCompleter::PopupCompletion);
|
||||
c->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
QObject::connect(c, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated), this, &AutoCompleteTextEdit::insertCompletion);
|
||||
}
|
||||
|
||||
AutoCompleteTextEdit::~AutoCompleteTextEdit() {
|
||||
}
|
||||
|
||||
void AutoCompleteTextEdit::insertCompletion(const QString &completion) {
|
||||
QTextCursor tc = textCursor();
|
||||
int extra = completion.length() - c->completionPrefix().length();
|
||||
tc.movePosition(QTextCursor::Left);
|
||||
tc.movePosition(QTextCursor::EndOfWord);
|
||||
tc.insertText(completion.right(extra).toLower());
|
||||
setTextCursor(tc);
|
||||
}
|
||||
|
||||
QString AutoCompleteTextEdit::lineUnderCursor() const {
|
||||
QTextCursor tc = textCursor();
|
||||
tc.select(QTextCursor::LineUnderCursor);
|
||||
return tc.selectedText();
|
||||
}
|
||||
|
||||
QString AutoCompleteTextEdit::wordUnderCursor() const {
|
||||
QTextCursor tc = textCursor();
|
||||
tc.select(QTextCursor::WordUnderCursor);
|
||||
return tc.selectedText();
|
||||
}
|
||||
|
||||
void AutoCompleteTextEdit::focusInEvent(QFocusEvent *e) {
|
||||
if (c)
|
||||
c->setWidget(this);
|
||||
|
||||
QPlainTextEdit::focusInEvent(e);
|
||||
}
|
||||
|
||||
void AutoCompleteTextEdit::keyPressEvent(QKeyEvent *e) {
|
||||
const bool hasCtrlOrShiftModifier = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::ShiftModifier);
|
||||
const bool hasOtherModifiers = (e->modifiers() != Qt::NoModifier) && !hasCtrlOrShiftModifier; // has other modifiers
|
||||
//
|
||||
const bool isSpace = (e->modifiers().testFlag(Qt::ShiftModifier) || e->modifiers().testFlag(Qt::NoModifier)) //
|
||||
&& e->key() == Qt::Key_Space;
|
||||
const bool isTab = (e->modifiers().testFlag(Qt::NoModifier) && e->key() == Qt::Key_Tab);
|
||||
const bool isOtherSpace = e->text() == " ";
|
||||
//
|
||||
if (isSpace || isTab || isOtherSpace) {
|
||||
QToolTip::showText(this->mapToGlobal(QPoint(0, 0)), tr("You can not input space characters here."), this, QRect{}, 2000);
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (c && c->popup()->isVisible()) {
|
||||
// The following keys are forwarded by the completer to the widget
|
||||
switch (e->key()) {
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab:
|
||||
e->ignore();
|
||||
return; // let the completer do default behavior
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPlainTextEdit::keyPressEvent(e);
|
||||
|
||||
if (!c || (hasCtrlOrShiftModifier && e->text().isEmpty()))
|
||||
return;
|
||||
|
||||
// if we have other modifiers, or the text is empty, or the line does not start with our prefix.
|
||||
if (hasOtherModifiers || e->text().isEmpty() || !lineUnderCursor().startsWith(prefix)) {
|
||||
c->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto word = wordUnderCursor(); word != c->completionPrefix()) {
|
||||
c->setCompletionPrefix(word);
|
||||
c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
|
||||
}
|
||||
|
||||
QRect cr = cursorRect();
|
||||
cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width());
|
||||
c->complete(cr); // popup it up!
|
||||
}
|
||||
} // namespace Qv2ray::ui::widgets
|
||||
85
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp
vendored
85
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp
vendored
@ -1,85 +0,0 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** BSD License Usage
|
||||
** Alternatively, you may use this file under the terms of the BSD license
|
||||
** as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * 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.
|
||||
** * Neither the name of The Qt Company Ltd 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 COPYRIGHT HOLDERS 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 COPYRIGHT
|
||||
** OWNER 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."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <QAbstractItemModel>
|
||||
#include <QPlainTextEdit>
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QCompleter;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Qv2ray {
|
||||
namespace ui {
|
||||
namespace widgets {
|
||||
class AutoCompleteTextEdit : public QPlainTextEdit {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent = nullptr);
|
||||
~AutoCompleteTextEdit();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void focusInEvent(QFocusEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void insertCompletion(const QString &completion);
|
||||
|
||||
private:
|
||||
QString lineUnderCursor() const;
|
||||
QString wordUnderCursor() const;
|
||||
|
||||
QString prefix;
|
||||
QCompleter *c = nullptr;
|
||||
};
|
||||
} // namespace widgets
|
||||
} // namespace ui
|
||||
} // namespace Qv2ray
|
||||
using namespace Qv2ray::ui::widgets;
|
||||
336
3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp
vendored
336
3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp
vendored
@ -1,336 +0,0 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2011 SCHUTZ Sacha
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "QJsonModel.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
||||
QJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
QJsonTreeItem::~QJsonTreeItem() {
|
||||
qDeleteAll(mChilds);
|
||||
}
|
||||
|
||||
void QJsonTreeItem::appendChild(QJsonTreeItem *item) {
|
||||
mChilds.append(item);
|
||||
}
|
||||
|
||||
QJsonTreeItem *QJsonTreeItem::child(int row) {
|
||||
return mChilds.value(row);
|
||||
}
|
||||
|
||||
QJsonTreeItem *QJsonTreeItem::parent() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
int QJsonTreeItem::childCount() const {
|
||||
return mChilds.count();
|
||||
}
|
||||
|
||||
int QJsonTreeItem::row() const {
|
||||
if (mParent)
|
||||
return mParent->mChilds.indexOf(const_cast<QJsonTreeItem *>(this));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void QJsonTreeItem::setKey(const QString &key) {
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
void QJsonTreeItem::setValue(const QString &value) {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
void QJsonTreeItem::setType(const QJsonValue::Type &type) {
|
||||
mType = type;
|
||||
}
|
||||
|
||||
QString QJsonTreeItem::key() const {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
QString QJsonTreeItem::value() const {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
QJsonValue::Type QJsonTreeItem::type() const {
|
||||
return mType;
|
||||
}
|
||||
|
||||
QJsonTreeItem *QJsonTreeItem::load(const QJsonValue &value, QJsonTreeItem *parent) {
|
||||
QJsonTreeItem *rootItem = new QJsonTreeItem(parent);
|
||||
rootItem->setKey("root");
|
||||
|
||||
if (value.isObject()) {
|
||||
// Get all QJsonValue childs
|
||||
for (QString key: value.toObject().keys()) {
|
||||
QJsonValue v = value.toObject().value(key);
|
||||
QJsonTreeItem *child = load(v, rootItem);
|
||||
child->setKey(key);
|
||||
child->setType(v.type());
|
||||
rootItem->appendChild(child);
|
||||
}
|
||||
} else if (value.isArray()) {
|
||||
// Get all QJsonValue childs
|
||||
int index = 0;
|
||||
|
||||
for (QJsonValue v: value.toArray()) {
|
||||
QJsonTreeItem *child = load(v, rootItem);
|
||||
child->setKey(QString::number(index));
|
||||
child->setType(v.type());
|
||||
rootItem->appendChild(child);
|
||||
++index;
|
||||
}
|
||||
} else {
|
||||
rootItem->setValue(value.toVariant().toString());
|
||||
rootItem->setType(value.type());
|
||||
}
|
||||
|
||||
return rootItem;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
|
||||
QJsonModel::QJsonModel(QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
|
||||
mHeaders.append("key");
|
||||
mHeaders.append("value");
|
||||
}
|
||||
|
||||
QJsonModel::QJsonModel(const QString &fileName, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
|
||||
mHeaders.append("key");
|
||||
mHeaders.append("value");
|
||||
load(fileName);
|
||||
}
|
||||
|
||||
QJsonModel::QJsonModel(QIODevice *device, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
|
||||
mHeaders.append("key");
|
||||
mHeaders.append("value");
|
||||
load(device);
|
||||
}
|
||||
|
||||
QJsonModel::QJsonModel(const QByteArray &json, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} {
|
||||
mHeaders.append("key");
|
||||
mHeaders.append("value");
|
||||
loadJson(json);
|
||||
}
|
||||
|
||||
QJsonModel::~QJsonModel() {
|
||||
delete mRootItem;
|
||||
}
|
||||
|
||||
bool QJsonModel::load(const QString &fileName) {
|
||||
QFile file(fileName);
|
||||
bool success = false;
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
success = load(&file);
|
||||
file.close();
|
||||
} else
|
||||
success = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool QJsonModel::load(QIODevice *device) {
|
||||
return loadJson(device->readAll());
|
||||
}
|
||||
|
||||
bool QJsonModel::loadJson(const QByteArray &json) {
|
||||
auto const &jdoc = QJsonDocument::fromJson(json);
|
||||
|
||||
if (!jdoc.isNull()) {
|
||||
beginResetModel();
|
||||
delete mRootItem;
|
||||
|
||||
if (jdoc.isArray()) {
|
||||
mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.array()));
|
||||
mRootItem->setType(QJsonValue::Array);
|
||||
} else {
|
||||
mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.object()));
|
||||
mRootItem->setType(QJsonValue::Object);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
return true;
|
||||
}
|
||||
|
||||
qDebug() << Q_FUNC_INFO << "cannot load json";
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant QJsonModel::data(const QModelIndex &index, int role) const {
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
QJsonTreeItem *item = static_cast<QJsonTreeItem *>(index.internalPointer());
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (index.column() == 0)
|
||||
return QStringLiteral("%1").arg(item->key());
|
||||
|
||||
if (index.column() == 1)
|
||||
return QStringLiteral("%1").arg(item->value());
|
||||
} else if (Qt::EditRole == role) {
|
||||
if (index.column() == 1) {
|
||||
return QStringLiteral("%1").arg(item->value());
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool QJsonModel::setData(const QModelIndex &index, const QVariant &value, int role) {
|
||||
int col = index.column();
|
||||
|
||||
if (Qt::EditRole == role) {
|
||||
if (col == 1) {
|
||||
QJsonTreeItem *item = static_cast<QJsonTreeItem *>(index.internalPointer());
|
||||
item->setValue(value.toString());
|
||||
emit dataChanged(index, index, {Qt::EditRole});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
if (orientation == Qt::Horizontal) {
|
||||
return mHeaders.value(section);
|
||||
} else
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex QJsonModel::index(int row, int column, const QModelIndex &parent) const {
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
QJsonTreeItem *parentItem;
|
||||
|
||||
if (!parent.isValid())
|
||||
parentItem = mRootItem;
|
||||
else
|
||||
parentItem = static_cast<QJsonTreeItem *>(parent.internalPointer());
|
||||
|
||||
QJsonTreeItem *childItem = parentItem->child(row);
|
||||
|
||||
if (childItem)
|
||||
return createIndex(row, column, childItem);
|
||||
else
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex QJsonModel::parent(const QModelIndex &index) const {
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
QJsonTreeItem *childItem = static_cast<QJsonTreeItem *>(index.internalPointer());
|
||||
QJsonTreeItem *parentItem = childItem->parent();
|
||||
|
||||
if (parentItem == mRootItem)
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
int QJsonModel::rowCount(const QModelIndex &parent) const {
|
||||
QJsonTreeItem *parentItem;
|
||||
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
parentItem = mRootItem;
|
||||
else
|
||||
parentItem = static_cast<QJsonTreeItem *>(parent.internalPointer());
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
|
||||
int QJsonModel::columnCount(const QModelIndex &parent) const {
|
||||
Q_UNUSED(parent)
|
||||
return 2;
|
||||
}
|
||||
|
||||
Qt::ItemFlags QJsonModel::flags(const QModelIndex &index) const {
|
||||
int col = index.column();
|
||||
auto item = static_cast<QJsonTreeItem *>(index.internalPointer());
|
||||
auto isArray = QJsonValue::Array == item->type();
|
||||
auto isObject = QJsonValue::Object == item->type();
|
||||
|
||||
if ((col == 1) && !(isArray || isObject)) {
|
||||
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
|
||||
} else {
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument QJsonModel::json() const {
|
||||
auto v = genJson(mRootItem);
|
||||
QJsonDocument doc;
|
||||
|
||||
if (v.isObject()) {
|
||||
doc = QJsonDocument(v.toObject());
|
||||
} else {
|
||||
doc = QJsonDocument(v.toArray());
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
QJsonValue QJsonModel::genJson(QJsonTreeItem *item) const {
|
||||
auto type = item->type();
|
||||
int nchild = item->childCount();
|
||||
|
||||
if (QJsonValue::Object == type) {
|
||||
QJsonObject jo;
|
||||
|
||||
for (int i = 0; i < nchild; ++i) {
|
||||
auto ch = item->child(i);
|
||||
auto key = ch->key();
|
||||
jo.insert(key, genJson(ch));
|
||||
}
|
||||
|
||||
return jo;
|
||||
} else if (QJsonValue::Array == type) {
|
||||
QJsonArray arr;
|
||||
|
||||
for (int i = 0; i < nchild; ++i) {
|
||||
auto ch = item->child(i);
|
||||
arr.append(genJson(ch));
|
||||
}
|
||||
|
||||
return arr;
|
||||
} else {
|
||||
QJsonValue va(item->value());
|
||||
return va;
|
||||
}
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2011 SCHUTZ Sacha
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QByteArray>
|
||||
#include <QIODevice>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
class QJsonModel;
|
||||
class QJsonItem;
|
||||
|
||||
class QJsonTreeItem {
|
||||
public:
|
||||
QJsonTreeItem(QJsonTreeItem *parent = nullptr);
|
||||
~QJsonTreeItem();
|
||||
void appendChild(QJsonTreeItem *item);
|
||||
QJsonTreeItem *child(int row);
|
||||
QJsonTreeItem *parent();
|
||||
int childCount() const;
|
||||
int row() const;
|
||||
void setKey(const QString &key);
|
||||
void setValue(const QString &value);
|
||||
void setType(const QJsonValue::Type &type);
|
||||
QString key() const;
|
||||
QString value() const;
|
||||
QJsonValue::Type type() const;
|
||||
|
||||
static QJsonTreeItem *load(const QJsonValue &value, QJsonTreeItem *parent = 0);
|
||||
|
||||
protected:
|
||||
private:
|
||||
QString mKey;
|
||||
QString mValue;
|
||||
QJsonValue::Type mType;
|
||||
QList<QJsonTreeItem *> mChilds;
|
||||
QJsonTreeItem *mParent;
|
||||
};
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
class QJsonModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit QJsonModel(QObject *parent = nullptr);
|
||||
QJsonModel(const QString &fileName, QObject *parent = nullptr);
|
||||
QJsonModel(QIODevice *device, QObject *parent = nullptr);
|
||||
QJsonModel(const QByteArray &json, QObject *parent = nullptr);
|
||||
~QJsonModel();
|
||||
bool load(const QString &fileName);
|
||||
bool load(QIODevice *device);
|
||||
bool loadJson(const QByteArray &json);
|
||||
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
|
||||
QJsonDocument json() const;
|
||||
|
||||
private:
|
||||
QJsonValue genJson(QJsonTreeItem *) const;
|
||||
|
||||
QJsonTreeItem *mRootItem;
|
||||
QStringList mHeaders;
|
||||
};
|
||||
@ -1,97 +0,0 @@
|
||||
#include "w_JsonEditor.hpp"
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
JsonEditor::JsonEditor(const QJsonObject& rootObject, QWidget* parent) : QDialog(parent) {
|
||||
setupUi(this);
|
||||
// QvMessageBusConnect(JsonEditor);
|
||||
//
|
||||
original = rootObject;
|
||||
final = rootObject;
|
||||
QString jsonString = JsonToString(rootObject);
|
||||
|
||||
if (VerifyJsonString(jsonString).isEmpty()) {
|
||||
jsonTree->setModel(&model);
|
||||
model.loadJson(QJsonDocument(rootObject).toJson());
|
||||
} else {
|
||||
QvMessageBoxWarn(this, tr("Json Contains Syntax Errors"),
|
||||
tr("Original Json may contain syntax errors. Json tree is disabled."));
|
||||
}
|
||||
|
||||
jsonEditor->setText(JsonToString(rootObject));
|
||||
jsonTree->expandAll();
|
||||
jsonTree->resizeColumnToContents(0);
|
||||
}
|
||||
|
||||
// QvMessageBusSlotImpl(JsonEditor)
|
||||
// {
|
||||
// switch (msg)
|
||||
// {
|
||||
// MBShowDefaultImpl;
|
||||
// MBHideDefaultImpl;
|
||||
// MBRetranslateDefaultImpl;
|
||||
// case UPDATE_COLORSCHEME:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
QJsonObject JsonEditor::OpenEditor() {
|
||||
int resultCode = this->exec();
|
||||
auto string = jsonEditor->toPlainText();
|
||||
|
||||
while (resultCode == QDialog::Accepted && !VerifyJsonString(string).isEmpty()) {
|
||||
if (string.isEmpty()) {
|
||||
resultCode = QDialog::Accepted;
|
||||
final = {};
|
||||
break;
|
||||
}
|
||||
QvMessageBoxWarn(this, tr("Json Contains Syntax Errors"),
|
||||
tr("You must correct these errors before continuing."));
|
||||
resultCode = this->exec();
|
||||
string = jsonEditor->toPlainText();
|
||||
}
|
||||
|
||||
return resultCode == QDialog::Accepted ? final : original;
|
||||
}
|
||||
|
||||
JsonEditor::~JsonEditor() {
|
||||
}
|
||||
|
||||
void JsonEditor::on_jsonEditor_textChanged() {
|
||||
auto string = jsonEditor->toPlainText();
|
||||
auto VerifyResult = VerifyJsonString(string);
|
||||
jsonValidateStatus->setText(VerifyResult);
|
||||
|
||||
if (VerifyResult.isEmpty()) {
|
||||
BLACK(jsonEditor);
|
||||
final = JsonFromString(string);
|
||||
model.loadJson(QJsonDocument(final).toJson());
|
||||
jsonTree->expandAll();
|
||||
jsonTree->resizeColumnToContents(0);
|
||||
} else {
|
||||
RED(jsonEditor);
|
||||
}
|
||||
}
|
||||
|
||||
void JsonEditor::on_formatJsonBtn_clicked() {
|
||||
auto string = jsonEditor->toPlainText();
|
||||
auto VerifyResult = VerifyJsonString(string);
|
||||
jsonValidateStatus->setText(VerifyResult);
|
||||
|
||||
if (VerifyResult.isEmpty()) {
|
||||
BLACK(jsonEditor);
|
||||
jsonEditor->setPlainText(JsonToString(JsonFromString(string)));
|
||||
model.loadJson(QJsonDocument(JsonFromString(string)).toJson());
|
||||
jsonTree->setModel(&model);
|
||||
jsonTree->expandAll();
|
||||
jsonTree->resizeColumnToContents(0);
|
||||
} else {
|
||||
RED(jsonEditor);
|
||||
QvMessageBoxWarn(this, tr("Syntax Errors"),
|
||||
tr("Please fix the JSON errors or remove the comments before continue"));
|
||||
}
|
||||
}
|
||||
|
||||
void JsonEditor::on_removeCommentsBtn_clicked() {
|
||||
jsonEditor->setPlainText(JsonToString(JsonFromString(jsonEditor->toPlainText())));
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "3rdparty/qv2ray/wrapper.hpp"
|
||||
#include "3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.hpp"
|
||||
#include "ui_w_JsonEditor.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class JsonEditor
|
||||
: public QDialog,
|
||||
private Ui::JsonEditor {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JsonEditor(const QJsonObject& rootObject, QWidget* parent = nullptr);
|
||||
~JsonEditor();
|
||||
QJsonObject OpenEditor();
|
||||
|
||||
private slots:
|
||||
void on_jsonEditor_textChanged();
|
||||
|
||||
void on_formatJsonBtn_clicked();
|
||||
|
||||
void on_removeCommentsBtn_clicked();
|
||||
|
||||
private:
|
||||
QJsonModel model;
|
||||
QJsonObject original;
|
||||
QJsonObject final;
|
||||
};
|
||||
@ -1,172 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>JsonEditor</class>
|
||||
<widget class="QDialog" name="JsonEditor">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>889</width>
|
||||
<height>572</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>JSON Editor</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidgetx">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTextEdit" name="jsonEditor">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
<property name="acceptRichText">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="formatJsonBtn">
|
||||
<property name="text">
|
||||
<string>Format JSON</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="removeCommentsBtn">
|
||||
<property name="text">
|
||||
<string>Remove All Comments</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Json Editor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Structure Preview</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="jsonTree">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indentation">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="headerMinimumSectionSize">
|
||||
<number>132</number>
|
||||
</attribute>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>152</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="jsonValidateStatus">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>jsonEditor</tabstop>
|
||||
<tabstop>formatJsonBtn</tabstop>
|
||||
<tabstop>removeCommentsBtn</tabstop>
|
||||
<tabstop>jsonTree</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>JsonEditor</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>JsonEditor</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@ -1,42 +0,0 @@
|
||||
#include "GeositeReader.hpp"
|
||||
|
||||
#include "3rdparty/qv2ray/wrapper.hpp"
|
||||
#include "picoproto.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QMap>
|
||||
|
||||
namespace Qv2ray::components::GeositeReader {
|
||||
QMap<QString, QStringList> 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::GeositeReader
|
||||
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Qv2ray::components::GeositeReader {
|
||||
QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache = true);
|
||||
} // namespace Qv2ray::components::GeositeReader
|
||||
@ -1,551 +0,0 @@
|
||||
/* 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<class Dest, class Source>
|
||||
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<class T>
|
||||
T ReadFromBytes(uint8_t **current, size_t *remaining) {
|
||||
PP_CHECK(ConsumeBytes(current, sizeof(T), remaining));
|
||||
const T result = *(bit_cast<T *>(*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<uint8_t>(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<uint32_t>();
|
||||
} break;
|
||||
case FIELD_UINT64: {
|
||||
value.v_uint64 = new std::vector<uint64_t>();
|
||||
} break;
|
||||
case FIELD_BYTES: {
|
||||
value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();
|
||||
cached_messages = new std::vector<Message *>();
|
||||
} 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<uint32_t>(*other.value.v_uint32);
|
||||
} break;
|
||||
case FIELD_UINT64: {
|
||||
value.v_uint64 = new std::vector<uint64_t>(*other.value.v_uint64);
|
||||
} break;
|
||||
case FIELD_BYTES: {
|
||||
if (owns_data) {
|
||||
value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();
|
||||
for (std::pair<uint8_t *, size_t> 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<std::pair<uint8_t *, size_t>>(*other.value.v_bytes);
|
||||
}
|
||||
cached_messages = new std::vector<Message *>();
|
||||
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<uint8_t *, size_t> 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<uint64_t>(¤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<uint32_t>(¤t, &remaining);
|
||||
field->value.v_uint32->push_back(value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
PP_LOG(ERROR) << "Unknown wire type encountered: " << static_cast<int>(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<int32_t>((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<int64_t>((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<float *>(&int_value));
|
||||
return float_value;
|
||||
}
|
||||
|
||||
double Message::GetDouble(int32_t number) {
|
||||
uint64_t int_value = GetUInt64(number);
|
||||
return *(bit_cast<double *>(&int_value));
|
||||
}
|
||||
|
||||
std::pair<uint8_t *, size_t> Message::GetBytes(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_BYTES);
|
||||
std::pair<uint8_t *, size_t> 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<uint8_t *, size_t> 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<uint8_t *, size_t> 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<int32_t> Message::GetInt32Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<int32_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
int32_t zig_zag_decoded = static_cast<int32_t>((raw_value >> 1) ^ (-(raw_value & 1)));
|
||||
result.push_back(zig_zag_decoded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int64_t> Message::GetInt64Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<int64_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
int64_t zig_zag_decoded = static_cast<int64_t>((raw_value >> 1) ^ (-(raw_value & 1)));
|
||||
result.push_back(zig_zag_decoded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Message::GetUInt32Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<uint32_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
result.push_back(static_cast<uint32_t>(raw_value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> Message::GetUInt64Array(int32_t number) {
|
||||
std::vector<uint64_t> 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<uint64_t>(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<uint64_t>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> 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<int64_t>(varint));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT32, UINT64, or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> Message::GetBoolArray(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<bool> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
result.push_back(raw_value != 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<float> Message::GetFloatArray(int32_t number) {
|
||||
std::vector<float> 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<float>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> 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<uint32_t>(varint & 0xffffffff);
|
||||
result.push_back(bit_cast<float>(varint32));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT32 or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> Message::GetDoubleArray(int32_t number) {
|
||||
std::vector<double> 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<double>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> 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<double>(varint));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT64 or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint8_t *, size_t>> Message::GetByteArray(int32_t number) {
|
||||
std::vector<std::pair<uint8_t *, size_t>> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
return result;
|
||||
}
|
||||
if (field->type == FIELD_BYTES) {
|
||||
result.reserve(field->value.v_bytes->size());
|
||||
for (std::pair<uint8_t *, size_t> 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<std::string> Message::GetStringArray(int32_t number) {
|
||||
std::vector<std::string> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field)
|
||||
return result;
|
||||
if (field->type == FIELD_BYTES) {
|
||||
result.reserve(field->value.v_bytes->size());
|
||||
for (std::pair<uint8_t *, size_t> 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 *> Message::GetMessageArray(int32_t number) {
|
||||
std::vector<Message *> 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<uint8_t *, size_t> 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
|
||||
@ -1,208 +0,0 @@
|
||||
/* 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<picoproto::Message*> 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 <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 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<uint32_t> *v_uint32;
|
||||
std::vector<uint64_t> *v_uint64;
|
||||
std::vector<std::pair<uint8_t *, size_t>> *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<Message *> *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<uint8_t *, size_t> 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<int32_t> GetInt32Array(int32_t number);
|
||||
std::vector<int64_t> GetInt64Array(int32_t number);
|
||||
std::vector<uint32_t> GetUInt32Array(int32_t number);
|
||||
std::vector<uint64_t> GetUInt64Array(int32_t number);
|
||||
std::vector<bool> GetBoolArray(int32_t number);
|
||||
std::vector<float> GetFloatArray(int32_t number);
|
||||
std::vector<double> GetDoubleArray(int32_t number);
|
||||
std::vector<std::pair<uint8_t *, size_t>> GetByteArray(int32_t number);
|
||||
std::vector<std::string> GetStringArray(int32_t number);
|
||||
std::vector<Message *> 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<int32_t, size_t> field_map;
|
||||
// The core list of fields that have been parsed.
|
||||
std::vector<Field> fields;
|
||||
bool copy_arrays;
|
||||
};
|
||||
|
||||
} // namespace picoproto
|
||||
|
||||
#endif // INCLUDE_PICOPROTO_H
|
||||
41
3rdparty/qv2ray/wrapper.hpp
vendored
41
3rdparty/qv2ray/wrapper.hpp
vendored
@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Qv2ray wrapper
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QDebug>
|
||||
|
||||
#define LOG(...) Qv2ray::base::log_internal(__VA_ARGS__)
|
||||
#define DEBUG(...) Qv2ray::base::log_internal(__VA_ARGS__)
|
||||
namespace Qv2ray {
|
||||
namespace base {
|
||||
template<typename... T>
|
||||
inline void log_internal(T... v) {}
|
||||
} // namespace base
|
||||
} // namespace Qv2ray
|
||||
|
||||
#define JsonToString(a) QJsonObject2QString(a, false)
|
||||
#define JsonFromString(a) QString2QJsonObject(a)
|
||||
#define QvMessageBoxWarn(a, b, c) MessageBoxWarning(b, c)
|
||||
|
||||
inline QString VerifyJsonString(const QString &source) {
|
||||
QJsonParseError error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error);
|
||||
Q_UNUSED(doc)
|
||||
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
return "";
|
||||
} else {
|
||||
// LOG("WARNING: Json parse returns: " + error.errorString());
|
||||
return error.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
#define RED(obj) \
|
||||
{ \
|
||||
auto _temp = obj->palette(); \
|
||||
_temp.setColor(QPalette::Text, Qt::red); \
|
||||
obj->setPalette(_temp); \
|
||||
}
|
||||
|
||||
#define BLACK(obj) obj->setPalette(QWidget::palette());
|
||||
302
CMakeLists.txt
302
CMakeLists.txt
@ -1,302 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(nekobox VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# WINDOWS PDB FILE
|
||||
if (WIN32)
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Find Qt
|
||||
if (NOT QT_VERSION_MAJOR)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
endif ()
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools)
|
||||
|
||||
if (NKR_CROSS)
|
||||
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
|
||||
set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
|
||||
set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
|
||||
set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
|
||||
set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
|
||||
endif ()
|
||||
|
||||
#### Platform Variables ####
|
||||
if (WIN32)
|
||||
include("cmake/windows/windows.cmake")
|
||||
else ()
|
||||
include("cmake/linux/linux.cmake")
|
||||
endif ()
|
||||
|
||||
#### default prefix path ####
|
||||
|
||||
if (NOT NKR_LIBS)
|
||||
if (NKR_PACKAGE)
|
||||
list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/package)
|
||||
else ()
|
||||
list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/built)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (NOT NKR_DISABLE_LIBS)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${NKR_LIBS})
|
||||
endif ()
|
||||
|
||||
message("[CMAKE_PREFIX_PATH] ${CMAKE_PREFIX_PATH}")
|
||||
|
||||
# for some cross toolchain
|
||||
list(APPEND CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH})
|
||||
message("[CMAKE_FIND_ROOT_PATH] ${CMAKE_FIND_ROOT_PATH}")
|
||||
|
||||
#### NKR ####
|
||||
|
||||
include("cmake/print.cmake")
|
||||
include("cmake/nkr.cmake")
|
||||
|
||||
find_package(Threads)
|
||||
|
||||
#### NKR EXTERNAL ####
|
||||
|
||||
if (NKR_NO_EXTERNAL)
|
||||
set(NKR_NO_GRPC 1)
|
||||
set(NKR_NO_YAML 1)
|
||||
set(NKR_NO_ZXING 1)
|
||||
set(NKR_NO_QHOTKEY 1)
|
||||
endif ()
|
||||
|
||||
# grpc
|
||||
if (NKR_NO_GRPC)
|
||||
nkr_add_compile_definitions(NKR_NO_GRPC)
|
||||
else ()
|
||||
# My proto
|
||||
include("cmake/myproto.cmake")
|
||||
list(APPEND NKR_EXTERNAL_TARGETS myproto)
|
||||
endif ()
|
||||
|
||||
# yaml-cpp
|
||||
if (NKR_NO_YAML)
|
||||
nkr_add_compile_definitions(NKR_NO_YAML)
|
||||
else ()
|
||||
find_package(yaml-cpp CONFIG REQUIRED) # only Release is built
|
||||
list(APPEND NKR_EXTERNAL_TARGETS yaml-cpp)
|
||||
endif ()
|
||||
|
||||
# zxing-cpp
|
||||
if (NKR_NO_ZXING)
|
||||
nkr_add_compile_definitions(NKR_NO_ZXING)
|
||||
else ()
|
||||
find_package(ZXing CONFIG REQUIRED)
|
||||
list(APPEND NKR_EXTERNAL_TARGETS ZXing::ZXing)
|
||||
endif ()
|
||||
|
||||
# QHotkey (static submodule)
|
||||
if (NKR_NO_QHOTKEY)
|
||||
nkr_add_compile_definitions(NKR_NO_QHOTKEY)
|
||||
else ()
|
||||
set(QHOTKEY_INSTALL OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(3rdparty/QHotkey)
|
||||
list(APPEND NKR_EXTERNAL_TARGETS qhotkey)
|
||||
endif ()
|
||||
|
||||
#### debug print ####
|
||||
|
||||
if (DBG_CMAKE)
|
||||
print_all_variables()
|
||||
print_target_properties(myproto)
|
||||
print_target_properties(yaml-cpp)
|
||||
print_target_properties(ZXing::ZXing)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")
|
||||
endif ()
|
||||
|
||||
# Sources
|
||||
set(PROJECT_SOURCES
|
||||
${PLATFORM_SOURCES}
|
||||
|
||||
main/main.cpp
|
||||
main/NekoGui.cpp
|
||||
main/NekoGui_Utils.cpp
|
||||
main/HTTPRequestHelper.cpp
|
||||
|
||||
3rdparty/base64.cpp
|
||||
3rdparty/qrcodegen.cpp
|
||||
3rdparty/QtExtKeySequenceEdit.cpp
|
||||
|
||||
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp
|
||||
3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui
|
||||
|
||||
3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp
|
||||
3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp
|
||||
|
||||
rpc/gRPC.cpp
|
||||
|
||||
db/Database.cpp
|
||||
db/traffic/TrafficLooper.cpp
|
||||
db/ProfileFilter.cpp
|
||||
db/ConfigBuilder.cpp
|
||||
|
||||
fmt/AbstractBean.cpp
|
||||
fmt/Bean2CoreObj_box.cpp
|
||||
fmt/Bean2External.cpp
|
||||
fmt/Bean2Link.cpp
|
||||
fmt/Link2Bean.cpp
|
||||
fmt/ChainBean.hpp # translate
|
||||
|
||||
sub/GroupUpdater.cpp
|
||||
|
||||
sys/ExternalProcess.cpp
|
||||
sys/AutoRun.cpp
|
||||
|
||||
ui/ThemeManager.cpp
|
||||
ui/Icon.cpp
|
||||
|
||||
ui/mainwindow_grpc.cpp
|
||||
ui/mainwindow.cpp
|
||||
ui/mainwindow.h
|
||||
ui/mainwindow.ui
|
||||
|
||||
ui/edit/dialog_edit_profile.h
|
||||
ui/edit/dialog_edit_profile.cpp
|
||||
ui/edit/dialog_edit_profile.ui
|
||||
ui/edit/dialog_edit_group.h
|
||||
ui/edit/dialog_edit_group.cpp
|
||||
ui/edit/dialog_edit_group.ui
|
||||
|
||||
ui/edit/edit_chain.h
|
||||
ui/edit/edit_chain.cpp
|
||||
ui/edit/edit_chain.ui
|
||||
ui/edit/edit_socks_http.h
|
||||
ui/edit/edit_socks_http.cpp
|
||||
ui/edit/edit_socks_http.ui
|
||||
ui/edit/edit_shadowsocks.h
|
||||
ui/edit/edit_shadowsocks.cpp
|
||||
ui/edit/edit_shadowsocks.ui
|
||||
ui/edit/edit_vmess.h
|
||||
ui/edit/edit_vmess.cpp
|
||||
ui/edit/edit_vmess.ui
|
||||
ui/edit/edit_trojan_vless.h
|
||||
ui/edit/edit_trojan_vless.cpp
|
||||
ui/edit/edit_trojan_vless.ui
|
||||
|
||||
ui/edit/edit_naive.h
|
||||
ui/edit/edit_naive.cpp
|
||||
ui/edit/edit_naive.ui
|
||||
|
||||
ui/edit/edit_quic.h
|
||||
ui/edit/edit_quic.cpp
|
||||
ui/edit/edit_quic.ui
|
||||
|
||||
ui/edit/edit_custom.h
|
||||
ui/edit/edit_custom.cpp
|
||||
ui/edit/edit_custom.ui
|
||||
|
||||
ui/dialog_basic_settings.cpp
|
||||
ui/dialog_basic_settings.h
|
||||
ui/dialog_basic_settings.ui
|
||||
|
||||
ui/dialog_manage_groups.cpp
|
||||
ui/dialog_manage_groups.h
|
||||
ui/dialog_manage_groups.ui
|
||||
|
||||
ui/dialog_manage_routes.cpp
|
||||
ui/dialog_manage_routes.h
|
||||
ui/dialog_manage_routes.ui
|
||||
|
||||
ui/dialog_vpn_settings.cpp
|
||||
ui/dialog_vpn_settings.h
|
||||
ui/dialog_vpn_settings.ui
|
||||
|
||||
ui/dialog_hotkey.cpp
|
||||
ui/dialog_hotkey.h
|
||||
ui/dialog_hotkey.ui
|
||||
|
||||
ui/widget/ProxyItem.cpp
|
||||
ui/widget/ProxyItem.h
|
||||
ui/widget/ProxyItem.ui
|
||||
ui/widget/GroupItem.cpp
|
||||
ui/widget/GroupItem.h
|
||||
ui/widget/GroupItem.ui
|
||||
|
||||
res/neko.qrc
|
||||
res/theme/feiyangqingyun/qss.qrc
|
||||
${QV2RAY_RC}
|
||||
)
|
||||
|
||||
# Qt exe
|
||||
if (${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(nekobox
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET nekobox APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||
else ()
|
||||
if (ANDROID)
|
||||
add_library(nekobox SHARED
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
else ()
|
||||
add_executable(nekobox
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Target
|
||||
|
||||
set_property(TARGET nekobox PROPERTY AUTOUIC ON)
|
||||
set_property(TARGET nekobox PROPERTY AUTOMOC ON)
|
||||
set_property(TARGET nekobox PROPERTY AUTORCC ON)
|
||||
|
||||
set_target_properties(nekobox PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
# Target Source Translations
|
||||
|
||||
set(TS_FILES
|
||||
translations/zh_CN.ts
|
||||
translations/fa_IR.ts
|
||||
translations/ru_RU.ts
|
||||
)
|
||||
set(LUPDATE_OPTIONS
|
||||
-locations none -no-obsolete
|
||||
)
|
||||
if (${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_lupdate(nekobox TS_FILES ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS})
|
||||
qt_add_lrelease(nekobox TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES)
|
||||
else ()
|
||||
qt5_create_translation(QM_FILES ${PROJECT_SOURCES} ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS})
|
||||
endif ()
|
||||
configure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY)
|
||||
target_sources(nekobox PRIVATE ${CMAKE_BINARY_DIR}/translations.qrc)
|
||||
|
||||
# Target Link
|
||||
|
||||
target_link_libraries(nekobox PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg
|
||||
Threads::Threads
|
||||
${NKR_EXTERNAL_TARGETS}
|
||||
${PLATFORM_LIBRARIES}
|
||||
)
|
||||
|
||||
if (QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_finalize_executable(nekobox)
|
||||
endif ()
|
||||
674
LICENSE
674
LICENSE
@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
119
README.md
119
README.md
@ -1,119 +0,0 @@
|
||||
# NekoBox For PC
|
||||
|
||||
Qt based cross-platform GUI proxy configuration manager (backend: sing-box)
|
||||
|
||||
Support Windows / Linux out of the box now.
|
||||
|
||||
基于 Qt 的跨平台代理配置管理器 (后端 sing-box)
|
||||
|
||||
目前支持 Windows / Linux 开箱即用
|
||||
|
||||
## 下载 / Download
|
||||
|
||||
### GitHub Releases (Portable ZIP)
|
||||
|
||||
便携格式,无安装器。转到 Releases 下载预编译的二进制文件,解压后即可使用。
|
||||
|
||||
[](https://github.com/Matsuridayo/nekoray/releases)
|
||||
|
||||
[下载 / Download](https://github.com/Matsuridayo/nekoray/releases)
|
||||
|
||||
[安装包的说明,如果你不知道要下载哪一个](https://github.com/MatsuriDayo/nekoray/wiki/Installation-package-description)
|
||||
|
||||
### Package
|
||||
|
||||
#### AUR
|
||||
|
||||
- [nekoray](https://aur.archlinux.org/packages/nekoray)
|
||||
- [nekoray-git](https://aur.archlinux.org/packages/nekoray-git)
|
||||
|
||||
#### archlinuxcn
|
||||
|
||||
- [nekoray](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray)
|
||||
- [nekoray-git](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray-git)
|
||||
|
||||
#### Scoop Extras
|
||||
|
||||
`scoop install nekoray`
|
||||
|
||||
## 更改记录 & 发布频道 / Changelog & Telegram Channel
|
||||
|
||||
https://t.me/Matsuridayo
|
||||
|
||||
## 项目主页 & 文档 / Homepage & Documents
|
||||
|
||||
https://matsuridayo.github.io
|
||||
|
||||
## 代理 / Proxy
|
||||
|
||||
- SOCKS (4/4a/5)
|
||||
- HTTP(S)
|
||||
- Shadowsocks
|
||||
- VMess
|
||||
- VLESS
|
||||
- Trojan
|
||||
- TUIC ( sing-box )
|
||||
- NaïveProxy ( Custom Core )
|
||||
- Hysteria2 ( Custom Core or sing-box )
|
||||
- Custom Outbound
|
||||
- Custom Config
|
||||
- Custom Core
|
||||
|
||||
## 订阅 / Subscription
|
||||
|
||||
- Raw: some widely used formats (like Shadowsocks, Clash and v2rayN)
|
||||
- 原始格式: 一些广泛使用的格式 (如 Shadowsocks、Clash 和 v2rayN)
|
||||
|
||||
## 运行参数
|
||||
|
||||
[运行参数](docs/RunFlags.md)
|
||||
|
||||
## Windows 运行
|
||||
|
||||
若提示 DLL 缺失,无法运行,请下载 安装 [微软 C++ 运行库](https://aka.ms/vs/17/release/vc_redist.x64.exe)
|
||||
|
||||
## Linux 运行
|
||||
|
||||
[Linux 运行教程](docs/Run_Linux.md)
|
||||
|
||||
## 编译教程 / Compile Tutorial
|
||||
|
||||
请看 [技术文档 / Technical documentation](https://github.com/MatsuriDayo/nekoray/tree/main/docs)
|
||||
|
||||
## 捐助 / Donate
|
||||
|
||||
如果这个项目对您有帮助,可以通过捐赠的方式帮助我们维持这个项目。
|
||||
|
||||
捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像,如果您未被添加到这里,欢迎联系我们补充。
|
||||
|
||||
Donations of 50 USD or more can display your avatar on the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please contact us to add it.
|
||||
|
||||
USDT TRC20
|
||||
|
||||
`TRhnA7SXE5Sap5gSG3ijxRmdYFiD4KRhPs`
|
||||
|
||||
XMR
|
||||
|
||||
`49bwESYQjoRL3xmvTcjZKHEKaiGywjLYVQJMUv79bXonGiyDCs8AzE3KiGW2ytTybBCpWJUvov8SjZZEGg66a4e59GXa6k5`
|
||||
|
||||
## Credits
|
||||
|
||||
Core:
|
||||
|
||||
- [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core) ( < 3.10 )
|
||||
- [MatsuriDayo/Matsuri](https://github.com/MatsuriDayo/Matsuri) ( < 3.10 )
|
||||
- [MatsuriDayo/v2ray-core](https://github.com/MatsuriDayo/v2ray-core) ( < 3.10 )
|
||||
- [XTLS/Xray-core](https://github.com/XTLS/Xray-core) ( 3.10 <= Version <= 3.26 )
|
||||
- [MatsuriDayo/Xray-core](https://github.com/MatsuriDayo/Xray-core) ( 3.10 <= Version <= 3.26 )
|
||||
- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
|
||||
- [Matsuridayo/sing-box-extra](https://github.com/MatsuriDayo/sing-box-extra)
|
||||
|
||||
Gui:
|
||||
|
||||
- [Qv2ray](https://github.com/Qv2ray/Qv2ray)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [protobuf](https://github.com/protocolbuffers/protobuf)
|
||||
- [yaml-cpp](https://github.com/jbeder/yaml-cpp)
|
||||
- [zxing-cpp](https://github.com/nu-book/zxing-cpp)
|
||||
- [QHotkey](https://github.com/Skycoder42/QHotkey)
|
||||
- [AppImageKit](https://github.com/AppImage/AppImageKit)
|
||||
@ -1,2 +0,0 @@
|
||||
set(PLATFORM_SOURCES sys/linux/LinuxCap.cpp)
|
||||
set(PLATFORM_LIBRARIES dl)
|
||||
@ -1,14 +0,0 @@
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
|
||||
set(PROTO_FILES
|
||||
go/grpc_server/gen/libcore.proto
|
||||
)
|
||||
|
||||
add_library(myproto STATIC ${PROTO_FILES})
|
||||
target_link_libraries(myproto
|
||||
PUBLIC
|
||||
protobuf::libprotobuf
|
||||
)
|
||||
target_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
protobuf_generate(TARGET myproto LANGUAGE cpp)
|
||||
@ -1,12 +0,0 @@
|
||||
# Release
|
||||
file(STRINGS nekoray_version.txt NKR_VERSION)
|
||||
add_compile_definitions(NKR_VERSION=\"${NKR_VERSION}\")
|
||||
|
||||
# Debug
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DNKR_CPP_DEBUG")
|
||||
|
||||
# Func
|
||||
function(nkr_add_compile_definitions arg)
|
||||
message("[add_compile_definitions] ${ARGV}")
|
||||
add_compile_definitions(${ARGV})
|
||||
endfunction()
|
||||
@ -1,43 +0,0 @@
|
||||
macro(print_all_variables)
|
||||
message(STATUS "print_all_variables------------------------------------------{")
|
||||
get_cmake_property(_variableNames VARIABLES)
|
||||
foreach (_variableName ${_variableNames})
|
||||
message(STATUS "${_variableName}=${${_variableName}}")
|
||||
endforeach()
|
||||
message(STATUS "print_all_variables------------------------------------------}")
|
||||
endmacro()
|
||||
|
||||
# Get all propreties that cmake supports
|
||||
if(NOT CMAKE_PROPERTY_LIST)
|
||||
execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)
|
||||
|
||||
# Convert command output into a CMake list
|
||||
string(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
|
||||
string(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
|
||||
endif()
|
||||
|
||||
function(print_properties)
|
||||
message("CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}")
|
||||
endfunction()
|
||||
|
||||
function(print_target_properties target)
|
||||
if(NOT TARGET ${target})
|
||||
message(STATUS "There is no target named '${target}'")
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(property ${CMAKE_PROPERTY_LIST})
|
||||
string(REPLACE "<CONFIG>" "${CMAKE_BUILD_TYPE}" property ${property})
|
||||
|
||||
# Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i
|
||||
if(property STREQUAL "LOCATION" OR property MATCHES "^LOCATION_" OR property MATCHES "_LOCATION$")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
get_property(was_set TARGET ${target} PROPERTY ${property} SET)
|
||||
if(was_set)
|
||||
get_target_property(value ${target} ${property})
|
||||
message("${target} ${property} = ${value}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PRODUCT_VERSION_MAJOR
|
||||
#define PRODUCT_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_MINOR
|
||||
#define PRODUCT_VERSION_MINOR @PRODUCT_VERSION_MINOR@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_PATCH
|
||||
#define PRODUCT_VERSION_PATCH @PRODUCT_VERSION_PATCH@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_BUILD
|
||||
#define PRODUCT_VERSION_BUILD @PRODUCT_VERSION_REVISION@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_MAJOR
|
||||
#define FILE_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_MINOR
|
||||
#define FILE_VERSION_MINOR @PRODUCT_VERSION_MINOR@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_PATCH
|
||||
#define FILE_VERSION_PATCH @PRODUCT_VERSION_PATCH@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_BUILD
|
||||
#define FILE_VERSION_BUILD @PRODUCT_VERSION_REVISION@
|
||||
#endif
|
||||
|
||||
#ifndef __TO_STRING
|
||||
#define __TO_STRING_IMPL(x) #x
|
||||
#define __TO_STRING(x) __TO_STRING_IMPL(x)
|
||||
#endif
|
||||
|
||||
#define PRODUCT_VERSION_MAJOR_MINOR_STR __TO_STRING(PRODUCT_VERSION_MAJOR) "." __TO_STRING(PRODUCT_VERSION_MINOR)
|
||||
#define PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR PRODUCT_VERSION_MAJOR_MINOR_STR "." __TO_STRING(PRODUCT_VERSION_PATCH)
|
||||
#define PRODUCT_VERSION_FULL_STR PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(PRODUCT_VERSION_BUILD)
|
||||
#define PRODUCT_VERSION_RESOURCE PRODUCT_VERSION_MAJOR,PRODUCT_VERSION_MINOR,PRODUCT_VERSION_PATCH,PRODUCT_VERSION_BUILD
|
||||
#define PRODUCT_VERSION_RESOURCE_STR PRODUCT_VERSION_FULL_STR "\0"
|
||||
|
||||
#define FILE_VERSION_MAJOR_MINOR_STR __TO_STRING(FILE_VERSION_MAJOR) "." __TO_STRING(FILE_VERSION_MINOR)
|
||||
#define FILE_VERSION_MAJOR_MINOR_PATCH_STR FILE_VERSION_MAJOR_MINOR_STR "." __TO_STRING(FILE_VERSION_PATCH)
|
||||
#define FILE_VERSION_FULL_STR FILE_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(FILE_VERSION_BUILD)
|
||||
#define FILE_VERSION_RESOURCE FILE_VERSION_MAJOR,FILE_VERSION_MINOR,FILE_VERSION_PATCH,FILE_VERSION_BUILD
|
||||
#define FILE_VERSION_RESOURCE_STR FILE_VERSION_FULL_STR "\0"
|
||||
|
||||
#ifndef PRODUCT_ICON
|
||||
#define PRODUCT_ICON "@PRODUCT_ICON@"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMMENTS
|
||||
#define PRODUCT_COMMENTS "@PRODUCT_COMMENTS@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMPANY_NAME
|
||||
#define PRODUCT_COMPANY_NAME "@PRODUCT_COMPANY_NAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMPANY_COPYRIGHT
|
||||
#define PRODUCT_COMPANY_COPYRIGHT "@PRODUCT_COMPANY_COPYRIGHT@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_FILE_DESCRIPTION
|
||||
#define PRODUCT_FILE_DESCRIPTION "@PRODUCT_FILE_DESCRIPTION@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_INTERNAL_NAME
|
||||
#define PRODUCT_INTERNAL_NAME "@PRODUCT_NAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_ORIGINAL_FILENAME
|
||||
#define PRODUCT_ORIGINAL_FILENAME "@PRODUCT_ORIGINAL_FILENAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_BUNDLE
|
||||
#define PRODUCT_BUNDLE "@PRODUCT_BUNDLE@\0"
|
||||
#endif
|
||||
@ -1,52 +0,0 @@
|
||||
#include "VersionInfo.h"
|
||||
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
IDI_ICON1 ICON PRODUCT_ICON
|
||||
|
||||
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION FILE_VERSION_RESOURCE
|
||||
PRODUCTVERSION PRODUCT_VERSION_RESOURCE
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x4L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "000904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", PRODUCT_COMMENTS
|
||||
VALUE "CompanyName", PRODUCT_COMPANY_NAME
|
||||
VALUE "FileDescription", PRODUCT_FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_RESOURCE_STR
|
||||
VALUE "InternalName", PRODUCT_INTERNAL_NAME
|
||||
VALUE "LegalCopyright", PRODUCT_COMPANY_COPYRIGHT
|
||||
VALUE "OriginalFilename", PRODUCT_ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_BUNDLE
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_RESOURCE_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x9, 1200
|
||||
END
|
||||
END
|
||||
@ -1,107 +0,0 @@
|
||||
include (CMakeParseArguments)
|
||||
|
||||
set (GenerateProductVersionCurrentDir ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# generate_product_version() function
|
||||
#
|
||||
# This function uses VersionInfo.in template file and VersionResource.rc file
|
||||
# to generate WIN32 resource with version information and general resource strings.
|
||||
#
|
||||
# Usage:
|
||||
# generate_product_version(
|
||||
# SomeOutputResourceVariable
|
||||
# NAME MyGreatProject
|
||||
# ICON ${PATH_TO_APP_ICON}
|
||||
# VERSION_MAJOR 2
|
||||
# VERSION_MINOR 3
|
||||
# VERSION_PATCH ${BUILD_COUNTER}
|
||||
# VERSION_REVISION ${BUILD_REVISION}
|
||||
# )
|
||||
# where BUILD_COUNTER and BUILD_REVISION could be values from your CI server.
|
||||
#
|
||||
# You can use generated resource for your executable targets:
|
||||
# add_executable(target-name ${target-files} ${SomeOutputResourceVariable})
|
||||
#
|
||||
# You can specify resource strings in arguments:
|
||||
# NAME - name of executable (no defaults, ex: Microsoft Word)
|
||||
# BUNDLE - bundle (${NAME} is default, ex: Microsoft Office)
|
||||
# ICON - path to application icon (${CMAKE_SOURCE_DIR}/product.ico by default)
|
||||
# VERSION_MAJOR - 1 is default
|
||||
# VERSION_MINOR - 0 is default
|
||||
# VERSION_PATCH - 0 is default
|
||||
# VERSION_REVISION - 0 is default
|
||||
# COMPANY_NAME - your company name (no defaults)
|
||||
# COMPANY_COPYRIGHT - ${COMPANY_NAME} (C) Copyright ${CURRENT_YEAR} is default
|
||||
# COMMENTS - ${NAME} v${VERSION_MAJOR}.${VERSION_MINOR} is default
|
||||
# ORIGINAL_FILENAME - ${NAME} is default
|
||||
# INTERNAL_NAME - ${NAME} is default
|
||||
# FILE_DESCRIPTION - ${NAME} is default
|
||||
function(generate_product_version outfiles)
|
||||
set (options)
|
||||
set (oneValueArgs
|
||||
NAME
|
||||
BUNDLE
|
||||
ICON
|
||||
VERSION_MAJOR
|
||||
VERSION_MINOR
|
||||
VERSION_PATCH
|
||||
VERSION_REVISION
|
||||
COMPANY_NAME
|
||||
COMPANY_COPYRIGHT
|
||||
COMMENTS
|
||||
ORIGINAL_FILENAME
|
||||
INTERNAL_NAME
|
||||
FILE_DESCRIPTION)
|
||||
set (multiValueArgs)
|
||||
cmake_parse_arguments(PRODUCT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (NOT PRODUCT_BUNDLE OR "${PRODUCT_BUNDLE}" STREQUAL "")
|
||||
set(PRODUCT_BUNDLE "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_ICON OR "${PRODUCT_ICON}" STREQUAL "")
|
||||
set(PRODUCT_ICON "${CMAKE_SOURCE_DIR}/product.ico")
|
||||
endif()
|
||||
|
||||
if (NOT PRODUCT_VERSION_MAJOR EQUAL 0 AND (NOT PRODUCT_VERSION_MAJOR OR "${PRODUCT_VERSION_MAJOR}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_MAJOR 1)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_MINOR EQUAL 0 AND (NOT PRODUCT_VERSION_MINOR OR "${PRODUCT_VERSION_MINOR}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_MINOR 0)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_PATCH EQUAL 0 AND (NOT PRODUCT_VERSION_PATCH OR "${PRODUCT_VERSION_PATCH}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_PATCH 0)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_REVISION EQUAL 0 AND (NOT PRODUCT_VERSION_REVISION OR "${PRODUCT_VERSION_REVISION}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_REVISION 0)
|
||||
endif()
|
||||
|
||||
if (NOT PRODUCT_COMPANY_COPYRIGHT OR "${PRODUCT_COMPANY_COPYRIGHT}" STREQUAL "")
|
||||
string(TIMESTAMP PRODUCT_CURRENT_YEAR "%Y")
|
||||
set(PRODUCT_COMPANY_COPYRIGHT "${PRODUCT_COMPANY_NAME} (C) Copyright ${PRODUCT_CURRENT_YEAR}")
|
||||
endif()
|
||||
if (NOT PRODUCT_COMMENTS OR "${PRODUCT_COMMENTS}" STREQUAL "")
|
||||
set(PRODUCT_COMMENTS "${PRODUCT_NAME} v${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}")
|
||||
endif()
|
||||
if (NOT PRODUCT_ORIGINAL_FILENAME OR "${PRODUCT_ORIGINAL_FILENAME}" STREQUAL "")
|
||||
set(PRODUCT_ORIGINAL_FILENAME "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_INTERNAL_NAME OR "${PRODUCT_INTERNAL_NAME}" STREQUAL "")
|
||||
set(PRODUCT_INTERNAL_NAME "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_FILE_DESCRIPTION OR "${PRODUCT_FILE_DESCRIPTION}" STREQUAL "")
|
||||
set(PRODUCT_FILE_DESCRIPTION "${PRODUCT_NAME}")
|
||||
endif()
|
||||
|
||||
set (_VersionInfoFile ${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.h)
|
||||
set (_VersionResourceFile ${CMAKE_CURRENT_BINARY_DIR}/VersionResource.rc)
|
||||
configure_file(
|
||||
${GenerateProductVersionCurrentDir}/VersionInfo.in
|
||||
${_VersionInfoFile}
|
||||
@ONLY)
|
||||
configure_file(
|
||||
${GenerateProductVersionCurrentDir}/VersionResource.rc
|
||||
${_VersionResourceFile}
|
||||
COPYONLY)
|
||||
list(APPEND ${outfiles} ${_VersionInfoFile} ${_VersionResourceFile})
|
||||
set (${outfiles} ${${outfiles}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@ -1,24 +0,0 @@
|
||||
set(PLATFORM_SOURCES 3rdparty/WinCommander.cpp sys/windows/guihelper.cpp sys/windows/MiniDump.cpp)
|
||||
set(PLATFORM_LIBRARIES wininet wsock32 ws2_32 user32 rasapi32 iphlpapi)
|
||||
|
||||
include(cmake/windows/generate_product_version.cmake)
|
||||
generate_product_version(
|
||||
QV2RAY_RC
|
||||
ICON "${CMAKE_SOURCE_DIR}/res/nekobox.ico"
|
||||
NAME "nekobox"
|
||||
BUNDLE "nekobox"
|
||||
COMPANY_NAME "nekobox"
|
||||
COMPANY_COPYRIGHT "nekobox"
|
||||
FILE_DESCRIPTION "nekobox"
|
||||
)
|
||||
add_definitions(-DUNICODE -D_UNICODE -DNOMINMAX)
|
||||
set(GUI_TYPE WIN32)
|
||||
if (MINGW)
|
||||
if (NOT DEFINED MinGW_ROOT)
|
||||
set(MinGW_ROOT "C:/msys64/mingw64")
|
||||
endif ()
|
||||
else ()
|
||||
add_compile_options("/utf-8")
|
||||
add_compile_options("/std:c++17")
|
||||
add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
|
||||
endif ()
|
||||
@ -1,818 +0,0 @@
|
||||
#include "db/ConfigBuilder.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "fmt/includes.h"
|
||||
#include "fmt/Preset.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#define BOX_UNDERLYING_DNS dataStore->core_box_underlying_dns.isEmpty() ? "local" : dataStore->core_box_underlying_dns
|
||||
|
||||
namespace NekoGui {
|
||||
|
||||
QStringList getAutoBypassExternalProcessPaths(const std::shared_ptr<BuildConfigResult> &result) {
|
||||
QStringList paths;
|
||||
for (const auto &extR: result->extRs) {
|
||||
auto path = extR->program;
|
||||
if (path.trimmed().isEmpty()) continue;
|
||||
paths << path.replace("\\", "/");
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
QString genTunName() {
|
||||
auto tun_name = "neko-tun";
|
||||
#ifdef Q_OS_MACOS
|
||||
tun_name = "utun9";
|
||||
#endif
|
||||
return tun_name;
|
||||
}
|
||||
|
||||
void MergeJson(QJsonObject &dst, const QJsonObject &src) {
|
||||
// 合并
|
||||
if (src.isEmpty()) return;
|
||||
for (const auto &key: src.keys()) {
|
||||
auto v_src = src[key];
|
||||
if (dst.contains(key)) {
|
||||
auto v_dst = dst[key];
|
||||
if (v_src.isObject() && v_dst.isObject()) { // isObject 则合并?
|
||||
auto v_src_obj = v_src.toObject();
|
||||
auto v_dst_obj = v_dst.toObject();
|
||||
MergeJson(v_dst_obj, v_src_obj);
|
||||
dst[key] = v_dst_obj;
|
||||
} else {
|
||||
dst[key] = v_src;
|
||||
}
|
||||
} else if (v_src.isArray()) {
|
||||
if (key.startsWith("+")) {
|
||||
auto key2 = SubStrAfter(key, "+");
|
||||
auto v_dst = dst[key2];
|
||||
auto v_src_arr = v_src.toArray();
|
||||
auto v_dst_arr = v_dst.toArray();
|
||||
QJSONARRAY_ADD(v_src_arr, v_dst_arr)
|
||||
dst[key2] = v_src_arr;
|
||||
} else if (key.endsWith("+")) {
|
||||
auto key2 = SubStrBefore(key, "+");
|
||||
auto v_dst = dst[key2];
|
||||
auto v_src_arr = v_src.toArray();
|
||||
auto v_dst_arr = v_dst.toArray();
|
||||
QJSONARRAY_ADD(v_dst_arr, v_src_arr)
|
||||
dst[key2] = v_dst_arr;
|
||||
} else {
|
||||
dst[key] = v_src;
|
||||
}
|
||||
} else {
|
||||
dst[key] = v_src;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common
|
||||
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport) {
|
||||
auto result = std::make_shared<BuildConfigResult>();
|
||||
auto status = std::make_shared<BuildConfigStatus>();
|
||||
status->ent = ent;
|
||||
status->result = result;
|
||||
status->forTest = forTest;
|
||||
status->forExport = forExport;
|
||||
|
||||
auto customBean = dynamic_cast<NekoGui_fmt::CustomBean *>(ent->bean.get());
|
||||
if (customBean != nullptr && customBean->core == "internal-full") {
|
||||
result->coreConfig = QString2QJsonObject(customBean->config_simple);
|
||||
} else {
|
||||
BuildConfigSingBox(status);
|
||||
}
|
||||
|
||||
// apply custom config
|
||||
MergeJson(result->coreConfig, QString2QJsonObject(ent->bean->custom_config));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BuildChain(int chainId, const std::shared_ptr<BuildConfigStatus> &status) {
|
||||
auto group = profileManager->GetGroup(status->ent->gid);
|
||||
if (group == nullptr) {
|
||||
status->result->error = QStringLiteral("This profile is not in any group, your data may be corrupted.");
|
||||
return {};
|
||||
}
|
||||
|
||||
auto resolveChain = [=](const std::shared_ptr<ProxyEntity> &ent) {
|
||||
QList<std::shared_ptr<ProxyEntity>> resolved;
|
||||
if (ent->type == "chain") {
|
||||
auto list = ent->ChainBean()->list;
|
||||
std::reverse(std::begin(list), std::end(list));
|
||||
for (auto id: list) {
|
||||
resolved += profileManager->GetProfile(id);
|
||||
if (resolved.last() == nullptr) {
|
||||
status->result->error = QStringLiteral("chain missing ent: %1").arg(id);
|
||||
break;
|
||||
}
|
||||
if (resolved.last()->type == "chain") {
|
||||
status->result->error = QStringLiteral("chain in chain is not allowed: %1").arg(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolved += ent;
|
||||
};
|
||||
return resolved;
|
||||
};
|
||||
|
||||
// Make list
|
||||
auto ents = resolveChain(status->ent);
|
||||
if (!status->result->error.isEmpty()) return {};
|
||||
|
||||
if (group->front_proxy_id >= 0) {
|
||||
auto fEnt = profileManager->GetProfile(group->front_proxy_id);
|
||||
if (fEnt == nullptr) {
|
||||
status->result->error = QStringLiteral("front proxy ent not found.");
|
||||
return {};
|
||||
}
|
||||
ents += resolveChain(fEnt);
|
||||
if (!status->result->error.isEmpty()) return {};
|
||||
}
|
||||
|
||||
// BuildChain
|
||||
QString chainTagOut = BuildChainInternal(0, ents, status);
|
||||
|
||||
// Chain ent traffic stat
|
||||
if (ents.length() > 1) {
|
||||
status->ent->traffic_data->id = status->ent->id;
|
||||
status->ent->traffic_data->tag = chainTagOut.toStdString();
|
||||
status->result->outboundStats += status->ent->traffic_data;
|
||||
}
|
||||
|
||||
return chainTagOut;
|
||||
}
|
||||
|
||||
#define DOMAIN_USER_RULE \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) { \
|
||||
if (dataStore->routing->dns_routing) status->domainListDNSRemote += line; \
|
||||
status->domainListRemote += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \
|
||||
if (dataStore->routing->dns_routing) status->domainListDNSDirect += line; \
|
||||
status->domainListDirect += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) { \
|
||||
status->domainListBlock += line; \
|
||||
}
|
||||
|
||||
#define IP_USER_RULE \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) { \
|
||||
status->ipListBlock += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) { \
|
||||
status->ipListRemote += line; \
|
||||
} \
|
||||
for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \
|
||||
status->ipListDirect += line; \
|
||||
}
|
||||
|
||||
QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,
|
||||
const std::shared_ptr<BuildConfigStatus> &status) {
|
||||
QString chainTag = "c-" + Int2String(chainId);
|
||||
QString chainTagOut;
|
||||
bool muxApplied = false;
|
||||
|
||||
QString pastTag;
|
||||
int pastExternalStat = 0;
|
||||
int index = 0;
|
||||
|
||||
for (const auto &ent: ents) {
|
||||
// tagOut: v2ray outbound tag for a profile
|
||||
// profile2 (in) (global) tag g-(id)
|
||||
// profile1 tag (chainTag)-(id)
|
||||
// profile0 (out) tag (chainTag)-(id) / single: chainTag=g-(id)
|
||||
auto tagOut = chainTag + "-" + Int2String(ent->id);
|
||||
|
||||
// needGlobal: can only contain one?
|
||||
bool needGlobal = false;
|
||||
|
||||
// first profile set as global
|
||||
auto isFirstProfile = index == ents.length() - 1;
|
||||
if (isFirstProfile) {
|
||||
needGlobal = true;
|
||||
tagOut = "g-" + Int2String(ent->id);
|
||||
}
|
||||
|
||||
// last profile set as "proxy"
|
||||
if (chainId == 0 && index == 0) {
|
||||
needGlobal = false;
|
||||
tagOut = "proxy";
|
||||
}
|
||||
|
||||
// ignoreConnTag
|
||||
if (index != 0) {
|
||||
status->result->ignoreConnTag << tagOut;
|
||||
}
|
||||
|
||||
if (needGlobal) {
|
||||
if (status->globalProfiles.contains(ent->id)) {
|
||||
continue;
|
||||
}
|
||||
status->globalProfiles += ent->id;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
// chain rules: past
|
||||
if (pastExternalStat == 0) {
|
||||
auto replaced = status->outbounds.last().toObject();
|
||||
replaced["detour"] = tagOut;
|
||||
status->outbounds.removeLast();
|
||||
status->outbounds += replaced;
|
||||
} else {
|
||||
status->routingRules += QJsonObject{
|
||||
{"inbound", QJsonArray{pastTag + "-mapping"}},
|
||||
{"outbound", tagOut},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// index == 0 means last profile in chain / not chain
|
||||
chainTagOut = tagOut;
|
||||
status->result->outboundStat = ent->traffic_data;
|
||||
}
|
||||
|
||||
// chain rules: this
|
||||
auto ext_mapping_port = 0;
|
||||
auto ext_socks_port = 0;
|
||||
auto thisExternalStat = ent->bean->NeedExternal(isFirstProfile);
|
||||
if (thisExternalStat < 0) {
|
||||
status->result->error = "This configuration cannot be set automatically, please try another.";
|
||||
return {};
|
||||
}
|
||||
|
||||
// determine port
|
||||
if (thisExternalStat > 0) {
|
||||
if (ent->type == "custom") {
|
||||
auto bean = ent->CustomBean();
|
||||
if (IsValidPort(bean->mapping_port)) {
|
||||
ext_mapping_port = bean->mapping_port;
|
||||
} else {
|
||||
ext_mapping_port = MkPort();
|
||||
}
|
||||
if (IsValidPort(bean->socks_port)) {
|
||||
ext_socks_port = bean->socks_port;
|
||||
} else {
|
||||
ext_socks_port = MkPort();
|
||||
}
|
||||
} else {
|
||||
ext_mapping_port = MkPort();
|
||||
ext_socks_port = MkPort();
|
||||
}
|
||||
}
|
||||
if (thisExternalStat == 2) dataStore->need_keep_vpn_off = true;
|
||||
if (thisExternalStat == 1) {
|
||||
// mapping
|
||||
status->inbounds += QJsonObject{
|
||||
{"type", "direct"},
|
||||
{"tag", tagOut + "-mapping"},
|
||||
{"listen", "127.0.0.1"},
|
||||
{"listen_port", ext_mapping_port},
|
||||
{"override_address", ent->bean->serverAddress},
|
||||
{"override_port", ent->bean->serverPort},
|
||||
};
|
||||
// no chain rule and not outbound, so need to set to direct
|
||||
if (isFirstProfile) {
|
||||
status->routingRules += QJsonObject{
|
||||
{"inbound", QJsonArray{tagOut + "-mapping"}},
|
||||
{"outbound", "direct"},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Outbound
|
||||
|
||||
QJsonObject outbound;
|
||||
auto stream = GetStreamSettings(ent->bean.get());
|
||||
|
||||
if (thisExternalStat > 0) {
|
||||
auto extR = ent->bean->BuildExternal(ext_mapping_port, ext_socks_port, thisExternalStat);
|
||||
if (extR.program.isEmpty()) {
|
||||
status->result->error = QObject::tr("Core not found: %1").arg(ent->bean->DisplayCoreType());
|
||||
return {};
|
||||
}
|
||||
if (!extR.error.isEmpty()) { // rejected
|
||||
status->result->error = extR.error;
|
||||
return {};
|
||||
}
|
||||
extR.tag = ent->bean->DisplayType();
|
||||
status->result->extRs.emplace_back(std::make_shared<NekoGui_fmt::ExternalBuildResult>(extR));
|
||||
|
||||
// SOCKS OUTBOUND
|
||||
outbound["type"] = "socks";
|
||||
outbound["server"] = "127.0.0.1";
|
||||
outbound["server_port"] = ext_socks_port;
|
||||
} else {
|
||||
const auto coreR = ent->bean->BuildCoreObjSingBox();
|
||||
if (coreR.outbound.isEmpty()) {
|
||||
status->result->error = "unsupported outbound";
|
||||
return {};
|
||||
}
|
||||
if (!coreR.error.isEmpty()) { // rejected
|
||||
status->result->error = coreR.error;
|
||||
return {};
|
||||
}
|
||||
outbound = coreR.outbound;
|
||||
}
|
||||
|
||||
// outbound misc
|
||||
outbound["tag"] = tagOut;
|
||||
ent->traffic_data->id = ent->id;
|
||||
ent->traffic_data->tag = tagOut.toStdString();
|
||||
status->result->outboundStats += ent->traffic_data;
|
||||
|
||||
// mux common
|
||||
auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless";
|
||||
needMux &= dataStore->mux_concurrency > 0;
|
||||
|
||||
if (stream != nullptr) {
|
||||
if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) {
|
||||
needMux = false;
|
||||
}
|
||||
if (stream->multiplex_status == 0) {
|
||||
if (!dataStore->mux_default_on) needMux = false;
|
||||
} else if (stream->multiplex_status == 1) {
|
||||
needMux = true;
|
||||
} else if (stream->multiplex_status == 2) {
|
||||
needMux = false;
|
||||
}
|
||||
}
|
||||
if (ent->type == "vless" && outbound["flow"] != "") {
|
||||
needMux = false;
|
||||
}
|
||||
|
||||
// common
|
||||
// apply domain_strategy
|
||||
outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy;
|
||||
// apply mux
|
||||
if (!muxApplied && needMux) {
|
||||
auto muxObj = QJsonObject{
|
||||
{"enabled", true},
|
||||
{"protocol", dataStore->mux_protocol},
|
||||
{"padding", dataStore->mux_padding},
|
||||
{"max_streams", dataStore->mux_concurrency},
|
||||
};
|
||||
outbound["multiplex"] = muxObj;
|
||||
muxApplied = true;
|
||||
}
|
||||
|
||||
// apply custom outbound settings
|
||||
MergeJson(outbound, QString2QJsonObject(ent->bean->custom_outbound));
|
||||
|
||||
// Bypass Lookup for the first profile
|
||||
auto serverAddress = ent->bean->serverAddress;
|
||||
|
||||
auto customBean = dynamic_cast<NekoGui_fmt::CustomBean *>(ent->bean.get());
|
||||
if (customBean != nullptr && customBean->core == "internal") {
|
||||
auto server = QString2QJsonObject(customBean->config_simple)["server"].toString();
|
||||
if (!server.isEmpty()) serverAddress = server;
|
||||
}
|
||||
|
||||
if (!IsIpAddress(serverAddress)) {
|
||||
status->domainListDNSDirect += "full:" + serverAddress;
|
||||
}
|
||||
|
||||
status->outbounds += outbound;
|
||||
pastTag = tagOut;
|
||||
pastExternalStat = thisExternalStat;
|
||||
index++;
|
||||
}
|
||||
|
||||
return chainTagOut;
|
||||
}
|
||||
|
||||
// SingBox
|
||||
|
||||
void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status) {
|
||||
// Log
|
||||
status->result->coreConfig["log"] = QJsonObject{{"level", dataStore->log_level}};
|
||||
|
||||
// Inbounds
|
||||
|
||||
// mixed-in
|
||||
if (IsValidPort(dataStore->inbound_socks_port) && !status->forTest) {
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["tag"] = "mixed-in";
|
||||
inboundObj["type"] = "mixed";
|
||||
inboundObj["listen"] = dataStore->inbound_address;
|
||||
inboundObj["listen_port"] = dataStore->inbound_socks_port;
|
||||
if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) {
|
||||
inboundObj["sniff"] = true;
|
||||
inboundObj["sniff_override_destination"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION;
|
||||
}
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
inboundObj["users"] = QJsonArray{
|
||||
QJsonObject{
|
||||
{"username", dataStore->inbound_auth->username},
|
||||
{"password", dataStore->inbound_auth->password},
|
||||
},
|
||||
};
|
||||
}
|
||||
inboundObj["domain_strategy"] = dataStore->routing->domain_strategy;
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
|
||||
// tun-in
|
||||
if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {
|
||||
QJsonObject inboundObj;
|
||||
inboundObj["tag"] = "tun-in";
|
||||
inboundObj["type"] = "tun";
|
||||
inboundObj["interface_name"] = genTunName();
|
||||
inboundObj["auto_route"] = true;
|
||||
inboundObj["endpoint_independent_nat"] = true;
|
||||
inboundObj["mtu"] = dataStore->vpn_mtu;
|
||||
inboundObj["stack"] = Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation);
|
||||
inboundObj["strict_route"] = dataStore->vpn_strict_route;
|
||||
inboundObj["inet4_address"] = "172.19.0.1/28";
|
||||
if (dataStore->vpn_ipv6) inboundObj["inet6_address"] = "fdfe:dcba:9876::1/126";
|
||||
if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) {
|
||||
inboundObj["sniff"] = true;
|
||||
inboundObj["sniff_override_destination"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION;
|
||||
}
|
||||
inboundObj["domain_strategy"] = dataStore->routing->domain_strategy;
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
|
||||
// Outbounds
|
||||
auto tagProxy = BuildChain(0, status);
|
||||
if (!status->result->error.isEmpty()) return;
|
||||
|
||||
// direct & bypass & block
|
||||
status->outbounds += QJsonObject{
|
||||
{"type", "direct"},
|
||||
{"tag", "direct"},
|
||||
};
|
||||
status->outbounds += QJsonObject{
|
||||
{"type", "direct"},
|
||||
{"tag", "bypass"},
|
||||
};
|
||||
status->outbounds += QJsonObject{
|
||||
{"type", "block"},
|
||||
{"tag", "block"},
|
||||
};
|
||||
if (!status->forTest) {
|
||||
status->outbounds += QJsonObject{
|
||||
{"type", "dns"},
|
||||
{"tag", "dns-out"},
|
||||
};
|
||||
}
|
||||
|
||||
// custom inbound
|
||||
if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray())
|
||||
|
||||
status->result->coreConfig.insert("inbounds", status->inbounds);
|
||||
status->result->coreConfig.insert("outbounds", status->outbounds);
|
||||
|
||||
// user rule
|
||||
if (!status->forTest) {
|
||||
DOMAIN_USER_RULE
|
||||
IP_USER_RULE
|
||||
}
|
||||
|
||||
// sing-box common rule object
|
||||
auto make_rule = [&](const QStringList &list, bool isIP = false) {
|
||||
QJsonObject rule;
|
||||
//
|
||||
QJsonArray ip_cidr;
|
||||
QJsonArray geoip;
|
||||
//
|
||||
QJsonArray domain_keyword;
|
||||
QJsonArray domain_subdomain;
|
||||
QJsonArray domain_regexp;
|
||||
QJsonArray domain_full;
|
||||
QJsonArray geosite;
|
||||
for (auto item: list) {
|
||||
if (isIP) {
|
||||
if (item.startsWith("geoip:")) {
|
||||
geoip += item.replace("geoip:", "");
|
||||
} else {
|
||||
ip_cidr += item;
|
||||
}
|
||||
} else {
|
||||
// https://www.v2fly.org/config/dns.html#dnsobject
|
||||
if (item.startsWith("geosite:")) {
|
||||
geosite += item.replace("geosite:", "");
|
||||
} else if (item.startsWith("full:")) {
|
||||
domain_full += item.replace("full:", "").toLower();
|
||||
} else if (item.startsWith("domain:")) {
|
||||
domain_subdomain += item.replace("domain:", "").toLower();
|
||||
} else if (item.startsWith("regexp:")) {
|
||||
domain_regexp += item.replace("regexp:", "").toLower();
|
||||
} else if (item.startsWith("keyword:")) {
|
||||
domain_keyword += item.replace("keyword:", "").toLower();
|
||||
} else {
|
||||
domain_subdomain += item.toLower();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isIP) {
|
||||
if (ip_cidr.isEmpty() && geoip.isEmpty()) return rule;
|
||||
rule["ip_cidr"] = ip_cidr;
|
||||
rule["geoip"] = geoip;
|
||||
} else {
|
||||
if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_regexp.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) {
|
||||
return rule;
|
||||
}
|
||||
rule["domain"] = domain_full;
|
||||
rule["domain_suffix"] = domain_subdomain; // v2ray Subdomain => sing-box suffix
|
||||
rule["domain_keyword"] = domain_keyword;
|
||||
rule["domain_regex"] = domain_regexp;
|
||||
rule["geosite"] = geosite;
|
||||
}
|
||||
return rule;
|
||||
};
|
||||
|
||||
// final add DNS
|
||||
QJsonObject dns;
|
||||
QJsonArray dnsServers;
|
||||
QJsonArray dnsRules;
|
||||
|
||||
// Remote
|
||||
if (!status->forTest)
|
||||
dnsServers += QJsonObject{
|
||||
{"tag", "dns-remote"},
|
||||
{"address_resolver", "dns-local"},
|
||||
{"strategy", dataStore->routing->remote_dns_strategy},
|
||||
{"address", dataStore->routing->remote_dns},
|
||||
{"detour", tagProxy},
|
||||
};
|
||||
|
||||
// Direct
|
||||
QJsonObject directObj{
|
||||
{"tag", "dns-direct"},
|
||||
{"address_resolver", "dns-local"},
|
||||
{"strategy", dataStore->routing->direct_dns_strategy},
|
||||
{"address", dataStore->routing->direct_dns},
|
||||
{"detour", "direct"},
|
||||
};
|
||||
if (dataStore->routing->dns_final_out == "bypass") {
|
||||
dnsServers.prepend(directObj);
|
||||
} else {
|
||||
dnsServers.append(directObj);
|
||||
}
|
||||
dnsRules.append(QJsonObject{
|
||||
{"outbound", "any"},
|
||||
{"server", "dns-direct"},
|
||||
});
|
||||
|
||||
// block
|
||||
if (!status->forTest)
|
||||
dnsServers += QJsonObject{
|
||||
{"tag", "dns-block"},
|
||||
{"address", "rcode://success"},
|
||||
};
|
||||
|
||||
// Fakedns
|
||||
if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {
|
||||
dnsServers += QJsonObject{
|
||||
{"tag", "dns-fake"},
|
||||
{"address", "fakeip"},
|
||||
};
|
||||
dns["fakeip"] = QJsonObject{
|
||||
{"enabled", true},
|
||||
{"inet4_range", "198.18.0.0/15"},
|
||||
{"inet6_range", "fc00::/18"},
|
||||
};
|
||||
}
|
||||
|
||||
// Underlying 100% Working DNS ?
|
||||
dnsServers += QJsonObject{
|
||||
{"tag", "dns-local"},
|
||||
{"address", BOX_UNDERLYING_DNS},
|
||||
{"detour", "direct"},
|
||||
};
|
||||
|
||||
// sing-box dns rule object
|
||||
auto add_rule_dns = [&](const QStringList &list, const QString &server) {
|
||||
auto rule = make_rule(list, false);
|
||||
if (rule.isEmpty()) return;
|
||||
rule["server"] = server;
|
||||
dnsRules += rule;
|
||||
};
|
||||
add_rule_dns(status->domainListDNSRemote, "dns-remote");
|
||||
add_rule_dns(status->domainListDNSDirect, "dns-direct");
|
||||
|
||||
// built-in rules
|
||||
if (!status->forTest) {
|
||||
dnsRules += QJsonObject{
|
||||
{"query_type", QJsonArray{32, 33}},
|
||||
{"server", "dns-block"},
|
||||
};
|
||||
dnsRules += QJsonObject{
|
||||
{"domain_suffix", ".lan"},
|
||||
{"server", "dns-block"},
|
||||
};
|
||||
}
|
||||
|
||||
// fakedns rule
|
||||
if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {
|
||||
dnsRules += QJsonObject{
|
||||
{"inbound", "tun-in"},
|
||||
{"server", "dns-fake"},
|
||||
};
|
||||
}
|
||||
|
||||
dns["servers"] = dnsServers;
|
||||
dns["rules"] = dnsRules;
|
||||
dns["independent_cache"] = true;
|
||||
|
||||
if (dataStore->routing->use_dns_object) {
|
||||
dns = QString2QJsonObject(dataStore->routing->dns_object);
|
||||
}
|
||||
status->result->coreConfig.insert("dns", dns);
|
||||
|
||||
// Routing
|
||||
|
||||
// dns hijack
|
||||
if (!status->forTest) {
|
||||
status->routingRules += QJsonObject{
|
||||
{"protocol", "dns"},
|
||||
{"outbound", "dns-out"},
|
||||
};
|
||||
}
|
||||
|
||||
// sing-box routing rule object
|
||||
auto add_rule_route = [&](const QStringList &list, bool isIP, const QString &out) {
|
||||
auto rule = make_rule(list, isIP);
|
||||
if (rule.isEmpty()) return;
|
||||
rule["outbound"] = out;
|
||||
status->routingRules += rule;
|
||||
};
|
||||
|
||||
// final add user rule
|
||||
add_rule_route(status->domainListBlock, false, "block");
|
||||
add_rule_route(status->domainListRemote, false, tagProxy);
|
||||
add_rule_route(status->domainListDirect, false, "bypass");
|
||||
add_rule_route(status->ipListBlock, true, "block");
|
||||
add_rule_route(status->ipListRemote, true, tagProxy);
|
||||
add_rule_route(status->ipListDirect, true, "bypass");
|
||||
|
||||
// built-in rules
|
||||
status->routingRules += QJsonObject{
|
||||
{"network", "udp"},
|
||||
{"port", QJsonArray{135, 137, 138, 139, 5353}},
|
||||
{"outbound", "block"},
|
||||
};
|
||||
status->routingRules += QJsonObject{
|
||||
{"ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}},
|
||||
{"outbound", "block"},
|
||||
};
|
||||
status->routingRules += QJsonObject{
|
||||
{"source_ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}},
|
||||
{"outbound", "block"},
|
||||
};
|
||||
|
||||
// tun user rule
|
||||
if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) {
|
||||
auto match_out = dataStore->vpn_rule_white ? "proxy" : "bypass";
|
||||
|
||||
QString process_name_rule = dataStore->vpn_rule_process.trimmed();
|
||||
if (!process_name_rule.isEmpty()) {
|
||||
auto arr = SplitLinesSkipSharp(process_name_rule);
|
||||
QJsonObject rule{{"outbound", match_out},
|
||||
{"process_name", QList2QJsonArray(arr)}};
|
||||
status->routingRules += rule;
|
||||
}
|
||||
|
||||
QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();
|
||||
if (!cidr_rule.isEmpty()) {
|
||||
auto arr = SplitLinesSkipSharp(cidr_rule);
|
||||
QJsonObject rule{{"outbound", match_out},
|
||||
{"ip_cidr", QList2QJsonArray(arr)}};
|
||||
status->routingRules += rule;
|
||||
}
|
||||
|
||||
auto autoBypassExternalProcessPaths = getAutoBypassExternalProcessPaths(status->result);
|
||||
if (!autoBypassExternalProcessPaths.isEmpty()) {
|
||||
QJsonObject rule{{"outbound", "bypass"},
|
||||
{"process_name", QList2QJsonArray(autoBypassExternalProcessPaths)}};
|
||||
status->routingRules += rule;
|
||||
}
|
||||
}
|
||||
|
||||
// geopath
|
||||
auto geoip = FindCoreAsset("geoip.db");
|
||||
auto geosite = FindCoreAsset("geosite.db");
|
||||
if (geoip.isEmpty()) status->result->error = +"geoip.db not found";
|
||||
if (geosite.isEmpty()) status->result->error = +"geosite.db not found";
|
||||
|
||||
// final add routing rule
|
||||
auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray();
|
||||
if (status->forTest) routingRules = {};
|
||||
if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray())
|
||||
QJSONARRAY_ADD(routingRules, status->routingRules)
|
||||
auto routeObj = QJsonObject{
|
||||
{"rules", routingRules},
|
||||
{"auto_detect_interface", dataStore->spmode_vpn}, // TODO force enable?
|
||||
{
|
||||
"geoip",
|
||||
QJsonObject{
|
||||
{"path", geoip},
|
||||
},
|
||||
},
|
||||
{
|
||||
"geosite",
|
||||
QJsonObject{
|
||||
{"path", geosite},
|
||||
},
|
||||
}};
|
||||
if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound;
|
||||
if (status->forExport) {
|
||||
routeObj.remove("geoip");
|
||||
routeObj.remove("geosite");
|
||||
routeObj.remove("auto_detect_interface");
|
||||
}
|
||||
status->result->coreConfig.insert("route", routeObj);
|
||||
|
||||
// experimental
|
||||
QJsonObject experimentalObj;
|
||||
|
||||
if (!status->forTest && dataStore->core_box_clash_api > 0) {
|
||||
QJsonObject clash_api = {
|
||||
{"external_controller", "127.0.0.1:" + Int2String(dataStore->core_box_clash_api)},
|
||||
{"secret", dataStore->core_box_clash_api_secret},
|
||||
{"external_ui", "dashboard"},
|
||||
};
|
||||
experimentalObj["clash_api"] = clash_api;
|
||||
}
|
||||
|
||||
if (!experimentalObj.isEmpty()) status->result->coreConfig.insert("experimental", experimentalObj);
|
||||
}
|
||||
|
||||
QString WriteVPNSingBoxConfig() {
|
||||
// tun user rule
|
||||
auto match_out = dataStore->vpn_rule_white ? "neko-socks" : "direct";
|
||||
auto no_match_out = dataStore->vpn_rule_white ? "direct" : "neko-socks";
|
||||
|
||||
QString process_name_rule = dataStore->vpn_rule_process.trimmed();
|
||||
if (!process_name_rule.isEmpty()) {
|
||||
auto arr = SplitLinesSkipSharp(process_name_rule);
|
||||
QJsonObject rule{{"outbound", match_out},
|
||||
{"process_name", QList2QJsonArray(arr)}};
|
||||
process_name_rule = "," + QJsonObject2QString(rule, false);
|
||||
}
|
||||
|
||||
QString cidr_rule = dataStore->vpn_rule_cidr.trimmed();
|
||||
if (!cidr_rule.isEmpty()) {
|
||||
auto arr = SplitLinesSkipSharp(cidr_rule);
|
||||
QJsonObject rule{{"outbound", match_out},
|
||||
{"ip_cidr", QList2QJsonArray(arr)}};
|
||||
cidr_rule = "," + QJsonObject2QString(rule, false);
|
||||
}
|
||||
|
||||
// TODO bypass ext core process path?
|
||||
|
||||
// auth
|
||||
QString socks_user_pass;
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
socks_user_pass = R"( "username": "%1", "password": "%2", )";
|
||||
socks_user_pass = socks_user_pass.arg(dataStore->inbound_auth->username, dataStore->inbound_auth->password);
|
||||
}
|
||||
// gen config
|
||||
auto configFn = ":/neko/vpn/sing-box-vpn.json";
|
||||
if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json";
|
||||
auto config = ReadFileText(configFn)
|
||||
.replace("//%IPV6_ADDRESS%", dataStore->vpn_ipv6 ? R"("inet6_address": "fdfe:dcba:9876::1/126",)" : "")
|
||||
.replace("//%SOCKS_USER_PASS%", socks_user_pass)
|
||||
.replace("//%PROCESS_NAME_RULE%", process_name_rule)
|
||||
.replace("//%CIDR_RULE%", cidr_rule)
|
||||
.replace("%MTU%", Int2String(dataStore->vpn_mtu))
|
||||
.replace("%STACK%", Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation))
|
||||
.replace("%TUN_NAME%", genTunName())
|
||||
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
|
||||
.replace("%FINAL_OUT%", no_match_out)
|
||||
.replace("%DNS_ADDRESS%", BOX_UNDERLYING_DNS)
|
||||
.replace("%FAKE_DNS_INBOUND%", dataStore->fake_dns ? "tun-in" : "empty")
|
||||
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
|
||||
// write config
|
||||
QFile file;
|
||||
file.setFileName(QFileInfo(configFn).fileName());
|
||||
file.open(QIODevice::ReadWrite | QIODevice::Truncate);
|
||||
file.write(config.toUtf8());
|
||||
file.close();
|
||||
return QFileInfo(file).absoluteFilePath();
|
||||
}
|
||||
|
||||
QString WriteVPNLinuxScript(const QString &configPath) {
|
||||
#ifdef Q_OS_WIN
|
||||
return {};
|
||||
#endif
|
||||
// gen script
|
||||
auto scriptFn = ":/neko/vpn/vpn-run-root.sh";
|
||||
if (QFile::exists("vpn/vpn-run-root.sh")) scriptFn = "vpn/vpn-run-root.sh";
|
||||
auto script = ReadFileText(scriptFn)
|
||||
.replace("./nekobox_core", QApplication::applicationDirPath() + "/nekobox_core")
|
||||
.replace("$CONFIG_PATH", configPath);
|
||||
// write script
|
||||
QFile file2;
|
||||
file2.setFileName(QFileInfo(scriptFn).fileName());
|
||||
file2.open(QIODevice::ReadWrite | QIODevice::Truncate);
|
||||
file2.write(script.toUtf8());
|
||||
file2.close();
|
||||
return QFileInfo(file2).absoluteFilePath();
|
||||
}
|
||||
|
||||
} // namespace NekoGui
|
||||
@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProxyEntity.hpp"
|
||||
#include "sys/ExternalProcess.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
class BuildConfigResult {
|
||||
public:
|
||||
QString error;
|
||||
QJsonObject coreConfig;
|
||||
|
||||
QList<std::shared_ptr<NekoGui_traffic::TrafficData>> outboundStats; // all, but not including "bypass" "block"
|
||||
std::shared_ptr<NekoGui_traffic::TrafficData> outboundStat; // main
|
||||
QStringList ignoreConnTag;
|
||||
|
||||
std::list<std::shared_ptr<NekoGui_fmt::ExternalBuildResult>> extRs;
|
||||
};
|
||||
|
||||
class BuildConfigStatus {
|
||||
public:
|
||||
std::shared_ptr<BuildConfigResult> result;
|
||||
std::shared_ptr<ProxyEntity> ent;
|
||||
bool forTest;
|
||||
bool forExport;
|
||||
|
||||
// priv
|
||||
QList<int> globalProfiles;
|
||||
|
||||
// xxList is V2Ray format string list
|
||||
|
||||
QStringList domainListDNSRemote;
|
||||
QStringList domainListDNSDirect;
|
||||
QStringList domainListRemote;
|
||||
QStringList domainListDirect;
|
||||
QStringList ipListRemote;
|
||||
QStringList ipListDirect;
|
||||
QStringList domainListBlock;
|
||||
QStringList ipListBlock;
|
||||
|
||||
// config format
|
||||
|
||||
QJsonArray routingRules;
|
||||
QJsonArray inbounds;
|
||||
QJsonArray outbounds;
|
||||
};
|
||||
|
||||
std::shared_ptr<BuildConfigResult> BuildConfig(const std::shared_ptr<ProxyEntity> &ent, bool forTest, bool forExport);
|
||||
|
||||
void BuildConfigSingBox(const std::shared_ptr<BuildConfigStatus> &status);
|
||||
|
||||
QString BuildChain(int chainId, const std::shared_ptr<BuildConfigStatus> &status);
|
||||
|
||||
QString BuildChainInternal(int chainId, const QList<std::shared_ptr<ProxyEntity>> &ents,
|
||||
const std::shared_ptr<BuildConfigStatus> &status);
|
||||
|
||||
QString WriteVPNSingBoxConfig();
|
||||
|
||||
QString WriteVPNLinuxScript(const QString &configPath);
|
||||
} // namespace NekoGui
|
||||
394
db/Database.cpp
394
db/Database.cpp
@ -1,394 +0,0 @@
|
||||
#include "Database.hpp"
|
||||
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QColor>
|
||||
|
||||
namespace NekoGui {
|
||||
|
||||
ProfileManager *profileManager = new ProfileManager();
|
||||
|
||||
ProfileManager::ProfileManager() : JsonStore("groups/pm.json") {
|
||||
_add(new configItem("groups", &groupsTabOrder, itemType::integerList));
|
||||
}
|
||||
|
||||
QList<int> filterIntJsonFile(const QString &path) {
|
||||
QList<int> result;
|
||||
QDir dr(path);
|
||||
auto entryList = dr.entryList(QDir::Files);
|
||||
for (auto e: entryList) {
|
||||
e = e.toLower();
|
||||
if (!e.endsWith(".json", Qt::CaseInsensitive)) continue;
|
||||
e = e.remove(".json", Qt::CaseInsensitive);
|
||||
bool ok;
|
||||
auto id = e.toInt(&ok);
|
||||
if (ok) {
|
||||
result << id;
|
||||
}
|
||||
}
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProfileManager::LoadManager() {
|
||||
JsonStore::Load();
|
||||
//
|
||||
profiles = {};
|
||||
groups = {};
|
||||
profilesIdOrder = filterIntJsonFile("profiles");
|
||||
groupsIdOrder = filterIntJsonFile("groups");
|
||||
// Load Proxys
|
||||
QList<int> delProfile;
|
||||
for (auto id: profilesIdOrder) {
|
||||
auto ent = LoadProxyEntity(QStringLiteral("profiles/%1.json").arg(id));
|
||||
// Corrupted profile?
|
||||
if (ent == nullptr || ent->bean == nullptr || ent->bean->version == -114514) {
|
||||
delProfile << id;
|
||||
continue;
|
||||
}
|
||||
profiles[id] = ent;
|
||||
}
|
||||
// Clear Corrupted profile
|
||||
for (auto id: delProfile) {
|
||||
DeleteProfile(id);
|
||||
}
|
||||
// Load Groups
|
||||
auto loadedOrder = groupsTabOrder;
|
||||
groupsTabOrder = {};
|
||||
for (auto id: groupsIdOrder) {
|
||||
auto ent = LoadGroup(QStringLiteral("groups/%1.json").arg(id));
|
||||
// Corrupted group?
|
||||
if (ent->id != id) {
|
||||
continue;
|
||||
}
|
||||
// Ensure order contains every group
|
||||
if (!loadedOrder.contains(id)) {
|
||||
loadedOrder << id;
|
||||
}
|
||||
groups[id] = ent;
|
||||
}
|
||||
// Ensure groups contains order
|
||||
for (auto id: loadedOrder) {
|
||||
if (groups.count(id)) {
|
||||
groupsTabOrder << id;
|
||||
}
|
||||
}
|
||||
// First setup
|
||||
if (groups.empty()) {
|
||||
auto defaultGroup = NekoGui::ProfileManager::NewGroup();
|
||||
defaultGroup->name = QObject::tr("Default");
|
||||
NekoGui::profileManager->AddGroup(defaultGroup);
|
||||
}
|
||||
//
|
||||
if (dataStore->flag_reorder) {
|
||||
{
|
||||
// remove all (contains orphan)
|
||||
for (const auto &profile: profiles) {
|
||||
QFile::remove(profile.second->fn);
|
||||
}
|
||||
}
|
||||
std::map<int, int> gidOld2New;
|
||||
{
|
||||
int i = 0;
|
||||
int ii = 0;
|
||||
QList<int> newProfilesIdOrder;
|
||||
std::map<int, std::shared_ptr<ProxyEntity>> newProfiles;
|
||||
for (auto gid: groupsTabOrder) {
|
||||
auto group = GetGroup(gid);
|
||||
gidOld2New[gid] = ii++;
|
||||
for (auto const &profile: group->ProfilesWithOrder()) {
|
||||
auto oldId = profile->id;
|
||||
auto newId = i++;
|
||||
profile->id = newId;
|
||||
profile->gid = gidOld2New[gid];
|
||||
profile->fn = QStringLiteral("profiles/%1.json").arg(newId);
|
||||
profile->Save();
|
||||
newProfiles[newId] = profile;
|
||||
newProfilesIdOrder << newId;
|
||||
}
|
||||
group->order = {};
|
||||
group->Save();
|
||||
}
|
||||
profiles = newProfiles;
|
||||
profilesIdOrder = newProfilesIdOrder;
|
||||
}
|
||||
{
|
||||
QList<int> newGroupsIdOrder;
|
||||
std::map<int, std::shared_ptr<Group>> newGroups;
|
||||
for (auto oldGid: groupsTabOrder) {
|
||||
auto newId = gidOld2New[oldGid];
|
||||
auto group = groups[oldGid];
|
||||
QFile::remove(group->fn);
|
||||
group->id = newId;
|
||||
group->fn = QStringLiteral("groups/%1.json").arg(newId);
|
||||
group->Save();
|
||||
newGroups[newId] = group;
|
||||
newGroupsIdOrder << newId;
|
||||
}
|
||||
groups = newGroups;
|
||||
groupsIdOrder = newGroupsIdOrder;
|
||||
groupsTabOrder = newGroupsIdOrder;
|
||||
}
|
||||
MessageBoxInfo(software_name, "Profiles and groups reorder complete.");
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileManager::SaveManager() {
|
||||
JsonStore::Save();
|
||||
}
|
||||
|
||||
std::shared_ptr<ProxyEntity> ProfileManager::LoadProxyEntity(const QString &jsonPath) {
|
||||
// Load type
|
||||
ProxyEntity ent0(nullptr, nullptr);
|
||||
ent0.fn = jsonPath;
|
||||
auto validJson = ent0.Load();
|
||||
auto type = ent0.type;
|
||||
|
||||
// Load content
|
||||
std::shared_ptr<ProxyEntity> ent;
|
||||
bool validType = validJson;
|
||||
|
||||
if (validType) {
|
||||
ent = NewProxyEntity(type);
|
||||
validType = ent->bean->version != -114514;
|
||||
}
|
||||
|
||||
if (validType) {
|
||||
ent->load_control_must = true;
|
||||
ent->fn = jsonPath;
|
||||
ent->Load();
|
||||
}
|
||||
return ent;
|
||||
}
|
||||
|
||||
// 新建的不给 fn 和 id
|
||||
|
||||
std::shared_ptr<ProxyEntity> ProfileManager::NewProxyEntity(const QString &type) {
|
||||
NekoGui_fmt::AbstractBean *bean;
|
||||
|
||||
if (type == "socks") {
|
||||
bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_Socks5);
|
||||
} else if (type == "http") {
|
||||
bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_HTTP);
|
||||
} else if (type == "shadowsocks") {
|
||||
bean = new NekoGui_fmt::ShadowSocksBean();
|
||||
} else if (type == "chain") {
|
||||
bean = new NekoGui_fmt::ChainBean();
|
||||
} else if (type == "vmess") {
|
||||
bean = new NekoGui_fmt::VMessBean();
|
||||
} else if (type == "trojan") {
|
||||
bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_Trojan);
|
||||
} else if (type == "vless") {
|
||||
bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_VLESS);
|
||||
} else if (type == "naive") {
|
||||
bean = new NekoGui_fmt::NaiveBean();
|
||||
} else if (type == "hysteria2") {
|
||||
bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_Hysteria2);
|
||||
} else if (type == "tuic") {
|
||||
bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_TUIC);
|
||||
} else if (type == "custom") {
|
||||
bean = new NekoGui_fmt::CustomBean();
|
||||
} else {
|
||||
bean = new NekoGui_fmt::AbstractBean(-114514);
|
||||
}
|
||||
|
||||
auto ent = std::make_shared<ProxyEntity>(bean, type);
|
||||
return ent;
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> ProfileManager::NewGroup() {
|
||||
auto ent = std::make_shared<Group>();
|
||||
return ent;
|
||||
}
|
||||
|
||||
// ProxyEntity
|
||||
|
||||
ProxyEntity::ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_) {
|
||||
if (type_ != nullptr) this->type = type_;
|
||||
|
||||
_add(new configItem("type", &type, itemType::string));
|
||||
_add(new configItem("id", &id, itemType::integer));
|
||||
_add(new configItem("gid", &gid, itemType::integer));
|
||||
_add(new configItem("yc", &latency, itemType::integer));
|
||||
_add(new configItem("report", &full_test_report, itemType::string));
|
||||
|
||||
// 可以不关联 bean,只加载 ProxyEntity 的信息
|
||||
if (bean != nullptr) {
|
||||
this->bean = std::shared_ptr<NekoGui_fmt::AbstractBean>(bean);
|
||||
// 有虚函数就要在这里 dynamic_cast
|
||||
_add(new configItem("bean", dynamic_cast<JsonStore *>(bean), itemType::jsonStore));
|
||||
_add(new configItem("traffic", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));
|
||||
}
|
||||
};
|
||||
|
||||
QString ProxyEntity::DisplayLatency() const {
|
||||
if (latency < 0) {
|
||||
return QObject::tr("Unavailable");
|
||||
} else if (latency > 0) {
|
||||
return UNICODE_LRO + QStringLiteral("%1 ms").arg(latency);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
QColor ProxyEntity::DisplayLatencyColor() const {
|
||||
if (latency < 0) {
|
||||
return Qt::red;
|
||||
} else if (latency > 0) {
|
||||
auto greenMs = dataStore->test_latency_url.startsWith("https://") ? 200 : 100;
|
||||
if (latency < greenMs) {
|
||||
return Qt::darkGreen;
|
||||
} else {
|
||||
return Qt::darkYellow;
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Profile
|
||||
|
||||
int ProfileManager::NewProfileID() const {
|
||||
if (profiles.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
return profilesIdOrder.last() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProfileManager::AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid) {
|
||||
if (ent->id >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ent->gid = gid < 0 ? dataStore->current_group : gid;
|
||||
ent->id = NewProfileID();
|
||||
profiles[ent->id] = ent;
|
||||
profilesIdOrder.push_back(ent->id);
|
||||
|
||||
ent->fn = QStringLiteral("profiles/%1.json").arg(ent->id);
|
||||
ent->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::DeleteProfile(int id) {
|
||||
if (id < 0) return;
|
||||
if (dataStore->started_id == id) return;
|
||||
profiles.erase(id);
|
||||
profilesIdOrder.removeAll(id);
|
||||
QFile(QStringLiteral("profiles/%1.json").arg(id)).remove();
|
||||
}
|
||||
|
||||
void ProfileManager::MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid) {
|
||||
if (gid == ent->gid || gid < 0) return;
|
||||
auto oldGroup = GetGroup(ent->gid);
|
||||
if (oldGroup != nullptr && !oldGroup->order.isEmpty()) {
|
||||
oldGroup->order.removeAll(ent->id);
|
||||
oldGroup->Save();
|
||||
}
|
||||
auto newGroup = GetGroup(gid);
|
||||
if (newGroup != nullptr && !newGroup->order.isEmpty()) {
|
||||
newGroup->order.push_back(ent->id);
|
||||
newGroup->Save();
|
||||
}
|
||||
ent->gid = gid;
|
||||
ent->Save();
|
||||
}
|
||||
|
||||
std::shared_ptr<ProxyEntity> ProfileManager::GetProfile(int id) {
|
||||
return profiles.count(id) ? profiles[id] : nullptr;
|
||||
}
|
||||
|
||||
// Group
|
||||
|
||||
Group::Group() {
|
||||
_add(new configItem("id", &id, itemType::integer));
|
||||
_add(new configItem("front_proxy_id", &front_proxy_id, itemType::integer));
|
||||
_add(new configItem("archive", &archive, itemType::boolean));
|
||||
_add(new configItem("skip_auto_update", &skip_auto_update, itemType::boolean));
|
||||
_add(new configItem("name", &name, itemType::string));
|
||||
_add(new configItem("order", &order, itemType::integerList));
|
||||
_add(new configItem("url", &url, itemType::string));
|
||||
_add(new configItem("info", &info, itemType::string));
|
||||
_add(new configItem("lastup", &sub_last_update, itemType::integer64));
|
||||
_add(new configItem("manually_column_width", &manually_column_width, itemType::boolean));
|
||||
_add(new configItem("column_width", &column_width, itemType::integerList));
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> ProfileManager::LoadGroup(const QString &jsonPath) {
|
||||
auto ent = std::make_shared<Group>();
|
||||
ent->fn = jsonPath;
|
||||
ent->Load();
|
||||
return ent;
|
||||
}
|
||||
|
||||
int ProfileManager::NewGroupID() const {
|
||||
if (groups.empty()) {
|
||||
return 0;
|
||||
} else {
|
||||
return groupsIdOrder.last() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool ProfileManager::AddGroup(const std::shared_ptr<Group> &ent) {
|
||||
if (ent->id >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ent->id = NewGroupID();
|
||||
groups[ent->id] = ent;
|
||||
groupsIdOrder.push_back(ent->id);
|
||||
groupsTabOrder.push_back(ent->id);
|
||||
|
||||
ent->fn = QStringLiteral("groups/%1.json").arg(ent->id);
|
||||
ent->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::DeleteGroup(int gid) {
|
||||
if (groups.size() <= 1) return;
|
||||
QList<int> toDelete;
|
||||
for (const auto &[id, profile]: profiles) {
|
||||
if (profile->gid == gid) toDelete += id; // map访问中,不能操作
|
||||
}
|
||||
for (const auto &id: toDelete) {
|
||||
DeleteProfile(id);
|
||||
}
|
||||
groups.erase(gid);
|
||||
groupsIdOrder.removeAll(gid);
|
||||
groupsTabOrder.removeAll(gid);
|
||||
QFile(QStringLiteral("groups/%1.json").arg(gid)).remove();
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> ProfileManager::GetGroup(int id) {
|
||||
return groups.count(id) ? groups[id] : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> ProfileManager::CurrentGroup() {
|
||||
return GetGroup(dataStore->current_group);
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {
|
||||
QList<std::shared_ptr<ProxyEntity>> ret;
|
||||
for (const auto &[_, profile]: profileManager->profiles) {
|
||||
if (id == profile->gid) ret += profile;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<ProxyEntity>> Group::ProfilesWithOrder() const {
|
||||
if (order.isEmpty()) {
|
||||
return Profiles();
|
||||
} else {
|
||||
QList<std::shared_ptr<ProxyEntity>> ret;
|
||||
for (auto _id: order) {
|
||||
auto ent = profileManager->GetProfile(_id);
|
||||
if (ent != nullptr) ret += ent;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace NekoGui
|
||||
@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
#include "ProxyEntity.hpp"
|
||||
#include "Group.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
class ProfileManager : private JsonStore {
|
||||
public:
|
||||
// JsonStore
|
||||
|
||||
// order -> id
|
||||
QList<int> groupsTabOrder;
|
||||
|
||||
// Manager
|
||||
|
||||
std::map<int, std::shared_ptr<ProxyEntity>> profiles;
|
||||
std::map<int, std::shared_ptr<Group>> groups;
|
||||
|
||||
ProfileManager();
|
||||
|
||||
// LoadManager Reset and loads profiles & groups
|
||||
void LoadManager();
|
||||
|
||||
void SaveManager();
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<ProxyEntity> NewProxyEntity(const QString &type);
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<Group> NewGroup();
|
||||
|
||||
bool AddProfile(const std::shared_ptr<ProxyEntity> &ent, int gid = -1);
|
||||
|
||||
void DeleteProfile(int id);
|
||||
|
||||
void MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid);
|
||||
|
||||
std::shared_ptr<ProxyEntity> GetProfile(int id);
|
||||
|
||||
bool AddGroup(const std::shared_ptr<Group> &ent);
|
||||
|
||||
void DeleteGroup(int gid);
|
||||
|
||||
std::shared_ptr<Group> GetGroup(int id);
|
||||
|
||||
std::shared_ptr<Group> CurrentGroup();
|
||||
|
||||
private:
|
||||
// sort by id
|
||||
QList<int> profilesIdOrder;
|
||||
QList<int> groupsIdOrder;
|
||||
|
||||
[[nodiscard]] int NewProfileID() const;
|
||||
|
||||
[[nodiscard]] int NewGroupID() const;
|
||||
|
||||
static std::shared_ptr<ProxyEntity> LoadProxyEntity(const QString &jsonPath);
|
||||
|
||||
static std::shared_ptr<Group> LoadGroup(const QString &jsonPath);
|
||||
};
|
||||
|
||||
extern ProfileManager *profileManager;
|
||||
} // namespace NekoGui
|
||||
31
db/Group.hpp
31
db/Group.hpp
@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
#include "ProxyEntity.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
class Group : public JsonStore {
|
||||
public:
|
||||
int id = -1;
|
||||
bool archive = false;
|
||||
bool skip_auto_update = false;
|
||||
QString name = "";
|
||||
QString url = "";
|
||||
QString info = "";
|
||||
qint64 sub_last_update = 0;
|
||||
int front_proxy_id = -1;
|
||||
|
||||
// list ui
|
||||
bool manually_column_width = false;
|
||||
QList<int> column_width;
|
||||
QList<int> order;
|
||||
|
||||
Group();
|
||||
|
||||
// 按 id 顺序
|
||||
[[nodiscard]] QList<std::shared_ptr<ProxyEntity>> Profiles() const;
|
||||
|
||||
// 按 显示 顺序
|
||||
[[nodiscard]] QList<std::shared_ptr<ProxyEntity>> ProfilesWithOrder() const;
|
||||
};
|
||||
} // namespace NekoGui
|
||||
@ -1,75 +0,0 @@
|
||||
#include "ProfileFilter.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
|
||||
QString ProfileFilter_ent_key(const std::shared_ptr<NekoGui::ProxyEntity> &ent, bool by_address) {
|
||||
by_address &= ent->type != "custom";
|
||||
return by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: QJsonObject2QString(ent->bean->ToJson({"c_cfg", "c_out"}), true) + ent->bean->DisplayType();
|
||||
}
|
||||
|
||||
void ProfileFilter::Uniq(const QList<std::shared_ptr<ProxyEntity>> &in,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out,
|
||||
bool by_address, bool keep_last) {
|
||||
QMap<QString, std::shared_ptr<ProxyEntity>> hashMap;
|
||||
|
||||
for (const auto &ent: in) {
|
||||
QString key = ProfileFilter_ent_key(ent, by_address);
|
||||
if (hashMap.contains(key)) {
|
||||
if (keep_last) {
|
||||
out.removeAll(hashMap[key]);
|
||||
hashMap[key] = ent;
|
||||
out += ent;
|
||||
}
|
||||
} else {
|
||||
hashMap[key] = ent;
|
||||
out += ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileFilter::Common(const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &outSrc,
|
||||
QList<std::shared_ptr<ProxyEntity>> &outDst,
|
||||
bool by_address) {
|
||||
QMap<QString, std::shared_ptr<ProxyEntity>> hashMap;
|
||||
|
||||
for (const auto &ent: src) {
|
||||
QString key = ProfileFilter_ent_key(ent, by_address);
|
||||
hashMap[key] = ent;
|
||||
}
|
||||
for (const auto &ent: dst) {
|
||||
QString key = ProfileFilter_ent_key(ent, by_address);
|
||||
if (hashMap.contains(key)) {
|
||||
outDst += ent;
|
||||
outSrc += hashMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileFilter::OnlyInSrc(const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out,
|
||||
bool by_address) {
|
||||
QMap<QString, bool> hashMap;
|
||||
|
||||
for (const auto &ent: dst) {
|
||||
QString key = ProfileFilter_ent_key(ent, by_address);
|
||||
hashMap[key] = true;
|
||||
}
|
||||
for (const auto &ent: src) {
|
||||
QString key = ProfileFilter_ent_key(ent, by_address);
|
||||
if (!hashMap.contains(key)) out += ent;
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileFilter::OnlyInSrc_ByPointer(const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out) {
|
||||
for (const auto &ent: src) {
|
||||
if (!dst.contains(ent)) out += ent;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace NekoGui
|
||||
@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProxyEntity.hpp"
|
||||
|
||||
namespace NekoGui {
|
||||
class ProfileFilter {
|
||||
public:
|
||||
static void Uniq(
|
||||
const QList<std::shared_ptr<ProxyEntity>> &in,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out,
|
||||
bool by_address = false, // def by bean
|
||||
bool keep_last = false // def keep first
|
||||
);
|
||||
|
||||
static void Common(
|
||||
const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &outSrc,
|
||||
QList<std::shared_ptr<ProxyEntity>> &outDst,
|
||||
bool by_address = false // def by bean
|
||||
);
|
||||
|
||||
static void OnlyInSrc(
|
||||
const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out,
|
||||
bool by_address = false // def by bean
|
||||
);
|
||||
|
||||
static void OnlyInSrc_ByPointer(
|
||||
const QList<std::shared_ptr<ProxyEntity>> &src,
|
||||
const QList<std::shared_ptr<ProxyEntity>> &dst,
|
||||
QList<std::shared_ptr<ProxyEntity>> &out);
|
||||
};
|
||||
} // namespace NekoGui
|
||||
@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
#include "db/traffic/TrafficData.hpp"
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class SocksHttpBean;
|
||||
|
||||
class ShadowSocksBean;
|
||||
|
||||
class VMessBean;
|
||||
|
||||
class TrojanVLESSBean;
|
||||
|
||||
class NaiveBean;
|
||||
|
||||
class QUICBean;
|
||||
|
||||
class CustomBean;
|
||||
|
||||
class ChainBean;
|
||||
}; // namespace NekoGui_fmt
|
||||
|
||||
namespace NekoGui {
|
||||
class ProxyEntity : public JsonStore {
|
||||
public:
|
||||
QString type;
|
||||
|
||||
int id = -1;
|
||||
int gid = 0;
|
||||
int latency = 0;
|
||||
std::shared_ptr<NekoGui_fmt::AbstractBean> bean;
|
||||
std::shared_ptr<NekoGui_traffic::TrafficData> traffic_data = std::make_shared<NekoGui_traffic::TrafficData>("");
|
||||
|
||||
QString full_test_report;
|
||||
|
||||
ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_);
|
||||
|
||||
[[nodiscard]] QString DisplayLatency() const;
|
||||
|
||||
[[nodiscard]] QColor DisplayLatencyColor() const;
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::ChainBean *ChainBean() const {
|
||||
return (NekoGui_fmt::ChainBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::SocksHttpBean *SocksHTTPBean() const {
|
||||
return (NekoGui_fmt::SocksHttpBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::ShadowSocksBean *ShadowSocksBean() const {
|
||||
return (NekoGui_fmt::ShadowSocksBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::VMessBean *VMessBean() const {
|
||||
return (NekoGui_fmt::VMessBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::TrojanVLESSBean *TrojanVLESSBean() const {
|
||||
return (NekoGui_fmt::TrojanVLESSBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::NaiveBean *NaiveBean() const {
|
||||
return (NekoGui_fmt::NaiveBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::QUICBean *QUICBean() const {
|
||||
return (NekoGui_fmt::QUICBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] NekoGui_fmt::CustomBean *CustomBean() const {
|
||||
return (NekoGui_fmt::CustomBean *) bean.get();
|
||||
};
|
||||
};
|
||||
} // namespace NekoGui
|
||||
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
namespace NekoGui_traffic {
|
||||
class TrafficData : public JsonStore {
|
||||
public:
|
||||
int id = -1; // ent id
|
||||
std::string tag;
|
||||
|
||||
long long downlink = 0;
|
||||
long long uplink = 0;
|
||||
long long downlink_rate = 0;
|
||||
long long uplink_rate = 0;
|
||||
|
||||
long long last_update;
|
||||
|
||||
explicit TrafficData(std::string tag) {
|
||||
this->tag = std::move(tag);
|
||||
_add(new configItem("dl", &downlink, itemType::integer64));
|
||||
_add(new configItem("ul", &uplink, itemType::integer64));
|
||||
};
|
||||
|
||||
void Reset() {
|
||||
downlink = 0;
|
||||
uplink = 0;
|
||||
downlink_rate = 0;
|
||||
uplink_rate = 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DisplaySpeed() const {
|
||||
return UNICODE_LRO + QStringLiteral("%1↑ %2↓").arg(ReadableSize(uplink_rate), ReadableSize(downlink_rate));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DisplayTraffic() const {
|
||||
if (downlink + uplink == 0) return "";
|
||||
return UNICODE_LRO + QStringLiteral("%1↑ %2↓").arg(ReadableSize(uplink), ReadableSize(downlink));
|
||||
}
|
||||
};
|
||||
} // namespace NekoGui_traffic
|
||||
@ -1,135 +0,0 @@
|
||||
#include "TrafficLooper.hpp"
|
||||
|
||||
#include "rpc/gRPC.h"
|
||||
#include "ui/mainwindow_interface.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
namespace NekoGui_traffic {
|
||||
|
||||
TrafficLooper *trafficLooper = new TrafficLooper;
|
||||
QElapsedTimer elapsedTimer;
|
||||
|
||||
TrafficData *TrafficLooper::update_stats(TrafficData *item) {
|
||||
#ifndef NKR_NO_GRPC
|
||||
// last update
|
||||
auto now = elapsedTimer.elapsed();
|
||||
auto interval = now - item->last_update;
|
||||
item->last_update = now;
|
||||
if (interval <= 0) return nullptr;
|
||||
|
||||
// query
|
||||
auto uplink = NekoGui_rpc::defaultClient->QueryStats(item->tag, "uplink");
|
||||
auto downlink = NekoGui_rpc::defaultClient->QueryStats(item->tag, "downlink");
|
||||
|
||||
// add diff
|
||||
item->downlink += downlink;
|
||||
item->uplink += uplink;
|
||||
item->downlink_rate = downlink * 1000 / interval;
|
||||
item->uplink_rate = uplink * 1000 / interval;
|
||||
|
||||
// return diff
|
||||
auto ret = new TrafficData(item->tag);
|
||||
ret->downlink = downlink;
|
||||
ret->uplink = uplink;
|
||||
ret->downlink_rate = item->downlink_rate;
|
||||
ret->uplink_rate = item->uplink_rate;
|
||||
return ret;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonArray TrafficLooper::get_connection_list() {
|
||||
#ifndef NKR_NO_GRPC
|
||||
auto str = NekoGui_rpc::defaultClient->ListConnections();
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(str.c_str());
|
||||
return jsonDocument.array();
|
||||
#else
|
||||
return QJsonArray{};
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrafficLooper::UpdateAll() {
|
||||
std::map<std::string, TrafficData *> updated; // tag to diff
|
||||
for (const auto &item: this->items) {
|
||||
auto data = item.get();
|
||||
auto diff = updated[data->tag];
|
||||
// 避免重复查询一个 outbound tag
|
||||
if (diff == nullptr) {
|
||||
diff = update_stats(data);
|
||||
updated[data->tag] = diff;
|
||||
} else {
|
||||
data->uplink += diff->uplink;
|
||||
data->downlink += diff->downlink;
|
||||
data->uplink_rate = diff->uplink_rate;
|
||||
data->downlink_rate = diff->downlink_rate;
|
||||
}
|
||||
}
|
||||
updated[bypass->tag] = update_stats(bypass);
|
||||
//
|
||||
for (const auto &pair: updated) {
|
||||
delete pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
void TrafficLooper::Loop() {
|
||||
elapsedTimer.start();
|
||||
while (true) {
|
||||
auto sleep_ms = NekoGui::dataStore->traffic_loop_interval;
|
||||
if (sleep_ms < 500 || sleep_ms > 5000) sleep_ms = 1000;
|
||||
QThread::msleep(sleep_ms);
|
||||
if (NekoGui::dataStore->traffic_loop_interval == 0) continue; // user disabled
|
||||
|
||||
// profile start and stop
|
||||
if (!loop_enabled) {
|
||||
// 停止
|
||||
if (looping) {
|
||||
looping = false;
|
||||
runOnUiThread([=] {
|
||||
auto m = GetMainWindow();
|
||||
m->refresh_status("STOP");
|
||||
});
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// 开始
|
||||
if (!looping) {
|
||||
looping = true;
|
||||
}
|
||||
}
|
||||
|
||||
// do update
|
||||
loop_mutex.lock();
|
||||
|
||||
UpdateAll();
|
||||
|
||||
// do conn list update
|
||||
QJsonArray conn_list;
|
||||
if (NekoGui::dataStore->connection_statistics) {
|
||||
conn_list = get_connection_list();
|
||||
}
|
||||
|
||||
loop_mutex.unlock();
|
||||
|
||||
// post to UI
|
||||
runOnUiThread([=] {
|
||||
auto m = GetMainWindow();
|
||||
if (proxy != nullptr) {
|
||||
m->refresh_status(QObject::tr("Proxy: %1\nDirect: %2").arg(proxy->DisplaySpeed(), bypass->DisplaySpeed()));
|
||||
}
|
||||
for (const auto &item: items) {
|
||||
if (item->id < 0) continue;
|
||||
m->refresh_proxy_list(item->id);
|
||||
}
|
||||
if (NekoGui::dataStore->connection_statistics) {
|
||||
m->refresh_connection_list(conn_list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace NekoGui_traffic
|
||||
@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
#include "TrafficData.hpp"
|
||||
|
||||
namespace NekoGui_traffic {
|
||||
class TrafficLooper {
|
||||
public:
|
||||
bool loop_enabled = false;
|
||||
bool looping = false;
|
||||
QMutex loop_mutex;
|
||||
|
||||
QList<std::shared_ptr<TrafficData>> items;
|
||||
TrafficData *proxy = nullptr;
|
||||
|
||||
void UpdateAll();
|
||||
|
||||
void Loop();
|
||||
|
||||
private:
|
||||
TrafficData *bypass = new TrafficData("bypass");
|
||||
|
||||
[[nodiscard]] static TrafficData *update_stats(TrafficData *item);
|
||||
|
||||
[[nodiscard]] static QJsonArray get_connection_list();
|
||||
};
|
||||
|
||||
extern TrafficLooper *trafficLooper;
|
||||
} // namespace NekoGui_traffic
|
||||
@ -1,24 +0,0 @@
|
||||
## 构建 nekobox_core
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
| nekoray
|
||||
| go/cmd/*
|
||||
| sing-box-extra
|
||||
| sing-box
|
||||
| ......
|
||||
```
|
||||
|
||||
### 常规构建
|
||||
|
||||
1. `bash libs/get_source.sh` (自动下载目录结构,自动 checkout commit)
|
||||
2. `GOOS=windows GOARCH=amd64 bash libs/build_go.sh`
|
||||
|
||||
具体支持的 GOOS 和 GOARCH 请看 `libs/build_go.sh`
|
||||
|
||||
非官方构建无需编译 `updater` `launcher`
|
||||
|
||||
### sing-box tags
|
||||
|
||||
具体使用的 tags 请看 `libs/build_go.sh`
|
||||
@ -1,76 +0,0 @@
|
||||
在 Linux 下编译 Nekoray
|
||||
|
||||
## git clone 源码
|
||||
|
||||
```
|
||||
git clone https://github.com/MatsuriDayo/nekoray.git --recursive
|
||||
```
|
||||
|
||||
## 简单编译法
|
||||
|
||||
条件:
|
||||
|
||||
1. C++ 依赖:`protobuf yaml-cpp zxing-cpp` 已用包管理器安装,并符合版本要求。
|
||||
2. 已安装 `qtbase` `qtsvg` `qttools` `qtx11extras`
|
||||
3. 已安装 Qt `5.12.x` 或 `5.15.x`
|
||||
4. 系统为 `x86-64-linux-gnu`
|
||||
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja ..
|
||||
ninja
|
||||
```
|
||||
|
||||
编译完成后得到 `nekobox`
|
||||
|
||||
解压 Release 的压缩包,替换其中的 `nekobox`,删除 `launcher` 即可使用。
|
||||
|
||||
## 复杂编译法
|
||||
|
||||
### CMake 参数
|
||||
|
||||
| CMake 参数 | 默认值 | 含义 |
|
||||
|-------------------|-------------------|-----------------------|
|
||||
| QT_VERSION_MAJOR | 5 | QT版本 |
|
||||
| NKR_NO_EXTERNAL | | 不包含外部 C/C++ 依赖 (以下所有) |
|
||||
| NKR_NO_YAML | | 不包含 yaml-cpp |
|
||||
| NKR_NO_QHOTKEY | | 不包含 qhotkey |
|
||||
| NKR_NO_ZXING | | 不包含 zxing |
|
||||
| NKR_NO_GRPC | | 不包含 gRPC |
|
||||
| NKR_PACKAGE | | 编译 package 版本 (aur) |
|
||||
| NKR_LIBS | ./libs/deps/built | 依赖搜索目录 |
|
||||
| NKR_DISABLE_LIBS | | 禁用 NKR_LIBS |
|
||||
|
||||
1. `NKR_LIBS` 的值会被追加到 `CMAKE_PREFIX_PATH`
|
||||
2. `NKR_PACKAGE` 打开后,`NKR_LIBS` 的默认值为 `./libs/deps/package` ,具体依赖请看 `build_deps_all.sh`
|
||||
3. `NKR_PACKAGE` 打开后,应用将使用 appdata 目录存放配置,自动更新等功能将被禁用。
|
||||
|
||||
### C++ 部分
|
||||
|
||||
当您的发行版没有上面几个 C++ 依赖包,或者版本不符合要求时,可以参考 `build_deps_all.sh` 编译脚本自行编译。
|
||||
|
||||
条件: 已安装 Qt `5.12.x` 或 `5.15.x`
|
||||
|
||||
#### 编译安装 C/C++ 依赖
|
||||
|
||||
(这一步可能要挂梯)
|
||||
|
||||
```shell
|
||||
./libs/build_deps_all.sh
|
||||
```
|
||||
|
||||
#### 编译本体
|
||||
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja ..
|
||||
ninja
|
||||
```
|
||||
|
||||
编译完成后得到 `nekobox`
|
||||
|
||||
### Go 部分编译
|
||||
|
||||
请看 [Build_Core.md](./Build_Core.md)
|
||||
@ -1,64 +0,0 @@
|
||||
在 Windows 下编译 Nekoray
|
||||
|
||||
### git clone 源码
|
||||
|
||||
```
|
||||
git clone https://github.com/MatsuriDayo/nekoray.git --recursive
|
||||
```
|
||||
|
||||
### 安装 Visual Studio
|
||||
|
||||
从微软官网安装,可以使用 2019 和 2022 版本,安装 Win32 C++ 开发环境。
|
||||
|
||||
安装好后可以在「开始」菜单找到 `x64 Native Tools Command Prompt`
|
||||
|
||||
本文之后的命令均在该 cmd 内执行。`cmake` `ninja` 等工具使用 VS 自带的即可。
|
||||
|
||||
### 下载 Qt SDK
|
||||
|
||||
目前 Windows Release 使用的版本是 Qt 6.5.x
|
||||
|
||||
下载解压后,将 bin 目录添加到环境变量。
|
||||
|
||||
#### Release 编译用到的 Qt 包下载 (MSVC2019 x86_64)
|
||||
|
||||
https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt6.5.0-Windows-x86_64-VS2022-17.5.5-20230507.7z
|
||||
|
||||
#### 官方签名版 Qt 5.15.2 (可选,已知有内存泄漏的BUG)
|
||||
|
||||
在此下载 `qtbase` `qtsvg` `qttools` 的包并解压到同一个目录。
|
||||
|
||||
https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win64_msvc2019_64/
|
||||
|
||||
### C++ 部分编译
|
||||
|
||||
#### 编译安装 C/C++ 依赖
|
||||
|
||||
(这一步可能要挂梯)
|
||||
|
||||
```shell
|
||||
bash ./libs/build_deps_all.sh
|
||||
```
|
||||
|
||||
目前只有 bash 脚本,没有批处理或 powershell,如果 Windows 没有带 bash 建议自行安装。
|
||||
|
||||
CMake 参数等细节与 Linux 大同小异,有问题可以参照 Build_Linux 文档。
|
||||
|
||||
#### 编译本体
|
||||
|
||||
请根据你的 QT Sdk 的位置替换命令
|
||||
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=D:/path/to/qt/5.15.2/msvc2019_64 ..
|
||||
ninja
|
||||
```
|
||||
|
||||
编译完成后得到 `nekobox.exe`
|
||||
|
||||
最后运行 `windeployqt nekobox.exe` 自动复制所需 DLL 等文件到当前目录
|
||||
|
||||
### Go 部分编译
|
||||
|
||||
请看 [Build_Core.md](./Build_Core.md)
|
||||
@ -1,5 +0,0 @@
|
||||
# 运行参数
|
||||
|
||||
- `-many` 无视同目录正在运行的实例,强行开启新的实例。
|
||||
- `-appdata` 开启后配置文件会指定目录,未指定目录则使用共享目录,无法多开和自动升级。
|
||||
- `-flag_reorder` 进行重新整理配置文件的顺序,并删除损坏和孤立的配置。
|
||||
@ -1,81 +0,0 @@
|
||||
## Linux 安装
|
||||
|
||||
### Debian 系发行版
|
||||
|
||||
使用 Debian 系发行版时,推荐使用 .deb 包安装:
|
||||
|
||||
```shell
|
||||
sudo apt install ./nekoray-*-debian-x64.deb
|
||||
```
|
||||
|
||||
安装完成后,桌面快捷方式启动自带参数 `-appdata`,如果想要直接启动并使用之前的配置,注意附带本参数。
|
||||
|
||||
### Arch 系发行版
|
||||
|
||||
使用 Arch 系发行版时,推荐从 ```aur``` 或 ```archlinuxcn``` 安装:
|
||||
|
||||
#### AUR
|
||||
##### 最新稳定版
|
||||
|
||||
```shell
|
||||
[yay/paru] -S nekoray
|
||||
```
|
||||
|
||||
##### 最新 Git 版 (开发版)
|
||||
|
||||
```shell
|
||||
[yay/paru] -S nekoray-git
|
||||
```
|
||||
|
||||
#### archlinuxcn
|
||||
|
||||
##### 最新稳定版
|
||||
|
||||
```shell
|
||||
sudo pacman -S nekoray
|
||||
```
|
||||
|
||||
##### 最新 Git 版 (开发版)
|
||||
|
||||
```shell
|
||||
sudo pacman -S nekoray-git
|
||||
```
|
||||
|
||||
### 其他发行版
|
||||
|
||||
下载 .zip 文件,解压到合适的路径,开箱即用。
|
||||
|
||||
或下载 .AppImage,并使用 `chmod +x nekoray-*-AppImage-x64.AppImage` 给予可执行权限。
|
||||
|
||||
具体使用方法见下文。
|
||||
|
||||
## Linux 运行
|
||||
|
||||
**使用 Linux 系统相信您已具备基本的排错能力,
|
||||
本项目不提供特定发行版/架构的支持,预编译文件不能满足您的需求时,请自行编译/适配。**
|
||||
|
||||
已知部分 Linux 发行版无法使用、非 x86_64 暂无适配,可以尝试自行编译。
|
||||
|
||||
目前 Release 便携包解压后,有两种使用方法:
|
||||
|
||||
1. System: 若要使用系统的 Qt5 运行库,请执行 `./nekoray`
|
||||
2. Bundle: 若要使用预编译的 Qt 运行库,请执行 `./launcher`
|
||||
|
||||
### Bundle
|
||||
|
||||
要求:已安装主流的发行版和 xcb 桌面环境。
|
||||
|
||||
运行: `./launcher` 或 部分系统可双击打开
|
||||
|
||||
launcher 参数
|
||||
|
||||
* `./launcher -- -appdata` ( `--` 后的参数传递给主程序 )
|
||||
* `-debug` Debug mode
|
||||
|
||||
Ubuntu 22.04: `sudo apt install libxcb-xinerama0`
|
||||
|
||||
### System
|
||||
|
||||
要求:已安装主流的发行版和 xcb 桌面环境,已安装 Qt5.12 ~ Qt5.15 环境。
|
||||
|
||||
运行: `./nekoray` 或 部分系统可双击打开。如果无法运行,建议使用 Bundle 版。
|
||||
@ -1,6 +0,0 @@
|
||||
# 技术文档
|
||||
|
||||
# Technical documentation
|
||||
|
||||
1. Build GUI: `Build_*.md`
|
||||
2. Build Core: `Build_Core.md`
|
||||
@ -1,79 +0,0 @@
|
||||
#include "includes.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHostInfo>
|
||||
#include <QUrl>
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
AbstractBean::AbstractBean(int version) {
|
||||
this->version = version;
|
||||
_add(new configItem("_v", &this->version, itemType::integer));
|
||||
_add(new configItem("name", &name, itemType::string));
|
||||
_add(new configItem("addr", &serverAddress, itemType::string));
|
||||
_add(new configItem("port", &serverPort, itemType::integer));
|
||||
_add(new configItem("c_cfg", &custom_config, itemType::string));
|
||||
_add(new configItem("c_out", &custom_outbound, itemType::string));
|
||||
}
|
||||
|
||||
QString AbstractBean::ToNekorayShareLink(const QString &type) {
|
||||
auto b = ToJson();
|
||||
QUrl url;
|
||||
url.setScheme("nekoray");
|
||||
url.setHost(type);
|
||||
url.setFragment(QJsonObject2QString(b, true)
|
||||
.toUtf8()
|
||||
.toBase64(QByteArray::Base64UrlEncoding));
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayAddress() {
|
||||
return ::DisplayAddress(serverAddress, serverPort);
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayName() {
|
||||
if (name.isEmpty()) {
|
||||
return DisplayAddress();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayTypeAndName() {
|
||||
return QStringLiteral("[%1] %2").arg(DisplayType(), DisplayName());
|
||||
}
|
||||
|
||||
void AbstractBean::ResolveDomainToIP(const std::function<void()> &onFinished) {
|
||||
bool noResolve = false;
|
||||
if (dynamic_cast<ChainBean *>(this) != nullptr) noResolve = true;
|
||||
if (dynamic_cast<CustomBean *>(this) != nullptr) noResolve = true;
|
||||
if (dynamic_cast<NaiveBean *>(this) != nullptr) noResolve = true;
|
||||
if (IsIpAddress(serverAddress)) noResolve = true;
|
||||
if (noResolve) {
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) // TODO older QT
|
||||
QHostInfo::lookupHost(serverAddress, QApplication::instance(), [=](const QHostInfo &host) {
|
||||
auto addr = host.addresses();
|
||||
if (!addr.isEmpty()) {
|
||||
auto domain = serverAddress;
|
||||
auto stream = GetStreamSettings(this);
|
||||
|
||||
// replace serverAddress
|
||||
serverAddress = addr.first().toString();
|
||||
|
||||
// replace ws tls
|
||||
if (stream != nullptr) {
|
||||
if (stream->security == "tls" && stream->sni.isEmpty()) {
|
||||
stream->sni = domain;
|
||||
}
|
||||
if (stream->network == "ws" && stream->host.isEmpty()) {
|
||||
stream->host = domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
onFinished();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,69 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
struct CoreObjOutboundBuildResult {
|
||||
public:
|
||||
QJsonObject outbound;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct ExternalBuildResult {
|
||||
public:
|
||||
QString program;
|
||||
QStringList env;
|
||||
QStringList arguments;
|
||||
//
|
||||
QString tag;
|
||||
//
|
||||
QString error;
|
||||
QString config_export;
|
||||
};
|
||||
|
||||
class AbstractBean : public JsonStore {
|
||||
public:
|
||||
int version;
|
||||
|
||||
QString name = "";
|
||||
QString serverAddress = "127.0.0.1";
|
||||
int serverPort = 1080;
|
||||
|
||||
QString custom_config = "";
|
||||
QString custom_outbound = "";
|
||||
|
||||
explicit AbstractBean(int version);
|
||||
|
||||
//
|
||||
|
||||
QString ToNekorayShareLink(const QString &type);
|
||||
|
||||
void ResolveDomainToIP(const std::function<void()> &onFinished);
|
||||
|
||||
//
|
||||
|
||||
[[nodiscard]] virtual QString DisplayAddress();
|
||||
|
||||
[[nodiscard]] virtual QString DisplayName();
|
||||
|
||||
virtual QString DisplayCoreType() { return software_core_name; };
|
||||
|
||||
virtual QString DisplayType() { return {}; };
|
||||
|
||||
virtual QString DisplayTypeAndName();
|
||||
|
||||
//
|
||||
|
||||
virtual int NeedExternal(bool isFirstProfile) { return 0; };
|
||||
|
||||
virtual CoreObjOutboundBuildResult BuildCoreObjSingBox() { return {}; };
|
||||
|
||||
virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) { return {}; };
|
||||
|
||||
virtual QString ToShareLink() { return {}; };
|
||||
};
|
||||
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,237 +0,0 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
void V2rayStreamSettings::BuildStreamSettingsSingBox(QJsonObject *outbound) {
|
||||
// https://sing-box.sagernet.org/configuration/shared/v2ray-transport
|
||||
|
||||
if (network != "tcp") {
|
||||
QJsonObject transport{{"type", network}};
|
||||
if (network == "ws") {
|
||||
if (!host.isEmpty()) transport["headers"] = QJsonObject{{"Host", host}};
|
||||
// ws path & ed
|
||||
auto pathWithoutEd = SubStrBefore(path, "?ed=");
|
||||
if (!pathWithoutEd.isEmpty()) transport["path"] = pathWithoutEd;
|
||||
if (pathWithoutEd != path) {
|
||||
auto ed = SubStrAfter(path, "?ed=").toInt();
|
||||
if (ed > 0) {
|
||||
transport["max_early_data"] = ed;
|
||||
transport["early_data_header_name"] = "Sec-WebSocket-Protocol";
|
||||
}
|
||||
}
|
||||
if (ws_early_data_length > 0) {
|
||||
transport["max_early_data"] = ws_early_data_length;
|
||||
transport["early_data_header_name"] = ws_early_data_name;
|
||||
}
|
||||
} else if (network == "http") {
|
||||
if (!path.isEmpty()) transport["path"] = path;
|
||||
if (!host.isEmpty()) transport["host"] = QList2QJsonArray(host.split(","));
|
||||
} else if (network == "grpc") {
|
||||
if (!path.isEmpty()) transport["service_name"] = path;
|
||||
} else if (network == "httpupgrade") {
|
||||
if (!path.isEmpty()) transport["path"] = path;
|
||||
if (!host.isEmpty()) transport["host"] = host;
|
||||
}
|
||||
outbound->insert("transport", transport);
|
||||
} else if (header_type == "http") {
|
||||
// TCP + headerType
|
||||
QJsonObject transport{
|
||||
{"type", "http"},
|
||||
{"method", "GET"},
|
||||
{"path", path},
|
||||
{"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}},
|
||||
};
|
||||
outbound->insert("transport", transport);
|
||||
}
|
||||
|
||||
// 对应字段 tls
|
||||
if (security == "tls") {
|
||||
QJsonObject tls{{"enabled", true}};
|
||||
if (allow_insecure || NekoGui::dataStore->skip_cert) tls["insecure"] = true;
|
||||
if (!sni.trimmed().isEmpty()) tls["server_name"] = sni;
|
||||
if (!certificate.trimmed().isEmpty()) {
|
||||
tls["certificate"] = certificate.trimmed();
|
||||
}
|
||||
if (!alpn.trimmed().isEmpty()) {
|
||||
tls["alpn"] = QList2QJsonArray(alpn.split(","));
|
||||
}
|
||||
QString fp = utlsFingerprint;
|
||||
if (!reality_pbk.trimmed().isEmpty()) {
|
||||
tls["reality"] = QJsonObject{
|
||||
{"enabled", true},
|
||||
{"public_key", reality_pbk},
|
||||
{"short_id", reality_sid.split(",")[0]},
|
||||
};
|
||||
if (fp.isEmpty()) fp = "random";
|
||||
}
|
||||
if (!fp.isEmpty()) {
|
||||
tls["utls"] = QJsonObject{
|
||||
{"enabled", true},
|
||||
{"fingerprint", fp},
|
||||
};
|
||||
}
|
||||
outbound->insert("tls", tls);
|
||||
}
|
||||
|
||||
if (outbound->value("type").toString() == "vmess" || outbound->value("type").toString() == "vless") {
|
||||
outbound->insert("packet_encoding", packet_encoding);
|
||||
}
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult SocksHttpBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound["type"] = socks_http_type == type_HTTP ? "http" : "socks";
|
||||
if (socks_http_type == type_Socks4) outbound["version"] = "4";
|
||||
outbound["server"] = serverAddress;
|
||||
outbound["server_port"] = serverPort;
|
||||
|
||||
if (!username.isEmpty() && !password.isEmpty()) {
|
||||
outbound["username"] = username;
|
||||
outbound["password"] = password;
|
||||
}
|
||||
|
||||
stream->BuildStreamSettingsSingBox(&outbound);
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult ShadowSocksBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound{{"type", "shadowsocks"}};
|
||||
outbound["server"] = serverAddress;
|
||||
outbound["server_port"] = serverPort;
|
||||
outbound["method"] = method;
|
||||
outbound["password"] = password;
|
||||
|
||||
if (uot != 0) {
|
||||
QJsonObject udp_over_tcp{
|
||||
{"enabled", true},
|
||||
{"version", uot},
|
||||
};
|
||||
outbound["udp_over_tcp"] = udp_over_tcp;
|
||||
} else {
|
||||
outbound["udp_over_tcp"] = false;
|
||||
}
|
||||
|
||||
if (!plugin.trimmed().isEmpty()) {
|
||||
outbound["plugin"] = SubStrBefore(plugin, ";");
|
||||
outbound["plugin_opts"] = SubStrAfter(plugin, ";");
|
||||
}
|
||||
|
||||
stream->BuildStreamSettingsSingBox(&outbound);
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult VMessBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound{
|
||||
{"type", "vmess"},
|
||||
{"server", serverAddress},
|
||||
{"server_port", serverPort},
|
||||
{"uuid", uuid.trimmed()},
|
||||
{"alter_id", aid},
|
||||
{"security", security},
|
||||
};
|
||||
|
||||
stream->BuildStreamSettingsSingBox(&outbound);
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult TrojanVLESSBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound{
|
||||
{"type", proxy_type == proxy_VLESS ? "vless" : "trojan"},
|
||||
{"server", serverAddress},
|
||||
{"server_port", serverPort},
|
||||
};
|
||||
|
||||
QJsonObject settings;
|
||||
if (proxy_type == proxy_VLESS) {
|
||||
if (flow.right(7) == "-udp443") {
|
||||
// 检查末尾是否包含"-udp443",如果是,则删去
|
||||
flow.chop(7);
|
||||
} else if (flow == "none") {
|
||||
// 不使用 flow
|
||||
flow = "";
|
||||
}
|
||||
outbound["uuid"] = password.trimmed();
|
||||
outbound["flow"] = flow;
|
||||
} else {
|
||||
outbound["password"] = password;
|
||||
}
|
||||
|
||||
stream->BuildStreamSettingsSingBox(&outbound);
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult QUICBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject coreTlsObj{
|
||||
{"enabled", true},
|
||||
{"disable_sni", disableSni},
|
||||
{"insecure", allowInsecure},
|
||||
{"certificate", caText.trimmed()},
|
||||
{"server_name", sni},
|
||||
};
|
||||
if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QList2QJsonArray(alpn.split(","));
|
||||
if (proxy_type == proxy_Hysteria2) coreTlsObj["alpn"] = "h3";
|
||||
|
||||
QJsonObject outbound{
|
||||
{"server", serverAddress},
|
||||
{"server_port", serverPort},
|
||||
{"tls", coreTlsObj},
|
||||
};
|
||||
|
||||
if (proxy_type == proxy_Hysteria2) {
|
||||
outbound["type"] = "hysteria2";
|
||||
outbound["password"] = password;
|
||||
outbound["up_mbps"] = uploadMbps;
|
||||
outbound["down_mbps"] = downloadMbps;
|
||||
|
||||
if (!hopPort.trimmed().isEmpty()) {
|
||||
outbound["hop_ports"] = hopPort;
|
||||
outbound["hop_interval"] = hopInterval;
|
||||
}
|
||||
if (!obfsPassword.isEmpty()) {
|
||||
outbound["obfs"] = QJsonObject{
|
||||
{"type", "salamander"},
|
||||
{"password", obfsPassword},
|
||||
};
|
||||
}
|
||||
} else if (proxy_type == proxy_TUIC) {
|
||||
outbound["type"] = "tuic";
|
||||
outbound["uuid"] = uuid;
|
||||
outbound["password"] = password;
|
||||
outbound["congestion_control"] = congestionControl;
|
||||
if (uos) {
|
||||
outbound["udp_over_stream"] = true;
|
||||
} else {
|
||||
outbound["udp_relay_mode"] = udpRelayMode;
|
||||
}
|
||||
outbound["zero_rtt_handshake"] = zeroRttHandshake;
|
||||
if (!heartbeat.trimmed().isEmpty()) outbound["heartbeat"] = heartbeat;
|
||||
}
|
||||
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult CustomBean::BuildCoreObjSingBox() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
if (core == "internal") {
|
||||
result.outbound = QString2QJsonObject(config_simple);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,265 +0,0 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#define WriteTempFile(fn, data) \
|
||||
QDir dir; \
|
||||
if (!dir.exists("temp")) dir.mkdir("temp"); \
|
||||
QFile f(QStringLiteral("temp/") + fn); \
|
||||
bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate); \
|
||||
if (ok) { \
|
||||
f.write(data); \
|
||||
} else { \
|
||||
result.error = f.errorString(); \
|
||||
} \
|
||||
f.close(); \
|
||||
auto TempFile = QFileInfo(f).absoluteFilePath();
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
// -1: Cannot use this config
|
||||
// 0: Internal
|
||||
// 1: Mapping External
|
||||
// 2: Direct External
|
||||
|
||||
int NaiveBean::NeedExternal(bool isFirstProfile) {
|
||||
if (isFirstProfile) {
|
||||
if (NekoGui::dataStore->spmode_vpn) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int QUICBean::NeedExternal(bool isFirstProfile) {
|
||||
auto extCore = [=] {
|
||||
if (isFirstProfile) {
|
||||
if (NekoGui::dataStore->spmode_vpn && hopPort.trimmed().isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
} else {
|
||||
if (!hopPort.trimmed().isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
if (!forceExternal) {
|
||||
// sing-box support
|
||||
return 0;
|
||||
} else {
|
||||
// external core support
|
||||
return extCore();
|
||||
}
|
||||
}
|
||||
|
||||
int CustomBean::NeedExternal(bool isFirstProfile) {
|
||||
if (core == "internal" || core == "internal-full") return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {
|
||||
ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("naive")};
|
||||
|
||||
auto is_direct = external_stat == 2;
|
||||
auto domain_address = sni.isEmpty() ? serverAddress : sni;
|
||||
auto connect_address = is_direct ? serverAddress : "127.0.0.1";
|
||||
auto connect_port = is_direct ? serverPort : mapping_port;
|
||||
domain_address = WrapIPV6Host(domain_address);
|
||||
connect_address = WrapIPV6Host(connect_address);
|
||||
|
||||
auto proxy_url = QUrl();
|
||||
proxy_url.setScheme(protocol);
|
||||
proxy_url.setUserName(username);
|
||||
proxy_url.setPassword(password);
|
||||
proxy_url.setPort(connect_port);
|
||||
proxy_url.setHost(domain_address);
|
||||
|
||||
if (!disable_log) result.arguments += "--log";
|
||||
result.arguments += "--listen=socks://127.0.0.1:" + Int2String(socks_port);
|
||||
result.arguments += "--proxy=" + proxy_url.toString(QUrl::FullyEncoded);
|
||||
if (domain_address != connect_address)
|
||||
result.arguments += "--host-resolver-rules=MAP " + domain_address + " " + connect_address;
|
||||
if (insecure_concurrency > 0) result.arguments += "--insecure-concurrency=" + Int2String(insecure_concurrency);
|
||||
if (!extra_headers.trimmed().isEmpty()) result.arguments += "--extra-headers=" + extra_headers;
|
||||
if (!certificate.trimmed().isEmpty()) {
|
||||
WriteTempFile("naive_" + GetRandomString(10) + ".crt", certificate.toUtf8());
|
||||
result.env += "SSL_CERT_FILE=" + TempFile;
|
||||
}
|
||||
|
||||
auto config_export = QStringList{result.program};
|
||||
config_export += result.arguments;
|
||||
result.config_export = QStringList2Command(config_export);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExternalBuildResult QUICBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {
|
||||
if (proxy_type == proxy_TUIC) {
|
||||
ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("tuic")};
|
||||
|
||||
QJsonObject relay;
|
||||
|
||||
relay["uuid"] = uuid;
|
||||
relay["password"] = password;
|
||||
relay["udp_relay_mode"] = udpRelayMode;
|
||||
relay["congestion_control"] = congestionControl;
|
||||
relay["zero_rtt_handshake"] = zeroRttHandshake;
|
||||
relay["disable_sni"] = disableSni;
|
||||
if (!heartbeat.trimmed().isEmpty()) relay["heartbeat"] = heartbeat;
|
||||
if (!alpn.trimmed().isEmpty()) relay["alpn"] = QList2QJsonArray(alpn.split(","));
|
||||
|
||||
if (!caText.trimmed().isEmpty()) {
|
||||
WriteTempFile("tuic_" + GetRandomString(10) + ".crt", caText.toUtf8());
|
||||
QJsonArray certificate;
|
||||
certificate.append(TempFile);
|
||||
relay["certificates"] = certificate;
|
||||
}
|
||||
|
||||
// The most confused part of TUIC......
|
||||
if (serverAddress == sni) {
|
||||
relay["server"] = serverAddress + ":" + Int2String(serverPort);
|
||||
} else {
|
||||
relay["server"] = sni + ":" + Int2String(serverPort);
|
||||
relay["ip"] = serverAddress;
|
||||
}
|
||||
|
||||
QJsonObject local{
|
||||
{"server", "127.0.0.1:" + Int2String(socks_port)},
|
||||
};
|
||||
|
||||
QJsonObject config{
|
||||
{"relay", relay},
|
||||
{"local", local},
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
result.config_export = QJsonObject2QString(config, false);
|
||||
WriteTempFile("tuic_" + GetRandomString(10) + ".json", result.config_export.toUtf8());
|
||||
result.arguments = QStringList{"-c", TempFile};
|
||||
|
||||
return result;
|
||||
} else if (proxy_type == proxy_Hysteria2) {
|
||||
ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("hysteria2")};
|
||||
|
||||
QJsonObject config;
|
||||
|
||||
auto server = serverAddress;
|
||||
if (!hopPort.trimmed().isEmpty()) {
|
||||
server = WrapIPV6Host(server) + ":" + hopPort;
|
||||
} else {
|
||||
server = WrapIPV6Host(server) + ":" + Int2String(serverPort);
|
||||
}
|
||||
|
||||
QJsonObject transport;
|
||||
transport["type"] = "udp";
|
||||
transport["udp"] = QJsonObject{
|
||||
{"hopInterval", QString::number(hopInterval) + "s"},
|
||||
};
|
||||
config["transport"] = transport;
|
||||
|
||||
config["server"] = server;
|
||||
config["socks5"] = QJsonObject{
|
||||
{"listen", "127.0.0.1:" + Int2String(socks_port)},
|
||||
{"disableUDP", false},
|
||||
};
|
||||
config["auth"] = password;
|
||||
|
||||
QJsonObject bandwidth;
|
||||
if (uploadMbps > 0) bandwidth["up"] = Int2String(uploadMbps) + " mbps";
|
||||
if (downloadMbps > 0) bandwidth["down"] = Int2String(downloadMbps) + " mbps";
|
||||
config["bandwidth"] = bandwidth;
|
||||
|
||||
QJsonObject quic;
|
||||
if (streamReceiveWindow > 0) quic["initStreamReceiveWindow"] = streamReceiveWindow;
|
||||
if (connectionReceiveWindow > 0) quic["initConnReceiveWindow"] = connectionReceiveWindow;
|
||||
if (disableMtuDiscovery) quic["disablePathMTUDiscovery"] = true;
|
||||
config["quic"] = quic;
|
||||
|
||||
config["fastOpen"] = true;
|
||||
config["lazy"] = true;
|
||||
|
||||
if (!obfsPassword.isEmpty()) {
|
||||
QJsonObject obfs;
|
||||
obfs["type"] = "salamander";
|
||||
obfs["salamander"] = QJsonObject{
|
||||
{"password", obfsPassword},
|
||||
};
|
||||
|
||||
config["obfs"] = obfs;
|
||||
}
|
||||
|
||||
QJsonObject tls;
|
||||
auto sniGen = sni;
|
||||
if (sni.isEmpty() && !IsIpAddress(serverAddress)) sniGen = serverAddress;
|
||||
tls["sni"] = sniGen;
|
||||
if (allowInsecure) tls["insecure"] = true;
|
||||
if (!caText.trimmed().isEmpty()) {
|
||||
WriteTempFile("hysteria2_" + GetRandomString(10) + ".crt", caText.toUtf8());
|
||||
QJsonArray certificate;
|
||||
certificate.append(TempFile);
|
||||
tls["certificates"] = certificate;
|
||||
}
|
||||
config["tls"] = tls;
|
||||
|
||||
result.config_export = QJsonObject2QString(config, false);
|
||||
WriteTempFile("hysteria2_" + GetRandomString(10) + ".json", result.config_export.toUtf8());
|
||||
result.arguments = QStringList{"-c", TempFile};
|
||||
|
||||
return result;
|
||||
}
|
||||
ExternalBuildResult e;
|
||||
e.error = "unknown type";
|
||||
return e;
|
||||
}
|
||||
|
||||
ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port, int external_stat) {
|
||||
ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(core)};
|
||||
|
||||
result.arguments = command; // TODO split?
|
||||
|
||||
for (int i = 0; i < result.arguments.length(); i++) {
|
||||
auto arg = result.arguments[i];
|
||||
arg = arg.replace("%mapping_port%", Int2String(mapping_port));
|
||||
arg = arg.replace("%socks_port%", Int2String(socks_port));
|
||||
arg = arg.replace("%server_addr%", serverAddress);
|
||||
arg = arg.replace("%server_port%", Int2String(serverPort));
|
||||
result.arguments[i] = arg;
|
||||
}
|
||||
|
||||
if (!config_simple.trimmed().isEmpty()) {
|
||||
auto config = config_simple;
|
||||
config = config.replace("%mapping_port%", Int2String(mapping_port));
|
||||
config = config.replace("%socks_port%", Int2String(socks_port));
|
||||
config = config.replace("%server_addr%", serverAddress);
|
||||
config = config.replace("%server_port%", Int2String(serverPort));
|
||||
|
||||
// suffix
|
||||
QString suffix;
|
||||
if (!config_suffix.isEmpty()) {
|
||||
suffix = "." + config_suffix;
|
||||
} else if (!QString2QJsonObject(config).isEmpty()) {
|
||||
// trojan-go: unsupported config format: xxx.tmp. use .yaml or .json instead.
|
||||
suffix = ".json";
|
||||
}
|
||||
|
||||
// write config
|
||||
WriteTempFile("custom_" + GetRandomString(10) + suffix, config.toUtf8());
|
||||
for (int i = 0; i < result.arguments.count(); i++) {
|
||||
result.arguments[i] = result.arguments[i].replace("%config%", TempFile);
|
||||
}
|
||||
|
||||
result.config_export = config;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,225 +0,0 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
QString SocksHttpBean::ToShareLink() {
|
||||
QUrl url;
|
||||
if (socks_http_type == type_HTTP) { // http
|
||||
if (stream->security == "tls") {
|
||||
url.setScheme("https");
|
||||
} else {
|
||||
url.setScheme("http");
|
||||
}
|
||||
} else {
|
||||
url.setScheme(QStringLiteral("socks%1").arg(socks_http_type));
|
||||
}
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
if (!username.isEmpty()) url.setUserName(username);
|
||||
if (!password.isEmpty()) url.setPassword(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QString TrojanVLESSBean::ToShareLink() {
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme(proxy_type == proxy_VLESS ? "vless" : "trojan");
|
||||
url.setUserName(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
|
||||
// security
|
||||
auto security = stream->security;
|
||||
if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality";
|
||||
query.addQueryItem("security", security);
|
||||
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
if (!stream->alpn.isEmpty()) query.addQueryItem("alpn", stream->alpn);
|
||||
if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1");
|
||||
if (!stream->utlsFingerprint.isEmpty()) query.addQueryItem("fp", stream->utlsFingerprint);
|
||||
|
||||
if (security == "reality") {
|
||||
query.addQueryItem("pbk", stream->reality_pbk);
|
||||
if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid);
|
||||
if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx);
|
||||
}
|
||||
|
||||
// type
|
||||
query.addQueryItem("type", stream->network);
|
||||
|
||||
if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host);
|
||||
} else if (stream->network == "grpc") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path);
|
||||
} else if (stream->network == "tcp") {
|
||||
if (stream->header_type == "http") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
query.addQueryItem("headerType", "http");
|
||||
query.addQueryItem("host", stream->host);
|
||||
}
|
||||
}
|
||||
|
||||
// protocol
|
||||
if (proxy_type == proxy_VLESS) {
|
||||
if (!flow.isEmpty()) {
|
||||
query.addQueryItem("flow", flow);
|
||||
}
|
||||
query.addQueryItem("encryption", "none");
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
const char* fixShadowsocksUserNameEncodeMagic = "fixShadowsocksUserNameEncodeMagic-holder-for-QUrl";
|
||||
|
||||
QString ShadowSocksBean::ToShareLink() {
|
||||
QUrl url;
|
||||
url.setScheme("ss");
|
||||
if (method.startsWith("2022-")) {
|
||||
url.setUserName(fixShadowsocksUserNameEncodeMagic);
|
||||
} else {
|
||||
auto method_password = method + ":" + password;
|
||||
url.setUserName(method_password.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding));
|
||||
}
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
QUrlQuery q;
|
||||
if (!plugin.isEmpty()) q.addQueryItem("plugin", plugin);
|
||||
if (!q.isEmpty()) url.setQuery(q);
|
||||
//
|
||||
auto link = url.toString(QUrl::FullyEncoded);
|
||||
link = link.replace(fixShadowsocksUserNameEncodeMagic, method + ":" + QUrl::toPercentEncoding(password));
|
||||
return link;
|
||||
}
|
||||
|
||||
QString VMessBean::ToShareLink() {
|
||||
if (NekoGui::dataStore->old_share_link_format) {
|
||||
// v2rayN format
|
||||
QJsonObject N{
|
||||
{"v", "2"},
|
||||
{"ps", name},
|
||||
{"add", serverAddress},
|
||||
{"port", Int2String(serverPort)},
|
||||
{"id", uuid},
|
||||
{"aid", Int2String(aid)},
|
||||
{"net", stream->network},
|
||||
{"host", stream->host},
|
||||
{"path", stream->path},
|
||||
{"type", stream->header_type},
|
||||
{"scy", security},
|
||||
{"tls", stream->security == "tls" ? "tls" : ""},
|
||||
{"sni", stream->sni},
|
||||
};
|
||||
return "vmess://" + QJsonObject2QString(N, true).toUtf8().toBase64();
|
||||
} else {
|
||||
// ducksoft format
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme("vmess");
|
||||
url.setUserName(uuid);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
|
||||
query.addQueryItem("encryption", security);
|
||||
|
||||
// security
|
||||
auto security = stream->security;
|
||||
if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality";
|
||||
query.addQueryItem("security", security);
|
||||
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1");
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
query.addQueryItem("fp", NekoGui::dataStore->utlsFingerprint);
|
||||
} else {
|
||||
query.addQueryItem("fp", stream->utlsFingerprint);
|
||||
}
|
||||
|
||||
if (security == "reality") {
|
||||
query.addQueryItem("pbk", stream->reality_pbk);
|
||||
if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid);
|
||||
if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx);
|
||||
}
|
||||
|
||||
// type
|
||||
query.addQueryItem("type", stream->network);
|
||||
|
||||
if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host);
|
||||
} else if (stream->network == "grpc") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path);
|
||||
} else if (stream->network == "tcp") {
|
||||
if (stream->header_type == "http") {
|
||||
query.addQueryItem("headerType", "http");
|
||||
query.addQueryItem("host", stream->host);
|
||||
}
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
}
|
||||
|
||||
QString NaiveBean::ToShareLink() {
|
||||
QUrl url;
|
||||
url.setScheme("naive+" + protocol);
|
||||
url.setUserName(username);
|
||||
url.setPassword(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QString QUICBean::ToShareLink() {
|
||||
QUrl url;
|
||||
if (proxy_type == proxy_TUIC) {
|
||||
url.setScheme("tuic");
|
||||
url.setUserName(uuid);
|
||||
url.setPassword(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
|
||||
QUrlQuery q;
|
||||
if (!congestionControl.isEmpty()) q.addQueryItem("congestion_control", congestionControl);
|
||||
if (!alpn.isEmpty()) q.addQueryItem("alpn", alpn);
|
||||
if (!sni.isEmpty()) q.addQueryItem("sni", sni);
|
||||
if (!udpRelayMode.isEmpty()) q.addQueryItem("udp_relay_mode", udpRelayMode);
|
||||
if (allowInsecure) q.addQueryItem("allow_insecure", "1");
|
||||
if (disableSni) q.addQueryItem("disable_sni", "1");
|
||||
if (!q.isEmpty()) url.setQuery(q);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
} else if (proxy_type == proxy_Hysteria2) {
|
||||
url.setScheme("hy2");
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (password.contains(":")) {
|
||||
url.setUserName(SubStrBefore(password, ":"));
|
||||
url.setPassword(SubStrAfter(password, ":"));
|
||||
} else {
|
||||
url.setUserName(password);
|
||||
}
|
||||
QUrlQuery q;
|
||||
if (!obfsPassword.isEmpty()) {
|
||||
q.addQueryItem("obfs", "salamander");
|
||||
q.addQueryItem("obfs-password", obfsPassword);
|
||||
}
|
||||
if (!hopPort.trimmed().isEmpty()) q.addQueryItem("mport", hopPort);
|
||||
if (allowInsecure) q.addQueryItem("insecure", "1");
|
||||
if (!sni.isEmpty()) q.addQueryItem("sni", sni);
|
||||
if (!q.isEmpty()) url.setQuery(q);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
}
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class ChainBean : public AbstractBean {
|
||||
public:
|
||||
QList<int> list; // in to out
|
||||
|
||||
ChainBean() : AbstractBean(0) {
|
||||
_add(new configItem("list", &list, itemType::integerList));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return QObject::tr("Chain Proxy"); };
|
||||
|
||||
QString DisplayAddress() override { return ""; };
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class CustomBean : public AbstractBean {
|
||||
public:
|
||||
QString core;
|
||||
QList<QString> command;
|
||||
QString config_suffix;
|
||||
QString config_simple;
|
||||
int mapping_port = 0;
|
||||
int socks_port = 0;
|
||||
|
||||
CustomBean() : AbstractBean(0) {
|
||||
_add(new configItem("core", &core, itemType::string));
|
||||
_add(new configItem("cmd", &command, itemType::stringList));
|
||||
_add(new configItem("cs", &config_simple, itemType::string));
|
||||
_add(new configItem("cs_suffix", &config_suffix, itemType::string));
|
||||
_add(new configItem("mapping_port", &mapping_port, itemType::integer));
|
||||
_add(new configItem("socks_port", &socks_port, itemType::integer));
|
||||
};
|
||||
|
||||
QString DisplayType() override {
|
||||
if (core == "internal") {
|
||||
auto obj = QString2QJsonObject(config_simple);
|
||||
return obj["type"].toString();
|
||||
} else if (core == "internal-full") {
|
||||
return software_core_name + " config";
|
||||
}
|
||||
return core;
|
||||
};
|
||||
|
||||
QString DisplayCoreType() override { return NeedExternal(true) == 0 ? software_core_name : core; };
|
||||
|
||||
QString DisplayAddress() override {
|
||||
if (core == "internal") {
|
||||
auto obj = QString2QJsonObject(config_simple);
|
||||
return ::DisplayAddress(obj["server"].toString(), obj["server_port"].toInt());
|
||||
} else if (core == "internal-full") {
|
||||
return {};
|
||||
}
|
||||
return AbstractBean::DisplayAddress();
|
||||
};
|
||||
|
||||
int NeedExternal(bool isFirstProfile) override;
|
||||
|
||||
ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,297 +0,0 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
|
||||
#define DECODE_V2RAY_N_1 \
|
||||
QString linkN = DecodeB64IfValid(SubStrBefore(SubStrAfter(link, "://"), "#"), QByteArray::Base64Option::Base64UrlEncoding); \
|
||||
if (linkN.isEmpty()) return false; \
|
||||
auto hasRemarks = link.contains("#"); \
|
||||
if (hasRemarks) linkN += "#" + SubStrAfter(link, "#"); \
|
||||
auto url = QUrl("https://" + linkN);
|
||||
|
||||
bool SocksHttpBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
if (link.startsWith("socks4")) socks_http_type = type_Socks4;
|
||||
if (link.startsWith("http")) socks_http_type = type_HTTP;
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
username = url.userName();
|
||||
password = url.password();
|
||||
if (serverPort == -1) serverPort = socks_http_type == type_HTTP ? 443 : 1080;
|
||||
|
||||
// v2rayN fmt
|
||||
if (password.isEmpty() && !username.isEmpty()) {
|
||||
QString n = DecodeB64IfValid(username);
|
||||
if (!n.isEmpty()) {
|
||||
username = SubStrBefore(n, ":");
|
||||
password = SubStrAfter(n, ":");
|
||||
}
|
||||
}
|
||||
|
||||
stream->security = GetQueryValue(query, "security", "");
|
||||
stream->sni = GetQueryValue(query, "sni");
|
||||
if (link.startsWith("https")) stream->security = "tls";
|
||||
|
||||
return !serverAddress.isEmpty();
|
||||
}
|
||||
|
||||
bool TrojanVLESSBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
password = url.userName();
|
||||
if (serverPort == -1) serverPort = 443;
|
||||
|
||||
// security
|
||||
|
||||
auto type = GetQueryValue(query, "type", "tcp");
|
||||
if (type == "h2") {
|
||||
type = "http";
|
||||
}
|
||||
stream->network = type;
|
||||
|
||||
if (proxy_type == proxy_Trojan) {
|
||||
stream->security = GetQueryValue(query, "security", "tls").replace("reality", "tls").replace("none", "");
|
||||
} else {
|
||||
stream->security = GetQueryValue(query, "security", "").replace("reality", "tls").replace("none", "");
|
||||
}
|
||||
auto sni1 = GetQueryValue(query, "sni");
|
||||
auto sni2 = GetQueryValue(query, "peer");
|
||||
if (!sni1.isEmpty()) stream->sni = sni1;
|
||||
if (!sni2.isEmpty()) stream->sni = sni2;
|
||||
stream->alpn = GetQueryValue(query, "alpn");
|
||||
if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true;
|
||||
stream->reality_pbk = GetQueryValue(query, "pbk", "");
|
||||
stream->reality_sid = GetQueryValue(query, "sid", "");
|
||||
stream->reality_spx = GetQueryValue(query, "spx", "");
|
||||
stream->utlsFingerprint = GetQueryValue(query, "fp", "");
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;
|
||||
}
|
||||
|
||||
// type
|
||||
if (stream->network == "ws") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
} else if (stream->network == "http") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "").replace("|", ",");
|
||||
} else if (stream->network == "httpupgrade") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
} else if (stream->network == "grpc") {
|
||||
stream->path = GetQueryValue(query, "serviceName", "");
|
||||
} else if (stream->network == "tcp") {
|
||||
if (GetQueryValue(query, "headerType") == "http") {
|
||||
stream->header_type = "http";
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
}
|
||||
}
|
||||
|
||||
// protocol
|
||||
if (proxy_type == proxy_VLESS) {
|
||||
flow = GetQueryValue(query, "flow", "");
|
||||
}
|
||||
|
||||
return !(password.isEmpty() || serverAddress.isEmpty());
|
||||
}
|
||||
|
||||
bool ShadowSocksBean::TryParseLink(const QString &link) {
|
||||
if (SubStrBefore(link, "#").contains("@")) {
|
||||
// SS
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
|
||||
if (url.password().isEmpty()) {
|
||||
// traditional format
|
||||
auto method_password = DecodeB64IfValid(url.userName(), QByteArray::Base64Option::Base64UrlEncoding);
|
||||
if (method_password.isEmpty()) return false;
|
||||
method = SubStrBefore(method_password, ":");
|
||||
password = SubStrAfter(method_password, ":");
|
||||
} else {
|
||||
// 2022 format
|
||||
method = url.userName();
|
||||
password = url.password();
|
||||
}
|
||||
|
||||
auto query = GetQuery(url);
|
||||
plugin = query.queryItemValue("plugin").replace("simple-obfs;", "obfs-local;");
|
||||
} else {
|
||||
// v2rayN
|
||||
DECODE_V2RAY_N_1
|
||||
|
||||
if (hasRemarks) name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
method = url.userName();
|
||||
password = url.password();
|
||||
}
|
||||
return !(serverAddress.isEmpty() || method.isEmpty() || password.isEmpty());
|
||||
}
|
||||
|
||||
bool VMessBean::TryParseLink(const QString &link) {
|
||||
// V2RayN Format
|
||||
auto linkN = DecodeB64IfValid(SubStrAfter(link, "vmess://"));
|
||||
if (!linkN.isEmpty()) {
|
||||
auto objN = QString2QJsonObject(linkN);
|
||||
if (objN.isEmpty()) return false;
|
||||
// REQUIRED
|
||||
uuid = objN["id"].toString();
|
||||
serverAddress = objN["add"].toString();
|
||||
serverPort = objN["port"].toVariant().toInt();
|
||||
// OPTIONAL
|
||||
name = objN["ps"].toString();
|
||||
aid = objN["aid"].toVariant().toInt();
|
||||
stream->host = objN["host"].toString();
|
||||
stream->path = objN["path"].toString();
|
||||
stream->sni = objN["sni"].toString();
|
||||
stream->header_type = objN["type"].toString();
|
||||
auto net = objN["net"].toString();
|
||||
if (!net.isEmpty()) {
|
||||
if (net == "h2") {
|
||||
net = "http";
|
||||
}
|
||||
stream->network = net;
|
||||
}
|
||||
auto scy = objN["scy"].toString();
|
||||
if (!scy.isEmpty()) security = scy;
|
||||
// TLS (XTLS?)
|
||||
stream->security = objN["tls"].toString();
|
||||
// TODO quic & kcp
|
||||
return true;
|
||||
} else {
|
||||
// https://github.com/XTLS/Xray-core/discussions/716
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
uuid = url.userName();
|
||||
if (serverPort == -1) serverPort = 443;
|
||||
|
||||
aid = 0; // “此分享标准仅针对 VMess AEAD 和 VLESS。”
|
||||
security = GetQueryValue(query, "encryption", "auto");
|
||||
|
||||
// security
|
||||
auto type = GetQueryValue(query, "type", "tcp");
|
||||
if (type == "h2") {
|
||||
type = "http";
|
||||
}
|
||||
stream->network = type;
|
||||
stream->security = GetQueryValue(query, "security", "tls").replace("reality", "tls");
|
||||
auto sni1 = GetQueryValue(query, "sni");
|
||||
auto sni2 = GetQueryValue(query, "peer");
|
||||
if (!sni1.isEmpty()) stream->sni = sni1;
|
||||
if (!sni2.isEmpty()) stream->sni = sni2;
|
||||
if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true;
|
||||
stream->reality_pbk = GetQueryValue(query, "pbk", "");
|
||||
stream->reality_sid = GetQueryValue(query, "sid", "");
|
||||
stream->reality_spx = GetQueryValue(query, "spx", "");
|
||||
stream->utlsFingerprint = GetQueryValue(query, "fp", "");
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint;
|
||||
}
|
||||
|
||||
// type
|
||||
if (stream->network == "ws") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
} else if (stream->network == "http") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "").replace("|", ",");
|
||||
} else if (stream->network == "httpupgrade") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
} else if (stream->network == "grpc") {
|
||||
stream->path = GetQueryValue(query, "serviceName", "");
|
||||
} else if (stream->network == "tcp") {
|
||||
if (GetQueryValue(query, "headerType") == "http") {
|
||||
stream->header_type = "http";
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
}
|
||||
}
|
||||
return !(uuid.isEmpty() || serverAddress.isEmpty());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NaiveBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
|
||||
protocol = url.scheme().replace("naive+", "");
|
||||
if (protocol != "https" && protocol != "quic") return false;
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
username = url.userName();
|
||||
password = url.password();
|
||||
|
||||
return !(username.isEmpty() || password.isEmpty() || serverAddress.isEmpty());
|
||||
}
|
||||
|
||||
bool QUICBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
auto query = QUrlQuery(url.query());
|
||||
if (url.host().isEmpty() || url.port() == -1) return false;
|
||||
|
||||
if (url.scheme() == "tuic") {
|
||||
// by daeuniverse
|
||||
// https://github.com/daeuniverse/dae/discussions/182
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
if (serverPort == -1) serverPort = 443;
|
||||
serverPort = url.port();
|
||||
|
||||
uuid = url.userName();
|
||||
password = url.password();
|
||||
|
||||
congestionControl = query.queryItemValue("congestion_control");
|
||||
alpn = query.queryItemValue("alpn");
|
||||
sni = query.queryItemValue("sni");
|
||||
udpRelayMode = query.queryItemValue("udp_relay_mode");
|
||||
allowInsecure = query.queryItemValue("allow_insecure") == "1";
|
||||
disableSni = query.queryItemValue("disable_sni") == "1";
|
||||
} else if (QStringList{"hy2", "hysteria2"}.contains(url.scheme())) {
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
hopPort = query.queryItemValue("mport");
|
||||
obfsPassword = query.queryItemValue("obfs-password");
|
||||
allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure"));
|
||||
|
||||
if (url.password().isEmpty()) {
|
||||
password = url.userName();
|
||||
} else {
|
||||
password = url.userName() + ":" + url.password();
|
||||
}
|
||||
|
||||
sni = query.queryItemValue("sni");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class NaiveBean : public AbstractBean {
|
||||
public:
|
||||
QString username = "";
|
||||
QString password = "";
|
||||
QString protocol = "https";
|
||||
QString extra_headers = "";
|
||||
QString sni = "";
|
||||
QString certificate = "";
|
||||
int insecure_concurrency = 0;
|
||||
|
||||
bool disable_log = false;
|
||||
|
||||
NaiveBean() : AbstractBean(0) {
|
||||
_add(new configItem("username", &username, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("protocol", &protocol, itemType::string));
|
||||
_add(new configItem("extra_headers", &extra_headers, itemType::string));
|
||||
_add(new configItem("sni", &sni, itemType::string));
|
||||
_add(new configItem("certificate", &certificate, itemType::string));
|
||||
_add(new configItem("insecure_concurrency", &insecure_concurrency, itemType::integer));
|
||||
_add(new configItem("disable_log", &disable_log, itemType::boolean));
|
||||
};
|
||||
|
||||
QString DisplayCoreType() override { return "Naive"; };
|
||||
|
||||
QString DisplayType() override { return "Naive"; };
|
||||
|
||||
int NeedExternal(bool isFirstProfile) override;
|
||||
|
||||
ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Preset {
|
||||
namespace SingBox {
|
||||
inline QStringList VpnImplementation = {"gvisor", "system", "mixed"};
|
||||
inline QStringList DomainStrategy = {"", "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6"};
|
||||
inline QStringList UtlsFingerPrint = {"", "chrome", "firefox", "edge", "safari", "360", "qq", "ios", "android", "random", "randomized"};
|
||||
inline QStringList ShadowsocksMethods = {"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20"};
|
||||
inline QStringList Flows = {"xtls-rprx-vision"};
|
||||
} // namespace SingBox
|
||||
|
||||
namespace Windows {
|
||||
inline QStringList system_proxy_format{"{ip}:{http_port}",
|
||||
"socks={ip}:{socks_port}",
|
||||
"http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}",
|
||||
"http=http://{ip}:{http_port};https=http://{ip}:{http_port}"};
|
||||
} // namespace Windows
|
||||
} // namespace Preset
|
||||
113
fmt/QUICBean.hpp
113
fmt/QUICBean.hpp
@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class QUICBean : public AbstractBean {
|
||||
public:
|
||||
// static constexpr int proxy_Hysteria = 0;
|
||||
static constexpr int proxy_TUIC = 1;
|
||||
static constexpr int proxy_Hysteria2 = 3;
|
||||
int proxy_type = proxy_Hysteria2;
|
||||
|
||||
bool forceExternal = false;
|
||||
|
||||
// Hysteria 2
|
||||
|
||||
QString obfsPassword = "";
|
||||
|
||||
int uploadMbps = 0;
|
||||
int downloadMbps = 0;
|
||||
|
||||
qint64 streamReceiveWindow = 0;
|
||||
qint64 connectionReceiveWindow = 0;
|
||||
bool disableMtuDiscovery = false;
|
||||
|
||||
int hopInterval = 10;
|
||||
QString hopPort = "";
|
||||
|
||||
// TUIC
|
||||
|
||||
QString uuid = "";
|
||||
QString congestionControl = "bbr";
|
||||
QString udpRelayMode = "native";
|
||||
bool zeroRttHandshake = false;
|
||||
QString heartbeat = "10s";
|
||||
bool uos = false;
|
||||
|
||||
// HY2&TUIC
|
||||
|
||||
QString password = "";
|
||||
|
||||
// TLS
|
||||
|
||||
bool allowInsecure = false;
|
||||
QString sni = "";
|
||||
QString alpn = "";
|
||||
QString caText = "";
|
||||
bool disableSni = false;
|
||||
|
||||
explicit QUICBean(int _proxy_type) : AbstractBean(0) {
|
||||
proxy_type = _proxy_type;
|
||||
if (proxy_type == proxy_Hysteria2) {
|
||||
_add(new configItem("obfsPassword", &obfsPassword, itemType::string));
|
||||
_add(new configItem("uploadMbps", &uploadMbps, itemType::integer));
|
||||
_add(new configItem("downloadMbps", &downloadMbps, itemType::integer));
|
||||
_add(new configItem("streamReceiveWindow", &streamReceiveWindow, itemType::integer64));
|
||||
_add(new configItem("connectionReceiveWindow", &connectionReceiveWindow, itemType::integer64));
|
||||
_add(new configItem("disableMtuDiscovery", &disableMtuDiscovery, itemType::boolean));
|
||||
_add(new configItem("hopInterval", &hopInterval, itemType::integer));
|
||||
_add(new configItem("hopPort", &hopPort, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
} else if (proxy_type == proxy_TUIC) {
|
||||
_add(new configItem("uuid", &uuid, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("congestionControl", &congestionControl, itemType::string));
|
||||
_add(new configItem("udpRelayMode", &udpRelayMode, itemType::string));
|
||||
_add(new configItem("zeroRttHandshake", &zeroRttHandshake, itemType::boolean));
|
||||
_add(new configItem("heartbeat", &heartbeat, itemType::string));
|
||||
_add(new configItem("uos", &uos, itemType::boolean));
|
||||
}
|
||||
_add(new configItem("forceExternal", &forceExternal, itemType::boolean));
|
||||
// TLS
|
||||
_add(new configItem("allowInsecure", &allowInsecure, itemType::boolean));
|
||||
_add(new configItem("sni", &sni, itemType::string));
|
||||
_add(new configItem("alpn", &alpn, itemType::string));
|
||||
_add(new configItem("caText", &caText, itemType::string));
|
||||
_add(new configItem("disableSni", &disableSni, itemType::boolean));
|
||||
};
|
||||
|
||||
QString DisplayAddress() override {
|
||||
if (!hopPort.trimmed().isEmpty()) return WrapIPV6Host(serverAddress) + ":" + hopPort;
|
||||
return ::DisplayAddress(serverAddress, serverPort);
|
||||
}
|
||||
|
||||
QString DisplayCoreType() override {
|
||||
if (NeedExternal(true) == 0) {
|
||||
return software_core_name;
|
||||
} else if (proxy_type == proxy_TUIC) {
|
||||
return "tuic";
|
||||
} else {
|
||||
return "hysteria2";
|
||||
}
|
||||
}
|
||||
|
||||
QString DisplayType() override {
|
||||
if (proxy_type == proxy_TUIC) {
|
||||
return "TUIC";
|
||||
} else {
|
||||
return "Hysteria2";
|
||||
}
|
||||
};
|
||||
|
||||
int NeedExternal(bool isFirstProfile) override;
|
||||
|
||||
ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override;
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class ShadowSocksBean : public AbstractBean {
|
||||
public:
|
||||
QString method = "aes-128-gcm";
|
||||
QString password = "";
|
||||
QString plugin = "";
|
||||
int uot = 0;
|
||||
|
||||
std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();
|
||||
|
||||
ShadowSocksBean() : AbstractBean(0) {
|
||||
_add(new configItem("method", &method, itemType::string));
|
||||
_add(new configItem("pass", &password, itemType::string));
|
||||
_add(new configItem("plugin", &plugin, itemType::string));
|
||||
_add(new configItem("uot", &uot, itemType::integer));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "Shadowsocks"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class SocksHttpBean : public AbstractBean {
|
||||
public:
|
||||
static constexpr int type_HTTP = -80;
|
||||
static constexpr int type_Socks4 = 4;
|
||||
static constexpr int type_Socks5 = 5;
|
||||
|
||||
int socks_http_type = type_Socks5;
|
||||
QString username = "";
|
||||
QString password = "";
|
||||
|
||||
std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();
|
||||
|
||||
explicit SocksHttpBean(int _socks_http_type) : AbstractBean(0) {
|
||||
this->socks_http_type = _socks_http_type;
|
||||
_add(new configItem("v", &socks_http_type, itemType::integer));
|
||||
_add(new configItem("username", &username, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return socks_http_type == type_HTTP ? "HTTP" : "Socks"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class TrojanVLESSBean : public AbstractBean {
|
||||
public:
|
||||
static constexpr int proxy_Trojan = 0;
|
||||
static constexpr int proxy_VLESS = 1;
|
||||
int proxy_type = proxy_Trojan;
|
||||
|
||||
QString password = "";
|
||||
QString flow = "";
|
||||
|
||||
std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();
|
||||
|
||||
explicit TrojanVLESSBean(int _proxy_type) : AbstractBean(0) {
|
||||
proxy_type = _proxy_type;
|
||||
_add(new configItem("pass", &password, itemType::string));
|
||||
_add(new configItem("flow", &flow, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return proxy_type == proxy_VLESS ? "VLESS" : "Trojan"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractBean.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class V2rayStreamSettings : public JsonStore {
|
||||
public:
|
||||
QString network = "tcp";
|
||||
QString security = "";
|
||||
QString packet_encoding = "";
|
||||
// ws/http/grpc/tcp-http/httpupgrade
|
||||
QString path = "";
|
||||
QString host = "";
|
||||
// kcp/quic/tcp-http
|
||||
QString header_type = "";
|
||||
// tls
|
||||
QString sni = "";
|
||||
QString alpn = "";
|
||||
QString certificate = "";
|
||||
QString utlsFingerprint = "";
|
||||
bool allow_insecure = false;
|
||||
// ws early data
|
||||
QString ws_early_data_name = "";
|
||||
int ws_early_data_length = 0;
|
||||
// reality
|
||||
QString reality_pbk = "";
|
||||
QString reality_sid = "";
|
||||
QString reality_spx = "";
|
||||
// multiplex
|
||||
int multiplex_status = 0;
|
||||
|
||||
V2rayStreamSettings() : JsonStore() {
|
||||
_add(new configItem("net", &network, itemType::string));
|
||||
_add(new configItem("sec", &security, itemType::string));
|
||||
_add(new configItem("pac_enc", &packet_encoding, itemType::string));
|
||||
_add(new configItem("path", &path, itemType::string));
|
||||
_add(new configItem("host", &host, itemType::string));
|
||||
_add(new configItem("sni", &sni, itemType::string));
|
||||
_add(new configItem("alpn", &alpn, itemType::string));
|
||||
_add(new configItem("cert", &certificate, itemType::string));
|
||||
_add(new configItem("insecure", &allow_insecure, itemType::boolean));
|
||||
_add(new configItem("h_type", &header_type, itemType::string));
|
||||
_add(new configItem("ed_name", &ws_early_data_name, itemType::string));
|
||||
_add(new configItem("ed_len", &ws_early_data_length, itemType::integer));
|
||||
_add(new configItem("utls", &utlsFingerprint, itemType::string));
|
||||
_add(new configItem("pbk", &reality_pbk, itemType::string));
|
||||
_add(new configItem("sid", &reality_sid, itemType::string));
|
||||
_add(new configItem("spx", &reality_spx, itemType::string));
|
||||
_add(new configItem("mux_s", &multiplex_status, itemType::integer));
|
||||
}
|
||||
|
||||
void BuildStreamSettingsSingBox(QJsonObject *outbound);
|
||||
};
|
||||
|
||||
inline V2rayStreamSettings *GetStreamSettings(AbstractBean *bean) {
|
||||
if (bean == nullptr) return nullptr;
|
||||
auto stream_item = bean->_get("stream");
|
||||
if (stream_item != nullptr) {
|
||||
auto stream_store = (JsonStore *) stream_item->ptr;
|
||||
auto stream = (NekoGui_fmt::V2rayStreamSettings *) stream_store;
|
||||
return stream;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoGui_fmt {
|
||||
class VMessBean : public AbstractBean {
|
||||
public:
|
||||
QString uuid = "";
|
||||
int aid = 0;
|
||||
QString security = "auto";
|
||||
|
||||
std::shared_ptr<V2rayStreamSettings> stream = std::make_shared<V2rayStreamSettings>();
|
||||
|
||||
VMessBean() : AbstractBean(0) {
|
||||
_add(new configItem("id", &uuid, itemType::string));
|
||||
_add(new configItem("aid", &aid, itemType::integer));
|
||||
_add(new configItem("sec", &security, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "VMess"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObjSingBox() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
} // namespace NekoGui_fmt
|
||||
@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "SocksHttpBean.hpp"
|
||||
#include "ShadowSocksBean.hpp"
|
||||
#include "ChainBean.hpp"
|
||||
#include "VMessBean.hpp"
|
||||
#include "TrojanVLESSBean.hpp"
|
||||
#include "NaiveBean.hpp"
|
||||
#include "QUICBean.hpp"
|
||||
#include "CustomBean.hpp"
|
||||
8
go/.gitignore
vendored
8
go/.gitignore
vendored
@ -1,8 +0,0 @@
|
||||
*.log
|
||||
*.pem
|
||||
*.json
|
||||
*.exe
|
||||
*.dat
|
||||
/cmd/nekoray_core/nekoray_core
|
||||
/cmd/nekobox_core/nekobox_core
|
||||
*.db
|
||||
@ -1,50 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
"github.com/matsuridayo/libneko/neko_log"
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/boxapi"
|
||||
boxmain "github.com/sagernet/sing-box/cmd/sing-box"
|
||||
)
|
||||
|
||||
var instance *box.Box
|
||||
var instance_cancel context.CancelFunc
|
||||
|
||||
func setupCore() {
|
||||
boxmain.SetDisableColor(true)
|
||||
//
|
||||
neko_log.SetupLog(50*1024, "./neko.log")
|
||||
//
|
||||
neko_common.GetCurrentInstance = func() interface{} {
|
||||
return instance
|
||||
}
|
||||
neko_common.DialContext = func(ctx context.Context, specifiedInstance interface{}, network, addr string) (net.Conn, error) {
|
||||
if i, ok := specifiedInstance.(*box.Box); ok {
|
||||
return boxapi.DialContext(ctx, i, network, addr)
|
||||
}
|
||||
if instance != nil {
|
||||
return boxapi.DialContext(ctx, instance, network, addr)
|
||||
}
|
||||
return neko_common.DialContextSystem(ctx, network, addr)
|
||||
}
|
||||
neko_common.DialUDP = func(ctx context.Context, specifiedInstance interface{}) (net.PacketConn, error) {
|
||||
if i, ok := specifiedInstance.(*box.Box); ok {
|
||||
return boxapi.DialUDP(ctx, i)
|
||||
}
|
||||
if instance != nil {
|
||||
return boxapi.DialUDP(ctx, instance)
|
||||
}
|
||||
return neko_common.DialUDPSystem(ctx)
|
||||
}
|
||||
neko_common.CreateProxyHttpClient = func(specifiedInstance interface{}) *http.Client {
|
||||
if i, ok := specifiedInstance.(*box.Box); ok {
|
||||
return boxapi.CreateProxyHttpClient(i)
|
||||
}
|
||||
return boxapi.CreateProxyHttpClient(instance)
|
||||
}
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
module nekobox_core
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/matsuridayo/libneko v1.0.0 // replaced
|
||||
github.com/sagernet/sing-box v1.0.0 // replaced
|
||||
// github.com/sagernet/sing-dns v1.0.0 // indirect; replaced
|
||||
grpc_server v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/caddyserver/certmagic v0.20.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cretz/bine v0.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.12 // indirect
|
||||
github.com/go-chi/cors v1.2.1 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.2.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/libdns/alidns v1.0.3 // indirect
|
||||
github.com/libdns/cloudflare v0.1.1 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/ooni/go-libtor v1.1.8 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect
|
||||
github.com/sagernet/quic-go v0.47.0-beta.2 // indirect
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
||||
github.com/sagernet/sing v0.4.3 // indirect
|
||||
github.com/sagernet/sing-dns v0.2.3 // indirect
|
||||
github.com/sagernet/sing-mux v0.2.0 // indirect
|
||||
github.com/sagernet/sing-quic v0.2.2 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
|
||||
github.com/sagernet/sing-tun v0.3.3 // indirect
|
||||
github.com/sagernet/sing-vmess v0.1.12 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/sagernet/utls v1.5.4 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
||||
github.com/spf13/cobra v1.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace grpc_server => ../../grpc_server
|
||||
|
||||
replace github.com/matsuridayo/libneko => ../../../../libneko
|
||||
|
||||
replace github.com/sagernet/sing-box => ../../../../sing-box
|
||||
|
||||
replace github.com/sagernet/sing-quic => ../../../../sing-quic
|
||||
|
||||
// replace github.com/sagernet/sing => ../../../../sing
|
||||
|
||||
// replace github.com/sagernet/sing-dns => ../../../../sing-dns
|
||||
@ -1,298 +0,0 @@
|
||||
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
||||
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
|
||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
||||
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U=
|
||||
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
|
||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||
github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
|
||||
github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
|
||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU=
|
||||
github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
|
||||
github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k=
|
||||
github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
|
||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||
github.com/sagernet/sing-tun v0.3.3 h1:LZnQNmfGcNG2KPTPkLgc+Lo7k606QJVkPp2DnjriwUk=
|
||||
github.com/sagernet/sing-tun v0.3.3/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
|
||||
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
|
||||
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
|
||||
github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs=
|
||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
@ -1,152 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"grpc_server"
|
||||
"grpc_server/gen"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
"github.com/matsuridayo/libneko/neko_log"
|
||||
"github.com/matsuridayo/libneko/speedtest"
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/boxapi"
|
||||
boxmain "github.com/sagernet/sing-box/cmd/sing-box"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
grpc_server.BaseServer
|
||||
}
|
||||
|
||||
func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.ErrorResp, _ error) {
|
||||
var err error
|
||||
|
||||
defer func() {
|
||||
out = &gen.ErrorResp{}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
instance = nil
|
||||
}
|
||||
}()
|
||||
|
||||
if neko_common.Debug {
|
||||
log.Println("Start:", in.CoreConfig)
|
||||
}
|
||||
|
||||
if instance != nil {
|
||||
err = errors.New("instance already started")
|
||||
return
|
||||
}
|
||||
|
||||
instance, instance_cancel, err = boxmain.Create([]byte(in.CoreConfig))
|
||||
|
||||
if instance != nil {
|
||||
// Logger
|
||||
instance.SetLogWritter(neko_log.LogWriter)
|
||||
// V2ray Service
|
||||
if in.StatsOutbounds != nil {
|
||||
instance.Router().SetV2RayServer(boxapi.NewSbV2rayServer(option.V2RayStatsServiceOptions{
|
||||
Enabled: true,
|
||||
Outbounds: in.StatsOutbounds,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp, _ error) {
|
||||
var err error
|
||||
|
||||
defer func() {
|
||||
out = &gen.ErrorResp{}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
}()
|
||||
|
||||
if instance == nil {
|
||||
return
|
||||
}
|
||||
|
||||
instance_cancel()
|
||||
instance.Close()
|
||||
|
||||
instance = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) {
|
||||
var err error
|
||||
out = &gen.TestResp{Ms: 0}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
}()
|
||||
|
||||
if in.Mode == gen.TestMode_UrlTest {
|
||||
var i *box.Box
|
||||
var cancel context.CancelFunc
|
||||
if in.Config != nil {
|
||||
// Test instance
|
||||
i, cancel, err = boxmain.Create([]byte(in.Config.CoreConfig))
|
||||
if i != nil {
|
||||
defer i.Close()
|
||||
defer cancel()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Test running instance
|
||||
i = instance
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Latency
|
||||
out.Ms, err = speedtest.UrlTest(boxapi.CreateProxyHttpClient(i), in.Url, in.Timeout, speedtest.UrlTestStandard_RTT)
|
||||
} else if in.Mode == gen.TestMode_TcpPing {
|
||||
out.Ms, err = speedtest.TcpPing(in.Address, in.Timeout)
|
||||
} else if in.Mode == gen.TestMode_FullTest {
|
||||
i, cancel, err := boxmain.Create([]byte(in.Config.CoreConfig))
|
||||
if i != nil {
|
||||
defer i.Close()
|
||||
defer cancel()
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return grpc_server.DoFullTest(ctx, in, i)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) {
|
||||
out = &gen.QueryStatsResp{}
|
||||
|
||||
if instance != nil {
|
||||
if ss, ok := instance.Router().V2RayServer().(*boxapi.SbV2rayServer); ok {
|
||||
out.Traffic = ss.QueryStats(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", in.Tag, in.Direct))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.ListConnectionsResp, error) {
|
||||
out := &gen.ListConnectionsResp{
|
||||
// TODO upstream api
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
_ "unsafe"
|
||||
|
||||
"grpc_server"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
boxmain "github.com/sagernet/sing-box/cmd/sing-box"
|
||||
"github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("sing-box:", constant.Version, "NekoBox:", neko_common.Version_neko)
|
||||
fmt.Println()
|
||||
|
||||
// nekobox_core
|
||||
if len(os.Args) > 1 && os.Args[1] == "nekobox" {
|
||||
neko_common.RunMode = neko_common.RunMode_NekoBox_Core
|
||||
grpc_server.RunCore(setupCore, &server{})
|
||||
return
|
||||
}
|
||||
|
||||
// sing-box
|
||||
boxmain.Main()
|
||||
}
|
||||
2
go/cmd/updater/.gitignore
vendored
2
go/cmd/updater/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/updater
|
||||
/launcher
|
||||
@ -1,11 +0,0 @@
|
||||
module updater
|
||||
|
||||
go 1.18
|
||||
|
||||
require github.com/codeclysm/extract v2.2.0+incompatible
|
||||
|
||||
require (
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect
|
||||
github.com/stretchr/testify v1.7.1 // indirect
|
||||
)
|
||||
@ -1,19 +0,0 @@
|
||||
github.com/codeclysm/extract v2.2.0+incompatible h1:q3wyckoA30bhUSiwdQezMqVhwd8+WGE64/GL//LtUhI=
|
||||
github.com/codeclysm/extract v2.2.0+incompatible/go.mod h1:2nhFMPHiU9At61hz+12bfrlpXSUrOnK+wR+KlGO4Uks=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY=
|
||||
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -1,12 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func Launcher() {
|
||||
log.Fatalln("launcher is not for your platform", runtime.GOOS)
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var local_qt_theme bool
|
||||
|
||||
func Launcher() {
|
||||
log.Println("Running as launcher")
|
||||
wd, _ := filepath.Abs(".")
|
||||
|
||||
_debug := flag.Bool("debug", false, "Debug mode")
|
||||
flag.Parse()
|
||||
|
||||
cmd := exec.Command("./nekobox", flag.Args()...)
|
||||
|
||||
ld_env := "LD_LIBRARY_PATH=" + filepath.Join(wd, "./usr/lib")
|
||||
qt_plugin_env := "QT_PLUGIN_PATH=" + filepath.Join(wd, "./usr/plugins")
|
||||
|
||||
// Qt 5.12 abi is usually compatible with system Qt 5.15
|
||||
// But use package Qt 5.12 by default.
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "NKR_FROM_LAUNCHER=1")
|
||||
cmd.Env = append(cmd.Env, ld_env, qt_plugin_env)
|
||||
log.Println(ld_env, qt_plugin_env, cmd)
|
||||
|
||||
if *_debug {
|
||||
cmd.Env = append(cmd.Env, "QT_DEBUG_PLUGINS=1")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
} else {
|
||||
cmd.Start()
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// update & launcher
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
wd := filepath.Dir(exe)
|
||||
os.Chdir(wd)
|
||||
exe = filepath.Base(os.Args[0])
|
||||
log.Println("exe:", exe, "exe dir:", wd)
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(exe), "updater") {
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.HasPrefix(strings.ToLower(exe), "updater.old") {
|
||||
// 2. "updater.old" update files
|
||||
time.Sleep(time.Second)
|
||||
Updater()
|
||||
// 3. start
|
||||
exec.Command("./nekobox.exe").Start()
|
||||
} else {
|
||||
// 1. main prog quit and run "updater.exe"
|
||||
Copy("./updater.exe", "./updater.old")
|
||||
exec.Command("./updater.old", os.Args[1:]...).Start()
|
||||
}
|
||||
} else {
|
||||
// 1. update files
|
||||
Updater()
|
||||
// 2. start
|
||||
if os.Getenv("NKR_FROM_LAUNCHER") == "1" {
|
||||
Launcher()
|
||||
} else {
|
||||
exec.Command("./nekobox").Start()
|
||||
}
|
||||
}
|
||||
return
|
||||
} else if strings.HasPrefix(strings.ToLower(exe), "launcher") {
|
||||
Launcher()
|
||||
return
|
||||
}
|
||||
log.Fatalf("wrong name")
|
||||
}
|
||||
|
||||
func Copy(src string, dst string) {
|
||||
// Read all content of src to data
|
||||
data, _ := ioutil.ReadFile(src)
|
||||
// Write data to dst
|
||||
ioutil.WriteFile(dst, data, 0644)
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
//go:build !windows
|
||||
|
||||
package main
|
||||
|
||||
func MessageBoxPlain(title, caption string) int {
|
||||
return 0
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// MessageBoxPlain of Win32 API.
|
||||
func MessageBoxPlain(title, caption string) int {
|
||||
const (
|
||||
NULL = 0
|
||||
MB_OK = 0
|
||||
)
|
||||
return MessageBox(NULL, caption, title, MB_OK)
|
||||
}
|
||||
|
||||
// MessageBox of Win32 API.
|
||||
func MessageBox(hwnd uintptr, caption, title string, flags uint) int {
|
||||
ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call(
|
||||
uintptr(hwnd),
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))),
|
||||
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))),
|
||||
uintptr(flags))
|
||||
|
||||
return int(ret)
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/codeclysm/extract"
|
||||
)
|
||||
|
||||
func Updater() {
|
||||
pre_cleanup := func() {
|
||||
if runtime.GOOS == "linux" {
|
||||
os.RemoveAll("./usr")
|
||||
}
|
||||
os.RemoveAll("./nekoray_update")
|
||||
}
|
||||
|
||||
// find update package
|
||||
var updatePackagePath string
|
||||
if len(os.Args) == 2 && Exist(os.Args[1]) {
|
||||
updatePackagePath = os.Args[1]
|
||||
} else if Exist("./nekoray.zip") {
|
||||
updatePackagePath = "./nekoray.zip"
|
||||
} else if Exist("./nekoray.tar.gz") {
|
||||
updatePackagePath = "./nekoray.tar.gz"
|
||||
} else {
|
||||
log.Fatalln("no update")
|
||||
}
|
||||
log.Println("updating from", updatePackagePath)
|
||||
|
||||
// extract update package
|
||||
if strings.HasSuffix(updatePackagePath, ".zip") {
|
||||
pre_cleanup()
|
||||
f, err := os.Open(updatePackagePath)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
err = extract.Zip(context.Background(), f, "./nekoray_update", nil)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
f.Close()
|
||||
} else if strings.HasSuffix(updatePackagePath, ".tar.gz") {
|
||||
pre_cleanup()
|
||||
f, err := os.Open(updatePackagePath)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
err = extract.Gz(context.Background(), f, "./nekoray_update", nil)
|
||||
if err != nil {
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// remove old file
|
||||
removeAll("./*.dll")
|
||||
removeAll("./*.dmp")
|
||||
|
||||
// update move
|
||||
err := Mv("./nekoray_update/nekoray", "./")
|
||||
if err != nil {
|
||||
MessageBoxPlain("NekoGui Updater", "Update failed. Please close the running instance and run the updater again.\n\n"+err.Error())
|
||||
log.Fatalln(err.Error())
|
||||
}
|
||||
|
||||
os.RemoveAll("./nekoray_update")
|
||||
os.RemoveAll("./nekoray.zip")
|
||||
os.RemoveAll("./nekoray.tar.gz")
|
||||
|
||||
// nekoray -> nekobox
|
||||
os.Remove("./nekoray.exe")
|
||||
os.Remove("./nekoray.png")
|
||||
os.Remove("./nekoray_core.exe")
|
||||
}
|
||||
|
||||
func Exist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func FindExist(paths []string) string {
|
||||
for _, path := range paths {
|
||||
if Exist(path) {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Mv(src, dst string) error {
|
||||
s, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.IsDir() {
|
||||
es, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range es {
|
||||
err = Mv(filepath.Join(src, e.Name()), filepath.Join(dst, e.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = os.MkdirAll(filepath.Dir(dst), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(src, dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAll(glob string) {
|
||||
files, _ := filepath.Glob(glob)
|
||||
for _, f := range files {
|
||||
os.Remove(f)
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Authenticator exposes a function for authenticating requests.
|
||||
type Authenticator struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// Authenticate checks that a token exists and is valid. It stores the user
|
||||
// metadata in the returned context and removes the token from the context.
|
||||
func (a Authenticator) Authenticate(ctx context.Context) (newCtx context.Context, err error) {
|
||||
auth, err := extractHeader(ctx, "nekoray_auth")
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if auth != a.Token {
|
||||
return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
|
||||
return purgeHeader(ctx, "nekoray_auth"), nil
|
||||
}
|
||||
|
||||
func extractHeader(ctx context.Context, header string) (string, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", status.Error(codes.Unauthenticated, "no headers in request")
|
||||
}
|
||||
|
||||
authHeaders, ok := md[header]
|
||||
if !ok {
|
||||
return "", status.Error(codes.Unauthenticated, "no header in request")
|
||||
}
|
||||
|
||||
if len(authHeaders) != 1 {
|
||||
return "", status.Error(codes.Unauthenticated, "more than 1 header in request")
|
||||
}
|
||||
|
||||
return authHeaders[0], nil
|
||||
}
|
||||
|
||||
func purgeHeader(ctx context.Context, header string) context.Context {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
mdCopy := md.Copy()
|
||||
mdCopy[header] = nil
|
||||
return metadata.NewIncomingContext(ctx, mdCopy)
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
package grpc_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"grpc_server/gen"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
"github.com/matsuridayo/libneko/speedtest"
|
||||
)
|
||||
|
||||
const (
|
||||
KiB = 1024
|
||||
MiB = 1024 * KiB
|
||||
)
|
||||
|
||||
func getBetweenStr(str, start, end string) string {
|
||||
n := strings.Index(str, start)
|
||||
if n == -1 {
|
||||
n = 0
|
||||
}
|
||||
str = string([]byte(str)[n:])
|
||||
m := strings.Index(str, end)
|
||||
if m == -1 {
|
||||
m = len(str)
|
||||
}
|
||||
str = string([]byte(str)[:m])
|
||||
return str[len(start):]
|
||||
}
|
||||
|
||||
func DoFullTest(ctx context.Context, in *gen.TestReq, instance interface{}) (out *gen.TestResp, _ error) {
|
||||
out = &gen.TestResp{}
|
||||
httpClient := neko_common.CreateProxyHttpClient(instance)
|
||||
|
||||
// Latency
|
||||
var latency string
|
||||
if in.FullLatency {
|
||||
t, _ := speedtest.UrlTest(httpClient, in.Url, in.Timeout, speedtest.UrlTestStandard_RTT)
|
||||
out.Ms = t
|
||||
if t > 0 {
|
||||
latency = fmt.Sprint(t, "ms")
|
||||
} else {
|
||||
latency = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// UDP Latency
|
||||
var udpLatency string
|
||||
if in.FullUdpLatency {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||
result := make(chan string)
|
||||
|
||||
go func() {
|
||||
var startTime = time.Now()
|
||||
pc, err := neko_common.DialContext(ctx, instance, "udp", "8.8.8.8:53")
|
||||
if err == nil {
|
||||
defer pc.Close()
|
||||
dnsPacket, _ := hex.DecodeString("0000010000010000000000000377777706676f6f676c6503636f6d0000010001")
|
||||
_, err = pc.Write(dnsPacket)
|
||||
if err == nil {
|
||||
var buf [1400]byte
|
||||
_, err = pc.Read(buf[:])
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
var endTime = time.Now()
|
||||
result <- fmt.Sprint(endTime.Sub(startTime).Abs().Milliseconds(), "ms")
|
||||
} else {
|
||||
log.Println("UDP Latency test error:", err)
|
||||
result <- "Error"
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
udpLatency = "Timeout"
|
||||
case r := <-result:
|
||||
udpLatency = r
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
|
||||
// 入口 IP
|
||||
var in_ip string
|
||||
if in.FullInOut {
|
||||
_in_ip, err := net.ResolveIPAddr("ip", in.InAddress)
|
||||
if err == nil {
|
||||
in_ip = _in_ip.String()
|
||||
} else {
|
||||
in_ip = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// 出口 IP
|
||||
var out_ip string
|
||||
if in.FullInOut {
|
||||
resp, err := httpClient.Get("https://www.cloudflare.com/cdn-cgi/trace")
|
||||
if err == nil {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
out_ip = getBetweenStr(string(b), "ip=", "\n")
|
||||
resp.Body.Close()
|
||||
} else {
|
||||
out_ip = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// 下载
|
||||
var speed string
|
||||
if in.FullSpeed {
|
||||
if in.FullSpeedTimeout <= 0 {
|
||||
in.FullSpeedTimeout = 30
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(in.FullSpeedTimeout))
|
||||
result := make(chan string)
|
||||
var bodyClose io.Closer
|
||||
|
||||
go func() {
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", in.FullSpeedUrl, nil)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err == nil && resp != nil && resp.Body != nil {
|
||||
bodyClose = resp.Body
|
||||
defer resp.Body.Close()
|
||||
|
||||
timeStart := time.Now()
|
||||
n, _ := io.Copy(io.Discard, resp.Body)
|
||||
timeEnd := time.Now()
|
||||
|
||||
duration := math.Max(timeEnd.Sub(timeStart).Seconds(), 0.000001)
|
||||
resultSpeed := (float64(n) / duration) / MiB
|
||||
result <- fmt.Sprintf("%.2fMiB/s", resultSpeed)
|
||||
} else {
|
||||
result <- "Error"
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
speed = "Timeout"
|
||||
case s := <-result:
|
||||
speed = s
|
||||
}
|
||||
|
||||
cancel()
|
||||
if bodyClose != nil {
|
||||
bodyClose.Close()
|
||||
}
|
||||
}
|
||||
|
||||
fr := make([]string, 0)
|
||||
if latency != "" {
|
||||
fr = append(fr, fmt.Sprintf("Latency: %s", latency))
|
||||
}
|
||||
if udpLatency != "" {
|
||||
fr = append(fr, fmt.Sprintf("UDPLatency: %s", udpLatency))
|
||||
}
|
||||
if speed != "" {
|
||||
fr = append(fr, fmt.Sprintf("Speed: %s", speed))
|
||||
}
|
||||
if in_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("In: %s", in_ip))
|
||||
}
|
||||
if out_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("Out: %s", out_ip))
|
||||
}
|
||||
|
||||
out.FullReport = strings.Join(fr, " / ")
|
||||
|
||||
return
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,94 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package libcore;
|
||||
option go_package = "grpc_server/gen";
|
||||
|
||||
service LibcoreService {
|
||||
rpc Exit(EmptyReq) returns (EmptyResp) {}
|
||||
rpc Update(UpdateReq) returns (UpdateResp) {}
|
||||
//
|
||||
rpc Start(LoadConfigReq) returns (ErrorResp) {}
|
||||
rpc Stop(EmptyReq) returns (ErrorResp) {}
|
||||
rpc Test(TestReq) returns (TestResp) {}
|
||||
rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {}
|
||||
rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {}
|
||||
}
|
||||
|
||||
message EmptyReq {}
|
||||
|
||||
message EmptyResp {}
|
||||
|
||||
message ErrorResp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message LoadConfigReq {
|
||||
string core_config = 1;
|
||||
bool enable_nekoray_connections = 2;
|
||||
repeated string stats_outbounds = 3;
|
||||
}
|
||||
|
||||
enum TestMode {
|
||||
TcpPing = 0;
|
||||
UrlTest = 1;
|
||||
FullTest = 2;
|
||||
}
|
||||
|
||||
message TestReq {
|
||||
TestMode mode = 1;
|
||||
int32 timeout = 6;
|
||||
// TcpPing
|
||||
string address = 2;
|
||||
// UrlTest
|
||||
LoadConfigReq config = 3;
|
||||
string inbound = 4;
|
||||
string url = 5;
|
||||
// FullTest
|
||||
string in_address = 7;
|
||||
bool full_latency = 8;
|
||||
bool full_speed = 9;
|
||||
string full_speed_url = 13;
|
||||
int32 full_speed_timeout = 14;
|
||||
bool full_in_out = 10;
|
||||
bool full_udp_latency = 12;
|
||||
//
|
||||
bool full_nat = 11 [deprecated = true];
|
||||
}
|
||||
|
||||
message TestResp {
|
||||
string error = 1;
|
||||
int32 ms = 2;
|
||||
string full_report = 3;
|
||||
}
|
||||
|
||||
message QueryStatsReq{
|
||||
string tag = 1;
|
||||
string direct = 2;
|
||||
}
|
||||
|
||||
message QueryStatsResp{
|
||||
int64 traffic = 1;
|
||||
}
|
||||
|
||||
enum UpdateAction {
|
||||
Check = 0;
|
||||
Download = 1;
|
||||
}
|
||||
|
||||
message UpdateReq {
|
||||
UpdateAction action = 1;
|
||||
bool check_pre_release = 2;
|
||||
}
|
||||
|
||||
message UpdateResp {
|
||||
string error = 1;
|
||||
string assets_name = 2;
|
||||
string download_url = 3;
|
||||
string release_url = 4;
|
||||
string release_note = 5;
|
||||
bool is_pre_release = 6;
|
||||
}
|
||||
|
||||
message ListConnectionsResp {
|
||||
string nekoray_connections_json = 1;
|
||||
}
|
||||
@ -1,321 +0,0 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.23.3
|
||||
// source: libcore.proto
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// LibcoreServiceClient is the client API for LibcoreService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type LibcoreServiceClient interface {
|
||||
Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error)
|
||||
Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error)
|
||||
Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error)
|
||||
QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error)
|
||||
ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error)
|
||||
}
|
||||
|
||||
type libcoreServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewLibcoreServiceClient(cc grpc.ClientConnInterface) LibcoreServiceClient {
|
||||
return &libcoreServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) {
|
||||
out := new(EmptyResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Exit", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error) {
|
||||
out := new(UpdateResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Update", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) {
|
||||
out := new(ErrorResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Start", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) {
|
||||
out := new(ErrorResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Stop", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) {
|
||||
out := new(TestResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Test", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) {
|
||||
out := new(QueryStatsResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/QueryStats", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) {
|
||||
out := new(ListConnectionsResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/ListConnections", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// LibcoreServiceServer is the server API for LibcoreService service.
|
||||
// All implementations must embed UnimplementedLibcoreServiceServer
|
||||
// for forward compatibility
|
||||
type LibcoreServiceServer interface {
|
||||
Exit(context.Context, *EmptyReq) (*EmptyResp, error)
|
||||
Update(context.Context, *UpdateReq) (*UpdateResp, error)
|
||||
Start(context.Context, *LoadConfigReq) (*ErrorResp, error)
|
||||
Stop(context.Context, *EmptyReq) (*ErrorResp, error)
|
||||
Test(context.Context, *TestReq) (*TestResp, error)
|
||||
QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error)
|
||||
ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error)
|
||||
mustEmbedUnimplementedLibcoreServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedLibcoreServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedLibcoreServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedLibcoreServiceServer) Exit(context.Context, *EmptyReq) (*EmptyResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Exit not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Update(context.Context, *UpdateReq) (*UpdateResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Update not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Start(context.Context, *LoadConfigReq) (*ErrorResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Start not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*ErrorResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Test not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListConnections not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {}
|
||||
|
||||
// UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to LibcoreServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeLibcoreServiceServer interface {
|
||||
mustEmbedUnimplementedLibcoreServiceServer()
|
||||
}
|
||||
|
||||
func RegisterLibcoreServiceServer(s grpc.ServiceRegistrar, srv LibcoreServiceServer) {
|
||||
s.RegisterService(&LibcoreService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _LibcoreService_Exit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Exit(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Exit",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Exit(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Update(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Update",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Update(ctx, req.(*UpdateReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LoadConfigReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Start(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Start",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Start(ctx, req.(*LoadConfigReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Stop(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Stop",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Stop(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TestReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Test(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Test",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Test(ctx, req.(*TestReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(QueryStatsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).QueryStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/QueryStats",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).QueryStats(ctx, req.(*QueryStatsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_ListConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).ListConnections(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/ListConnections",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).ListConnections(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var LibcoreService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "libcore.LibcoreService",
|
||||
HandlerType: (*LibcoreServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Exit",
|
||||
Handler: _LibcoreService_Exit_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Update",
|
||||
Handler: _LibcoreService_Update_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Start",
|
||||
Handler: _LibcoreService_Start_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Stop",
|
||||
Handler: _LibcoreService_Stop_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Test",
|
||||
Handler: _LibcoreService_Test_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "QueryStats",
|
||||
Handler: _LibcoreService_QueryStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListConnections",
|
||||
Handler: _LibcoreService_ListConnections_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "libcore.proto",
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto
|
||||
|
||||
# protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto
|
||||
@ -1,21 +0,0 @@
|
||||
module grpc_server
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/matsuridayo/libneko v1.0.0 // replaced
|
||||
google.golang.org/grpc v1.49.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
)
|
||||
|
||||
replace github.com/matsuridayo/libneko v1.0.0 => ../../../libneko
|
||||
@ -1,178 +0,0 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -1,103 +0,0 @@
|
||||
package grpc_server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"grpc_server/auth"
|
||||
"grpc_server/gen"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
|
||||
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type BaseServer struct {
|
||||
gen.LibcoreServiceServer
|
||||
}
|
||||
|
||||
func (s *BaseServer) Exit(ctx context.Context, in *gen.EmptyReq) (out *gen.EmptyResp, _ error) {
|
||||
out = &gen.EmptyResp{}
|
||||
|
||||
// Connection closed
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
func RunCore(setupCore func(), server gen.LibcoreServiceServer) {
|
||||
_token := flag.String("token", "", "")
|
||||
_port := flag.Int("port", 19810, "")
|
||||
_debug := flag.Bool("debug", false, "")
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
|
||||
neko_common.Debug = *_debug
|
||||
|
||||
go func() {
|
||||
parent, err := os.FindProcess(os.Getppid())
|
||||
if err != nil {
|
||||
log.Fatalln("find parent:", err)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
state, err := parent.Wait()
|
||||
log.Fatalln("parent exited:", state, err)
|
||||
} else {
|
||||
for {
|
||||
time.Sleep(time.Second * 10)
|
||||
err = parent.Signal(syscall.Signal(0))
|
||||
if err != nil {
|
||||
log.Fatalln("parent exited:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Libcore
|
||||
setupCore()
|
||||
|
||||
// GRPC
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(*_port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
token := *_token
|
||||
if token == "" {
|
||||
os.Stderr.WriteString("Please set a token: ")
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
if s.Scan() {
|
||||
token = strings.TrimSpace(s.Text())
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
fmt.Println("You must set a token")
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Stderr.WriteString("token is set\n")
|
||||
|
||||
auther := auth.Authenticator{
|
||||
Token: token,
|
||||
}
|
||||
|
||||
s := grpc.NewServer(
|
||||
grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)),
|
||||
grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)),
|
||||
)
|
||||
gen.RegisterLibcoreServiceServer(s, server)
|
||||
|
||||
name := "nekobox_core"
|
||||
|
||||
log.Printf("%s grpc server listening at %v\n", name, lis.Addr())
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
package grpc_server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"grpc_server/gen"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/matsuridayo/libneko/neko_common"
|
||||
)
|
||||
|
||||
var update_download_url string
|
||||
|
||||
func (s *BaseServer) Update(ctx context.Context, in *gen.UpdateReq) (*gen.UpdateResp, error) {
|
||||
ret := &gen.UpdateResp{}
|
||||
|
||||
client := neko_common.CreateProxyHttpClient(neko_common.GetCurrentInstance())
|
||||
|
||||
if in.Action == gen.UpdateAction_Check { // Check update
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/MatsuriDayo/nekoray/releases", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
v := []struct {
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadUrl string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
Body string `json:"body"`
|
||||
}{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&v)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
nowVer := strings.TrimLeft(neko_common.Version_neko, "nekoray-")
|
||||
|
||||
var search string
|
||||
if runtime.GOOS == "windows" && runtime.GOARCH == "amd64" {
|
||||
search = "windows64"
|
||||
} else if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
||||
search = "linux64"
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
search = "macos-" + runtime.GOARCH
|
||||
} else {
|
||||
ret.Error = "Not official support platform"
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
for _, release := range v {
|
||||
if len(release.Assets) > 0 {
|
||||
for _, asset := range release.Assets {
|
||||
if strings.Contains(asset.Name, nowVer) {
|
||||
return ret, nil // No update
|
||||
}
|
||||
if strings.Contains(asset.Name, search) {
|
||||
if release.Prerelease && !in.CheckPreRelease {
|
||||
continue
|
||||
}
|
||||
update_download_url = asset.BrowserDownloadUrl
|
||||
ret.AssetsName = asset.Name
|
||||
ret.DownloadUrl = asset.BrowserDownloadUrl
|
||||
ret.ReleaseUrl = release.HtmlUrl
|
||||
ret.ReleaseNote = release.Body
|
||||
ret.IsPreRelease = release.Prerelease
|
||||
return ret, nil // update
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Download update
|
||||
if update_download_url == "" {
|
||||
ret.Error = "?"
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", update_download_url, nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.OpenFile("../nekoray.zip", os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
f.Sync()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
2
libs/.gitignore
vendored
2
libs/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/deps*
|
||||
downloaded
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user