- 修改cpp代码
- 与之配合修改了相关lua代码

!注意从此开始,lua代码的更新转移到freekill-core仓库
Qsgs-Fans/freekill-core#1
This commit is contained in:
notify 2024-06-10 14:58:31 +08:00 committed by GitHub
parent 766e93378e
commit ebc5675ead
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
50 changed files with 1438 additions and 1020 deletions

View File

@ -86,6 +86,7 @@ jobs:
cp -r /etc/ssl/certs . cp -r /etc/ssl/certs .
cp /usr/share/ca-certificates/mozilla/* certs/ cp /usr/share/ca-certificates/mozilla/* certs/
echo ${FKVER%)} > fk_ver echo ${FKVER%)} > fk_ver
./genfkver.sh
- name: Configure CMake Project - name: Configure CMake Project
working-directory: ${{github.workspace}} working-directory: ${{github.workspace}}

1
.gitignore vendored
View File

@ -15,6 +15,7 @@
/*.kdev4 /*.kdev4
/.cache/ /.cache/
/tags /tags
/.luarc.json
# file produced by game # file produced by game
/FreeKill /FreeKill

View File

@ -39,11 +39,6 @@ include_directories(include/lua)
include_directories(include) include_directories(include)
include_directories(include/libgit2) include_directories(include/libgit2)
include_directories(src) include_directories(src)
include_directories(src/client)
include_directories(src/core)
include_directories(src/network)
include_directories(src/server)
include_directories(src/ui)
file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i") file(GLOB SWIG_FILES "${PROJECT_SOURCE_DIR}/src/swig/*.i")
if (DEFINED FK_SERVER_ONLY) if (DEFINED FK_SERVER_ONLY)
@ -78,6 +73,7 @@ add_custom_command(
POST_BUILD POST_BUILD
COMMENT "Generating version file fk_ver" COMMENT "Generating version file fk_ver"
COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver COMMAND echo ${CMAKE_PROJECT_VERSION} > ${PROJECT_SOURCE_DIR}/fk_ver
COMMAND ${PROJECT_SOURCE_DIR}/genfkver.sh
) )
add_subdirectory(src) add_subdirectory(src)

View File

@ -43,6 +43,8 @@ QtObject {
property string password: "" property string password: ""
property string cipherText property string cipherText
property string aeskey property string aeskey
// string => { roomId => config }
property var roomConfigCache: ({})
// Client data // Client data
property string serverMotd: "" property string serverMotd: ""

View File

@ -81,6 +81,25 @@ callbacks["ErrorMsg"] = (jsonData) => {
} }
} }
callbacks["ErrorDlg"] = (jsonData) => {
let log;
try {
const a = JSON.parse(jsonData);
log = qsTr(a[0]).arg(a[1]);
} catch (e) {
log = qsTr(jsonData);
}
console.log("ERROR: " + log);
Backend.showDialog("warning", log, jsonData);
mainWindow.busy = false;
if (sheduled_download !== "") {
mainWindow.busy = true;
Pacman.loadSummary(JSON.stringify(sheduled_download), true);
sheduled_download = "";
}
}
callbacks["UpdatePackage"] = (jsonData) => sheduled_download = jsonData; callbacks["UpdatePackage"] = (jsonData) => sheduled_download = jsonData;
callbacks["UpdateBusyText"] = (jsonData) => { callbacks["UpdateBusyText"] = (jsonData) => {

View File

@ -183,17 +183,6 @@ Item {
} }
} }
// Temp
Button {
text: qsTr("Making Mod")
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: Debugging
onClicked: {
mainStack.push(modMaker);
}
}
function downloadComplete() { function downloadComplete() {
toast.show(qsTr("updated packages for md5")); toast.show(qsTr("updated packages for md5"));
} }

View File

@ -11,111 +11,150 @@ import "Logic.js" as Logic
Item { Item {
id: root id: root
property alias roomModel: roomModel property alias roomModel: roomModel
property var roomInfoCache: ({})
property string password property string password
Rectangle {
width: parent.width / 2 - roomListLayout.width / 2 - 50
height: parent.height * 0.7
anchors.top: exitButton.bottom
anchors.bottom: createRoomButton.top
anchors.right: parent.right
anchors.rightMargin: 20
color: "#88EEEEEE"
radius: 6
Flickable {
id: flickableContainer
ScrollBar.vertical: ScrollBar {}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
flickableDirection: Flickable.VerticalFlick
width: parent.width - 10
height: parent.height - 10
contentHeight: bulletin_info.height
clip: true
Text {
id: bulletin_info
width: parent.width
wrapMode: TextEdit.WordWrap
textFormat: Text.MarkdownText
text: config.serverMotd + "\n___\n" + luatr('Bulletin Info')
onLinkActivated: Qt.openUrlExternally(link);
}
}
}
Component { Component {
id: roomDelegate id: roomDelegate
Item { Rectangle {
height: 48 radius: 8
width: roomList.width height: 124 - 8
width: 124 - 8
RowLayout { color: outdated ? "#E2E2E2" : "lightgreen"
anchors.fill: parent
spacing: 16
Text {
text: roomId
color: "grey"
}
Text { Text {
id: roomNameText
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true width: parent.width - 16
text: { height: contentHeight
let ret = roomName; maximumLineCount: 2
if (outdated) { wrapMode: Text.WrapAnywhere
ret = '<font color="grey"><del>' + ret + '</del></font>'; textFormat: Text.PlainText
} text: roomName
return ret; // color: outdated ? "gray" : "black"
} font.pixelSize: 16
font.pixelSize: 20 // elide: Label.ElideRight
elide: Label.ElideRight anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 8
}
Text {
id: roomIdText
text: luatr(gameMode) + ' #' + roomId
anchors.top: roomNameText.bottom
anchors.left: roomNameText.left
} }
Item {
Layout.preferredWidth: 16
Image { Image {
source: AppPath + "/image/button/skill/locked.png" source: AppPath + "/image/button/skill/locked.png"
visible: hasPassword visible: hasPassword
anchors.centerIn: parent
scale: 0.8 scale: 0.8
} anchors.bottom: parent.bottom
} anchors.left: parent.left
anchors.margins: -4
Text {
text: luatr(gameMode)
} }
Text { Text {
color: (playerNum == capacity) ? "red" : "black" color: (playerNum == capacity) ? "red" : "black"
text: playerNum + "/" + capacity text: playerNum + "/" + capacity
font.pixelSize: 20 font.pixelSize: 18
font.bold: true font.bold: true
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
anchors.right: parent.right
anchors.rightMargin: 8
}
TapHandler {
gesturePolicy: TapHandler.WithinBounds
enabled: !opTimer.running && !outdated
onTapped: {
lobby_dialog.sourceComponent = roomDetailDialog;
lobby_dialog.item.roomData = {
roomId, roomName, gameMode, playerNum, capacity,
hasPassword, outdated,
};
lobby_dialog.item.roomConfig = config.roomConfigCache?.[config.serverAddr]?.[roomId]
lobby_drawer.open();
}
}
}
}
Component {
id: roomDetailDialog
ColumnLayout {
property var roomData: ({
roomName: "",
hasPassword: true,
})
property var roomConfig: undefined
signal finished()
anchors.fill: parent
anchors.margins: 16
Text {
text: roomData.roomName
font.pixelSize: 18
}
Text {
font.pixelSize: 18
text: {
let ret = luatr(roomData.gameMode);
ret += (' #' + roomData.roomId);
ret += (' ' + roomData.playerNum + '/' + roomData.capacity);
return ret;
}
}
Item { Layout.fillHeight: true }
// Dummy
Text {
text: "在未来的版本中这一块区域将增加更多实用的功能,<br>"+
"例如直接查看房间的各种配置信息<br>"+
"以及更多与禁将有关的实用功能!"+
"<font color='gray'>注绿色按钮为试做型UI 后面优化</font>"
font.pixelSize: 18
}
RowLayout {
Layout.fillWidth: true
Text {
visible: roomData.hasPassword
text: luatr("Please input room's password")
}
TextField {
id: passwordEdit
visible: roomData.hasPassword
Layout.fillWidth: true
onTextChanged: root.password = text;
}
Item {
visible: !roomData.hasPassword
Layout.fillWidth: true
} }
Button { Button {
text: (playerNum < capacity) ? luatr("Enter") : // text: "OK"
luatr("Observe") text: (roomData.playerNum < roomData.capacity) ? luatr("Enter") : luatr("Observe")
enabled: !opTimer.running && !outdated
onClicked: { onClicked: {
opTimer.start(); enterRoom(roomData.roomId, roomData.playerNum, roomData.capacity,
if (hasPassword) { roomData.hasPassword ? root.password : "");
lobby_dialog.sourceComponent = enterPassword; lobby_dialog.item.finished();
lobby_dialog.item.roomId = roomId;
lobby_dialog.item.playerNum = playerNum;
lobby_dialog.item.capacity = capacity;
lobby_drawer.open();
} else {
enterRoom(roomId, playerNum, capacity, "");
} }
} }
} }
Component.onCompleted: {
passwordEdit.text = "";
} }
} }
} }
@ -124,8 +163,7 @@ Item {
id: roomModel id: roomModel
} }
PersonalSettings { PersonalSettings {}
}
Timer { Timer {
id: opTimer id: opTimer
@ -134,55 +172,30 @@ Item {
ColumnLayout { ColumnLayout {
id: roomListLayout id: roomListLayout
anchors.top: parent.top height: root.height - 72
anchors.topMargin: 10 y: 16
anchors.horizontalCenter: parent.horizontalCenter anchors.left: parent.left
width: root.width * 0.48 anchors.leftMargin: root.width * 0.03 + root.width * 0.94 * 0.8 % 128 / 2
height: root.height - 80 width: {
let ret = root.width * 0.94 * 0.8;
ret -= ret % 128;
return ret;
}
clip: true
RowLayout {
Layout.fillWidth: true
Item { Layout.fillWidth: true }
Button { Button {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: luatr("Refresh Room List") text: luatr("Refresh Room List").arg(roomModel.count)
enabled: !opTimer.running enabled: !opTimer.running
onClicked: { onClicked: {
opTimer.start(); opTimer.start();
ClientInstance.notifyServer("RefreshRoomList", ""); ClientInstance.notifyServer("RefreshRoomList", "");
} }
} }
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
anchors.centerIn: parent
color: "#88EEEEEE"
radius: 16
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: luatr("Room List").arg(roomModel.count)
}
ListView {
id: roomList
height: parent.height * 0.9
width: parent.width * 0.95
contentHeight: roomDelegate.height * count
ScrollBar.vertical: ScrollBar {}
anchors.centerIn: parent
delegate: roomDelegate
clip: true
model: roomModel
}
}
}
}
Button { Button {
id: createRoomButton
anchors.bottom: buttonRow.top
anchors.right: parent.right
width: 120
display: AbstractButton.TextUnderIcon
icon.name: "media-playback-start"
text: luatr("Create Room") text: luatr("Create Room")
onClicked: { onClicked: {
lobby_dialog.sourceComponent = lobby_dialog.sourceComponent =
@ -192,11 +205,99 @@ Item {
config.replaying = false; config.replaying = false;
} }
} }
}
GridView {
id: roomList
cellWidth: 128
cellHeight: 128
Layout.fillHeight: true
Layout.fillWidth: true
ScrollBar.vertical: ScrollBar {}
delegate: roomDelegate
clip: true
model: roomModel
}
}
Rectangle {
id: serverInfoLayout
height: root.height - 112
y: 56
width: root.width * 0.94 * 0.2
anchors.right: parent.right
anchors.rightMargin: root.width * 0.03
// anchors.horizontalCenter: parent.horizontalCenter
color: "#88EEEEEE"
property bool chatShown: true
Flickable {
ScrollBar.vertical: ScrollBar {}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 10
flickableDirection: Flickable.VerticalFlick
width: parent.width - 10
height: parent.height - 10 - (parent.chatShown ? 200 : 0)
contentHeight: bulletin_info.height
clip: true
Text {
id: bulletin_info
width: parent.width
wrapMode: TextEdit.WordWrap
textFormat: Text.MarkdownText
text: config.serverMotd + "\n\n___\n\n" + luatr('Bulletin Info')
onLinkActivated: Qt.openUrlExternally(link);
}
}
MetroButton {
text: "🗨️" + (parent.chatShown ? "" : "")
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: lobbyChat.top
onClicked: {
parent.chatShown = !parent.chatShown
}
}
ChatBox {
id: lobbyChat
width: parent.width
height: parent.chatShown ? 200 : 0
Behavior on height { NumberAnimation { duration: 200 } }
anchors.bottom: parent.bottom
isLobby: true
color: "#88EEEEEE"
clip: true
}
}
RowLayout { RowLayout {
id: buttonRow id: buttonRow
anchors.right: parent.right anchors.left: parent.left
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
width: parent.width
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: childrenRect.width + 48
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.8; color: "white" }
GradientStop { position: 1.0; color: "transparent" }
}
Text {
x: 16; y: 4
font.pixelSize: 16
text: luatr("$OnlineInfo")
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
+ "Powered by FreeKill " + FkVersion
}
}
Item { Layout.fillWidth: true }
Button { Button {
text: luatr("Generals Overview") text: luatr("Generals Overview")
onClicked: { onClicked: {
@ -276,39 +377,6 @@ Item {
} }
} }
Component {
id: enterPassword
ColumnLayout {
property int roomId
property int playerNum
property int capacity
signal finished()
anchors.fill: parent
anchors.margins: 16
Text {
text: luatr("Please input room's password")
}
TextField {
id: passwordEdit
onTextChanged: root.password = text;
}
Button {
text: "OK"
onClicked: {
enterRoom(roomId, playerNum, capacity, root.password);
parent.finished();
}
}
Component.onCompleted: {
passwordEdit.text = "";
}
}
}
function enterRoom(roomId, playerNum, capacity, pw) { function enterRoom(roomId, playerNum, capacity, pw) {
config.replaying = false; config.replaying = false;
if (playerNum < capacity) { if (playerNum < capacity) {
@ -333,40 +401,15 @@ Item {
property int lobbyPlayerNum: 0 property int lobbyPlayerNum: 0
property int serverPlayerNum: 0 property int serverPlayerNum: 0
/*
function updateOnlineInfo() { function updateOnlineInfo() {
} }
onLobbyPlayerNumChanged: updateOnlineInfo(); onLobbyPlayerNumChanged: updateOnlineInfo();
onServerPlayerNumChanged: updateOnlineInfo(); onServerPlayerNumChanged: updateOnlineInfo();
Rectangle { /*
id: info */
color: "#88EEEEEE"
width: root.width * 0.23 // childrenRect.width + 8
height: childrenRect.height + 4
anchors.bottom: parent.bottom
anchors.left: parent.left
radius: 4
Text {
anchors.horizontalCenter: parent.horizontalCenter
x: 4; y: 2
font.pixelSize: 16
text: luatr("$OnlineInfo")
.arg(lobbyPlayerNum).arg(serverPlayerNum) + "\n"
+ "Powered by FreeKill " + FkVersion
}
}
ChatBox {
id: lobbyChat
anchors.bottom: info.top
width: info.width
height: root.height * 0.6
isLobby: true
color: "#88EEEEEE"
radius: 4
}
Danmaku { Danmaku {
id: danmaku id: danmaku

View File

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

21
genfkver.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# 为fk_ver文件追加编译时相关文件列表
# 类似其他项目中flist.txt的功能
cd $(dirname $0)
sed -i '2,$d' ./fk_ver
fn() {
for f in $(ls -1 $1); do
if [ -d $1/$f ]; then
fn $1/$f
else
echo $1/$f >> ./fk_ver
fi
done
}
fn lua
fn Fk
cd -

View File

@ -113,6 +113,38 @@
</message> </message>
</context> </context>
<context>
<name>QmlBackend</name>
<message>
<source>FreeKill</source>
<translation></translation>
</message>
<message>
<source>help: others logged in again with this name</source>
<translation></translation>
</message>
<message>
<source>help: unknown password error</source>
<translation></translation>
</message>
<message>
<source>help: you have been banned!</source>
<translation></translation>
</message>
<message>
<source>help: you have been temporarily banned!</source>
<translation></translation>
</message>
<message>
<source>help: user name not in whitelist</source>
<translation></translation>
</message>
<message>
<source>help: username or password error</source>
<translation></translation>
</message>
</context>
<context> <context>
<name>Init</name> <name>Init</name>
<message> <message>
@ -292,7 +324,15 @@
</message> </message>
<message> <message>
<source>others logged in again with this name</source> <source>others logged in again with this name</source>
<translation></translation> <translation></translation>
</message>
<message>
<source>unknown password error</source>
<translation></translation>
</message>
<message>
<source>user name not in whitelist</source>
<translation></translation>
</message> </message>
<message> <message>
<source>invalid user name</source> <source>invalid user name</source>

10
sgs Normal file
View File

@ -0,0 +1,10 @@
{
"banwords": [ "习近", "近平", "共产党", "介石", "刘少奇", "邓小平", "江泽民", "胡锦涛", "毛泽东" ],
"description": "新月杀 [0.4.15] 主力联机服务器!请素质交流、理性对局!<b>交流请去贴吧[新月杀]吧</b>",
"iconUrl": "http://175.178.66.93/ba-freekill.png",
"capacity": 800,
"tempBanTime": 15,
"motd": "6.5更新\n\n手杀测试服司马孚、成济、SP毌丘俭、李昭焦伯十周年一将24获奖版初稿宣公主、徐琨、令狐愚、司马孚\n\n6.3~6.4更新\n\nOL界法正、蒋琬暂不实现禁用手牌排序且点击“牌序”按钮并不影响真实顺序如不小心点击则通过点击武将上的“自若”标记查看真实顺序十周年韩嵩、马铁线下周姬、鄂焕\n\n5.31~6.1更新\n\n十周年乐诸葛果、小孙权、乐邹氏、乐祢衡、谋张绣\n\n\n\n请为新月杀的Github仓库点一个star吧感谢 https://github.com/Notify-ctrl/FreeKill\n\n## 点此查看游玩教程: https://fkbook-all-in-one.readthedocs.io",
"hiddenPacks": [],
"enableBots": false
}

View File

@ -8,10 +8,14 @@ set(freekill_SRCS
"network/server_socket.cpp" "network/server_socket.cpp"
"network/client_socket.cpp" "network/client_socket.cpp"
"network/router.cpp" "network/router.cpp"
"server/auth.cpp"
"server/server.cpp" "server/server.cpp"
"server/serverplayer.cpp" "server/serverplayer.cpp"
"server/roombase.cpp"
"server/lobby.cpp"
"server/room.cpp" "server/room.cpp"
"server/roomthread.cpp" "server/roomthread.cpp"
"server/scheduler.cpp"
"ui/qmlbackend.cpp" "ui/qmlbackend.cpp"
"swig/freekill-wrap.cxx" "swig/freekill-wrap.cxx"
) )
@ -21,7 +25,7 @@ if (NOT DEFINED FK_SERVER_ONLY)
"client/client.cpp" "client/client.cpp"
"client/clientplayer.cpp" "client/clientplayer.cpp"
"client/replayer.cpp" "client/replayer.cpp"
"ui/mod.cpp" # "ui/mod.cpp"
) )
endif () endif ()

