Compare commits

...

16 Commits

Author SHA1 Message Date
hiker
90f4855938 Only do default assignments of stk- to steam-accounts on first
start, after that use the saved settings (otherwise a user deciding
to play without steam would get his setting overridden).
2017-05-09 09:21:19 +10:00
hiker
9d350ebaa6 Removed unnecessary line. 2017-05-08 17:48:46 +10:00
hiker
059b77d34b Allow to connect and disconnet stk and steam accounts in the login screen. 2017-05-05 23:59:43 +10:00
hiker
31f970f625 Save Steam user name for accounts connected to steam. 2017-05-05 09:36:11 +10:00
hiker
d651e0c710 Started to support steam accounts with existing STK accounts. 2017-05-04 00:06:10 +10:00
hiker
b0408d414c Removed now unnecessary command line parameter. 2017-05-03 09:30:04 +10:00
hiker
dddd00142b Fixed windows version. 2017-05-03 08:57:59 +10:00
Joerg Henrichs
01aeb9f2fd First linux version now working. 2017-05-03 00:23:50 +10:00
Joerg Henrichs
29e1541b47 Fixed line ending style. 2017-05-01 08:17:15 +10:00
hiker
92709b2ada Try to fix linux compilation. 2017-04-25 01:14:47 +10:00
hiker
ffca9b14d8 Automatically create offline account when logged into steam. 2017-04-25 01:01:03 +10:00
hiker
bff3989bfc Implemented singleton. 2017-04-23 23:36:02 +10:00
hiker
1d7aa7a2f5 Use reference instead of copy. 2017-04-23 00:48:31 +10:00
hiker
de795b766a Made the Steam class in STK and SSM more compatible. Improve error
handling, and cache frequently used values (e.g. name and id).
2017-04-23 00:32:47 +10:00
hiker
eb4989c2ef Renamte ssh to Steam. 2017-04-22 23:35:18 +10:00
hiker
9760826252 Very first steam test case - gets username and id, then downloads avatar. 2017-04-22 00:31:06 +10:00
17 changed files with 874 additions and 26 deletions

View File

@ -14,6 +14,12 @@
<spacer height="15" width="10"/>
<div width="90%" align="center" layout="vertical-row" proportion="1">
<div width="100%" height="fit" layout="horizontal-row" >
<checkbox width="fit" id="steam" I18N="In the user screen" text_align="left"/>
<spacer width="10"/>
<label proportion="1" id="label-steam" height="100%" text_align="left"
I18N="In the user screen" text="Is a Steam account"/>
</div>
<div width="100%" height="fit" layout="horizontal-row" >
<checkbox width="fit" id="online" I18N="In the user screen" text_align="left"/>
<spacer width="10"/>

View File

@ -23,6 +23,12 @@
<spacer height="15" width="10"/>
<div width="90%" align="center" layout="vertical-row" height="fit">
<div width="100%" height="fit" layout="horizontal-row" >
<checkbox width="fit" id="steam" I18N="In the user screen" text_align="left"/>
<spacer width="10"/>
<label proportion="1" id="label-steam" height="100%" text_align="left"
I18N="In the user screen" text="Is a Steam account"/>
</div>
<div width="100%" height="fit" layout="horizontal-row" >
<checkbox width="fit" id="online" I18N="In the user screen" text_align="left"/>
<spacer width="10"/>

View File

