mirror of
https://github.com/DaKerboul/perun.git
synced 2026-08-09 19:05:39 +02:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f2ec7de29 | |||
|
|
28fba57604 | ||
|
|
da26e9b37f | ||
|
|
9ddf139767 | ||
|
|
af310ba403 | ||
|
|
661d05526d | ||
|
|
2ce3836130 | ||
|
|
7fd4a1e95c | ||
|
|
b26b5b115b | ||
|
|
0fd2633d5b | ||
|
|
d239f63a56 | ||
|
|
25dcbb818b | ||
|
|
7074503039 | ||
|
|
d0620a3dca | ||
|
|
8ba30d31cd | ||
|
|
009117d3c5 | ||
|
|
483b15276e | ||
|
|
07f8b14611 | ||
|
|
6648d5cad3 | ||
|
|
4bbb2ad98a | ||
|
|
98fc7a799f | ||
|
|
a88109b85c | ||
|
|
c9f55992ad | ||
|
|
3cbbcd023f | ||
|
|
4bea05e7a2 | ||
|
|
f46ffd9385 | ||
|
|
7697e30fb3 | ||
|
|
3ec0e7bd15 | ||
|
|
727b026207 |
@@ -18,6 +18,7 @@ PerunConfig.ConnectionError_L1 = "[Perun] ERROR: Connection broken - contact ser
|
|||||||
|
|
||||||
-- Misc
|
-- Misc
|
||||||
PerunConfig.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun
|
PerunConfig.BroadcastPerunErrors = 1 -- (int) [0 (default),1] Value greater than 0 will broadcast chat message about missing connection to Perun
|
||||||
|
PerunConfig.RecordChatMessages = 1
|
||||||
|
|
||||||
-- Debug
|
-- Debug
|
||||||
PerunConfig.DebugMode = 1 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged
|
PerunConfig.DebugMode = 1 -- (int) [0 (default),1,2] Value greater than 0 will display Perun information in DCS log file, values: 1 - minimal verbose, 2 - all log information will be logged
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ Perun.MOTD_L1 = PerunConfig.MOTD_L1
|
|||||||
Perun.MOTD_L2 = PerunConfig.MOTD_L2
|
Perun.MOTD_L2 = PerunConfig.MOTD_L2
|
||||||
Perun.ConnectionError = PerunConfig.ConnectionError_L1
|
Perun.ConnectionError = PerunConfig.ConnectionError_L1
|
||||||
Perun.BroadcastPerunErrors = PerunConfig.BroadcastPerunErrors
|
Perun.BroadcastPerunErrors = PerunConfig.BroadcastPerunErrors
|
||||||
|
Perun.RecordChatMessages = PerunConfig.RecordChatMessages
|
||||||
|
|
||||||
-- Variable init
|
-- Variable init
|
||||||
Perun.Version = "v0.12.0"
|
Perun.Version = "v0.12.1"
|
||||||
|
|
||||||
Perun.StatusData = {}
|
Perun.StatusData = {}
|
||||||
Perun.SlotsData = {}
|
Perun.SlotsData = {}
|
||||||
@@ -47,6 +48,14 @@ Perun.lastFrameTime = 0;
|
|||||||
Perun.ReconnectTimeout = 30;
|
Perun.ReconnectTimeout = 30;
|
||||||
Perun.RefreshKeepAlive = 3
|
Perun.RefreshKeepAlive = 3
|
||||||
|
|
||||||
|
Perun.PTS = function(data) -- ProtectedToString
|
||||||
|
if data == nil then
|
||||||
|
return "[nil]"
|
||||||
|
else
|
||||||
|
return tostring(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- ################################ Helper function definitions ################################
|
-- ################################ Helper function definitions ################################
|
||||||
Perun.GetCategory = function(id)
|
Perun.GetCategory = function(id)
|
||||||
-- Helper function returns object category basing on https://pastebin.com/GUAXrd2U
|
-- Helper function returns object category basing on https://pastebin.com/GUAXrd2U
|
||||||
@@ -81,7 +90,7 @@ Perun.SideID2Name = function(id)
|
|||||||
[2] = 'BLUE',
|
[2] = 'BLUE',
|
||||||
[3] = 'NEUTRAL', -- TBD check once this is released in DCS
|
[3] = 'NEUTRAL', -- TBD check once this is released in DCS
|
||||||
}
|
}
|
||||||
if id > 0 and id <= 3 then
|
if id and id > 0 and id <= 3 then
|
||||||
return _sides[id]
|
return _sides[id]
|
||||||
else
|
else
|
||||||
return "?"
|
return "?"
|
||||||
@@ -621,7 +630,7 @@ end
|
|||||||
Perun.onSimulationStart = function()
|
Perun.onSimulationStart = function()
|
||||||
-- Simulation was started
|
-- Simulation was started
|
||||||
Perun.MissionHash=Perun.GenerateMissionHash()
|
Perun.MissionHash=Perun.GenerateMissionHash()
|
||||||
Perun.LogEvent("SimStart","Mission " .. Perun.MissionHash .. " started",nil,nil);
|
Perun.LogEvent("SimStart","Mission " .. Perun.PTS(Perun.MissionHash) .. " started",nil,nil);
|
||||||
Perun.StatData = {}
|
Perun.StatData = {}
|
||||||
Perun.StatDataLastType = {}
|
Perun.StatDataLastType = {}
|
||||||
Perun.PlayersTableCache = {}
|
Perun.PlayersTableCache = {}
|
||||||
@@ -632,7 +641,7 @@ end
|
|||||||
|
|
||||||
Perun.onSimulationStop = function()
|
Perun.onSimulationStop = function()
|
||||||
-- Simulation was stopped
|
-- Simulation was stopped
|
||||||
Perun.LogEvent("SimStop","Mission " .. Perun.MissionHash .. " finished",nil,nil);
|
Perun.LogEvent("SimStop","Mission " .. Perun.PTS(Perun.MissionHash) .. " finished",nil,nil);
|
||||||
Perun.LogAllStats()
|
Perun.LogAllStats()
|
||||||
Perun.MissionHash=Perun.GenerateMissionHash();
|
Perun.MissionHash=Perun.GenerateMissionHash();
|
||||||
Perun.StatData = {}
|
Perun.StatData = {}
|
||||||
@@ -644,14 +653,14 @@ end
|
|||||||
|
|
||||||
Perun.onPlayerDisconnect = function(id, err_code)
|
Perun.onPlayerDisconnect = function(id, err_code)
|
||||||
-- Player disconnected
|
-- Player disconnected
|
||||||
Perun.LogEvent("disconnect", "Player " .. id .. " disconnected.",nil,nil);
|
Perun.LogEvent("disconnect", "Player " .. Perun.PTS(id) .. " disconnected.",nil,nil);
|
||||||
|
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Perun.onPlayerStop = function (id)
|
Perun.onPlayerStop = function (id)
|
||||||
-- Player left the simulation (happens right before a disconnect, if player exited by desire)
|
-- Player left the simulation (happens right before a disconnect, if player exited by desire)
|
||||||
Perun.LogEvent("quit", "Player " .. id .. " quit the server.",nil,nil);
|
Perun.LogEvent("quit", "Player " .. Perun.PTS(id) .. " quit the server.",nil,nil);
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -706,7 +715,7 @@ end
|
|||||||
|
|
||||||
Perun.onPlayerTrySendChat = function (playerID, msg, all)
|
Perun.onPlayerTrySendChat = function (playerID, msg, all)
|
||||||
-- Somebody tries to send chat message
|
-- Somebody tries to send chat message
|
||||||
if msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 and msg~=Perun.ConnectionError then
|
if Perun.RecordChatMessages and msg~=Perun.MOTD_L1 and msg~=Perun.MOTD_L2 and msg~=Perun.ConnectionError then
|
||||||
Perun.LogChat(playerID,msg,all)
|
Perun.LogChat(playerID,msg,all)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -724,11 +733,11 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
arg2 = "Cannon"
|
arg2 = "Cannon"
|
||||||
end
|
end
|
||||||
|
|
||||||
Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .." killed friendly " .. Perun.GetMulticrewCrewNames(arg3) .. " using " .. arg2,nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),Perun.SideID2Name( Perun.PTS(net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .." killed friendly " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg3)) .. " using " .. Perun.PTS(arg2),nil,nil);
|
||||||
|
|
||||||
elseif eventName == "mission_end" then
|
elseif eventName == "mission_end" then
|
||||||
--"mission_end", winner, msg
|
--"mission_end", winner, msg
|
||||||
Perun.LogEvent(eventName,"Mission finished, winner " .. arg1 .. " message: " .. arg2,nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),"Mission finished, winner " .. Perun.PTS(arg1) .. " message: " .. Perun.PTS(arg2),nil,nil);
|
||||||
|
|
||||||
elseif eventName == "kill" then
|
elseif eventName == "kill" then
|
||||||
--"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName
|
--"kill", killerPlayerID, killerUnitType, killerSide, victimPlayerID, victimUnitType, victimSide, weaponName
|
||||||
@@ -791,12 +800,12 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
victim_vehicle = "?"
|
victim_vehicle = "?"
|
||||||
end
|
end
|
||||||
|
|
||||||
Perun.LogEvent(eventName,Perun.SideID2Name(arg3) .. _temp_killers .. " in " .. arg2 .. " killed " .. Perun.SideID2Name(arg6) .. _temp_victims .. " in " .. victim_vehicle .. " using " .. arg7 .. " [".. Perun.GetCategory(arg5).."]",arg7,Perun.GetCategory(arg5));
|
Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(Perun.SideID2Name(arg3)) .. _temp_killers .. " in " .. Perun.PTS(arg2) .. " killed " .. Perun.PTS(Perun.SideID2Name(arg6)) .. Perun.PTS(_temp_victims) .. " in " .. Perun.PTS(victim_vehicle) .. " using " .. Perun.PTS(arg7) .. " [".. Perun.PTS(Perun.GetCategory(arg5)).."]",arg7,Perun.GetCategory(arg5));
|
||||||
|
|
||||||
elseif eventName == "self_kill" then
|
elseif eventName == "self_kill" then
|
||||||
--"self_kill", playerID
|
--"self_kill", playerID
|
||||||
Perun.LogStats(arg1);
|
Perun.LogStats(arg1);
|
||||||
Perun.LogEvent(eventName,net.get_player_info(arg1, "name") .. " killed himself",nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(net.get_player_info(arg1, "name")) .. " killed himself",nil,nil);
|
||||||
|
|
||||||
elseif eventName == "change_slot" then
|
elseif eventName == "change_slot" then
|
||||||
--"change_slot", playerID, slotID, prevSide
|
--"change_slot", playerID, slotID, prevSide
|
||||||
@@ -807,7 +816,7 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
else
|
else
|
||||||
_sub_slot =" (" .. _sub_slot .. ") "
|
_sub_slot =" (" .. _sub_slot .. ") "
|
||||||
end
|
end
|
||||||
Perun.LogEvent(eventName,Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player " .. net.get_player_info(arg1, "name") .. " changed slot to " .. _master_type .. " " .. _sub_slot,nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player " .. Perun.PTS(net.get_player_info(arg1, "name")) .. " changed slot to " .. Perun.PTS(_master_type) .. " " .. Perun.PTS(_sub_slot),nil,nil);
|
||||||
|
|
||||||
Perun.LogStats(arg1);
|
Perun.LogStats(arg1);
|
||||||
Perun.LogStatsCount(arg1,"init")
|
Perun.LogStatsCount(arg1,"init")
|
||||||
@@ -816,23 +825,23 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
elseif eventName == "connect" then
|
elseif eventName == "connect" then
|
||||||
--"connect", playerID, name
|
--"connect", playerID, name
|
||||||
Perun.LogLogin(arg1);
|
Perun.LogLogin(arg1);
|
||||||
Perun.LogEvent(eventName,"Player "..net.get_player_info(arg1, "name") .. " connected",nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),"Player "..Perun.PTS(net.get_player_info(arg1, "name")) .. " connected",nil,nil);
|
||||||
Perun.PlayersTableCache["p"..arg1]=net.get_player_info(arg1);
|
Perun.PlayersTableCache["p"..arg1]=net.get_player_info(arg1);
|
||||||
|
|
||||||
elseif eventName == "disconnect" then
|
elseif eventName == "disconnect" then
|
||||||
--"disconnect", playerID, name, playerSide, reason_code
|
--"disconnect", playerID, name, playerSide, reason_code
|
||||||
Perun.LogEvent(eventName,"Player " .. arg2 .. " disconnected (".. arg4 .. ")." ,arg4,nil);
|
Perun.LogEvent(Perun.PTS(eventName),"Player " .. Perun.PTS(arg2) .. " disconnected (".. Perun.PTS(arg4) .. ")." ,arg4,nil);
|
||||||
Perun.LogStats(arg1);
|
Perun.LogStats(arg1);
|
||||||
|
|
||||||
elseif eventName == "crash" then
|
elseif eventName == "crash" then
|
||||||
--"crash", playerID, unit_missionID
|
--"crash", playerID, unit_missionID
|
||||||
Perun.LogStatsCountCrew (arg1,"crash")
|
Perun.LogStatsCountCrew (arg1,"crash")
|
||||||
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " crashed in " .. DCS.getUnitType(arg2),nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " crashed in " .. Perun.PTS(DCS.getUnitType(arg2)),nil,nil);
|
||||||
|
|
||||||
elseif eventName == "eject" then
|
elseif eventName == "eject" then
|
||||||
--"eject", playerID, unit_missionID
|
--"eject", playerID, unit_missionID
|
||||||
Perun.LogStatsCountCrew (arg1,"eject") -- TBD crew or initiator only?
|
Perun.LogStatsCountCrew (arg1,"eject") -- TBD crew or initiator only?
|
||||||
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " ejected " .. DCS.getUnitType(arg2),nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " ejected " .. Perun.PTS(DCS.getUnitType(arg2)),nil,nil);
|
||||||
|
|
||||||
elseif eventName == "takeoff" then
|
elseif eventName == "takeoff" then
|
||||||
--"takeoff", playerID, unit_missionID, airdromeName
|
--"takeoff", playerID, unit_missionID, airdromeName
|
||||||
@@ -843,7 +852,7 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(true,arg3))
|
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(true,arg3))
|
||||||
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " took off in ".. DCS.getUnitType(arg2) .. _temp_airfield,arg3,nil);
|
Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " took off in ".. Perun.PTS(DCS.getUnitType(arg2)) .. Perun.PTS(_temp_airfield),arg3,nil);
|
||||||
|
|
||||||
elseif eventName == "landing" then
|
elseif eventName == "landing" then
|
||||||
--"landing", playerID, unit_missionID, airdromeName
|
--"landing", playerID, unit_missionID, airdromeName
|
||||||
@@ -854,15 +863,15 @@ Perun.onGameEvent = function (eventName,arg1,arg2,arg3,arg4,arg5,arg6,arg7)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(false,arg3))
|
Perun.LogStatsCountCrew (arg1,Perun.GetTakeOffLandingEvent(false,arg3))
|
||||||
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " landed in " .. DCS.getUnitType(arg2).. _temp_airfield,arg3,nil);
|
Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " landed in " .. Perun.PTS(DCS.getUnitType(arg2)).. Perun.PTS(_temp_airfield),arg3,nil);
|
||||||
|
|
||||||
elseif eventName == "pilot_death" then
|
elseif eventName == "pilot_death" then
|
||||||
--"pilot_death", playerID, unit_missionID
|
--"pilot_death", playerID, unit_missionID
|
||||||
Perun.LogStatsCountCrew (arg1,"pilot_death") -- TBD crew or initiator only?
|
Perun.LogStatsCountCrew (arg1,"pilot_death") -- TBD crew or initiator only?
|
||||||
Perun.LogEvent(eventName, Perun.SideID2Name( net.get_player_info(arg1, "side")) .. " player(s) " .. Perun.GetMulticrewCrewNames(arg1) .. " in " .. DCS.getUnitType(arg2) .. " died",nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName), Perun.PTS(Perun.SideID2Name( net.get_player_info(arg1, "side"))) .. " player(s) " .. Perun.PTS(Perun.GetMulticrewCrewNames(arg1)) .. " in " .. Perun.PTS(DCS.getUnitType(arg2)) .. " died",nil,nil);
|
||||||
|
|
||||||
else
|
else
|
||||||
Perun.LogEvent(eventName,"Unknown event type",nil,nil);
|
Perun.LogEvent(Perun.PTS(eventName),"Unknown event type",nil,nil);
|
||||||
|
|
||||||
end
|
end
|
||||||
local _delay = (DCS.getRealTime() - _now) * 1000000
|
local _delay = (DCS.getRealTime() - _now) * 1000000
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="Google.Protobuf" version="3.5.1" targetFramework="net461" />
|
<package id="Google.Protobuf" version="3.15.0" targetFramework="net461" />
|
||||||
<package id="MySql.Data" version="8.0.15" targetFramework="net461" />
|
<package id="MySql.Data" version="8.0.15" targetFramework="net461" />
|
||||||
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net461" />
|
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net461" />
|
||||||
</packages>
|
</packages>
|
||||||
@@ -261,6 +261,14 @@ public class DatabaseController
|
|||||||
DatabaseStatus = true; // True as connection is not broken
|
DatabaseStatus = true; // True as connection is not broken
|
||||||
ReturnValue = 1; // Return a value to remove this query from quae
|
ReturnValue = 1; // Return a value to remove this query from quae
|
||||||
break;
|
break;
|
||||||
|
case 1366: // Invalid character
|
||||||
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType);
|
||||||
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType);
|
||||||
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error: {m_ex.Message}", 1, 1, TCPFrameType);
|
||||||
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - frame skipped, it will not be saved to the database", 1, 1, TCPFrameType);
|
||||||
|
DatabaseStatus = true; // True as connection is not broken
|
||||||
|
ReturnValue = 1; // Return a value to remove this query from queue
|
||||||
|
break;
|
||||||
default: // Default error handler
|
default: // Default error handler
|
||||||
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType);
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - error id: {m_ex.Number}", 1, 1, TCPFrameType);
|
||||||
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType);
|
PerunHelper.LogError(ref Globals.AppLogHistory, $"ERROR MySQL - query: {SQLQueryTxt}", 1, 1, TCPFrameType);
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ namespace Perun_v1
|
|||||||
trayIconMain.Text = this.Text; // Set notification icon text
|
trayIconMain.Text = this.Text; // Set notification icon text
|
||||||
|
|
||||||
// Prepare MySQL connection string
|
// Prepare MySQL connection string
|
||||||
Globals.DatabaseConnection.DatabaseConnectionString = $"server={con_txt_mysql_server.Text};user={con_txt_mysql_username.Text};database={con_txt_mysql_database.Text};port={con_txt_mysql_port.Text};password={con_txt_mysql_password.Text}";
|
Globals.DatabaseConnection.DatabaseConnectionString = $"server={con_txt_mysql_server.Text};user={con_txt_mysql_username.Text};database={con_txt_mysql_database.Text};port={con_txt_mysql_port.Text};password={con_txt_mysql_password.Text};CharSet=utf8mb4";
|
||||||
|
|
||||||
// Start listening
|
// Start listening
|
||||||
PerunHelper.LogInfo(ref Globals.AppLogHistory, "Opening connections", 0, 1);
|
PerunHelper.LogInfo(ref Globals.AppLogHistory, "Opening connections", 0, 1);
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<PublisherName>szporwolik</PublisherName>
|
<PublisherName>szporwolik</PublisherName>
|
||||||
<SuiteName>Perun</SuiteName>
|
<SuiteName>Perun</SuiteName>
|
||||||
<WebPage>Perun.htm</WebPage>
|
<WebPage>Perun.htm</WebPage>
|
||||||
<ApplicationRevision>1</ApplicationRevision>
|
<ApplicationRevision>0</ApplicationRevision>
|
||||||
<ApplicationVersion>0.12.0.%2a</ApplicationVersion>
|
<ApplicationVersion>0.12.1.%2a</ApplicationVersion>
|
||||||
<UseApplicationTrust>false</UseApplicationTrust>
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
<CreateDesktopShortcut>true</CreateDesktopShortcut>
|
<CreateDesktopShortcut>true</CreateDesktopShortcut>
|
||||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ using System.Runtime.InteropServices;
|
|||||||
// Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki
|
// Możesz określić wszystkie wartości lub użyć domyślnych numerów kompilacji i poprawki
|
||||||
// przy użyciu symbolu „*”, tak jak pokazano poniżej:
|
// przy użyciu symbolu „*”, tak jak pokazano poniżej:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("0.12.0.0")]
|
[assembly: AssemblyVersion("0.12.1.0")]
|
||||||
[assembly: AssemblyFileVersion("0.12.0.0")]
|
[assembly: AssemblyFileVersion("0.12.1.0")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
<value>3306</value>
|
<value>3306</value>
|
||||||
</setting>
|
</setting>
|
||||||
<setting name="DCS_Server_Port" serializeAs="String">
|
<setting name="DCS_Server_Port" serializeAs="String">
|
||||||
<value>48620</value>
|
<value>48621</value>
|
||||||
</setting>
|
</setting>
|
||||||
<setting name="DCS_Instance" serializeAs="String">
|
<setting name="DCS_Instance" serializeAs="String">
|
||||||
<value>1</value>
|
<value>1</value>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="BouncyCastle" version="1.8.3.1" targetFramework="net48" />
|
<package id="BouncyCastle" version="1.8.9" targetFramework="net48" />
|
||||||
<package id="Google.Protobuf" version="3.6.1" targetFramework="net48" />
|
<package id="Google.Protobuf" version="3.15.0" targetFramework="net48" />
|
||||||
<package id="MySql.Data" version="8.0.17" targetFramework="net48" />
|
<package id="MySql.Data" version="8.0.17" targetFramework="net48" />
|
||||||
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net48" />
|
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
|
||||||
<package id="SSH.NET" version="2016.1.0" targetFramework="net48" />
|
<package id="SSH.NET" version="2020.0.2" targetFramework="net48" />
|
||||||
</packages>
|
</packages>
|
||||||
@@ -14,10 +14,10 @@ CREATE TABLE IF NOT EXISTS `pe_Config` (
|
|||||||
`pe_Config_id` int(11) NOT NULL,
|
`pe_Config_id` int(11) NOT NULL,
|
||||||
`pe_Config_payload` varchar(255) DEFAULT NULL,
|
`pe_Config_payload` varchar(255) DEFAULT NULL,
|
||||||
PRIMARY KEY (`pe_Config_id`)
|
PRIMARY KEY (`pe_Config_id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES
|
INSERT INTO `pe_Config` (`pe_Config_id`, `pe_Config_payload`) VALUES
|
||||||
(1, 'v0.12.0');
|
(1, 'v0.12.1');
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_DataMissionHashes`;
|
DROP TABLE IF EXISTS `pe_DataMissionHashes`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
|
CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
|
||||||
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataMissionHashes` (
|
|||||||
PRIMARY KEY (`pe_DataMissionHashes_id`),
|
PRIMARY KEY (`pe_DataMissionHashes_id`),
|
||||||
UNIQUE KEY `UNIQUE_hash` (`pe_DataMissionHashes_hash`),
|
UNIQUE KEY `UNIQUE_hash` (`pe_DataMissionHashes_hash`),
|
||||||
KEY `pe_DataMissionHashes_instance` (`pe_DataMissionHashes_instance`)
|
KEY `pe_DataMissionHashes_instance` (`pe_DataMissionHashes_instance`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_DataPlayers`;
|
DROP TABLE IF EXISTS `pe_DataPlayers`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
|
CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
|
||||||
@@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataPlayers` (
|
|||||||
`pe_DataPlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
`pe_DataPlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`pe_DataPlayers_id`),
|
PRIMARY KEY (`pe_DataPlayers_id`),
|
||||||
UNIQUE KEY `UNIQUE_UCID` (`pe_DataPlayers_ucid`)
|
UNIQUE KEY `UNIQUE_UCID` (`pe_DataPlayers_ucid`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_DataRaw`;
|
DROP TABLE IF EXISTS `pe_DataRaw`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
|
CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
|
||||||
@@ -50,7 +50,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataRaw` (
|
|||||||
PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`),
|
PRIMARY KEY (`pe_dataraw_type`,`pe_dataraw_instance`),
|
||||||
KEY `pe_dataraw_type_pe_dataraw_instance` (`pe_dataraw_type`,`pe_dataraw_instance`),
|
KEY `pe_dataraw_type_pe_dataraw_instance` (`pe_dataraw_type`,`pe_dataraw_instance`),
|
||||||
KEY `pe_dataraw_updated` (`pe_dataraw_updated`)
|
KEY `pe_dataraw_updated` (`pe_dataraw_updated`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_DataTypes`;
|
DROP TABLE IF EXISTS `pe_DataTypes`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
|
CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
|
||||||
@@ -59,7 +59,7 @@ CREATE TABLE IF NOT EXISTS `pe_DataTypes` (
|
|||||||
`pe_DataTypes_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
`pe_DataTypes_update` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
PRIMARY KEY (`pe_DataTypes_id`),
|
PRIMARY KEY (`pe_DataTypes_id`),
|
||||||
UNIQUE KEY `UNIQUE_TYPE_NAME` (`pe_DataTypes_name`)
|
UNIQUE KEY `UNIQUE_TYPE_NAME` (`pe_DataTypes_name`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_LogChat`;
|
DROP TABLE IF EXISTS `pe_LogChat`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_LogChat` (
|
CREATE TABLE IF NOT EXISTS `pe_LogChat` (
|
||||||
@@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogChat` (
|
|||||||
KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`),
|
KEY `pe_LogChat_playerid` (`pe_LogChat_playerid`),
|
||||||
KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`),
|
KEY `pe_LogChat_datetime` (`pe_LogChat_datetime`),
|
||||||
KEY `pe_LogChat_all` (`pe_LogChat_all`)
|
KEY `pe_LogChat_all` (`pe_LogChat_all`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_LogEvent`;
|
DROP TABLE IF EXISTS `pe_LogEvent`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
|
CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
|
||||||
@@ -90,7 +90,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogEvent` (
|
|||||||
KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`),
|
KEY `pe_LogEvent_missionhash_id` (`pe_LogEvent_missionhash_id`),
|
||||||
KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`),
|
KEY `pe_LogEvent_datetime` (`pe_LogEvent_datetime`),
|
||||||
KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`)
|
KEY `pe_LogEvent_type_2` (`pe_LogEvent_type`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_LogLogins`;
|
DROP TABLE IF EXISTS `pe_LogLogins`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
|
CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
|
||||||
@@ -104,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogLogins` (
|
|||||||
KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`),
|
KEY `pe_LogLogins_playerid` (`pe_LogLogins_playerid`),
|
||||||
KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`),
|
KEY `pe_LogLogins_datetime` (`pe_LogLogins_datetime`),
|
||||||
KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`)
|
KEY `pe_LogLogins_instance` (`pe_LogLogins_instance`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_LogStats`;
|
DROP TABLE IF EXISTS `pe_LogStats`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_LogStats` (
|
CREATE TABLE IF NOT EXISTS `pe_LogStats` (
|
||||||
@@ -149,7 +149,7 @@ CREATE TABLE IF NOT EXISTS `pe_LogStats` (
|
|||||||
KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`),
|
KEY `pe_LogStats_masterslot` (`pe_LogStats_masterslot`),
|
||||||
KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`),
|
KEY `pe_LogStats_mstatus` (`pe_LogStats_mstatus`),
|
||||||
KEY `pe_LogStats_seat` (`pe_LogStats_seat`)
|
KEY `pe_LogStats_seat` (`pe_LogStats_seat`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`;
|
DROP TRIGGER IF EXISTS `pe_LogStats_UPDATE`;
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN
|
CREATE TRIGGER `pe_LogStats_UPDATE` BEFORE UPDATE ON `pe_LogStats` FOR EACH ROW BEGIN
|
||||||
@@ -182,7 +182,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlinePlayers` (
|
|||||||
`pe_OnlinePlayers_slot` varchar(255) DEFAULT NULL,
|
`pe_OnlinePlayers_slot` varchar(255) DEFAULT NULL,
|
||||||
`pe_OnlinePlayers_ucid` varchar(255) DEFAULT NULL,
|
`pe_OnlinePlayers_ucid` varchar(255) DEFAULT NULL,
|
||||||
`pe_OnlinePlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
`pe_OnlinePlayers_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `pe_OnlineStatus`;
|
DROP TABLE IF EXISTS `pe_OnlineStatus`;
|
||||||
CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
|
CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
|
||||||
@@ -197,7 +197,7 @@ CREATE TABLE IF NOT EXISTS `pe_OnlineStatus` (
|
|||||||
`pe_OnlineStatus_perunversion_winapp` varchar(255) DEFAULT NULL,
|
`pe_OnlineStatus_perunversion_winapp` varchar(255) DEFAULT NULL,
|
||||||
`pe_OnlineStatus_perunversion_dcshook` varchar(255) DEFAULT NULL,
|
`pe_OnlineStatus_perunversion_dcshook` varchar(255) DEFAULT NULL,
|
||||||
`pe_OnlineStatus_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
`pe_OnlineStatus_updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
|
||||||
ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`);
|
ALTER TABLE `pe_DataMissionHashes` ADD FULLTEXT KEY `pe_DataMissionHashes_hash` (`pe_DataMissionHashes_hash`);
|
||||||
|
|||||||
238
AUDIT.md
Normal file
238
AUDIT.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Audit de reprise — Perun for DCS World
|
||||||
|
|
||||||
|
> Revue menée pour décider d'une reprise du projet (upstream `szporwolik/perun`,
|
||||||
|
> **archivé le 2026-03-29**, licence MIT). Fork de travail : `DaKerboul/perun`,
|
||||||
|
> miroir Gitea : `git.kerboul.me/kerboul/perun`.
|
||||||
|
>
|
||||||
|
> **Méthode.** Revue à 5 casquettes (Architecte, Sécurité, Backend .NET, DCS/Lua,
|
||||||
|
> DevOps/DBA). **Périmètre audité** : le code réellement écrit par le projet —
|
||||||
|
> hook Lua (`01_DCS`), app C# WinForms (`02_Windows_App`), wrapper C++ (`03_*`,
|
||||||
|
> hors arbre Lua 5.1.5 vendoré), schéma MySQL (`04_MySQL`), exemple PHP
|
||||||
|
> (`05_Misc`). Les ~35 `.c`/`.h` de `lua-5.1.5/` sont du Lua amont vendoré et **ne
|
||||||
|
> sont pas audités**.
|
||||||
|
> Date : 2026-06-11.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Synthèse — verdict
|
||||||
|
|
||||||
|
Projet **fonctionnellement riche et bien pensé sur le fond** (modèle de données
|
||||||
|
propre, couverture événementielle DCS complète, multi-instances, intégrations
|
||||||
|
SRS/LotATC). Mais **dette de sécurité critique** et code applicatif daté
|
||||||
|
(monolithes, threading artisanal, zéro test, zéro CI, Windows/.NET Framework
|
||||||
|
uniquement). C'est une **bonne base à reprendre**, à condition de traiter les
|
||||||
|
points P0 **avant** toute remise en production.
|
||||||
|
|
||||||
|
| # | Domaine | Gravité | Sévérité |
|
||||||
|
|---|---------|---------|----------|
|
||||||
|
| S1 | Injection SQL via données joueur (app C#) | **Critique** | 🔴 P0 |
|
||||||
|
| S2 | Port TCP sans authentification, bind `0.0.0.0` | **Critique** | 🔴 P0 |
|
||||||
|
| S3 | XSS stocké dans l'exemple PHP | Élevé | 🟠 P1 |
|
||||||
|
| S4 | Mot de passe MySQL stocké en clair | Élevé | 🟠 P1 |
|
||||||
|
| S5 | PII (IP, UCID) sans rétention ni consentement (RGPD) | Élevé | 🟠 P1 |
|
||||||
|
| B1 | Threading non synchronisé (buffer partagé) | Élevé | 🟠 P1 |
|
||||||
|
| B2 | `ExecuteReader` pour des INSERT/UPDATE, 1 connexion/frame | Moyen | 🟡 P2 |
|
||||||
|
| A1 | Monolithes, `dynamic`, aucun test, aucune CI | Moyen | 🟡 P2 |
|
||||||
|
| L1 | Fuite de variables globales dans le hook DCS | Moyen | 🟡 P2 |
|
||||||
|
| D1 | Lua 5.1.5 vendoré dans le repo, pas de build reproductible | Moyen | 🟡 P2 |
|
||||||
|
| D2 | Pas de FK, dépendance à `STRICT_TRANS_TABLES` off, pas de migrations | Faible | 🟢 P3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecte / Lead — structure & dette
|
||||||
|
|
||||||
|
**Points forts**
|
||||||
|
- Découpage fonctionnel clair en 5 dossiers numérotés, lisible d'emblée.
|
||||||
|
- Séparation nette des responsabilités : collecte (Lua) → transport (TCP/DLL) →
|
||||||
|
persistance (C#) → restitution (PHP). Le protocole de trames (IDs 1/2/3/50…101)
|
||||||
|
est documenté dans le README.
|
||||||
|
- Multi-instances supporté de bout en bout (champ `instance` partout).
|
||||||
|
|
||||||
|
**Points faibles**
|
||||||
|
- **Monolithes.** `DatabaseController.SendToMySql` fait ~290 lignes avec un `switch`
|
||||||
|
géant mêlant construction SQL, mapping métier et logging
|
||||||
|
(`01_Classes/DatabaseController.cs:12-304`). Idem `TCPController.StartListen`
|
||||||
|
(boucles imbriquées sur ~170 lignes).
|
||||||
|
- **`dynamic` partout** pour le JSON entrant (`DatabaseController.cs:25`,
|
||||||
|
`TCPController.cs:121`) : aucune validation de schéma, accès `TCPFrame.payload.x`
|
||||||
|
qui lèvent au moindre champ manquant → exceptions au lieu d'un rejet propre.
|
||||||
|
- **Plateforme verrouillée** : VS2017, .NET Framework 4.8, WinForms → Windows
|
||||||
|
uniquement, fin de vie. Or le serveur DCS est Windows, mais l'app de
|
||||||
|
persistance n'a aucune raison d'y être clouée (elle ne parle que TCP + MySQL).
|
||||||
|
- **TODO laissé en dur** (commentaire polonais) :
|
||||||
|
`DatabaseController.cs:92` « TUTAJ DODAC CATCH TBD » → gestion d'erreur inachevée.
|
||||||
|
- **Aucun test, aucune CI**, dérive de version (cf. §4 L-version).
|
||||||
|
|
||||||
|
**À faire**
|
||||||
|
- [ ] Extraire la construction SQL dans une couche dédiée (un handler par type de
|
||||||
|
trame) + DTO typés à la place de `dynamic`.
|
||||||
|
- [ ] Cibler **.NET 8** + un worker headless multiplateforme ; garder l'UI WinForms
|
||||||
|
en option (ou la remplacer par un petit panneau web/CLI).
|
||||||
|
- [ ] Introduire des tests unitaires (parsing de trames, génération SQL) et un
|
||||||
|
pipeline CI.
|
||||||
|
|
||||||
|
## 2. Sécurité (AppSec) — **bloquant pour la prod**
|
||||||
|
|
||||||
|
**S1 — Injection SQL via données contrôlées par le joueur. 🔴**
|
||||||
|
L'app paramètre *certaines* valeurs (`@PAR_*`) mais **en concatène des dizaines
|
||||||
|
d'autres** directement dans le SQL, dont des champs que n'importe quel joueur
|
||||||
|
maîtrise (UCID, nom, hash de mission, IP, datetime, tous les compteurs `ps_*`) :
|
||||||
|
- `DatabaseController.cs:128-132` (chat) : `ucid`, `missionhash`, `all`, `datetime`.
|
||||||
|
- `DatabaseController.cs:159-165` (stats) : `stat_ucid`, `stat_missionhash` et
|
||||||
|
~30 valeurs `stat_data_perun.ps_*` injectées telles quelles.
|
||||||
|
- `DatabaseController.cs:175-177` (login) : `login_ucid`, `login_ipaddr`, `login_datetime`.
|
||||||
|
- `DatabaseController.cs:50-51, 77-89, 205-206` : `instance`/`type` concaténés.
|
||||||
|
|
||||||
|
Un UCID/nom forgé (ou un module client modifié) permet l'exfiltration ou la
|
||||||
|
destruction de la base. C'est le **défaut n°1 à corriger**.
|
||||||
|
→ **Tout** passer en requêtes paramétrées (`MySqlParameter`), sans exception.
|
||||||
|
|
||||||
|
**S2 — Listener TCP non authentifié, exposé. 🔴**
|
||||||
|
`new TcpListener(IPAddress.Any, intListenPort)` (`TCPController.cs:51`) écoute sur
|
||||||
|
**toutes** les interfaces, **sans authentification ni allowlist**. Couplé à S1,
|
||||||
|
n'importe quel hôte joignant le port (48621 par défaut) injecte des trames
|
||||||
|
arbitraires → compromission complète de la base.
|
||||||
|
→ Par défaut **bind `127.0.0.1`** (hook et app sont quasi toujours sur la même
|
||||||
|
machine), + secret partagé/HMAC sur les trames, + allowlist d'IP.
|
||||||
|
|
||||||
|
**S3 — XSS stocké (PHP). 🟠**
|
||||||
|
`05_Misc/05_PHP_Example/index.php` réinjecte en HTML des données joueur **sans
|
||||||
|
échappement** : message de chat (`:110`), nom (`:92, :109, :143`),
|
||||||
|
contenu d'événement (`:127`). Un joueur dont le nom vaut `<script>…</script>`
|
||||||
|
exécute du JS dans le navigateur de l'admin. Les requêtes elles-mêmes sont
|
||||||
|
statiques (pas de SQLi côté PHP), le risque est l'**XSS**.
|
||||||
|
→ `htmlspecialchars()` systématique sur toute sortie ; corriger aussi le HTML
|
||||||
|
invalide (`<h1>` fermé par `</h2>` ligne 44).
|
||||||
|
|
||||||
|
**S4 — Identifiants MySQL en clair. 🟠**
|
||||||
|
Le mot de passe est stocké en `String` dans les user settings .NET
|
||||||
|
(`02_Forms/form_Main.cs:119`, clé `MYSQL_Password` de `app.config`) → écrit en
|
||||||
|
clair dans `user.config`.
|
||||||
|
→ Chiffrer via **DPAPI** (`ProtectedData`) ou déléguer à un gestionnaire de
|
||||||
|
secrets ; a minima ne jamais journaliser la chaîne de connexion.
|
||||||
|
|
||||||
|
**S5 — Données personnelles (RGPD). 🟠**
|
||||||
|
Le hook collecte et stocke **adresses IP** et **UCID** des joueurs
|
||||||
|
(`Perun-hook.lua:364-366` → `pe_DataPlayers_lastip`, `pe_LogLogins_ip`), sans
|
||||||
|
politique de rétention ni information des joueurs.
|
||||||
|
→ Définir une durée de rétention + purge, anonymiser/hacher l'IP si non
|
||||||
|
nécessaire, documenter (mention serveur + Discord).
|
||||||
|
|
||||||
|
## 3. Backend / .NET — qualité & robustesse
|
||||||
|
|
||||||
|
**B1 — Threading artisanal non synchronisé. 🟠**
|
||||||
|
Le thread TCP écrit dans `Globals.arrMySQLSendBuffer` (tableau fixe) pendant que
|
||||||
|
le thread d'envoi le lit, **sans verrou** (`TCPController.cs:130-142`). Buffer
|
||||||
|
plein = paquets **silencieusement perdus** (`:139-141`), scan linéaire O(n) par
|
||||||
|
paquet. Accès concurrents → corruption/race.
|
||||||
|
→ Remplacer par une `BlockingCollection<T>`/`Channel<T>` thread-safe et bornée.
|
||||||
|
|
||||||
|
**B2 — Accès base inefficace. 🟡**
|
||||||
|
- `ExecuteReader()` utilisé pour exécuter des lots d'INSERT/UPDATE
|
||||||
|
(`DatabaseController.cs:217`) : devrait être `ExecuteNonQuery()`.
|
||||||
|
- **Une nouvelle `MySqlConnection` ouverte/fermée par trame** (`:34, :292`) :
|
||||||
|
pas de réutilisation du pool, surcoût réseau par paquet.
|
||||||
|
- Tout est **synchrone bloquant** (pas d'`async`/`await`).
|
||||||
|
→ Connexion/pool réutilisé, requêtes asynchrones, `ExecuteNonQueryAsync`.
|
||||||
|
|
||||||
|
**Autres**
|
||||||
|
- Dépendance `Newtonsoft.Json` → migrable vers `System.Text.Json`.
|
||||||
|
- Gestion d'erreurs par numéros MySQL en dur (`:246-279`) : utile mais fragile,
|
||||||
|
à compléter (le catch manquant signalé en `:92`).
|
||||||
|
|
||||||
|
## 4. DCS / Lua / Intégration
|
||||||
|
|
||||||
|
**Points forts**
|
||||||
|
- Couverture événementielle DCS **très complète** : kill (avec catégorisation
|
||||||
|
PvP/AI), friendly fire, crash, eject, takeoff/landing (airfield/ship/FARP),
|
||||||
|
multicrew, change_slot, connect/disconnect, chat, MOTD. C'est le vrai actif du
|
||||||
|
projet (`Perun-hook.lua:725-879`).
|
||||||
|
- Comptage de stats maison car les stats natives DCS sont peu fiables (choix
|
||||||
|
assumé et pertinent).
|
||||||
|
|
||||||
|
**Points faibles**
|
||||||
|
- **L1 — Fuite de variables globales 🟡** : plusieurs variables sont assignées
|
||||||
|
sans `local` dans l'environnement *hook* (privilégié et partagé) — ex.
|
||||||
|
`_temp_killers`/`_temp_event_type` (`:754-756`), `_master_type`/`_master_slot`/
|
||||||
|
`_sub_slot` (`:813`), `_temp_airfield` (`:849`). Risque de collision avec
|
||||||
|
d'autres hooks installés sur le serveur.
|
||||||
|
- **L-version — dérive de version** : la version du hook est codée en dur
|
||||||
|
`"v0.12.1"` (`:30`) et vit séparément de la version de l'app (`Globals.VersionPerun`).
|
||||||
|
→ source unique de vérité (tag git → injecté au build).
|
||||||
|
- **Coût par frame** : `onSimulationFrame` fait du `table.concat`/JSON à chaque
|
||||||
|
frame ; sur serveur chargé, surveiller le budget temps (déjà mesuré en µs dans
|
||||||
|
les logs — bon réflexe à conserver/exposer).
|
||||||
|
- **Dépendance à une DLL compilée** (`perun.dll`, issue de `03_Perun_Lua_Wrapper`)
|
||||||
|
livrée hors repo : reproductibilité du build à fiabiliser (cf. D1).
|
||||||
|
- TCP **en clair**, pas de TLS (acceptable en loopback, à revoir si distant).
|
||||||
|
|
||||||
|
**À faire**
|
||||||
|
- [ ] `local` sur toutes les temporaires ; passe `luacheck`.
|
||||||
|
- [ ] Versionner hook + app depuis le tag git.
|
||||||
|
- [ ] Documenter/reproduire le build de `perun.dll` (CMake déjà présent).
|
||||||
|
|
||||||
|
## 5. DevOps / DBA / Release
|
||||||
|
|
||||||
|
**Base de données — plutôt saine.**
|
||||||
|
- InnoDB + `utf8mb4`/`unicode_ci`, PK/`AUTO_INCREMENT`, **clés UNIQUE**
|
||||||
|
pertinentes (UCID, hash, type, stats par mission+ucid+type) et index sur les
|
||||||
|
colonnes de tri (`datetime`, `instance`, `type`) — `04_MySQL/m1081_perun.sql`.
|
||||||
|
- **D2 🟢** : pas de **`FOREIGN KEY`** (intégrité référentielle non garantie),
|
||||||
|
dépendance documentée à **`STRICT_TRANS_TABLES` désactivé** (README:42,84) —
|
||||||
|
c'est-à-dire qu'on s'appuie sur la coercition/troncature silencieuse de MySQL,
|
||||||
|
ce qui masque des bugs. Nom de fichier cryptique (`m1081_perun.sql`).
|
||||||
|
- Pas d'outil de **migration** (un seul dump). → introduire des migrations
|
||||||
|
versionnées (Flyway/dbmate/sqitch) et, à terme, des FK + un mode strict assumé.
|
||||||
|
|
||||||
|
**Build / release / repo**
|
||||||
|
- **D1 🟡** : arbre **Lua 5.1.5 complet vendoré** dans `03_Perun_Lua_Wrapper/lua-5.1.5/`
|
||||||
|
→ gonfle le repo et l'audit. Le passer en sous-module / téléchargement au build.
|
||||||
|
- Build **manuel VS2017**, pas de CI, pas d'artefacts reproductibles.
|
||||||
|
- Contributions historiquement attendues sur la branche `dev` (README:135).
|
||||||
|
- Pas de `.gitignore` racine (un seul dans le wrapper).
|
||||||
|
|
||||||
|
**À faire**
|
||||||
|
- [ ] CI (build C# + `luacheck` + lint PHP + validation du schéma SQL).
|
||||||
|
- [ ] Migrations DB versionnées ; activer les FK progressivement.
|
||||||
|
- [ ] Sortir Lua amont du repo ; pipeline de build de la DLL.
|
||||||
|
- [ ] Releases taguées avec binaires (`perun.dll` + app) attachés.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feuille de route priorisée
|
||||||
|
|
||||||
|
**P0 — Sécurité bloquante (avant toute prod)**
|
||||||
|
1. Paramétrer **100 %** des requêtes SQL (S1).
|
||||||
|
2. Bind loopback par défaut + auth/allowlist sur le listener TCP (S2).
|
||||||
|
|
||||||
|
**P1 — Durcissement & conformité**
|
||||||
|
3. Échappement HTML de l'exemple PHP (S3).
|
||||||
|
4. Chiffrer le mot de passe MySQL — DPAPI (S4).
|
||||||
|
5. Rétention/anonymisation IP & UCID, doc RGPD (S5).
|
||||||
|
6. File thread-safe bornée à la place du buffer tableau (B1).
|
||||||
|
|
||||||
|
**P2 — Modernisation**
|
||||||
|
7. Découper les monolithes, DTO typés au lieu de `dynamic` (A1).
|
||||||
|
8. Connexion poolée + accès DB async + `ExecuteNonQuery` (B2).
|
||||||
|
9. `local` + `luacheck` sur le hook, version unifiée depuis git (L1).
|
||||||
|
10. Cibler .NET 8 / worker multiplateforme.
|
||||||
|
11. Tests + CI.
|
||||||
|
|
||||||
|
**P3 — Hygiène long terme**
|
||||||
|
12. Migrations DB, FK, mode SQL strict assumé (D2).
|
||||||
|
13. Désvendoriser Lua, build reproductible de la DLL (D1).
|
||||||
|
14. Releases taguées + artefacts.
|
||||||
|
|
||||||
|
## Plan de reprise suggéré
|
||||||
|
|
||||||
|
1. **Geler le comportement** : quelques tests de caractérisation sur le parsing de
|
||||||
|
trames et la génération SQL, pour refactorer sans régresser.
|
||||||
|
2. **Sprint sécu (P0)** sur une branche `security/sql-and-tcp`, puis tag
|
||||||
|
`v0.13.0-rc` testé en loopback.
|
||||||
|
3. **P1** en incréments livrables.
|
||||||
|
4. Décider de la cible app (garder WinForms vs worker .NET 8) **avant** d'attaquer
|
||||||
|
P2 — ça oriente tout le refactor.
|
||||||
|
|
||||||
|
> Adapté à ton contexte : tu fais déjà tourner Gitea + CI sur le cluster ; un
|
||||||
|
> pipeline build/lint Perun s'y intègre directement, et la communauté Commus DCS
|
||||||
|
> est un terrain de test naturel pour les stats.
|
||||||
13
README.md
13
README.md
@@ -1,8 +1,14 @@
|
|||||||
|
> [!IMPORTANT]
|
||||||
|
> This repository is no longer actively maintained.
|
||||||
|
>
|
||||||
|
> I have not hosted games or played DCS for a long time, so I decided to archive this project.
|
||||||
|
>
|
||||||
|
> The code will remain available for reference. Anyone interested is welcome to fork the repository and continue maintaining or improving it.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ZBW8R5L25A8QW¤cy_code=EUR&source=url)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -30,7 +36,7 @@ Core:
|
|||||||
## Installing
|
## Installing
|
||||||
* Download latest [release](https://github.com/szporwolik/perun/releases)
|
* Download latest [release](https://github.com/szporwolik/perun/releases)
|
||||||
* Copy contents of [01_DCS](https://github.com/szporwolik/perun/tree/master/01_DCS) to your DCS folder in your Saved Games
|
* Copy contents of [01_DCS](https://github.com/szporwolik/perun/tree/master/01_DCS) to your DCS folder in your Saved Games
|
||||||
* From the downloaded release package, put ```perun.dll``` into your DCS folder in your Saved Games,```Mods/services/Perun/dll```
|
* From the downloaded release package, put ```perun.dll``` into your DCS folder in your Saved Games,```Mods/services/Perun/bin```
|
||||||
* You can change your configuration settings in DCS\Mods\services\Perun\lua\perun_config
|
* You can change your configuration settings in DCS\Mods\services\Perun\lua\perun_config
|
||||||
* Create MySQL database using SQL script located in [04_MySQL](https://github.com/szporwolik/perun/tree/master/04_MySQL); note that you need just a one database per DCS server machine - multiple instances pushing data to the one database are supported
|
* Create MySQL database using SQL script located in [04_MySQL](https://github.com/szporwolik/perun/tree/master/04_MySQL); note that you need just a one database per DCS server machine - multiple instances pushing data to the one database are supported
|
||||||
* Ensure that your MySQL config is not using STRICT_TRANS_TABLES
|
* Ensure that your MySQL config is not using STRICT_TRANS_TABLES
|
||||||
@@ -77,6 +83,9 @@ That probably means that your database does not support JSON functions, you shal
|
|||||||
### I keep getting 1364 MySQL error
|
### I keep getting 1364 MySQL error
|
||||||
Make sure that STRICT_TRANS_TABLES is disabled at your MySQL server, see: https://stackoverflow.com/questions/37964325/how-to-find-and-disable-mysql-strict-mode
|
Make sure that STRICT_TRANS_TABLES is disabled at your MySQL server, see: https://stackoverflow.com/questions/37964325/how-to-find-and-disable-mysql-strict-mode
|
||||||
|
|
||||||
|
### Dynamic slots (DCS 2.9.6 onwards) are not tracked
|
||||||
|
Not yet handled. (Existing server-side events do not support this yet).
|
||||||
|
|
||||||
### Carrier landing are not tracked correctly
|
### Carrier landing are not tracked correctly
|
||||||
DCS API does not track carrier or FARP operations natively, so there is a trick to achive that. At this point of time to detect FARP operations, the FARPs shall have "FARP" string in the group name (set in mission editor). For carrier operations the string "SHIP" is required within group name.
|
DCS API does not track carrier or FARP operations natively, so there is a trick to achive that. At this point of time to detect FARP operations, the FARPs shall have "FARP" string in the group name (set in mission editor). For carrier operations the string "SHIP" is required within group name.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user