View File

@ -1,13 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "client.h" #include "client/client.h"
#include "client_socket.h" #include "client/clientplayer.h"
#include "clientplayer.h" #include "ui/qmlbackend.h"
#include "qmlbackend.h" #include "core/util.h"
#include "util.h" #include "server/server.h"
#include "server.h" #include "network/client_socket.h"
#include <qforeach.h>
#include <qlogging.h>
Client *ClientInstance = nullptr; Client *ClientInstance = nullptr;
ClientPlayer *Self = nullptr; ClientPlayer *Self = nullptr;

View File

@ -3,12 +3,11 @@
#ifndef _CLIENT_H #ifndef _CLIENT_H
#define _CLIENT_H #define _CLIENT_H
#include "router.h" #include "network/router.h"
#include "clientplayer.h" #include "client/clientplayer.h"
#include <qfilesystemwatcher.h>
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#endif #endif
class Client : public QObject { class Client : public QObject {

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "clientplayer.h" #include "client/clientplayer.h"
ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) { ClientPlayer::ClientPlayer(int id, QObject *parent) : Player(parent) {
setId(id); setId(id);

View File

@ -3,7 +3,7 @@
#ifndef _CLIENTPLAYER_H #ifndef _CLIENTPLAYER_H
#define _CLIENTPLAYER_H #define _CLIENTPLAYER_H
#include "player.h" #include "core/player.h"
class ClientPlayer : public Player { class ClientPlayer : public Player {
Q_OBJECT Q_OBJECT

View File

@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "replayer.h" #include "client/replayer.h"
#include "client.h" #include "client/client.h"
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#include "util.h" #include "core/util.h"
Replayer::Replayer(QObject *parent, const QString &filename) : Replayer::Replayer(QObject *parent, const QString &filename) :
QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""), QThread(parent), fileName(filename), roomSettings(""), origPlayerInfo(""),

View File

@ -1,10 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "packman.h" #include "core/packman.h"
#include "git2.h" #include "git2.h"
#include "util.h" #include "core/util.h"
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#include <qjsondocument.h>
PackMan *Pacman; PackMan *Pacman;
@ -70,13 +69,16 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
auto obj = e.toObject(); auto obj = e.toObject();
auto name = obj["name"].toString(); auto name = obj["name"].toString();
auto url = obj["url"].toString(); auto url = obj["url"].toString();
#ifndef FK_SERVER_ONLY bool toast_showed = false;
Backend->showToast(tr("[%1/%2] upgrading package '%3'").arg(i).arg(arr.count()).arg(name));
#endif
if (SelectFromDatabase( if (SelectFromDatabase(
db, db,
QString("SELECT name FROM packages WHERE name='%1';").arg(name)) QString("SELECT name FROM packages WHERE name='%1';").arg(name))
.isEmpty()) { .isEmpty()) {
#ifndef FK_SERVER_ONLY
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
.arg(i).arg(arr.count()).arg(name));
toast_showed = true;
#endif
downloadNewPack(url); downloadNewPack(url);
} }
ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'") ExecSQL(db, QString("UPDATE packages SET hash='%1' WHERE name='%2'")
@ -85,6 +87,11 @@ void PackMan::loadSummary(const QString &jsonData, bool useThread) {
enablePack(name); enablePack(name);
if (head(name) != obj["hash"].toString()) { if (head(name) != obj["hash"].toString()) {
#ifndef FK_SERVER_ONLY
if (!toast_showed)
Backend->showToast(tr("[%1/%2] upgrading package '%3'")
.arg(i).arg(arr.count()).arg(name));
#endif
updatePack(name); updatePack(name);
} }
} }
@ -171,7 +178,7 @@ void PackMan::updatePack(const QString &pack) {
if (error != 0) { if (error != 0) {
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
if (Backend != nullptr) { if (Backend != nullptr) {
Backend->showToast(tr("packages/%1: some error occured.").arg(pack)); Backend->dialog("critical", tr("packages/%1: some error occured.").arg(pack));
} }
#endif #endif
return; return;
@ -193,7 +200,7 @@ void PackMan::upgradePack(const QString &pack) {
if (error != 0) { if (error != 0) {
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
if (Backend != nullptr) { if (Backend != nullptr) {
Backend->showToast(tr("packages/%1: some error occured.").arg(pack)); Backend->showDialog("critical", tr("packages/%1: some error occured.").arg(pack));
} }
#endif #endif
return; return;

View File

@ -3,8 +3,6 @@
#ifndef _PACKMAN_H #ifndef _PACKMAN_H
#define _PACKMAN_H #define _PACKMAN_H
#include <qtmetamacros.h>
// 管理拓展包所需的类本质上是libgit2接口的再封装。 // 管理拓展包所需的类本质上是libgit2接口的再封装。
class PackMan : public QObject { class PackMan : public QObject {
Q_OBJECT Q_OBJECT

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "player.h" #include "core/player.h"
Player::Player(QObject *parent) Player::Player(QObject *parent)
: QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false), : QObject(parent), id(0), state(Player::Invalid), totalGameTime(0), ready(false),

View File

@ -1,10 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "util.h" #include "core/util.h"
#include "packman.h" #include "core/packman.h"
#include <qcryptographichash.h>
#include <qnamespace.h>
#include <qregularexpression.h>
#include <QSysInfo> #include <QSysInfo>
extern "C" { extern "C" {
@ -172,6 +169,17 @@ static void writeDirMD5(QFile &dest, const QString &dir,
} }
} }
static void writeFkVerMD5(QFile &dest) {
QFile flist("fk_ver");
if (flist.exists() && flist.open(QIODevice::ReadOnly)) {
while (true) {
QByteArray bytes = flist.readLine().simplified();
if (bytes.isNull()) break;
writeFileMD5(dest, bytes);
}
}
}
QString calcFileMD5() { QString calcFileMD5() {
// First, generate flist.txt // First, generate flist.txt
// flist.txt is a file contains all md5sum for code files // flist.txt is a file contains all md5sum for code files
@ -180,12 +188,13 @@ QString calcFileMD5() {
qFatal("Cannot open flist.txt. Quitting."); qFatal("Cannot open flist.txt. Quitting.");
} }
writeFkVerMD5(flist);
writeDirMD5(flist, "packages", "*.lua"); writeDirMD5(flist, "packages", "*.lua");
writeDirMD5(flist, "packages", "*.qml"); writeDirMD5(flist, "packages", "*.qml");
writeDirMD5(flist, "packages", "*.js"); writeDirMD5(flist, "packages", "*.js");
writeDirMD5(flist, "lua", "*.lua"); // writeDirMD5(flist, "lua", "*.lua");
writeDirMD5(flist, "Fk", "*.qml"); // writeDirMD5(flist, "Fk", "*.qml");
writeDirMD5(flist, "Fk", "*.js"); // writeDirMD5(flist, "Fk", "*.js");
// then, return flist.txt's md5 // then, return flist.txt's md5
flist.close(); flist.close();

View File

@ -1,14 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "client.h" #include "client/client.h"
#include "util.h" #include "core/util.h"
using namespace fkShell; using namespace fkShell;
#include "packman.h" #include "core/packman.h"
#include "server.h" #include "server/server.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include "shell.h" #include "server/shell.h"
#endif #endif
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32)
@ -22,7 +22,7 @@ using namespace fkShell;
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
#include <QQuickStyle> #include <QQuickStyle>
#endif #endif
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#endif #endif
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
@ -113,10 +113,10 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
break; break;
} }
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day()); fprintf(stderr, "%02d/%02d ", date.month(), date.day());
fprintf(stderr, "%s ", fprintf(stderr, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData()); QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
fprintf(file, "\r%02d/%02d ", date.month(), date.day()); fprintf(file, "%02d/%02d ", date.month(), date.day());
fprintf(file, "%s ", fprintf(file, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData()); QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
@ -150,8 +150,7 @@ void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
"C", localMsg.constData()); "C", localMsg.constData());
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
if (Backend != nullptr) { if (Backend != nullptr) {
Backend->notifyUI( Backend->notifyUI("ErrorDialog",
"ErrorDialog",
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg)); QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
} }
#endif #endif
@ -329,6 +328,7 @@ int main(int argc, char *argv[]) {
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
system = "Android"; system = "Android";
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
qputenv("QT_MEDIA_BACKEND", "windows");
system = "Win"; system = "Win";
::system("chcp 65001"); ::system("chcp 65001");
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)

View File

@ -1,9 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "client_socket.h" #include "network/client_socket.h"
#include <openssl/aes.h> #include <openssl/aes.h>
#include <qabstractsocket.h>
#include <qrandom.h>
ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) {
aes_ready = false; aes_ready = false;
@ -35,7 +33,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) {
void ClientSocket::getMessage() { void ClientSocket::getMessage() {
while (socket->canReadLine()) { while (socket->canReadLine()) {
auto msg = socket->readLine(); auto msg = socket->readLine();
msg = aesDecrypt(msg); msg = aesDec(msg);
if (msg.startsWith("Compressed")) { if (msg.startsWith("Compressed")) {
msg = msg.sliced(10); msg = msg.sliced(10);
msg = qUncompress(QByteArray::fromBase64(msg)); msg = qUncompress(QByteArray::fromBase64(msg));
@ -54,9 +52,9 @@ void ClientSocket::send(const QByteArray &msg) {
if (msg.length() >= 1024) { if (msg.length() >= 1024) {
auto comp = qCompress(msg); auto comp = qCompress(msg);
_msg = "Compressed" + comp.toBase64(); _msg = "Compressed" + comp.toBase64();
_msg = aesEncrypt(_msg) + "\n"; _msg = aesEnc(_msg) + "\n";
} else { } else {
_msg = aesEncrypt(msg) + "\n"; _msg = aesEnc(msg) + "\n";
} }
socket->write(_msg); socket->write(_msg);
@ -156,7 +154,7 @@ void ClientSocket::installAESKey(const QByteArray &key) {
aes_ready = true; aes_ready = true;
} }
QByteArray ClientSocket::aesEncrypt(const QByteArray &in) { QByteArray ClientSocket::aesEnc(const QByteArray &in) {
if (!aes_ready) { if (!aes_ready) {
return in; return in;
} }
@ -182,7 +180,7 @@ QByteArray ClientSocket::aesEncrypt(const QByteArray &in) {
return iv + out.toBase64(); return iv + out.toBase64();
} }
QByteArray ClientSocket::aesDecrypt(const QByteArray &in) { QByteArray ClientSocket::aesDec(const QByteArray &in) {
if (!aes_ready) { if (!aes_ready) {
return in; return in;
} }

View File

@ -33,8 +33,8 @@ private slots:
void raiseError(QAbstractSocket::SocketError error); void raiseError(QAbstractSocket::SocketError error);
private: private:
QByteArray aesEncrypt(const QByteArray &in); QByteArray aesEnc(const QByteArray &in);
QByteArray aesDecrypt(const QByteArray &out); QByteArray aesDec(const QByteArray &out);
AES_KEY aes_key; AES_KEY aes_key;
bool aes_ready; bool aes_ready;
QTcpSocket *socket; QTcpSocket *socket;

View File

@ -1,13 +1,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "router.h" #include "network/router.h"
#include "client.h" #include "client/client.h"
#include "client_socket.h" #include "network/client_socket.h"
#include "roomthread.h" #include "server/roomthread.h"
#include <qjsondocument.h> #include "server/server.h"
#include "server.h" #include "server/serverplayer.h"
#include "serverplayer.h" #include "core/util.h"
#include "util.h"
Router::Router(QObject *parent, ClientSocket *socket, RouterType type) Router::Router(QObject *parent, ClientSocket *socket, RouterType type)
: QObject(parent) { : QObject(parent) {
@ -160,7 +159,7 @@ void Router::handlePacket(const QByteArray &rawPacket) {
return; return;
} }
Room *room = player->getRoom(); auto room = player->getRoom();
room->handlePacket(player, command, jsonData); room->handlePacket(player, command, jsonData);
} }
} else if (type & TYPE_REQUEST) { } else if (type & TYPE_REQUEST) {
@ -180,10 +179,13 @@ void Router::handlePacket(const QByteArray &rawPacket) {
ServerPlayer *player = qobject_cast<ServerPlayer *>(parent()); ServerPlayer *player = qobject_cast<ServerPlayer *>(parent());
player->setThinking(false); player->setThinking(false);
// qDebug() << "wake up!"; auto _room = player->getRoom();
auto room = player->getRoom(); if (!_room->isLobby()) {
auto room = qobject_cast<Room *>(_room);
if (room->getThread()) { if (room->getThread()) {
room->getThread()->wakeUp(); room->getThread()->wakeUp(room->getId());
// TODO: signal
}
} }
if (requestId != this->expectedReplyId) if (requestId != this->expectedReplyId)

View File

@ -1,15 +1,22 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "server_socket.h" #include "network/server_socket.h"
#include "client_socket.h" #include "network/client_socket.h"
#include "server/server.h"
#include "core/util.h"
ServerSocket::ServerSocket() { ServerSocket::ServerSocket(QObject *parent) : QObject(parent) {
server = new QTcpServer(this); server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, connect(server, &QTcpServer::newConnection, this,
&ServerSocket::processNewConnection); &ServerSocket::processNewConnection);
udpSocket = new QUdpSocket(this);
connect(udpSocket, &QUdpSocket::readyRead,
this, &ServerSocket::readPendingDatagrams);
} }
bool ServerSocket::listen(const QHostAddress &address, ushort port) { bool ServerSocket::listen(const QHostAddress &address, ushort port) {
udpSocket->bind(port);
return server->listen(address, port); return server->listen(address, port);
} }
@ -20,3 +27,29 @@ void ServerSocket::processNewConnection() {
[connection]() { connection->deleteLater(); }); [connection]() { connection->deleteLater(); });
emit new_connection(connection); emit new_connection(connection);
} }
void ServerSocket::readPendingDatagrams() {
while (udpSocket->hasPendingDatagrams()) {
QNetworkDatagram datagram = udpSocket->receiveDatagram();
if (datagram.isValid()) {
processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
}
}
}
void ServerSocket::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
auto server = qobject_cast<Server *>(parent());
if (msg == "fkDetectServer") {
udpSocket->writeDatagram("me", addr, port);
} else if (msg.startsWith("fkGetDetail,")) {
udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
FK_VERSION,
server->getConfig("iconUrl"),
server->getConfig("description"),
server->getConfig("capacity"),
server->getPlayers().count(),
msg.sliced(12).constData(),
})), addr, port);
}
udpSocket->flush();
}

View File

@ -10,7 +10,7 @@ class ServerSocket : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ServerSocket(); ServerSocket(QObject *parent = nullptr);
bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u); bool listen(const QHostAddress &address = QHostAddress::Any, ushort port = 9527u);
@ -20,9 +20,12 @@ signals:
private slots: private slots:
// 新建一个ClientSocket然后立刻交给Server相关函数处理。 // 新建一个ClientSocket然后立刻交给Server相关函数处理。
void processNewConnection(); void processNewConnection();
void readPendingDatagrams();
private: private:
QTcpServer *server; QTcpServer *server;
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
}; };
#endif // _SERVER_SOCKET_H #endif // _SERVER_SOCKET_H

195
src/server/auth.cpp Normal file
View File

@ -0,0 +1,195 @@
#include "server/auth.h"
#include "server/server.h"
#include "server/serverplayer.h"
#include "core/util.h"
#include "network/client_socket.h"
AuthManager::AuthManager(QObject *parent) : QObject(parent) {
rsa = initRSA();
QFile file("server/rsa_pub");
file.open(QIODevice::ReadOnly);
QTextStream in(&file);
public_key = in.readAll();
}
AuthManager::~AuthManager() noexcept {
RSA_free(rsa);
}
RSA *AuthManager::initRSA() {
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;
}
bool AuthManager::checkClientVersion(ClientSocket *client, const QString &cver) {
auto server = qobject_cast<Server *>(parent());
auto client_ver = QVersionNumber::fromString(cver);
auto ver = QVersionNumber::fromString(FK_VERSION);
int cmp = QVersionNumber::compare(ver, client_ver);
if (cmp != 0) {
auto errmsg = QString();
if (cmp < 0) {
errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
.arg(FK_VERSION, "1");
} else {
errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
.arg(FK_VERSION, "1");
}
server->sendEarlyPacket(client, "ErrorDlg", errmsg);
client->disconnectFromHost();
return false;
}
return true;
}
QJsonObject AuthManager::queryUserInfo(ClientSocket *client, const QString &name,
const QByteArray &password) {
auto server = qobject_cast<Server *>(parent());
auto db = server->getDatabase();
auto pw = password;
auto sql_find = QString("SELECT * FROM userinfo WHERE name='%1';")
.arg(name);
auto result = SelectFromDatabase(db, sql_find);
if (result.isEmpty()) {
auto salt_gen = QRandomGenerator::securelySeeded();
auto salt = QByteArray::number(salt_gen(), 16);
pw.append(salt);
auto passwordHash =
QCryptographicHash::hash(pw, QCryptographicHash::Sha256).toHex();
auto sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
.arg(name).arg(QString(passwordHash))
.arg(salt).arg("liubei").arg(client->peerAddress())
.arg("FALSE");
ExecSQL(db, sql_reg);
result = SelectFromDatabase(db, sql_find); // refresh result
auto obj = result[0].toObject();
auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
ExecSQL(db, info_update);
}
return result[0].toObject();
}
QJsonObject AuthManager::checkPassword(ClientSocket *client, const QString &name,
const QString &password) {
auto server = qobject_cast<Server *>(parent());
bool passed = false;
QString error_msg;
QJsonObject obj;
int id;
QByteArray salt;
QByteArray passwordHash;
auto players = server->getPlayers();
auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
unsigned char buf[4096] = {0};
RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
buf, rsa, RSA_PKCS1_PADDING);
auto decrypted_pw =
QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
if (decrypted_pw.length() > 32) {
auto aes_bytes = decrypted_pw.first(32);
// tell client to install aes key
server->sendEarlyPacket(client, "InstallKey", "");
client->installAESKey(aes_bytes);
decrypted_pw.remove(0, 32);
} else {
// FIXME
// decrypted_pw = "\xFF";
error_msg = "unknown password error";
goto FAIL;
}
if (!CheckSqlString(name) || !server->checkBanWord(name)) {
error_msg = "invalid user name";
goto FAIL;
}
if (server->getConfig("whitelist").isArray() &&
!server->getConfig("whitelist").toArray().toVariantList().contains(name)) {
error_msg = "user name not in whitelist";
goto FAIL;
}
obj = queryUserInfo(client, name, decrypted_pw);
// check ban account
id = obj["id"].toString().toInt();
passed = obj["banned"].toString().toInt() == 0;
if (!passed) {
error_msg = "you have been banned!";
goto FAIL;
}
// check if password is the same
salt = obj["salt"].toString().toLatin1();
decrypted_pw.append(salt);
passwordHash =
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256).toHex();
passed = (passwordHash == obj["password"].toString());
if (!passed) {
error_msg = "username or password error";
goto FAIL;
}
if (players.value(id)) {
auto player = players.value(id);
// 顶号机制,如果在线的话就让他变成不在线
if (player->getState() == Player::Online) {
player->doNotify("ErrorDlg", "others logged in again with this name");
emit player->kicked();
}
if (player->getState() == Player::Offline) {
player->reconnect(client);
passed = true;
return QJsonObject();
} else {
error_msg = "others logged in with this name";
passed = false;
}
}
FAIL:
if (!passed) {
qInfo() << client->peerAddress() << "lost connection:" << error_msg;
server->sendEarlyPacket(client, "ErrorDlg", error_msg);
client->disconnectFromHost();
return QJsonObject();
}
return obj;
}

