parent
375147afa1
commit
48f3ae3ecd
|
@ -16,6 +16,7 @@ tags
|
|||
|
||||
# file produced by game
|
||||
/FreeKill
|
||||
/mymod
|
||||
FreeKill.exe
|
||||
freekill-wrap.cxx
|
||||
server/users.db
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
module Fk.ModMaker
|
||||
ModMaker 1.0 main.qml
|
|
@ -54,6 +54,7 @@ Item {
|
|||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.MarkdownText
|
||||
font.pixelSize: 18
|
||||
onLinkActivated: Qt.openUrlExternally(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import Fk.ModMaker as Md
|
||||
|
||||
Md.ModMaker {}
|
|
@ -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
|
||||
|
|
|
@ -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 {} }
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 330 B |
Binary file not shown.
After Width: | Height: | Size: 358 B |
Binary file not shown.
After Width: | Height: | Size: 351 B |
Binary file not shown.
After Width: | Height: | Size: 414 B |
|
@ -79,6 +79,10 @@
|
|||
<source>PackageManage</source>
|
||||
<translation>管理拓展包</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mod Making</source>
|
||||
<translation>制作Mod</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome back!</source>
|
||||
<translation>欢迎回来!</translation>
|
||||
|
@ -212,4 +216,67 @@
|
|||
<translation>已复制。</translation>
|
||||
</message>
|
||||
</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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
|
|||
list(APPEND freekill_SRCS
|
||||
"client/client.cpp"
|
||||
"client/clientplayer.cpp"
|
||||
"ui/mod.cpp"
|
||||
)
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -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 + ';');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,9 +14,7 @@
|
|||
typedef int LuaFunction;
|
||||
#include "lua.hpp"
|
||||
#include "sqlite3.h"
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#define OPENSSL_API_COMPAT 0x10101000L
|
||||
|
||||
#if !defined (Q_OS_ANDROID) && !defined (Q_OS_WASM)
|
||||
#define DESKTOP_BUILD
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#ifndef _SERVER_H
|
||||
#define _SERVER_H
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <qjsonobject.h>
|
||||
#include <qjsonvalue.h>
|
||||
class ServerSocket;
|
||||
|
@ -67,6 +70,8 @@ private:
|
|||
sqlite3 *db;
|
||||
QString md5;
|
||||
|
||||
static RSA *initServerRSA();
|
||||
|
||||
QJsonObject config;
|
||||
void readConfig();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <QClipboard>
|
||||
#include <QMediaPlayer>
|
||||
#include "mod.h"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
|
@ -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
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#ifndef _QMLBACKEND_H
|
||||
#define _QMLBACKEND_H
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include <qtmetamacros.h>
|
||||
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; }
|
||||
|
||||
|
|
Loading…
Reference in New Issue