From 48f3ae3ecd1574b6373f0ef6dac5113ce0c081de Mon Sep 17 00:00:00 2001 From: notify Date: Fri, 26 May 2023 20:53:26 +0800 Subject: [PATCH] ModMaker start (#163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 做了个Mod制作器的壳 修手气卡bug 修旁观/重连看不到标记 优化Card元表实现 --- .gitignore | 1 + Fk/ModMaker/CreateSomething.qml | 50 +++++++ Fk/ModMaker/ModConfig.qml | 36 +++++ Fk/ModMaker/ModInit.qml | 113 ++++++++++++++++ Fk/ModMaker/UserInfo.qml | 75 +++++++++++ Fk/ModMaker/main.qml | 55 ++++++++ Fk/ModMaker/qmldir | 2 + Fk/Pages/About.qml | 1 + Fk/Pages/Init.qml | 10 ++ Fk/Pages/ModMaker.qml | 3 + Fk/Pages/qmldir | 1 + Fk/main.qml | 1 + image/modmaker/add.png | Bin 0 -> 330 bytes image/modmaker/back.png | Bin 0 -> 358 bytes image/modmaker/menu.png | Bin 0 -> 351 bytes image/modmaker/ok.png | Bin 0 -> 414 bytes lang/zh_CN.ts | 67 ++++++++++ lua/core/card.lua | 38 ++---- lua/server/request.lua | 1 + lua/server/serverplayer.lua | 6 +- packages/standard/init.lua | 2 +- src/CMakeLists.txt | 1 + src/core/util.cpp | 28 +--- src/core/util.h | 4 - src/main.cpp | 1 + src/pch.h | 4 +- src/server/server.cpp | 28 +++- src/server/server.h | 5 + src/ui/mod.cpp | 230 ++++++++++++++++++++++++++++++++ src/ui/mod.h | 32 +++++ src/ui/qmlbackend.cpp | 16 ++- src/ui/qmlbackend.h | 5 + 32 files changed, 753 insertions(+), 63 deletions(-) create mode 100644 Fk/ModMaker/CreateSomething.qml create mode 100644 Fk/ModMaker/ModConfig.qml create mode 100644 Fk/ModMaker/ModInit.qml create mode 100644 Fk/ModMaker/UserInfo.qml create mode 100644 Fk/ModMaker/main.qml create mode 100644 Fk/ModMaker/qmldir create mode 100644 Fk/Pages/ModMaker.qml create mode 100644 image/modmaker/add.png create mode 100644 image/modmaker/back.png create mode 100644 image/modmaker/menu.png create mode 100644 image/modmaker/ok.png create mode 100644 src/ui/mod.cpp create mode 100644 src/ui/mod.h diff --git a/.gitignore b/.gitignore index 1282cf31..586b5bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ tags # file produced by game /FreeKill +/mymod FreeKill.exe freekill-wrap.cxx server/users.db diff --git a/Fk/ModMaker/CreateSomething.qml b/Fk/ModMaker/CreateSomething.qml new file mode 100644 index 00000000..ff2fb692 --- /dev/null +++ b/Fk/ModMaker/CreateSomething.qml @@ -0,0 +1,50 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + id: root + anchors.fill: parent + anchors.margins: 16 + signal finished() + signal accepted(string result) + + property string head + property string hint + + Text { + text: qsTr(head) + font.pixelSize: 20 + font.bold: true + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + } + + Text { + text: qsTr(hint) + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + } + + Text { + text: qsTr("validator_hint") + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + } + + TextField { + id: edit + font.pixelSize: 18 + Layout.fillWidth: true + validator: RegularExpressionValidator { regularExpression: /[0-9A-Za-z_]+/ } + } + + Button { + text: "OK" + enabled: edit.text.length >= 4 + onClicked: { + accepted(edit.text); + finished(); + } + } +} diff --git a/Fk/ModMaker/ModConfig.qml b/Fk/ModMaker/ModConfig.qml new file mode 100644 index 00000000..51bfc119 --- /dev/null +++ b/Fk/ModMaker/ModConfig.qml @@ -0,0 +1,36 @@ +import QtQuick + +QtObject { + property var conf + + property string userName + property string email + property var modList: [] + + function loadConf() { + conf = JSON.parse(ModBackend.readFile("mymod/config.json")); + userName = conf.userName ?? ""; + email = conf.email ?? ""; + modList = conf.modList ?? []; + } + + function saveConf() { + conf.userName = userName; + conf.email = email; + conf.modList = modList; + + ModBackend.saveToFile("mymod/config.json", JSON.stringify(conf, undefined, 2)); + } + + function addMod(mod) { + modList.push(mod); + saveConf(); + modListChanged(); + } + + function removeMod(mod) { + modList.splice(modList.indexOf(mod), 1); + saveConf(); + modListChanged(); + } +} diff --git a/Fk/ModMaker/ModInit.qml b/Fk/ModMaker/ModInit.qml new file mode 100644 index 00000000..c8a2ad3e --- /dev/null +++ b/Fk/ModMaker/ModInit.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + property bool configOK: modConfig.userName !== "" && modConfig.email !== "" + + ToolBar { + id: bar + width: parent.width + RowLayout { + anchors.fill: parent + ToolButton { + icon.source: AppPath + "/image/modmaker/back" + onClicked: mainStack.pop(); + } + Label { + text: qsTr("ModMaker") + horizontalAlignment: Qt.AlignHCenter + Layout.fillWidth: true + } + ToolButton { + icon.source: AppPath + "/image/modmaker/menu" + onClicked: { + dialog.source = "UserInfo.qml"; + drawer.open(); + } + } + } + } + + Rectangle { + width: parent.width + height: parent.height - bar.height + anchors.top: bar.bottom + color: "snow" + opacity: 0.75 + + Text { + anchors.centerIn: parent + text: root.configOK ? "" : qsTr("config is incomplete") + } + + ListView { + anchors.fill: parent + model: modConfig.modList + delegate: Text { text: modelData } + } + } + + RoundButton { + visible: root.configOK + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 40 + scale: 2 + icon.source: AppPath + "/image/modmaker/add" + onClicked: { + dialog.source = "CreateSomething.qml"; + dialog.item.head = "create_mod"; + dialog.item.hint = "create_mod_hint"; + drawer.open(); + dialog.item.accepted.connect((name) => { + createNewMod(name); + }); + } + } + + Drawer { + id: drawer + width: parent.width * 0.4 / mainWindow.scale + height: parent.height / mainWindow.scale + dim: false + clip: true + dragMargin: 0 + scale: mainWindow.scale + transformOrigin: Item.TopLeft + + Loader { + id: dialog + anchors.fill: parent + onSourceChanged: { + if (item === null) + return; + item.finished.connect(() => { + sourceComponent = undefined; + drawer.close(); + }); + } + onSourceComponentChanged: sourceChanged(); + } + } + + function createNewMod(name) { + const banned = [ "test", "standard", "standard_cards", "maneuvering" ]; + if (banned.indexOf(name) !== -1 || modConfig.modList.indexOf(name) !== -1) { + toast.show(qsTr("cannot use this mod name")); + return; + } + ModBackend.createMod(name); + const modInfo = { + name: name, + descrption: "", + author: modConfig.userName, + }; + ModBackend.saveToFile(`mymod/${name}/mod.json`, JSON.stringify(modInfo, undefined, 2)); + ModBackend.saveToFile(`mymod/${name}/.gitignore`, "init.lua"); + ModBackend.stageFiles(name); + ModBackend.commitChanges(name, "Initial commit", modConfig.userName, modConfig.email); + modConfig.addMod(name); + } +} diff --git a/Fk/ModMaker/UserInfo.qml b/Fk/ModMaker/UserInfo.qml new file mode 100644 index 00000000..3bc9bb32 --- /dev/null +++ b/Fk/ModMaker/UserInfo.qml @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + signal finished() + + Text { + text: qsTr("help_text") + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: qsTr("username") + } + TextField { + id: userName + font.pixelSize: 18 + text: modConfig.userName + Layout.fillWidth: true + onTextChanged: { + modConfig.userName = text; + modConfig.saveConf(); + } + } + } + + RowLayout { + anchors.rightMargin: 8 + spacing: 16 + Text { + text: qsTr("email") + } + TextField { + id: emailAddr + font.pixelSize: 18 + Layout.fillWidth: true + text: modConfig.email + onTextChanged: { + modConfig.email = text; + modConfig.saveConf(); + } + } + } + + Text { + text: qsTr("key_help_text") + Layout.fillWidth: true + wrapMode: Text.WrapAnywhere + textFormat: Text.RichText + onLinkActivated: Qt.openUrlExternally(link); + } + + Button { + text: qsTr("copy pubkey") + Layout.fillWidth: true + onClicked: { + const key = "mymod/id_rsa.pub"; + if (!Backend.exists(key)) { + ModBackend.initKey(); + } + const pubkey = ModBackend.readFile(key); + Backend.copyToClipboard(pubkey); + toast.show(qsTr("pubkey copied")); + } + } +} diff --git a/Fk/ModMaker/main.qml b/Fk/ModMaker/main.qml new file mode 100644 index 00000000..44793ad7 --- /dev/null +++ b/Fk/ModMaker/main.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Controls + +Item { + Component { id: modInit; ModInit {} } + + StackView { + id: modStack + anchors.fill: parent + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + } + + ModConfig { + id: modConfig + } + + Component.onCompleted: { + if (!ModBackend) { + Backend.createModBackend(); + } + modConfig.loadConf(); + modStack.push(modInit); + } +} diff --git a/Fk/ModMaker/qmldir b/Fk/ModMaker/qmldir new file mode 100644 index 00000000..4a0e4264 --- /dev/null +++ b/Fk/ModMaker/qmldir @@ -0,0 +1,2 @@ +module Fk.ModMaker +ModMaker 1.0 main.qml diff --git a/Fk/Pages/About.qml b/Fk/Pages/About.qml index 22edbb8b..19e16c21 100644 --- a/Fk/Pages/About.qml +++ b/Fk/Pages/About.qml @@ -54,6 +54,7 @@ Item { wrapMode: Text.WordWrap textFormat: Text.MarkdownText font.pixelSize: 18 + onLinkActivated: Qt.openUrlExternally(link); } } } diff --git a/Fk/Pages/Init.qml b/Fk/Pages/Init.qml index 626353cd..47a1f70d 100644 --- a/Fk/Pages/Init.qml +++ b/Fk/Pages/Init.qml @@ -183,6 +183,16 @@ Item { } } + // Temp + Button { + text: qsTr("Mod Making") + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: { + mainStack.push(modMaker); + } + } + function downloadComplete() { toast.show(qsTr("updated packages for md5")); } diff --git a/Fk/Pages/ModMaker.qml b/Fk/Pages/ModMaker.qml new file mode 100644 index 00000000..b9d6360f --- /dev/null +++ b/Fk/Pages/ModMaker.qml @@ -0,0 +1,3 @@ +import Fk.ModMaker as Md + +Md.ModMaker {} diff --git a/Fk/Pages/qmldir b/Fk/Pages/qmldir index 63044414..f473a9ec 100644 --- a/Fk/Pages/qmldir +++ b/Fk/Pages/qmldir @@ -10,3 +10,4 @@ ModesOverview 1.0 ModesOverview.qml PackageManage 1.0 PackageManage.qml Room 1.0 Room.qml TileButton 1.0 TileButton.qml +ModMaker 1.0 ModMaker.qml diff --git a/Fk/main.qml b/Fk/main.qml index 8decf09c..b95ee963 100644 --- a/Fk/main.qml +++ b/Fk/main.qml @@ -51,6 +51,7 @@ Item { Component { id: init; Init {} } Component { id: packageManage; PackageManage {} } + Component { id: modMaker; ModMaker {} } Component { id: lobby; Lobby {} } Component { id: generalsOverview; GeneralsOverview {} } Component { id: cardsOverview; CardsOverview {} } diff --git a/image/modmaker/add.png b/image/modmaker/add.png new file mode 100644 index 0000000000000000000000000000000000000000..46c8336accd1bb6dc46107671d6843858d794400 GIT binary patch literal 330 zcmV-Q0k!^#P)2)wao4UZcZ=<(7>-9gadHEsQ?gi3h3S*?LZ?H_U=kQB#3NaLua1k+%2u=h$bQ%XsO_p6y4Ff zMgs#1z<~r7KuB|EfDjPUaFU1tLO|$?2=Q#QGn}`i*tx{tXuTNr9<{TR_73n=c{LO; zMd*T(^tnWT(*O&>2<>x_KrkE#5Y?0t4w&Q$2!;az cqMA}Z0H%~aF%5s@)c^nh07*qoM6N<$f>^19p#T5? literal 0 HcmV?d00001 diff --git a/image/modmaker/back.png b/image/modmaker/back.png new file mode 100644 index 0000000000000000000000000000000000000000..fdce1585efde8d914fe9d0cb924d3482e39133bf GIT binary patch literal 358 zcmV-s0h#`ZP)POlm9-+`sQ|>pQvisH#{!6p#{j4hUkji{d<}pq@udLj#Fqf56rT$a6CX~O z;gH6;?vVVFDv+}izf(yV-zU#ee*q91p;x03I;Y&taUkR{{S!}3PzjOifLe%B0CnS3 z1E30`7C;RI20#S_7C;<=0zedk3P22k56qt178o>&l|%tL%CG z1smv^APJCw(E%PIqk-mh5-?k66bKO*iT2E>pDO(-LXU4^p$2Y}FiBN`QKyppX-ori z2|gP)O`X0F!ON#;h`>>Tm#M30sCSaQRSbcK`?R0NYqWjO-*+Sd5->V2=p%%CcVjdt zRr+3p@Lj5{Uj#5hV3hE*FiKSaeu;bE02;*++B!k7i#ekB`^ev>f?;wH%x`sdk zo(UXY--51d#Qqo~)-j{RApX>2?ihQpUuw9^593X3X%AZ<;bGx9CHi5@L*Z%-yhc@v xG6#@AW`k9E5>PG596$n@4OZnzK(#1yz!QGd7%|0{_{0DJ002ovPDHLkV1jj>lJDjWOJTi(-%CD ze~kZZ`HL`)HR-MWq6NziT-JCXQM|R%C8fPz^uvM!y{w|8_SQ;W2aa~w!o#~NN3~&Ro#?szJsmTCTqR*o@ABq?V}@~NW#CK z84)L$^~I)Z9k}kN=MebgiWt|{%Q{od+ZFg)Zgt*X^W%i#f!yVfa+_lo2a7(Hud#_c ydOA;b!s$lKnf2#_7_WNS%dNo{3<`7pu}*hzFuZr@;9Fq0GkCiCxvXPackageManage 管理拓展包 + + Mod Making + 制作Mod + Welcome back! 欢迎回来! @@ -212,4 +216,67 @@ 已复制。 + + + ModInit + + ModMaker + 新月杀Mod制作器 - 首页 + + + config is incomplete + + Mod制作器还未正确配置! +请点击右上角配置好用户名和邮箱 + + + cannot use this mod name + 不能给mod取这个名字 + + + + + UserInfo + + help_text + 用户名和邮箱需要填入和git服务器上相同的名字和邮箱。 + + + username + 用户名 + + + email + 邮箱 + + + key_help_text + 公钥是你向git服务器证明身份的手段。请点击按钮复制公钥,然后在网页中添加SSH密钥。详见新月杀Mod制作器教程。 + + + copy pubkey + 复制公钥 + + + pubkey copied + 公钥已经复制到剪贴板。 + + + + + CreateSomething + + validator_hint + 注意:你仅可以输入大小写字母、数字和下划线,且长度至少为4。这里输入的只是内部名称,它的中文名你可以稍后指定。 + + + create_mod + 新建Mod + + + create_mod_hint + 请输入mod的名称。 + + + diff --git a/lua/core/card.lua b/lua/core/card.lua index 52e1e504..88118f8f 100644 --- a/lua/core/card.lua +++ b/lua/core/card.lua @@ -86,13 +86,13 @@ function Card:initialize(name, suit, number, color) self.color = Card.NoColor end - self.package = nil + -- self.package = nil self.id = 0 self.type = 0 self.sub_type = Card.SubTypeNone - self.skill = nil + -- self.skill = nil self.subcards = {} - self.skillName = nil -- "" + -- self.skillName = nil self._skillName = "" self.skillNames = {} self.mark = {} @@ -101,31 +101,21 @@ function Card:initialize(name, suit, number, color) self.name = string.sub(name, 2, #name) self.is_derived = true end +end - local mt = table.simpleClone(getmetatable(self)) - local newidx = mt.__newindex or rawset - mt.__newindex = function(t, k, v) - if k == "skillName" then - table.insertIfNeed(self.skillNames, v) - t._skillName = v - else - return newidx(t, k, v) - end +function Card:__index(k) + if k == "skillName" then + return self._skillName end +end - local idx = mt.__index or rawget - mt.__index = function(t, k) - if k == "skillName" then - return t._skillName - end - if type(idx) == "table" then - return idx[k] - end - if type(idx) == "function" then - return idx(t, k) - end +function Card:__newindex(k, v) + if k == "skillName" then + table.insertIfNeed(self.skillNames, v) + self._skillName = v + else + rawset(self, k, v) end - setmetatable(self, mt) end function Card:__tostring() diff --git a/lua/server/request.lua b/lua/server/request.lua index afa85f03..c5c6cca8 100644 --- a/lua/server/request.lua +++ b/lua/server/request.lua @@ -90,6 +90,7 @@ request_handlers["luckcard"] = function(room, id, reqlist) local p = room:getPlayerById(id) local cancel = reqlist[3] == "false" local luck_data = room:getTag("LuckCardData") + if not (p and luck_data) then return end local pdata = luck_data[id] if not cancel then diff --git a/lua/server/serverplayer.lua b/lua/server/serverplayer.lua index 36bf646e..b024d758 100644 --- a/lua/server/serverplayer.lua +++ b/lua/server/serverplayer.lua @@ -243,7 +243,11 @@ function ServerPlayer:marshal(player) room:notifyMoveCards({ player }, card_moves) end - -- TODO: pile, mark + -- TODO: pile + + for k, v in pairs(self.mark) do + player:doNotify("SetPlayerMark", json.encode{self.id, k, v}) + end for _, s in ipairs(self.player_skills) do player:doNotify("AddSkill", json.encode{self.id, s.name}) diff --git a/packages/standard/init.lua b/packages/standard/init.lua index cb32619f..1ab74b47 100644 --- a/packages/standard/init.lua +++ b/packages/standard/init.lua @@ -1043,7 +1043,7 @@ local wushuang = fk.CreateTriggerSkill{ end if event == fk.TargetSpecified then - return target == player and table.contains({ "slash", "duel" }, data.card.name) + return target == player and table.contains({ "slash", "duel" }, data.card.trueName) else return data.to == player.id and data.card.name == "duel" end diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3c7b8fd..cd1da7a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ if (NOT DEFINED FK_SERVER_ONLY) list(APPEND freekill_SRCS "client/client.cpp" "client/clientplayer.cpp" + "ui/mod.cpp" ) endif () diff --git a/src/core/util.cpp b/src/core/util.cpp index 9a1c52ac..ed679644 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -139,33 +139,6 @@ void ExecSQL(sqlite3 *db, const QString &sql) { void CloseDatabase(sqlite3 *db) { sqlite3_close(db); } -#ifndef Q_OS_WASM -RSA *InitServerRSA() { - RSA *rsa = RSA_new(); - if (!QFile::exists("server/rsa_pub")) { - BIGNUM *bne = BN_new(); - BN_set_word(bne, RSA_F4); - RSA_generate_key_ex(rsa, 2048, bne, NULL); - - BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+"); - PEM_write_bio_RSAPublicKey(bp_pub, rsa); - BIO *bp_pri = BIO_new_file("server/rsa", "w+"); - PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL); - - BIO_free_all(bp_pub); - BIO_free_all(bp_pri); - BN_free(bne); - } - FILE *keyFile = fopen("server/rsa_pub", "r"); - PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL); - fclose(keyFile); - keyFile = fopen("server/rsa", "r"); - PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL); - fclose(keyFile); - return rsa; -} -#endif - static void writeFileMD5(QFile &dest, const QString &fname) { QFile f(fname); if (!f.open(QIODevice::ReadOnly)) { @@ -173,6 +146,7 @@ static void writeFileMD5(QFile &dest, const QString &fname) { } auto data = f.readAll(); + f.close(); data.replace(QByteArray("\r\n"), QByteArray("\n")); auto hash = QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); dest.write(fname.toUtf8() + '=' + hash + ';'); diff --git a/src/core/util.h b/src/core/util.h index 5773b328..1a18ad60 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -16,10 +16,6 @@ QString SelectFromDb(sqlite3 *db, const QString &sql); void ExecSQL(sqlite3 *db, const QString &sql); void CloseDatabase(sqlite3 *db); -#ifndef Q_OS_WASM -RSA *InitServerRSA(); -#endif - QString calcFileMD5(); QByteArray JsonArray2Bytes(const QJsonArray &arr); QJsonDocument String2Json(const QString &str); diff --git a/src/main.cpp b/src/main.cpp index ac98457f..3188818a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -260,6 +260,7 @@ int main(int argc, char *argv[]) { // 向 Qml 中先定义几个全局变量 engine->rootContext()->setContextProperty("FkVersion", FK_VERSION); engine->rootContext()->setContextProperty("Backend", &backend); + engine->rootContext()->setContextProperty("ModBackend", nullptr); engine->rootContext()->setContextProperty("Pacman", Pacman); #ifdef QT_DEBUG diff --git a/src/pch.h b/src/pch.h index f73e304b..f1189708 100644 --- a/src/pch.h +++ b/src/pch.h @@ -14,9 +14,7 @@ typedef int LuaFunction; #include "lua.hpp" #include "sqlite3.h" - -#include -#include +#define OPENSSL_API_COMPAT 0x10101000L #if !defined (Q_OS_ANDROID) && !defined (Q_OS_WASM) #define DESKTOP_BUILD diff --git a/src/server/server.cpp b/src/server/server.cpp index 3aa87567..41e7a55e 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -22,7 +22,7 @@ Server *ServerInstance; Server::Server(QObject *parent) : QObject(parent) { ServerInstance = this; db = OpenDatabase(); - rsa = InitServerRSA(); + rsa = initServerRSA(); QFile file("server/rsa_pub"); file.open(QIODevice::ReadOnly); QTextStream in(&file); @@ -431,6 +431,32 @@ void Server::onUserDisconnected() { void Server::onUserStateChanged() {} +RSA *Server::initServerRSA() { + RSA *rsa = RSA_new(); + if (!QFile::exists("server/rsa_pub")) { + BIGNUM *bne = BN_new(); + BN_set_word(bne, RSA_F4); + RSA_generate_key_ex(rsa, 2048, bne, NULL); + + BIO *bp_pub = BIO_new_file("server/rsa_pub", "w+"); + PEM_write_bio_RSAPublicKey(bp_pub, rsa); + BIO *bp_pri = BIO_new_file("server/rsa", "w+"); + PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL); + + BIO_free_all(bp_pub); + BIO_free_all(bp_pri); + QFile("server/rsa").setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + BN_free(bne); + } + FILE *keyFile = fopen("server/rsa_pub", "r"); + PEM_read_RSAPublicKey(keyFile, &rsa, NULL, NULL); + fclose(keyFile); + keyFile = fopen("server/rsa", "r"); + PEM_read_RSAPrivateKey(keyFile, &rsa, NULL, NULL); + fclose(keyFile); + return rsa; +} + void Server::readConfig() { QFile file("freekill.server.config.json"); if (!file.open(QIODevice::ReadOnly)) { diff --git a/src/server/server.h b/src/server/server.h index 701d3a68..015176bb 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -3,6 +3,9 @@ #ifndef _SERVER_H #define _SERVER_H +#include +#include + #include #include class ServerSocket; @@ -67,6 +70,8 @@ private: sqlite3 *db; QString md5; + static RSA *initServerRSA(); + QJsonObject config; void readConfig(); diff --git a/src/ui/mod.cpp b/src/ui/mod.cpp new file mode 100644 index 00000000..3271a92e --- /dev/null +++ b/src/ui/mod.cpp @@ -0,0 +1,230 @@ +#include "mod.h" +#include "git2.h" +#include "util.h" +#include +#include +#include +#include + +ModMaker::ModMaker(QObject *parent) : QObject(parent) { + git_libgit2_init(); +#ifdef Q_OS_ANDROID + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, "./certs"); +#endif + + if (!QDir("mymod").exists()) { + QDir(".").mkdir("mymod"); + } + + db = OpenDatabase("mymod/packages.db", "packages/mymod.sql"); +} + +ModMaker::~ModMaker() { + // git_libgit2_shutdown(); + sqlite3_close(db); +} + +// copied from https://stackoverflow.com/questions/1011572/convert-pem-key-to-ssh-rsa-format +static unsigned char pSshHeader[11] = { 0x00, 0x00, 0x00, 0x07, 0x73, 0x73, 0x68, 0x2D, 0x72, 0x73, 0x61}; + +static int SshEncodeBuffer(unsigned char *pEncoding, int bufferLen, unsigned char* pBuffer) { + int adjustedLen = bufferLen, index; + if (*pBuffer & 0x80) { + adjustedLen++; + pEncoding[4] = 0; + index = 5; + } else { + index = 4; + } + + pEncoding[0] = (unsigned char) (adjustedLen >> 24); + pEncoding[1] = (unsigned char) (adjustedLen >> 16); + pEncoding[2] = (unsigned char) (adjustedLen >> 8); + pEncoding[3] = (unsigned char) (adjustedLen ); + memcpy(&pEncoding[index], pBuffer, bufferLen); + return index + bufferLen; +} + +static void initSSHKeyPair() { + if (!QFile::exists("mymod/id_rsa.pub")) { + RSA *rsa = RSA_new(); + BIGNUM *bne = BN_new(); + BN_set_word(bne, RSA_F4); + RSA_generate_key_ex(rsa, 3072, bne, NULL); + + BIO *bp_pri = BIO_new_file("mymod/id_rsa", "w"); + PEM_write_bio_RSAPrivateKey(bp_pri, rsa, NULL, NULL, 0, NULL, NULL); + BIO_free_all(bp_pri); + QFile("mymod/id_rsa").setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + + auto n = RSA_get0_n(rsa); + auto e = RSA_get0_e(rsa); + auto nLen = BN_num_bytes(n); + auto eLen = BN_num_bytes(e); + auto nBytes = (unsigned char *)malloc(nLen); + auto eBytes = (unsigned char *)malloc(eLen); + BN_bn2bin(n, nBytes); + BN_bn2bin(e, eBytes); + + auto encodingLength = 11 + 4 + eLen + 4 + nLen; + // correct depending on the MSB of e and N + if (eBytes[0] & 0x80) + encodingLength++; + if (nBytes[0] & 0x80) + encodingLength++; + + auto pEncoding = (unsigned char *)malloc(encodingLength); + memcpy(pEncoding, pSshHeader, 11); + int index = 0; + index = SshEncodeBuffer(&pEncoding[11], eLen, eBytes); + index = SshEncodeBuffer(&pEncoding[11 + index], nLen, nBytes); + + auto b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + auto bio = BIO_new_file("mymod/id_rsa.pub", "w"); + BIO_printf(bio, "ssh-rsa "); + bio = BIO_push(b64, bio); + BIO_write(bio, pEncoding, encodingLength); + BIO_flush(bio); + bio = BIO_pop(b64); + BIO_printf(bio, " FreeKill\n"); + BIO_flush(bio); + + BIO_free_all(bio); + BIO_free_all(b64); + BN_free(bne); + RSA_free(rsa); + } +} + +void ModMaker::initKey() { initSSHKeyPair(); } + +QString ModMaker::readFile(const QString &fileName) { + QFile conf(fileName); + if (!conf.exists()) { + conf.open(QIODevice::WriteOnly); + static const char *init_conf = "{}"; + conf.write(init_conf); + conf.close(); + return init_conf; + } + conf.open(QIODevice::ReadOnly); + QString ret = conf.readAll(); + conf.close(); + return ret; +} + +void ModMaker::saveToFile(const QString &fName, const QString &content) { + QFile c(fName); + c.open(QIODevice::WriteOnly); + c.write(content.toUtf8()); + c.close(); +} + +void ModMaker::createMod(const QString &name) { + init(name); +} + +void ModMaker::commitChanges(const QString &name, const QString &msg, + const QString &user, const QString &email) +{ + auto userBytes = user.toUtf8(); + auto emailBytes = email.toUtf8(); + commit(name, msg, userBytes, emailBytes); +} + +#define GIT_FAIL \ + const git_error *e = git_error_last(); \ + qCritical("Error %d/%d: %s\n", error, e->klass, e->message) + +#define GIT_CHK(s) do { \ + error = (s); \ + if (error < 0) { \ + GIT_FAIL; \ + goto clean; \ + }} while (0) + +static int fk_cred_cb(git_cred **out, const char *url, const char *name, + unsigned int allowed_types, void *payload) +{ + initSSHKeyPair(); + return git_cred_ssh_key_new(out, "git", "mymod/id_rsa.pub", "mymod/id_rsa", ""); +} + +int ModMaker::init(const QString &pkg) { + QString path = "mymod/" + pkg; + int error; + git_repository *repo = NULL; + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + opts.flags |= GIT_REPOSITORY_INIT_MKPATH; /* mkdir as needed to create repo */ + error = git_repository_init_ext(&repo, path.toLatin1().constData(), &opts); + if (error < 0) { + GIT_FAIL; + } + git_repository_free(repo); + return error; +} + +int ModMaker::add(const QString &pkg) { + QString path = "mymod/" + pkg; + int error; + git_repository *repo = NULL; + git_index *index = NULL; + + GIT_CHK(git_repository_open(&repo, path.toLatin1())); + GIT_CHK(git_repository_index(&index, repo)); + GIT_CHK(git_index_add_all(index, NULL, GIT_INDEX_ADD_DEFAULT, NULL, NULL)); + GIT_CHK(git_index_write(index)); + +clean: + git_repository_free(repo); + git_index_free(index); + return error; +} + +int ModMaker::commit(const QString &pkg, const QString &msg, const char *user, const char *email) { + QString path = "mymod/" + pkg; + int error; + git_repository *repo = NULL; + git_oid commit_oid,tree_oid; + git_tree *tree; + git_index *index; + git_object *parent = NULL; + git_reference *ref = NULL; + git_signature *signature; + + GIT_CHK(git_repository_open(&repo, path.toLatin1())); + error = git_revparse_ext(&parent, &ref, repo, "HEAD"); + if (error == GIT_ENOTFOUND) { + // printf("HEAD not found. Creating first commit\n"); + error = 0; + } else if (error != 0) { + GIT_FAIL; + goto clean; + } + + GIT_CHK(git_repository_index(&index, repo)); + GIT_CHK(git_index_write_tree(&tree_oid, index)); + GIT_CHK(git_index_write(index)); + GIT_CHK(git_tree_lookup(&tree, repo, &tree_oid)); + GIT_CHK(git_signature_now(&signature, user, email)); + GIT_CHK(git_commit_create_v( + &commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + msg.toUtf8(), + tree, + parent ? 1 : 0, parent)); + +clean: + git_repository_free(repo); + git_index_free(index); + git_signature_free(signature); + git_tree_free(tree); + git_object_free(parent); + git_reference_free(ref); + return error; +} diff --git a/src/ui/mod.h b/src/ui/mod.h new file mode 100644 index 00000000..9e21679c --- /dev/null +++ b/src/ui/mod.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef _DIY_H +#define _DIY_H + +#include +class ModMaker : public QObject { + Q_OBJECT +public: + ModMaker(QObject *parent = nullptr); + ~ModMaker(); + + Q_INVOKABLE void initKey(); + + Q_INVOKABLE QString readFile(const QString &fileName); + Q_INVOKABLE void saveToFile(const QString &fileName, const QString &content); + + Q_INVOKABLE void createMod(const QString &name); + Q_INVOKABLE void stageFiles(const QString &name) { add(name); } + Q_INVOKABLE void commitChanges(const QString &name, const QString &msg, + const QString &user, const QString &email); + +private: + sqlite3 *db; + + // git functions + int init(const QString &pkg); + int add(const QString &pkg); + int commit(const QString &pkg, const QString &msg, const char *user, const char *email); +}; + +#endif diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 3c5c41c6..53b2b2f4 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -9,6 +9,7 @@ #include #include +#include "mod.h" #endif #include @@ -227,10 +228,13 @@ QString QmlBackend::loadConf() { conf.open(QIODevice::WriteOnly); static const char *init_conf = "{}"; conf.write(init_conf); + conf.close(); return init_conf; } conf.open(QIODevice::ReadOnly); - return conf.readAll(); + auto ret = conf.readAll(); + conf.close(); + return ret; } QString QmlBackend::loadTips() { @@ -239,16 +243,20 @@ QString QmlBackend::loadTips() { conf.open(QIODevice::WriteOnly); static const char *init_conf = "转啊~ 转啊~"; conf.write(init_conf); + conf.close(); return init_conf; } conf.open(QIODevice::ReadOnly); - return conf.readAll(); + auto ret = conf.readAll(); + conf.close(); + return ret; } void QmlBackend::saveConf(const QString &conf) { QFile c("freekill.client.config.json"); c.open(QIODevice::WriteOnly); c.write(conf.toUtf8()); + c.close(); } void QmlBackend::replyDelayTest(const QString &screenName, @@ -308,4 +316,8 @@ void QmlBackend::installAESKey() { ClientInstance->installAESKey(aes_key.toLatin1()); } +void QmlBackend::createModBackend() { + engine->rootContext()->setContextProperty("ModBackend", new ModMaker); +} + #endif diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index 3c49dc5a..46d21820 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -3,6 +3,9 @@ #ifndef _QMLBACKEND_H #define _QMLBACKEND_H +#include +#include + #include class QmlBackend : public QObject { Q_OBJECT @@ -50,6 +53,8 @@ public: Q_INVOKABLE QString getAESKey() const; Q_INVOKABLE void installAESKey(); + Q_INVOKABLE void createModBackend(); + qreal volume() const { return m_volume; } void setVolume(qreal v) { m_volume = v; }