27
src/server/auth.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef _AUTH_H
#define _AUTH_H
#include <openssl/rsa.h>
#include <openssl/pem.h>
class ClientSocket;
class AuthManager : public QObject {
Q_OBJECT
public:
AuthManager(QObject *parent = nullptr);
~AuthManager() noexcept;
auto getPublicKey() const { return public_key; }
bool checkClientVersion(ClientSocket *client, const QString &ver);
QJsonObject checkPassword(ClientSocket *client, const QString &name, const QString &password);
private:
RSA *rsa;
QString public_key;
static RSA *initRSA();
QJsonObject queryUserInfo(ClientSocket *client, const QString &name, const QByteArray &password);
};
#endif // _AUTH_H

162
src/server/lobby.cpp Normal file
View File

@ -0,0 +1,162 @@
#include "server/lobby.h"
#include "server/server.h"
#include "server/serverplayer.h"
#include "core/util.h"
Lobby::Lobby(Server *server) {
this->server = server;
setParent(server);
}
void Lobby::addPlayer(ServerPlayer *player) {
if (!player) return;
players.append(player);
player->setRoom(this);
if (player->getState() == Player::Robot) {
removePlayer(player);
player->deleteLater();
} else {
player->doNotify("EnterLobby", "[]");
}
server->updateOnlineInfo();
}
void Lobby::removePlayer(ServerPlayer *player) {
players.removeOne(player);
server->updateOnlineInfo();
}
void Lobby::updateAvatar(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto avatar = arr[0].toString();
if (CheckSqlString(avatar)) {
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
.arg(avatar)
.arg(sender->getId());
ExecSQL(ServerInstance->getDatabase(), sql);
sender->setAvatar(avatar);
sender->doNotify("UpdateAvatar", avatar);
}
}
void Lobby::updatePassword(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto oldpw = arr[0].toString();
auto newpw = arr[1].toString();
auto sql_find =
QString("SELECT password, salt FROM userinfo WHERE id=%1;")
.arg(sender->getId());
auto passed = false;
auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
auto result = arr2[0].toObject();
passed = (result["password"].toString() ==
QCryptographicHash::hash(
oldpw.append(result["salt"].toString()).toLatin1(),
QCryptographicHash::Sha256)
.toHex());
if (passed) {
auto sql_update =
QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
.arg(QCryptographicHash::hash(
newpw.append(result["salt"].toString()).toLatin1(),
QCryptographicHash::Sha256)
.toHex())
.arg(sender->getId());
ExecSQL(ServerInstance->getDatabase(), sql_update);
}
sender->doNotify("UpdatePassword", passed ? "1" : "0");
}
void Lobby::createRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto name = arr[0].toString();
auto capacity = arr[1].toInt();
auto timeout = arr[2].toInt();
auto settings =
QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
ServerInstance->createRoom(sender, name, capacity, timeout, settings);
}
void Lobby::getRoomConfig(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto roomId = arr[0].toInt();
auto room = ServerInstance->findRoom(roomId);
if (room) {
auto settings = room->getSettings();
// 手搓JSON数组 跳过编码解码
sender->doNotify("GetRoomConfig", QString("[%1,%2]").arg(roomId).arg(settings));
} else {
sender->doNotify("ErrorMsg", "no such room");
}
}
void Lobby::enterRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto roomId = arr[0].toInt();
auto room = ServerInstance->findRoom(roomId);
if (room) {
auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString();
if (password.isEmpty() || arr[1].toString() == password) {
if (room->isOutdated()) {
sender->doNotify("ErrorMsg", "room is outdated");
} else {
room->addPlayer(sender);
}
} else {
sender->doNotify("ErrorMsg", "room password error");
}
} else {
sender->doNotify("ErrorMsg", "no such room");
}
}
void Lobby::observeRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto roomId = arr[0].toInt();
auto room = ServerInstance->findRoom(roomId);
if (room) {
auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString();
if (password.isEmpty() || arr[1].toString() == password) {
if (room->isOutdated()) {
sender->doNotify("ErrorMsg", "room is outdated");
} else {
room->addObserver(sender);
}
} else {
sender->doNotify("ErrorMsg", "room password error");
}
} else {
sender->doNotify("ErrorMsg", "no such room");
}
}
void Lobby::refreshRoomList(ServerPlayer *sender, const QString &) {
ServerInstance->updateRoomList(sender);
};
typedef void (Lobby::*room_cb)(ServerPlayer *, const QString &);
void Lobby::handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData) {
static const QMap<QString, room_cb> lobby_actions = {
{"UpdateAvatar", &Lobby::updateAvatar},
{"UpdatePassword", &Lobby::updatePassword},
{"CreateRoom", &Lobby::createRoom},
{"GetRoomConfig", &Lobby::getRoomConfig},
{"EnterRoom", &Lobby::enterRoom},
{"ObserveRoom", &Lobby::observeRoom},
{"RefreshRoomList", &Lobby::refreshRoomList},
{"Chat", &Lobby::chat},
};
auto func = lobby_actions[command];
if (func) (this->*func)(sender, jsonData);
}