@ -162,6 +162,11 @@
work anymore - so for now don't enable this. -->
<networking enable="false"/>
<!-- Steam related settings:
ask-for-internet - If true the user on first start will be asked
for permission to connect to stk servers. -->
<steam ask-for-internet="false"/>
<!-- The field od views for 1-4 player split screen. fov-3 is
actually not used (since 3 player split screen uses the
same layout as 4 player split screen) -->

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -25,6 +25,7 @@
#include "io/utf_writer.hpp"
#include "io/xml_node.hpp"
#include "online/online_player_profile.hpp"
#include "online/steam.hpp"
#include "utils/log.hpp"
#include "utils/translation.hpp"
@ -209,17 +210,126 @@ void PlayerManager::load()
PlayerProfile *player = new Online::OnlinePlayerProfile(player_xml);
m_all_players.push_back(player);
}
m_current_player = NULL;
const XMLNode *current = m_player_data->getNode("current");
if(current)
stringw current_name;
if (current)
{
stringw name;
current->getAndDecode("player", &name);
m_current_player = getPlayer(name);
current->getAndDecode("player", &current_name);
}
if (!checkSteamAccount(current_name))
{
m_current_player = getPlayer(current_name);
} // if current
} // load
// ----------------------------------------------------------------------------
/** This functions checks if a stk user exists with the same steam id as the
* current steam user, and if so switches to that player (ignoring that stk
* might have a different 'current' player). If no stk account with same
* steam id is found, it then also checks if a non-steam stk account with the
* same name (case independent) exists, and if so, switches that account
* to be connect to the current steam user.
* If no matching stk account can be found, do nothing - the user then would
* have to rename an existing account or create a new account to be connected
* to the steam id.
* \return True if a matching steam account was found (which means that the
* current user in stk is ignored).
*/
bool PlayerManager::checkSteamAccount(const irr::core::stringw &current_name)
{
if (!Steam::get()->isSteamAvailable())
return false;
// First check for an stk account with same steam id:
const stringw &steam_name = Steam::get()->getUserNameWchar();
const std::string &steam_id = Steam::get()->getSteamID();
for (unsigned int i = 0; i < m_all_players.size(); i++)
{
if (m_all_players[i].isSteamUser() &&
m_all_players[i].getSteamUserID() == steam_id)
{
// Check if the previously (current) name is different from
// the steam name, and if so print a warning, but continue
// with switching to the correct steam user id.
if (!current_name.equals_ignore_case(steam_name))
{
Log::warn("PlayerManager",
"Switching from previous user '%ls' to '%ls'.",
current_name.c_str(), steam_name.c_str() );
} // if steam and current name are different
m_current_player = getPlayer(i);
// Update the steam name in case that the user did a rename
m_current_player->setToCurrentSteamUser();
return true;
} // same steam user id
} // for i in m_all_players
// Now check for an existing account name that matches the steam name.
// This is only done on the first time an STK version is running that
// connects to steam. From then on the user must go to the user screen
// to connect or disconnect STK accounts to steam accounts.
if (UserConfigParams::m_steam_first_start)
{
for (unsigned int i = 0; i < m_all_players.size(); i++)
{
// Ignore existing steam users, they must have a different id
// (otherwise they would have been picked in the previous loop)
if (m_all_players[i].isSteamUser()) continue;
stringw name = m_all_players[i].getName();
if (name.equals_ignore_case(steam_name))
{
Log::warn("PlayerManager",
"Connecting '%ls' to current steam account '%ls'.",
current_name.c_str(), steam_name.c_str());
m_current_player = getPlayer(i);
m_current_player->setToCurrentSteamUser();
return true;
} // if steam and current name are different
} // for i in m_all_players
// No matching existing user found. Last try: if there is only
// one stk account, connect it to the current steam account:
if (m_all_players.size() == 1)
{
m_current_player = getPlayer(0);
m_current_player->setToCurrentSteamUser();
Log::warn("PlayerManager",
"Connecting only account '%ls' to current steam account '%ls'.",
m_current_player->getName().c_str(), steam_name.c_str());
return true;
}
} // If first time steam version is started.
// Otherwise now current user is NULL: if this is the first time that
// stk starts under steam (and an stk account existed, otherwise this
// function is not called), main() will bring up the user login screen
// where the user can connect and disconnect stk- and steam-accounts.
return false;
} // checkSteamAccount
// ----------------------------------------------------------------------------
/** Disconnects all account(s) that uses the given steam id from steam.
*/
void PlayerManager::disconnectSteamAccount(const std::string &steam_id)
{
for (unsigned int i = 0; i < m_all_players.size(); i++)
{
PlayerProfile &player = m_all_players[i];
if (player.isSteamUser() && player.getSteamUserID() == steam_id)
{
player.clearSteamData();
} // if right steam user
} // for i in m_all_players
} // disconnectSteamAccount
// ----------------------------------------------------------------------------
/** The 2nd loading stage. During this stage achievements and story mode
* data is initialised for each player. In case of existing player (i.e. not

View File

@ -66,6 +66,7 @@ private:
const XMLNode *m_player_data;
void load();
bool checkSteamAccount(const irr::core::stringw &current_name);
PlayerManager();
~PlayerManager();
@ -101,6 +102,8 @@ public:
static void setUserDetails(Online::HTTPRequest *request,
const std::string &action,
const std::string &php_name = "");
void disconnectSteamAccount(const std::string &steam_id);
static unsigned int getCurrentOnlineId();
static bool isCurrentLoggedIn();
static Online::OnlineProfile* getCurrentOnlineProfile();

View File

@ -24,6 +24,7 @@
#include "karts/kart_properties.hpp"
#include "karts/kart_properties_manager.hpp"
#include "online/online_player_profile.hpp"
#include "online/steam.hpp"
#include "io/xml_node.hpp"
#include "io/utf_writer.hpp"
#include "utils/string_utils.hpp"
@ -38,6 +39,17 @@ PlayerProfile::PlayerProfile(const core::stringw& name, bool is_guest)
#ifdef DEBUG
m_magic_number = 0xABCD1234;
#endif
if(Steam::get()->isSteamAvailable() &&
name == Steam::get()->getUserNameWchar() )
{
m_steam_id = Steam::get()->getSteamID();
m_steam_name = Steam::get()->getUserNameWchar();
}
else
{
m_steam_id = "";
m_steam_name = "";
}
m_local_name = name;
m_is_guest_account = is_guest;
m_use_frequency = is_guest ? -1 : 0;
@ -68,6 +80,8 @@ PlayerProfile::PlayerProfile(const core::stringw& name, bool is_guest)
*/
PlayerProfile::PlayerProfile(const XMLNode* node)
{
m_steam_id = "";
m_steam_name = "";
m_saved_session = false;
m_saved_token = "";
m_saved_user_id = 0;
@ -78,7 +92,9 @@ PlayerProfile::PlayerProfile(const XMLNode* node)
m_achievements_status = NULL;
m_icon_filename = "";
node->getAndDecode("name", &m_local_name);
node->getAndDecode("name", &m_local_name );
node->get("steam-id", &m_steam_id );
node->get("steam-name", &m_steam_name );
node->get("guest", &m_is_guest_account );
node->get("use-frequency", &m_use_frequency );
node->get("unique-id", &m_unique_id );
@ -154,6 +170,23 @@ void PlayerProfile::addIcon()
if (m_icon_filename.size() > 0 || isGuestAccount())
return;
if (isSteamUser())
{
std::ostringstream out;
out << m_unique_id << ".png";
std::string full_path = file_manager->getUserConfigFile(out.str());
Steam::get()->saveAvatarAs(full_path);
if (file_manager->fileExists(full_path))
{
m_icon_filename = out.str();
}
else
{
m_icon_filename = "";
}
return;
}
int n = (m_unique_id + kart_properties_manager->getKartId("tux") - 1)
% kart_properties_manager->getNumberOfKarts();
@ -202,6 +235,9 @@ void PlayerProfile::save(UTFWriter &out)
<< L"\" guest=\"" << m_is_guest_account
<< L"\" use-frequency=\"" << m_use_frequency << L"\"\n";
out << L" steam-id=\"" << m_steam_id
<< L"\" steam-name=\"" << m_steam_name << L"\"\n";
out << L" icon-filename=\"" << m_icon_filename << L"\"\n";
out << L" unique-id=\"" << m_unique_id
@ -266,6 +302,30 @@ void PlayerProfile::raceFinished()
m_achievements_status->onRaceEnd();
} // raceFinished
//------------------------------------------------------------------------------
/** Connects this user to the currently logged in Steam account. It saves the
* unique steam id and the (not necessarily unique) steam user name.
*/
void PlayerProfile::setToCurrentSteamUser()
{
if (!Steam::get()->isSteamAvailable())
{
Log::error("PlayerProfile",
"Can not set to current steam user, steam is not available.");
return;
}
m_steam_id = Steam::get()->getSteamID();
m_steam_name = Steam::get()->getUserNameWchar();
} // setToCurrentSteamUser
//------------------------------------------------------------------------------
/** Disconnects this player from its steam account.
*/
void PlayerProfile::clearSteamData()
{
m_steam_id = "";
m_steam_name = "";
} // clearSteamData
//------------------------------------------------------------------------------
/** Comparison used to sort players.
*/

