mirror of https://github.com/procxx/kepka.git
Move DumpToText from Telegram to lib_mtproto.
This commit is contained in:
parent
7243fb52ad
commit
8b14249cd2
|
@ -28,7 +28,6 @@ generate({
|
|||
'buffer': 'mtpBuffer',
|
||||
},
|
||||
'sections': [
|
||||
'serialization',
|
||||
'read-write',
|
||||
],
|
||||
|
||||
|
@ -91,4 +90,8 @@ generate({
|
|||
},
|
||||
'builtinInclude': 'mtproto/core_types.h',
|
||||
|
||||
'dumpToText': {
|
||||
'include': 'mtproto/details/mtproto_dump_to_text.h',
|
||||
},
|
||||
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include "mtproto/details/mtproto_dc_key_creator.h"
|
||||
#include "mtproto/details/mtproto_dc_key_checker.h"
|
||||
#include "mtproto/details/mtproto_dump_to_text.h"
|
||||
#include "mtproto/session.h"
|
||||
#include "mtproto/rsa_public_key.h"
|
||||
#include "mtproto/rpc_sender.h"
|
||||
|
@ -1433,7 +1434,7 @@ void ConnectionPrivate::handleReceived() {
|
|||
auto from = decryptedInts + kEncryptedHeaderIntsCount;
|
||||
auto end = from + (messageLength / kIntSize);
|
||||
auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message
|
||||
MTP_LOG(_shiftedDcId, ("Recv: ") + mtpTextSerialize(sfrom, end));
|
||||
MTP_LOG(_shiftedDcId, ("Recv: ") + details::DumpToText(sfrom, end));
|
||||
|
||||
bool needToHandle = false;
|
||||
{
|
||||
|
@ -2622,7 +2623,7 @@ bool ConnectionPrivate::sendSecureRequest(
|
|||
memcpy(request->data() + 2, &session, 2 * sizeof(mtpPrime));
|
||||
|
||||
auto from = request->constData() + 4;
|
||||
MTP_LOG(_shiftedDcId, ("Send: ") + mtpTextSerialize(from, from + messageSize));
|
||||
MTP_LOG(_shiftedDcId, ("Send: ") + details::DumpToText(from, from + messageSize));
|
||||
|
||||
#ifdef TDESKTOP_MTPROTO_OLD
|
||||
uint32 padding = fullSize - 4 - messageSize;
|
||||
|
|
|
@ -7,8 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "mtproto/core_types.h"
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
|
@ -175,149 +173,3 @@ const void *SecureRequest::dataInBytes() const {
|
|||
}
|
||||
|
||||
} // namespace MTP
|
||||
|
||||
bool mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) {
|
||||
switch (mtpTypeId(cons)) {
|
||||
case mtpc_int: {
|
||||
MTPint value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [INT]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_long: {
|
||||
MTPlong value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [LONG]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int128: {
|
||||
MTPint128 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h)).add(" * 2^64 + ").add(QString::number(value.l)).add(" [INT128]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int256: {
|
||||
MTPint256 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h.h)).add(" * 2^192 + ").add(QString::number(value.h.l)).add(" * 2^128 + ").add(QString::number(value.l.h)).add(" * 2 ^ 64 + ").add(QString::number(value.l.l)).add(" [INT256]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_double: {
|
||||
MTPdouble value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [DOUBLE]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_string: {
|
||||
MTPstring value;
|
||||
if (value.read(from, end, cons)) {
|
||||
auto strUtf8 = value.v;
|
||||
auto str = QString::fromUtf8(strUtf8);
|
||||
if (str.toUtf8() == strUtf8) {
|
||||
to.add("\"").add(str.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")).add("\" [STRING]");
|
||||
} else if (strUtf8.size() < 64) {
|
||||
to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
} else {
|
||||
to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_vector: {
|
||||
if (from < end) {
|
||||
int32 cnt = *(from++);
|
||||
to.add("[ vector<0x").add(QString::number(vcons, 16)).add(">");
|
||||
if (cnt) {
|
||||
to.add("\n").addSpaces(level);
|
||||
for (int32 i = 0; i < cnt; ++i) {
|
||||
to.add(" ");
|
||||
if (!mtpTextSerializeType(to, from, end, vcons, level + 1)) {
|
||||
return false;
|
||||
}
|
||||
to.add(",\n").addSpaces(level);
|
||||
}
|
||||
} else {
|
||||
to.add(" ");
|
||||
}
|
||||
to.add("]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_gzip_packed: {
|
||||
MTPstring packed;
|
||||
// read packed string as serialized mtp string type
|
||||
if (!packed.read(from, end)) {
|
||||
return false;
|
||||
}
|
||||
uint32 packedLen = packed.v.size(), unpackedChunk = packedLen;
|
||||
mtpBuffer result; // * 4 because of mtpPrime type
|
||||
result.resize(0);
|
||||
|
||||
z_stream stream;
|
||||
stream.zalloc = nullptr;
|
||||
stream.zfree = nullptr;
|
||||
stream.opaque = nullptr;
|
||||
stream.avail_in = 0;
|
||||
stream.next_in = nullptr;
|
||||
int res = inflateInit2(&stream, 16 + MAX_WBITS);
|
||||
if (res != Z_OK) {
|
||||
return false;
|
||||
}
|
||||
stream.avail_in = packedLen;
|
||||
stream.next_in = reinterpret_cast<Bytef*>(packed.v.data());
|
||||
stream.avail_out = 0;
|
||||
while (!stream.avail_out) {
|
||||
result.resize(result.size() + unpackedChunk);
|
||||
stream.avail_out = unpackedChunk * sizeof(mtpPrime);
|
||||
stream.next_out = (Bytef*)&result[result.size() - unpackedChunk];
|
||||
int res = inflate(&stream, Z_NO_FLUSH);
|
||||
if (res != Z_OK && res != Z_STREAM_END) {
|
||||
inflateEnd(&stream);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (stream.avail_out & 0x03) {
|
||||
return false;
|
||||
}
|
||||
result.resize(result.size() - (stream.avail_out >> 2));
|
||||
inflateEnd(&stream);
|
||||
|
||||
if (result.empty()) {
|
||||
return false;
|
||||
}
|
||||
const mtpPrime *newFrom = result.constData(), *newEnd = result.constData() + result.size();
|
||||
to.add("[GZIPPED] ");
|
||||
return mtpTextSerializeType(to, newFrom, newEnd, 0, level);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
|
||||
if (cons == mtpLayers[i]) {
|
||||
to.add("[LAYER").add(QString::number(i + 1)).add("] ");
|
||||
return mtpTextSerializeType(to, from, end, 0, level);
|
||||
}
|
||||
}
|
||||
if (cons == mtpc_invokeWithLayer) {
|
||||
if (from >= end) {
|
||||
return false;
|
||||
}
|
||||
int32 layer = *(from++);
|
||||
to.add("[LAYER").add(QString::number(layer)).add("] ");
|
||||
return mtpTextSerializeType(to, from, end, 0, level);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -335,80 +335,6 @@ inline MTPvector<T> MTP_vector() {
|
|||
return tl::make_vector<T>();
|
||||
}
|
||||
|
||||
// Human-readable text serialization
|
||||
|
||||
struct MTPStringLogger {
|
||||
static constexpr auto kBufferSize = 1024 * 1024; // 1 mb start size
|
||||
|
||||
MTPStringLogger()
|
||||
: p(new char[kBufferSize])
|
||||
, alloced(kBufferSize) {
|
||||
}
|
||||
~MTPStringLogger() {
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
MTPStringLogger &add(const QString &data) {
|
||||
auto d = data.toUtf8();
|
||||
return add(d.constData(), d.size());
|
||||
}
|
||||
|
||||
MTPStringLogger &add(const char *data, int32 len = -1) {
|
||||
if (len < 0) len = strlen(data);
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
memcpy(p + size, data, len);
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
MTPStringLogger &addSpaces(int32 level) {
|
||||
int32 len = level * 2;
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
for (char *ptr = p + size, *end = ptr + len; ptr != end; ++ptr) {
|
||||
*ptr = ' ';
|
||||
}
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
MTPStringLogger &error(const char *problem = "could not decode type") {
|
||||
return add("[ERROR] (").add(problem).add(")");
|
||||
}
|
||||
|
||||
void ensureLength(int32 add) {
|
||||
if (size + add <= alloced) return;
|
||||
|
||||
int32 newsize = size + add;
|
||||
if (newsize % kBufferSize) {
|
||||
newsize += kBufferSize - (newsize % kBufferSize);
|
||||
}
|
||||
char *b = new char[newsize];
|
||||
memcpy(b, p, size);
|
||||
alloced = newsize;
|
||||
delete[] p;
|
||||
p = b;
|
||||
}
|
||||
|
||||
char *p = nullptr;
|
||||
int size = 0;
|
||||
int alloced = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons = 0, uint32 level = 0, mtpPrime vcons = 0);
|
||||
|
||||
[[nodiscard]] bool mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons = 0);
|
||||
|
||||
inline QString mtpTextSerialize(const mtpPrime *&from, const mtpPrime *end) {
|
||||
MTPStringLogger to;
|
||||
[[maybe_unused]] bool result = mtpTextSerializeType(to, from, end, mtpc_core_message);
|
||||
return QString::fromUtf8(to.p, to.size);
|
||||
}
|
||||
|
||||
namespace tl {
|
||||
|
||||
template <typename Accumulator>
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "mtproto/details/mtproto_dump_to_text.h"
|
||||
|
||||
#include "base/zlib_help.h"
|
||||
#include "scheme-dump_to_text.h"
|
||||
#include "scheme.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) {
|
||||
switch (mtpTypeId(cons)) {
|
||||
case mtpc_int: {
|
||||
MTPint value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [INT]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_long: {
|
||||
MTPlong value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [LONG]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int128: {
|
||||
MTPint128 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h)).add(" * 2^64 + ").add(QString::number(value.l)).add(" [INT128]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int256: {
|
||||
MTPint256 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h.h)).add(" * 2^192 + ").add(QString::number(value.h.l)).add(" * 2^128 + ").add(QString::number(value.l.h)).add(" * 2 ^ 64 + ").add(QString::number(value.l.l)).add(" [INT256]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_double: {
|
||||
MTPdouble value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [DOUBLE]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_string: {
|
||||
MTPstring value;
|
||||
if (value.read(from, end, cons)) {
|
||||
auto strUtf8 = value.v;
|
||||
auto str = QString::fromUtf8(strUtf8);
|
||||
if (str.toUtf8() == strUtf8) {
|
||||
to.add("\"").add(str.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")).add("\" [STRING]");
|
||||
} else if (strUtf8.size() < 64) {
|
||||
to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
} else {
|
||||
to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_vector: {
|
||||
if (from < end) {
|
||||
int32 cnt = *(from++);
|
||||
to.add("[ vector<0x").add(QString::number(vcons, 16)).add(">");
|
||||
if (cnt) {
|
||||
to.add("\n").addSpaces(level);
|
||||
for (int32 i = 0; i < cnt; ++i) {
|
||||
to.add(" ");
|
||||
if (!DumpToTextType(to, from, end, vcons, level + 1)) {
|
||||
return false;
|
||||
}
|
||||
to.add(",\n").addSpaces(level);
|
||||
}
|
||||
} else {
|
||||
to.add(" ");
|
||||
}
|
||||
to.add("]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_gzip_packed: {
|
||||
MTPstring packed;
|
||||
// read packed string as serialized mtp string type
|
||||
if (!packed.read(from, end)) {
|
||||
return false;
|
||||
}
|
||||
uint32 packedLen = packed.v.size(), unpackedChunk = packedLen;
|
||||
mtpBuffer result; // * 4 because of mtpPrime type
|
||||
result.resize(0);
|
||||
|
||||
z_stream stream;
|
||||
stream.zalloc = nullptr;
|
||||
stream.zfree = nullptr;
|
||||
stream.opaque = nullptr;
|
||||
stream.avail_in = 0;
|
||||
stream.next_in = nullptr;
|
||||
int res = inflateInit2(&stream, 16 + MAX_WBITS);
|
||||
if (res != Z_OK) {
|
||||
return false;
|
||||
}
|
||||
stream.avail_in = packedLen;
|
||||
stream.next_in = reinterpret_cast<Bytef*>(packed.v.data());
|
||||
stream.avail_out = 0;
|
||||
while (!stream.avail_out) {
|
||||
result.resize(result.size() + unpackedChunk);
|
||||
stream.avail_out = unpackedChunk * sizeof(mtpPrime);
|
||||
stream.next_out = (Bytef*)&result[result.size() - unpackedChunk];
|
||||
int res = inflate(&stream, Z_NO_FLUSH);
|
||||
if (res != Z_OK && res != Z_STREAM_END) {
|
||||
inflateEnd(&stream);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (stream.avail_out & 0x03) {
|
||||
return false;
|
||||
}
|
||||
result.resize(result.size() - (stream.avail_out >> 2));
|
||||
inflateEnd(&stream);
|
||||
|
||||
if (result.empty()) {
|
||||
return false;
|
||||
}
|
||||
const mtpPrime *newFrom = result.constData(), *newEnd = result.constData() + result.size();
|
||||
to.add("[GZIPPED] ");
|
||||
return DumpToTextType(to, newFrom, newEnd, 0, level);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
|
||||
if (cons == mtpLayers[i]) {
|
||||
to.add("[LAYER").add(QString::number(i + 1)).add("] ");
|
||||
return DumpToTextType(to, from, end, 0, level);
|
||||
}
|
||||
}
|
||||
if (cons == mtpc_invokeWithLayer) {
|
||||
if (from >= end) {
|
||||
return false;
|
||||
}
|
||||
int32 layer = *(from++);
|
||||
to.add("[LAYER").add(QString::number(layer)).add("] ");
|
||||
return DumpToTextType(to, from, end, 0, level);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DumpToText(const mtpPrime *&from, const mtpPrime *end) {
|
||||
DumpToTextBuffer to;
|
||||
[[maybe_unused]] bool result = DumpToTextType(to, from, end, mtpc_core_message);
|
||||
return QString::fromUtf8(to.p, to.size);
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
// Human-readable text serialization
|
||||
QString DumpToText(const mtpPrime *&from, const mtpPrime *end);
|
||||
|
||||
struct DumpToTextBuffer {
|
||||
static constexpr auto kBufferSize = 1024 * 1024; // 1 mb start size
|
||||
|
||||
DumpToTextBuffer()
|
||||
: p(new char[kBufferSize])
|
||||
, alloced(kBufferSize) {
|
||||
}
|
||||
~DumpToTextBuffer() {
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
DumpToTextBuffer &add(const QString &data) {
|
||||
auto d = data.toUtf8();
|
||||
return add(d.constData(), d.size());
|
||||
}
|
||||
|
||||
DumpToTextBuffer &add(const char *data, int32 len = -1) {
|
||||
if (len < 0) len = strlen(data);
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
memcpy(p + size, data, len);
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
DumpToTextBuffer &addSpaces(int32 level) {
|
||||
int32 len = level * 2;
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
for (char *ptr = p + size, *end = ptr + len; ptr != end; ++ptr) {
|
||||
*ptr = ' ';
|
||||
}
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
DumpToTextBuffer &error(const char *problem = "could not decode type") {
|
||||
return add("[ERROR] (").add(problem).add(")");
|
||||
}
|
||||
|
||||
void ensureLength(int32 add) {
|
||||
if (size + add <= alloced) return;
|
||||
|
||||
int32 newsize = size + add;
|
||||
if (newsize % kBufferSize) {
|
||||
newsize += kBufferSize - (newsize % kBufferSize);
|
||||
}
|
||||
char *b = new char[newsize];
|
||||
memcpy(b, p, size);
|
||||
alloced = newsize;
|
||||
delete[] p;
|
||||
p = b;
|
||||
}
|
||||
|
||||
char *p = nullptr;
|
||||
int size = 0;
|
||||
int alloced = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons = 0);
|
||||
|
||||
} // namespace MTP::details
|
|
@ -99,7 +99,6 @@
|
|||
'<(SHARED_INTERMEDIATE_DIR)',
|
||||
'<(libs_loc)/breakpad/src',
|
||||
'<(libs_loc)/lzma/C',
|
||||
'<(libs_loc)/zlib',
|
||||
'<(libs_loc)/openal-soft/include',
|
||||
'<(libs_loc)/opus/include',
|
||||
'<(minizip_loc)',
|
||||
|
|
|
@ -38,6 +38,8 @@
|
|||
'<(src_loc)/mtproto/details/mtproto_dc_key_checker.h',
|
||||
'<(src_loc)/mtproto/details/mtproto_dc_key_creator.cpp',
|
||||
'<(src_loc)/mtproto/details/mtproto_dc_key_creator.h',
|
||||
'<(src_loc)/mtproto/details/mtproto_dump_to_text.cpp',
|
||||
'<(src_loc)/mtproto/details/mtproto_dump_to_text.h',
|
||||
'<(src_loc)/mtproto/mtproto_dh_utils.cpp',
|
||||
'<(src_loc)/mtproto/mtproto_dh_utils.h',
|
||||
'<(src_loc)/mtproto/mtproto_proxy_data.cpp',
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
'outputs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/scheme.cpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/scheme.h',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/scheme-dump_to_text.cpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/scheme-dump_to_text.h',
|
||||
],
|
||||
'action': [
|
||||
'python', '<(src_loc)/codegen/scheme/codegen_scheme.py',
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c1941996febf02c4216a16fc6037e7473bcf9d00
|
||||
Subproject commit bdc4127ff5d5a14eb9d04986725ef7ec94e6d807
|
|
@ -1 +1 @@
|
|||
Subproject commit 89a1e3fed24bc0bfc268fa0352e7d2658ee4ef22
|
||||
Subproject commit 2bf101114f0a54c868a2703a9a80c06c0f2d2c41
|
Loading…
Reference in New Issue