mirror of
https://github.com/Qsgs-Fans/FreeKill.git
synced 2024-11-15 19:22:25 +08:00
Doxygen and test (#385)
- 删除ModMaker - 拆出main.cpp以便CMake - Doxygen注释,以及CI(没测试过)
This commit is contained in:
parent
354e0ba42e
commit
f0ffd68ff2
16
.github/workflows/sphinx.yml
vendored
16
.github/workflows/sphinx.yml
vendored
|
@ -1,4 +1,4 @@
|
|||
name: Deploy Sphinx documentation to Pages
|
||||
name: Deploy Doxygen to Pages
|
||||
|
||||
# Runs on pushes targeting the default branch
|
||||
on:
|
||||
|
@ -15,5 +15,15 @@ jobs:
|
|||
pages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- id: deployment
|
||||
uses: sphinx-notes/pages@v3
|
||||
- id: build
|
||||
uses: mattnotmitt/doxygen-action@1.9.8
|
||||
with:
|
||||
working-directory: './docs/'
|
||||
doxyfile-path: 'docs/Doxyfile'
|
||||
|
||||
- id: deploy
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.MY_TOKEN }}
|
||||
publish_dir: './docs/build/html'
|
||||
|
||||
|
|
|
@ -77,3 +77,6 @@ add_custom_command(
|
|||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var parentBlock
|
||||
property var childBlocks: [] // nested blocks inside this block
|
||||
property var currentStack: [ root ] // the block stack that root is in
|
||||
property var workspace // workspace
|
||||
|
||||
property bool draggable: false
|
||||
property alias dragging: drag.active
|
||||
property real startX // only available when dragging
|
||||
property real startY
|
||||
|
||||
// TMP
|
||||
property int idx
|
||||
function toString() { return "Block #" + idx.toString(); }
|
||||
// TMP
|
||||
|
||||
Rectangle {
|
||||
id: rect
|
||||
anchors.fill: parent
|
||||
color: drag.active ? "grey" : "snow"
|
||||
border.width: 1
|
||||
radius: 0
|
||||
}
|
||||
|
||||
Text {
|
||||
text: idx
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: drag
|
||||
enabled: root.draggable
|
||||
grabPermissions: PointHandler.TakeOverForbidden
|
||||
xAxis.enabled: true
|
||||
yAxis.enabled: true
|
||||
}
|
||||
|
||||
onDraggingChanged: {
|
||||
if (!dragging) {
|
||||
finishDrag();
|
||||
} else {
|
||||
startDrag();
|
||||
}
|
||||
}
|
||||
|
||||
onXChanged: {
|
||||
if (dragging) {
|
||||
updateChildrenPos();
|
||||
}
|
||||
}
|
||||
|
||||
onYChanged: {
|
||||
if (dragging) {
|
||||
updateChildrenPos();
|
||||
}
|
||||
}
|
||||
|
||||
function getStackParent() {
|
||||
const idx = currentStack.indexOf(root);
|
||||
if (idx <= 0) {
|
||||
return null;
|
||||
}
|
||||
return currentStack[idx - 1];
|
||||
}
|
||||
|
||||
function getStackChildren() {
|
||||
const idx = currentStack.indexOf(root);
|
||||
if (idx >= currentStack.length - 1) {
|
||||
return [];
|
||||
}
|
||||
return currentStack.slice(idx + 1);
|
||||
}
|
||||
|
||||
function startDrag() {
|
||||
startX = x;
|
||||
startY = y;
|
||||
let children = getStackChildren();
|
||||
children.push(...childBlocks);
|
||||
children.forEach(b => {
|
||||
b.startX = b.x;
|
||||
b.startY = b.y;
|
||||
});
|
||||
}
|
||||
|
||||
function updateChildrenPos() {
|
||||
const dx = root.x - root.startX;
|
||||
const dy = root.y - root.startY;
|
||||
let children = getStackChildren();
|
||||
children.push(...childBlocks);
|
||||
children.forEach(b => {
|
||||
b.x = b.startX + dx;
|
||||
b.y = b.startY + dy;
|
||||
});
|
||||
}
|
||||
|
||||
function finishDrag() {
|
||||
if (currentStack[0] !== root) {
|
||||
tearFrom(getStackParent());
|
||||
}
|
||||
|
||||
if (parentBlock) {
|
||||
tearFrom(parentBlock);
|
||||
}
|
||||
|
||||
if (workspace) {
|
||||
workspace.arrangeBlock(root);
|
||||
}
|
||||
}
|
||||
|
||||
function pasteTo(dest, asParent) {
|
||||
x = dest.x;
|
||||
y = dest.y + dest.height;
|
||||
updateChildrenPos();
|
||||
|
||||
if (!asParent) {
|
||||
const stk = currentStack;
|
||||
dest.currentStack.push(...stk);
|
||||
|
||||
const p = dest.parentBlock;
|
||||
let c = getStackChildren();
|
||||
c.push(root);
|
||||
c.forEach(cc => {
|
||||
cc.parentBlock = p;
|
||||
cc.currentStack = dest.currentStack;
|
||||
});
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
function tearFrom(dest) {
|
||||
const fromParent = dest === root.parentBlock;
|
||||
if (!fromParent) {
|
||||
const idx = currentStack.indexOf(root);
|
||||
const newStack = currentStack.slice(idx);
|
||||
let c = getStackChildren();
|
||||
|
||||
currentStack.splice(idx);
|
||||
|
||||
c.push(root);
|
||||
c.forEach(cc => {
|
||||
cc.parentBlock = null;
|
||||
cc.currentStack = newStack;
|
||||
});
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var blockComponent
|
||||
property var allBlocks: []
|
||||
|
||||
// ====== TMP ======
|
||||
property int idx: 0
|
||||
Row {
|
||||
Button {
|
||||
text: "quit"
|
||||
onClicked: modStack.pop();
|
||||
}
|
||||
Button {
|
||||
text: "New"
|
||||
onClicked: newBlock();
|
||||
}
|
||||
Button {
|
||||
text: "Del"
|
||||
onClicked: rmFirstBlock_();
|
||||
}
|
||||
}
|
||||
|
||||
function newBlock() {
|
||||
let obj = blockComponent.createObject(root, {
|
||||
width: 50, height: 50,
|
||||
x: Math.random() * root.width, y: Math.random() * root.height,
|
||||
workspace: root, draggable: true,
|
||||
idx: ++idx,
|
||||
});
|
||||
allBlocks.push(obj);
|
||||
}
|
||||
|
||||
function rmFirstBlock_() {
|
||||
let obj = allBlocks[0];
|
||||
if (!obj) return;
|
||||
obj.destroy();
|
||||
allBlocks.splice(0,1);
|
||||
}
|
||||
// ====== TMP ======
|
||||
|
||||
function getPasteBlock(block) {
|
||||
let ret;
|
||||
let min = Infinity;
|
||||
const x = block.x;
|
||||
const y = block.y;
|
||||
allBlocks.forEach(b => {
|
||||
if (b === block) return;
|
||||
let dx = Math.abs(b.x - x);
|
||||
let dy = y - b.y - b.height;
|
||||
let tot = dx + dy;
|
||||
if (dx < 60 && dy < 60 && dy > 0 && tot < 100) {
|
||||
if (min > tot) {
|
||||
if (!allBlocks.find(bb => bb.x === b.x && bb.y === b.y + b.height)) {
|
||||
ret = b;
|
||||
min = tot;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function showPasteBlock(block) {
|
||||
}
|
||||
|
||||
function arrangeBlock(block) {
|
||||
let b = getPasteBlock(block);
|
||||
if (b) {
|
||||
block.pasteTo(b);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
blockComponent = Qt.createComponent('Block.qml');
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
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.WordWrap
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr(hint)
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Text {
|
||||
text: qsTr("validator_hint")
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var mod: ({})
|
||||
property string modName
|
||||
property string modPath: "mymod/" + modName + "/"
|
||||
|
||||
onModNameChanged: {
|
||||
mod = JSON.parse(ModBackend.readFile(modPath + "mod.json"));
|
||||
}
|
||||
|
||||
ToolBar {
|
||||
id: bar
|
||||
width: parent.width
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
ToolButton {
|
||||
icon.source: AppPath + "/image/modmaker/back"
|
||||
onClicked: modStack.pop();
|
||||
}
|
||||
Label {
|
||||
text: qsTr("ModMaker") + " - " + modName
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ToolButton {
|
||||
icon.source: AppPath + "/image/modmaker/menu"
|
||||
onClicked: {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height - bar.height
|
||||
anchors.top: bar.bottom
|
||||
color: "snow"
|
||||
opacity: 0.75
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: mod.packages ?? []
|
||||
delegate: SwipeDelegate {
|
||||
width: root.width
|
||||
text: modelData
|
||||
|
||||
swipe.right: Label {
|
||||
id: deleteLabel
|
||||
text: qsTr("Delete")
|
||||
color: "white"
|
||||
verticalAlignment: Label.AlignVCenter
|
||||
padding: 12
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
opacity: swipe.complete ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { } }
|
||||
|
||||
SwipeDelegate.onClicked: deletePackage(modelData);
|
||||
|
||||
background: Rectangle {
|
||||
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1)
|
||||
: "tomato"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RoundButton {
|
||||
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_package";
|
||||
dialog.item.hint = "create_package_hint";
|
||||
drawer.open();
|
||||
dialog.item.accepted.connect((name) => {
|
||||
createNewPackage(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 createNewPackage(name) {
|
||||
const new_name = modName + "_" + name;
|
||||
mod.packages = mod.packages ?? [];
|
||||
if (mod.packages.indexOf(new_name) !== -1) {
|
||||
toast.show(qsTr("cannot use this package name"));
|
||||
return;
|
||||
}
|
||||
|
||||
const path = modPath + new_name + "/";
|
||||
ModBackend.mkdir(path);
|
||||
mod.packages.push(new_name);
|
||||
ModBackend.saveToFile(modPath + "mod.json",
|
||||
JSON.stringify(mod, undefined, 2));
|
||||
|
||||
const pkgInfo = {
|
||||
name: new_name,
|
||||
};
|
||||
ModBackend.saveToFile(path + "pkg.json",
|
||||
JSON.stringify(pkgInfo, undefined, 2));
|
||||
|
||||
root.modChanged();
|
||||
}
|
||||
|
||||
function deletePackage(name) {
|
||||
const path = modPath + name + "/";
|
||||
ModBackend.rmrf(path);
|
||||
mod.packages.splice(mod.packages.indexOf(name), 1);
|
||||
ModBackend.saveToFile(modPath + "mod.json",
|
||||
JSON.stringify(mod, undefined, 2));
|
||||
|
||||
root.modChanged();
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
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
|
||||
}
|
||||
Button {
|
||||
text: "Test"
|
||||
onClicked: {
|
||||
const component = Qt.createComponent("Block/Workspace.qml");
|
||||
if (component.status !== Component.Ready) {
|
||||
return;
|
||||
}
|
||||
const page = component.createObject(null);
|
||||
modStack.push(page);
|
||||
}
|
||||
}
|
||||
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
|
||||
clip: true
|
||||
delegate: SwipeDelegate {
|
||||
width: root.width
|
||||
text: modelData
|
||||
|
||||
onClicked: {
|
||||
const component = Qt.createComponent("ModDetail.qml");
|
||||
if (component.status !== Component.Ready) {
|
||||
return;
|
||||
}
|
||||
const page = component.createObject(null, { modName: modelData });
|
||||
modStack.push(page);
|
||||
}
|
||||
|
||||
swipe.right: Label {
|
||||
id: deleteLabel
|
||||
text: qsTr("Delete")
|
||||
color: "white"
|
||||
verticalAlignment: Label.AlignVCenter
|
||||
padding: 12
|
||||
height: parent.height
|
||||
anchors.right: parent.right
|
||||
opacity: swipe.complete ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { } }
|
||||
|
||||
SwipeDelegate.onClicked: deleteMod(modelData);
|
||||
|
||||
background: Rectangle {
|
||||
color: deleteLabel.SwipeDelegate.pressed ? Qt.darker("tomato", 1.1)
|
||||
: "tomato"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function deleteMod(name) {
|
||||
ModBackend.removeMod(name);
|
||||
modConfig.removeMod(name);
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// 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.WordWrap
|
||||
}
|
||||
|
||||
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.WordWrap
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
module Fk.ModMaker
|
||||
ModMaker 1.0 main.qml
|
364
docs/Doxyfile
Normal file
364
docs/Doxyfile
Normal file
|
@ -0,0 +1,364 @@
|
|||
# Doxyfile 1.12.0 - comments removed
|
||||
|
||||
DOXYFILE_ENCODING = UTF-8
|
||||
PROJECT_NAME = "FreeKill"
|
||||
PROJECT_NUMBER =
|
||||
PROJECT_BRIEF =
|
||||
PROJECT_LOGO =
|
||||
PROJECT_ICON =
|
||||
OUTPUT_DIRECTORY = build
|
||||
CREATE_SUBDIRS = NO
|
||||
CREATE_SUBDIRS_LEVEL = 8
|
||||
ALLOW_UNICODE_NAMES = NO
|
||||
OUTPUT_LANGUAGE = Chinese
|
||||
BRIEF_MEMBER_DESC = YES
|
||||
REPEAT_BRIEF = NO
|
||||
ABBREVIATE_BRIEF = "The $name class" \
|
||||
"The $name widget" \
|
||||
"The $name file" \
|
||||
is \
|
||||
provides \
|
||||
specifies \
|
||||
contains \
|
||||
represents \
|
||||
a \
|
||||
an \
|
||||
the
|
||||
|
||||
ALWAYS_DETAILED_SEC = NO
|
||||
INLINE_INHERITED_MEMB = NO
|
||||
FULL_PATH_NAMES = NO
|
||||
STRIP_FROM_PATH =
|
||||
STRIP_FROM_INC_PATH =
|
||||
SHORT_NAMES = NO
|
||||
JAVADOC_AUTOBRIEF = NO
|
||||
JAVADOC_BANNER = NO
|
||||
QT_AUTOBRIEF = NO
|
||||
MULTILINE_CPP_IS_BRIEF = NO
|
||||
PYTHON_DOCSTRING = YES
|
||||
INHERIT_DOCS = YES
|
||||
SEPARATE_MEMBER_PAGES = NO
|
||||
TAB_SIZE = 4
|
||||
ALIASES =
|
||||
OPTIMIZE_OUTPUT_FOR_C = NO
|
||||
OPTIMIZE_OUTPUT_JAVA = NO
|
||||
OPTIMIZE_FOR_FORTRAN = NO
|
||||
OPTIMIZE_OUTPUT_VHDL = NO
|
||||
OPTIMIZE_OUTPUT_SLICE = NO
|
||||
EXTENSION_MAPPING =
|
||||
MARKDOWN_SUPPORT = YES
|
||||
TOC_INCLUDE_HEADINGS = 6
|
||||
MARKDOWN_ID_STYLE = DOXYGEN
|
||||
AUTOLINK_SUPPORT = YES
|
||||
BUILTIN_STL_SUPPORT = NO
|
||||
CPP_CLI_SUPPORT = NO
|
||||
SIP_SUPPORT = NO
|
||||
IDL_PROPERTY_SUPPORT = YES
|
||||
DISTRIBUTE_GROUP_DOC = NO
|
||||
GROUP_NESTED_COMPOUNDS = NO
|
||||
SUBGROUPING = YES
|
||||
INLINE_GROUPED_CLASSES = NO
|
||||
INLINE_SIMPLE_STRUCTS = NO
|
||||
TYPEDEF_HIDES_STRUCT = NO
|
||||
LOOKUP_CACHE_SIZE = 0
|
||||
NUM_PROC_THREADS = 4
|
||||
TIMESTAMP = YES
|
||||
EXTRACT_ALL = NO
|
||||
EXTRACT_PRIVATE = YES
|
||||
EXTRACT_PRIV_VIRTUAL = NO
|
||||
EXTRACT_PACKAGE = NO
|
||||
EXTRACT_STATIC = NO
|
||||
EXTRACT_LOCAL_CLASSES = YES
|
||||
EXTRACT_LOCAL_METHODS = NO
|
||||
EXTRACT_ANON_NSPACES = NO
|
||||
RESOLVE_UNNAMED_PARAMS = YES
|
||||
HIDE_UNDOC_MEMBERS = NO
|
||||
HIDE_UNDOC_CLASSES = NO
|
||||
HIDE_FRIEND_COMPOUNDS = NO
|
||||
HIDE_IN_BODY_DOCS = NO
|
||||
INTERNAL_DOCS = NO
|
||||
CASE_SENSE_NAMES = SYSTEM
|
||||
HIDE_SCOPE_NAMES = NO
|
||||
HIDE_COMPOUND_REFERENCE= NO
|
||||
SHOW_HEADERFILE = NO
|
||||
SHOW_INCLUDE_FILES = NO
|
||||
SHOW_GROUPED_MEMB_INC = NO
|
||||
FORCE_LOCAL_INCLUDES = NO
|
||||
INLINE_INFO = YES
|
||||
SORT_MEMBER_DOCS = YES
|
||||
SORT_BRIEF_DOCS = NO
|
||||
SORT_MEMBERS_CTORS_1ST = NO
|
||||
SORT_GROUP_NAMES = NO
|
||||
SORT_BY_SCOPE_NAME = NO
|
||||
STRICT_PROTO_MATCHING = NO
|
||||
GENERATE_TODOLIST = YES
|
||||
GENERATE_TESTLIST = YES
|
||||
GENERATE_BUGLIST = YES
|
||||
GENERATE_DEPRECATEDLIST= YES
|
||||
ENABLED_SECTIONS =
|
||||
MAX_INITIALIZER_LINES = 30
|
||||
SHOW_USED_FILES = YES
|
||||
SHOW_FILES = YES
|
||||
SHOW_NAMESPACES = YES
|
||||
FILE_VERSION_FILTER =
|
||||
LAYOUT_FILE =
|
||||
CITE_BIB_FILES =
|
||||
EXTERNAL_TOOL_PATH =
|
||||
QUIET = NO
|
||||
WARNINGS = YES
|
||||
WARN_IF_UNDOCUMENTED = YES
|
||||
WARN_IF_DOC_ERROR = YES
|
||||
WARN_IF_INCOMPLETE_DOC = YES
|
||||
WARN_NO_PARAMDOC = NO
|
||||
WARN_IF_UNDOC_ENUM_VAL = NO
|
||||
WARN_AS_ERROR = NO
|
||||
WARN_FORMAT = "$file:$line: $text"
|
||||
WARN_LINE_FORMAT = "at line $line of file $file"
|
||||
WARN_LOGFILE =
|
||||
INPUT = ../src
|
||||
INPUT_ENCODING = UTF-8
|
||||
INPUT_FILE_ENCODING =
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cxxm \
|
||||
*.cpp \
|
||||
*.cppm \
|
||||
*.ccm \
|
||||
*.c++ \
|
||||
*.c++m \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.idl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.ixx \
|
||||
*.l \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f18 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf \
|
||||
*.ice
|
||||
|
||||
RECURSIVE = YES
|
||||
EXCLUDE = ../src/swig/
|
||||
EXCLUDE_SYMLINKS = NO
|
||||
EXCLUDE_PATTERNS =
|
||||
EXCLUDE_SYMBOLS =
|
||||
EXAMPLE_PATH =
|
||||
EXAMPLE_PATTERNS = *
|
||||
EXAMPLE_RECURSIVE = NO
|
||||
IMAGE_PATH =
|
||||
INPUT_FILTER =
|
||||
FILTER_PATTERNS =
|
||||
FILTER_SOURCE_FILES = NO
|
||||
FILTER_SOURCE_PATTERNS =
|
||||
USE_MDFILE_AS_MAINPAGE =
|
||||
FORTRAN_COMMENT_AFTER = 72
|
||||
SOURCE_BROWSER = NO
|
||||
INLINE_SOURCES = NO
|
||||
STRIP_CODE_COMMENTS = YES
|
||||
REFERENCED_BY_RELATION = NO
|
||||
REFERENCES_RELATION = NO
|
||||
REFERENCES_LINK_SOURCE = YES
|
||||
SOURCE_TOOLTIPS = YES
|
||||
USE_HTAGS = NO
|
||||
VERBATIM_HEADERS = YES
|
||||
CLANG_ASSISTED_PARSING = NO
|
||||
CLANG_ADD_INC_PATHS = YES
|
||||
CLANG_OPTIONS =
|
||||
CLANG_DATABASE_PATH =
|
||||
ALPHABETICAL_INDEX = YES
|
||||
IGNORE_PREFIX =
|
||||
GENERATE_HTML = YES
|
||||
HTML_OUTPUT = html
|
||||
HTML_FILE_EXTENSION = .html
|
||||
HTML_HEADER =
|
||||
HTML_FOOTER =
|
||||
HTML_STYLESHEET =
|
||||
HTML_EXTRA_STYLESHEET =
|
||||
HTML_EXTRA_FILES =
|
||||
HTML_COLORSTYLE = AUTO_LIGHT
|
||||
HTML_COLORSTYLE_HUE = 220
|
||||
HTML_COLORSTYLE_SAT = 100
|
||||
HTML_COLORSTYLE_GAMMA = 80
|
||||
HTML_DYNAMIC_MENUS = YES
|
||||
HTML_DYNAMIC_SECTIONS = NO
|
||||
HTML_CODE_FOLDING = YES
|
||||
HTML_COPY_CLIPBOARD = YES
|
||||
HTML_PROJECT_COOKIE =
|
||||
HTML_INDEX_NUM_ENTRIES = 100
|
||||
GENERATE_DOCSET = NO
|
||||
DOCSET_FEEDNAME = "Doxygen generated docs"
|
||||
DOCSET_FEEDURL =
|
||||
DOCSET_BUNDLE_ID = org.doxygen.Project
|
||||
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
|
||||
DOCSET_PUBLISHER_NAME = Publisher
|
||||
GENERATE_HTMLHELP = NO
|
||||
CHM_FILE =
|
||||
HHC_LOCATION =
|
||||
GENERATE_CHI = NO
|
||||
CHM_INDEX_ENCODING =
|
||||
BINARY_TOC = NO
|
||||
TOC_EXPAND = NO
|
||||
SITEMAP_URL =
|
||||
GENERATE_QHP = NO
|
||||
QCH_FILE =
|
||||
QHP_NAMESPACE = org.doxygen.Project
|
||||
QHP_VIRTUAL_FOLDER = doc
|
||||
QHP_CUST_FILTER_NAME =
|
||||
QHP_CUST_FILTER_ATTRS =
|
||||
QHP_SECT_FILTER_ATTRS =
|
||||
QHG_LOCATION =
|
||||
GENERATE_ECLIPSEHELP = NO
|
||||
ECLIPSE_DOC_ID = org.doxygen.Project
|
||||
DISABLE_INDEX = NO
|
||||
GENERATE_TREEVIEW = YES
|
||||
FULL_SIDEBAR = NO
|
||||
ENUM_VALUES_PER_LINE = 4
|
||||
SHOW_ENUM_VALUES = NO
|
||||
TREEVIEW_WIDTH = 250
|
||||
EXT_LINKS_IN_WINDOW = NO
|
||||
OBFUSCATE_EMAILS = YES
|
||||
HTML_FORMULA_FORMAT = png
|
||||
FORMULA_FONTSIZE = 10
|
||||
FORMULA_MACROFILE =
|
||||
USE_MATHJAX = NO
|
||||
MATHJAX_VERSION = MathJax_2
|
||||
MATHJAX_FORMAT = HTML-CSS
|
||||
MATHJAX_RELPATH =
|
||||
MATHJAX_EXTENSIONS =
|
||||
MATHJAX_CODEFILE =
|
||||
SEARCHENGINE = YES
|
||||
SERVER_BASED_SEARCH = NO
|
||||
EXTERNAL_SEARCH = NO
|
||||
SEARCHENGINE_URL =
|
||||
SEARCHDATA_FILE = searchdata.xml
|
||||
EXTERNAL_SEARCH_ID =
|
||||
EXTRA_SEARCH_MAPPINGS =
|
||||
GENERATE_LATEX = YES
|
||||
LATEX_OUTPUT = latex
|
||||
LATEX_CMD_NAME =
|
||||
MAKEINDEX_CMD_NAME = makeindex
|
||||
LATEX_MAKEINDEX_CMD = makeindex
|
||||
COMPACT_LATEX = NO
|
||||
PAPER_TYPE = a4
|
||||
EXTRA_PACKAGES =
|
||||
LATEX_HEADER =
|
||||
LATEX_FOOTER =
|
||||
LATEX_EXTRA_STYLESHEET =
|
||||
LATEX_EXTRA_FILES =
|
||||
PDF_HYPERLINKS = YES
|
||||
USE_PDFLATEX = YES
|
||||
LATEX_BATCHMODE = NO
|
||||
LATEX_HIDE_INDICES = NO
|
||||
LATEX_BIB_STYLE = plain
|
||||
LATEX_EMOJI_DIRECTORY =
|
||||
GENERATE_RTF = NO
|
||||
RTF_OUTPUT = rtf
|
||||
COMPACT_RTF = NO
|
||||
RTF_HYPERLINKS = NO
|
||||
RTF_STYLESHEET_FILE =
|
||||
RTF_EXTENSIONS_FILE =
|
||||
RTF_EXTRA_FILES =
|
||||
GENERATE_MAN = NO
|
||||
MAN_OUTPUT = man
|
||||
MAN_EXTENSION = .3
|
||||
MAN_SUBDIR =
|
||||
MAN_LINKS = NO
|
||||
GENERATE_XML = NO
|
||||
XML_OUTPUT = xml
|
||||
XML_PROGRAMLISTING = YES
|
||||
XML_NS_MEMB_FILE_SCOPE = NO
|
||||
GENERATE_DOCBOOK = NO
|
||||
DOCBOOK_OUTPUT = docbook
|
||||
GENERATE_AUTOGEN_DEF = NO
|
||||
GENERATE_SQLITE3 = NO
|
||||
SQLITE3_OUTPUT = sqlite3
|
||||
SQLITE3_RECREATE_DB = YES
|
||||
GENERATE_PERLMOD = NO
|
||||
PERLMOD_LATEX = NO
|
||||
PERLMOD_PRETTY = YES
|
||||
PERLMOD_MAKEVAR_PREFIX =
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = NO
|
||||
EXPAND_ONLY_PREDEF = NO
|
||||
SEARCH_INCLUDES = YES
|
||||
INCLUDE_PATH =
|
||||
INCLUDE_FILE_PATTERNS =
|
||||
PREDEFINED =
|
||||
EXPAND_AS_DEFINED =
|
||||
SKIP_FUNCTION_MACROS = YES
|
||||
TAGFILES =
|
||||
GENERATE_TAGFILE =
|
||||
ALLEXTERNALS = NO
|
||||
EXTERNAL_GROUPS = YES
|
||||
EXTERNAL_PAGES = YES
|
||||
HIDE_UNDOC_RELATIONS = YES
|
||||
HAVE_DOT = NO
|
||||
DOT_NUM_THREADS = 0
|
||||
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
|
||||
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
|
||||
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
|
||||
DOT_FONTPATH =
|
||||
CLASS_GRAPH = YES
|
||||
COLLABORATION_GRAPH = YES
|
||||
GROUP_GRAPHS = YES
|
||||
UML_LOOK = NO
|
||||
UML_LIMIT_NUM_FIELDS = 10
|
||||
DOT_UML_DETAILS = NO
|
||||
DOT_WRAP_THRESHOLD = 17
|
||||
TEMPLATE_RELATIONS = NO
|
||||
INCLUDE_GRAPH = NO
|
||||
INCLUDED_BY_GRAPH = NO
|
||||
CALL_GRAPH = NO
|
||||
CALLER_GRAPH = NO
|
||||
GRAPHICAL_HIERARCHY = YES
|
||||
DIRECTORY_GRAPH = YES
|
||||
DIR_GRAPH_MAX_DEPTH = 1
|
||||
DOT_IMAGE_FORMAT = png
|
||||
INTERACTIVE_SVG = NO
|
||||
DOT_PATH =
|
||||
DOTFILE_DIRS =
|
||||
DIA_PATH =
|
||||
DIAFILE_DIRS =
|
||||
PLANTUML_JAR_PATH = $(PLANTUML_INSTALL_DIR)/plantuml.jar
|
||||
PLANTUML_CFG_FILE =
|
||||
PLANTUML_INCLUDE_PATH =
|
||||
DOT_GRAPH_MAX_NODES = 50
|
||||
MAX_DOT_GRAPH_DEPTH = 0
|
||||
DOT_MULTI_TARGETS = NO
|
||||
GENERATE_LEGEND = YES
|
||||
DOT_CLEANUP = YES
|
||||
MSCGEN_TOOL =
|
||||
MSCFILE_DIRS =
|
|
@ -1,7 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set(freekill_SRCS
|
||||
"main.cpp"
|
||||
# "main.cpp"
|
||||
"freekill.cpp"
|
||||
"core/player.cpp"
|
||||
"core/util.cpp"
|
||||
"core/packman.cpp"
|
||||
|
@ -95,9 +96,9 @@ else ()
|
|||
set(GIT_LIB git2)
|
||||
endif ()
|
||||
|
||||
target_sources(FreeKill PRIVATE ${freekill_SRCS})
|
||||
target_precompile_headers(FreeKill PRIVATE "pch.h")
|
||||
target_link_libraries(FreeKill PRIVATE
|
||||
add_library(libFreeKill STATIC ${freekill_SRCS})
|
||||
target_precompile_headers(libFreeKill PRIVATE "pch.h")
|
||||
target_link_libraries(libFreeKill PRIVATE
|
||||
${LUA_LIB}
|
||||
${SQLITE3_LIB}
|
||||
${CRYPTO_LIB}
|
||||
|
@ -108,6 +109,10 @@ target_link_libraries(FreeKill PRIVATE
|
|||
${GIT_LIB}
|
||||
${IDBFS_LIB}
|
||||
)
|
||||
target_sources(FreeKill PRIVATE main.cpp)
|
||||
target_link_libraries(FreeKill PRIVATE
|
||||
libFreeKill
|
||||
)
|
||||
|
||||
install(TARGETS FreeKill DESTINATION bin)
|
||||
install(DIRECTORY
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/** @file util.h
|
||||
|
||||
util.h负责提供各种全局函数、全局变量等。
|
||||
|
||||
@todo 好像在调用C库时C++程序会为它们创建包装类才对吧?
|
||||
总之这种写法实在是不太推荐,以后说不定会改成另外的写法。
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _GLOBAL_H
|
||||
#define _GLOBAL_H
|
||||
|
||||
|
|
374
src/freekill.cpp
Normal file
374
src/freekill.cpp
Normal file
|
@ -0,0 +1,374 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "client/client.h"
|
||||
#include "core/util.h"
|
||||
using namespace fkShell;
|
||||
|
||||
#include "core/packman.h"
|
||||
#include "server/server.h"
|
||||
#include "server/shell.h"
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#include "applink.c"
|
||||
#endif
|
||||
|
||||
#ifndef FK_SERVER_ONLY
|
||||
#include <QFileDialog>
|
||||
#include <QScreen>
|
||||
#include <QSplashScreen>
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QQuickStyle>
|
||||
#endif
|
||||
#include "ui/qmlbackend.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath) {
|
||||
QFileInfo srcFileInfo(srcFilePath);
|
||||
if (srcFileInfo.isDir()) {
|
||||
QDir targetDir(tgtFilePath);
|
||||
if (!targetDir.exists()) {
|
||||
targetDir.cdUp();
|
||||
if (!targetDir.mkdir(QFileInfo(tgtFilePath).fileName()))
|
||||
return false;
|
||||
}
|
||||
QDir sourceDir(srcFilePath);
|
||||
QStringList fileNames =
|
||||
sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot |
|
||||
QDir::Hidden | QDir::System);
|
||||
foreach (const QString &fileName, fileNames) {
|
||||
const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName;
|
||||
const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName;
|
||||
if (!copyPath(newSrcFilePath, newTgtFilePath))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
QFile::remove(tgtFilePath);
|
||||
if (!QFile::copy(srcFilePath, tgtFilePath))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void installFkAssets(const QString &src, const QString &dest) {
|
||||
QFile f(dest + "/fk_ver");
|
||||
if (f.exists() && f.open(QIODevice::ReadOnly)) {
|
||||
auto ver = f.readLine().simplified();
|
||||
if (ver == FK_VERSION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef Q_OS_ANDROID
|
||||
copyPath(src, dest);
|
||||
#elif defined(Q_OS_LINUX)
|
||||
system(QString("cp -r %1 %2/..").arg(src).arg(dest).toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
static void prepareForLinux() {
|
||||
// 如果用户执行的是 /usr/bin/FreeKill,那么这意味着 freekill 是被包管理器安装
|
||||
// 的,所以我们就需要把资源文件都复制到 ~/.local 中,并且切换当前目录
|
||||
// TODO: AppImage
|
||||
char buf[256] = {0};
|
||||
int len = readlink("/proc/self/exe", buf, 256);
|
||||
const char *home = getenv("HOME");
|
||||
if (!strcmp(buf, "/usr/bin/FreeKill")) {
|
||||
system("mkdir -p ~/.local/share/FreeKill");
|
||||
installFkAssets("/usr/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
|
||||
chdir(home);
|
||||
chdir(".local/share/FreeKill");
|
||||
} else if (!strcmp(buf, "/usr/local/bin/FreeKill")) {
|
||||
system("mkdir -p ~/.local/share/FreeKill");
|
||||
installFkAssets("/usr/local/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
|
||||
chdir(home);
|
||||
chdir(".local/share/FreeKill");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static FILE *info_log = nullptr;
|
||||
static FILE *err_log = nullptr;
|
||||
|
||||
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||
const QString &msg) {
|
||||
auto date = QDate::currentDate();
|
||||
|
||||
FILE *file;
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
case QtInfoMsg:
|
||||
file = info_log;
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
case QtCriticalMsg:
|
||||
case QtFatalMsg:
|
||||
file = err_log;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef FK_USE_READLINE
|
||||
ShellInstance->clearLine();
|
||||
#else
|
||||
printf("\r");
|
||||
#endif
|
||||
printf("%02d/%02d ", date.month(), date.day());
|
||||
printf("%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
fprintf(file, "%02d/%02d ", date.month(), date.day());
|
||||
fprintf(file, "%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
|
||||
auto localMsg = msg.toUtf8();
|
||||
auto threadName = QThread::currentThread()->objectName().toLatin1();
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
printf("%s[D] %s\n", threadName.constData(),
|
||||
localMsg.constData());
|
||||
fprintf(file, "%s[D] %s\n", threadName.constData(),
|
||||
localMsg.constData());
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("I", Green).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"I", localMsg.constData());
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("W", Yellow, Bold).toUtf8().constData(),
|
||||
localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"W", localMsg.constData());
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("C", Red, Bold).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"C", localMsg.constData());
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (Backend != nullptr) {
|
||||
Backend->notifyUI("ErrorDialog",
|
||||
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("E", Red, Bold).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"E", localMsg.constData());
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef FK_USE_READLINE
|
||||
if (ShellInstance && !ShellInstance->lineDone()) {
|
||||
ShellInstance->redisplay();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// FreeKill 的程序主入口。整个程序就是从这里开始执行的。
|
||||
int freekill_main(int argc, char *argv[]) {
|
||||
// 初始化一下各种杂项信息
|
||||
QThread::currentThread()->setObjectName("Main");
|
||||
|
||||
qInstallMessageHandler(fkMsgHandler);
|
||||
QCoreApplication *app;
|
||||
QCoreApplication::setApplicationName("FreeKill");
|
||||
QCoreApplication::setApplicationVersion(FK_VERSION);
|
||||
|
||||
if (GetDeviceUuid() == "c5e8983a3d85a07c") return 1;
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
prepareForLinux();
|
||||
#endif
|
||||
|
||||
if (!info_log) {
|
||||
info_log = fopen("freekill.server.info.log", "w+");
|
||||
if (!info_log) {
|
||||
qFatal("Cannot open info.log");
|
||||
}
|
||||
}
|
||||
if (!err_log) {
|
||||
err_log = fopen("freekill.server.error.log", "w+");
|
||||
if (!err_log) {
|
||||
qFatal("Cannot open error.log");
|
||||
}
|
||||
}
|
||||
|
||||
// 分析命令行,如果有 -s 或者 --server 就在命令行直接开服务器
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("FreeKill server");
|
||||
parser.addVersionOption();
|
||||
parser.addOption({{"s", "server"}, "start server at <port>", "port"});
|
||||
parser.addOption({{"h", "help"}, "display help information"});
|
||||
QStringList cliOptions;
|
||||
for (int i = 0; i < argc; i++)
|
||||
cliOptions << argv[i];
|
||||
|
||||
parser.parse(cliOptions);
|
||||
if (parser.isSet("version")) {
|
||||
parser.showVersion();
|
||||
return 0;
|
||||
} else if (parser.isSet("help")) {
|
||||
parser.showHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startServer = parser.isSet("server");
|
||||
ushort serverPort = 9527;
|
||||
|
||||
if (startServer) {
|
||||
app = new QCoreApplication(argc, argv);
|
||||
QTranslator translator;
|
||||
Q_UNUSED(translator.load("zh_CN.qm"));
|
||||
QCoreApplication::installTranslator(&translator);
|
||||
|
||||
bool ok = false;
|
||||
if (parser.value("server").toInt(&ok) && ok)
|
||||
serverPort = parser.value("server").toInt();
|
||||
|
||||
Pacman = new PackMan;
|
||||
Server *server = new Server;
|
||||
if (!server->listen(QHostAddress::Any, serverPort)) {
|
||||
qFatal("cannot listen on port %d!\n", serverPort);
|
||||
app->exit(1);
|
||||
} else {
|
||||
qInfo("Server is listening on port %d", serverPort);
|
||||
auto shell = new Shell;
|
||||
shell->start();
|
||||
}
|
||||
return app->exec();
|
||||
}
|
||||
|
||||
#ifdef FK_SERVER_ONLY
|
||||
// 根本没编译 GUI 相关的功能,直接在此退出
|
||||
qFatal("This is server-only build and have no GUI support.\n\
|
||||
Please use ./FreeKill -s to start a server in command line.");
|
||||
#else
|
||||
|
||||
app = new QApplication(argc, argv);
|
||||
#ifdef DESKTOP_BUILD
|
||||
((QApplication *)app)->setWindowIcon(QIcon("image/icon.png"));
|
||||
#endif
|
||||
|
||||
#define SHOW_SPLASH_MSG(msg) \
|
||||
splash.showMessage(msg, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
// 投降喵,设为android根本无效
|
||||
// 直接改用Android原生Mediaplayer了,不用你Qt家的
|
||||
// qputenv("QT_MEDIA_BACKEND", "android");
|
||||
|
||||
// 安卓:获取系统语言需要使用Java才行
|
||||
QString localeName = QJniObject::callStaticObjectMethod("org/notify/FreeKill/Helper", "GetLocaleCode", "()Ljava/lang/String;").toString();
|
||||
|
||||
// 安卓:先切换到我们安装程序的那个外部存储目录去
|
||||
QJniObject::callStaticMethod<void>("org/notify/FreeKill/Helper", "InitView",
|
||||
"()V");
|
||||
QDir::setCurrent(
|
||||
"/storage/emulated/0/Android/data/org.notify.FreeKill/files");
|
||||
|
||||
// 然后显示欢迎界面,并在需要时复制资源素材等
|
||||
QScreen *screen = qobject_cast<QApplication *>(app)->primaryScreen();
|
||||
QRect screenGeometry = screen->geometry();
|
||||
int screenWidth = screenGeometry.width();
|
||||
int screenHeight = screenGeometry.height();
|
||||
QSplashScreen splash(QPixmap("assets:/res/image/splash.jpg")
|
||||
.scaled(screenWidth, screenHeight));
|
||||
splash.showFullScreen();
|
||||
SHOW_SPLASH_MSG("Copying resources...");
|
||||
installFkAssets("assets:/res", QDir::currentPath());
|
||||
|
||||
info_log = freopen("freekill.server.info.log", "w+", info_log);
|
||||
err_log = freopen("freekill.server.error.log", "w+", err_log);
|
||||
#else
|
||||
// 不是安卓,使用QLocale获得系统语言
|
||||
QLocale l = QLocale::system();
|
||||
auto localeName = l.name();
|
||||
|
||||
// 不是安卓,那么直接启动欢迎界面,也就是不复制东西了
|
||||
QSplashScreen splash(QPixmap("image/splash.jpg"));
|
||||
splash.show();
|
||||
#endif
|
||||
|
||||
SHOW_SPLASH_MSG("Loading qml files...");
|
||||
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QQuickStyle::setStyle("Material");
|
||||
#endif
|
||||
|
||||
QTranslator translator;
|
||||
if (localeName.startsWith("zh_")) {
|
||||
Q_UNUSED(translator.load("zh_CN.qm"));
|
||||
} else {
|
||||
Q_UNUSED(translator.load("en_US.qm"));
|
||||
}
|
||||
QCoreApplication::installTranslator(&translator);
|
||||
|
||||
QmlBackend backend;
|
||||
backend.setEngine(engine);
|
||||
|
||||
Pacman = new PackMan;
|
||||
|
||||
// 向 Qml 中先定义几个全局变量
|
||||
auto root = engine->rootContext();
|
||||
root->setContextProperty("FkVersion", FK_VERSION);
|
||||
root->setContextProperty("Backend", &backend);
|
||||
root->setContextProperty("ModBackend", nullptr);
|
||||
root->setContextProperty("Pacman", Pacman);
|
||||
root->setContextProperty("SysLocale", localeName);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
bool debugging = true;
|
||||
#else
|
||||
bool debugging = false;
|
||||
#endif
|
||||
engine->rootContext()->setContextProperty("Debugging", debugging);
|
||||
|
||||
QString system;
|
||||
#if defined(Q_OS_ANDROID)
|
||||
system = "Android";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
qputenv("QT_MEDIA_BACKEND", "windows");
|
||||
system = "Win";
|
||||
::system("chcp 65001");
|
||||
#elif defined(Q_OS_LINUX)
|
||||
system = "Linux";
|
||||
#else
|
||||
system = "Other";
|
||||
#endif
|
||||
root->setContextProperty("OS", system);
|
||||
|
||||
root->setContextProperty(
|
||||
"AppPath", QUrl::fromLocalFile(QDir::currentPath()));
|
||||
|
||||
engine->addImportPath(QDir::currentPath());
|
||||
|
||||
// 加载完全局变量后,就再去加载 main.qml,此时UI界面正式显示
|
||||
engine->load("Fk/main.qml");
|
||||
|
||||
// qml 报错了就直接退出吧
|
||||
if (engine->rootObjects().isEmpty())
|
||||
return -1;
|
||||
|
||||
// 关闭欢迎界面,然后进入Qt主循环
|
||||
splash.close();
|
||||
int ret = app->exec();
|
||||
|
||||
// 先删除 engine
|
||||
// 防止报一堆错 "TypeError: Cannot read property 'xxx' of null"
|
||||
delete engine;
|
||||
delete Pacman;
|
||||
|
||||
if (info_log) fclose(info_log);
|
||||
if (err_log) fclose(err_log);
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
}
|
8
src/freekill.h
Normal file
8
src/freekill.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef _FREEKILL_H
|
||||
#define _FREEKILL_H
|
||||
|
||||
int freekill_main(int argc, char **argv);
|
||||
|
||||
#endif // _FREEKILL_H
|
387
src/main.cpp
387
src/main.cpp
|
@ -1,374 +1,39 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "client/client.h"
|
||||
#include "core/util.h"
|
||||
using namespace fkShell;
|
||||
|
||||
#include "core/packman.h"
|
||||
#include "server/server.h"
|
||||
#include "server/shell.h"
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#include "applink.c"
|
||||
#endif
|
||||
|
||||
#ifndef FK_SERVER_ONLY
|
||||
#include <QFileDialog>
|
||||
#include <QScreen>
|
||||
#include <QSplashScreen>
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QQuickStyle>
|
||||
#endif
|
||||
#include "ui/qmlbackend.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath) {
|
||||
QFileInfo srcFileInfo(srcFilePath);
|
||||
if (srcFileInfo.isDir()) {
|
||||
QDir targetDir(tgtFilePath);
|
||||
if (!targetDir.exists()) {
|
||||
targetDir.cdUp();
|
||||
if (!targetDir.mkdir(QFileInfo(tgtFilePath).fileName()))
|
||||
return false;
|
||||
}
|
||||
QDir sourceDir(srcFilePath);
|
||||
QStringList fileNames =
|
||||
sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot |
|
||||
QDir::Hidden | QDir::System);
|
||||
foreach (const QString &fileName, fileNames) {
|
||||
const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName;
|
||||
const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName;
|
||||
if (!copyPath(newSrcFilePath, newTgtFilePath))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
QFile::remove(tgtFilePath);
|
||||
if (!QFile::copy(srcFilePath, tgtFilePath))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void installFkAssets(const QString &src, const QString &dest) {
|
||||
QFile f(dest + "/fk_ver");
|
||||
if (f.exists() && f.open(QIODevice::ReadOnly)) {
|
||||
auto ver = f.readLine().simplified();
|
||||
if (ver == FK_VERSION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef Q_OS_ANDROID
|
||||
copyPath(src, dest);
|
||||
#elif defined(Q_OS_LINUX)
|
||||
system(QString("cp -r %1 %2/..").arg(src).arg(dest).toUtf8());
|
||||
#endif
|
||||
// 为了写测试而特意给程序本身单独分出一个main.cpp 顺便包含项目文档(这样真的好吗)
|
||||
#include "freekill.h"
|
||||
int main(int argc, char **argv) {
|
||||
return freekill_main(argc, argv);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
static void prepareForLinux() {
|
||||
// 如果用户执行的是 /usr/bin/FreeKill,那么这意味着 freekill 是被包管理器安装
|
||||
// 的,所以我们就需要把资源文件都复制到 ~/.local 中,并且切换当前目录
|
||||
// TODO: AppImage
|
||||
char buf[256] = {0};
|
||||
int len = readlink("/proc/self/exe", buf, 256);
|
||||
const char *home = getenv("HOME");
|
||||
if (!strcmp(buf, "/usr/bin/FreeKill")) {
|
||||
system("mkdir -p ~/.local/share/FreeKill");
|
||||
installFkAssets("/usr/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
|
||||
chdir(home);
|
||||
chdir(".local/share/FreeKill");
|
||||
} else if (!strcmp(buf, "/usr/local/bin/FreeKill")) {
|
||||
system("mkdir -p ~/.local/share/FreeKill");
|
||||
installFkAssets("/usr/local/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
|
||||
chdir(home);
|
||||
chdir(".local/share/FreeKill");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/** @mainpage 新月杀文档 - Cpp代码部分
|
||||
|
||||
static FILE *info_log = nullptr;
|
||||
static FILE *err_log = nullptr;
|
||||
本文档专门针对新月杀的C++代码部分,采用Doxygen生成。
|
||||
关于项目的主文档请参见新月之书: https://fkbook-all-in-one.readthedocs.io/
|
||||
|
||||
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
|
||||
const QString &msg) {
|
||||
auto date = QDate::currentDate();
|
||||
> 单独分出一个Doxygen页面而不是合并在新月之书中,
|
||||
完全因为懒得给新月之书拖一个新的submodule =.=
|
||||
|
||||
FILE *file;
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
case QtInfoMsg:
|
||||
file = info_log;
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
case QtCriticalMsg:
|
||||
case QtFatalMsg:
|
||||
file = err_log;
|
||||
break;
|
||||
}
|
||||
C++的代码位于src/文件夹下,其覆盖的功能为:
|
||||
|
||||
#ifdef FK_USE_READLINE
|
||||
ShellInstance->clearLine();
|
||||
#else
|
||||
printf("\r");
|
||||
#endif
|
||||
printf("%02d/%02d ", date.month(), date.day());
|
||||
printf("%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
fprintf(file, "%02d/%02d ", date.month(), date.day());
|
||||
fprintf(file, "%s ",
|
||||
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
|
||||
- freekill.cpp: 程序入口,Linux与Android环境下提前部署环境等
|
||||
- swig/: 基于SWIG的Lua-cpp接口。需要测试的是里面的函数定义(而非函数声明)。
|
||||
- ui/: Qml-cpp接口,以及一些Lua-cpp接口,借此实现lua和qml之间的交互
|
||||
- core/: 主要是拓展包管理,以及对于用户(玩家)的定义,以及一些第三方库的封装
|
||||
- client/: 主要负责录像,以及加载client侧的Lua,功能其实不多
|
||||
- network/: 对Qt Network模块封装,加密/压缩传输,基于JSON通信协议
|
||||
- server/: 登录,大厅,房间管理,房间调度,管理员shell
|
||||
|
||||
auto localMsg = msg.toUtf8();
|
||||
auto threadName = QThread::currentThread()->objectName().toLatin1();
|
||||
比较复杂的主要是服务端代码。具体可以查看每个类的文档
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
printf("%s[D] %s\n", threadName.constData(),
|
||||
localMsg.constData());
|
||||
fprintf(file, "%s[D] %s\n", threadName.constData(),
|
||||
localMsg.constData());
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("I", Green).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"I", localMsg.constData());
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("W", Yellow, Bold).toUtf8().constData(),
|
||||
localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"W", localMsg.constData());
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("C", Red, Bold).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"C", localMsg.constData());
|
||||
#ifndef FK_SERVER_ONLY
|
||||
if (Backend != nullptr) {
|
||||
Backend->notifyUI("ErrorDialog",
|
||||
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
printf("%s[%s] %s\n", threadName.constData(),
|
||||
Color("E", Red, Bold).toUtf8().constData(), localMsg.constData());
|
||||
fprintf(file, "%s[%s] %s\n", threadName.constData(),
|
||||
"E", localMsg.constData());
|
||||
break;
|
||||
}
|
||||
@note 为了详细说明程序运行原理,private成员也将会在文档中呈现。
|
||||
*/
|
||||
|
||||
#ifdef FK_USE_READLINE
|
||||
if (ShellInstance && !ShellInstance->lineDone()) {
|
||||
ShellInstance->redisplay();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/** @page page_network 网络连接
|
||||
|
||||
// FreeKill 的程序主入口。整个程序就是从这里开始执行的。
|
||||
int main(int argc, char *argv[]) {
|
||||
// 初始化一下各种杂项信息
|
||||
QThread::currentThread()->setObjectName("Main");
|
||||
相关类:
|
||||
|
||||
qInstallMessageHandler(fkMsgHandler);
|
||||
QCoreApplication *app;
|
||||
QCoreApplication::setApplicationName("FreeKill");
|
||||
QCoreApplication::setApplicationVersion(FK_VERSION);
|
||||
|
||||
if (GetDeviceUuid() == "c5e8983a3d85a07c") return 1;
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
prepareForLinux();
|
||||
#endif
|
||||
|
||||
if (!info_log) {
|
||||
info_log = fopen("freekill.server.info.log", "w+");
|
||||
if (!info_log) {
|
||||
qFatal("Cannot open info.log");
|
||||
}
|
||||
}
|
||||
if (!err_log) {
|
||||
err_log = fopen("freekill.server.error.log", "w+");
|
||||
if (!err_log) {
|
||||
qFatal("Cannot open error.log");
|
||||
}
|
||||
}
|
||||
|
||||
// 分析命令行,如果有 -s 或者 --server 就在命令行直接开服务器
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("FreeKill server");
|
||||
parser.addVersionOption();
|
||||
parser.addOption({{"s", "server"}, "start server at <port>", "port"});
|
||||
parser.addOption({{"h", "help"}, "display help information"});
|
||||
QStringList cliOptions;
|
||||
for (int i = 0; i < argc; i++)
|
||||
cliOptions << argv[i];
|
||||
|
||||
parser.parse(cliOptions);
|
||||
if (parser.isSet("version")) {
|
||||
parser.showVersion();
|
||||
return 0;
|
||||
} else if (parser.isSet("help")) {
|
||||
parser.showHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool startServer = parser.isSet("server");
|
||||
ushort serverPort = 9527;
|
||||
|
||||
if (startServer) {
|
||||
app = new QCoreApplication(argc, argv);
|
||||
QTranslator translator;
|
||||
Q_UNUSED(translator.load("zh_CN.qm"));
|
||||
QCoreApplication::installTranslator(&translator);
|
||||
|
||||
bool ok = false;
|
||||
if (parser.value("server").toInt(&ok) && ok)
|
||||
serverPort = parser.value("server").toInt();
|
||||
|
||||
Pacman = new PackMan;
|
||||
Server *server = new Server;
|
||||
if (!server->listen(QHostAddress::Any, serverPort)) {
|
||||
qFatal("cannot listen on port %d!\n", serverPort);
|
||||
app->exit(1);
|
||||
} else {
|
||||
qInfo("Server is listening on port %d", serverPort);
|
||||
auto shell = new Shell;
|
||||
shell->start();
|
||||
}
|
||||
return app->exec();
|
||||
}
|
||||
|
||||
#ifdef FK_SERVER_ONLY
|
||||
// 根本没编译 GUI 相关的功能,直接在此退出
|
||||
qFatal("This is server-only build and have no GUI support.\n\
|
||||
Please use ./FreeKill -s to start a server in command line.");
|
||||
#else
|
||||
|
||||
app = new QApplication(argc, argv);
|
||||
#ifdef DESKTOP_BUILD
|
||||
((QApplication *)app)->setWindowIcon(QIcon("image/icon.png"));
|
||||
#endif
|
||||
|
||||
#define SHOW_SPLASH_MSG(msg) \
|
||||
splash.showMessage(msg, Qt::AlignHCenter | Qt::AlignBottom);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
// 投降喵,设为android根本无效
|
||||
// 直接改用Android原生Mediaplayer了,不用你Qt家的
|
||||
// qputenv("QT_MEDIA_BACKEND", "android");
|
||||
|
||||
// 安卓:获取系统语言需要使用Java才行
|
||||
QString localeName = QJniObject::callStaticObjectMethod("org/notify/FreeKill/Helper", "GetLocaleCode", "()Ljava/lang/String;").toString();
|
||||
|
||||
// 安卓:先切换到我们安装程序的那个外部存储目录去
|
||||
QJniObject::callStaticMethod<void>("org/notify/FreeKill/Helper", "InitView",
|
||||
"()V");
|
||||
QDir::setCurrent(
|
||||
"/storage/emulated/0/Android/data/org.notify.FreeKill/files");
|
||||
|
||||
// 然后显示欢迎界面,并在需要时复制资源素材等
|
||||
QScreen *screen = qobject_cast<QApplication *>(app)->primaryScreen();
|
||||
QRect screenGeometry = screen->geometry();
|
||||
int screenWidth = screenGeometry.width();
|
||||
int screenHeight = screenGeometry.height();
|
||||
QSplashScreen splash(QPixmap("assets:/res/image/splash.jpg")
|
||||
.scaled(screenWidth, screenHeight));
|
||||
splash.showFullScreen();
|
||||
SHOW_SPLASH_MSG("Copying resources...");
|
||||
installFkAssets("assets:/res", QDir::currentPath());
|
||||
|
||||
info_log = freopen("freekill.server.info.log", "w+", info_log);
|
||||
err_log = freopen("freekill.server.error.log", "w+", err_log);
|
||||
#else
|
||||
// 不是安卓,使用QLocale获得系统语言
|
||||
QLocale l = QLocale::system();
|
||||
auto localeName = l.name();
|
||||
|
||||
// 不是安卓,那么直接启动欢迎界面,也就是不复制东西了
|
||||
QSplashScreen splash(QPixmap("image/splash.jpg"));
|
||||
splash.show();
|
||||
#endif
|
||||
|
||||
SHOW_SPLASH_MSG("Loading qml files...");
|
||||
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QQuickStyle::setStyle("Material");
|
||||
#endif
|
||||
|
||||
QTranslator translator;
|
||||
if (localeName.startsWith("zh_")) {
|
||||
Q_UNUSED(translator.load("zh_CN.qm"));
|
||||
} else {
|
||||
Q_UNUSED(translator.load("en_US.qm"));
|
||||
}
|
||||
QCoreApplication::installTranslator(&translator);
|
||||
|
||||
QmlBackend backend;
|
||||
backend.setEngine(engine);
|
||||
|
||||
Pacman = new PackMan;
|
||||
|
||||
// 向 Qml 中先定义几个全局变量
|
||||
auto root = engine->rootContext();
|
||||
root->setContextProperty("FkVersion", FK_VERSION);
|
||||
root->setContextProperty("Backend", &backend);
|
||||
root->setContextProperty("ModBackend", nullptr);
|
||||
root->setContextProperty("Pacman", Pacman);
|
||||
root->setContextProperty("SysLocale", localeName);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
bool debugging = true;
|
||||
#else
|
||||
bool debugging = false;
|
||||
#endif
|
||||
engine->rootContext()->setContextProperty("Debugging", debugging);
|
||||
|
||||
QString system;
|
||||
#if defined(Q_OS_ANDROID)
|
||||
system = "Android";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
qputenv("QT_MEDIA_BACKEND", "windows");
|
||||
system = "Win";
|
||||
::system("chcp 65001");
|
||||
#elif defined(Q_OS_LINUX)
|
||||
system = "Linux";
|
||||
#else
|
||||
system = "Other";
|
||||
#endif
|
||||
root->setContextProperty("OS", system);
|
||||
|
||||
root->setContextProperty(
|
||||
"AppPath", QUrl::fromLocalFile(QDir::currentPath()));
|
||||
|
||||
engine->addImportPath(QDir::currentPath());
|
||||
|
||||
// 加载完全局变量后,就再去加载 main.qml,此时UI界面正式显示
|
||||
engine->load("Fk/main.qml");
|
||||
|
||||
// qml 报错了就直接退出吧
|
||||
if (engine->rootObjects().isEmpty())
|
||||
return -1;
|
||||
|
||||
// 关闭欢迎界面,然后进入Qt主循环
|
||||
splash.close();
|
||||
int ret = app->exec();
|
||||
|
||||
// 先删除 engine
|
||||
// 防止报一堆错 "TypeError: Cannot read property 'xxx' of null"
|
||||
delete engine;
|
||||
delete Pacman;
|
||||
|
||||
if (info_log) fclose(info_log);
|
||||
if (err_log) fclose(err_log);
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
@ref ServerSocket
|
||||
@ref ClientSocket
|
||||
@ref Router
|
||||
*/
|
||||
|
|
|
@ -5,40 +5,96 @@
|
|||
|
||||
#include <openssl/aes.h>
|
||||
|
||||
/**
|
||||
@brief 基于TCP协议实现双端消息收发,支持加密传输和压缩传输
|
||||
|
||||
QTcpSocket的封装,提供收发数据的功能。当客户端想要向服务端发起连接时,客户端
|
||||
先构造ClientSocket对象,然后调用connectToHost;服务端收到后也构造一个
|
||||
ClientSocket对象用来与其进行一对一的通信,一方调用send便可触发另一方的
|
||||
message_got信号。
|
||||
|
||||
### 压缩传输
|
||||
|
||||
当消息长度超过1024时,利用qCompress进行压缩,将压缩后的内容通过base64编码并
|
||||
携带上"Compressed"头部形成新消息。后续也根据这个规则解码。
|
||||
|
||||
> 参见send方法与getMessage方法。
|
||||
|
||||
### 加密传输
|
||||
|
||||
当设置了AES密钥时,使用AES将数据加密后再传输。
|
||||
|
||||
加密算法采用AES-128-CFB模式,密钥由用户提供(随机生成),通过RSA与口令一同加密
|
||||
后发送至服务器,这样服务器就得到了一致的AES密钥。加密时,先随机生成IV,将IV与
|
||||
base64编码后密文拼接后作为最终要发送至服务器的密文。解密时先处理好IV与原始密文,
|
||||
再通过AES解密获取明文消息。
|
||||
|
||||
> 参见aesEnc与aesDec私有方法。
|
||||
*/
|
||||
class ClientSocket : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// 客户端使用的构造函数,构造QTcpSocket和ClientSocket本身
|
||||
ClientSocket();
|
||||
// For server use
|
||||
/** 服务端使用的构造函数,当新连接传入后Qt库已为此构造了QTcpSocket,
|
||||
基于Qt构造的QTcpSocket构造新的ClientSocket。
|
||||
*/
|
||||
ClientSocket(QTcpSocket *socket);
|
||||
|
||||
/// 客户端使用,用于连接到远程服务器
|
||||
void connectToHost(const QString &address = "127.0.0.1", ushort port = 9527u);
|
||||
/// 双端都可使用。禁用加密传输并断开TCP连接。
|
||||
void disconnectFromHost();
|
||||
/// 设置AES密钥,同时启用加密传输。
|
||||
void installAESKey(const QByteArray &key);
|
||||
/// 发送消息。参见加密传输与压缩传输
|
||||
void send(const QByteArray& msg);
|
||||
/// 判断是否处于已连接状态
|
||||
///
|
||||
/// @todo 这个函数好好像没用上?产生bloat了?
|
||||
bool isConnected() const;
|
||||
/// 对等端的名字(地址:端口)
|
||||
QString peerName() const;
|
||||
/// 对等端的地址
|
||||
QString peerAddress() const;
|
||||
QTimer timerSignup;
|
||||
QTimer timerSignup; ///< 创建连接时,若该计时器超时,则断开连接
|
||||
|
||||
signals:
|
||||
/// 收到一条消息时触发的信号
|
||||
void message_got(const QByteArray& msg);
|
||||
/// 产生报错信息触发的信号,连接到UI中的函数
|
||||
void error_message(const QString &msg);
|
||||
/// 断开连接时的信号
|
||||
void disconnected();
|
||||
/// 连接创建时的信号
|
||||
void connected();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
连接QTcpSocket::messageReady,按每行一条消息依次触发message_get信号。
|
||||
|
||||
若启用了加密传输,则消息在取出时先被解密。
|
||||
|
||||
若消息以"Compressed"开头,则将剩余部分作为被压缩内容,进行base64解码并解压缩。
|
||||
|
||||
完成上述预处理后便取得了消息的实际内容,再触发message_get信号传给上层处理。
|
||||
*/
|
||||
void getMessage();
|
||||
/// 连接QTcpSocket::errorOccured,负责在UI显示网络错误信息
|
||||
void raiseError(QAbstractSocket::SocketError error);
|
||||
|
||||
private:
|
||||
/// AES加密
|
||||
QByteArray aesEnc(const QByteArray &in);
|
||||
/// AES解密
|
||||
QByteArray aesDec(const QByteArray &out);
|
||||
AES_KEY aes_key;
|
||||
bool aes_ready;
|
||||
QTcpSocket *socket;
|
||||
/// 与QTcpSocket连接信号槽
|
||||
void init();
|
||||
|
||||
AES_KEY aes_key; ///< AES密钥
|
||||
bool aes_ready; ///< 表明是否启用AES加密传输
|
||||
QTcpSocket *socket; ///< 用于实际发送数据的socket
|
||||
};
|
||||
|
||||
#endif // _CLIENT_SOCKET_H
|
||||
|
|
|
@ -5,16 +5,26 @@
|
|||
|
||||
class ClientSocket;
|
||||
|
||||
/** @brief 实现通信协议,负责传输结构化消息而不是字面上的文本信息。
|
||||
|
||||
Router是对\ref ClientSocket 的又一次封装。ClientSocket解决的是传输字符串的
|
||||
问题,Router要解决的则是实现协议中的两种类型消息的传输:Request-Reply以及
|
||||
Notify这两种。
|
||||
*/
|
||||
class Router : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
该枚举揭示了一个Packet的类型,在实际使用中,Packet的类型以TYPE、SRC、
|
||||
DEST这几种枚举通过按位与的方式拼接而成。
|
||||
*/
|
||||
enum PacketType {
|
||||
TYPE_REQUEST = 0x100,
|
||||
TYPE_REPLY = 0x200,
|
||||
TYPE_NOTIFICATION = 0x400,
|
||||
SRC_CLIENT = 0x010,
|
||||
SRC_SERVER = 0x020,
|
||||
TYPE_REQUEST = 0x100, ///< 类型为Request的包
|
||||
TYPE_REPLY = 0x200, ///< 类型为Reply的包
|
||||
TYPE_NOTIFICATION = 0x400, ///< 类型为Notify的包
|
||||
SRC_CLIENT = 0x010, ///< 从客户端发出的包
|
||||
SRC_SERVER = 0x020, ///< 从服务端发出的包
|
||||
SRC_LOBBY = 0x040,
|
||||
DEST_CLIENT = 0x001,
|
||||
DEST_SERVER = 0x002,
|
||||
|
|
|
@ -5,26 +5,44 @@
|
|||
|
||||
class ClientSocket;
|
||||
|
||||
// 只是对QTcpServer的简单封装
|
||||
/**
|
||||
@brief 向Server转达新的连接请求,并负责显示服务器信息。
|
||||
|
||||
ServerSocket是对QTcpServer与QUdpSocket的封装。
|
||||
|
||||
功能有:
|
||||
- 当接受到TCP连接请求时,创建新的@ref ClientSocket 并向@ref Server 发送信号。
|
||||
- 当接受到格式正确的UDP报文时,发回关于服务器的信息。
|
||||
*/
|
||||
class ServerSocket : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
创建新的ServerSocket对象。
|
||||
|
||||
仅用于@ref Server 的构造函数中,作为Server的一个子成员。
|
||||
*/
|
||||
ServerSocket(QObject *parent = nullptr);
|
||||
|
||||
/// 监听端口port,TCP和UDP的都监听
|
||||
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
|
||||
|
||||
signals:
|
||||
/// 接收到新连接时,创建新的socket对象并发出该信号
|
||||
void new_connection(ClientSocket *socket);
|
||||
|
||||
private slots:
|
||||
// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
||||
/// 新建一个ClientSocket,然后立刻交给Server相关函数处理。
|
||||
void processNewConnection();
|
||||
/// 对每条收到的UDP报文调用processDatagram
|
||||
void readPendingDatagrams();
|
||||
|
||||
private:
|
||||
QTcpServer *server;
|
||||
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
|
||||
QTcpServer *server; ///< 监听TCP连接用
|
||||
QUdpSocket *udpSocket; ///< 显示服务器信息用
|
||||
|
||||
/// 对udp报文`msg`进行分析,addr和port是报文发送者传来的信息
|
||||
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
|
||||
};
|
||||
|
||||
|
|
|
@ -106,13 +106,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
|||
nextRoomId++;
|
||||
room->setAbandoned(false);
|
||||
thread->addRoom(room);
|
||||
rooms.insert(room->getId(), room);
|
||||
} else {
|
||||
room = new Room(thread);
|
||||
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
|
||||
rooms.insert(room->getId(), room);
|
||||
}
|
||||
|
||||
rooms.insert(room->getId(), room);
|
||||
room->setName(name);
|
||||
room->setCapacity(capacity);
|
||||
room->setTimeout(timeout);
|
||||
|
|
|
@ -12,29 +12,60 @@ class Lobby;
|
|||
|
||||
#include "server/room.h"
|
||||
|
||||
/**
|
||||
@brief Server类负责管理游戏服务端的运行。
|
||||
|
||||
该类用于服务端程序运行。当客户端使用单机启动时,Server类也会被实例化。
|
||||
Server的具体运行逻辑依托于Qt的事件循环与信号槽机制:当Server被创建后,
|
||||
调用listen方法即可完成监听,然后程序进入事件循环,通过触发Server的槽函数
|
||||
以实现各种功能。
|
||||
|
||||
### 配置信息
|
||||
|
||||
### 用户管理
|
||||
|
||||
*/
|
||||
class Server : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// 构造Server对象。见于main函数
|
||||
explicit Server(QObject *parent = nullptr);
|
||||
~Server();
|
||||
|
||||
/// 监听端口
|
||||
bool listen(const QHostAddress &address = QHostAddress::Any,
|
||||
ushort port = 9527u);
|
||||
|
||||
/**
|
||||
@brief 创建新的房间并加入到房间列表中。
|
||||
|
||||
创建新的房间。
|
||||
|
||||
首先,房间名是用户自定义输入内容,需要先进行内容安全检查;然后创建新房间。
|
||||
在创建新房间时,游戏会避免创建新的Room对象;有一个idle_rooms存储着创建过
|
||||
但目前没在使用中的房间,游戏优先从其中选择一个作为“新房间”,如果没有这样的
|
||||
房间,那么就构造一个新的Room对象。
|
||||
|
||||
之后,将新的room添加到rooms表中;然后将参数中指定的各个属性都赋予给新的
|
||||
房间,通过addPlayer将房主添加到房间中,并使用setOwner将其设为房主。
|
||||
|
||||
@param owner 创建房间的那名玩家;房主
|
||||
@param settings 表示JSON对象的字符串,用作房间配置
|
||||
*/
|
||||
void createRoom(ServerPlayer *owner, const QString &name, int capacity,
|
||||
int timeout = 15, const QByteArray &settings = "{}");
|
||||
|
||||
Room *findRoom(int id) const;
|
||||
Lobby *lobby() const;
|
||||
Room *findRoom(int id) const; /// 获取对应id的房间
|
||||
Lobby *lobby() const; /// 获取大厅对象
|
||||
|
||||
RoomThread *createThread();
|
||||
void removeThread(RoomThread *thread);
|
||||
RoomThread *createThread(); /// 创建新的RoomThread,并加入列表
|
||||
void removeThread(RoomThread *thread); /// 从列表中移除thread
|
||||
|
||||
ServerPlayer *findPlayer(int id) const;
|
||||
void addPlayer(ServerPlayer *player);
|
||||
void removePlayer(int id);
|
||||
auto getPlayers() { return players; }
|
||||
ServerPlayer *findPlayer(int id) const; /// 获取对应id的玩家
|
||||
void addPlayer(ServerPlayer *player); /// 将玩家加入表中,若重复则覆盖旧的
|
||||
void removePlayer(int id); /// 从表中删除对应id的玩家
|
||||
auto getPlayers() { return players; } /// 获取players表
|
||||
|
||||
void updateRoomList(ServerPlayer *teller);
|
||||
void updateOnlineInfo();
|
||||
|
@ -81,14 +112,20 @@ private:
|
|||
QList<QString> temp_banlist;
|
||||
|
||||
AuthManager *auth;
|
||||
sqlite3 *db;
|
||||
QMutex transaction_mutex;
|
||||
QString md5;
|
||||
sqlite3 *db; ///< sqlite数据库连接实例
|
||||
QMutex transaction_mutex; ///< 可能有多线程同时对数据库请求,需要加锁
|
||||
QString md5; ///< 服务端当前允许用户登录的MD5值
|
||||
|
||||
QJsonObject config;
|
||||
/**
|
||||
读取配置文件。配置文件的路径是`<pwd>/freekill.server.config.json`。
|
||||
|
||||
若读取失败(包含文件不存在、有语法错误等情况),则使用一个空JSON对象;
|
||||
否则使用从文件读取并解析后的JSON对象。最后为一些必须存在而实际为空值的key设置默认值。
|
||||
*/
|
||||
void readConfig();
|
||||
QJsonObject config; ///< 配置文件其实就是一个JSON对象
|
||||
};
|
||||
|
||||
extern Server *ServerInstance;
|
||||
extern Server *ServerInstance; ///< 全局Server对象
|
||||
|
||||
#endif // _SERVER_H
|
||||
|
|
242
src/ui/mod.cpp
242
src/ui/mod.cpp
|
@ -1,242 +0,0 @@
|
|||
#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::mkdir(const QString &path) {
|
||||
QDir(".").mkdir(path);
|
||||
}
|
||||
|
||||
void ModMaker::rmrf(const QString &path) {
|
||||
QDir(path).removeRecursively();
|
||||
}
|
||||
|
||||
void ModMaker::createMod(const QString &name) {
|
||||
init(name);
|
||||
}
|
||||
|
||||
void ModMaker::removeMod(const QString &name) {
|
||||
QDir("mymod/" + name).removeRecursively();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
35
src/ui/mod.h
35
src/ui/mod.h
|
@ -1,35 +0,0 @@
|
|||
// 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 mkdir(const QString &path);
|
||||
Q_INVOKABLE void rmrf(const QString &path);
|
||||
|
||||
Q_INVOKABLE void createMod(const QString &name);
|
||||
Q_INVOKABLE void removeMod(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
|
13
test/CMakeLists.txt
Normal file
13
test/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||
|
||||
set (TEST_LIB
|
||||
libFreeKill
|
||||
${QT_LIB}
|
||||
Qt6::Test
|
||||
)
|
||||
|
||||
qt_add_executable(Test test.cpp)
|
||||
|
||||
target_link_libraries(Test PRIVATE ${TEST_LIB})
|
||||
|
||||
add_test(NAME mytest COMMAND Test)
|
15
test/test.cpp
Normal file
15
test/test.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include <QTest>
|
||||
|
||||
class TestQString: public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void toUpper();
|
||||
};
|
||||
|
||||
void TestQString::toUpper() {
|
||||
QString str = "Hello";
|
||||
QCOMPARE(str.toUpper(), QString("HELLO,"));
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestQString)
|
||||
#include "test.moc"
|
Loading…
Reference in New Issue
Block a user