View File

@ -97,6 +97,15 @@ private:
/** The token of the saved session. */
std::string m_saved_token;
/** If the account is a steam account, this stores the steam user id.
* Otherwise it is empty. A steam account can only be selected if
* the user is logged into steam! */
std::string m_steam_id;
/** Name of the corresponding steam account. Empty if this is
* not a steam account. */
core::stringw m_steam_name;
/** The online user name used last (empty if not used online). */
core::stringw m_last_online_name;
@ -125,6 +134,8 @@ public:
void saveSession(int user_id, const std::string &token);
void clearSession(bool save=true);
void addIcon();
void setToCurrentSteamUser();
void clearSteamData();
/** Abstract virtual classes, to be implemented by the OnlinePlayer. */
virtual void setUserDetails(Online::HTTPRequest *request,
@ -300,6 +311,16 @@ public:
/** Sets if this player was logged in last time it was used. */
void setRememberPassword(bool b) { m_remember_password = b; }
// ------------------------------------------------------------------------
/** Returns if this account is a steam account. */
bool isSteamUser() const { return m_steam_id != ""; }
// ------------------------------------------------------------------------
/** Returns the steam user id of this player ("" if not a steam account).*/
const std::string &getSteamUserID() const { return m_steam_id; }
// ------------------------------------------------------------------------
/** Returns the steam name of the connected steam account (or "" if
* this account is not a steam account. */
const core::stringw &getSteamName() const { return m_steam_name; }
// ------------------------------------------------------------------------
}; // class PlayerProfile
#endif

