2021-12-07 22:47:41 +01:00
|
|
|
#include "content/views/view_bookmarks.hpp"
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2022-08-14 14:45:32 +02:00
|
|
|
#include <hex/api/content_registry.hpp>
|
2022-08-08 21:23:52 +02:00
|
|
|
#include <hex/api/project_file_manager.hpp>
|
2021-01-13 17:28:27 +01:00
|
|
|
#include <hex/providers/provider.hpp>
|
2021-08-29 22:15:18 +02:00
|
|
|
#include <hex/helpers/fmt.hpp>
|
2022-08-14 14:45:32 +02:00
|
|
|
#include <hex/helpers/file.hpp>
|
2021-08-29 22:15:18 +02:00
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
#include <nlohmann/json.hpp>
|
2020-11-28 00:33:26 +01:00
|
|
|
#include <cstring>
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
#include <provider_extra_data.hpp>
|
|
|
|
|
2021-12-07 22:47:41 +01:00
|
|
|
namespace hex::plugin::builtin {
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2021-12-07 22:47:41 +01:00
|
|
|
ViewBookmarks::ViewBookmarks() : View("hex.builtin.view.bookmarks.name") {
|
2022-08-08 21:23:52 +02:00
|
|
|
EventManager::subscribe<RequestAddBookmark>(this, [](Region region, std::string name, std::string comment, color_t color) {
|
2022-02-01 18:09:40 +01:00
|
|
|
if (name.empty()) {
|
|
|
|
name = hex::format("hex.builtin.view.bookmarks.default_title"_lang, region.address, region.address + region.size - 1);
|
2021-01-14 17:01:44 +01:00
|
|
|
}
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2022-02-01 18:09:40 +01:00
|
|
|
if (color == 0x00)
|
|
|
|
color = ImGui::GetColorU32(ImGuiCol_Header);
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
ProviderExtraData::getCurrent().bookmarks.push_back({
|
|
|
|
region,
|
2022-02-01 18:09:40 +01:00
|
|
|
name,
|
|
|
|
std::move(comment),
|
|
|
|
color,
|
2022-05-27 20:42:07 +02:00
|
|
|
false
|
|
|
|
});
|
2021-01-13 14:11:23 +01:00
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
ImHexApi::Provider::markDirty();
|
2020-11-30 00:03:12 +01:00
|
|
|
});
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
ImHexApi::HexEditor::addBackgroundHighlightingProvider([](u64 address, const u8* data, size_t size) -> std::optional<color_t> {
|
2022-05-27 20:42:07 +02:00
|
|
|
hex::unused(data);
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
for (const auto &bookmark : ProviderExtraData::getCurrent().bookmarks) {
|
2022-05-27 20:42:07 +02:00
|
|
|
if (Region { address, size }.isWithin(bookmark.region))
|
|
|
|
return bookmark.color;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
});
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
ImHexApi::HexEditor::addTooltipProvider([](u64 address, const u8 *data, size_t size) {
|
2022-05-27 20:42:07 +02:00
|
|
|
hex::unused(data);
|
2022-08-08 21:23:52 +02:00
|
|
|
for (const auto &bookmark : ProviderExtraData::getCurrent().bookmarks) {
|
2022-05-27 20:42:07 +02:00
|
|
|
if (!Region { address, size }.isWithin(bookmark.region))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ImGui::BeginTooltip();
|
|
|
|
|
2022-07-17 13:12:28 +02:00
|
|
|
ImGui::PushID(&bookmark);
|
|
|
|
if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) {
|
2022-05-27 20:42:07 +02:00
|
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
|
|
|
{
|
|
|
|
ImGui::ColorButton("##color", ImColor(bookmark.color));
|
|
|
|
ImGui::SameLine(0, 10);
|
|
|
|
ImGui::TextUnformatted(bookmark.name.c_str());
|
|
|
|
|
|
|
|
if (ImGui::GetIO().KeyShift) {
|
|
|
|
ImGui::Indent();
|
2022-07-17 13:12:28 +02:00
|
|
|
if (ImGui::BeginTable("##extra_info", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) {
|
2022-05-27 20:42:07 +02:00
|
|
|
|
|
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableNextColumn();
|
|
|
|
|
|
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableNextColumn();
|
2022-07-29 13:59:57 +02:00
|
|
|
ImGui::TextFormatted("{}: ", "hex.builtin.common.region"_lang.get());
|
2022-05-27 20:42:07 +02:00
|
|
|
ImGui::TableNextColumn();
|
|
|
|
ImGui::TextFormatted("[ 0x{:08X} - 0x{:08X} ]", bookmark.region.getStartAddress(), bookmark.region.getEndAddress());
|
|
|
|
|
|
|
|
if (!bookmark.comment.empty() && bookmark.comment[0] != '\x00') {
|
|
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableNextColumn();
|
2022-07-29 13:59:57 +02:00
|
|
|
ImGui::TextFormatted("{}: ", "hex.builtin.view.bookmarks.header.comment"_lang.get());
|
2022-05-27 20:42:07 +02:00
|
|
|
ImGui::TableNextColumn();
|
|
|
|
ImGui::TextFormattedWrapped("\"{}\"", bookmark.comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
}
|
|
|
|
ImGui::Unindent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_TableRowBg, bookmark.color);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, bookmark.color);
|
|
|
|
ImGui::EndTable();
|
|
|
|
ImGui::PopStyleColor(2);
|
|
|
|
}
|
2022-07-17 13:12:28 +02:00
|
|
|
ImGui::PopID();
|
2022-05-27 20:42:07 +02:00
|
|
|
|
|
|
|
ImGui::EndTooltip();
|
|
|
|
}
|
|
|
|
});
|
2022-08-08 21:23:52 +02:00
|
|
|
|
|
|
|
ProjectFile::registerPerProviderHandler({
|
|
|
|
.basePath = "bookmarks.json",
|
|
|
|
.load = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) -> bool {
|
|
|
|
auto fileContent = tar.read(basePath);
|
|
|
|
if (fileContent.empty())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
auto data = nlohmann::json::parse(fileContent.begin(), fileContent.end());
|
2022-08-14 14:45:32 +02:00
|
|
|
ProviderExtraData::get(provider).bookmarks.clear();
|
|
|
|
return ViewBookmarks::importBookmarks(provider, data);
|
2022-08-08 21:23:52 +02:00
|
|
|
},
|
|
|
|
.store = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) -> bool {
|
|
|
|
nlohmann::json data;
|
|
|
|
|
2022-08-14 14:45:32 +02:00
|
|
|
bool result = ViewBookmarks::exportBookmarks(provider, data);
|
2022-08-08 21:23:52 +02:00
|
|
|
tar.write(basePath, data.dump(4));
|
|
|
|
|
2022-08-14 14:45:32 +02:00
|
|
|
return result;
|
2022-08-08 21:23:52 +02:00
|
|
|
}
|
|
|
|
});
|
2022-08-14 14:45:32 +02:00
|
|
|
|
|
|
|
this->registerMenuItems();
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ViewBookmarks::~ViewBookmarks() {
|
2021-03-27 11:36:36 +01:00
|
|
|
EventManager::unsubscribe<RequestAddBookmark>(this);
|
2022-07-30 22:01:49 +02:00
|
|
|
EventManager::unsubscribe<EventProviderDeleted>(this);
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
|
2020-12-22 18:10:01 +01:00
|
|
|
void ViewBookmarks::drawContent() {
|
2021-12-07 22:47:41 +01:00
|
|
|
if (ImGui::Begin(View::toWindowName("hex.builtin.view.bookmarks.name").c_str(), &this->getWindowOpenState())) {
|
2022-07-23 20:38:38 +02:00
|
|
|
|
2022-08-03 23:32:34 +02:00
|
|
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
2022-07-29 13:59:57 +02:00
|
|
|
ImGui::InputTextWithHint("##filter", "hex.builtin.common.filter"_lang, this->m_currFilter);
|
2022-07-23 20:38:38 +02:00
|
|
|
ImGui::PopItemWidth();
|
2022-07-23 20:46:20 +02:00
|
|
|
|
2022-07-23 20:38:38 +02:00
|
|
|
ImGui::NewLine();
|
|
|
|
|
|
|
|
if (ImGui::BeginChild("##bookmarks")) {
|
2022-08-08 21:23:52 +02:00
|
|
|
auto &bookmarks = ProviderExtraData::getCurrent().bookmarks;
|
|
|
|
if (bookmarks.empty()) {
|
2022-01-24 00:46:19 +01:00
|
|
|
ImGui::TextFormattedCentered("hex.builtin.view.bookmarks.no_bookmarks"_lang);
|
2021-01-08 19:34:29 +01:00
|
|
|
}
|
|
|
|
|
2022-07-23 20:38:38 +02:00
|
|
|
u32 id = 1;
|
2022-08-08 21:23:52 +02:00
|
|
|
auto bookmarkToRemove = bookmarks.end();
|
|
|
|
for (auto iter = bookmarks.begin(); iter != bookmarks.end(); iter++) {
|
2022-05-27 20:42:07 +02:00
|
|
|
auto &[region, name, comment, color, locked] = *iter;
|
2021-01-13 14:11:23 +01:00
|
|
|
|
2022-07-23 20:38:38 +02:00
|
|
|
if (!this->m_currFilter.empty()) {
|
|
|
|
if (!name.contains(this->m_currFilter) && !comment.contains(this->m_currFilter))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-01-13 14:11:23 +01:00
|
|
|
auto headerColor = ImColor(color);
|
2022-02-01 22:09:44 +01:00
|
|
|
auto hoverColor = ImColor(color);
|
2021-01-13 14:11:23 +01:00
|
|
|
hoverColor.Value.w *= 1.3F;
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2021-01-13 16:55:42 +01:00
|
|
|
ImGui::PushID(id);
|
2021-01-13 14:11:23 +01:00
|
|
|
ImGui::PushStyleColor(ImGuiCol_Header, color);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, color);
|
|
|
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, u32(hoverColor));
|
2022-03-26 16:55:48 +01:00
|
|
|
|
|
|
|
bool open = true;
|
2022-06-03 10:34:52 +02:00
|
|
|
if (ImGui::CollapsingHeader(hex::format("{}###bookmark", name).c_str(), &open)) {
|
2021-12-07 22:47:41 +01:00
|
|
|
ImGui::TextUnformatted("hex.builtin.view.bookmarks.title.info"_lang);
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::Separator();
|
2021-12-31 01:10:06 +01:00
|
|
|
ImGui::TextFormatted("hex.builtin.view.bookmarks.address"_lang, region.address, region.address + region.size - 1, region.size);
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2021-08-28 01:47:26 +02:00
|
|
|
if (ImGui::BeginChild("hexData", ImVec2(0, ImGui::GetTextLineHeight() * 8), true)) {
|
|
|
|
size_t offset = region.address % 0x10;
|
2020-11-28 00:33:26 +01:00
|
|
|
|
2022-01-15 14:14:53 +01:00
|
|
|
for (u8 byte = 0; byte < 0x10; byte++) {
|
|
|
|
ImGui::TextFormattedDisabled("{0:02X}", byte);
|
2021-08-28 01:47:26 +02:00
|
|
|
ImGui::SameLine();
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
|
2021-08-28 01:47:26 +02:00
|
|
|
ImGui::NewLine();
|
|
|
|
|
|
|
|
// TODO: Clip this somehow
|
|
|
|
|
|
|
|
// First line
|
|
|
|
{
|
|
|
|
std::array<u8, 0x10> bytes = { 0 };
|
2022-02-01 22:09:44 +01:00
|
|
|
size_t byteCount = std::min<size_t>(0x10 - offset, region.size);
|
2021-09-21 02:29:54 +02:00
|
|
|
ImHexApi::Provider::get()->read(region.address, bytes.data() + offset, byteCount);
|
2021-08-28 01:47:26 +02:00
|
|
|
|
|
|
|
for (size_t byte = 0; byte < 0x10; byte++) {
|
|
|
|
if (byte < offset)
|
|
|
|
ImGui::TextUnformatted(" ");
|
|
|
|
else
|
2021-12-31 01:10:06 +01:00
|
|
|
ImGui::TextFormatted("{0:02X}", bytes[byte]);
|
2021-08-28 01:47:26 +02:00
|
|
|
ImGui::SameLine();
|
|
|
|
}
|
|
|
|
ImGui::NewLine();
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
|
2021-08-28 01:47:26 +02:00
|
|
|
// Other lines
|
|
|
|
{
|
|
|
|
std::array<u8, 0x10> bytes = { 0 };
|
|
|
|
for (u32 i = 0x10 - offset; i < region.size; i += 0x10) {
|
|
|
|
size_t byteCount = std::min<size_t>(region.size - i, 0x10);
|
2021-09-21 02:29:54 +02:00
|
|
|
ImHexApi::Provider::get()->read(region.address + i, bytes.data(), byteCount);
|
2021-08-28 01:47:26 +02:00
|
|
|
|
|
|
|
for (size_t byte = 0; byte < byteCount; byte++) {
|
2021-12-31 01:10:06 +01:00
|
|
|
ImGui::TextFormatted("{0:02X}", bytes[byte]);
|
2021-08-28 01:47:26 +02:00
|
|
|
ImGui::SameLine();
|
|
|
|
}
|
|
|
|
ImGui::NewLine();
|
|
|
|
}
|
|
|
|
}
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
2021-08-28 16:02:53 +02:00
|
|
|
ImGui::EndChild();
|
2021-08-28 01:47:26 +02:00
|
|
|
|
2021-12-07 22:47:41 +01:00
|
|
|
if (ImGui::Button("hex.builtin.view.bookmarks.button.jump"_lang))
|
2022-02-08 18:38:54 +01:00
|
|
|
ImHexApi::HexEditor::setSelection(region);
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::SameLine(0, 15);
|
|
|
|
|
2021-02-25 21:51:12 +01:00
|
|
|
if (locked) {
|
|
|
|
if (ImGui::Button(ICON_FA_LOCK)) locked = false;
|
|
|
|
} else {
|
|
|
|
if (ImGui::Button(ICON_FA_UNLOCK)) locked = true;
|
|
|
|
}
|
2020-11-28 00:33:26 +01:00
|
|
|
|
|
|
|
ImGui::NewLine();
|
2021-12-07 22:47:41 +01:00
|
|
|
ImGui::TextUnformatted("hex.builtin.view.bookmarks.header.name"_lang);
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::Separator();
|
2021-02-24 21:42:18 +01:00
|
|
|
|
2022-01-24 20:53:17 +01:00
|
|
|
ImGui::ColorEdit4("hex.builtin.view.bookmarks.header.color"_lang, (float *)&headerColor.Value, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoAlpha | (locked ? ImGuiColorEditFlags_NoPicker : ImGuiColorEditFlags_None));
|
2021-01-13 14:11:23 +01:00
|
|
|
color = headerColor;
|
2021-02-24 21:42:18 +01:00
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
if (locked)
|
|
|
|
ImGui::TextUnformatted(name.data());
|
|
|
|
else
|
2022-03-03 09:24:09 +01:00
|
|
|
ImGui::InputText("##nameInput", name);
|
2021-02-24 21:42:18 +01:00
|
|
|
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::NewLine();
|
2021-12-07 22:47:41 +01:00
|
|
|
ImGui::TextUnformatted("hex.builtin.view.bookmarks.header.comment"_lang);
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::Separator();
|
2021-02-24 21:42:18 +01:00
|
|
|
|
|
|
|
if (locked)
|
2022-01-15 14:14:53 +01:00
|
|
|
ImGui::TextFormattedWrapped("{}", comment.data());
|
2021-02-24 21:42:18 +01:00
|
|
|
else
|
2022-03-03 09:24:09 +01:00
|
|
|
ImGui::InputTextMultiline("##commentInput", comment);
|
2021-02-24 21:42:18 +01:00
|
|
|
|
2020-11-28 00:33:26 +01:00
|
|
|
ImGui::NewLine();
|
|
|
|
}
|
2022-03-26 16:55:48 +01:00
|
|
|
|
|
|
|
if (!open)
|
|
|
|
bookmarkToRemove = iter;
|
|
|
|
|
2021-01-13 16:55:42 +01:00
|
|
|
ImGui::PopID();
|
2021-01-13 14:11:23 +01:00
|
|
|
ImGui::PopStyleColor(3);
|
2021-01-13 16:55:42 +01:00
|
|
|
id++;
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
|
2022-08-08 21:23:52 +02:00
|
|
|
if (bookmarkToRemove != bookmarks.end()) {
|
|
|
|
bookmarks.erase(bookmarkToRemove);
|
2020-11-30 00:03:12 +01:00
|
|
|
}
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
2021-08-28 16:02:53 +02:00
|
|
|
ImGui::EndChild();
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|
|
|
|
ImGui::End();
|
|
|
|
}
|
|
|
|
|
2022-08-14 14:45:32 +02:00
|
|
|
bool ViewBookmarks::importBookmarks(prv::Provider *provider, const nlohmann::json &json) {
|
|
|
|
if (!json.contains("bookmarks"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto &bookmarks = ProviderExtraData::get(provider).bookmarks;
|
|
|
|
for (const auto &bookmark : json["bookmarks"]) {
|
|
|
|
if (!bookmark.contains("name") || !bookmark.contains("comment") || !bookmark.contains("color") || !bookmark.contains("region") || !bookmark.contains("locked"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const auto ®ion = bookmark["region"];
|
|
|
|
if (!region.contains("address") || !region.contains("size"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
bookmarks.push_back({
|
|
|
|
.region = { region["address"], region["size"] },
|
|
|
|
.name = bookmark["name"],
|
|
|
|
.comment = bookmark["comment"],
|
|
|
|
.color = bookmark["color"],
|
|
|
|
.locked = bookmark["locked"]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ViewBookmarks::exportBookmarks(prv::Provider *provider, nlohmann::json &json) {
|
|
|
|
json["bookmarks"] = nlohmann::json::array();
|
|
|
|
size_t index = 0;
|
|
|
|
for (const auto &bookmark : ProviderExtraData::get(provider).bookmarks) {
|
|
|
|
json["bookmarks"][index] = {
|
|
|
|
{ "name", bookmark.name },
|
|
|
|
{ "comment", bookmark.comment },
|
|
|
|
{ "color", bookmark.color },
|
|
|
|
{ "region", {
|
|
|
|
{ "address", bookmark.region.address },
|
|
|
|
{ "size", bookmark.region.size }
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ "locked", bookmark.locked }
|
|
|
|
};
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ViewBookmarks::registerMenuItems() {
|
|
|
|
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1050, [&] {
|
|
|
|
bool providerValid = ImHexApi::Provider::isValid();
|
|
|
|
auto selection = ImHexApi::HexEditor::getSelection();
|
|
|
|
|
|
|
|
if (ImGui::MenuItem("hex.builtin.menu.edit.bookmark.create"_lang, nullptr, false, selection.has_value() && providerValid)) {
|
|
|
|
ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 4000, [&] {
|
|
|
|
bool providerValid = ImHexApi::Provider::isValid();
|
|
|
|
|
2022-08-16 11:49:40 +02:00
|
|
|
if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.import"_lang, nullptr, false, providerValid)) {
|
2022-08-14 14:45:32 +02:00
|
|
|
fs::openFileBrowser(fs::DialogMode::Open, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
|
|
|
try {
|
|
|
|
importBookmarks(ImHexApi::Provider::get(), nlohmann::json::parse(fs::File(path, fs::File::Mode::Read).readString()));
|
|
|
|
} catch (...) { }
|
|
|
|
});
|
|
|
|
}
|
2022-08-16 11:49:40 +02:00
|
|
|
if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.export"_lang, nullptr, false, providerValid && !ProviderExtraData::getCurrent().bookmarks.empty())) {
|
2022-08-14 14:45:32 +02:00
|
|
|
fs::openFileBrowser(fs::DialogMode::Save, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
|
|
|
nlohmann::json json;
|
|
|
|
exportBookmarks(ImHexApi::Provider::get(), json);
|
|
|
|
|
|
|
|
fs::File(path, fs::File::Mode::Create).write(json.dump(4));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-28 00:33:26 +01:00
|
|
|
}
|