27
src/server/lobby.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef _LOBBY_H
#define _LOBBY_H
#include "server/roombase.h"
class Lobby : public RoomBase {
Q_OBJECT
public:
Lobby(Server *server);
void addPlayer(ServerPlayer *player);
void removePlayer(ServerPlayer *player);
void handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData);
private:
// for handle packet
void updateAvatar(ServerPlayer *, const QString &);
void updatePassword(ServerPlayer *, const QString &);
void createRoom(ServerPlayer *, const QString &);
void getRoomConfig(ServerPlayer *, const QString &);
void enterRoom(ServerPlayer *, const QString &);
void observeRoom(ServerPlayer *, const QString &);
void refreshRoomList(ServerPlayer *, const QString &);
};
#endif // _LOBBY_H

View File

@ -1,28 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "room.h" #include "server/room.h"
#include "server/lobby.h"
#include <qjsonarray.h>
#include <qjsondocument.h>
#ifdef FK_SERVER_ONLY #ifdef FK_SERVER_ONLY
static void *ClientInstance = nullptr; static void *ClientInstance = nullptr;
#else #else
#include "client.h" #include "client/client.h"
#endif #endif
#include "client_socket.h" #include "network/client_socket.h"
#include "roomthread.h" #include "server/roomthread.h"
#include "server.h" #include "server/server.h"
#include "serverplayer.h" #include "server/serverplayer.h"
#include "util.h" #include "core/util.h"
Room::Room(RoomThread *m_thread) { Room::Room(RoomThread *m_thread) {
auto server = ServerInstance; auto server = ServerInstance;
id = server->nextRoomId; id = server->nextRoomId;
server->nextRoomId++; server->nextRoomId++;
this->server = server; this->server = server;
setThread(m_thread);
if (m_thread) { // In case of lobby if (m_thread) { // In case of lobby
m_thread->addRoom(this); m_thread->addRoom(this);
} }
@ -36,14 +33,8 @@ Room::Room(RoomThread *m_thread) {
m_ready = true; m_ready = true;
// 如果是普通房间而不是大厅就初始化Lua否则置Lua为nullptr connect(this, &Room::playerAdded, server->lobby(), &Lobby::removePlayer);
if (!isLobby()) { connect(this, &Room::playerRemoved, server->lobby(), &Lobby::addPlayer);
// 如果不是大厅,那么:
// * 只要房间添加人了,那么从大厅中移掉这个人
// * 只要有人离开房间,那就把他加到大厅去
connect(this, &Room::playerAdded, server->lobby(), &Room::removePlayer);
connect(this, &Room::playerRemoved, server->lobby(), &Room::addPlayer);
}
} }
Room::~Room() { Room::~Room() {
@ -56,8 +47,6 @@ Room::~Room() {
} }
} }
Server *Room::getServer() const { return server; }
RoomThread *Room::getThread() const { return m_thread; } RoomThread *Room::getThread() const { return m_thread; }
void Room::setThread(RoomThread *t) { void Room::setThread(RoomThread *t) {
@ -71,8 +60,6 @@ int Room::getId() const { return id; }
void Room::setId(int id) { this->id = id; } void Room::setId(int id) { this->id = id; }
bool Room::isLobby() const { return id == 0; }
QString Room::getName() const { return name; } QString Room::getName() const { return name; }
void Room::setName(const QString &name) { this->name = name; } void Room::setName(const QString &name) { this->name = name; }
@ -88,9 +75,6 @@ const QByteArray Room::getSettings() const { return settings; }
void Room::setSettings(QByteArray settings) { this->settings = settings; } void Room::setSettings(QByteArray settings) { this->settings = settings; }
bool Room::isAbandoned() const { bool Room::isAbandoned() const {
if (isLobby())
return false;
if (players.isEmpty()) if (players.isEmpty())
return true; return true;
@ -151,27 +135,16 @@ void Room::addPlayer(ServerPlayer *player) {
auto mode = settings["gameMode"].toString(); auto mode = settings["gameMode"].toString();
// 告诉房里所有玩家有新人进来了 // 告诉房里所有玩家有新人进来了
if (!isLobby()) {
jsonData << player->getId(); jsonData << player->getId();
jsonData << player->getScreenName(); jsonData << player->getScreenName();
jsonData << player->getAvatar(); jsonData << player->getAvatar();
jsonData << player->isReady(); jsonData << player->isReady();
jsonData << player->getTotalGameTime(); jsonData << player->getTotalGameTime();
doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData)); doBroadcastNotify(getPlayers(), "AddPlayer", JsonArray2Bytes(jsonData));
}
players.append(player); players.append(player);
player->setRoom(this); player->setRoom(this);
if (isLobby()) {
// 有机器人进入大厅(可能因为被踢),那么改为销毁
if (player->getState() == Player::Robot) {
removePlayer(player);
player->deleteLater();
} else {
player->doNotify("EnterLobby", "[]");
}
} else {
// Second, let the player enter room and add other players // Second, let the player enter room and add other players
jsonData = QJsonArray(); jsonData = QJsonArray();
jsonData << this->capacity; jsonData << this->capacity;
@ -216,7 +189,6 @@ void Room::addPlayer(ServerPlayer *player) {
// 玩家手动启动 // 玩家手动启动
// if (isFull() && !gameStarted) // if (isFull() && !gameStarted)
// start(); // start();
}
emit playerAdded(player); emit playerAdded(player);
} }
@ -251,12 +223,7 @@ void Room::removePlayer(ServerPlayer *player) {
} }
emit playerRemoved(player); emit playerRemoved(player);
if (isLobby()) doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes({ player->getId() }));
return;
QJsonArray jsonData;
jsonData << player->getId();
doBroadcastNotify(getPlayers(), "RemovePlayer", JsonArray2Bytes(jsonData));
} else { } else {
// 否则给跑路玩家召唤个AI代打 // 否则给跑路玩家召唤个AI代打
// TODO: if the player is died.. // TODO: if the player is died..
@ -287,7 +254,7 @@ void Room::removePlayer(ServerPlayer *player) {
// 原先的跑路机器人会在游戏结束后自动销毁掉 // 原先的跑路机器人会在游戏结束后自动销毁掉
server->addPlayer(runner); server->addPlayer(runner);
m_thread->wakeUp(); // m_thread->wakeUp();
// 发出信号,让大厅添加这个人 // 发出信号,让大厅添加这个人
emit playerRemoved(runner); emit playerRemoved(runner);
@ -312,22 +279,6 @@ void Room::removePlayer(ServerPlayer *player) {
} }
} }
QList<ServerPlayer *> Room::getPlayers() const { return players; }
QList<ServerPlayer *> Room::getOtherPlayers(ServerPlayer *expect) const {
QList<ServerPlayer *> others = getPlayers();
others.removeOne(expect);
return others;
}
ServerPlayer *Room::findPlayer(int id) const {
foreach (ServerPlayer *p, players) {
if (p->getId() == id)
return p;
}
return nullptr;
}
void Room::addObserver(ServerPlayer *player) { void Room::addObserver(ServerPlayer *player) {
// 首先只能旁观在运行的房间因为旁观是由Lua处理的 // 首先只能旁观在运行的房间因为旁观是由Lua处理的
if (!gameStarted) { if (!gameStarted) {
@ -371,6 +322,10 @@ int Room::getTimeout() const { return timeout; }
void Room::setTimeout(int timeout) { this->timeout = timeout; } void Room::setTimeout(int timeout) { this->timeout = timeout; }
void Room::delay(int ms) {
m_thread->delay(id, ms);
}
bool Room::isOutdated() { bool Room::isOutdated() {
bool ret = md5 != server->getMd5(); bool ret = md5 != server->getMd5();
if (ret) md5 = ""; if (ret) md5 = "";
@ -379,42 +334,6 @@ bool Room::isOutdated() {
bool Room::isStarted() const { return gameStarted; } bool Room::isStarted() const { return gameStarted; }
void Room::doBroadcastNotify(const QList<ServerPlayer *> targets,
const QString &command, const QString &jsonData) {
foreach (ServerPlayer *p, targets) {
p->doNotify(command, jsonData);
}
}
void Room::chat(ServerPlayer *sender, const QString &jsonData) {
auto doc = String2Json(jsonData).object();
auto type = doc["type"].toInt();
doc["sender"] = sender->getId();
// 屏蔽.号防止有人在HTML文本发链接而正常发链接看不出来有啥改动
auto msg = doc["msg"].toString();
msg.replace(".", "");
// 300字限制与客户端相同
msg.erase(msg.begin() + 300, msg.end());
doc["msg"] = msg;
if (!server->checkBanWord(msg)) {
return;
}
if (type == 1) {
doc["userName"] = sender->getScreenName();
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
} else {
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
doBroadcastNotify(observers, "Chat", json);
}
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
doc["msg"].toString().toUtf8().constData());
}
static const QString findWinRate = static const QString findWinRate =
QString("SELECT win, lose, draw " QString("SELECT win, lose, draw "
"FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';"); "FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';");
@ -551,18 +470,18 @@ void Room::updatePlayerGameData(int id, const QString &mode) {
auto room = player->getRoom(); auto room = player->getRoom();
player->setGameData(total, win, run); player->setGameData(total, win, run);
auto data_arr = QJsonArray({ player->getId(), total, win, run }); auto data_arr = QJsonArray({ player->getId(), total, win, run });
if (!room->isLobby()) {
room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr)); room->doBroadcastNotify(room->getPlayers(), "UpdateGameData", JsonArray2Bytes(data_arr));
} }
}
void Room::gameOver() { void Room::gameOver() {
if (!gameStarted) return; if (!gameStarted) return;
insideGameOver = true;
gameStarted = false; gameStarted = false;
runned_players.clear(); runned_players.clear();
// 清理所有状态不是“在线”的玩家,增加逃率、游戏时长 // 清理所有状态不是“在线”的玩家,增加逃率、游戏时长
auto settings = QJsonDocument::fromJson(this->settings); auto settings = QJsonDocument::fromJson(this->settings);
auto mode = settings["gameMode"].toString(); auto mode = settings["gameMode"].toString();
server->beginTransaction();
foreach (ServerPlayer *p, players) { foreach (ServerPlayer *p, players) {
auto pid = p->getId(); auto pid = p->getId();
@ -578,7 +497,7 @@ void Room::gameOver() {
realPlayer->doNotify("AddTotalGameTime", bytes); realPlayer->doNotify("AddTotalGameTime", bytes);
} }
// 摸了,这么写总之不会有问题 // 将游戏时间更新到数据库中
auto info_update = QString("UPDATE usergameinfo SET totalGameTime = " auto info_update = QString("UPDATE usergameinfo SET totalGameTime = "
"IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time); "IIF(totalGameTime IS NULL, %2, totalGameTime + %2) WHERE id = %1;").arg(pid).arg(time);
ExecSQL(server->getDatabase(), info_update); ExecSQL(server->getDatabase(), info_update);
@ -587,18 +506,13 @@ void Room::gameOver() {
if (p->getState() != Player::Online) { if (p->getState() != Player::Online) {
if (p->getState() == Player::Offline) { if (p->getState() == Player::Offline) {
addRunRate(pid, mode); addRunRate(pid, mode);
// addRunRate(pid, mode);
server->temporarilyBan(pid); server->temporarilyBan(pid);
} }
p->deleteLater(); p->deleteLater();
} }
} }
// 旁观者不能在这清除因为removePlayer逻辑不一样 server->endTransaction();
// observers.clear(); insideGameOver = true;
// 玩家也不能在这里清除,因为要能返回原来房间继续玩呢
// players.clear();
// owner = nullptr;
// clearRequest();
} }
void Room::manuallyStart() { void Room::manuallyStart() {
@ -620,7 +534,6 @@ void Room::pushRequest(const QString &req) {
} }
void Room::addRejectId(int id) { void Room::addRejectId(int id) {
if (isLobby()) return;
rejected_players << id; rejected_players << id;
} }
@ -629,184 +542,84 @@ void Room::removeRejectId(int id) {
} }
// ------------------------------------------------ // ------------------------------------------------
static void updateAvatar(ServerPlayer *sender, const QString &jsonData) { void Room::quitRoom(ServerPlayer *player, const QString &) {
auto arr = String2Json(jsonData).array(); removePlayer(player);
auto avatar = arr[0].toString(); if (isOutdated()) {
if (CheckSqlString(avatar)) {
auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;")
.arg(avatar)
.arg(sender->getId());
ExecSQL(ServerInstance->getDatabase(), sql);
sender->setAvatar(avatar);
sender->doNotify("UpdateAvatar", avatar);
}
}
static void updatePassword(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto oldpw = arr[0].toString();
auto newpw = arr[1].toString();
auto sql_find =
QString("SELECT password, salt FROM userinfo WHERE id=%1;")
.arg(sender->getId());
auto passed = false;
auto arr2 = SelectFromDatabase(ServerInstance->getDatabase(), sql_find);
auto result = arr2[0].toObject();
passed = (result["password"].toString() ==
QCryptographicHash::hash(
oldpw.append(result["salt"].toString()).toLatin1(),
QCryptographicHash::Sha256)
.toHex());
if (passed) {
auto sql_update =
QString("UPDATE userinfo SET password='%1' WHERE id=%2;")
.arg(QCryptographicHash::hash(
newpw.append(result["salt"].toString()).toLatin1(),
QCryptographicHash::Sha256)
.toHex())
.arg(sender->getId());
ExecSQL(ServerInstance->getDatabase(), sql_update);
}
sender->doNotify("UpdatePassword", passed ? "1" : "0");
}
static void createRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto name = arr[0].toString();
auto capacity = arr[1].toInt();
auto timeout = arr[2].toInt();
auto settings =
QJsonDocument(arr[3].toObject()).toJson(QJsonDocument::Compact);
ServerInstance->createRoom(sender, name, capacity, timeout, settings);
}
static void enterRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto roomId = arr[0].toInt();
auto room = ServerInstance->findRoom(roomId);
if (room) {
auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString();
if (password.isEmpty() || arr[1].toString() == password) {
if (room->isOutdated()) {
sender->doNotify("ErrorMsg", "room is outdated");
} else {
room->addPlayer(sender);
}
} else {
sender->doNotify("ErrorMsg", "room password error");
}
} else {
sender->doNotify("ErrorMsg", "no such room");
}
}
static void observeRoom(ServerPlayer *sender, const QString &jsonData) {
auto arr = String2Json(jsonData).array();
auto roomId = arr[0].toInt();
auto room = ServerInstance->findRoom(roomId);
if (room) {
auto settings = QJsonDocument::fromJson(room->getSettings());
auto password = settings["password"].toString();
if (password.isEmpty() || arr[1].toString() == password) {
if (room->isOutdated()) {
sender->doNotify("ErrorMsg", "room is outdated");
} else {
room->addObserver(sender);
}
} else {
sender->doNotify("ErrorMsg", "room password error");
}
} else {
sender->doNotify("ErrorMsg", "no such room");
}
}
static void refreshRoomList(ServerPlayer *sender, const QString &) {
ServerInstance->updateRoomList(sender);
};
static void quitRoom(ServerPlayer *player, const QString &) {
auto room = player->getRoom();
room->removePlayer(player);
if (room->isOutdated()) {
player->kicked(); player->kicked();
} }
} }
static void addRobot(ServerPlayer *player, const QString &) { void Room::addRobotRequest(ServerPlayer *player, const QString &) {
auto room = player->getRoom();
if (ServerInstance->getConfig("enableBots").toBool()) if (ServerInstance->getConfig("enableBots").toBool())
room->addRobot(player); addRobot(player);
} }
static void kickPlayer(ServerPlayer *player, const QString &jsonData) { void Room::kickPlayer(ServerPlayer *player, const QString &jsonData) {
auto room = player->getRoom();
int i = jsonData.toInt(); int i = jsonData.toInt();
auto p = room->findPlayer(i); auto p = findPlayer(i);
if (p && !room->isStarted()) { if (p && !isStarted()) {
room->removePlayer(p); removePlayer(p);
room->addRejectId(i); addRejectId(i);
QTimer::singleShot(30000, room, [=]() { QTimer::singleShot(30000, this, [=]() {
room->removeRejectId(i); removeRejectId(i);
}); });
} }
} }
static void ready(ServerPlayer *player, const QString &) { void Room::ready(ServerPlayer *player, const QString &) {
auto room = player->getRoom();
player->setReady(!player->isReady()); player->setReady(!player->isReady());
room->doBroadcastNotify(room->getPlayers(), "ReadyChanged", doBroadcastNotify(getPlayers(), "ReadyChanged",
QString("[%1,%2]").arg(player->getId()).arg(player->isReady())); QString("[%1,%2]").arg(player->getId()).arg(player->isReady()));
} }
static void startGame(ServerPlayer *player, const QString &) { void Room::startGame(ServerPlayer *player, const QString &) {
auto room = player->getRoom(); if (isOutdated()) {
if (room->isOutdated()) { foreach (auto p, getPlayers()) {
foreach (auto p, room->getPlayers()) {
p->doNotify("ErrorMsg", "room is outdated"); p->doNotify("ErrorMsg", "room is outdated");
p->kicked(); p->kicked();
} }
} else { } else {
room->manuallyStart(); manuallyStart();
} }
} }
typedef void (*room_cb)(ServerPlayer *, const QString &); typedef void (Room::*room_cb)(ServerPlayer *, const QString &);
static const QMap<QString, room_cb> lobby_actions = {
{"UpdateAvatar", updateAvatar},
{"UpdatePassword", updatePassword},
{"CreateRoom", createRoom},
{"EnterRoom", enterRoom},
{"ObserveRoom", observeRoom},
{"RefreshRoomList", refreshRoomList},
};
static const QMap<QString, room_cb> room_actions = {
{"QuitRoom", quitRoom},
{"AddRobot", addRobot},
{"KickPlayer", kickPlayer},
{"Ready", ready},
{"StartGame", startGame},
};
void Room::handlePacket(ServerPlayer *sender, const QString &command, void Room::handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData) { const QString &jsonData) {
if (command == "Chat") { static const QMap<QString, room_cb> room_actions = {
chat(sender, jsonData); {"QuitRoom", &Room::quitRoom},
return; {"AddRobot", &Room::addRobotRequest},
} else if (command == "PushRequest") { {"KickPlayer", &Room::kickPlayer},
if (!isLobby()) {"Ready", &Room::ready},
{"StartGame", &Room::startGame},
{"Chat", &Room::chat},
};
if (command == "PushRequest") {
pushRequest(QString("%1,").arg(sender->getId()) + jsonData); pushRequest(QString("%1,").arg(sender->getId()) + jsonData);
return;
} }
auto func_table = lobby_actions; auto func = room_actions[command];
if (!isLobby()) func_table = room_actions; if (func) (this->*func)(sender, jsonData);
auto func = func_table[command];
if (func) {
func(sender, jsonData);
} }
// Lua用request之前设置计时器防止等到死。
void Room::setRequestTimer(int ms) {
request_timer = new QTimer();
request_timer->setSingleShot(true);
request_timer->setInterval(ms);
connect(request_timer, &QTimer::timeout, this, [=](){
m_thread->wakeUp(id);
});
request_timer->start();
}
// Lua用当request完成后手动销毁计时器。
void Room::destroyRequestTimer() {
if (!request_timer) return;
request_timer->stop();
delete request_timer;
request_timer = nullptr;
} }

View File

@ -3,11 +3,13 @@
#ifndef _ROOM_H #ifndef _ROOM_H
#define _ROOM_H #define _ROOM_H
#include "server/roombase.h"
class Server; class Server;
class ServerPlayer; class ServerPlayer;
class RoomThread; class RoomThread;
class Room : public QObject { class Room : public RoomBase {
Q_OBJECT Q_OBJECT
public: public:
explicit Room(RoomThread *m_thread); explicit Room(RoomThread *m_thread);
@ -15,12 +17,11 @@ class Room : public QObject {
// Property reader & setter // Property reader & setter
// ==================================={ // ==================================={
Server *getServer() const;
RoomThread *getThread() const; RoomThread *getThread() const;
void setThread(RoomThread *t); void setThread(RoomThread *t);
int getId() const; int getId() const;
void setId(int id); void setId(int id);
bool isLobby() const;
QString getName() const; QString getName() const;
void setName(const QString &name); void setName(const QString &name);
int getCapacity() const; int getCapacity() const;
@ -38,9 +39,6 @@ class Room : public QObject {
void addPlayer(ServerPlayer *player); void addPlayer(ServerPlayer *player);
void addRobot(ServerPlayer *player); void addRobot(ServerPlayer *player);
void removePlayer(ServerPlayer *player); void removePlayer(ServerPlayer *player);
QList<ServerPlayer *> getPlayers() const;
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(int id) const;
void addObserver(ServerPlayer *player); void addObserver(ServerPlayer *player);
void removeObserver(ServerPlayer *player); void removeObserver(ServerPlayer *player);
@ -49,16 +47,13 @@ class Room : public QObject {
int getTimeout() const; int getTimeout() const;
void setTimeout(int timeout); void setTimeout(int timeout);
void delay(int ms);
bool isOutdated(); bool isOutdated();
bool isStarted() const; bool isStarted() const;
// ====================================} // ====================================}
void doBroadcastNotify(const QList<ServerPlayer *> targets,
const QString &command, const QString &jsonData);
void chat(ServerPlayer *sender, const QString &jsonData);
void updateWinRate(int id, const QString &general, const QString &mode, void updateWinRate(int id, const QString &general, const QString &mode,
int result, bool dead); int result, bool dead);
void gameOver(); void gameOver();
@ -71,6 +66,13 @@ class Room : public QObject {
// router用 // router用
void handlePacket(ServerPlayer *sender, const QString &command, void handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData); const QString &jsonData);
void setRequestTimer(int ms);
void destroyRequestTimer();
// FIXME
volatile bool insideGameOver = false;
signals: signals:
void abandoned(); void abandoned();
@ -78,8 +80,7 @@ class Room : public QObject {
void playerRemoved(ServerPlayer *player); void playerRemoved(ServerPlayer *player);
private: private:
Server *server; RoomThread *m_thread = nullptr;
RoomThread *m_thread;
int id; // Lobby's id is 0 int id; // Lobby's id is 0
QString name; // “阴间大乱斗” QString name; // “阴间大乱斗”
int capacity; // by default is 5, max is 8 int capacity; // by default is 5, max is 8
@ -87,8 +88,6 @@ class Room : public QObject {
bool m_abandoned; // If room is empty, delete it bool m_abandoned; // If room is empty, delete it
ServerPlayer *owner; // who created this room? ServerPlayer *owner; // who created this room?
QList<ServerPlayer *> players;
QList<ServerPlayer *> observers;
QList<int> runned_players; QList<int> runned_players;
QList<int> rejected_players; QList<int> rejected_players;
int robot_id; int robot_id;
@ -98,8 +97,17 @@ class Room : public QObject {
int timeout; int timeout;
QString md5; QString md5;
QTimer *request_timer = nullptr;
void addRunRate(int id, const QString &mode); void addRunRate(int id, const QString &mode);
void updatePlayerGameData(int id, const QString &mode); void updatePlayerGameData(int id, const QString &mode);
// handle packet
void quitRoom(ServerPlayer *, const QString &);
void addRobotRequest(ServerPlayer *, const QString &);
void kickPlayer(ServerPlayer *, const QString &);
void ready(ServerPlayer *, const QString &);
void startGame(ServerPlayer *, const QString &);
}; };
#endif // _ROOM_H #endif // _ROOM_H

62
src/server/roombase.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "server/roombase.h"
#include "server/serverplayer.h"
#include "server/server.h"
#include "core/util.h"
Server *RoomBase::getServer() const { return server; }
bool RoomBase::isLobby() const {
return inherits("Lobby");
}
QList<ServerPlayer *> RoomBase::getPlayers() const { return players; }
QList<ServerPlayer *> RoomBase::getOtherPlayers(ServerPlayer *expect) const {
QList<ServerPlayer *> others = getPlayers();
others.removeOne(expect);
return others;
}
ServerPlayer *RoomBase::findPlayer(int id) const {
foreach (ServerPlayer *p, players) {
if (p->getId() == id)
return p;
}
return nullptr;
}
void RoomBase::doBroadcastNotify(const QList<ServerPlayer *> targets,
const QString &command, const QString &jsonData) {
foreach (ServerPlayer *p, targets) {
p->doNotify(command, jsonData);
}
}
void RoomBase::chat(ServerPlayer *sender, const QString &jsonData) {
auto doc = String2Json(jsonData).object();
auto type = doc["type"].toInt();
doc["sender"] = sender->getId();
// 屏蔽.号防止有人在HTML文本发链接而正常发链接看不出来有啥改动
auto msg = doc["msg"].toString();
msg.replace(".", "");
// 300字限制与客户端相同
msg.erase(msg.begin() + 300, msg.end());
doc["msg"] = msg;
if (!server->checkBanWord(msg)) {
return;
}
if (type == 1) {
doc["userName"] = sender->getScreenName();
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
} else {
auto json = QJsonDocument(doc).toJson(QJsonDocument::Compact);
doBroadcastNotify(players, "Chat", json);
doBroadcastNotify(observers, "Chat", json);
}
qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(),
doc["msg"].toString().toUtf8().constData());
}

30
src/server/roombase.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef _ROOMBASE_H
#define _ROOMBASE_H
class Server;
class ServerPlayer;
class RoomBase : public QObject {
public:
Server *getServer() const;
bool isLobby() const;
QList<ServerPlayer *> getPlayers() const;
QList<ServerPlayer *> getOtherPlayers(ServerPlayer *expect) const;
ServerPlayer *findPlayer(int id) const;
void doBroadcastNotify(const QList<ServerPlayer *> targets,
const QString &command, const QString &jsonData);
void chat(ServerPlayer *sender, const QString &jsonData);
virtual void addPlayer(ServerPlayer *player) = 0;
virtual void removePlayer(ServerPlayer *player) = 0;
virtual void handlePacket(ServerPlayer *sender, const QString &command,
const QString &jsonData) = 0;
protected:
Server *server;
QList<ServerPlayer *> players;
QList<ServerPlayer *> observers;
};
#endif // _ROOMBASE_H

View File

@ -1,45 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "roomthread.h" #include "server/roomthread.h"
#include "server.h" #include "server/scheduler.h"
#include "util.h" #include "server/server.h"
#include <lua.h>
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
#include "client.h" #include "client/client.h"
#endif #endif
RoomThread::RoomThread(Server *m_server) { RoomThread::RoomThread(Server *m_server) {
setObjectName("Room"); setObjectName("Room");
this->m_server = m_server; this->m_server = m_server;
m_capacity = 100; // TODO: server cfg m_capacity = 100; // TODO: server cfg
terminated = false;
md5 = m_server->getMd5(); md5 = m_server->getMd5();
L = CreateLuaState();
if (QFile::exists("packages/freekill-core") &&
!GetDisabledPacks().contains("freekill-core")) {
// 危险的cd操作记得在lua中切回游戏根目录
QDir::setCurrent("packages/freekill-core");
}
DoLuaScript(L, "lua/freekill.lua");
DoLuaScript(L, "lua/server/scheduler.lua");
start(); start();
} }
RoomThread::~RoomThread() { RoomThread::~RoomThread() {
tryTerminate();
if (isRunning()) { if (isRunning()) {
wait(); quit();
} }
lua_close(L); delete m_scheduler;
m_server->removeThread(this); m_server->removeThread(this);
// foreach (auto room, room_list) { // foreach (auto room, room_list) {
// room->deleteLater(); // room->deleteLater();
// } // }
} }
void RoomThread::run() {
// 在run中创建这样就能在接下来的exec中处理事件了
m_scheduler = new Scheduler(this);
connect(this, &RoomThread::pushRequest, m_scheduler, &Scheduler::handleRequest);
connect(this, &RoomThread::delay, m_scheduler, &Scheduler::doDelay);
connect(this, &RoomThread::wakeUp, m_scheduler, &Scheduler::resumeRoom);
exec();
}
Server *RoomThread::getServer() const { Server *RoomThread::getServer() const {
return m_server; return m_server;
} }
@ -56,7 +53,7 @@ Room *RoomThread::getRoom(int id) const {
} }
void RoomThread::addRoom(Room *room) { void RoomThread::addRoom(Room *room) {
Q_UNUSED(room); room->setThread(this);
m_capacity--; m_capacity--;
} }
@ -69,6 +66,7 @@ void RoomThread::removeRoom(Room *room) {
} }
} }
/*
QString RoomThread::fetchRequest() { QString RoomThread::fetchRequest() {
// if (!gameStarted) // if (!gameStarted)
// return ""; // return "";
@ -124,6 +122,7 @@ void RoomThread::tryTerminate() {
bool RoomThread::isTerminated() const { bool RoomThread::isTerminated() const {
return terminated; return terminated;
} }
*/
bool RoomThread::isConsoleStart() const { bool RoomThread::isConsoleStart() const {
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY

View File

@ -3,9 +3,9 @@
#ifndef _ROOMTHREAD_H #ifndef _ROOMTHREAD_H
#define _ROOMTHREAD_H #define _ROOMTHREAD_H
#include <qsemaphore.h>
class Room; class Room;
class Server; class Server;
class Scheduler;
class RoomThread : public QThread { class RoomThread : public QThread {
Q_OBJECT Q_OBJECT
@ -21,21 +21,24 @@ class RoomThread : public QThread {
void addRoom(Room *room); void addRoom(Room *room);
void removeRoom(Room *room); void removeRoom(Room *room);
QString fetchRequest(); //QString fetchRequest();
void pushRequest(const QString &req); //void clearRequest();
void clearRequest(); //bool hasRequest();
bool hasRequest();
void trySleep(int ms); // void trySleep(int ms);
void wakeUp();
void tryTerminate(); // void tryTerminate();
bool isTerminated() const; // bool isTerminated() const;
bool isConsoleStart() const; bool isConsoleStart() const;
bool isOutdated(); bool isOutdated();
signals:
void pushRequest(const QString &req);
void delay(int roomId, int ms);
void wakeUp(int roomId);
protected: protected:
virtual void run(); virtual void run();
@ -45,11 +48,11 @@ class RoomThread : public QThread {
int m_capacity; int m_capacity;
QString md5; QString md5;
lua_State *L; Scheduler *m_scheduler;
QMutex request_queue_mutex; // QMutex request_queue_mutex;
QQueue<QString> request_queue; // json string // QQueue<QString> request_queue; // json string
QSemaphore sema_wake; // QSemaphore sema_wake;
volatile bool terminated; // volatile bool terminated;
}; };
#endif // _ROOMTHREAD_H #endif // _ROOMTHREAD_H

55
src/server/scheduler.cpp Normal file
View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include "server/scheduler.h"
#include "server/roomthread.h"
#include "core/util.h"
Scheduler::Scheduler(RoomThread *thread) {
m_thread = thread;
L = CreateLuaState();
if (QFile::exists("packages/freekill-core") &&
!GetDisabledPacks().contains("freekill-core")) {
// 危险的cd操作记得在lua中切回游戏根目录
QDir::setCurrent("packages/freekill-core");
}
DoLuaScript(L, "lua/freekill.lua");
DoLuaScript(L, "lua/server/scheduler.lua");
tellThreadToLua();
}
Scheduler::~Scheduler() {
lua_close(L);
}
void Scheduler::handleRequest(const QString &req) {
lua_getglobal(L, "HandleRequest");
auto bytes = req.toUtf8();
lua_pushstring(L, bytes.data());
int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1);
if (err) {
qCritical() << result;
lua_pop(L, 1);
}
lua_pop(L, 1);
}
void Scheduler::doDelay(int roomId, int ms) {
QTimer::singleShot(ms, [=](){ resumeRoom(roomId); });
}
bool Scheduler::resumeRoom(int roomId) {
lua_getglobal(L, "ResumeRoom");
lua_pushnumber(L, roomId);
int err = lua_pcall(L, 1, 1, 0);
const char *result = lua_tostring(L, -1);
if (err) {
qCritical() << result;
lua_pop(L, 1);
return true;
}
auto ret = lua_toboolean(L, -1);
return ret;
}

29
src/server/scheduler.h Normal file
View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef _SCHEDULER_H
#define _SCHEDULER_H
class Room;
class RoomThread;
class Scheduler : public QObject {
Q_OBJECT
public:
explicit Scheduler(RoomThread *m_thread);
~Scheduler();
void tellThreadToLua(); // 转swig
public slots:
// 跨线程传递引用可能出问题!
void handleRequest(const QString &req);
void doDelay(int roomId, int ms);
bool resumeRoom(int roomId);
private:
RoomThread *m_thread;
lua_State *L;
// QList<Room *> room_list;
};
#endif // _ROOMTHREAD_H

View File

@ -1,54 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "server.h" #include "server/server.h"
#include "server/auth.h"
#include "server/room.h"
#include "server/lobby.h"
#include "server/roomthread.h"
#include "server/serverplayer.h"
#include "network/router.h"
#include "network/client_socket.h"
#include "network/server_socket.h"
#include "core/packman.h"
#include "core/util.h"
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonvalue.h>
#include <qobject.h>
#include <qversionnumber.h>
#include <QNetworkDatagram> #include <QNetworkDatagram>
#include <openssl/bn.h>
#include "client_socket.h"
#include "packman.h"
#include "player.h"
#include "room.h"
#include "roomthread.h"
#include "router.h"
#include "server_socket.h"
#include "serverplayer.h"
#include "util.h"
Server *ServerInstance = nullptr; Server *ServerInstance = nullptr;
Server::Server(QObject *parent) : QObject(parent) { Server::Server(QObject *parent) : QObject(parent) {
ServerInstance = this; ServerInstance = this;
db = OpenDatabase(); db = OpenDatabase();
rsa = initServerRSA();
QFile file("server/rsa_pub");
file.open(QIODevice::ReadOnly);
QTextStream in(&file);
public_key = in.readAll();
md5 = calcFileMD5(); md5 = calcFileMD5();
readConfig(); readConfig();
server = new ServerSocket(); auth = new AuthManager(this);
server->setParent(this); server = new ServerSocket(this);
connect(server, &ServerSocket::new_connection, this, connect(server, &ServerSocket::new_connection, this,
&Server::processNewConnection); &Server::processNewConnection);
udpSocket = new QUdpSocket(this); nextRoomId = 1;
connect(udpSocket, &QUdpSocket::readyRead, m_lobby = new Lobby(this);
this, &Server::readPendingDatagrams);
// 创建第一个房间,这个房间作为“大厅房间”
nextRoomId = 0;
createRoom(nullptr, "Lobby", INT32_MAX);
// 大厅只要发生人员变动,就向所有人广播一下房间列表
connect(lobby(), &Room::playerAdded, this, &Server::updateOnlineInfo);
connect(lobby(), &Room::playerRemoved, this, &Server::updateOnlineInfo);
// 启动心跳包线程 // 启动心跳包线程
auto heartbeatThread = QThread::create([=]() { auto heartbeatThread = QThread::create([=]() {
@ -90,12 +70,10 @@ Server::~Server() {
thread->deleteLater(); thread->deleteLater();
} }
sqlite3_close(db); sqlite3_close(db);
RSA_free(rsa);
} }
bool Server::listen(const QHostAddress &address, ushort port) { bool Server::listen(const QHostAddress &address, ushort port) {
bool ret = server->listen(address, port); bool ret = server->listen(address, port);
udpSocket->bind(port);
isListening = ret; isListening = ret;
return ret; return ret;
} }
@ -118,7 +96,7 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
} }
} }
if (!thread && nextRoomId != 0) { if (!thread) {
thread = createThread(); thread = createThread();
} }
@ -127,15 +105,11 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
room->setId(nextRoomId); room->setId(nextRoomId);
nextRoomId++; nextRoomId++;
room->setAbandoned(false); room->setAbandoned(false);
room->setThread(thread);
thread->addRoom(room); thread->addRoom(room);
rooms.insert(room->getId(), room); rooms.insert(room->getId(), room);
} else { } else {
room = new Room(thread); room = new Room(thread);
connect(room, &Room::abandoned, this, &Server::onRoomAbandoned); connect(room, &Room::abandoned, this, &Server::onRoomAbandoned);
if (room->isLobby())
m_lobby = room;
else
rooms.insert(room->getId(), room); rooms.insert(room->getId(), room);
} }
@ -144,13 +118,12 @@ void Server::createRoom(ServerPlayer *owner, const QString &name, int capacity,
room->setTimeout(timeout); room->setTimeout(timeout);
room->setSettings(settings); room->setSettings(settings);
room->addPlayer(owner); room->addPlayer(owner);
if (!room->isLobby())
room->setOwner(owner); room->setOwner(owner);
} }
Room *Server::findRoom(int id) const { return rooms.value(id); } Room *Server::findRoom(int id) const { return rooms.value(id); }
Room *Server::lobby() const { return m_lobby; } Lobby *Server::lobby() const { return m_lobby; }
RoomThread *Server::createThread() { RoomThread *Server::createThread() {
RoomThread *thread = new RoomThread(this); RoomThread *thread = new RoomThread(this);
@ -234,27 +207,6 @@ void Server::sendEarlyPacket(ClientSocket *client, const QString &type, const QS
client->send(JsonArray2Bytes(body)); client->send(JsonArray2Bytes(body));
} }
bool Server::checkClientVersion(ClientSocket *client, const QString &cver) {
auto client_ver = QVersionNumber::fromString(cver);
auto ver = QVersionNumber::fromString(FK_VERSION);
int cmp = QVersionNumber::compare(ver, client_ver);
if (cmp != 0) {
auto errmsg = QString();
if (cmp < 0) {
errmsg = QString("[\"server is still on version %%2\",\"%1\"]")
.arg(FK_VERSION, "1");
} else {
errmsg = QString("[\"server is using version %%2, please update\",\"%1\"]")
.arg(FK_VERSION, "1");
}
sendEarlyPacket(client, "ErrorMsg", errmsg);
client->disconnectFromHost();
return false;
}
return true;
}
void Server::setupPlayer(ServerPlayer *player, bool all_info) { void Server::setupPlayer(ServerPlayer *player, bool all_info) {
// tell the lobby player's basic property // tell the lobby player's basic property
QJsonArray arr; QJsonArray arr;
@ -291,7 +243,7 @@ void Server::processNewConnection(ClientSocket *client) {
} }
if (!errmsg.isEmpty()) { if (!errmsg.isEmpty()) {
sendEarlyPacket(client, "ErrorMsg", errmsg); sendEarlyPacket(client, "ErrorDlg", errmsg);
qInfo() << "Refused banned IP:" << addr; qInfo() << "Refused banned IP:" << addr;
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
@ -301,7 +253,7 @@ void Server::processNewConnection(ClientSocket *client) {
[client]() { qInfo() << client->peerAddress() << "disconnected"; }); [client]() { qInfo() << client->peerAddress() << "disconnected"; });
// network delay test // network delay test
sendEarlyPacket(client, "NetworkDelayTest", public_key); sendEarlyPacket(client, "NetworkDelayTest", auth->getPublicKey());
// Note: the client should send a setup string next // Note: the client should send a setup string next
connect(client, &ClientSocket::message_got, this, &Server::processRequest); connect(client, &ClientSocket::message_got, this, &Server::processRequest);
client->timerSignup.start(30000); client->timerSignup.start(30000);
@ -328,56 +280,30 @@ void Server::processRequest(const QByteArray &msg) {
if (!valid) { if (!valid) {
qWarning() << "Invalid setup string:" << msg; qWarning() << "Invalid setup string:" << msg;
sendEarlyPacket(client, "ErrorMsg", "INVALID SETUP STRING"); sendEarlyPacket(client, "ErrorDlg", "INVALID SETUP STRING");
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
} }
QJsonArray arr = String2Json(doc[3].toString()).array(); QJsonArray arr = String2Json(doc[3].toString()).array();
if (!checkClientVersion(client, arr[3].toString())) return; if (!auth->checkClientVersion(client, arr[3].toString())) return;
auto uuid = arr[4].toString(); auto uuid_str = arr[4].toString();
auto result2 = QJsonArray({1}); auto result2 = QJsonArray({1});
if (CheckSqlString(uuid)) { if (CheckSqlString(uuid_str)) {
result2 = SelectFromDatabase( result2 = SelectFromDatabase(
db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid)); db, QString("SELECT * FROM banuuid WHERE uuid='%1';").arg(uuid_str));
} }
if (!result2.isEmpty()) { if (!result2.isEmpty()) {
sendEarlyPacket(client, "ErrorMsg", "you have been banned!"); sendEarlyPacket(client, "ErrorDlg", "you have been banned!");
qInfo() << "Refused banned UUID:" << uuid; qInfo() << "Refused banned UUID:" << uuid_str;
client->disconnectFromHost(); client->disconnectFromHost();
return; return;
} }
handleNameAndPassword(client, arr[0].toString(), arr[1].toString(), auto md5_str = arr[2].toString();
arr[2].toString(), uuid);
}
void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
const QString &password,
const QString &md5_str,
const QString &uuid_str) {
auto encryted_pw = QByteArray::fromBase64(password.toLatin1());
unsigned char buf[4096] = {0};
RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(),
buf, rsa, RSA_PKCS1_PADDING);
auto decrypted_pw =
QByteArray::fromRawData((const char *)buf, strlen((const char *)buf));
if (decrypted_pw.length() > 32) {
auto aes_bytes = decrypted_pw.first(32);
// tell client to install aes key
sendEarlyPacket(client, "InstallKey", "");
client->installAESKey(aes_bytes);
decrypted_pw.remove(0, 32);
} else {
decrypted_pw = "\xFF";
}
if (md5 != md5_str) { if (md5 != md5_str) {
sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!"); sendEarlyPacket(client, "ErrorMsg", "MD5 check failed!");
sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary()); sendEarlyPacket(client, "UpdatePackage", Pacman->getPackSummary());
@ -385,99 +311,11 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
return; return;
} }
bool passed = false; auto name = arr[0].toString();
QString error_msg; auto password = arr[1].toString();
QJsonArray result; auto obj = auth->checkPassword(client, name, password);
QJsonObject obj; if (obj.isEmpty()) return;
if (CheckSqlString(name) && checkBanWord(name)) {
// Then we check the database,
QString sql_find = QString("SELECT * FROM userinfo \
WHERE name='%1';")
.arg(name);
result = SelectFromDatabase(db, sql_find);
if (result.isEmpty()) {
auto salt_gen = QRandomGenerator::securelySeeded();
auto salt = QByteArray::number(salt_gen(), 16);
decrypted_pw.append(salt);
auto passwordHash =
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
.toHex();
// not present in database, register
QString sql_reg = QString("INSERT INTO userinfo (name,password,salt,\
avatar,lastLoginIp,banned) VALUES ('%1','%2','%3','%4','%5',%6);")
.arg(name)
.arg(QString(passwordHash))
.arg(salt)
.arg("liubei")
.arg(client->peerAddress())
.arg("FALSE");
ExecSQL(db, sql_reg);
result = SelectFromDatabase(db, sql_find); // refresh result
obj = result[0].toObject();
auto info_update = QString("INSERT INTO usergameinfo (id, registerTime) VALUES (%1, %2);").arg(obj["id"].toString().toInt()).arg(QDateTime::currentSecsSinceEpoch());
ExecSQL(db, info_update);
passed = true;
} else {
obj = result[0].toObject();
// check ban account
int id = obj["id"].toString().toInt();
passed = obj["banned"].toString().toInt() == 0;
if (!passed) {
error_msg = "you have been banned!";
}
// check if password is the same
auto salt = obj["salt"].toString().toLatin1();
decrypted_pw.append(salt);
auto passwordHash =
QCryptographicHash::hash(decrypted_pw, QCryptographicHash::Sha256)
.toHex();
passed = (passwordHash == obj["password"].toString());
if (!passed) {
error_msg = "username or password error";
} else if (players.value(id)) {
auto player = players.value(id);
// 顶号机制,如果在线的话就让他变成不在线
if (player->getState() == Player::Online) {
player->doNotify("ErrorMsg", "others logged in again with this name");
emit player->kicked();
}
if (player->getState() == Player::Offline) {
auto room = player->getRoom();
player->setSocket(client);
player->alive = true;
client->disconnect(this);
if (players.count() <= 10) {
broadcast("ServerMessage", tr("%1 backed").arg(player->getScreenName()));
}
if (room && !room->isLobby()) {
setupPlayer(player, true);
room->pushRequest(QString("%1,reconnect").arg(id));
} else {
// 懒得处理掉线玩家在大厅了!踢掉得了
player->doNotify("ErrorMsg", "Unknown Error");
emit player->kicked();
}
return;
} else {
error_msg = "others logged in with this name";
passed = false;
}
}
}
} else {
error_msg = "invalid user name";
}
if (passed) {
// update lastLoginIp // update lastLoginIp
int id = obj["id"].toString().toInt(); int id = obj["id"].toString().toInt();
beginTransaction(); beginTransaction();
@ -487,7 +325,8 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
.arg(id); .arg(id);
ExecSQL(db, sql_update); ExecSQL(db, sql_update);
auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');").arg(id).arg(uuid_str); auto uuid_update = QString("REPLACE INTO uuidinfo (id, uuid) VALUES (%1, '%2');")
.arg(id).arg(uuid_str);
ExecSQL(db, uuid_update); ExecSQL(db, uuid_update);
// 来晚了,有很大可能存在已经注册但是表里面没数据的人 // 来晚了,有很大可能存在已经注册但是表里面没数据的人
@ -519,12 +358,6 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name,
player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time })); player->doNotify("AddTotalGameTime", JsonArray2Bytes({ id, time }));
lobby()->addPlayer(player); lobby()->addPlayer(player);
} else {
qInfo() << client->peerAddress() << "lost connection:" << error_msg;
sendEarlyPacket(client, "ErrorMsg", error_msg);
client->disconnectFromHost();
return;
}
} }
void Server::onRoomAbandoned() { void Server::onRoomAbandoned() {
@ -537,16 +370,23 @@ void Server::onRoomAbandoned() {
// FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。 // FIXME: 但是这终归是内存泄漏!以后啥时候再改吧。
// room->deleteLater(); // room->deleteLater();
idle_rooms.push(room); idle_rooms.push(room);
room->getThread()->wakeUp(room->getId());
room->getThread()->removeRoom(room); room->getThread()->removeRoom(room);
} }
void Server::onUserDisconnected() { void Server::onUserDisconnected() {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender()); auto player = qobject_cast<ServerPlayer *>(sender());
qInfo() << "Player" << player->getId() << "disconnected"; qInfo() << "Player" << player->getId() << "disconnected";
if (players.count() <= 10) { if (players.count() <= 10) {
broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName())); broadcast("ServerMessage", tr("%1 logged out").arg(player->getScreenName()));
} }
Room *room = player->getRoom();
auto _room = player->getRoom();
if (_room->isLobby()) {
player->setState(Player::Robot); // 大厅然而又不能设Offline
player->deleteLater();
} else {
auto room = qobject_cast<Room *>(_room);
if (room->isStarted()) { if (room->isStarted()) {
if (room->getObservers().contains(player)) { if (room->getObservers().contains(player)) {
room->removeObserver(player); room->removeObserver(player);
@ -558,16 +398,21 @@ void Server::onUserDisconnected() {
// TODO: add a robot // TODO: add a robot
} else { } else {
player->setState(Player::Robot); // 大厅然而又不能设Offline player->setState(Player::Robot); // 大厅然而又不能设Offline
// 这里有一个多线程问题可能与Room::gameOver同时deleteLater导致出事
// FIXME: 这种解法肯定不安全
if (!room->insideGameOver)
player->deleteLater(); player->deleteLater();
} }
} }
}
void Server::onUserStateChanged() { void Server::onUserStateChanged() {
ServerPlayer *player = qobject_cast<ServerPlayer *>(sender()); ServerPlayer *player = qobject_cast<ServerPlayer *>(sender());
auto room = player->getRoom(); auto _room = player->getRoom();
if (!room || room->isLobby() || room->isAbandoned()) { if (!_room || _room->isLobby()) return;
return; auto room = qobject_cast<Room *>(_room);
} if (room->isAbandoned()) return;
auto state = player->getState(); auto state = player->getState();
room->doBroadcastNotify(room->getPlayers(), "NetStateChanged", room->doBroadcastNotify(room->getPlayers(), "NetStateChanged",
QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString())); QString("[%1,\"%2\"]").arg(player->getId()).arg(player->getStateString()));
@ -579,33 +424,6 @@ 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;
}
#define SET_DEFAULT_CONFIG(k, v) do {\ #define SET_DEFAULT_CONFIG(k, v) do {\
if (config.value(k).isUndefined()) { \ if (config.value(k).isUndefined()) { \
config[k] = (v); \ config[k] = (v); \
@ -705,28 +523,3 @@ void Server::refreshMd5() {
emit p->kicked(); emit p->kicked();
} }
} }
void Server::readPendingDatagrams() {
while (udpSocket->hasPendingDatagrams()) {
QNetworkDatagram datagram = udpSocket->receiveDatagram();
if (datagram.isValid()) {
processDatagram(datagram.data(), datagram.senderAddress(), datagram.senderPort());
}
}
}
void Server::processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port) {
if (msg == "fkDetectServer") {
udpSocket->writeDatagram("me", addr, port);
} else if (msg.startsWith("fkGetDetail,")) {
udpSocket->writeDatagram(JsonArray2Bytes(QJsonArray({
FK_VERSION,
getConfig("iconUrl"),
getConfig("description"),
getConfig("capacity"),
players.count(),
msg.sliced(12).constData(),
})), addr, port);
}
udpSocket->flush();
}