View File

@ -181,6 +181,7 @@ void STKConfig::init_defaults()
m_disable_steer_while_unskid = false;
m_camera_follow_skid = false;
m_cutscene_fov = 0.61f;
m_steam_ask_for_internet = false;
m_score_increase.clear();
m_leader_intervals.clear();
@ -274,6 +275,11 @@ void STKConfig::getAllData(const XMLNode * root)
camera->get("cutscene-fov", &m_cutscene_fov);
}
if (const XMLNode *steam = root->getNode("steam"))
{
steam->get("ask-for-internet", &m_steam_ask_for_internet);
}
if (const XMLNode *music_node = root->getNode("music"))
{
std::string title_music;

View File

@ -152,6 +152,10 @@ public:
float m_cutscene_fov;
/** True if the internet question should still be asked when
* a steam connection is make. */
bool m_steam_ask_for_internet;
/** Lists of TTF files used in STK. */
std::vector<std::string> m_normal_ttf;
std::vector<std::string> m_digit_ttf;

View File

@ -914,6 +914,16 @@ namespace UserConfigParams
&m_online_group,
"Version of the server API to use."));
// ---- Online gameplay related
PARAM_PREFIX GroupUserConfigParam m_steam_group
PARAM_DEFAULT(GroupUserConfigParam("Steam",
"Everything related to steam."));
PARAM_PREFIX BoolUserConfigParam m_steam_first_start
PARAM_DEFAULT(BoolUserConfigParam(true,
"steam-first-start",
&m_steam_group,
"If this is the first time the STK steam version was started"));
// ---- Addon server related entries
PARAM_PREFIX GroupUserConfigParam m_addon_group

View File

@ -215,6 +215,7 @@
#include "network/protocols/get_public_address.hpp"
#include "online/profile_manager.hpp"
#include "online/request_manager.hpp"
#include "online/steam.hpp"
#include "race/grand_prix_manager.hpp"
#include "race/highscore_manager.hpp"
#include "race/history.hpp"
@ -1458,6 +1459,16 @@ void askForInternetPermission()
Online::RequestManager::IPERM_NOT_ASKED)
return;
// If a steam connection is available, we could skip the
// internet permission question (depending on settings).
if (Steam::get()->isSteamAvailable() &&
!stk_config->m_steam_ask_for_internet)
{
UserConfigParams::m_internet_status =
Online::RequestManager::IPERM_ALLOWED;
return;
}
class ConfirmServer :
public MessageDialog::IConfirmDialogListener
{
@ -1515,6 +1526,8 @@ int main(int argc, char *argv[] )
{
CommandLine::init(argc, argv);
Steam::create();
CrashReporting::installHandlers();
srand(( unsigned ) time( 0 ));
@ -1688,23 +1701,43 @@ int main(int argc, char *argv[] )
// so we immediately start the main menu (unless it was requested
// to always show the login screen). Otherwise show the login
// screen first.
if(PlayerManager::getCurrentPlayer() && !
UserConfigParams::m_always_show_login_screen)
if(PlayerManager::getCurrentPlayer() &&
!UserConfigParams::m_always_show_login_screen)
{
MainMenuScreen::getInstance()->push();
// If this is the first time that steam starts, and at startup
// we could not detect a 'matching' stk account to the steam
// account, bring up the user screen (on top of the main menu),
// so that the user can manually connect an stk account with
// a steam account.
if (UserConfigParams::m_steam_first_start &&
!PlayerManager::getCurrentPlayer()->isSteamUser())
{
UserScreen::getInstance()->push();
}
}
else
{
UserScreen::getInstance()->push();
// If there is no player, push the RegisterScreen on top of
// the login screen. This way on first start players are
// forced to create a player.
if (PlayerManager::get()->getNumPlayers() == 0)
if (Steam::get()->isSteamAvailable())
{
RegisterScreen::getInstance()->push();
RegisterScreen::getInstance()->setParent(UserScreen::getInstance());
PlayerProfile *steam_player = PlayerManager::get()
->addNewPlayer(Steam::get()->getUserNameWchar());
PlayerManager::get()->setCurrentPlayer(steam_player);
MainMenuScreen::getInstance()->push();
}
}
else
{
UserScreen::getInstance()->push();
// If there is no player, push the RegisterScreen on top of
// the login screen. This way on first start players are
// forced to create a player.
if (PlayerManager::get()->getNumPlayers() == 0)
{
RegisterScreen::getInstance()->push();
RegisterScreen::getInstance()->setParent(UserScreen::getInstance());
}
} // !steam->isAvailable
} // no player available
#ifdef ENABLE_WIIUSE
// Show a dialog to allow connection of wiimotes. */
if(WiimoteManager::isEnabled())
@ -1778,7 +1811,7 @@ int main(int argc, char *argv[] )
}
/* Program closing...*/
UserConfigParams::m_steam_first_start = false;
#ifdef ENABLE_WIIUSE
if(wiimote_manager)
delete wiimote_manager;
@ -1792,6 +1825,7 @@ int main(int argc, char *argv[] )
if(NetworkConfig::get()->isNetworking() && STKHost::existHost())
STKHost::get()->abort();
Steam::destroy();
cleanSuperTuxKart();
#ifdef DEBUG

