ModMaker start (#163)

做了个Mod制作器的壳
修手气卡bug
修旁观/重连看不到标记
优化Card元表实现
This commit is contained in:
notify 2023-05-26 20:53:26 +08:00 committed by GitHub
parent 375147afa1
commit 48f3ae3ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 753 additions and 63 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ tags
# file produced by game # file produced by game
/FreeKill /FreeKill
/mymod
FreeKill.exe FreeKill.exe
freekill-wrap.cxx freekill-wrap.cxx
server/users.db server/users.db

View File

@ -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();
}
}
}

36
Fk/ModMaker/ModConfig.qml Normal file
View File

@ -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();
}
}

113
Fk/ModMaker/ModInit.qml Normal file
View File

@ -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);
}
}

75
Fk/ModMaker/UserInfo.qml Normal file
View File

@ -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"));
}
}
}

55
Fk/ModMaker/main.qml Normal file
View File

@ -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);
}
}

2
Fk/ModMaker/qmldir Normal file
View File

@ -0,0 +1,2 @@
module Fk.ModMaker
ModMaker 1.0 main.qml

View File

@ -54,6 +54,7 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
textFormat: Text.MarkdownText textFormat: Text.MarkdownText
font.pixelSize: 18 font.pixelSize: 18
onLinkActivated: Qt.openUrlExternally(link);
} }
} }
} }

View File

@ -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() { function downloadComplete() {
toast.show(qsTr("updated packages for md5")); toast.show(qsTr("updated packages for md5"));
} }

3
Fk/Pages/ModMaker.qml Normal file
View File

@ -0,0 +1,3 @@
import Fk.ModMaker as Md
Md.ModMaker {}

View File

@ -10,3 +10,4 @@ ModesOverview 1.0 ModesOverview.qml
PackageManage 1.0 PackageManage.qml PackageManage 1.0 PackageManage.qml
Room 1.0 Room.qml Room 1.0 Room.qml
TileButton 1.0 TileButton.qml TileButton 1.0 TileButton.qml
ModMaker 1.0 ModMaker.qml

View File