View File

@ -3,17 +3,14 @@
#ifndef _SERVER_H #ifndef _SERVER_H
#define _SERVER_H #define _SERVER_H
#include <openssl/rsa.h> class AuthManager;
#include <openssl/pem.h>
#include <qjsonobject.h>
#include <qjsonvalue.h>
class ServerSocket; class ServerSocket;
class ClientSocket; class ClientSocket;
class ServerPlayer; class ServerPlayer;
class RoomThread; class RoomThread;
class Lobby;
#include "room.h" #include "server/room.h"
class Server : public QObject { class Server : public QObject {
Q_OBJECT Q_OBJECT
@ -29,7 +26,7 @@ public:
int timeout = 15, const QByteArray &settings = "{}"); int timeout = 15, const QByteArray &settings = "{}");
Room *findRoom(int id) const; Room *findRoom(int id) const;
Room *lobby() const; Lobby *lobby() const;
RoomThread *createThread(); RoomThread *createThread();
void removeThread(RoomThread *thread); void removeThread(RoomThread *thread);
@ -37,6 +34,7 @@ public:
ServerPlayer *findPlayer(int id) const; ServerPlayer *findPlayer(int id) const;
void addPlayer(ServerPlayer *player); void addPlayer(ServerPlayer *player);
void removePlayer(int id); void removePlayer(int id);
auto getPlayers() { return players; }
void updateRoomList(ServerPlayer *teller); void updateRoomList(ServerPlayer *teller);
void updateOnlineInfo(); void updateOnlineInfo();
@ -44,6 +42,8 @@ public:
sqlite3 *getDatabase(); sqlite3 *getDatabase();
void broadcast(const QString &command, const QString &jsonData); void broadcast(const QString &command, const QString &jsonData);
void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
void setupPlayer(ServerPlayer *player, bool all_info = true);
bool isListening; bool isListening;
QJsonValue getConfig(const QString &command); QJsonValue getConfig(const QString &command);
@ -64,7 +64,6 @@ signals:
public slots: public slots:
void processNewConnection(ClientSocket *client); void processNewConnection(ClientSocket *client);
void processRequest(const QByteArray &msg); void processRequest(const QByteArray &msg);
void readPendingDatagrams();
void onRoomAbandoned(); void onRoomAbandoned();
void onUserDisconnected(); void onUserDisconnected();
@ -73,9 +72,8 @@ public slots:
private: private:
friend class Shell; friend class Shell;
ServerSocket *server; ServerSocket *server;
QUdpSocket *udpSocket; // 服务器列表页面显示服务器信息用
Room *m_lobby; Lobby *m_lobby;
QMap<int, Room *> rooms; QMap<int, Room *> rooms;
QStack<Room *> idle_rooms; QStack<Room *> idle_rooms;
QList<RoomThread *> threads; QList<RoomThread *> threads;
@ -84,26 +82,13 @@ private:
QHash<int, ServerPlayer *> players; QHash<int, ServerPlayer *> players;
QList<QString> temp_banlist; QList<QString> temp_banlist;
RSA *rsa; AuthManager *auth;
QString public_key;
sqlite3 *db; sqlite3 *db;
QMutex transaction_mutex; QMutex transaction_mutex;
QString md5; QString md5;
static RSA *initServerRSA();
QJsonObject config; QJsonObject config;
void readConfig(); void readConfig();
// 用于确定建立连接之前与客户端通信连接后用doNotify
void sendEarlyPacket(ClientSocket *client, const QString &type, const QString &msg);
bool checkClientVersion(ClientSocket *client, const QString &ver);
// 某玩家刚刚连入之后,服务器告诉他关于他的一些基本信息
void setupPlayer(ServerPlayer *player, bool all_info = true);
void handleNameAndPassword(ClientSocket *client, const QString &name,
const QString &password, const QString &md5_str, const QString &uuid_str);
void processDatagram(const QByteArray &msg, const QHostAddress &addr, uint port);
}; };
extern Server *ServerInstance; extern Server *ServerInstance;