389
src/online/steam.cpp Normal file
View File

@ -0,0 +1,389 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 Joerg Henrichs
//
// This program 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 3
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "online/steam.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
#ifdef WIN32
# include <windows.h>
#else
# include <iostream>
# include <unistd.h>
#endif
Steam * Steam::m_steam = NULL;
// ----------------------------------------------------------------------------
Steam::Steam()
{
m_steam_available = false;
// Create the child process SSM to mamage steam:
if (!createChildProcess())
{
Log::error("Steam", "Could not start ssm.exe");
return;
}
Log::info("Steam", "Starting steam manager");
std::string s = sendCommand("init");
if (s != "1")
{
Log::error("Steam", "Could not initialise Steam API.");
return;
}
s = sendCommand("name");
m_user_name = decodeString(s);
if (m_user_name == "")
{
Log::error("Steam", "Can not get Steam user name.");
return;
}
m_user_name_wchar = StringUtils::utf8ToWide(m_user_name);
s = sendCommand("id");
m_steam_id = decodeString(s);
if (m_steam_id== "")
{
Log::error("Steam", "Can not get Steam id.");
return;
}
m_steam_available = true;
} // Steam
// ----------------------------------------------------------------------------
/** Terminates the child processes and shuts down the Steam API.
*/
Steam::~Steam()
{
std::string s = sendCommand("quit");
if (s != "quit")
{
Log::error("Steam", "Could not shutdown Steam process properly");
}
#ifdef LINUX
close(m_child_stdin_write);
close(m_child_stdout_read);
#endif
Log::info("Steam", "Shutting down steam manager");
} // ~Steam
// ----------------------------------------------------------------------------
/** Starts ssm.exe (on windows) or ssm (other platforms) as a child process
* and sets up communication via pipes.
* \return True if the child process creation was successful.
*/
#ifdef WIN32
bool Steam::createChildProcess()
{
// Based on: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
SECURITY_ATTRIBUTES sec_attr;
// Set the bInheritHandle flag so pipe handles are inherited.
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&m_child_stdout_read, &m_child_stdout_write, &sec_attr, 0))
{
Log::error("Steam", "Error creating StdoutRd CreatePipe");
return false;
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(m_child_stdout_read, HANDLE_FLAG_INHERIT, 0))
{
Log::error("Steam", "Stdout SetHandleInformation");
return false;
}
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&m_child_stdin_read, &m_child_stdin_write, &sec_attr, 0))
{
Log::error("Steam", "Stdin CreatePipe");
return false;
}
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(m_child_stdin_write, HANDLE_FLAG_INHERIT, 0))
{
Log::error("Steam", "Stdin SetHandleInformation");
return false;
}
TCHAR command_line[] = TEXT("ssm.exe");
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFO structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = m_child_stdout_write;
siStartInfo.hStdOutput = m_child_stdout_write;
siStartInfo.hStdInput = m_child_stdin_read;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bool success = CreateProcess(NULL,
command_line, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo) != 0; // receives PROCESS_INFORMATION
if (!success)
{
return false;
}
// Close handles to the child process and its primary thread.
// Some applications might keep these handles to monitor the status
// of the child process, for example.
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
return true;
} // createChildProcess - windows version
#else // linux and osx
bool Steam::createChildProcess()
{
const int PIPE_READ=0;
const int PIPE_WRITE=1;
int stdin_pipe[2];
int stdout_pipe[2];
int child;
if (pipe(stdin_pipe) < 0)
{
Log::error("Steam", "Can't allocate pipe for input redirection.");
return -1;
}
if (pipe(stdout_pipe) < 0)
{
close(stdin_pipe[PIPE_READ]);
close(stdin_pipe[PIPE_WRITE]);
Log::error("Steam", "allocating pipe for child output redirect");
return false;
}
child = fork();
if (child == 0)
{
// Child process:
Log::info("Steam", "Child process started.");
// redirect stdin
if (dup2(stdin_pipe[PIPE_READ], STDIN_FILENO) == -1)
{
Log::error("Steam", "Redirecting stdin");
return false;
}
// redirect stdout
if (dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO) == -1)
{
Log::error("Steam", "Redirecting stdout");
return false;
}
// all these are for use by parent only
close(stdin_pipe[PIPE_READ]);
close(stdin_pipe[PIPE_WRITE]);
close(stdout_pipe[PIPE_READ]);
close(stdout_pipe[PIPE_WRITE]);
// run child process image
execl("./ssm", "", NULL);
Log::error("Steam", "Error in execl: errnp %d", errno);
// if we get here at all, an error occurred, but we are in the child
// process, so just exit
perror("Steam: execl error");
exit(-1);
}
else if (child > 0)
{
// parent continues here
// close unused file descriptors, these are for child only
close(stdin_pipe[PIPE_READ]);
close(stdout_pipe[PIPE_WRITE]);
m_child_stdin_write = stdin_pipe[PIPE_WRITE];
m_child_stdout_read = stdout_pipe[PIPE_READ];
}
else // child < 0
{
// failed to create child
close(stdin_pipe[PIPE_READ]);
close(stdin_pipe[PIPE_WRITE]);
close(stdout_pipe[PIPE_READ]);
close(stdout_pipe[PIPE_WRITE]);
return false;
}
return true;
} // createChildProcess
#endif
// ----------------------------------------------------------------------------
/** Reads a command from the input pipe.
*/
std::string Steam::getLine()
{
#define BUFSIZE 1024
char buffer[BUFSIZE];
#ifdef WIN32
DWORD bytes_read;
// Read from pipe that is the standard output for child process.
bool success = ReadFile(m_child_stdout_read, buffer, BUFSIZE-1,
&bytes_read, NULL)!=0;
if (success && bytes_read < BUFSIZE)
{
buffer[bytes_read] = 0;
std::string s = buffer;
return s;
}
#else
//std::string s;
//std::getline(std::cin, s);
//return s;
int bytes_read = read(m_child_stdout_read, buffer, BUFSIZE-1);
if(bytes_read>0)
{
buffer[bytes_read] = 0;
std::string s = buffer;
return s;
}
#endif
return std::string("");
} // getLine
// ----------------------------------------------------------------------------
/** Sends a command to the SSM via a pipe, and reads the answer.
* \return Answer from SSM.
*/
std::string Steam::sendCommand(const std::string &command)
{
#ifdef WIN32
// Write to the pipe that is the standard input for a child process.
// Data is written to the pipe's buffers, so it is not necessary to wait
// until the child process is running before writing data.
DWORD bytes_written;
bool success = WriteFile(m_child_stdin_write, command.c_str(),
command.size()+1, &bytes_written, NULL ) != 0;
#else
write(m_child_stdin_write, (command+"\n").c_str(), command.size()+1);
#endif
return getLine();
return std::string("");
} // sendCommand
// ----------------------------------------------------------------------------
/** All answer strings from 'SSM' are in the form: "length string", i.e. the
* length of the string, followed by a space and then the actual strings.
* This allows for checking on some potential problems (e.g. if a pipe should
* only send part of the answer string - todo: handle this problem instead of
* ignoring it.
*/
std::string Steam::decodeString(const std::string &s)
{
std::vector<std::string> l = StringUtils::split(s, ' ');
if (l.size() != 2) return "INVALID ANSWER - wrong number of fields";
int n;
StringUtils::fromString(l[0], n);
if (n != (int)l[1].size()) return "INVALID ANSWER - incorrect length";
return l[1];
} // decodeString
// ----------------------------------------------------------------------------
/** Returns the steam user name. SSM returns 'N name" where N is
* the length of the name.
*/
const std::string& Steam::getUserName()
{
assert(m_steam_available);
return m_user_name;
} // getUserName
// ----------------------------------------------------------------------------
/** Returns a unique id (string) from steam. SSM returns 'N ID" where N is
* the length of the ID.
*/
const std::string& Steam::getSteamID()
{
assert(m_steam_available);
return m_steam_id;
} // getSteamID
// ----------------------------------------------------------------------------
/** Returns a std::vector with the names of all friends. SSM returns a first
* line with the number of friends, then one friend in a line.
*/
std::vector<std::string> Steam::getFriends()
{
std::string s = sendCommand("friends");
int num_friends;
StringUtils::fromString(s, num_friends);
std::vector<std::string> result;
for (int i = 0; i < num_friends; i++)
{
std::string f = getLine();
result.push_back(decodeString(f));
}
return result;
}
// ----------------------------------------------------------------------------
/** Instructs the SSM to save the avatar of the user with the specified
* filename. Note that the avatar is always saved in png format (independent
* on what is specified as filename).
*/
int Steam::saveAvatarAs(const std::string &filename)
{
std::string s = sendCommand("avatar");
if(s=="filename")
s=sendCommand(filename);
return s == "done";
} // saveAvatarAs

