Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg (revision 21519)
+++ ps/trunk/binaries/data/config/default.cfg (revision 21520)
@@ -1,485 +1,486 @@
; Global Configuration Settings
;
; **************************************************************
; * DO NOT EDIT THIS FILE if you want personal customisations: *
; * create a text file called "local.cfg" instead, and copy *
; * the lines from this file that you want to change. *
; * *
; * If a setting is part of a section (for instance [hotkey]) *
; * you need to append the section name at the beginning of *
; * your custom line (for instance you need to write *
; * "hotkey.pause = Space" if you want to change the pausing *
; * hotkey to the spacebar). *
; * *
; * On Linux, create: *
; * $XDG_CONFIG_HOME/0ad/config/local.cfg *
; * (Note: $XDG_CONFIG_HOME defaults to ~/.config) *
; * *
; * On OS X, create: *
; * ~/Library/Application\ Support/0ad/config/local.cfg *
; * *
; * On Windows, create: *
; * %appdata%\0ad\config\local.cfg *
; * *
; **************************************************************
; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.)
windowed = false
; Show detailed tooltips (Unit stats)
showdetailedtooltips = false
; Pause the game on window focus loss (Only applicable to single player mode)
pauseonfocusloss = true
; Persist settings after leaving the game setup screen
persistmatchsettings = true
; Default player name to use in multiplayer
; playername = "anonymous"
; Default server name or IP to use in multiplayer
multiplayerserver = "127.0.0.1"
; Force a particular resolution. (If these are 0, the default is
; to keep the current desktop resolution in fullscreen mode or to
; use 1024x768 in windowed mode.)
xres = 0
yres = 0
; Force a non-standard bit depth (if 0 then use the current desktop bit depth)
bpp = 0
; Preferred display (for multidisplay setups, only works with SDL 2.0)
display = 0
; Emulate right-click with Ctrl+Click on Mac mice
macmouse = false
; System settings:
; if false, actors won't be rendered but anything entity will be.
renderactors = true
watereffects=true ; When disabled, force usage of the fixed pipeline water. This is faster, but really, really ugly.
waterfancyeffects = false
waterrealdepth = true
waterrefraction = true
waterreflection = true
shadowsonwater = false
shadows = true
shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High)
; High values can crash the game when using a graphics card with low memory!
shadowpcf = true
vsync = false
particles = true
fog = true
silhouettes = true
showsky = true
nos3tc = false
noautomipmap = true
novbo = false
noframebufferobject = false
; Disable hardware cursors
nohwcursor = false
; Linux only: Set the driconf force_s3tc_enable option at startup,
; for compressed texture support
force_s3tc_enable = true
; Specify the render path. This can be one of:
; default Automatically select one of the below, depending on system capabilities
; fixed Only use OpenGL fixed function pipeline
; shader Use vertex/fragment shaders for transform and lighting where possible
; Using 'fixed' instead of 'default' may work around some graphics-related problems,
; but will reduce performance and features when a modern graphics card is available.
renderpath = default
;;;;; EXPERIMENTAL ;;;;;
; Prefer GLSL shaders over ARB shaders. Allows fancier graphical effects.
preferglsl = false
; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk
gpuskinning = false
; Use smooth LOS interpolation
smoothlos = false
; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath.
postproc = false
; Quality level of shader effects (set to 10 to display all effects)
materialmgr.quality = 2.0
; Maximum distance to display parallax effect. Set to 0 to disable parallax.
materialmgr.PARALLAX_DIST.max = 150
; Maximum distance to display high quality parallax effect.
materialmgr.PARALLAX_HQ_DIST.max = 75
; Maximum distance to display very high quality parallax effect. Set to 30 to enable.
materialmgr.PARALLAX_VHQ_DIST.max = 0
;;;;;;;;;;;;;;;;;;;;;;;;
; Replace alpha-blending with alpha-testing, for performance experiments
forcealphatest = false
; Color of the sky (in "r g b" format)
skycolor = "0 0 0"
[adaptivefps]
session = 60 ; Throttle FPS in running games (prevents 100% CPU workload).
menu = 30 ; Throttle FPS in menus only.
[hotkey]
; Each one of the specified keys will trigger the action on the left
; for multiple-key combinations, separate keys with '+'.
; See keys.txt for the list of key names.
; > SYSTEM SETTINGS
exit = "Ctrl+Break", "Super+Q" ; Exit to desktop
cancel = Escape ; Close or cancel the current dialog box/popup
leave = Escape ; End current game or Exit
confirm = Return ; Confirm the current command
pause = Pause ; Pause/unpause game
screenshot = F2 ; Take PNG screenshot
bigscreenshot = "Shift+F2" ; Take large BMP screenshot
togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode
screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots
wireframe = "Alt+Shift+W" ; Toggle wireframe mode
silhouettes = "Alt+Shift+S" ; Toggle unit silhouettes
showsky = "Alt+Z" ; Toggle sky
; > DIALOG HOTKEYS
summary = "Ctrl+Tab" ; Toggle in-game summary
lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window.
structree = "Alt+Shift+S" ; Show structure tree
civinfo = "Alt+Shift+H" ; Show civilization info
; > CLIPBOARD CONTROLS
copy = "Ctrl+C" ; Copy to clipboard
paste = "Ctrl+V" ; Paste from clipboard
cut = "Ctrl+X" ; Cut selected text and copy to the clipboard
; > CONSOLE SETTINGS
console.toggle = BackQuote, F9 ; Open/close console
; > OVERLAY KEYS
fps.toggle = "Alt+F" ; Toggle frame counter
realtime.toggle = "Alt+T" ; Toggle current display of computer time
session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel
timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter
session.showstatusbars = Tab ; Toggle display of status bars
session.highlightguarding = PgDn ; Toggle highlight of guarding units
session.highlightguarded = PgUp ; Toggle highlight of guarded units
session.toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures
session.toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures
session.togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units
session.diplomacycolors = "Alt+X" ; Toggle diplomacy colors
; > HOTKEYS ONLY
chat = Return ; Toggle chat window
teamchat = "T" ; Toggle chat window in team chat mode
privatechat = "L" ; Toggle chat window and select the previous private chat partner
; > QUICKSAVE
quicksave = "Shift+F5"
quickload = "Shift+F8"
[hotkey.camera]
reset = "R" ; Reset camera rotation to default.
follow = "F" ; Follow the first unit in the selection
rallypointfocus = unused ; Focus the camera on the rally point of the selected building
zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control)
zoom.out = Minus, NumMinus ; Zoom camera out (continuous control)
zoom.wheel.in = WheelUp ; Zoom camera in (stepped control)
zoom.wheel.out = WheelDown ; Zoom camera out (stepped control)
rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards
rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards
rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain
rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain
rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control)
rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control)
pan = MouseMiddle ; Enable scrolling by moving mouse
left = A, LeftArrow ; Scroll or rotate left
right = D, RightArrow ; Scroll or rotate right
up = W, UpArrow ; Scroll or rotate up/forwards
down = S, DownArrow ; Scroll or rotate down/backwards
scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed
scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed
rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed
rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed
zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed
zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed
[hotkey.camera.jump]
1 = F5 ; Jump to position N
2 = F6
3 = F7
4 = F8
;5 =
;6 =
;7 =
;8 =
;9 =
;10 =
[hotkey.camera.jump.set]
1 = "Ctrl+F5" ; Set jump position N
2 = "Ctrl+F6"
3 = "Ctrl+F7"
4 = "Ctrl+F8"
;5 =
;6 =
;7 =
;8 =
;9 =
;10 =
[hotkey.profile]
toggle = "F11" ; Enable/disable real-time profiler
save = "Shift+F11" ; Save current profiler data to logs/profile.txt
[hotkey.profile2]
toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler
[hotkey.selection]
add = Shift ; Add units to selection
militaryonly = Alt ; Add only military units to the selection
nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection
idleonly = "I" ; Select only idle units
woundedonly = "O" ; Select only wounded units
remove = Ctrl ; Remove units from selection
cancel = Esc ; Un-select all units and cancel building placement
idleworker = Period ; Select next idle worker
idlewarrior = ForwardSlash ; Select next idle warrior
idleunit = BackSlash ; Select next idle unit
offscreen = Alt ; Include offscreen units in selection
[hotkey.selection.group.add]
0 = "Shift+0"
1 = "Shift+1"
2 = "Shift+2"
3 = "Shift+3"
4 = "Shift+4"
5 = "Shift+5"
6 = "Shift+6"
7 = "Shift+7"
8 = "Shift+8"
9 = "Shift+9"
[hotkey.selection.group.save]
0 = "Ctrl+0"
1 = "Ctrl+1"
2 = "Ctrl+2"
3 = "Ctrl+3"
4 = "Ctrl+4"
5 = "Ctrl+5"
6 = "Ctrl+6"
7 = "Ctrl+7"
8 = "Ctrl+8"
9 = "Ctrl+9"
[hotkey.selection.group.select]
0 = 0
1 = 1
2 = 2
3 = 3
4 = 4
5 = 5
6 = 6
7 = 7
8 = 8
9 = 9
[hotkey.session]
kill = Delete ; Destroy selected units
stop = "H" ; Stop the current action
backtowork = "Y" ; The unit will go back to work
unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected
move = unused ; Modifier to move to a point instead of another action (e.g. gather)
attack = Ctrl ; Modifier to attack instead of another action (e.g. capture)
attackmove = Ctrl ; Modifier to attackmove when clicking on a point
attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys)
garrison = Ctrl ; Modifier to garrison when clicking on building
autorallypoint = Ctrl ; Modifier to set the rally point on the building itself
guard = "G" ; Modifier to escort/guard when clicking on unit/building
patrol = "P" ; Modifier to patrol a unit
repair = "J" ; Modifier to repair when clicking on building/mechanical unit
queue = Shift ; Modifier to queue unit orders instead of replacing
orderone = Alt ; Modifier to order only one entity in selection.
batchtrain = Shift ; Modifier to train units in batches
massbarter = Shift ; Modifier to barter bunch of resources
masstribute = Shift ; Modifier to tribute bunch of resources
noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit
fulltradeswap = Shift ; Modifier to put the desired trade resource to 100%
unloadtype = Shift ; Modifier to unload all units of type
deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
rotate.cw = RightBracket ; Rotate building placement preview clockwise
rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
[hotkey.session.gui]
toggle = "Alt+G" ; Toggle visibility of session GUI
menu.toggle = "F10" ; Toggle in-game menu
barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page
tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel
[hotkey.session.savedgames]
delete = Delete ; Delete the selected saved game asking confirmation
noconfirmation = Shift ; Do not ask confirmation when deleting a game
[hotkey.session.queueunit] ; > UNIT TRAINING
1 = "Z" ; add first unit type to queue
2 = "X" ; add second unit type to queue
3 = "C" ; add third unit type to queue
4 = "V" ; add fourth unit type to queue
5 = "B" ; add fivth unit type to queue
6 = "N" ; add sixth unit type to queue
7 = "M" ; add seventh unit type to queue
8 = Comma ; add eighth unit type to queue
[hotkey.session.timewarp]
fastforward = Space ; If timewarp mode enabled, speed up the game
rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game
[hotkey.tab]
next = "Tab", "Alt+S" ; Show the next tab
prev = "Shift+Tab", "Alt+W" ; Show the previous tab
[hotkey.text] ; > GUI TEXTBOX HOTKEYS
delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor
delete.right = "Ctrl+Del" ; Delete word to the right of cursor
move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor
move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor
[gui]
cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking)
scale = 1.0 ; GUI scaling factor, for improved compatibility with 4K displays
[gui.gamesetup]
enabletips = true ; Enable/Disable tips during gamesetup (for newcomers)
assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled.
aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest)
aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive)
settingsslide = true ; Enable/Disable settings panel slide
[gui.session]
camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one?
timeelapsedcounter = false ; Show the game duration in the top right corner
batchtrainingsize = 5 ; Number of units to be trained per batch by default (when pressing the hotkey)
scrollbatchratio = 1 ; Number of times you have to scroll to increase/decrease the batchsize by 1
woundedunithotkeythreshold = 33 ; The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number
attackrange = true ; Display attack range overlays of selected defensive structures
aurasrange = true ; Display aura range overlays of selected units and structures
healrange = true ; Display heal range overlays of selected units
rankabovestatusbar = true ; Show rank icons above status bars
respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending)
[gui.session.minimap]
blinkduration = 1.7 ; The blink duration while pinging
pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification
[gui.session.notifications]
attack = true ; Show a chat notification if you are attacked by another player
tribute = true ; Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode
barter = true ; Show a chat notification to observers when a player bartered resources
phase = completed ; Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode. Possible values: none, completed, all.
[gui.splashscreen]
enable = true ; Enable/disable the splashscreen
version = 0 ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch
[joystick] ; EXPERIMENTAL: joystick/gamepad settings
enable = false
deadzone = 8192
[joystick.camera]
pan.x = 0
pan.y = 1
rotate.x = 3
rotate.y = 2
zoom.in = 5
zoom.out = 4
[chat]
timestamp = true ; Show at which time chat messages have been sent
[chat.session]
extended = true ; Whether to display the chat history
[lobby]
history = 0 ; Number of past messages to display on join
room = "arena23" ; Default MUC room to join
server = "lobby.wildfiregames.com" ; Address of lobby server
xpartamupp = "wfgbot23" ; Name of the server-side xmpp client that manage games
buddies = "," ; Comma separated list of playernames that the current user has marked as buddies
rememberpassword = true ; Whether to store the encrypted password in the user config
+secureauth = true ; Secure Lobby Authentication: This prevents the impersonation of other players. The lobby server confirms the identity of the player before they join.
[lobby.columns]
gamerating = false ; Show the average rating of the participating players in a column of the gamelist
[lobby.stun]
enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router.
; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding.
server = "lobby.wildfiregames.com" ; Address of the STUN server.
port = 3478 ; Port of the STUN server.
delay = 200 ; Duration in milliseconds that is waited between STUN messages.
; Smaller numbers speed up joins but also become less stable.
[mod]
enabledmods = "mod public"
[network]
duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join.
lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled.
observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached
[overlay]
fps = "false" ; Show frames per second in top right corner
realtime = "false" ; Show current system time in top right corner
netwarnings = "true" ; Show warnings if the network connection is bad
[profiler2]
autoenable = false ; Enable HTTP server output at startup (default off for security/performance)
gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available
gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available
gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available
[sound]
mastergain = 0.9
musicgain = 0.2
ambientgain = 0.6
actiongain = 0.7
uigain = 0.7
[sound.notify]
nick = true ; Play a sound when someone mentions your name in the lobby or game
[tinygettext]
debug = false ; Print error messages each time a translation for an English string is not found.
[userreport] ; Opt-in online user reporting system
url = "http://feedback.wildfiregames.com/report/upload/v1/"
[view] ; Camera control settings
scroll.speed = 120.0
scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed
rotate.x.speed = 1.2
rotate.x.min = 28.0
rotate.x.max = 60.0
rotate.x.default = 35.0
rotate.y.speed = 2.0
rotate.y.speed.wheel = 0.45
rotate.y.default = 0.0
rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed
drag.speed = 0.5
zoom.speed = 256.0
zoom.speed.wheel = 32.0
zoom.min = 50.0
zoom.max = 200.0
zoom.default = 120.0
zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed
pos.smoothness = 0.1
zoom.smoothness = 0.4
rotate.x.smoothness = 0.5
rotate.y.smoothness = 0.3
near = 2.0 ; Near plane distance
far = 4096.0 ; Far plane distance
fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide
height.smoothness = 0.5
height.min = 16
Index: ps/trunk/binaries/data/mods/public/gui/common/network.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/network.js (revision 21519)
+++ ps/trunk/binaries/data/mods/public/gui/common/network.js (revision 21520)
@@ -1,246 +1,247 @@
/**
* Number of milliseconds to display network warnings.
*/
var g_NetworkWarningTimeout = 3000;
/**
* Currently displayed network warnings. At most one message per user.
*/
var g_NetworkWarnings = {};
/**
* Message-types to be displayed.
*/
var g_NetworkWarningTexts = {
"server-timeout": (msg, username) =>
sprintf(translate("Losing connection to server (%(seconds)ss)"), {
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
}),
"client-timeout": (msg, username) =>
sprintf(translate("%(player)s losing connection (%(seconds)ss)"), {
"player": username,
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
}),
"server-latency": (msg, username) =>
sprintf(translate("Bad connection to server (%(milliseconds)sms)"), {
"milliseconds": msg.meanRTT
}),
"client-latency": (msg, username) =>
sprintf(translate("Bad connection to %(player)s (%(milliseconds)sms)"), {
"player": username,
"milliseconds": msg.meanRTT
})
};
var g_NetworkCommands = {
"/kick": argument => kickPlayer(argument, false),
"/ban": argument => kickPlayer(argument, true),
"/kickspecs": argument => kickObservers(false),
"/banspecs": argument => kickObservers(true),
"/list": argument => addChatMessage({ "type": "clientlist" }),
"/clear": argument => clearChatMessages()
};
var g_ValidPorts = { "min": 1, "max": 65535 };
function getValidPort(port)
{
if (isNaN(+port) || +port < g_ValidPorts.min || +port > g_ValidPorts.max)
return Engine.GetDefaultPort();
return +port;
}
/**
* Must be kept in sync with source/network/NetHost.h
*/
function getDisconnectReason(id, wasConnected)
{
switch (id)
{
case 0: return wasConnected ?
translateWithContext("network disconnect", "Unknown reason") :
translate("This is often caused by UDP port 20595 not being forwarded on the host side, by a firewall, or anti-virus software");
case 1: return translate("The host has ended the game");
case 2: return translate("Incorrect network protocol version");
case 3: return translate("Game is loading, please try again later");
case 4: return translate("Game has already started, no observers allowed");
case 5: return translate("You have been kicked");
case 6: return translate("You have been banned");
case 7: return translate("Playername in use. If you were disconnected, retry in few seconds");
case 8: return translate("Server full");
+ case 9: return translate("Secure lobby authentication failed. Join via lobby");
default:
warn("Unknown disconnect-reason ID received: " + id);
return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
}
}
/**
* Show the disconnect reason in a message box.
*
* @param {number} reason
*/
function reportDisconnect(reason, wasConnected)
{
// Translation: States the reason why the client disconnected from the server.
let reasonText = sprintf(translate("Reason: %(reason)s."), { "reason": getDisconnectReason(reason, wasConnected) });
messageBox(
400, 200,
(wasConnected ?
translate("Lost connection to the server.") :
translate("Failed to connect to the server.")
) + "\n\n" + reasonText,
translate("Disconnected")
);
}
function kickError()
{
addChatMessage({
"type": "system",
"text": translate("Only the host can kick clients!")
});
}
function kickPlayer(username, ban)
{
if (g_IsController)
Engine.KickPlayer(username, ban);
else
kickError();
}
function kickObservers(ban)
{
if (!g_IsController)
{
kickError();
return;
}
for (let guid in g_PlayerAssignments)
if (g_PlayerAssignments[guid].player == -1)
Engine.KickPlayer(g_PlayerAssignments[guid].name, ban);
}
/**
* Sort GUIDs of connected users sorted by playerindex, observers last.
* Requires g_PlayerAssignments.
*/
function sortGUIDsByPlayerID()
{
return Object.keys(g_PlayerAssignments).sort((guidA, guidB) => {
let playerIdA = g_PlayerAssignments[guidA].player;
let playerIdB = g_PlayerAssignments[guidB].player;
if (playerIdA == -1) return +1;
if (playerIdB == -1) return -1;
return playerIdA - playerIdB;
});
}
/**
* Get a colorized list of usernames sorted by player slot, observers last.
* Requires g_PlayerAssignments and colorizePlayernameByGUID.
*
* @returns {string}
*/
function getUsernameList()
{
let usernames = sortGUIDsByPlayerID().map(guid => colorizePlayernameByGUID(guid));
// Translation: Number of currently connected players/observers and their names
return sprintf(translate("Users (%(num)s): %(users)s"), {
"users": usernames.join(translate(", ")),
"num": usernames.length
});
}
/**
* Execute a command locally. Requires addChatMessage.
*
* @param {string} input
* @returns {Boolean} whether a command was executed
*/
function executeNetworkCommand(input)
{
if (input.indexOf("/") != 0)
return false;
let command = input.split(" ", 1)[0];
let argument = input.substr(command.length + 1);
if (g_NetworkCommands[command])
g_NetworkCommands[command](argument);
return !!g_NetworkCommands[command];
}
/**
* Remember this warning for a few seconds.
* Overwrite previous warnings for this user.
*
* @param msg - GUI message sent by NetServer or NetClient
*/
function addNetworkWarning(msg)
{
if (!g_NetworkWarningTexts[msg.warntype])
{
warn("Unknown network warning type received: " + uneval(msg));
return;
}
if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") != "true")
return;
g_NetworkWarnings[msg.guid || "server"] = {
"added": Date.now(),
"msg": msg
};
}
/**
* Colorizes and concatenates all network warnings.
* Returns text and textWidth.
*/
function getNetworkWarnings()
{
// Remove outdated messages
for (let guid in g_NetworkWarnings)
if (Date.now() > g_NetworkWarnings[guid].added + g_NetworkWarningTimeout ||
guid != "server" && !g_PlayerAssignments[guid])
delete g_NetworkWarnings[guid];
// Show local messages first
let guids = Object.keys(g_NetworkWarnings).sort(guid => guid != "server");
let font = Engine.GetGUIObjectByName("gameStateNotifications").font;
let messages = [];
let maxTextWidth = 0;
for (let guid of guids)
{
let msg = g_NetworkWarnings[guid].msg;
// Add formatted text
messages.push(g_NetworkWarningTexts[msg.warntype](msg, colorizePlayernameByGUID(guid)));
// Add width of unformatted text
let username = guid != "server" && g_PlayerAssignments[guid].name;
let textWidth = Engine.GetTextWidth(font, g_NetworkWarningTexts[msg.warntype](msg, username));
maxTextWidth = Math.max(textWidth, maxTextWidth);
}
return {
"messages": messages,
"maxTextWidth": maxTextWidth
};
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js (revision 21519)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.js (revision 21520)
@@ -1,371 +1,381 @@
/**
* Whether we are attempting to join or host a game.
*/
var g_IsConnecting = false;
/**
* "server" or "client"
*/
var g_GameType;
/**
* Server title shown in the lobby gamelist.
*/
var g_ServerName = "";
/**
* Cached to pass it to the gamesetup of the controller to report the game to the lobby.
*/
var g_ServerPort;
var g_IsRejoining = false;
var g_GameAttributes; // used when rejoining
var g_PlayerAssignments; // used when rejoining
var g_UserRating;
/**
* Object containing the IP address and port of the STUN server.
*/
var g_StunEndpoint;
function init(attribs)
{
g_UserRating = attribs.rating;
switch (attribs.multiplayerGameType)
{
case "join":
{
if (Engine.HasXmppClient())
{
if (startJoin(attribs.name, attribs.ip, getValidPort(attribs.port), attribs.useSTUN, attribs.hostJID))
switchSetupPage("pageConnecting");
}
else
switchSetupPage("pageJoin");
break;
}
case "host":
{
Engine.GetGUIObjectByName("hostSTUNWrapper").hidden = !Engine.HasXmppClient();
+ Engine.GetGUIObjectByName("hostLobbyAuthWrapper").hidden = !Engine.HasXmppClient();
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name;
Engine.GetGUIObjectByName("hostServerName").caption =
sprintf(translate("%(name)s's game"), { "name": attribs.name });
Engine.GetGUIObjectByName("useSTUN").checked = Engine.ConfigDB_GetValue("user", "lobby.stun.enabled") == "true";
+ Engine.GetGUIObjectByName("useLobbyAuth").checked = Engine.ConfigDB_GetValue("user", "lobby.secureauth") == "true";
}
switchSetupPage("pageHost");
break;
}
default:
error("Unrecognised multiplayer game type: " + attribs.multiplayerGameType);
break;
}
}
function cancelSetup()
{
if (g_IsConnecting)
Engine.DisconnectNetworkGame();
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("available");
// Keep the page open if an attempt to join/host by ip failed
if (!g_IsConnecting || (Engine.HasXmppClient() && g_GameType == "client"))
{
Engine.PopGuiPage();
return;
}
g_IsConnecting = false;
Engine.GetGUIObjectByName("hostFeedback").caption = "";
if (g_GameType == "client")
switchSetupPage("pageJoin");
else if (g_GameType == "server")
switchSetupPage("pageHost");
else
error("cancelSetup: Unrecognised multiplayer game type: " + g_GameType);
}
function confirmSetup()
{
if (!Engine.GetGUIObjectByName("pageJoin").hidden)
{
let joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;
let joinServer = Engine.GetGUIObjectByName("joinServer").caption;
let joinPort = Engine.GetGUIObjectByName("joinPort").caption;
if (startJoin(joinPlayerName, joinServer, getValidPort(joinPort), false))
switchSetupPage("pageConnecting");
}
else if (!Engine.GetGUIObjectByName("pageHost").hidden)
{
let hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
let hostServerName = Engine.GetGUIObjectByName("hostServerName").caption;
let hostPort = Engine.GetGUIObjectByName("hostPort").caption;
if (!hostServerName)
{
Engine.GetGUIObjectByName("hostFeedback").caption = translate("Please enter a valid server name.");
return;
}
if (getValidPort(hostPort) != +hostPort)
{
Engine.GetGUIObjectByName("hostFeedback").caption = sprintf(
translate("Server port number must be between %(min)s and %(max)s."), {
"min": g_ValidPorts.min,
"max": g_ValidPorts.max
});
return;
}
if (startHost(hostPlayerName, hostServerName, getValidPort(hostPort)))
switchSetupPage("pageConnecting");
}
}
function startConnectionStatus(type)
{
g_GameType = type;
g_IsConnecting = true;
g_IsRejoining = false;
Engine.GetGUIObjectByName("connectionStatus").caption = translate("Connecting to server...");
}
function onTick()
{
if (!g_IsConnecting)
return;
pollAndHandleNetworkClient();
}
function pollAndHandleNetworkClient()
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) }));
// If we're rejoining an active game, we don't want to actually display
// the game setup screen, so perform similar processing to gamesetup.js
// in this screen
if (g_IsRejoining)
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "disconnected":
cancelSetup();
reportDisconnect(message.reason, false);
return;
default:
error("Unrecognised netstatus type: " + message.status);
break;
}
break;
case "gamesetup":
g_GameAttributes = message.data;
break;
case "players":
g_PlayerAssignments = message.newAssignments;
break;
case "start":
// Copy playernames from initial player assignment to the settings
for (let guid in g_PlayerAssignments)
{
let player = g_PlayerAssignments[guid];
if (player.player > 0) // not observer or GAIA
g_GameAttributes.settings.PlayerData[player.player - 1].Name = player.name;
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked": true,
"isRejoining": g_IsRejoining,
"playerAssignments": g_PlayerAssignments
});
break;
case "chat":
break;
case "netwarn":
break;
default:
error("Unrecognised net message type: " + message.type);
}
else
// Not rejoining - just trying to connect to server
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "connected":
Engine.GetGUIObjectByName("connectionStatus").caption = translate("Registering with server...");
break;
case "authenticated":
if (message.rejoining)
{
Engine.GetGUIObjectByName("connectionStatus").caption = translate("Game has already started, rejoining...");
g_IsRejoining = true;
return; // we'll process the game setup messages in the next tick
}
Engine.SwitchGuiPage("page_gamesetup.xml", {
"type": g_GameType,
"serverName": g_ServerName,
"serverPort": g_ServerPort,
"stunEndpoint": g_StunEndpoint
});
return; // don't process any more messages - leave them for the game GUI loop
case "disconnected":
cancelSetup();
reportDisconnect(message.reason, false);
return;
default:
error("Unrecognised netstatus type: " + message.status);
break;
}
break;
case "netwarn":
break;
default:
error("Unrecognised net message type: " + message.type);
break;
}
}
}
function switchSetupPage(newPage)
{
- for (let page of Engine.GetGUIObjectByName("multiplayerPages").children)
- if (page.name.substr(0, 4) == "page")
+ let multiplayerPages = Engine.GetGUIObjectByName("multiplayerPages");
+ for (let page of multiplayerPages.children)
+ if (page.name.startsWith("page"))
page.hidden = true;
+ if (newPage == "pageJoin" || newPage == "pageHost")
+ {
+ let pageSize = multiplayerPages.size;
+ let halfHeight = newPage == "pageJoin" ? 130 : Engine.HasXmppClient() ? 145 : 110;
+ pageSize.top = -halfHeight;
+ pageSize.bottom = halfHeight;
+ multiplayerPages.size = pageSize;
+ }
+
Engine.GetGUIObjectByName(newPage).hidden = false;
Engine.GetGUIObjectByName("hostPlayerNameWrapper").hidden = Engine.HasXmppClient();
Engine.GetGUIObjectByName("hostServerNameWrapper").hidden = !Engine.HasXmppClient();
Engine.GetGUIObjectByName("continueButton").hidden = newPage == "pageConnecting";
}
function startHost(playername, servername, port)
{
startConnectionStatus("server");
saveSettingAndWriteToUserConfig("playername.multiplayer", playername);
saveSettingAndWriteToUserConfig("multiplayerhosting.port", port);
let hostFeedback = Engine.GetGUIObjectByName("hostFeedback");
// Disallow identically named games in the multiplayer lobby
if (Engine.HasXmppClient() &&
Engine.GetGameList().some(game => game.name == servername))
{
cancelSetup();
hostFeedback.caption = translate("Game name already in use.");
return false;
}
if (Engine.HasXmppClient() && Engine.GetGUIObjectByName("useSTUN").checked)
{
g_StunEndpoint = Engine.FindStunEndpoint(port);
if (!g_StunEndpoint)
{
cancelSetup();
hostFeedback.caption = translate("Failed to host via STUN.");
return false;
}
}
+ let useLobbyAuth = Engine.HasXmppClient() && Engine.GetGUIObjectByName("useLobbyAuth").checked;
try
{
- if (g_UserRating)
- Engine.StartNetworkHost(playername + " (" + g_UserRating + ")", port);
- else
- Engine.StartNetworkHost(playername, port);
+ Engine.StartNetworkHost(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), port, playername, useLobbyAuth);
}
catch (e)
{
cancelSetup();
messageBox(
400, 200,
sprintf(translate("Cannot host game: %(message)s."), { "message": e.message }),
translate("Error")
);
return false;
}
g_ServerName = servername;
g_ServerPort = port;
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
return true;
}
/**
* Connects via STUN if the hostJID is given.
*/
function startJoin(playername, ip, port, useSTUN, hostJID = "")
{
try
{
Engine.StartNetworkJoin(playername + (g_UserRating ? " (" + g_UserRating + ")" : ""), ip, port, useSTUN, hostJID);
}
catch (e)
{
cancelSetup();
messageBox(
400, 200,
sprintf(translate("Cannot join game: %(message)s."), { "message": e.message }),
translate("Error")
);
return false;
}
startConnectionStatus("client");
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
else
{
// Only save the player name and host address if they're valid and we're not in the lobby
saveSettingAndWriteToUserConfig("playername.multiplayer", playername);
saveSettingAndWriteToUserConfig("multiplayerserver", ip);
saveSettingAndWriteToUserConfig("multiplayerjoining.port", port);
}
return true;
}
function getDefaultGameName()
{
return sprintf(translate("%(playername)s's game"), {
"playername": multiplayerName()
});
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml (revision 21519)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup_mp/gamesetup_mp.xml (revision 21520)
@@ -1,137 +1,147 @@
Index: ps/trunk/source/lobby/IXmppClient.h
===================================================================
--- ps/trunk/source/lobby/IXmppClient.h (revision 21519)
+++ ps/trunk/source/lobby/IXmppClient.h (revision 21520)
@@ -1,66 +1,67 @@
/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef IXMPPCLIENT_H
#define IXMPPCLIENT_H
#include "scriptinterface/ScriptTypes.h"
class ScriptInterface;
namespace StunClient {
struct StunEndpoint;
}
class IXmppClient
{
public:
static IXmppClient* create(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, bool regOpt = false);
virtual ~IXmppClient() {}
virtual void connect() = 0;
virtual void disconnect() = 0;
virtual bool isConnected() = 0;
virtual void recv() = 0;
virtual void SendIqGetBoardList() = 0;
virtual void SendIqGetProfile(const std::string& player) = 0;
virtual void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqUnregisterGame() = 0;
virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0;
+ virtual void SendIqLobbyAuth(const std::string& to, const std::string& token) = 0;
virtual void SetNick(const std::string& nick) = 0;
virtual void GetNick(std::string& nick) = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;
virtual void GetPresence(const std::string& nickname, std::string& presence) = 0;
virtual void GetRole(const std::string& nickname, std::string& role) = 0;
virtual void GetSubject(std::string& subject) = 0;
virtual void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void ClearPresenceUpdates() = 0;
virtual void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual JS::Value GuiPollNewMessage(const ScriptInterface& scriptInterface) = 0;
virtual JS::Value GuiPollHistoricMessages(const ScriptInterface& scriptInterface) = 0;
virtual void SendMUCMessage(const std::string& message) = 0;
virtual void SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJID) = 0;
};
extern IXmppClient *g_XmppClient;
extern bool g_rankedGame;
#endif // XMPPCLIENT_H
Index: ps/trunk/source/lobby/StanzaExtensions.cpp
===================================================================
--- ps/trunk/source/lobby/StanzaExtensions.cpp (revision 21519)
+++ ps/trunk/source/lobby/StanzaExtensions.cpp (revision 21520)
@@ -1,240 +1,285 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "StanzaExtensions.h"
/******************************************************
* GameReport, fairly generic custom stanza extension used
* to report game statistics.
*/
GameReport::GameReport(const glooxwrapper::Tag* tag)
: StanzaExtension(EXTGAMEREPORT)
{
if (!tag || tag->name() != "report" || tag->xmlns() != XMLNS_GAMEREPORT)
return;
// TODO if we want to handle receiving this stanza extension.
};
/**
* Required by gloox, used to serialize the GameReport into XML for sending.
*/
glooxwrapper::Tag* GameReport::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("report");
t->setXmlns(XMLNS_GAMEREPORT);
for (const glooxwrapper::Tag* const& tag : m_GameReport)
t->addChild(tag->clone());
return t;
}
/**
* Required by gloox, used to find the GameReport element in a recived IQ.
*/
const glooxwrapper::string& GameReport::filterString() const
{
static const glooxwrapper::string filter = "/iq/report[@xmlns='" XMLNS_GAMEREPORT "']";
return filter;
}
glooxwrapper::StanzaExtension* GameReport::clone() const
{
GameReport* q = new GameReport();
return q;
}
/******************************************************
* BoardListQuery, a flexible custom IQ Stanza useful for anything with ratings, used to
* request and receive leaderboard and rating data from server.
* Example stanza:
* 1200
*/
BoardListQuery::BoardListQuery(const glooxwrapper::Tag* tag)
: StanzaExtension(EXTBOARDLISTQUERY)
{
if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_BOARDLIST)
return;
const glooxwrapper::Tag* c = tag->findTag_clone("query/command");
if (c)
m_Command = c->cdata();
glooxwrapper::Tag::free(c);
for (const glooxwrapper::Tag* const& t : tag->findTagList_clone("query/board"))
m_StanzaBoardList.emplace_back(t);
}
/**
* Required by gloox, used to find the BoardList element in a received IQ.
*/
const glooxwrapper::string& BoardListQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_BOARDLIST "']";
return filter;
}
/**
* Required by gloox, used to serialize the BoardList request into XML for sending.
*/
glooxwrapper::Tag* BoardListQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("query");
t->setXmlns(XMLNS_BOARDLIST);
// Check for ratinglist or boardlist command
if (!m_Command.empty())
t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
for (const glooxwrapper::Tag* const& tag : m_StanzaBoardList)
t->addChild(tag->clone());
return t;
}
glooxwrapper::StanzaExtension* BoardListQuery::clone() const
{
BoardListQuery* q = new BoardListQuery();
return q;
}
BoardListQuery::~BoardListQuery()
{
for (const glooxwrapper::Tag* const& t : m_StanzaBoardList)
glooxwrapper::Tag::free(t);
m_StanzaBoardList.clear();
}
/******************************************************
* GameListQuery, custom IQ Stanza, used to receive
* the listing of games from the server, and register/
* unregister/changestate games on the server.
*/
-GameListQuery::GameListQuery( const glooxwrapper::Tag* tag )
+GameListQuery::GameListQuery(const glooxwrapper::Tag* tag)
: StanzaExtension(EXTGAMELISTQUERY)
{
if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_GAMELIST)
return;
- const glooxwrapper::Tag* c = tag->findTag_clone( "query/game" );
+ const glooxwrapper::Tag* c = tag->findTag_clone("query/game");
if (c)
m_Command = c->cdata();
glooxwrapper::Tag::free(c);
for (const glooxwrapper::Tag* const& t : tag->findTagList_clone("query/game"))
m_GameList.emplace_back(t);
}
/**
* Required by gloox, used to find the GameList element in a received IQ.
*/
const glooxwrapper::string& GameListQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_GAMELIST "']";
return filter;
}
/**
* Required by gloox, used to serialize the game object into XML for sending.
*/
glooxwrapper::Tag* GameListQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("query");
t->setXmlns(XMLNS_GAMELIST);
// Check for register / unregister command
if (!m_Command.empty())
t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
for (const glooxwrapper::Tag* const& tag : m_GameList)
t->addChild(tag->clone());
return t;
}
glooxwrapper::StanzaExtension* GameListQuery::clone() const
{
GameListQuery* q = new GameListQuery();
return q;
}
GameListQuery::~GameListQuery()
{
for (const glooxwrapper::Tag* const & t : m_GameList)
glooxwrapper::Tag::free(t);
m_GameList.clear();
}
/******************************************************
* ProfileQuery, a custom IQ Stanza useful for fetching
* user profiles
* Example stanza:
* foobar
*/
ProfileQuery::ProfileQuery(const glooxwrapper::Tag* tag)
: StanzaExtension(EXTPROFILEQUERY)
{
if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_PROFILE)
return;
const glooxwrapper::Tag* c = tag->findTag_clone("query/command");
if (c)
m_Command = c->cdata();
glooxwrapper::Tag::free(c);
for (const glooxwrapper::Tag* const& t : tag->findTagList_clone("query/profile"))
m_StanzaProfile.emplace_back(t);
}
/**
* Required by gloox, used to find the Profile element in a received IQ.
*/
const glooxwrapper::string& ProfileQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_PROFILE "']";
return filter;
}
/**
* Required by gloox, used to serialize the Profile request into XML for sending.
*/
glooxwrapper::Tag* ProfileQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("query");
t->setXmlns(XMLNS_PROFILE);
if (!m_Command.empty())
t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
for (const glooxwrapper::Tag* const& tag : m_StanzaProfile)
t->addChild(tag->clone());
return t;
}
glooxwrapper::StanzaExtension* ProfileQuery::clone() const
{
ProfileQuery* q = new ProfileQuery();
return q;
}
ProfileQuery::~ProfileQuery()
{
for (const glooxwrapper::Tag* const& t : m_StanzaProfile)
glooxwrapper::Tag::free(t);
m_StanzaProfile.clear();
}
+
+/******************************************************
+ * LobbyAuth, a custom IQ Stanza, used to send and
+ * receive a security token for hosting authentication.
+ */
+LobbyAuth::LobbyAuth(const glooxwrapper::Tag* tag)
+ : StanzaExtension(EXTLOBBYAUTH)
+{
+ if (!tag || tag->name() != "auth" || tag->xmlns() != XMLNS_LOBBYAUTH)
+ return;
+
+ const glooxwrapper::Tag* c = tag->findTag_clone("auth/token");
+ if (c)
+ m_Token = c->cdata();
+
+ glooxwrapper::Tag::free(c);
+}
+
+/**
+ * Required by gloox, used to find the LobbyAuth element in a received IQ.
+ */
+const glooxwrapper::string& LobbyAuth::filterString() const
+{
+ static const glooxwrapper::string filter = "/iq/auth[@xmlns='" XMLNS_LOBBYAUTH "']";
+ return filter;
+}
+
+/**
+ * Required by gloox, used to serialize the auth object into XML for sending.
+ */
+glooxwrapper::Tag* LobbyAuth::tag() const
+{
+ glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("auth");
+ t->setXmlns(XMLNS_LOBBYAUTH);
+
+ // Check for the auth token
+ if (!m_Token.empty())
+ t->addChild(glooxwrapper::Tag::allocate("token", m_Token));
+ return t;
+}
+
+glooxwrapper::StanzaExtension* LobbyAuth::clone() const
+{
+ return new LobbyAuth();
+}
Index: ps/trunk/source/lobby/StanzaExtensions.h
===================================================================
--- ps/trunk/source/lobby/StanzaExtensions.h (revision 21519)
+++ ps/trunk/source/lobby/StanzaExtensions.h (revision 21520)
@@ -1,114 +1,135 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef STANZAEXTENSIONS_H
#define STANZAEXTENSIONS_H
#include "glooxwrapper/glooxwrapper.h"
/// Global Gamelist Extension
#define EXTGAMELISTQUERY 1403
#define XMLNS_GAMELIST "jabber:iq:gamelist"
/// Global Boardlist Extension
#define EXTBOARDLISTQUERY 1404
#define XMLNS_BOARDLIST "jabber:iq:boardlist"
/// Global Gamereport Extension
#define EXTGAMEREPORT 1405
#define XMLNS_GAMEREPORT "jabber:iq:gamereport"
/// Global Profile Extension
#define EXTPROFILEQUERY 1406
#define XMLNS_PROFILE "jabber:iq:profile"
+/// Global Lobby Authentication Extension
+#define EXTLOBBYAUTH 1407
+#define XMLNS_LOBBYAUTH "jabber:iq:lobbyauth"
+
class GameReport : public glooxwrapper::StanzaExtension
{
public:
GameReport(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new GameReport(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
std::vector m_GameReport;
};
class GameListQuery : public glooxwrapper::StanzaExtension
{
public:
GameListQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new GameListQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~GameListQuery();
glooxwrapper::string m_Command;
std::vector m_GameList;
};
class BoardListQuery : public glooxwrapper::StanzaExtension
{
public:
BoardListQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new BoardListQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~BoardListQuery();
glooxwrapper::string m_Command;
std::vector m_StanzaBoardList;
};
class ProfileQuery : public glooxwrapper::StanzaExtension
{
public:
ProfileQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new ProfileQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~ProfileQuery();
glooxwrapper::string m_Command;
std::vector m_StanzaProfile;
};
+
+class LobbyAuth : public glooxwrapper::StanzaExtension
+{
+public:
+ LobbyAuth(const glooxwrapper::Tag* tag = 0);
+
+ // Following four methods are all required by gloox
+ virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
+ {
+ return new LobbyAuth(tag);
+ }
+ virtual const glooxwrapper::string& filterString() const;
+ virtual glooxwrapper::Tag* tag() const;
+ virtual glooxwrapper::StanzaExtension* clone() const;
+
+ glooxwrapper::string m_Token;
+};
#endif // STANZAEXTENSIONS_H
Index: ps/trunk/source/lobby/XmppClient.cpp
===================================================================
--- ps/trunk/source/lobby/XmppClient.cpp (revision 21519)
+++ ps/trunk/source/lobby/XmppClient.cpp (revision 21520)
@@ -1,1153 +1,1188 @@
/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "XmppClient.h"
#include "StanzaExtensions.h"
#ifdef WIN32
# include
#endif
#include "i18n/L10n.h"
#include "lib/external_libraries/enet.h"
#include "lib/utf8.h"
#include "network/NetServer.h"
#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Pyrogenesis.h"
#include "scriptinterface/ScriptInterface.h"
//debug
#if 1
#define DbgXMPP(x)
#else
#define DbgXMPP(x) std::cout << x << std::endl;
static std::string tag_xml(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->xml().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
#endif
static std::string tag_name(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->name().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
IXmppClient* IXmppClient::create(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt)
{
return new XmppClient(sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt);
}
/**
* Construct the XMPP client.
*
* @param sUsername Username to login with of register.
* @param sPassword Password to login with or register.
* @param sRoom MUC room to join.
* @param sNick Nick to join with.
* @param historyRequestSize Number of stanzas of room history to request.
* @param regOpt If we are just registering or not.
*/
XmppClient::XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
- : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick), m_initialLoadComplete(false), m_isConnected(false), m_sessionManager()
+ : m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_room(sRoom), m_nick(sNick), m_initialLoadComplete(false), m_isConnected(false), m_sessionManager()
{
// Read lobby configuration from default.cfg
- std::string sServer;
std::string sXpartamupp;
- CFG_GET_VAL("lobby.server", sServer);
+ CFG_GET_VAL("lobby.server", m_server);
CFG_GET_VAL("lobby.xpartamupp", sXpartamupp);
- m_xpartamuppId = sXpartamupp + "@" + sServer + "/CC";
- glooxwrapper::JID clientJid(sUsername + "@" + sServer + "/0ad");
- glooxwrapper::JID roomJid(sRoom + "@conference." + sServer + "/" + sNick);
+ m_xpartamuppId = sXpartamupp + "@" + m_server + "/CC";
+ glooxwrapper::JID clientJid(sUsername + "@" + m_server + "/0ad");
+ glooxwrapper::JID roomJid(m_room + "@conference." + m_server + "/" + sNick);
// If we are connecting, use the full jid and a password
// If we are registering, only use the server name
if (!regOpt)
m_client = new glooxwrapper::Client(clientJid, sPassword);
else
- m_client = new glooxwrapper::Client(sServer);
+ m_client = new glooxwrapper::Client(m_server);
// Disable TLS as we haven't set a certificate on the server yet
m_client->setTls(gloox::TLSDisabled);
// Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
// if the server doesn't list any supported SASL mechanism or the response
// has been modified to exclude those.
const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
m_client->setSASLMechanisms(mechs);
m_client->registerConnectionListener(this);
m_client->setPresence(gloox::Presence::Available, -1);
m_client->disco()->setVersion("Pyrogenesis", engine_version);
m_client->disco()->setIdentity("client", "bot");
m_client->setCompression(false);
m_client->registerStanzaExtension(new GameListQuery());
m_client->registerIqHandler(this, EXTGAMELISTQUERY);
m_client->registerStanzaExtension(new BoardListQuery());
m_client->registerIqHandler(this, EXTBOARDLISTQUERY);
m_client->registerStanzaExtension(new ProfileQuery());
m_client->registerIqHandler(this, EXTPROFILEQUERY);
+ m_client->registerStanzaExtension(new LobbyAuth());
+ m_client->registerIqHandler(this, EXTLOBBYAUTH);
+
m_client->registerMessageHandler(this);
// Uncomment to see the raw stanzas
//m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
if (!regOpt)
{
// Create a Multi User Chat Room
m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
// Get room history.
m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
}
else
{
// Registration
m_registration = new glooxwrapper::Registration(m_client);
m_registration->registerRegistrationHandler(this);
}
m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
// Register plugins to allow gloox parse them in incoming sessions
m_sessionManager->registerPlugins();
}
/**
* Destroy the xmpp client
*/
XmppClient::~XmppClient()
{
DbgXMPP("XmppClient destroyed");
delete m_registration;
delete m_mucRoom;
// Workaround for memory leak in gloox 1.0/1.0.1
m_client->removePresenceExtension(gloox::ExtCaps);
delete m_client;
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
}
/// Network
void XmppClient::connect()
{
m_initialLoadComplete = false;
m_client->connect(false);
}
void XmppClient::disconnect()
{
m_client->disconnect();
}
bool XmppClient::isConnected()
{
return m_isConnected;
}
void XmppClient::recv()
{
m_client->recv(1);
}
/**
* Log (debug) Handler
*/
void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
{
std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
}
/*****************************************************
* Connection handlers *
*****************************************************/
/**
* Handle connection
*/
void XmppClient::onConnect()
{
if (m_mucRoom)
{
m_isConnected = true;
CreateGUIMessage("system", "connected");
m_mucRoom->join();
}
if (m_registration)
m_registration->fetchRegistrationFields();
}
/**
* Handle disconnection
*/
void XmppClient::onDisconnect(gloox::ConnectionError error)
{
// Make sure we properly leave the room so that
// everything works if we decide to come back later
if (m_mucRoom)
m_mucRoom->leave();
// Clear game, board and player lists.
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
m_BoardList.clear();
m_GameList.clear();
m_PlayerMap.clear();
m_Profile.clear();
m_HistoricGuiMessages.clear();
m_isConnected = false;
CreateGUIMessage("system", "disconnected", "reason", ConnectionErrorToString(error));
}
/**
* Handle TLS connection
*/
bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
{
UNUSED2(info);
DbgXMPP("onTLSConnect");
DbgXMPP(
"status: " << info.status <<
"\nissuer: " << info.issuer <<
"\npeer: " << info.server <<
"\nprotocol: " << info.protocol <<
"\nmac: " << info.mac <<
"\ncipher: " << info.cipher <<
"\ncompression: " << info.compression );
return true;
}
/**
* Handle MUC room errors
*/
void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err)
{
CreateGUIMessage("system", "error", "text", StanzaErrorToString(err));
}
/*****************************************************
* Requests to server *
*****************************************************/
/**
* Request the leaderboard data from the server.
*/
void XmppClient::SendIqGetBoardList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
BoardListQuery* b = new BoardListQuery();
b->m_Command = "getleaderboard";
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid, m_client->getID());
iq.addExtension(b);
DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the profile data from the server.
*/
void XmppClient::SendIqGetProfile(const std::string& player)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
ProfileQuery* b = new ProfileQuery();
b->m_Command = player;
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid, m_client->getID());
iq.addExtension(b);
DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send game report containing numerous game properties to the server.
*
* @param data A JS array of game statistics
*/
void XmppClient::SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Setup some base stanza attributes
GameReport* game = new GameReport();
glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties);
for (const std::string& p : properties)
{
std::wstring value;
scriptInterface.GetProperty(data, p.c_str(), value);
report->addAttribute(p, utf8_from_wstring(value));
}
// Add stanza to IQ
game->m_GameReport.emplace_back(report);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
iq.addExtension(game);
DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
m_client->send(iq);
};
/**
* Send a request to register a game to the server.
*
* @param data A JS array of game attributes
*/
void XmppClient::SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Setup some base stanza attributes
GameListQuery* g = new GameListQuery();
g->m_Command = "register";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
// Add a fake ip which will be overwritten by the ip stamp XMPP module on the server.
game->addAttribute("ip", "fake");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties);
for (const std::string& p : properties)
{
std::wstring value;
scriptInterface.GetProperty(data, p.c_str(), value);
game->addAttribute(p, utf8_from_wstring(value));
}
// Push the stanza onto the IQ
g->m_GameList.emplace_back(game);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
iq.addExtension(g);
DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to unregister a game to the server.
*/
void XmppClient::SendIqUnregisterGame()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "unregister";
g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game"));
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
iq.addExtension(g);
DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to change the state of a registered game on the server.
*
* A game can either be in the 'running' or 'waiting' state - the server
* decides which - but we need to update the current players that are
* in-game so the server can make the calculation.
*/
void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "changestate";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
game->addAttribute("nbp", nbp);
game->addAttribute("players", players);
g->m_GameList.emplace_back(game);
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid, m_client->getID());
iq.addExtension(g);
DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/*****************************************************
+ * iq to clients *
+ *****************************************************/
+
+/**
+ * Send lobby authentication token.
+ */
+void XmppClient::SendIqLobbyAuth(const std::string& to, const std::string& token)
+{
+ LobbyAuth* auth = new LobbyAuth();
+ auth->m_Token = token;
+
+ glooxwrapper::JID clientJid(to + "@" + m_server + "/0ad");
+ glooxwrapper::IQ iq(gloox::IQ::Set, clientJid, m_client->getID());
+ iq.addExtension(auth);
+ DbgXMPP("SendIqLobbyAuth [" << tag_xml(iq) << "]");
+ m_client->send(iq);
+}
+
+/*****************************************************
* Account registration *
*****************************************************/
void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
{
glooxwrapper::RegistrationFields vals;
vals.username = m_username;
vals.password = m_password;
m_registration->createAccount(fields, vals);
}
void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
{
if (result == gloox::RegistrationSuccess)
CreateGUIMessage("system", "registered");
else
CreateGUIMessage("system", "error", "text", RegistrationResultToString(result));
disconnect();
}
void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
{
DbgXMPP("the account already exists");
}
void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
{
DbgXMPP("dataForm received");
}
void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
{
DbgXMPP("OOB registration requested");
}
/*****************************************************
* Requests from GUI *
*****************************************************/
/**
* Handle requests from the GUI for the list of players.
*
* @return A JS array containing all known players and their presences
*/
void XmppClient::GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
// Convert the internal data structure to a Javascript object.
for (const std::pair >& p : m_PlayerMap)
{
JS::RootedValue player(cx);
scriptInterface.Eval("({})", &player);
scriptInterface.SetProperty(player, "name", wstring_from_utf8(p.first));
scriptInterface.SetProperty(player, "presence", wstring_from_utf8(p.second[0]));
scriptInterface.SetProperty(player, "rating", wstring_from_utf8(p.second[1]));
scriptInterface.SetProperty(player, "role", wstring_from_utf8(p.second[2]));
scriptInterface.CallFunctionVoid(ret, "push", player);
}
}
/**
* Handle requests from the GUI for the list of all active games.
*
* @return A JS array containing all known games
*/
void XmppClient::GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state",
"nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType",
"victoryCondition", "startTime", "mods" };
for(const glooxwrapper::Tag* const& t : m_GameList)
{
JS::RootedValue game(cx);
scriptInterface.Eval("({})", &game);
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(game, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", game);
}
}
/**
* Handle requests from the GUI for leaderboard data.
*
* @return A JS array containing all known leaderboard data
*/
void XmppClient::GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* attributes[] = { "name", "rank", "rating" };
for(const glooxwrapper::Tag* const& t : m_BoardList)
{
JS::RootedValue board(cx);
scriptInterface.Eval("({})", &board);
for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
scriptInterface.SetProperty(board, attributes[i], wstring_from_utf8(t->findAttribute(attributes[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", board);
}
}
/**
* Handle requests from the GUI for profile data.
*
* @return A JS array containing the specific user's profile data
*/
void XmppClient::GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
for (const glooxwrapper::Tag* const& t : m_Profile)
{
JS::RootedValue profile(cx);
scriptInterface.Eval("({})", &profile);
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(profile, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", profile);
}
}
/*****************************************************
* Message interfaces *
*****************************************************/
void XmppClient::CreateGUIMessage(
const std::string& type,
const std::string& level,
const std::string& property1_name,
const std::string& property1_value,
const std::string& property2_name,
const std::string& property2_value,
const std::time_t time)
{
GUIMessage message;
message.type = type;
message.level = level;
message.property1_name = property1_name;
message.property1_value = property1_value;
message.property2_name = property2_name;
message.property2_value = property2_value;
message.time = time;
m_GuiMessageQueue.push_back(std::move(message));
}
JS::Value XmppClient::GuiMessageToJSVal(const ScriptInterface& scriptInterface, const GUIMessage& message, const bool historic)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue ret(cx);
scriptInterface.Eval("({})", &ret);
scriptInterface.SetProperty(ret, "type", wstring_from_utf8(message.type));
if (!message.level.empty())
scriptInterface.SetProperty(ret, "level", wstring_from_utf8(message.level));
if (!message.property1_name.empty())
scriptInterface.SetProperty(ret, message.property1_name.c_str(), wstring_from_utf8(message.property1_value));
if (!message.property2_name.empty())
scriptInterface.SetProperty(ret, message.property2_name.c_str(), wstring_from_utf8(message.property2_value));
scriptInterface.SetProperty(ret, "time", (double)message.time);
scriptInterface.SetProperty(ret, "historic", historic);
return ret;
}
JS::Value XmppClient::GuiPollNewMessage(const ScriptInterface& scriptInterface)
{
if (m_GuiMessageQueue.empty())
return JS::UndefinedValue();
GUIMessage message = m_GuiMessageQueue.front();
m_GuiMessageQueue.pop_front();
// Since there can be hundreds of presence changes while playing a game, ignore these for performance
if (message.type == "chat" && message.level != "presence")
m_HistoricGuiMessages.push_back(message);
return GuiMessageToJSVal(scriptInterface, message, false);
}
JS::Value XmppClient::GuiPollHistoricMessages(const ScriptInterface& scriptInterface)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedObject ret(cx, JS_NewArrayObject(cx, 0));
uint32_t i = 0;
for (const GUIMessage& message : m_HistoricGuiMessages)
{
JS::RootedValue msg(cx, GuiMessageToJSVal(scriptInterface, message, true));
JS_SetElement(cx, ret, i++, msg);
}
return JS::ObjectValue(*ret);
}
/**
* Send a standard MUC textual message.
*/
void XmppClient::SendMUCMessage(const std::string& message)
{
m_mucRoom->send(message);
}
/**
* Clears all presence updates from the message queue.
* Used when rejoining the lobby, since we don't need to handle past presence changes.
*/
void XmppClient::ClearPresenceUpdates()
{
m_GuiMessageQueue.erase(
std::remove_if(m_GuiMessageQueue.begin(), m_GuiMessageQueue.end(),
[](XmppClient::GUIMessage& message)
{
return message.type == "chat" && message.level == "presence";
}
), m_GuiMessageQueue.end());
}
/**
* Handle a room message.
*/
void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Message& msg, bool priv)
{
DbgXMPP(msg.from().resource() << " said " << msg.body());
CreateGUIMessage(
"chat",
priv ? "private-message" : "room-message",
"from", msg.from().resource().to_string(),
"text", msg.body().to_string(),
ComputeTimestamp(msg));
}
/**
* Handle a private message.
*/
void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession*)
{
DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
<< ", message " << msg.body() << ", thread id " << msg.thread());
CreateGUIMessage(
"chat",
"private-message",
"from", msg.from().resource().to_string(),
"text", msg.body().to_string(),
ComputeTimestamp(msg));
}
/**
* Handle portions of messages containing custom stanza extensions.
*/
bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
{
DbgXMPP("handleIq [" << tag_xml(iq) << "]");
if (iq.subtype() == gloox::IQ::Result)
{
const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY);
const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY);
const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY);
if (gq)
{
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
m_GameList.clear();
for (const glooxwrapper::Tag* const& t : gq->m_GameList)
m_GameList.emplace_back(t->clone());
CreateGUIMessage("game", "gamelist");
}
if (bq)
{
if (bq->m_Command == "boardlist")
{
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
m_BoardList.clear();
for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
m_BoardList.emplace_back(t->clone());
CreateGUIMessage("game", "leaderboard");
}
else if (bq->m_Command == "ratinglist")
{
for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
{
std::string name = t->findAttribute("name").to_string();
if (m_PlayerMap.find(name) != m_PlayerMap.end())
m_PlayerMap[name][1] = t->findAttribute("rating").to_string();
}
CreateGUIMessage("game", "ratinglist");
}
}
if (pq)
{
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
m_Profile.clear();
for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile)
m_Profile.emplace_back(t->clone());
CreateGUIMessage("game", "profile");
}
}
+ else if (iq.subtype() == gloox::IQ::Set)
+ {
+ const LobbyAuth* lobbyAuth = iq.findExtension(EXTLOBBYAUTH);
+ if (lobbyAuth)
+ {
+ LOGMESSAGE("XmppClient: Received lobby auth: %s from %s", lobbyAuth->m_Token.to_string(), iq.from().username());
+
+ glooxwrapper::IQ response(gloox::IQ::Result, iq.from(), iq.id());
+ m_client->send(response);
+
+ if (g_NetServer)
+ g_NetServer->OnLobbyAuth(iq.from().username(), lobbyAuth->m_Token.to_string());
+ }
+ }
else if (iq.subtype() == gloox::IQ::Error)
CreateGUIMessage("system", "error", "text", StanzaErrorToString(iq.error_error()));
else
{
CreateGUIMessage("system", "error", "text", g_L10n.Translate("unknown subtype (see logs)"));
LOGMESSAGE("unknown subtype '%s'", tag_name(iq).c_str());
}
return true;
}
/*****************************************************
* Presence, nickname, and subject *
*****************************************************/
/**
* Update local data when a user changes presence.
*/
void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
{
std::string nick = participant.nick->resource().to_string();
gloox::Presence::PresenceType presenceType = presence.presence();
std::string presenceString, roleString;
GetPresenceString(presenceType, presenceString);
GetRoleString(participant.role, roleString);
if (presenceType == gloox::Presence::Unavailable)
{
if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
{
// we have a nick change
std::string newNick = participant.newNick.to_string();
m_PlayerMap[newNick].resize(3);
m_PlayerMap[newNick][0] = presenceString;
m_PlayerMap[newNick][2] = roleString;
DbgXMPP(nick << " is now known as " << participant.newNick.to_string());
CreateGUIMessage("chat", "nick", "oldnick", nick, "newnick", participant.newNick.to_string());
}
else if (participant.flags & gloox::UserKicked)
{
DbgXMPP(nick << " was kicked. Reason: " << participant.reason.to_string());
CreateGUIMessage("chat", "kicked", "nick", nick, "reason", participant.reason.to_string());
}
else if (participant.flags & gloox::UserBanned)
{
DbgXMPP(nick << " was banned. Reason: " << participant.reason.to_string());
CreateGUIMessage("chat", "banned", "nick", nick, "reason", participant.reason.to_string());
}
else
{
DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
CreateGUIMessage("chat", "leave", "nick", nick);
}
m_PlayerMap.erase(nick);
}
else
{
/* During the initialization process, we recieve join messages for everyone
* currently in the room. We don't want to display these, so we filter them
* out. We will always be the last to join during initialization.
*/
if (!m_initialLoadComplete)
{
if (m_mucRoom->nick().to_string() == nick)
m_initialLoadComplete = true;
}
else if (m_PlayerMap.find(nick) == m_PlayerMap.end())
CreateGUIMessage("chat", "join", "nick", nick);
else if (m_PlayerMap[nick][2] != roleString)
CreateGUIMessage("chat", "role", "nick", nick, "oldrole", m_PlayerMap[nick][2]);
else
CreateGUIMessage("chat", "presence", "nick", nick);
DbgXMPP(nick << " is in the room, presence : " << (int)presenceType);
m_PlayerMap[nick].resize(3);
m_PlayerMap[nick][0] = presenceString;
m_PlayerMap[nick][2] = roleString;
}
}
/**
* Update local cache when subject changes.
*/
void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& nick, const glooxwrapper::string& subject)
{
m_Subject = subject.c_str();
CreateGUIMessage("chat", "subject", "nick", nick.c_str(), "subject", m_Subject);
}
/**
* Get current subject.
*
* @param topic Variable to store subject in.
*/
void XmppClient::GetSubject(std::string& subject)
{
subject = m_Subject;
}
/**
* Request nick change, real change via mucRoomHandler.
*
* @param nick Desired nickname
*/
void XmppClient::SetNick(const std::string& nick)
{
m_mucRoom->setNick(nick);
}
/**
* Get current nickname.
*
* @param nick Variable to store the nickname in.
*/
void XmppClient::GetNick(std::string& nick)
{
nick = m_mucRoom->nick().to_string();
}
/**
* Kick a player from the current room.
*
* @param nick Nickname to be kicked
* @param reason Reason the player was kicked
*/
void XmppClient::kick(const std::string& nick, const std::string& reason)
{
m_mucRoom->kick(nick, reason);
}
/**
* Ban a player from the current room.
*
* @param nick Nickname to be banned
* @param reason Reason the player was banned
*/
void XmppClient::ban(const std::string& nick, const std::string& reason)
{
m_mucRoom->ban(nick, reason);
}
/**
* Change the xmpp presence of the client.
*
* @param presence A string containing the desired presence
*/
void XmppClient::SetPresence(const std::string& presence)
{
#define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
IF("available", Available);
else IF("chat", Chat);
else IF("away", Away);
else IF("playing", DND);
else IF("offline", Unavailable);
// The others are not to be set
#undef IF
else LOGERROR("Unknown presence '%s'", presence.c_str());
}
/**
* Get the current xmpp presence of the given nick.
*
* @param nick Nickname to look up presence for
* @param presence Variable to store the presence in
*/
void XmppClient::GetPresence(const std::string& nick, std::string& presence)
{
if (m_PlayerMap.find(nick) != m_PlayerMap.end())
presence = m_PlayerMap[nick][0];
else
presence = "offline";
}
/**
* Get the current xmpp role of the given nick.
*
* @param nick Nickname to look up presence for
* @param role Variable to store the role in
*/
void XmppClient::GetRole(const std::string& nick, std::string& role)
{
if (m_PlayerMap.find(nick) != m_PlayerMap.end())
role = m_PlayerMap[nick][2];
else
role = "";
}
/*****************************************************
* Utilities *
*****************************************************/
/**
* Parse and return the timestamp of a historic chat message and return the current time for new chat messages.
* Historic chat messages are implement as DelayedDelivers as specified in XEP-0203.
* Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
*
* @returns Seconds since the epoch.
*/
std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) const
{
// Only historic messages contain a timestamp!
if (!msg.when())
return std::time(nullptr);
// The locale is irrelevant, because the XMPP date format doesn't contain written month names
for (const std::string& format : std::vector{ "Y-M-d'T'H:m:sZ", "Y-M-d'T'H:m:s.SZ" })
{
UDate dateTime = g_L10n.ParseDateTime(msg.when()->stamp().to_string(), format, Locale::getUS());
if (dateTime)
return dateTime / 1000.0;
}
return std::time(nullptr);
}
/**
* Convert a gloox presence type to string.
*
* @param p Presence to be converted
* @param presence Variable to store the converted presence string in
*/
void XmppClient::GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const
{
switch(p)
{
#define CASE(x,y) case gloox::Presence::x: presence = y; break
CASE(Available, "available");
CASE(Chat, "chat");
CASE(Away, "away");
CASE(DND, "playing");
CASE(XA, "away");
CASE(Unavailable, "offline");
CASE(Probe, "probe");
CASE(Error, "error");
CASE(Invalid, "invalid");
default:
LOGERROR("Unknown presence type '%d'", (int)p);
break;
#undef CASE
}
}
/**
* Convert a gloox role type to string.
*
* @param p Role to be converted
* @param presence Variable to store the converted role string in
*/
void XmppClient::GetRoleString(const gloox::MUCRoomRole r, std::string& role) const
{
switch(r)
{
#define CASE(X, Y) case gloox::X: role = Y; break
CASE(RoleNone, "none");
CASE(RoleVisitor, "visitor");
CASE(RoleParticipant, "participant");
CASE(RoleModerator, "moderator");
CASE(RoleInvalid, "invalid");
default:
LOGERROR("Unknown role type '%d'", (int)r);
break;
#undef CASE
}
}
/**
* Convert a gloox stanza error type to string.
* Keep in sync with Gloox documentation
*
* @param err Error to be converted
* @return Converted error string
*/
std::string XmppClient::StanzaErrorToString(gloox::StanzaError err) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (err)
{
CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
DEBUG_CASE(StanzaErrorBadRequest, "Server received malformed XML");
CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in"));
DEBUG_CASE(StanzaErrorFeatureNotImplemented, "Server does not implement requested feature");
CASE(StanzaErrorForbidden, g_L10n.Translate("Forbidden"));
DEBUG_CASE(StanzaErrorGone, "Unable to find message receipiant");
CASE(StanzaErrorInternalServerError, g_L10n.Translate("Internal server error"));
DEBUG_CASE(StanzaErrorItemNotFound, "Message receipiant does not exist");
DEBUG_CASE(StanzaErrorJidMalformed, "JID (XMPP address) malformed");
DEBUG_CASE(StanzaErrorNotAcceptable, "Receipiant refused message. Possible policy issue");
CASE(StanzaErrorNotAllowed, g_L10n.Translate("Not allowed"));
CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized"));
DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request");
DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment");
CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable"));
DEBUG_CASE(StanzaErrorRedirect, "Request redirected");
CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required"));
DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out");
DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints");
CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable"));
DEBUG_CASE(StanzaErrorSubscribtionRequired, "Service requires subscription");
DEBUG_CASE(StanzaErrorUnexpectedRequest, "Attempt to send from invalid stanza address");
DEBUG_CASE(StanzaErrorUnknownSender, "Invalid 'from' address");
default:
return g_L10n.Translate("Unknown error");
}
#undef DEBUG_CASE
#undef CASE
}
/**
* Convert a gloox connection error enum to string
* Keep in sync with Gloox documentation
*
* @param err Error to be converted
* @return Converted error string
*/
std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (err)
{
CASE(ConnNoError, g_L10n.Translate("No error"));
CASE(ConnStreamError, g_L10n.Translate("Stream error"));
CASE(ConnStreamVersionError, g_L10n.Translate("The incoming stream version is unsupported"));
CASE(ConnStreamClosed, g_L10n.Translate("The stream has been closed by the server"));
DEBUG_CASE(ConnProxyAuthRequired, "The HTTP/SOCKS5 proxy requires authentication");
DEBUG_CASE(ConnProxyAuthFailed, "HTTP/SOCKS5 proxy authentication failed");
DEBUG_CASE(ConnProxyNoSupportedAuth, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism");
CASE(ConnIoError, g_L10n.Translate("An I/O error occurred"));
DEBUG_CASE(ConnParseError, "An XML parse error occurred");
CASE(ConnConnectionRefused, g_L10n.Translate("The connection was refused by the server"));
CASE(ConnDnsError, g_L10n.Translate("Resolving the server's hostname failed"));
CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory"));
DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption"));
DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed");
CASE(ConnAuthenticationFailed, g_L10n.Translate("Authentication failed. Incorrect password or account does not exist"));
CASE(ConnUserDisconnected, g_L10n.Translate("The user or system requested a disconnect"));
CASE(ConnNotConnected, g_L10n.Translate("There is no active connection"));
default:
return g_L10n.Translate("Unknown error");
}
#undef DEBUG_CASE
#undef CASE
}
/**
* Convert a gloox registration result enum to string
* Keep in sync with Gloox documentation
*
* @param err Enum to be converted
* @return Converted string
*/
std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (res)
{
CASE(RegistrationSuccess, g_L10n.Translate("Your account has been successfully registered"));
CASE(RegistrationNotAcceptable, g_L10n.Translate("Not all necessary information provided"));
CASE(RegistrationConflict, g_L10n.Translate("Username already exists"));
DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change");
DEBUG_CASE(RegistrationBadRequest, "Server received an incomplete request");
DEBUG_CASE(RegistrationForbidden, "Registration forbidden");
DEBUG_CASE(RegistrationRequired, "Account cannot be removed as it does not exist");
DEBUG_CASE(RegistrationUnexpectedRequest, "This client is unregistered with the server");
DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes");
default:
return "";
}
#undef DEBUG_CASE
#undef CASE
}
void XmppClient::SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJIDStr)
{
ENSURE(stunEndpoint);
char ipStr[256] = "(error)";
ENetAddress addr;
addr.host = ntohl(stunEndpoint->ip);
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
glooxwrapper::JID hostJID(hostJIDStr);
glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
session.sessionInitiate(ipStr, stunEndpoint->port);
}
void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session* UNUSED(session), const glooxwrapper::Jingle::Session::Jingle* jingle)
{
if (action == gloox::Jingle::SessionInitiate)
handleSessionInitiation(jingle);
}
void XmppClient::handleSessionInitiation(const glooxwrapper::Jingle::Session::Jingle* jingle)
{
glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle->getCandidate();
if (candidate.ip.empty())
{
LOGERROR("Failed to retrieve Jingle candidate");
return;
}
g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
}
Index: ps/trunk/source/lobby/XmppClient.h
===================================================================
--- ps/trunk/source/lobby/XmppClient.h (revision 21519)
+++ ps/trunk/source/lobby/XmppClient.h (revision 21520)
@@ -1,180 +1,183 @@
/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef XXXMPPCLIENT_H
#define XXXMPPCLIENT_H
#include "IXmppClient.h"
#include
#include "glooxwrapper/glooxwrapper.h"
#include "scriptinterface/ScriptVal.h"
class ScriptInterface;
namespace glooxwrapper
{
class Client;
struct CertInfo;
}
class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler
{
NONCOPYABLE(XmppClient);
private:
// Components
glooxwrapper::Client* m_client;
glooxwrapper::MUCRoom* m_mucRoom;
glooxwrapper::Registration* m_registration;
glooxwrapper::SessionManager* m_sessionManager;
// Account infos
std::string m_username;
std::string m_password;
+ std::string m_server;
+ std::string m_room;
std::string m_nick;
std::string m_xpartamuppId;
// State
bool m_initialLoadComplete;
bool m_isConnected;
public:
// Basic
XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, const bool regOpt = false);
virtual ~XmppClient();
// Network
void connect();
void disconnect();
bool isConnected();
void recv();
void SendIqGetBoardList();
void SendIqGetProfile(const std::string& player);
void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqUnregisterGame();
void SendIqChangeStateGame(const std::string& nbp, const std::string& players);
+ void SendIqLobbyAuth(const std::string& to, const std::string& token);
void SetNick(const std::string& nick);
void GetNick(std::string& nick);
void kick(const std::string& nick, const std::string& reason);
void ban(const std::string& nick, const std::string& reason);
void SetPresence(const std::string& presence);
void GetPresence(const std::string& nickname, std::string& presence);
void GetRole(const std::string& nickname, std::string& role);
void GetSubject(std::string& subject);
void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJID);
protected:
/* Xmpp handlers */
/* MUC handlers */
virtual void handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant, const glooxwrapper::Presence&);
virtual void handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError);
virtual void handleMUCMessage(glooxwrapper::MUCRoom* room, const glooxwrapper::Message& msg, bool priv);
virtual void handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& nick, const glooxwrapper::string& subject);
/* MUC handlers not supported by glooxwrapper */
// virtual bool handleMUCRoomCreation(glooxwrapper::MUCRoom*) {return false;}
// virtual void handleMUCInviteDecline(glooxwrapper::MUCRoom*, const glooxwrapper::JID&, const std::string&) {}
// virtual void handleMUCInfo(glooxwrapper::MUCRoom*, int, const std::string&, const glooxwrapper::DataForm*) {}
// virtual void handleMUCItems(glooxwrapper::MUCRoom*, const std::list >&) {}
/* Log handler */
virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message);
/* ConnectionListener handlers*/
virtual void onConnect();
virtual void onDisconnect(gloox::ConnectionError e);
virtual bool onTLSConnect(const glooxwrapper::CertInfo& info);
/* Iq Handlers */
virtual bool handleIq(const glooxwrapper::IQ& iq);
virtual void handleIqID(const glooxwrapper::IQ&, int) {}
/* Registration Handlers */
virtual void handleRegistrationFields(const glooxwrapper::JID& /*from*/, int fields, glooxwrapper::string instructions );
virtual void handleRegistrationResult(const glooxwrapper::JID& /*from*/, gloox::RegistrationResult result);
virtual void handleAlreadyRegistered(const glooxwrapper::JID& /*from*/);
virtual void handleDataForm(const glooxwrapper::JID& /*from*/, const glooxwrapper::DataForm& /*form*/);
virtual void handleOOB(const glooxwrapper::JID& /*from*/, const glooxwrapper::OOB& oob);
/* Message Handler */
virtual void handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession* session);
/* Session Handler */
virtual void handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session* UNUSED(session), const glooxwrapper::Jingle::Session::Jingle* jingle);
virtual void handleSessionInitiation(const glooxwrapper::Jingle::Session::Jingle* jingle);
// Helpers
void GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const;
void GetRoleString(const gloox::MUCRoomRole r, std::string& role) const;
std::string StanzaErrorToString(gloox::StanzaError err) const;
std::string ConnectionErrorToString(gloox::ConnectionError err) const;
std::string RegistrationResultToString(gloox::RegistrationResult res) const;
std::time_t ComputeTimestamp(const glooxwrapper::Message& msg) const;
public:
/* Messages */
struct GUIMessage
{
std::string type;
std::string level;
std::string property1_name;
std::string property1_value;
std::string property2_name;
std::string property2_value;
std::time_t time;
};
JS::Value GuiMessageToJSVal(const ScriptInterface& scriptInterface, const GUIMessage& message, const bool historic);
JS::Value GuiPollNewMessage(const ScriptInterface& scriptInterface);
JS::Value GuiPollHistoricMessages(const ScriptInterface& scriptInterface);
void SendMUCMessage(const std::string& message);
void ClearPresenceUpdates();
protected:
void CreateGUIMessage(
const std::string& type,
const std::string& level = "",
const std::string& property1_name = "",
const std::string& property1_value = "",
const std::string& property2_name = "",
const std::string& property2_value = "",
const std::time_t time = std::time(nullptr));
private:
/// Map of players
std::map > m_PlayerMap;
/// List of games
std::vector m_GameList;
/// List of rankings
std::vector m_BoardList;
/// Profile data
std::vector m_Profile;
/// Queue of messages for the GUI
std::deque m_GuiMessageQueue;
/// Cache of all GUI messages received since the login
std::vector m_HistoricGuiMessages;
/// Current room subject/topic.
std::string m_Subject;
};
#endif // XMPPCLIENT_H
Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp
===================================================================
--- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp (revision 21519)
+++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.cpp (revision 21520)
@@ -1,909 +1,918 @@
/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "glooxwrapper.h"
#include
#include
#include
#include
#if OS_WIN
const std::string gloox::EmptyString = "";
#endif
void* glooxwrapper::glooxwrapper_alloc(size_t size)
{
void* p = malloc(size);
if (p == NULL)
throw std::bad_alloc();
return p;
}
void glooxwrapper::glooxwrapper_free(void* p)
{
free(p);
}
namespace glooxwrapper
{
class ConnectionListenerWrapper : public gloox::ConnectionListener
{
glooxwrapper::ConnectionListener* m_Wrapped;
public:
ConnectionListenerWrapper(glooxwrapper::ConnectionListener* wrapped) : m_Wrapped(wrapped) {}
virtual void onConnect()
{
m_Wrapped->onConnect();
}
virtual void onDisconnect(gloox::ConnectionError e)
{
m_Wrapped->onDisconnect(e);
}
virtual bool onTLSConnect(const gloox::CertInfo& info)
{
glooxwrapper::CertInfo infoWrapped;
#define COPY(n) infoWrapped.n = info.n
COPY(status);
COPY(chain);
COPY(issuer);
COPY(server);
COPY(date_from);
COPY(date_to);
COPY(protocol);
COPY(cipher);
COPY(mac);
COPY(compression);
#undef COPY
return m_Wrapped->onTLSConnect(infoWrapped);
}
};
class IqHandlerWrapper : public gloox::IqHandler
{
glooxwrapper::IqHandler* m_Wrapped;
public:
IqHandlerWrapper(glooxwrapper::IqHandler* wrapped) : m_Wrapped(wrapped) {}
virtual bool handleIq(const gloox::IQ& iq)
{
glooxwrapper::IQ iqWrapper(iq);
return m_Wrapped->handleIq(iqWrapper);
}
virtual void handleIqID(const gloox::IQ& iq, int context)
{
glooxwrapper::IQ iqWrapper(iq);
m_Wrapped->handleIqID(iqWrapper, context);
}
};
class MessageHandlerWrapper : public gloox::MessageHandler
{
glooxwrapper::MessageHandler* m_Wrapped;
public:
MessageHandlerWrapper(glooxwrapper::MessageHandler* wrapped) : m_Wrapped(wrapped) {}
virtual void handleMessage(const gloox::Message& msg, gloox::MessageSession* UNUSED(session))
{
/* MessageSession not supported */
glooxwrapper::Message msgWrapper(const_cast(&msg), false);
m_Wrapped->handleMessage(msgWrapper, NULL);
}
};
class MUCRoomHandlerWrapper : public gloox::MUCRoomHandler
{
glooxwrapper::MUCRoomHandler* m_Wrapped;
public:
MUCRoomHandlerWrapper(glooxwrapper::MUCRoomHandler* wrapped) : m_Wrapped(wrapped) {}
virtual ~MUCRoomHandlerWrapper() {}
virtual void handleMUCParticipantPresence(gloox::MUCRoom* UNUSED(room), const gloox::MUCRoomParticipant participant, const gloox::Presence& presence)
{
glooxwrapper::MUCRoomParticipant part;
glooxwrapper::JID nick(*participant.nick);
glooxwrapper::JID jid(*participant.jid);
glooxwrapper::JID actor(*participant.actor);
glooxwrapper::JID alternate(*participant.alternate);
part.nick = participant.nick ? &nick : NULL;
part.affiliation = participant.affiliation;
part.role = participant.role;
part.jid = participant.jid ? &jid : NULL;
part.flags = participant.flags;
part.reason = participant.reason;
part.actor = participant.actor ? &actor : NULL;
part.newNick = participant.newNick;
part.status = participant.status;
part.alternate = participant.alternate ? &alternate : NULL;
/* MUCRoom not supported */
m_Wrapped->handleMUCParticipantPresence(NULL, part, glooxwrapper::Presence(presence.presence()));
/* gloox 1.0 leaks some JIDs (fixed in 1.0.1), so clean them up */
#if GLOOXVERSION == 0x10000
delete participant.jid;
delete participant.actor;
delete participant.alternate;
#endif
}
virtual void handleMUCMessage(gloox::MUCRoom* UNUSED(room), const gloox::Message& msg, bool priv)
{
glooxwrapper::Message msgWrapper(const_cast(&msg), false);
/* MUCRoom not supported */
m_Wrapped->handleMUCMessage(NULL, msgWrapper, priv);
}
virtual bool handleMUCRoomCreation(gloox::MUCRoom* UNUSED(room))
{
/* Not supported */
return false;
}
virtual void handleMUCSubject(gloox::MUCRoom* UNUSED(room), const std::string& nick, const std::string& subject)
{
/* MUCRoom not supported */
m_Wrapped->handleMUCSubject(NULL, nick, subject);
}
virtual void handleMUCInviteDecline(gloox::MUCRoom* UNUSED(room), const gloox::JID& UNUSED(invitee), const std::string& UNUSED(reason))
{
/* Not supported */
}
virtual void handleMUCError(gloox::MUCRoom* UNUSED(room), gloox::StanzaError error)
{
/* MUCRoom not supported */
m_Wrapped->handleMUCError(NULL, error);
}
virtual void handleMUCInfo(gloox::MUCRoom* UNUSED(room), int UNUSED(features), const std::string& UNUSED(name), const gloox::DataForm* UNUSED(infoForm))
{
/* Not supported */
}
virtual void handleMUCItems(gloox::MUCRoom* UNUSED(room), const gloox::Disco::ItemList& UNUSED(items))
{
/* Not supported */
}
};
class RegistrationHandlerWrapper : public gloox::RegistrationHandler
{
glooxwrapper::RegistrationHandler* m_Wrapped;
public:
RegistrationHandlerWrapper(glooxwrapper::RegistrationHandler* wrapped) : m_Wrapped(wrapped) {}
virtual void handleRegistrationFields(const gloox::JID& from, int fields, std::string instructions)
{
glooxwrapper::JID fromWrapped(from);
m_Wrapped->handleRegistrationFields(fromWrapped, fields, instructions);
}
virtual void handleAlreadyRegistered(const gloox::JID& from)
{
glooxwrapper::JID fromWrapped(from);
m_Wrapped->handleAlreadyRegistered(fromWrapped);
}
virtual void handleRegistrationResult(const gloox::JID& from, gloox::RegistrationResult regResult)
{
glooxwrapper::JID fromWrapped(from);
m_Wrapped->handleRegistrationResult(fromWrapped, regResult);
}
virtual void handleDataForm(const gloox::JID& UNUSED(from), const gloox::DataForm& UNUSED(form))
{
/* DataForm not supported */
}
virtual void handleOOB(const gloox::JID& UNUSED(from), const gloox::OOB& UNUSED(oob))
{
/* OOB not supported */
}
};
class StanzaExtensionWrapper : public gloox::StanzaExtension
{
public:
const glooxwrapper::StanzaExtension* m_Wrapped;
bool m_Owned;
std::string m_FilterString;
StanzaExtensionWrapper(const glooxwrapper::StanzaExtension* wrapped, bool owned) :
gloox::StanzaExtension(wrapped->extensionType()), m_Wrapped(wrapped), m_Owned(owned)
{
m_FilterString = m_Wrapped->filterString().to_string();
}
~StanzaExtensionWrapper()
{
if (m_Owned)
delete m_Wrapped;
}
virtual const std::string& filterString() const
{
return m_FilterString;
}
virtual gloox::StanzaExtension* newInstance(const gloox::Tag* tag) const
{
glooxwrapper::Tag* tagWrapper = glooxwrapper::Tag::allocate(const_cast(tag), false);
gloox::StanzaExtension* ret = new StanzaExtensionWrapper(m_Wrapped->newInstance(tagWrapper), true);
glooxwrapper::Tag::free(tagWrapper);
return ret;
}
virtual gloox::Tag* tag() const
{
glooxwrapper::Tag* wrapper = m_Wrapped->tag();
gloox::Tag* ret = wrapper->stealWrapped();
glooxwrapper::Tag::free(wrapper);
return ret;
}
virtual gloox::StanzaExtension* clone() const
{
return new StanzaExtensionWrapper(m_Wrapped->clone(), true);
}
};
class SessionHandlerWrapper : public gloox::Jingle::SessionHandler
{
public:
glooxwrapper::Jingle::SessionHandler* m_Wrapped;
bool m_Owned;
SessionHandlerWrapper(glooxwrapper::Jingle::SessionHandler* wrapped, bool owned)
: m_Wrapped(wrapped), m_Owned(owned) {}
virtual void handleSessionAction(gloox::Jingle::Action action, gloox::Jingle::Session* session, const gloox::Jingle::Session::Jingle* jingle)
{
m_Wrapped->handleSessionAction(action, new glooxwrapper::Jingle::Session(session, false), new glooxwrapper::Jingle::Session::Jingle(jingle, false));
}
virtual void handleSessionActionError(gloox::Jingle::Action UNUSED(action), gloox::Jingle::Session* UNUSED(session), const gloox::Error* UNUSED(error))
{
}
virtual void handleIncomingSession(gloox::Jingle::Session* UNUSED(session))
{
}
};
class ClientImpl
{
public:
// List of registered callback wrappers, to get deleted when Client is deleted
std::list > m_ConnectionListeners;
std::list > m_MessageHandlers;
std::list > m_IqHandlers;
};
static const std::string XMLNS = "xmlns";
static const std::string XMLNS_JINGLE_0AD_GAME = "urn:xmpp:jingle:apps:0ad-game:1";
} // namespace glooxwrapper
glooxwrapper::Client::Client(const string& server)
{
m_Wrapped = new gloox::Client(server.to_string());
m_DiscoWrapper = new glooxwrapper::Disco(m_Wrapped->disco());
m_Impl = new ClientImpl;
}
glooxwrapper::Client::Client(const JID& jid, const string& password, int port)
{
m_Wrapped = new gloox::Client(jid.getWrapped(), password.to_string(), port);
m_DiscoWrapper = new glooxwrapper::Disco(m_Wrapped->disco());
m_Impl = new ClientImpl;
}
glooxwrapper::Client::~Client()
{
delete m_Wrapped;
delete m_DiscoWrapper;
delete m_Impl;
}
bool glooxwrapper::Client::connect(bool block)
{
return m_Wrapped->connect(block);
}
gloox::ConnectionError glooxwrapper::Client::recv(int timeout)
{
return m_Wrapped->recv(timeout);
}
const glooxwrapper::string glooxwrapper::Client::getID() const
{
return m_Wrapped->getID();
}
void glooxwrapper::Client::send(const IQ& iq)
{
m_Wrapped->send(iq.getWrapped());
}
void glooxwrapper::Client::setTls(gloox::TLSPolicy tls)
{
m_Wrapped->setTls(tls);
}
void glooxwrapper::Client::setCompression(bool compression)
{
m_Wrapped->setCompression(compression);
}
void glooxwrapper::Client::setSASLMechanisms(int mechanisms)
{
m_Wrapped->setSASLMechanisms(mechanisms);
}
void glooxwrapper::Client::disconnect()
{
m_Wrapped->disconnect();
}
void glooxwrapper::Client::registerStanzaExtension(glooxwrapper::StanzaExtension* ext)
{
gloox::StanzaExtension* stanza = new StanzaExtensionWrapper(ext, true);
m_Wrapped->registerStanzaExtension(stanza);
}
void glooxwrapper::Client::registerConnectionListener(glooxwrapper::ConnectionListener* hnd)
{
gloox::ConnectionListener* listener = new ConnectionListenerWrapper(hnd);
m_Wrapped->registerConnectionListener(listener);
m_Impl->m_ConnectionListeners.push_back(shared_ptr(listener));
}
void glooxwrapper::Client::registerMessageHandler(glooxwrapper::MessageHandler* hnd)
{
gloox::MessageHandler* handler = new MessageHandlerWrapper(hnd);
m_Wrapped->registerMessageHandler(handler);
m_Impl->m_MessageHandlers.push_back(shared_ptr(handler));
}
void glooxwrapper::Client::registerIqHandler(glooxwrapper::IqHandler* ih, int exttype)
{
gloox::IqHandler* handler = new IqHandlerWrapper(ih);
m_Wrapped->registerIqHandler(handler, exttype);
m_Impl->m_IqHandlers.push_back(shared_ptr(handler));
}
bool glooxwrapper::Client::removePresenceExtension(int type)
{
return m_Wrapped->removePresenceExtension(type);
}
void glooxwrapper::Client::setPresence(gloox::Presence::PresenceType pres, int priority, const string& status)
{
m_Wrapped->setPresence(pres, priority, status.to_string());
}
glooxwrapper::DelayedDelivery::DelayedDelivery(const gloox::DelayedDelivery* wrapped)
{
m_Wrapped = wrapped;
}
const glooxwrapper::string glooxwrapper::DelayedDelivery::stamp() const
{
return m_Wrapped->stamp();
}
glooxwrapper::Disco::Disco(gloox::Disco* wrapped)
{
m_Wrapped = wrapped;
}
void glooxwrapper::Disco::setVersion(const string& name, const string& version, const string& os)
{
m_Wrapped->setVersion(name.to_string(), version.to_string(), os.to_string());
}
void glooxwrapper::Disco::setIdentity(const string& category, const string& type, const string& name)
{
m_Wrapped->setIdentity(category.to_string(), type.to_string(), name.to_string());
}
glooxwrapper::IQ::IQ(gloox::IQ::IqType type, const JID& to, const string& id)
{
m_Wrapped = new gloox::IQ(type, to.getWrapped(), id.to_string());
m_Owned = true;
}
glooxwrapper::IQ::~IQ()
{
if (m_Owned)
delete m_Wrapped;
}
void glooxwrapper::IQ::addExtension(const glooxwrapper::StanzaExtension* se)
{
m_Wrapped->addExtension(new StanzaExtensionWrapper(se, true));
}
const glooxwrapper::StanzaExtension* glooxwrapper::IQ::findExtension(int type) const
{
const gloox::StanzaExtension* ext = m_Wrapped->findExtension(type);
if (!ext)
return NULL;
return static_cast(ext)->m_Wrapped;
}
gloox::StanzaError glooxwrapper::IQ::error_error() const
{
const gloox::Error* error = m_Wrapped->error();
if (!error)
return gloox::StanzaErrorInternalServerError;
return error->error();
}
glooxwrapper::Tag* glooxwrapper::IQ::tag() const
{
return Tag::allocate(m_Wrapped->tag(), true);
}
gloox::IQ::IqType glooxwrapper::IQ::subtype() const
{
return m_Wrapped->subtype();
}
+const glooxwrapper::string glooxwrapper::IQ::id() const
+{
+ return m_Wrapped->id();
+}
+
+const gloox::JID& glooxwrapper::IQ::from() const
+{
+ return m_Wrapped->from();
+}
glooxwrapper::JID::JID()
{
m_Wrapped = new gloox::JID();
m_Owned = true;
}
glooxwrapper::JID::JID(const glooxwrapper::string& jid)
{
m_Wrapped = new gloox::JID(jid.to_string());
m_Owned = true;
}
void glooxwrapper::JID::init(const char* data, size_t len)
{
m_Wrapped = new gloox::JID(std::string(data, data+len));
m_Owned = true;
}
glooxwrapper::JID::~JID()
{
if (m_Owned)
delete m_Wrapped;
}
glooxwrapper::string glooxwrapper::JID::username() const
{
return m_Wrapped->username();
}
glooxwrapper::string glooxwrapper::JID::resource() const
{
return m_Wrapped->resource();
};
glooxwrapper::Message::Message(gloox::Message* wrapped, bool owned)
: m_Wrapped(wrapped), m_Owned(owned)
{
m_From = new glooxwrapper::JID(m_Wrapped->from());
}
glooxwrapper::Message::~Message()
{
if (m_Owned)
delete m_Wrapped;
delete m_From;
}
gloox::Message::MessageType glooxwrapper::Message::subtype() const
{
return m_Wrapped->subtype();
}
const glooxwrapper::JID& glooxwrapper::Message::from() const
{
return *m_From;
}
glooxwrapper::string glooxwrapper::Message::body() const
{
return m_Wrapped->body();
}
glooxwrapper::string glooxwrapper::Message::subject(const string& lang) const
{
return m_Wrapped->subject(lang.to_string());
}
glooxwrapper::string glooxwrapper::Message::thread() const
{
return m_Wrapped->thread();
}
const glooxwrapper::DelayedDelivery* glooxwrapper::Message::when() const
{
const gloox::DelayedDelivery* wrapped = m_Wrapped->when();
if (wrapped == 0)
return 0;
return new glooxwrapper::DelayedDelivery(wrapped);
}
glooxwrapper::MUCRoom::MUCRoom(Client* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* UNUSED(mrch))
{
m_HandlerWrapper = new MUCRoomHandlerWrapper(mrh);
m_Wrapped = new gloox::MUCRoom(parent ? parent->getWrapped() : NULL, nick.getWrapped(), m_HandlerWrapper);
}
glooxwrapper::MUCRoom::~MUCRoom()
{
delete m_Wrapped;
delete m_HandlerWrapper;
}
const glooxwrapper::string glooxwrapper::MUCRoom::nick() const
{
return m_Wrapped->nick();
}
void glooxwrapper::MUCRoom::join(gloox::Presence::PresenceType type, const string& status, int priority)
{
m_Wrapped->join(type, status.to_string(), priority);
}
void glooxwrapper::MUCRoom::leave(const string& msg)
{
m_Wrapped->leave(msg.to_string());
}
void glooxwrapper::MUCRoom::send(const string& message)
{
m_Wrapped->send(message.to_string());
}
void glooxwrapper::MUCRoom::setNick(const string& nick)
{
m_Wrapped->setNick(nick.to_string());
}
void glooxwrapper::MUCRoom::setPresence(gloox::Presence::PresenceType presence, const string& msg)
{
m_Wrapped->setPresence(presence, msg.to_string());
}
void glooxwrapper::MUCRoom::setRequestHistory(int value, gloox::MUCRoom::HistoryRequestType type)
{
m_Wrapped->setRequestHistory(value, type);
}
void glooxwrapper::MUCRoom::kick(const string& nick, const string& reason)
{
m_Wrapped->kick(nick.to_string(), reason.to_string());
}
void glooxwrapper::MUCRoom::ban(const string& nick, const string& reason)
{
m_Wrapped->ban(nick.to_string(), reason.to_string());
}
glooxwrapper::Registration::Registration(Client* parent)
{
m_Wrapped = new gloox::Registration(parent->getWrapped());
}
glooxwrapper::Registration::~Registration()
{
delete m_Wrapped;
}
void glooxwrapper::Registration::fetchRegistrationFields()
{
m_Wrapped->fetchRegistrationFields();
}
bool glooxwrapper::Registration::createAccount(int fields, const glooxwrapper::RegistrationFields& values)
{
gloox::RegistrationFields valuesUnwrapped;
#define COPY(n) valuesUnwrapped.n = values.n.to_string()
COPY(username);
COPY(nick);
COPY(password);
COPY(name);
COPY(first);
COPY(last);
COPY(email);
COPY(address);
COPY(city);
COPY(state);
COPY(zip);
COPY(phone);
COPY(url);
COPY(date);
COPY(misc);
COPY(text);
#undef COPY
return m_Wrapped->createAccount(fields, valuesUnwrapped);
}
void glooxwrapper::Registration::registerRegistrationHandler(RegistrationHandler* rh)
{
gloox::RegistrationHandler* handler = new RegistrationHandlerWrapper(rh);
m_Wrapped->registerRegistrationHandler(handler);
m_RegistrationHandlers.push_back(shared_ptr(handler));
}
glooxwrapper::Tag::Tag(const string& name)
{
m_Wrapped = new gloox::Tag(name.to_string());
m_Owned = true;
}
glooxwrapper::Tag::Tag(const string& name, const string& cdata)
{
m_Wrapped = new gloox::Tag(name.to_string(), cdata.to_string());
m_Owned = true;
}
glooxwrapper::Tag::~Tag()
{
if (m_Owned)
delete m_Wrapped;
}
glooxwrapper::Tag* glooxwrapper::Tag::allocate(const string& name)
{
return new glooxwrapper::Tag(name);
}
glooxwrapper::Tag* glooxwrapper::Tag::allocate(const string& name, const string& cdata)
{
return new glooxwrapper::Tag(name, cdata);
}
glooxwrapper::Tag* glooxwrapper::Tag::allocate(gloox::Tag* wrapped, bool owned)
{
return new glooxwrapper::Tag(wrapped, owned);
}
void glooxwrapper::Tag::free(const glooxwrapper::Tag* tag)
{
delete tag;
}
bool glooxwrapper::Tag::addAttribute(const string& name, const string& value)
{
return m_Wrapped->addAttribute(name.to_string(), value.to_string());
}
glooxwrapper::string glooxwrapper::Tag::findAttribute(const string& name) const
{
return m_Wrapped->findAttribute(name.to_string());
}
glooxwrapper::Tag* glooxwrapper::Tag::clone() const
{
return new glooxwrapper::Tag(m_Wrapped->clone(), true);
}
glooxwrapper::string glooxwrapper::Tag::xmlns() const
{
return m_Wrapped->xmlns();
}
bool glooxwrapper::Tag::setXmlns(const string& xmlns)
{
return m_Wrapped->setXmlns(xmlns.to_string());
}
glooxwrapper::string glooxwrapper::Tag::xml() const
{
return m_Wrapped->xml();
}
void glooxwrapper::Tag::addChild(Tag* child)
{
m_Wrapped->addChild(child->stealWrapped());
Tag::free(child);
}
glooxwrapper::string glooxwrapper::Tag::name() const
{
return m_Wrapped->name();
}
glooxwrapper::string glooxwrapper::Tag::cdata() const
{
return m_Wrapped->cdata();
}
const glooxwrapper::Tag* glooxwrapper::Tag::findTag_clone(const string& expression) const
{
const gloox::Tag* tag = m_Wrapped->findTag(expression.to_string());
if (!tag)
return NULL;
return new glooxwrapper::Tag(const_cast(tag), false);
}
glooxwrapper::ConstTagList glooxwrapper::Tag::findTagList_clone(const string& expression) const
{
glooxwrapper::ConstTagList tagListWrapper;
for (const gloox::Tag* const& t : m_Wrapped->findTagList(expression.to_string()))
tagListWrapper.push_back(new glooxwrapper::Tag(const_cast(t), false));
return tagListWrapper;
}
glooxwrapper::Jingle::Plugin::~Plugin()
{
if (m_Owned)
delete m_Wrapped;
}
const glooxwrapper::Jingle::Plugin glooxwrapper::Jingle::Plugin::findPlugin(int type) const
{
return glooxwrapper::Jingle::Plugin(m_Wrapped->findPlugin(type), false);
}
glooxwrapper::Jingle::Content::Content(const string& name, const PluginList& plugins)
: glooxwrapper::Jingle::Plugin(NULL, false)
{
gloox::Jingle::PluginList glooxPluginList;
for (const glooxwrapper::Jingle::Plugin* const& plugin: plugins)
glooxPluginList.push_back(plugin->getWrapped());
m_Wrapped = new gloox::Jingle::Content(name.to_string(), glooxPluginList);
m_Owned = true;
}
glooxwrapper::Jingle::Content::Content()
: glooxwrapper::Jingle::Plugin(NULL, false)
{
m_Wrapped = new gloox::Jingle::Content();
m_Owned = true;
}
const glooxwrapper::Jingle::PluginList glooxwrapper::Jingle::Session::Jingle::plugins() const
{
glooxwrapper::Jingle::PluginList pluginListWrapper;
for (const gloox::Jingle::Plugin* const& plugin : m_Wrapped->plugins())
pluginListWrapper.push_back(new glooxwrapper::Jingle::Plugin(const_cast(plugin), false));
return pluginListWrapper;
}
glooxwrapper::Jingle::ICEUDP::Candidate glooxwrapper::Jingle::Session::Jingle::getCandidate() const
{
const gloox::Jingle::Content* content = static_cast(m_Wrapped->plugins().front());
if (!content)
return glooxwrapper::Jingle::ICEUDP::Candidate();
const gloox::Jingle::ICEUDP* iceUDP = static_cast(content->findPlugin(gloox::Jingle::PluginICEUDP));
if (!iceUDP)
return glooxwrapper::Jingle::ICEUDP::Candidate();
gloox::Jingle::ICEUDP::Candidate glooxCandidate = iceUDP->candidates().front();
return glooxwrapper::Jingle::ICEUDP::Candidate{glooxCandidate.ip, glooxCandidate.port};
}
bool glooxwrapper::Jingle::Session::sessionInitiate(char* ipStr, u16 port)
{
gloox::Jingle::ICEUDP::CandidateList* candidateList = new gloox::Jingle::ICEUDP::CandidateList();
candidateList->push_back(gloox::Jingle::ICEUDP::Candidate
{
"1", // component_id,
"1", // foundation
"0", // andidate_generation
"1", // candidate_id
ipStr,
"0", // network
port,
0, // priotiry
"udp",
"", // base_ip
0, // base_port
gloox::Jingle::ICEUDP::ServerReflexive
});
gloox::Jingle::PluginList* pluginList = new gloox::Jingle::PluginList();
pluginList->push_back(new gloox::Jingle::ICEUDP(/*local_pwd*/"", /*local_ufrag*/"", *candidateList));
return m_Wrapped->sessionInitiate(new gloox::Jingle::Content(std::string("game-data"), *pluginList));
}
glooxwrapper::Jingle::ICEUDP::ICEUDP(glooxwrapper::Jingle::ICEUDP::CandidateList& candidates)
: glooxwrapper::Jingle::Plugin(NULL, false)
{
gloox::Jingle::ICEUDP::CandidateList glooxCandidates;
for (const glooxwrapper::Jingle::ICEUDP::Candidate& candidate : candidates)
glooxCandidates.push_back(gloox::Jingle::ICEUDP::Candidate
{
"1", // component_id,
"1", // foundation
"0", // candidate_generation
"1", // candidate_id
candidate.ip.to_string(),
"0", // network
candidate.port,
0, // priority
"udp",
"", // base_ip
0, // base_port
gloox::Jingle::ICEUDP::ServerReflexive
});
m_Wrapped = new gloox::Jingle::ICEUDP(/*local_pwd*/"", /*local_ufrag*/"", glooxCandidates);
m_Owned = true;
}
glooxwrapper::Jingle::ICEUDP::ICEUDP()
: glooxwrapper::Jingle::Plugin(NULL, false)
{
m_Wrapped = new gloox::Jingle::ICEUDP();
m_Owned = true;
}
const glooxwrapper::Jingle::ICEUDP::CandidateList glooxwrapper::Jingle::ICEUDP::candidates() const
{
glooxwrapper::Jingle::ICEUDP::CandidateList candidateListWrapper;
for (const gloox::Jingle::ICEUDP::Candidate& candidate : static_cast(m_Wrapped)->candidates())
candidateListWrapper.push_back(glooxwrapper::Jingle::ICEUDP::Candidate{candidate.ip, candidate.port});
return candidateListWrapper;
}
glooxwrapper::SessionManager::SessionManager(Client* parent, Jingle::SessionHandler* sh)
{
m_HandlerWrapper = new SessionHandlerWrapper(sh, false);
m_Wrapped = new gloox::Jingle::SessionManager(parent->getWrapped(), m_HandlerWrapper);
}
glooxwrapper::SessionManager::~SessionManager()
{
delete m_Wrapped;
delete m_HandlerWrapper;
}
void glooxwrapper::SessionManager::registerPlugins()
{
m_Wrapped->registerPlugin(new gloox::Jingle::Content());
m_Wrapped->registerPlugin(new gloox::Jingle::ICEUDP());
}
glooxwrapper::Jingle::Session glooxwrapper::SessionManager::createSession(const JID& callee)
{
gloox::Jingle::Session* glooxSession = m_Wrapped->createSession(callee.getWrapped(), m_HandlerWrapper);
return glooxwrapper::Jingle::Session(glooxSession, false);
}
Index: ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h
===================================================================
--- ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 21519)
+++ ps/trunk/source/lobby/glooxwrapper/glooxwrapper.h (revision 21520)
@@ -1,678 +1,680 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_GLOOXWRAPPER_H
#define INCLUDED_GLOOXWRAPPER_H
/*
The gloox API uses various STL types (std::string, std::list, etc), and
it has functions that acquire/release ownership of objects and expect the
library's user's 'new'/'delete' functions to be compatible with the library's.
These assumptions are invalid when the game and library are built with
different compiler versions (or the same version with different build flags):
the STL types have different layouts, and new/delete can use different heaps.
We want to let people build the game on Windows with any compiler version
(VC2008, 2010, 2012, 2013, and debug vs release), without requiring them to
rebuild the gloox library themselves. And we don't want to provide ~8 different
prebuilt versions of the library.
glooxwrapper replaces the gloox API with a version that is safe to use across
compiler versions. glooxwrapper and gloox must be compiled together with the
same version, but the resulting library can be used with any other compiler.
This is the small subset of the API that the game currently uses, with no
attempt to be comprehensive.
General design and rules:
* There is a strict boundary between gloox+glooxwrapper.cpp, and the game
code that includes glooxwrapper.h.
Objects allocated with new/delete on one side of the boundary must be
freed/allocated on the same side.
Objects allocated with glooxwrapper_alloc()/glooxwrapper_delete() can be
freely shared across the boundary.
* glooxwrapper.h and users of glooxwrapper must not use any types from
the gloox namespace, except for enums.
* std::string is replaced with glooxwrapper::string,
std::list with glooxwrapper::list
* Most glooxwrapper classes are simple wrappers around gloox classes.
Some always take ownership of their wrapped gloox object (i.e. their
destructor will delete the wrapped object too); some never do; and some
can be used either way (indicated by an m_Owned field).
*/
#if OS_WIN
# include "lib/sysdep/os/win/win.h"
// Prevent gloox pulling in windows.h
# define _WINDOWS_
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if OS_WIN
#define GLOOXWRAPPER_API __declspec(dllexport)
#else
#define GLOOXWRAPPER_API
#endif
namespace glooxwrapper
{
class Client;
class DataForm;
class DelayedDelivery;
class Disco;
class IQ;
class JID;
class MUCRoom;
class MUCRoomConfigHandler;
class Message;
class MessageSession;
class OOB;
class Presence;
class StanzaError;
class StanzaExtension;
class Tag;
class ClientImpl;
class MUCRoomHandlerWrapper;
class SessionHandlerWrapper;
GLOOXWRAPPER_API void* glooxwrapper_alloc(size_t size);
GLOOXWRAPPER_API void glooxwrapper_free(void* p);
class string
{
private:
size_t m_Size;
char* m_Data;
public:
string()
{
m_Size = 0;
m_Data = (char*)glooxwrapper_alloc(1);
m_Data[0] = '\0';
}
string(const string& str)
{
m_Size = str.m_Size;
m_Data = (char*)glooxwrapper_alloc(m_Size + 1);
memcpy(m_Data, str.m_Data, m_Size + 1);
}
string(const std::string& str) : m_Data(NULL)
{
m_Size = str.size();
m_Data = (char*)glooxwrapper_alloc(m_Size + 1);
memcpy(m_Data, str.c_str(), m_Size + 1);
}
string(const char* str)
{
m_Size = strlen(str);
m_Data = (char*)glooxwrapper_alloc(m_Size + 1);
memcpy(m_Data, str, m_Size + 1);
}
string& operator=(const string& str)
{
if (this != &str)
{
glooxwrapper_free(m_Data);
m_Size = str.m_Size;
m_Data = (char*)glooxwrapper_alloc(m_Size + 1);
memcpy(m_Data, str.m_Data, m_Size + 1);
}
return *this;
}
~string()
{
glooxwrapper_free(m_Data);
}
std::string to_string() const
{
return std::string(m_Data, m_Size);
}
const char* c_str() const
{
return m_Data;
}
bool empty() const
{
return m_Size == 0;
}
bool operator==(const char* str) const
{
return strcmp(m_Data, str) == 0;
}
bool operator!=(const char* str) const
{
return strcmp(m_Data, str) != 0;
}
};
static inline std::ostream& operator<<(std::ostream& stream, const string& string)
{
return stream << string.c_str();
}
template
class list
{
private:
struct node
{
node(const T& item) : m_Item(item), m_Next(NULL) {}
T m_Item;
node* m_Next;
};
node* m_Head;
node* m_Tail;
public:
struct const_iterator
{
const node* m_Node;
const_iterator(const node* n) : m_Node(n) {}
bool operator!=(const const_iterator& it) { return m_Node != it.m_Node; }
const_iterator& operator++() { m_Node = m_Node->m_Next; return *this; }
const T& operator*() { return m_Node->m_Item; }
};
const_iterator begin() const { return const_iterator(m_Head); }
const_iterator end() const { return const_iterator(NULL); }
list() : m_Head(NULL), m_Tail(NULL) {}
list(const list& src) : m_Head(NULL), m_Tail(NULL)
{
*this = src;
}
list& operator=(const list& src)
{
if (this != &src)
{
clear();
for (node* n = src.m_Head; n; n = n->m_Next)
push_back(n->m_Item);
}
return *this;
}
~list()
{
clear();
}
void push_back(const T& item)
{
node* n = new (glooxwrapper_alloc(sizeof(node))) node(item);
if (m_Tail)
m_Tail->m_Next = n;
m_Tail = n;
if (!m_Head)
m_Head = n;
}
void clear()
{
node* n = m_Head;
while (n)
{
node* next = n->m_Next;
glooxwrapper_free(n);
n = next;
}
m_Head = m_Tail = NULL;
}
const T& front() const
{
return *begin();
}
};
typedef glooxwrapper::list TagList;
typedef glooxwrapper::list ConstTagList;
struct CertInfo
{
int status;
bool chain;
string issuer;
string server;
int date_from;
int date_to;
string protocol;
string cipher;
string mac;
string compression;
};
struct RegistrationFields
{
string username;
string nick;
string password;
string name;
string first;
string last;
string email;
string address;
string city;
string state;
string zip;
string phone;
string url;
string date;
string misc;
string text;
};
struct MUCRoomParticipant
{
JID* nick;
gloox::MUCRoomAffiliation affiliation;
gloox::MUCRoomRole role;
JID* jid;
int flags;
string reason;
JID* actor;
string newNick;
string status;
JID* alternate;
};
class GLOOXWRAPPER_API ConnectionListener
{
public:
virtual ~ConnectionListener() {}
virtual void onConnect() = 0;
virtual void onDisconnect(gloox::ConnectionError e) = 0;
virtual bool onTLSConnect(const CertInfo& info) = 0;
};
class GLOOXWRAPPER_API IqHandler
{
public:
virtual ~IqHandler() {}
virtual bool handleIq(const IQ& iq) = 0;
virtual void handleIqID(const IQ& iq, int context) = 0;
};
class GLOOXWRAPPER_API MessageHandler
{
public:
virtual ~MessageHandler() {}
virtual void handleMessage(const Message& msg, MessageSession* session = 0) = 0; // MessageSession not supported
};
class GLOOXWRAPPER_API MUCRoomHandler
{
public:
virtual ~MUCRoomHandler() {}
virtual void handleMUCParticipantPresence(MUCRoom* room, const MUCRoomParticipant participant, const Presence& presence) = 0; // MUCRoom not supported
virtual void handleMUCMessage(MUCRoom* room, const Message& msg, bool priv) = 0; // MUCRoom not supported
virtual void handleMUCError(MUCRoom* room, gloox::StanzaError error) = 0; // MUCRoom not supported
virtual void handleMUCSubject(MUCRoom* room, const string& nick, const string& subject) = 0; // MUCRoom not supported
};
class GLOOXWRAPPER_API RegistrationHandler
{
public:
virtual ~RegistrationHandler() {}
virtual void handleRegistrationFields(const JID& from, int fields, string instructions) = 0;
virtual void handleAlreadyRegistered(const JID& from) = 0;
virtual void handleRegistrationResult(const JID& from, gloox::RegistrationResult regResult) = 0;
virtual void handleDataForm(const JID& from, const DataForm& form) = 0; // DataForm not supported
virtual void handleOOB(const JID& from, const OOB& oob) = 0; // OOB not supported
};
class GLOOXWRAPPER_API StanzaExtension
{
public:
StanzaExtension(int type) : m_extensionType(type) {}
virtual ~StanzaExtension() {}
virtual const string& filterString() const = 0;
virtual StanzaExtension* newInstance(const Tag* tag) const = 0;
virtual glooxwrapper::Tag* tag() const = 0;
virtual StanzaExtension* clone() const = 0;
int extensionType() const { return m_extensionType; }
private:
int m_extensionType;
};
class GLOOXWRAPPER_API Client
{
NONCOPYABLE(Client);
private:
gloox::Client* m_Wrapped;
ClientImpl* m_Impl;
Disco* m_DiscoWrapper;
public:
gloox::Client* getWrapped() { return m_Wrapped; }
bool connect(bool block = true);
gloox::ConnectionError recv(int timeout = -1);
const string getID() const;
void send(const IQ& iq);
void setTls(gloox::TLSPolicy tls);
void setCompression(bool compression);
void setSASLMechanisms(int mechanisms);
void registerStanzaExtension(StanzaExtension* ext);
void registerConnectionListener(ConnectionListener* cl);
void registerIqHandler(IqHandler* ih, int exttype);
void registerMessageHandler(MessageHandler* mh);
bool removePresenceExtension(int type);
Disco* disco() const { return m_DiscoWrapper; }
Client(const string& server);
Client(const JID& jid, const string& password, int port = -1);
~Client();
void setPresence(gloox::Presence::PresenceType pres, int priority, const string& status = "");
void disconnect();
};
class GLOOXWRAPPER_API DelayedDelivery
{
NONCOPYABLE(DelayedDelivery);
private:
const gloox::DelayedDelivery* m_Wrapped;
public:
DelayedDelivery(const gloox::DelayedDelivery* wrapped);
const string stamp() const;
};
class GLOOXWRAPPER_API Disco
{
NONCOPYABLE(Disco);
private:
gloox::Disco* m_Wrapped;
public:
Disco(gloox::Disco* wrapped);
void setVersion(const string& name, const string& version, const string& os = "");
void setIdentity(const string& category, const string& type, const string& name = "");
};
class GLOOXWRAPPER_API IQ
{
NONCOPYABLE(IQ);
private:
gloox::IQ* m_Wrapped;
bool m_Owned;
public:
const gloox::IQ& getWrapped() const { return *m_Wrapped; }
IQ(const gloox::IQ& iq) : m_Wrapped(const_cast(&iq)), m_Owned(false) { }
IQ(gloox::IQ::IqType type, const JID& to, const string& id);
~IQ();
void addExtension(const StanzaExtension* se);
const StanzaExtension* findExtension(int type) const;
template
inline const T* findExtension(int type) const
{
return static_cast(findExtension(type));
}
gloox::IQ::IqType subtype() const;
+ const string id() const;
+ const gloox::JID& from() const;
gloox::StanzaError error_error() const; // wrapper for ->error()->error()
Tag* tag() const;
};
class GLOOXWRAPPER_API JID
{
NONCOPYABLE(JID);
private:
gloox::JID* m_Wrapped;
bool m_Owned;
void init(const char* data, size_t len);
public:
const gloox::JID& getWrapped() const { return *m_Wrapped; }
JID(const gloox::JID& jid) : m_Wrapped(const_cast(&jid)), m_Owned(false) { }
JID();
JID(const string& jid);
JID(const std::string& jid) { init(jid.c_str(), jid.size()); }
~JID();
string username() const;
string resource() const;
};
class GLOOXWRAPPER_API Message
{
NONCOPYABLE(Message);
private:
gloox::Message* m_Wrapped;
bool m_Owned;
glooxwrapper::JID* m_From;
public:
Message(gloox::Message* wrapped, bool owned);
~Message();
gloox::Message::MessageType subtype() const;
const JID& from() const;
string body() const;
string subject(const string& lang = "default") const;
string thread() const;
const glooxwrapper::DelayedDelivery* when() const;
};
class GLOOXWRAPPER_API MUCRoom
{
NONCOPYABLE(MUCRoom);
private:
gloox::MUCRoom* m_Wrapped;
MUCRoomHandlerWrapper* m_HandlerWrapper;
public:
MUCRoom(Client* parent, const JID& nick, MUCRoomHandler* mrh, MUCRoomConfigHandler* mrch = 0);
~MUCRoom();
const string nick() const;
void join(gloox::Presence::PresenceType type = gloox::Presence::Available, const string& status = "", int priority = 0);
void leave(const string& msg = "");
void send(const string& message);
void setNick(const string& nick);
void setPresence(gloox::Presence::PresenceType presence, const string& msg = "");
void setRequestHistory(int value, gloox::MUCRoom::HistoryRequestType type);
void kick(const string& nick, const string& reason);
void ban(const string& nick, const string& reason);
};
class GLOOXWRAPPER_API Presence
{
gloox::Presence::PresenceType m_Presence;
public:
Presence(gloox::Presence::PresenceType presence) : m_Presence(presence) {}
gloox::Presence::PresenceType presence() const { return m_Presence; }
};
class GLOOXWRAPPER_API Registration
{
NONCOPYABLE(Registration);
private:
gloox::Registration* m_Wrapped;
std::list > m_RegistrationHandlers;
public:
Registration(Client* parent);
~Registration();
void fetchRegistrationFields();
bool createAccount(int fields, const RegistrationFields& values);
void registerRegistrationHandler(RegistrationHandler* rh);
};
class GLOOXWRAPPER_API Tag
{
NONCOPYABLE(Tag);
private:
gloox::Tag* m_Wrapped;
bool m_Owned;
Tag(const string& name);
Tag(const string& name, const string& cdata);
Tag(gloox::Tag* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
~Tag();
public:
// Internal use:
gloox::Tag* getWrapped() { return m_Wrapped; }
gloox::Tag* stealWrapped() { m_Owned = false; return m_Wrapped; }
static Tag* allocate(gloox::Tag* wrapped, bool owned);
// Instead of using new/delete, Tags must be allocated/freed with these functions
static Tag* allocate(const string& name);
static Tag* allocate(const string& name, const string& cdata);
static void free(const Tag* tag);
bool addAttribute(const string& name, const string& value);
string findAttribute(const string& name) const;
Tag* clone() const;
string xmlns() const;
bool setXmlns(const string& xmlns);
string xml() const;
void addChild(Tag* child);
string name() const;
string cdata() const;
const Tag* findTag_clone(const string& expression) const; // like findTag but must be Tag::free()d
ConstTagList findTagList_clone(const string& expression) const; // like findTagList but each tag must be Tag::free()d
};
namespace Jingle
{
class GLOOXWRAPPER_API Plugin
{
protected:
const gloox::Jingle::Plugin* m_Wrapped;
bool m_Owned;
public:
Plugin(const gloox::Jingle::Plugin* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
virtual ~Plugin();
const Plugin findPlugin(int type) const;
const gloox::Jingle::Plugin* getWrapped() const { return m_Wrapped; }
};
typedef list PluginList;
class GLOOXWRAPPER_API Content : public Plugin
{
public:
Content(const string& name, const PluginList& plugins);
Content();
};
class GLOOXWRAPPER_API ICEUDP : public Plugin
{
public:
struct Candidate {
string ip;
int port;
};
typedef std::list CandidateList;
ICEUDP(CandidateList& candidates);
ICEUDP();
const CandidateList candidates() const;
};
class GLOOXWRAPPER_API Session
{
protected:
gloox::Jingle::Session* m_Wrapped;
bool m_Owned;
public:
class GLOOXWRAPPER_API Jingle
{
private:
const gloox::Jingle::Session::Jingle* m_Wrapped;
bool m_Owned;
public:
Jingle(const gloox::Jingle::Session::Jingle* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
const PluginList plugins() const;
ICEUDP::Candidate getCandidate() const;
};
Session(gloox::Jingle::Session* wrapped, bool owned) : m_Wrapped(wrapped), m_Owned(owned) {}
bool sessionInitiate(char* ipStr, uint16_t port);
};
class GLOOXWRAPPER_API SessionHandler
{
public:
virtual ~SessionHandler() {}
virtual void handleSessionAction(gloox::Jingle::Action action, Session* session, const Session::Jingle* jingle) = 0;
};
}
class GLOOXWRAPPER_API SessionManager
{
private:
gloox::Jingle::SessionManager* m_Wrapped;
SessionHandlerWrapper* m_HandlerWrapper;
public:
SessionManager(Client* parent, Jingle::SessionHandler* sh);
~SessionManager();
void registerPlugins();
Jingle::Session createSession(const JID& callee);
};
}
#endif // INCLUDED_GLOOXWRAPPER_H
Index: ps/trunk/source/network/NetClient.cpp
===================================================================
--- ps/trunk/source/network/NetClient.cpp (revision 21519)
+++ ps/trunk/source/network/NetClient.cpp (revision 21520)
@@ -1,876 +1,914 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "NetClient.h"
#include "NetClientTurnManager.h"
#include "NetMessage.h"
#include "NetSession.h"
#include "lib/byte_order.h"
#include "lib/external_libraries/enet.h"
#include "lib/sysdep/sysdep.h"
+#include "lobby/IXmppClient.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Compress.h"
#include "ps/CStr.h"
#include "ps/Game.h"
#include "ps/Loader.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
CNetClient *g_NetClient = NULL;
/**
* Async task for receiving the initial game state when rejoining an
* in-progress network game.
*/
class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
{
NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
public:
CNetFileReceiveTask_ClientRejoin(CNetClient& client)
: m_Client(client)
{
}
virtual void OnComplete()
{
// We've received the game state from the server
// Save it so we can use it after the map has finished loading
m_Client.m_JoinSyncBuffer = m_Buffer;
// Pretend the server told us to start the game
CGameStartMessage start;
m_Client.HandleMessage(&start);
}
private:
CNetClient& m_Client;
};
CNetClient::CNetClient(CGame* game, bool isLocalClient) :
m_Session(NULL),
m_UserName(L"anonymous"),
m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
m_IsLocalClient(isLocalClient),
m_LastConnectionCheck(0),
m_Rejoin(false)
{
m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
void* context = this;
JS_AddExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
// Set up transitions for session
AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
+ AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context);
AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context);
AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_KICKED, NCS_JOIN_SYNCING, (void*)&OnKicked, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_LOADING, (uint)NMT_KICKED, NCS_LOADING, (void*)&OnKicked, context);
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
AddTransition(NCS_LOADING, (uint)NMT_CLIENTS_LOADING, NCS_LOADING, (void*)&OnClientsLoading, context);
AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENTS_LOADING, NCS_INGAME, (void*)&OnClientsLoading, context);
AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
// Set first state
SetFirstState(NCS_UNCONNECTED);
}
CNetClient::~CNetClient()
{
DestroyConnection();
JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
}
void CNetClient::TraceMember(JSTracer *trc)
{
for (JS::Heap& guiMessage : m_GuiMessageQueue)
JS_CallValueTracer(trc, &guiMessage, "m_GuiMessageQueue");
}
void CNetClient::SetUserName(const CStrW& username)
{
ENSURE(!m_Session); // must be called before we start the connection
m_UserName = username;
}
+void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName)
+{
+ m_HostingPlayerName = hostingPlayerName;
+}
+
bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient)
{
CNetClientSession* session = new CNetClientSession(*this);
bool ok = session->Connect(server, port, m_IsLocalClient, enetClient);
SetAndOwnSession(session);
return ok;
}
void CNetClient::SetAndOwnSession(CNetClientSession* session)
{
delete m_Session;
m_Session = session;
}
void CNetClient::DestroyConnection()
{
// Send network messages from the current frame before connection is destroyed.
if (m_ClientTurnManager)
{
m_ClientTurnManager->OnDestroyConnection(); // End sending of commands for scheduled turn.
Flush(); // Make sure the messages are sent.
}
SAFE_DELETE(m_Session);
}
void CNetClient::Poll()
{
if (!m_Session)
return;
CheckServerConnection();
m_Session->Poll();
}
void CNetClient::CheckServerConnection()
{
// Trigger local warnings if the connection to the server is bad.
// At most once per second.
std::time_t now = std::time(nullptr);
if (now <= m_LastConnectionCheck)
return;
m_LastConnectionCheck = now;
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// Report if we are losing the connection to the server
u32 lastReceived = m_Session->GetLastReceivedTime();
if (lastReceived > NETWORK_WARNING_TIMEOUT)
{
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
PushGuiMessage(msg);
return;
}
// Report if we have a bad ping to the server
u32 meanRTT = m_Session->GetMeanRTT();
if (meanRTT > DEFAULT_TURN_LENGTH_MP)
{
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
PushGuiMessage(msg);
}
}
void CNetClient::Flush()
{
if (m_Session)
m_Session->Flush();
}
void CNetClient::GuiPoll(JS::MutableHandleValue ret)
{
if (m_GuiMessageQueue.empty())
{
ret.setUndefined();
return;
}
ret.set(m_GuiMessageQueue.front());
m_GuiMessageQueue.pop_front();
}
void CNetClient::PushGuiMessage(const JS::HandleValue message)
{
ENSURE(!message.isUndefined());
m_GuiMessageQueue.push_back(JS::Heap(message));
}
std::string CNetClient::TestReadGuiMessages()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
std::string r;
JS::RootedValue msg(cx);
while (true)
{
GuiPoll(&msg);
if (msg.isUndefined())
break;
r += GetScriptInterface().ToString(&msg) + "\n";
}
return r;
}
const ScriptInterface& CNetClient::GetScriptInterface()
{
return m_Game->GetSimulation2()->GetScriptInterface();
}
void CNetClient::PostPlayerAssignmentsToScript()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'players', 'newAssignments':{}})", &msg);
JS::RootedValue newAssignments(cx);
GetScriptInterface().GetProperty(msg, "newAssignments", &newAssignments);
for (const std::pair& p : m_PlayerAssignments)
{
JS::RootedValue assignment(cx);
GetScriptInterface().Eval("({})", &assignment);
GetScriptInterface().SetProperty(assignment, "name", CStrW(p.second.m_Name), false);
GetScriptInterface().SetProperty(assignment, "player", p.second.m_PlayerID, false);
GetScriptInterface().SetProperty(assignment, "status", p.second.m_Status, false);
GetScriptInterface().SetProperty(newAssignments, p.first.c_str(), assignment, false);
}
PushGuiMessage(msg);
}
bool CNetClient::SendMessage(const CNetMessage* message)
{
if (!m_Session)
return false;
return m_Session->SendMessage(message);
}
void CNetClient::HandleConnect()
{
Update((uint)NMT_CONNECT_COMPLETE, NULL);
}
void CNetClient::HandleDisconnect(u32 reason)
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
GetScriptInterface().SetProperty(msg, "reason", (int)reason, false);
PushGuiMessage(msg);
SAFE_DELETE(m_Session);
// Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
// we'd need one for every single state, and we don't need to use per-state actions)
SetCurrState(NCS_UNCONNECTED);
}
void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface)
{
CGameSetupMessage gameSetup(scriptInterface);
gameSetup.m_Data = attrs;
SendMessage(&gameSetup);
}
void CNetClient::SendAssignPlayerMessage(const int playerID, const CStr& guid)
{
CAssignPlayerMessage assignPlayer;
assignPlayer.m_PlayerID = playerID;
assignPlayer.m_GUID = guid;
SendMessage(&assignPlayer);
}
void CNetClient::SendChatMessage(const std::wstring& text)
{
CChatMessage chat;
chat.m_Message = text;
SendMessage(&chat);
}
void CNetClient::SendReadyMessage(const int status)
{
CReadyMessage readyStatus;
readyStatus.m_Status = status;
SendMessage(&readyStatus);
}
void CNetClient::SendClearAllReadyMessage()
{
CClearAllReadyMessage clearAllReady;
SendMessage(&clearAllReady);
}
void CNetClient::SendStartGameMessage()
{
CGameStartMessage gameStart;
SendMessage(&gameStart);
}
void CNetClient::SendRejoinedMessage()
{
CRejoinedMessage rejoinedMessage;
SendMessage(&rejoinedMessage);
}
void CNetClient::SendKickPlayerMessage(const CStrW& playerName, bool ban)
{
CKickedMessage kickPlayer;
kickPlayer.m_Name = playerName;
kickPlayer.m_Ban = ban;
SendMessage(&kickPlayer);
}
void CNetClient::SendPausedMessage(bool pause)
{
CClientPausedMessage pausedMessage;
pausedMessage.m_Pause = pause;
SendMessage(&pausedMessage);
}
bool CNetClient::HandleMessage(CNetMessage* message)
{
// Handle non-FSM messages first
Status status = m_Session->GetFileTransferer().HandleMessageReceive(message);
if (status == INFO::OK)
return true;
if (status != INFO::SKIPPED)
return false;
if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
{
CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
// TODO: we should support different transfer request types, instead of assuming
// it's always requesting the simulation state
std::stringstream stream;
LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
stream.write((char*)&turn, sizeof(turn));
bool ok = m_Game->GetSimulation2()->SerializeState(stream);
ENSURE(ok);
// Compress the content with zlib to save bandwidth
// (TODO: if this is still too large, compressing with e.g. LZMA works much better)
std::string compressed;
CompressZLib(stream.str(), compressed, true);
m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
return true;
}
// Update FSM
bool ok = Update(message->GetType(), message);
if (!ok)
LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
return ok;
}
void CNetClient::LoadFinished()
{
JSContext* cx = GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
if (!m_JoinSyncBuffer.empty())
{
// We're rejoining a game, and just finished loading the initial map,
// so deserialize the saved game state now
std::string state;
DecompressZLib(m_JoinSyncBuffer, state, true);
std::stringstream stream(state);
u32 turn;
stream.read((char*)&turn, sizeof(turn));
turn = to_le32(turn);
LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
ENSURE(ok);
m_ClientTurnManager->ResetState(turn, turn);
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'join_syncing'})", &msg);
PushGuiMessage(msg);
}
else
{
// Connecting at the start of a game, so we'll wait for other players to finish loading
JS::RootedValue msg(cx);
GetScriptInterface().Eval("({'type':'netstatus','status':'waiting_for_players'})", &msg);
PushGuiMessage(msg);
}
CLoadedGameMessage loaded;
loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
SendMessage(&loaded);
}
+void CNetClient::SendAuthenticateMessage()
+{
+ CAuthenticateMessage authenticate;
+ authenticate.m_Name = m_UserName;
+ authenticate.m_Password = L""; // TODO
+ authenticate.m_IsLocalClient = m_IsLocalClient;
+ SendMessage(&authenticate);
+}
+
bool CNetClient::OnConnect(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'connected'})", &msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
CNetClient* client = (CNetClient*)context;
CCliHandshakeMessage handshake;
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
client->SendMessage(&handshake);
return true;
}
bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
CNetClient* client = (CNetClient*)context;
CSrvHandshakeResponseMessage* message = (CSrvHandshakeResponseMessage*)event->GetParamRef();
client->m_GUID = message->m_GUID;
- CAuthenticateMessage authenticate;
- authenticate.m_Name = client->m_UserName;
- authenticate.m_Password = L""; // TODO
- authenticate.m_IsLocalClient = client->m_IsLocalClient;
- client->SendMessage(&authenticate);
+ if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
+ {
+ if (g_XmppClient && !client->m_HostingPlayerName.empty())
+ g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID);
+ else
+ {
+ JSContext* cx = client->GetScriptInterface().GetContext();
+ JSAutoRequest rq(cx);
+
+ JS::RootedValue msg(cx);
+ client->GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
+ client->GetScriptInterface().SetProperty(msg, "reason", (int)NDR_LOBBY_AUTH_FAILED, false);
+ client->PushGuiMessage(msg);
+ LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message");
+ }
+ return true;
+ }
+ client->SendAuthenticateMessage();
+ return true;
+}
+
+bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event)
+{
+ ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
+
+ CNetClient* client = (CNetClient*)context;
+ client->SendAuthenticateMessage();
return true;
}
bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CAuthenticateResultMessage* message = (CAuthenticateResultMessage*)event->GetParamRef();
LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
client->m_HostID = message->m_HostID;
client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'authenticated'})", &msg);
client->GetScriptInterface().SetProperty(msg, "rejoining", client->m_Rejoin);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnChat(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_CHAT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CChatMessage* message = (CChatMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'chat'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->GetScriptInterface().SetProperty(msg, "text", std::wstring(message->m_Message), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnReady(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_READY);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'ready'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->GetScriptInterface().SetProperty(msg, "status", int (message->m_Status), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
client->m_GameAttributes = message->m_Data;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'gamesetup'})", &msg);
client->GetScriptInterface().SetProperty(msg, "data", message->m_Data, false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
CNetClient* client = (CNetClient*)context;
CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
// Unpack the message
PlayerAssignmentMap newPlayerAssignments;
for (size_t i = 0; i < message->m_Hosts.size(); ++i)
{
PlayerAssignment assignment;
assignment.m_Enabled = true;
assignment.m_Name = message->m_Hosts[i].m_Name;
assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
assignment.m_Status = message->m_Hosts[i].m_Status;
newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
}
client->m_PlayerAssignments.swap(newPlayerAssignments);
client->PostPlayerAssignmentsToScript();
return true;
}
bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_GAME_START);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// Find the player assigned to our GUID
int player = -1;
if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
client->m_ClientTurnManager = new CNetClientTurnManager(
*client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
client->m_Game->SetPlayerID(player);
client->m_Game->StartGame(&client->m_GameAttributes, "");
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'start'})", &msg);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
CNetClient* client = (CNetClient*)context;
// The server wants us to start downloading the game state from it, so do so
client->m_Session->GetFileTransferer().StartTask(
shared_ptr(new CNetFileReceiveTask_ClientRejoin(*client))
);
return true;
}
bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
CNetClient* client = (CNetClient*)context;
CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
// Execute all the received commands for the latest turn
client->m_ClientTurnManager->UpdateFastForward();
return true;
}
bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_REJOINED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'rejoined'})", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnKicked(void *context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_KICKED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({})", &msg);
client->GetScriptInterface().SetProperty(msg, "username", message->m_Name);
client->GetScriptInterface().SetProperty(msg, "type", CStr("kicked"));
client->GetScriptInterface().SetProperty(msg, "banned", message->m_Ban != 0);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
{
// Report the timeout of some other client
ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
if (client->GetCurrState() == NCS_LOADING)
return true;
CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
{
// Performance statistics for one or multiple clients
ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
if (client->GetCurrState() == NCS_LOADING)
return true;
CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
// Display warnings for other clients with bad ping
for (size_t i = 0; i < message->m_Clients.size(); ++i)
{
if (message->m_Clients[i].m_MeanRTT < DEFAULT_TURN_LENGTH_MP || message->m_Clients[i].m_GUID == client->m_GUID)
continue;
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
client->GetScriptInterface().SetProperty(msg, "guid", message->m_Clients[i].m_GUID);
client->GetScriptInterface().SetProperty(msg, "meanRTT", message->m_Clients[i].m_MeanRTT);
client->PushGuiMessage(msg);
}
return true;
}
bool CNetClient::OnClientsLoading(void *context, CFsmEvent *event)
{
ENSURE(event->GetType() == (uint)NMT_CLIENTS_LOADING);
CClientsLoadingMessage* message = (CClientsLoadingMessage*)event->GetParamRef();
std::vector guids;
guids.reserve(message->m_Clients.size());
for (const CClientsLoadingMessage::S_m_Clients& client : message->m_Clients)
guids.push_back(client.m_GUID);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'clients-loading' })", &msg);
client->GetScriptInterface().SetProperty(msg, "guids", guids);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
{
ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({ 'type':'paused' })", &msg);
client->GetScriptInterface().SetProperty(msg, "pause", message->m_Pause != 0);
client->GetScriptInterface().SetProperty(msg, "guid", message->m_GUID);
client->PushGuiMessage(msg);
return true;
}
bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
{
ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
CNetClient* client = (CNetClient*)context;
JSContext* cx = client->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// All players have loaded the game - start running the turn manager
// so that the game begins
client->m_Game->SetTurnManager(client->m_ClientTurnManager);
JS::RootedValue msg(cx);
client->GetScriptInterface().Eval("({'type':'netstatus','status':'active'})", &msg);
client->PushGuiMessage(msg);
// If we have rejoined an in progress game, send the rejoined message to the server.
if (client->m_Rejoin)
client->SendRejoinedMessage();
return true;
}
bool CNetClient::OnInGame(void *context, CFsmEvent* event)
{
// TODO: should split each of these cases into a separate method
CNetClient* client = (CNetClient*)context;
CNetMessage* message = (CNetMessage*)event->GetParamRef();
if (message)
{
if (message->GetType() == NMT_SIMULATION_COMMAND)
{
CSimulationMessage* simMessage = static_cast (message);
client->m_ClientTurnManager->OnSimulationMessage(simMessage);
}
else if (message->GetType() == NMT_SYNC_ERROR)
{
CSyncErrorMessage* syncMessage = static_cast (message);
client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
}
else if (message->GetType() == NMT_END_COMMAND_BATCH)
{
CEndCommandBatchMessage* endMessage = static_cast (message);
client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
}
}
return true;
}
Index: ps/trunk/source/network/NetClient.h
===================================================================
--- ps/trunk/source/network/NetClient.h (revision 21519)
+++ ps/trunk/source/network/NetClient.h (revision 21520)
@@ -1,292 +1,303 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2018 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef NETCLIENT_H
#define NETCLIENT_H
#include "network/fsm.h"
#include "network/NetFileTransfer.h"
#include "network/NetHost.h"
#include "scriptinterface/ScriptVal.h"
#include "ps/CStr.h"
#include
class CGame;
class CNetClientSession;
class CNetClientTurnManager;
class CNetServer;
class ScriptInterface;
typedef struct _ENetHost ENetHost;
// NetClient session FSM states
enum
{
NCS_UNCONNECTED,
NCS_CONNECT,
NCS_HANDSHAKE,
NCS_AUTHENTICATE,
NCS_INITIAL_GAMESETUP,
NCS_PREGAME,
NCS_LOADING,
NCS_JOIN_SYNCING,
NCS_INGAME
};
/**
* Network client.
* This code is run by every player (including the host, if they are not
* a dedicated server).
* It provides an interface between the GUI, the network (via CNetClientSession),
* and the game (via CGame and CNetClientTurnManager).
*/
class CNetClient : public CFsm
{
NONCOPYABLE(CNetClient);
friend class CNetFileReceiveTask_ClientRejoin;
public:
/**
* Construct a client associated with the given game object.
* The game must exist for the lifetime of this object.
*/
CNetClient(CGame* game, bool isLocalClient);
virtual ~CNetClient();
/**
* We assume that adding a tracing function that's only called
* during GC is better for performance than using a
* PersistentRooted where each value needs to be added to
* the root set.
*/
static void Trace(JSTracer *trc, void *data)
{
reinterpret_cast(data)->TraceMember(trc);
}
void TraceMember(JSTracer *trc);
/**
* Set the user's name that will be displayed to all players.
* This must not be called after the connection setup.
*/
void SetUserName(const CStrW& username);
/**
+ * Set the name of the hosting player.
+ * This is needed for the secure lobby authentication.
+ */
+ void SetHostingPlayerName(const CStr& hostingPlayerName);
+
+ /**
* Returns the GUID of the local client.
* Used for distinguishing observers.
*/
CStr GetGUID() const { return m_GUID; }
/**
* Set up a connection to the remote networked server.
* @param server IP address or host name to connect to
* @return true on success, false on connection failure
*/
bool SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient = NULL);
/**
* Destroy the connection to the server.
* This client probably cannot be used again.
*/
void DestroyConnection();
/**
* Poll the connection for messages from the server and process them, and send
* any queued messages.
* This must be called frequently (i.e. once per frame).
*/
void Poll();
/**
* Locally triggers a GUI message if the connection to the server is being lost or has bad latency.
*/
void CheckServerConnection();
/**
* Flush any queued outgoing network messages.
* This should be called soon after sending a group of messages that may be batched together.
*/
void Flush();
/**
* Retrieves the next queued GUI message, and removes it from the queue.
* The returned value is in the GetScriptInterface() JS context.
*
* This is the only mechanism for the networking code to send messages to
* the GUI - it is pull-based (instead of push) so the engine code does not
* need to know anything about the code structure of the GUI scripts.
*
* The structure of the messages is { "type": "...", ... }.
* The exact types and associated data are not specified anywhere - the
* implementation and GUI scripts must make the same assumptions.
*
* @return next message, or the value 'undefined' if the queue is empty
*/
void GuiPoll(JS::MutableHandleValue);
/**
* Add a message to the queue, to be read by GuiPoll.
* The script value must be in the GetScriptInterface() JS context.
*/
void PushGuiMessage(const JS::HandleValue message);
/**
* Return a concatenation of all messages in the GUI queue,
* for test cases to easily verify the queue contents.
*/
std::string TestReadGuiMessages();
/**
* Get the script interface associated with this network client,
* which is equivalent to the one used by the CGame in the constructor.
*/
const ScriptInterface& GetScriptInterface();
/**
* Send a message to the server.
* @param message message to send
* @return true on success
*/
bool SendMessage(const CNetMessage* message);
/**
* Call when the network connection has been successfully initiated.
*/
void HandleConnect();
/**
* Call when the network connection has been lost.
*/
void HandleDisconnect(u32 reason);
/**
* Call when a message has been received from the network.
*/
bool HandleMessage(CNetMessage* message);
/**
* Call when the game has started and all data files have been loaded,
* to signal to the server that we are ready to begin the game.
*/
void LoadFinished();
void SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface);
void SendAssignPlayerMessage(const int playerID, const CStr& guid);
void SendChatMessage(const std::wstring& text);
void SendReadyMessage(const int status);
void SendClearAllReadyMessage();
void SendStartGameMessage();
/**
* Call when the client has rejoined a running match and finished
* the loading screen.
*/
void SendRejoinedMessage();
/**
* Call to kick/ban a client
*/
void SendKickPlayerMessage(const CStrW& playerName, bool ban);
/**
* Call when the client has paused or unpaused the game.
*/
void SendPausedMessage(bool pause);
private:
+
+ void SendAuthenticateMessage();
+
// Net message / FSM transition handlers
static bool OnConnect(void* context, CFsmEvent* event);
static bool OnHandshake(void* context, CFsmEvent* event);
static bool OnHandshakeResponse(void* context, CFsmEvent* event);
+ static bool OnAuthenticateRequest(void* context, CFsmEvent* event);
static bool OnAuthenticate(void* context, CFsmEvent* event);
static bool OnChat(void* context, CFsmEvent* event);
static bool OnReady(void* context, CFsmEvent* event);
static bool OnGameSetup(void* context, CFsmEvent* event);
static bool OnPlayerAssignment(void* context, CFsmEvent* event);
static bool OnInGame(void* context, CFsmEvent* event);
static bool OnGameStart(void* context, CFsmEvent* event);
static bool OnJoinSyncStart(void* context, CFsmEvent* event);
static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
static bool OnRejoined(void* context, CFsmEvent* event);
static bool OnKicked(void* context, CFsmEvent* event);
static bool OnClientTimeout(void* context, CFsmEvent* event);
static bool OnClientPerformance(void* context, CFsmEvent* event);
static bool OnClientsLoading(void* context, CFsmEvent* event);
static bool OnClientPaused(void* context, CFsmEvent* event);
static bool OnLoadedGame(void* context, CFsmEvent* event);
/**
* Take ownership of a session object, and use it for all network communication.
*/
void SetAndOwnSession(CNetClientSession* session);
/**
* Push a message onto the GUI queue listing the current player assignments.
*/
void PostPlayerAssignmentsToScript();
CGame *m_Game;
CStrW m_UserName;
+ CStr m_HostingPlayerName;
/// Current network session (or NULL if not connected)
CNetClientSession* m_Session;
/// Turn manager associated with the current game (or NULL if we haven't started the game yet)
CNetClientTurnManager* m_ClientTurnManager;
/// Unique-per-game identifier of this client, used to identify the sender of simulation commands
u32 m_HostID;
/// True if the player is currently rejoining or has already rejoined the game.
bool m_Rejoin;
/// Whether to prevent the client of the host from timing out
bool m_IsLocalClient;
/// Latest copy of game setup attributes heard from the server
JS::PersistentRootedValue m_GameAttributes;
/// Latest copy of player assignments heard from the server
PlayerAssignmentMap m_PlayerAssignments;
/// Globally unique identifier to distinguish users beyond the lifetime of a single network session
CStr m_GUID;
/// Queue of messages for GuiPoll
std::deque> m_GuiMessageQueue;
/// Serialized game state received when joining an in-progress game
std::string m_JoinSyncBuffer;
/// Time when the server was last checked for timeouts and bad latency
std::time_t m_LastConnectionCheck;
};
/// Global network client for the standard game
extern CNetClient *g_NetClient;
#endif // NETCLIENT_H
Index: ps/trunk/source/network/NetHost.h
===================================================================
--- ps/trunk/source/network/NetHost.h (revision 21519)
+++ ps/trunk/source/network/NetHost.h (revision 21520)
@@ -1,105 +1,106 @@
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef NETHOST_H
#define NETHOST_H
#include "ps/CStr.h"
#include