@ -51,6 +51,7 @@ Item {
Component { id: init; Init {} } Component { id: init; Init {} }
Component { id: packageManage; PackageManage {} } Component { id: packageManage; PackageManage {} }
Component { id: modMaker; ModMaker {} }
Component { id: lobby; Lobby {} } Component { id: lobby; Lobby {} }
Component { id: generalsOverview; GeneralsOverview {} } Component { id: generalsOverview; GeneralsOverview {} }
Component { id: cardsOverview; CardsOverview {} } Component { id: cardsOverview; CardsOverview {} }

BIN
image/modmaker/add.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

BIN
image/modmaker/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
image/modmaker/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

BIN
image/modmaker/ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

View File

@ -79,6 +79,10 @@
<source>PackageManage</source> <source>PackageManage</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Mod Making</source>
<translation>Mod</translation>
</message>
<message> <message>
<source>Welcome back!</source> <source>Welcome back!</source>
<translation></translation> <translation></translation>
@ -212,4 +216,67 @@
<translation></translation> <translation></translation>
</message> </message>
</context> </context>
<context>
<name>ModInit</name>
<message>
<source>ModMaker</source>
<translation>Mod制作器 - </translation>
</message>
<message>
<source>config is incomplete</source>
<translation>
Mod制作器还未正确配置
</translation>
</message>
<message>
<source>cannot use this mod name</source>
<translation>mod取这个名字</translation>
</message>
</context>
<context>
<name>UserInfo</name>
<message>
<source>help_text</source>
<translation>git服务器上相同的名字和邮箱</translation>
</message>
<message>
<source>username</source>
<translation></translation>
</message>
<message>
<source>email</source>
<translation></translation>
</message>
<message>
<source>key_help_text</source>
<translation>git服务器证明身份的手段SSH密钥Mod制作器教程</translation>
</message>
<message>
<source>copy pubkey</source>
<translation></translation>
</message>
<message>
<source>pubkey copied</source>
<translation></translation>
</message>
</context>
<context>
<name>CreateSomething</name>
<message>
<source>validator_hint</source>
<translation>线4</translation>
</message>
<message>
<source>create_mod</source>
<translation>Mod</translation>
</message>
<message>
<source>create_mod_hint</source>
<translation>mod的名称</translation>
</message>
</context>
</TS> </TS>

View File

@ -86,13 +86,13 @@ function Card:initialize(name, suit, number, color)
self.color = Card.NoColor self.color = Card.NoColor
end end
self.package = nil -- self.package = nil
self.id = 0 self.id = 0
self.type = 0 self.type = 0
self.sub_type = Card.SubTypeNone self.sub_type = Card.SubTypeNone
self.skill = nil -- self.skill = nil
self.subcards = {} self.subcards = {}
self.skillName = nil -- "" -- self.skillName = nil
self._skillName = "" self._skillName = ""
self.skillNames = {} self.skillNames = {}
self.mark = {} self.mark = {}
@ -101,33 +101,23 @@ function Card:initialize(name, suit, number, color)
self.name = string.sub(name, 2, #name) self.name = string.sub(name, 2, #name)
self.is_derived = true self.is_derived = true
end end
end
local mt = table.simpleClone(getmetatable(self)) function Card:__index(k)
local newidx = mt.__newindex or rawset if k == "skillName" then
mt.__newindex = function(t, k, v) return self._skillName
end
end
function Card:__newindex(k, v)
if k == "skillName" then if k == "skillName" then
table.insertIfNeed(self.skillNames, v) table.insertIfNeed(self.skillNames, v)
t._skillName = v self._skillName = v
else else
return newidx(t, k, v) rawset(self, k, v)
end end
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
end
setmetatable(self, mt)
end
function Card:__tostring() function Card:__tostring()
return string.format("<Card %s[%s %d]>", self.name, self:getSuitString(), self.number) return string.format("<Card %s[%s %d]>", self.name, self:getSuitString(), self.number)
end end

View File

@ -90,6 +90,7 @@ request_handlers["luckcard"] = function(room, id, reqlist)
local p = room:getPlayerById(id) local p = room:getPlayerById(id)
local cancel = reqlist[3] == "false" local cancel = reqlist[3] == "false"
local luck_data = room:getTag("LuckCardData") local luck_data = room:getTag("LuckCardData")
if not (p and luck_data) then return end
local pdata = luck_data[id] local pdata = luck_data[id]
if not cancel then if not cancel then

View File

@ -243,7 +243,11 @@ function ServerPlayer:marshal(player)
room:notifyMoveCards({ player }, card_moves) room:notifyMoveCards({ player }, card_moves)
end 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 for _, s in ipairs(self.player_skills) do
player:doNotify("AddSkill", json.encode{self.id, s.name}) player:doNotify("AddSkill", json.encode{self.id, s.name})

View File

@ -1043,7 +1043,7 @@ local wushuang = fk.CreateTriggerSkill{
end end
if event == fk.TargetSpecified then 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 else
return data.to == player.id and data.card.name == "duel" return data.to == player.id and data.card.name == "duel"
end end

View File

@ -19,6 +19,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
list(APPEND freekill_SRCS list(APPEND freekill_SRCS
"client/client.cpp" "client/client.cpp"
"client/clientplayer.cpp" "client/clientplayer.cpp"
"ui/mod.cpp"
) )
endif () endif ()

View File

@ -139,33 +139,6 @@ void ExecSQL(sqlite3 *db, const QString &sql) {
void CloseDatabase(sqlite3 *db) { sqlite3_close(db); } 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) { static void writeFileMD5(QFile &dest, const QString &fname) {
QFile f(fname); QFile f(fname);
if (!f.open(QIODevice::ReadOnly)) { if (!f.open(QIODevice::ReadOnly)) {
@ -173,6 +146,7 @@ static void writeFileMD5(QFile &dest, const QString &fname) {
} }
auto data = f.readAll(); auto data = f.readAll();
f.close();
data.replace(QByteArray("\r\n"), QByteArray("\n")); data.replace(QByteArray("\r\n"), QByteArray("\n"));
auto hash = QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex(); auto hash = QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
dest.write(fname.toUtf8() + '=' + hash + ';'); dest.write(fname.toUtf8() + '=' + hash + ';');

View File

@ -16,10 +16,6 @@ QString SelectFromDb(sqlite3 *db, const QString &sql);
void ExecSQL(sqlite3 *db, const QString &sql); void ExecSQL(sqlite3 *db, const QString &sql);
void CloseDatabase(sqlite3 *db); void CloseDatabase(sqlite3 *db);
#ifndef Q_OS_WASM
RSA *InitServerRSA();
#endif
QString calcFileMD5(); QString calcFileMD5();
QByteArray JsonArray2Bytes(const QJsonArray &arr); QByteArray JsonArray2Bytes(const QJsonArray &arr);
QJsonDocument String2Json(const QString &str); QJsonDocument String2Json(const QString &str);

View File

@ -260,6 +260,7 @@ int main(int argc, char *argv[]) {
// 向 Qml 中先定义几个全局变量 // 向 Qml 中先定义几个全局变量
engine->rootContext()->setContextProperty("FkVersion", FK_VERSION); engine->rootContext()->setContextProperty("FkVersion", FK_VERSION);
engine->rootContext()->setContextProperty("Backend", &backend); engine->rootContext()->setContextProperty("Backend", &backend);
engine->rootContext()->setContextProperty("ModBackend", nullptr);
engine->rootContext()->setContextProperty("Pacman", Pacman); engine->rootContext()->setContextProperty("Pacman", Pacman);
#ifdef QT_DEBUG #ifdef QT_DEBUG

View File

@ -14,9 +14,7 @@
typedef int LuaFunction; typedef int LuaFunction;
#include "lua.hpp" #include "lua.hpp"
#include "sqlite3.h" #include "sqlite3.h"
#define OPENSSL_API_COMPAT 0x10101000L
#include <openssl/rsa.h>
#include <openssl/pem.h>
#if !defined (Q_OS_ANDROID) && !defined (Q_OS_WASM) #if !defined (Q_OS_ANDROID) && !defined (Q_OS_WASM)
#define DESKTOP_BUILD #define DESKTOP_BUILD

View File

@ -22,7 +22,7 @@ Server *ServerInstance;
Server::Server(QObject *parent) : QObject(parent) { Server::Server(QObject *parent) : QObject(parent) {
ServerInstance = this; ServerInstance = this;
db = OpenDatabase(); db = OpenDatabase();
rsa = InitServerRSA(); rsa = initServerRSA();
QFile file("server/rsa_pub"); QFile file("server/rsa_pub");
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
QTextStream in(&file); QTextStream in(&file);
@ -431,6 +431,32 @@ void Server::onUserDisconnected() {
void Server::onUserStateChanged() {} 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() { void Server::readConfig() {
QFile file("freekill.server.config.json"); QFile file("freekill.server.config.json");
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {

View File

@ -3,6 +3,9 @@
#ifndef _SERVER_H #ifndef _SERVER_H
#define _SERVER_H #define _SERVER_H
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <qjsonobject.h> #include <qjsonobject.h>
#include <qjsonvalue.h> #include <qjsonvalue.h>
class ServerSocket; class ServerSocket;
@ -67,6 +70,8 @@ private:
sqlite3 *db; sqlite3 *db;
QString md5; QString md5;
static RSA *initServerRSA();
QJsonObject config; QJsonObject config;
void readConfig(); void readConfig();

230
src/ui/mod.cpp Normal file
View File

@ -0,0 +1,230 @@
#include "mod.h"
#include "git2.h"
#include "util.h"
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <qfiledevice.h>
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;
}

32
src/ui/mod.h Normal file
View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef _DIY_H
#define _DIY_H
#include <qtmetamacros.h>
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

View File

@ -9,6 +9,7 @@
#include <QClipboard> #include <QClipboard>
#include <QMediaPlayer> #include <QMediaPlayer>
#include "mod.h"
#endif #endif
#include <cstdlib> #include <cstdlib>
@ -227,10 +228,13 @@ QString QmlBackend::loadConf() {
conf.open(QIODevice::WriteOnly); conf.open(QIODevice::WriteOnly);
static const char *init_conf = "{}"; static const char *init_conf = "{}";
conf.write(init_conf); conf.write(init_conf);
conf.close();
return init_conf; return init_conf;
} }
conf.open(QIODevice::ReadOnly); conf.open(QIODevice::ReadOnly);
return conf.readAll(); auto ret = conf.readAll();
conf.close();
return ret;
} }
QString QmlBackend::loadTips() { QString QmlBackend::loadTips() {
@ -239,16 +243,20 @@ QString QmlBackend::loadTips() {
conf.open(QIODevice::WriteOnly); conf.open(QIODevice::WriteOnly);
static const char *init_conf = "转啊~ 转啊~"; static const char *init_conf = "转啊~ 转啊~";
conf.write(init_conf); conf.write(init_conf);
conf.close();
return init_conf; return init_conf;
} }
conf.open(QIODevice::ReadOnly); conf.open(QIODevice::ReadOnly);
return conf.readAll(); auto ret = conf.readAll();
conf.close();
return ret;
} }
void QmlBackend::saveConf(const QString &conf) { void QmlBackend::saveConf(const QString &conf) {
QFile c("freekill.client.config.json"); QFile c("freekill.client.config.json");
c.open(QIODevice::WriteOnly); c.open(QIODevice::WriteOnly);
c.write(conf.toUtf8()); c.write(conf.toUtf8());
c.close();
} }
void QmlBackend::replyDelayTest(const QString &screenName, void QmlBackend::replyDelayTest(const QString &screenName,
@ -308,4 +316,8 @@ void QmlBackend::installAESKey() {
ClientInstance->installAESKey(aes_key.toLatin1()); ClientInstance->installAESKey(aes_key.toLatin1());
} }
void QmlBackend::createModBackend() {
engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
}
#endif #endif

View File

@ -3,6 +3,9 @@
#ifndef _QMLBACKEND_H #ifndef _QMLBACKEND_H
#define _QMLBACKEND_H #define _QMLBACKEND_H
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
class QmlBackend : public QObject { class QmlBackend : public QObject {
Q_OBJECT Q_OBJECT
@ -50,6 +53,8 @@ public:
Q_INVOKABLE QString getAESKey() const; Q_INVOKABLE QString getAESKey() const;
Q_INVOKABLE void installAESKey(); Q_INVOKABLE void installAESKey();
Q_INVOKABLE void createModBackend();
qreal volume() const { return m_volume; } qreal volume() const { return m_volume; }
void setVolume(qreal v) { m_volume = v; } void setVolume(qreal v) { m_volume = v; }