123
src/online/steam.hpp Normal file
View File

@ -0,0 +1,123 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 Joerg Henrichs
//
// This program 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 3
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_STEAM_HPP
#define HEADER_STEAM_HPP
#include "irrString.h"
#ifdef WIN32
# include <windows.h>
#endif
#include <assert.h>
#include <string>
#include <vector>
/** This class provides a simple interface to the SteamWorks API. Due to
* our current license, the SteamWorks lib can not be linked with STK.
* So this wrapper class actually starts a seprate process (ssm, see
* https://github.com/hiker/steam-synchron-manager) and communicats
* with the SSM using pipes.
*/
class Steam
{
private:
/** Singleton pointer. */
static Steam *m_steam;
/** True if a connection to steam was made successfully. */
bool m_steam_available;
/** Steam user name. Only defined if m_steam_available. */
std::string m_user_name;
/** User name as irr::stringw (wchar), which is used in STK. */
irr::core::stringw m_user_name_wchar;
/** Unique steam id. */
std::string m_steam_id;
#ifdef WIN32
// Various handles for the window pipes
HANDLE m_child_stdin_read;
HANDLE m_child_stdin_write;
HANDLE m_child_stdout_read;
HANDLE m_child_stdout_write;
#else
int m_child_stdin_read;
int m_child_stdin_write;
int m_child_stdout_read;
int m_child_stdout_write;
#endif
bool createChildProcess();
std::string decodeString(const std::string &s);
std::string sendCommand(const std::string &command);
std::string getLine();
Steam();
~Steam();
public:
/** Creates a singleton. */
static void create()
{
assert(!m_steam);
m_steam = new Steam();
} // create;
// ------------------------------------------------------------------------
/** Returns the singleton pf the Steam class. */
static Steam *get()
{
assert(m_steam);
return m_steam;
} // get
// ------------------------------------------------------------------------
/** Destroys the singleton of the Steam class. */
static void destroy()
{
assert(m_steam);
delete m_steam;
m_steam = NULL;
} // destroy
// ------------------------------------------------------------------------
const std::string& getUserName();
const std::string& getSteamID();
int saveAvatarAs(const std::string &filename);
std::vector<std::string> getFriends();
// ------------------------------------------------------------------------
/** Returns the user name as wide string. */
const irr::core::stringw& getUserNameWchar()
{
assert(m_steam_available);
return m_user_name_wchar;
} // getUserNameWchar
// ------------------------------------------------------------------------
/** Returns true if the connection to the Steam API was successful, i.e.
* connection to steam worked, and SteamWorks API could be initialised. */
bool isSteamAvailable() const { return m_steam_available; }
}; // class Steam
#endif // HEADER_STEAM_HPP