View File

@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "serverplayer.h" #include "server/serverplayer.h"
#include "client_socket.h" #include "network/client_socket.h"
#include "room.h" #include "server/room.h"
#include "roomthread.h" #include "server/roomthread.h"
#include "router.h" #include "network/router.h"
#include "server.h" #include "server/server.h"
ServerPlayer::ServerPlayer(Room *room) { ServerPlayer::ServerPlayer(RoomBase *room) {
socket = nullptr; socket = nullptr;
router = new Router(this, socket, Router::TYPE_SERVER); router = new Router(this, socket, Router::TYPE_SERVER);
setState(Player::Online); setState(Player::Online);
@ -61,9 +61,9 @@ void ServerPlayer::removeSocket() {
Server *ServerPlayer::getServer() const { return server; } Server *ServerPlayer::getServer() const { return server; }
Room *ServerPlayer::getRoom() const { return room; } RoomBase *ServerPlayer::getRoom() const { return room; }
void ServerPlayer::setRoom(Room *room) { this->room = room; } void ServerPlayer::setRoom(RoomBase *room) { this->room = room; }
void ServerPlayer::speak(const QString &message) { ; } void ServerPlayer::speak(const QString &message) { ; }
@ -112,6 +112,24 @@ void ServerPlayer::kick() {
setSocket(nullptr); setSocket(nullptr);
} }
void ServerPlayer::reconnect(ClientSocket *client) {
setSocket(client);
alive = true;
client->disconnect(this);
if (server->getPlayers().count() <= 10) {
server->broadcast("ServerMessage", tr("%1 backed").arg(getScreenName()));
}
if (room && !room->isLobby()) {
server->setupPlayer(this, true);
qobject_cast<Room *>(room)->pushRequest(QString("%1,reconnect").arg(getId()));
} else {
// 懒得处理掉线玩家在大厅了!踢掉得了
doNotify("ErrorMsg", "Unknown Error");
emit kicked();
}
}
bool ServerPlayer::thinking() { bool ServerPlayer::thinking() {
m_thinking_mutex.lock(); m_thinking_mutex.lock();
bool ret = m_thinking; bool ret = m_thinking;

View File

@ -3,17 +3,18 @@
#ifndef _SERVERPLAYER_H #ifndef _SERVERPLAYER_H
#define _SERVERPLAYER_H #define _SERVERPLAYER_H
#include "player.h" #include "core/player.h"
class ClientSocket; class ClientSocket;
class Router; class Router;
class Server; class Server;
class Room; class Room;
class RoomBase;
class ServerPlayer : public Player { class ServerPlayer : public Player {
Q_OBJECT Q_OBJECT
public: public:
explicit ServerPlayer(Room *room); explicit ServerPlayer(RoomBase *room);
~ServerPlayer(); ~ServerPlayer();
void setSocket(ClientSocket *socket); void setSocket(ClientSocket *socket);
@ -21,8 +22,8 @@ public:
ClientSocket *getSocket() const; ClientSocket *getSocket() const;
Server *getServer() const; Server *getServer() const;
Room *getRoom() const; RoomBase *getRoom() const;
void setRoom(Room *room); void setRoom(RoomBase *room);
void speak(const QString &message); void speak(const QString &message);
@ -37,6 +38,7 @@ public:
volatile bool alive; // For heartbeat volatile bool alive; // For heartbeat
void kick(); void kick();
void reconnect(ClientSocket *socket);
bool busy() const { return m_busy; } bool busy() const { return m_busy; }
void setBusy(bool busy) { m_busy = busy; } void setBusy(bool busy) { m_busy = busy; }
@ -57,7 +59,7 @@ private:
ClientSocket *socket; // socket for communicating with client ClientSocket *socket; // socket for communicating with client
Router *router; Router *router;
Server *server; Server *server;
Room *room; // Room that player is in, maybe lobby RoomBase *room; // Room that player is in, maybe lobby
bool m_busy; // (Lua专用) 是否有doRequest没处理完见于神貂蝉这种一控多的 bool m_busy; // (Lua专用) 是否有doRequest没处理完见于神貂蝉这种一控多的
bool m_thinking; // 是否在烧条? bool m_thinking; // 是否在烧条?
QMutex m_thinking_mutex; // 注意setBusy只在Lua使用所以不需要锁。 QMutex m_thinking_mutex; // 注意setBusy只在Lua使用所以不需要锁。

View File

@ -1,11 +1,11 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include "shell.h" #include "server/shell.h"
#include "packman.h" #include "core/packman.h"
#include "server.h" #include "server/server.h"
#include "serverplayer.h" #include "server/serverplayer.h"
#include "util.h" #include "core/util.h"
#include <readline/history.h> #include <readline/history.h>
#include <readline/readline.h> #include <readline/readline.h>
#include <signal.h> #include <signal.h>

View File

@ -3,13 +3,13 @@
%module fk %module fk
%{ %{
#include "server.h" #include "server/server.h"
#include "serverplayer.h" #include "server/serverplayer.h"
#include "clientplayer.h" #include "client/clientplayer.h"
#include "room.h" #include "server/room.h"
#include "roomthread.h" #include "server/roomthread.h"
#include "util.h" #include "core/util.h"
#include "qmlbackend.h" #include "ui/qmlbackend.h"
class ClientPlayer *Self = nullptr; class ClientPlayer *Self = nullptr;
%} %}

View File

@ -3,14 +3,14 @@
%module fk %module fk
%{ %{
#include "client.h" #include "client/client.h"
#include "server.h" #include "server/server.h"
#include "serverplayer.h" #include "server/serverplayer.h"
#include "clientplayer.h" #include "client/clientplayer.h"
#include "room.h" #include "server/room.h"
#include "roomthread.h" #include "server/roomthread.h"
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#include "util.h" #include "core/util.h"
const char *FK_VER = FK_VERSION; const char *FK_VER = FK_VERSION;
%} %}

View File

@ -5,7 +5,7 @@
// ------------------------------------------------------ // ------------------------------------------------------
%{ %{
#include <qmlbackend.h> #include "ui/qmlbackend.h"
%} %}
// Lua 5.4 特有的不能pushnumber swig迟迟不更只好手动调教 // Lua 5.4 特有的不能pushnumber swig迟迟不更只好手动调教

View File

@ -1,5 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
%nodefaultctor Server;
%nodefaultdtor Server;
class Server : public QObject {
public:
void beginTransaction();
void endTransaction();
};
extern Server *ServerInstance;
%nodefaultctor Room; %nodefaultctor Room;
%nodefaultdtor Room; %nodefaultdtor Room;
class Room : public QObject { class Room : public QObject {
@ -14,11 +23,14 @@ public:
QList<ServerPlayer *> getObservers() const; QList<ServerPlayer *> getObservers() const;
bool hasObserver(ServerPlayer *player) const; bool hasObserver(ServerPlayer *player) const;
int getTimeout() const; int getTimeout() const;
void delay(int ms);
void checkAbandoned(); void checkAbandoned();
void updateWinRate(int id, const QString &general, const QString &mode, void updateWinRate(int id, const QString &general, const QString &mode,
int result, bool dead); int result, bool dead);
void gameOver(); void gameOver();
void setRequestTimer(int ms);
void destroyRequestTimer();
}; };
%extend Room { %extend Room {
@ -33,25 +45,26 @@ class RoomThread : public QThread {
public: public:
Room *getRoom(int id); Room *getRoom(int id);
QString fetchRequest(); // QString fetchRequest();
void clearRequest(); // void clearRequest();
bool hasRequest(); // bool hasRequest();
void trySleep(int ms); // void trySleep(int ms);
bool isTerminated() const; // bool isTerminated() const;
bool isConsoleStart() const; bool isConsoleStart() const;
bool isOutdated(); bool isOutdated();
}; };
%{ %{
void RoomThread::run() #include "server/scheduler.h"
void Scheduler::tellThreadToLua()
{ {
lua_getglobal(L, "debug"); lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback"); lua_getfield(L, -1, "traceback");
lua_replace(L, -2); lua_replace(L, -2);
lua_getglobal(L, "InitScheduler"); lua_getglobal(L, "InitScheduler");
SWIG_NewPointerObj(L, this, SWIGTYPE_p_RoomThread, 0); SWIG_NewPointerObj(L, m_thread, SWIGTYPE_p_RoomThread, 0);
int error = lua_pcall(L, 1, 0, -2); int error = lua_pcall(L, 1, 0, -2);
lua_pop(L, 1); lua_pop(L, 1);
if (error) { if (error) {

View File

@ -1,27 +1,23 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include "qmlbackend.h" #include "ui/qmlbackend.h"
#include <lua.h>
#include <qjsondocument.h>
#include <qvariant.h>
#ifndef FK_SERVER_ONLY #ifndef FK_SERVER_ONLY
#include <qaudiooutput.h> #include <QAudioOutput>
#include <qmediaplayer.h>
#include <qrandom.h>
#include <QNetworkDatagram> #include <QNetworkDatagram>
#include <QDnsLookup> #include <QDnsLookup>
#include <QClipboard> #include <QClipboard>
#include <QMediaPlayer> #include <QMediaPlayer>
#include "mod.h" #include <QMessageBox>
// #include "mod.h"
#endif #endif
#include <cstdlib> #include <cstdlib>
#include "server.h" #include "server/server.h"
#include "client.h" #include "client/client.h"
#include "util.h" #include "core/util.h"
#include "replayer.h" #include "client/replayer.h"
QmlBackend *Backend = nullptr; QmlBackend *Backend = nullptr;
@ -35,6 +31,7 @@ QmlBackend::QmlBackend(QObject *parent) : QObject(parent) {
udpSocket->bind(0); udpSocket->bind(0);
connect(udpSocket, &QUdpSocket::readyRead, connect(udpSocket, &QUdpSocket::readyRead,
this, &QmlBackend::readPendingDatagrams); this, &QmlBackend::readPendingDatagrams);
connect(this, &QmlBackend::dialog, this, &QmlBackend::showDialog);
#endif #endif
} }
@ -464,7 +461,7 @@ void QmlBackend::installAESKey() {
} }
void QmlBackend::createModBackend() { void QmlBackend::createModBackend() {
engine->rootContext()->setContextProperty("ModBackend", new ModMaker); //engine->rootContext()->setContextProperty("ModBackend", new ModMaker);
} }
@ -549,6 +546,30 @@ void QmlBackend::readPendingDatagrams() {
} }
} }
void QmlBackend::showDialog(const QString &type, const QString &text, const QString &orig) {
static const QString title = tr("FreeKill") + " v" + FK_VERSION;
QMessageBox *box = nullptr;
if (type == "critical") {
box = new QMessageBox(QMessageBox::Critical, title, text, QMessageBox::Ok);
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
} else if (type == "info") {
box = new QMessageBox(QMessageBox::Information, title, text, QMessageBox::Ok);
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
} else if (type == "warning") {
box = new QMessageBox(QMessageBox::Warning, title, text, QMessageBox::Ok);
connect(box, &QMessageBox::buttonClicked, box, &QObject::deleteLater);
}
if (box) {
if (!orig.isEmpty()) {
auto bytes = orig.toLocal8Bit().prepend("help: ");
if (tr(bytes) != bytes) box->setInformativeText(tr(bytes));
}
box->setWindowModality(Qt::NonModal);
box->show();
}
}
void QmlBackend::removeRecord(const QString &fname) { void QmlBackend::removeRecord(const QString &fname) {
QFile::remove("recording/" + fname); QFile::remove("recording/" + fname);
} }

View File

@ -64,6 +64,9 @@ public:
Q_INVOKABLE void detectServer(); Q_INVOKABLE void detectServer();
Q_INVOKABLE void getServerInfo(const QString &addr); Q_INVOKABLE void getServerInfo(const QString &addr);
Q_INVOKABLE void showDialog(const QString &type, const QString &text,
const QString &orig = QString());
qreal volume() const { return m_volume; } qreal volume() const { return m_volume; }
void setVolume(qreal v) { m_volume = v; } void setVolume(qreal v) { m_volume = v; }
@ -77,6 +80,7 @@ public:
signals: signals:
void notifyUI(const QString &command, const QVariant &data); void notifyUI(const QString &command, const QVariant &data);
void dialog(const QString &type, const QString &text, const QString &orig = QString());
void volumeChanged(qreal); void volumeChanged(qreal);
void replayerToggle(); void replayerToggle();
void replayerSpeedUp(); void replayerSpeedUp();