View File

@ -24,6 +24,7 @@
#include "guiengine/widgets/label_widget.hpp"
#include "guiengine/widgets/ribbon_widget.hpp"
#include "guiengine/widgets/text_box_widget.hpp"
#include "online/steam.hpp"
#include "online/xml_request.hpp"
#include "states_screens/dialogs/registration_dialog.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
@ -88,7 +89,9 @@ void RegisterScreen::init()
}
else if (PlayerManager::get()->getNumPlayers() == 0)
{
if (getenv("USERNAME") != NULL) // for windows
if (Steam::get()->isSteamAvailable())
username = StringUtils::utf8ToWide( Steam::get()->getUserName());
else if (getenv("USERNAME") != NULL) // for windows
username = getenv("USERNAME");
else if (getenv("USER") != NULL) // Linux, Macs
username = getenv("USER");

View File

@ -26,6 +26,7 @@
#include "guiengine/widgets/label_widget.hpp"
#include "guiengine/widgets/list_widget.hpp"
#include "guiengine/widgets/text_box_widget.hpp"
#include "online/steam.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
#include "states_screens/dialogs/recovery_dialog.hpp"
#include "states_screens/main_menu_screen.hpp"
@ -55,6 +56,10 @@ BaseUserScreen::BaseUserScreen(const std::string &name) : Screen(name.c_str())
void BaseUserScreen::loadedFromFile()
{
m_steam_cb = getWidget<CheckBoxWidget>("steam");
assert(m_steam_cb);
m_steam_label = getWidget<LabelWidget>("label-steam");
assert(m_steam_label);
m_online_cb = getWidget<CheckBoxWidget>("online");
assert(m_online_cb);
m_username_tb = getWidget<TextBoxWidget >("username");
@ -184,10 +189,10 @@ void BaseUserScreen::tearDown()
// ----------------------------------------------------------------------------
EventPropagation BaseUserScreen::filterActions(PlayerAction action,
int deviceID,
const unsigned int value,
Input::InputType type,
int playerId)
int deviceID,
const unsigned int value,
Input::InputType type,
int playerId )
{
if (action == PA_MENU_SELECT)
{
@ -218,6 +223,27 @@ void BaseUserScreen::selectUser(int index)
m_players->setSelection(StringUtils::toString(index), PLAYER_ID_GAME_MASTER,
focus_it);
m_steam_cb->setVisible (Steam::get()->isSteamAvailable());
m_steam_label->setVisible(Steam::get()->isSteamAvailable());
if (Steam::get()->isSteamAvailable())
{
m_steam_cb->setState(profile->isSteamUser());
if (profile->isSteamUser())
{
//I18N: Checkbox text shown when this player is a steam user
// with the name displayed in ().
core::stringw label = _("Is Steam user (%s)",
profile->getSteamName() );
m_steam_label->setText(label, true);
}
else
{
//I18N: Checbox text shown when the user is not a steam user
// but can select the check box in order to become one
m_steam_label->setText(_("Is Steam user"), true);
} // no steam user
} // If steam is available
if (!m_new_registered_data)
m_username_tb->setText(profile->getLastOnlineName(true/*ignoreRTL*/));
@ -300,9 +326,8 @@ void BaseUserScreen::makeEntryFieldsVisible()
// ----------------------------------------------------------------------------
/** Called when the user selects anything on the screen.
*/
void BaseUserScreen::eventCallback(Widget* widget,
const std::string& name,
const int player_id)
void BaseUserScreen::eventCallback(Widget* widget, const std::string& name,
const int player_id)
{
// Clean any error message still shown
m_info_widget->setText("", true);
@ -389,11 +414,47 @@ void BaseUserScreen::eventCallback(Widget* widget,
{
StateManager::get()->escapePressed();
}
else if (name == "steam")
{
handleSteamAccount(player_id);
}
return;
} // eventCallback
// ----------------------------------------------------------------------------
/** Called when an STK account is connected or disconnected to a steam
* account.
* \param player_id The index of the currently selected player.
*/
void BaseUserScreen::handleSteamAccount(int player_id)
{
// Shouldn't happen, this option is only shown when
// steam is available
if (!Steam::get()->isSteamAvailable()) return;
const std::string &s_index = getWidget<DynamicRibbonWidget>("players")
->getSelectionIDString(player_id);
if (s_index == "") return; // can happen if the list is empty
unsigned int id = 0;
StringUtils::fromString(s_index, id);
PlayerProfile *profile = PlayerManager::get()->getPlayer(id);
if (m_steam_cb->getState())
{
// We need to disconnect any other STK account which uses the
// same steam account
PlayerManager::get()->disconnectSteamAccount(Steam::get()->getSteamID());
profile->setToCurrentSteamUser();
}
else
profile->clearSteamData();
selectUser(id);
} // handleSteamAccount
// ----------------------------------------------------------------------------
/** Closes the BaseUserScreen, and makes sure that the right screen is displayed
* next.

View File

@ -65,6 +65,12 @@ private:
* display more meaningful sign-out message. */
irr::core::stringw m_sign_in_name;
/** Online check box. */
GUIEngine::CheckBoxWidget *m_steam_cb;
/** Label field for steam details. */
GUIEngine::LabelWidget * m_steam_label;
/** Online check box. */
GUIEngine::CheckBoxWidget *m_online_cb;
@ -98,6 +104,7 @@ private:
void closeScreen();
void deletePlayer();
void doDeletePlayer();
void handleSteamAccount(int player_id);
PlayerProfile* getSelectedPlayer();
virtual void onUpdate(float dt) OVERRIDE;