summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authormadmaxoft <github@xoft.cz>2014-08-03 21:32:20 +0200
committermadmaxoft <github@xoft.cz>2014-08-03 22:04:51 +0200
commit21f52676f3848d58ff1e4eb511c691d4a4ed824b (patch)
treee0d2c30fff958dbf73ada22f2c64936597441f14 /src
parentTrailing whitespace fix. (diff)
downloadcuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar.gz
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar.bz2
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar.lz
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar.xz
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.tar.zst
cuberite-21f52676f3848d58ff1e4eb511c691d4a4ed824b.zip
Diffstat (limited to '')
-rw-r--r--src/Bindings/ManualBindings.cpp98
-rw-r--r--src/Protocol/Authenticator.cpp4
-rw-r--r--src/Protocol/MojangAPI.cpp383
-rw-r--r--src/Protocol/MojangAPI.h97
-rw-r--r--src/Root.h2
5 files changed, 522 insertions, 62 deletions
diff --git a/src/Bindings/ManualBindings.cpp b/src/Bindings/ManualBindings.cpp
index 34f0d7e30..042ffb19e 100644
--- a/src/Bindings/ManualBindings.cpp
+++ b/src/Bindings/ManualBindings.cpp
@@ -2158,6 +2158,99 @@ static int tolua_cClientHandle_SendPluginMessage(lua_State * L)
+static int tolua_cMojangAPI_AddPlayerNameToUUIDMapping(lua_State * L)
+{
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cMojangAPI") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamString(3) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ // Retrieve the parameters:
+ AString UUID, PlayerName;
+ S.GetStackValue(2, PlayerName);
+ S.GetStackValue(3, UUID);
+
+ // Store in the cache:
+ cRoot::Get()->GetMojangAPI().AddPlayerNameToUUIDMapping(PlayerName, UUID);
+ return 0;
+}
+
+
+
+
+
+static int tolua_cMojangAPI_GetPlayerNameFromUUID(lua_State * L)
+{
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cMojangAPI") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ AString UUID;
+ S.GetStackValue(2, UUID);
+
+ // If the UseOnlyCached param was given, read it; default to false
+ bool ShouldUseCacheOnly = false;
+ if (lua_gettop(L) == 3)
+ {
+ ShouldUseCacheOnly = (lua_toboolean(L, 3) != 0);
+ lua_pop(L, 1);
+ }
+
+ // Return the PlayerName:
+ AString PlayerName = cRoot::Get()->GetMojangAPI().GetPlayerNameFromUUID(UUID, ShouldUseCacheOnly);
+ S.Push(PlayerName);
+ return 1;
+}
+
+
+
+
+
+static int tolua_cMojangAPI_GetUUIDFromPlayerName(lua_State * L)
+{
+ cLuaState S(L);
+ if (
+ !S.CheckParamUserTable(1, "cMojangAPI") ||
+ !S.CheckParamString(2) ||
+ !S.CheckParamEnd(4)
+ )
+ {
+ return 0;
+ }
+
+ AString PlayerName;
+ S.GetStackValue(2, PlayerName);
+
+ // If the UseOnlyCached param was given, read it; default to false
+ bool ShouldUseCacheOnly = false;
+ if (lua_gettop(L) == 3)
+ {
+ ShouldUseCacheOnly = (lua_toboolean(L, 3) != 0);
+ lua_pop(L, 1);
+ }
+
+ // Return the UUID:
+ AString UUID = cRoot::Get()->GetMojangAPI().GetUUIDFromPlayerName(PlayerName, ShouldUseCacheOnly);
+ S.Push(UUID);
+ return 1;
+}
+
+
+
+
+
static int tolua_cMojangAPI_GetUUIDsFromPlayerNames(lua_State * L)
{
cLuaState S(L);
@@ -3158,7 +3251,10 @@ void ManualBindings::Bind(lua_State * tolua_S)
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cMojangAPI");
- tolua_function(tolua_S, "GetUUIDsFromPlayerNames", tolua_cMojangAPI_GetUUIDsFromPlayerNames);
+ tolua_function(tolua_S, "AddPlayerNameToUUIDMapping", tolua_cMojangAPI_AddPlayerNameToUUIDMapping);
+ tolua_function(tolua_S, "GetPlayerNameFromUUID", tolua_cMojangAPI_GetPlayerNameFromUUID);
+ tolua_function(tolua_S, "GetUUIDFromPlayerName", tolua_cMojangAPI_GetUUIDFromPlayerName);
+ tolua_function(tolua_S, "GetUUIDsFromPlayerNames", tolua_cMojangAPI_GetUUIDsFromPlayerNames);
tolua_endmodule(tolua_S);
tolua_beginmodule(tolua_S, "cItemGrid");
diff --git a/src/Protocol/Authenticator.cpp b/src/Protocol/Authenticator.cpp
index 160564d51..984000795 100644
--- a/src/Protocol/Authenticator.cpp
+++ b/src/Protocol/Authenticator.cpp
@@ -191,8 +191,8 @@ bool cAuthenticator::AuthWithYggdrasil(AString & a_UserName, const AString & a_S
a_UUID = cMojangAPI::MakeUUIDShort(root.get("id", "").asString());
a_Properties = root["properties"];
- // Store the player's UUID in the NameToUUID map in MojangAPI:
- cRoot::Get()->GetMojangAPI().AddPlayerNameToUUIDMapping(a_UserName, a_UUID);
+ // Store the player's profile in the MojangAPI caches:
+ cRoot::Get()->GetMojangAPI().AddPlayerProfile(a_UserName, a_UUID, a_Properties);
return true;
}
diff --git a/src/Protocol/MojangAPI.cpp b/src/Protocol/MojangAPI.cpp
index f53df1cba..9fdf07380 100644
--- a/src/Protocol/MojangAPI.cpp
+++ b/src/Protocol/MojangAPI.cpp
@@ -25,8 +25,10 @@ const int MAX_PER_QUERY = 100;
-#define DEFAULT_NAME_TO_UUID_SERVER "api.mojang.com"
-#define DEFAULT_NAME_TO_UUID_ADDRESS "/profiles/minecraft"
+#define DEFAULT_NAME_TO_UUID_SERVER "api.mojang.com"
+#define DEFAULT_NAME_TO_UUID_ADDRESS "/profiles/minecraft"
+#define DEFAULT_UUID_TO_PROFILE_SERVER "sessionserver.mojang.com"
+#define DEFAULT_UUID_TO_PROFILE_ADDRESS "/session/minecraft/profile/%UUID%?unsigned=false"
@@ -97,11 +99,65 @@ static const AString & StarfieldCACert(void)
////////////////////////////////////////////////////////////////////////////////
+// cMojangAPI::sProfile:
+
+cMojangAPI::sProfile::sProfile(
+ const AString & a_PlayerName,
+ const AString & a_UUID,
+ const Json::Value & a_Properties,
+ Int64 a_DateTime
+) :
+ m_PlayerName(a_PlayerName),
+ m_UUID(a_UUID),
+ m_Textures(),
+ m_TexturesSignature(),
+ m_DateTime(a_DateTime)
+{
+ /*
+ Example a_Profile contents:
+ "properties":
+ [
+ {
+ "name": "textures",
+ "value": "eyJ0aW1lc3RhbXAiOjE0MDcwNzAzMjEyNzEsInByb2ZpbGVJZCI6ImIxY2FmMjQyMDJhODQxYTc4MDU1YTA3OWM0NjBlZWU3IiwicHJvZmlsZU5hbWUiOiJ4b2Z0IiwiaXNQdWJsaWMiOnRydWUsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNzc5YmFiZjVhNTg3Zjk0OGFkNjc0N2VhOTEyNzU0MjliNjg4Mjk1YWUzYzA3YmQwZTJmNWJmNGQwNTIifX19",
+ "signature": "XCty+jGEF39hEPrPhYNnCX087kPaoCjYruzYI/DS4nkL5hbjnkSM5Rh15hnUyv/FHhC8OF5rif3D1tQjtMI19KSVaXoUFXpbJM8/+PB8GDgEbX8Fc3u9nYkzOcM/xfxdYsFAdFhLQMkvase/BZLSuPhdy9DdI+TCrO7xuSTZfYmmwVuWo3w5gCY+mSIAnqltnOzaOOTcly75xvO0WYpVk7nJdnR2tvSi0wfrQPDrIg/uzhX7p0SnDqijmBU4QaNez/TNKiFxy69dAzt0RSotlQzqkDbyVKhhv9a4eY8h3pXi4UMftKEj4FAKczxLImkukJXuOn5NN15/Q+le0rJVBC60/xjKIVzltEsMN6qjWD0lQjey7WEL+4pGhCVuWY5KzuZjFvgqszuJTFz7lo+bcHiceldJtea8/fa02eTRObZvdLxbWC9ZfFY0IhpOVKfcLdno/ddDMNMQMi5kMrJ8MZZ/PcW1w5n7MMGWPGCla1kOaC55AL0QYSMGRVEZqgU9wXI5M7sHGZKGM4mWxkbEJYBkpI/p3GyxWgV6v33ZWlsz65TqlNrR1gCLaoFCm7Sif8NqPBZUAONHYon0roXhin/DyEanS93WV6i6FC1Wisscjq2AcvnOlgTo/5nN/1QsMbjNumuMGo37sqjRqlXoPb8zEUbAhhztYuJjEfQ2Rd8="
+ }
+ ]
+ */
+
+ // Parse the Textures and TexturesSignature from the Profile:
+ if (!a_Properties.isArray())
+ {
+ // Properties is not a valid array, bail out
+ return;
+ }
+ Json::UInt Size = a_Properties.size();
+ for (Json::UInt i = 0; i < Size; i++)
+ {
+ const Json::Value & Prop = a_Properties[i];
+ AString PropName = Prop.get("name", "").asString();
+ if (PropName != "textures")
+ {
+ continue;
+ }
+ m_Textures = Prop.get("value", "").asString();
+ m_TexturesSignature = Prop.get("signature", "").asString();
+ break;
+ } // for i - Properties[]
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
// cMojangAPI:
cMojangAPI::cMojangAPI(void) :
m_NameToUUIDServer(DEFAULT_NAME_TO_UUID_SERVER),
- m_NameToUUIDAddress(DEFAULT_NAME_TO_UUID_ADDRESS)
+ m_NameToUUIDAddress(DEFAULT_NAME_TO_UUID_ADDRESS),
+ m_UUIDToProfileServer(DEFAULT_UUID_TO_PROFILE_SERVER),
+ m_UUIDToProfileAddress(DEFAULT_UUID_TO_PROFILE_ADDRESS)
{
}
@@ -120,8 +176,10 @@ cMojangAPI::~cMojangAPI()
void cMojangAPI::Start(cIniFile & a_SettingsIni)
{
- m_NameToUUIDServer = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER);
- m_NameToUUIDAddress = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS);
+ m_NameToUUIDServer = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDServer", DEFAULT_NAME_TO_UUID_SERVER);
+ m_NameToUUIDAddress = a_SettingsIni.GetValueSet("MojangAPI", "NameToUUIDAddress", DEFAULT_NAME_TO_UUID_ADDRESS);
+ m_UUIDToProfileServer = a_SettingsIni.GetValueSet("MojangAPI", "UUIDToProfileServer", DEFAULT_UUID_TO_PROFILE_SERVER);
+ m_UUIDToProfileAddress = a_SettingsIni.GetValueSet("MojangAPI", "UUIDToProfileAddress", DEFAULT_UUID_TO_PROFILE_ADDRESS);
LoadCachesFromDisk();
}
@@ -135,7 +193,7 @@ AString cMojangAPI::GetUUIDFromPlayerName(const AString & a_PlayerName, bool a_U
AString lcPlayerName(a_PlayerName);
StrToLower(lcPlayerName);
- // Request the cache to populate any names not yet contained:
+ // Request the cache to query the name if not yet cached:
if (!a_UseOnlyCached)
{
AStringVector PlayerNames;
@@ -144,7 +202,8 @@ AString cMojangAPI::GetUUIDFromPlayerName(const AString & a_PlayerName, bool a_U
}
// Retrieve from cache:
- cNameToUUIDMap::const_iterator itr = m_NameToUUID.find(lcPlayerName);
+ cCSLock Lock(m_CSNameToUUID);
+ cProfileMap::const_iterator itr = m_NameToUUID.find(lcPlayerName);
if (itr == m_NameToUUID.end())
{
// No UUID found
@@ -157,6 +216,44 @@ AString cMojangAPI::GetUUIDFromPlayerName(const AString & a_PlayerName, bool a_U
+AString cMojangAPI::GetPlayerNameFromUUID(const AString & a_UUID, bool a_UseOnlyCached)
+{
+ // Normalize the UUID to lowercase short format that is used as the map key:
+ AString UUID = StrToLower(MakeUUIDShort(a_UUID));
+
+ // Retrieve from caches:
+ {
+ cCSLock Lock(m_CSUUIDToProfile);
+ cProfileMap::const_iterator itr = m_UUIDToProfile.find(UUID);
+ if (itr != m_UUIDToProfile.end())
+ {
+ return itr->second.m_PlayerName;
+ }
+ }
+ {
+ cCSLock Lock(m_CSUUIDToName);
+ cProfileMap::const_iterator itr = m_UUIDToName.find(UUID);
+ if (itr != m_UUIDToName.end())
+ {
+ return itr->second.m_PlayerName;
+ }
+ }
+
+ // Name not yet cached, request cache and retry:
+ if (!a_UseOnlyCached)
+ {
+ CacheUUIDToProfile(UUID);
+ return GetPlayerNameFromUUID(a_UUID, true);
+ }
+
+ // No value found, none queried. Return an error:
+ return "";
+}
+
+
+
+
+
AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_PlayerNames, bool a_UseOnlyCached)
{
// Convert all playernames to lowercase:
@@ -180,7 +277,7 @@ AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_Player
cCSLock Lock(m_CSNameToUUID);
for (AStringVector::const_iterator itr = PlayerNames.begin(), end = PlayerNames.end(); itr != end; ++itr, ++idx)
{
- cNameToUUIDMap::const_iterator itrN = m_NameToUUID.find(*itr);
+ cProfileMap::const_iterator itrN = m_NameToUUID.find(*itr);
if (itrN != m_NameToUUID.end())
{
res[idx] = itrN->second.m_UUID;
@@ -196,10 +293,39 @@ AStringVector cMojangAPI::GetUUIDsFromPlayerNames(const AStringVector & a_Player
void cMojangAPI::AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID)
{
AString lcName(a_PlayerName);
- AString UUID = MakeUUIDShort(a_UUID);
+ AString UUID = StrToLower(MakeUUIDShort(a_UUID));
Int64 Now = time(NULL);
- cCSLock Lock(m_CSNameToUUID);
- m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(a_PlayerName, UUID, Now);
+ {
+ cCSLock Lock(m_CSNameToUUID);
+ m_NameToUUID[StrToLower(lcName)] = sProfile(a_PlayerName, UUID, "", "", Now);
+ }
+ {
+ cCSLock Lock(m_CSUUIDToName);
+ m_UUIDToName[UUID] = sProfile(a_PlayerName, UUID, "", "", Now);
+ }
+}
+
+
+
+
+
+void cMojangAPI::AddPlayerProfile(const AString & a_PlayerName, const AString & a_UUID, const Json::Value & a_Properties)
+{
+ AString lcName(a_PlayerName);
+ AString UUID = StrToLower(MakeUUIDShort(a_UUID));
+ Int64 Now = time(NULL);
+ {
+ cCSLock Lock(m_CSNameToUUID);
+ m_NameToUUID[StrToLower(lcName)] = sProfile(a_PlayerName, UUID, "", "", Now);
+ }
+ {
+ cCSLock Lock(m_CSUUIDToName);
+ m_UUIDToName[UUID] = sProfile(a_PlayerName, UUID, "", "", Now);
+ }
+ {
+ cCSLock Lock(m_CSUUIDToProfile);
+ m_UUIDToProfile[UUID] = sProfile(a_PlayerName, UUID, a_Properties, Now);
+ }
}
@@ -337,6 +463,7 @@ void cMojangAPI::LoadCachesFromDisk(void)
// Open up the SQLite DB:
SQLite::Database db("MojangAPI.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)");
+ db.exec("CREATE TABLE IF NOT EXISTS UUIDToProfile (UUID, PlayerName, Textures, TexturesSignature, DateTime)");
// Clean up old entries:
{
@@ -345,16 +472,40 @@ void cMojangAPI::LoadCachesFromDisk(void)
stmt.bind(1, LimitDateTime);
stmt.exec();
}
+ {
+ SQLite::Statement stmt(db, "DELETE FROM UUIDToProfile WHERE DateTime < ?");
+ Int64 LimitDateTime = time(NULL) - MAX_AGE;
+ stmt.bind(1, LimitDateTime);
+ stmt.exec();
+ }
- // Retrieve all remaining entries::
- SQLite::Statement stmt(db, "SELECT PlayerName, UUID, DateTime FROM PlayerNameToUUID");
- while (stmt.executeStep())
+ // Retrieve all remaining entries:
+ {
+ SQLite::Statement stmt(db, "SELECT PlayerName, UUID, DateTime FROM PlayerNameToUUID");
+ while (stmt.executeStep())
+ {
+ AString PlayerName = stmt.getColumn(0);
+ AString UUID = stmt.getColumn(1);
+ Int64 DateTime = stmt.getColumn(2);
+ AString lcPlayerName = PlayerName;
+ UUID = StrToLower(MakeUUIDShort(UUID));
+ m_NameToUUID[StrToLower(lcPlayerName)] = sProfile(PlayerName, UUID, "", "", DateTime);
+ m_UUIDToName[UUID] = sProfile(PlayerName, UUID, "", "", DateTime);
+ }
+ }
{
- AString PlayerName = stmt.getColumn(0);
- AString UUID = stmt.getColumn(1);
- Int64 DateTime = stmt.getColumn(2);
- AString lcPlayerName = PlayerName;
- m_NameToUUID[StrToLower(lcPlayerName)] = sUUIDRecord(PlayerName, UUID, DateTime);
+ SQLite::Statement stmt(db, "SELECT PlayerName, UUID, Textures, TexturesSignature, DateTime FROM UUIDToProfile");
+ while (stmt.executeStep())
+ {
+ AString PlayerName = stmt.getColumn(0);
+ AString UUID = stmt.getColumn(1);
+ AString Textures = stmt.getColumn(2);
+ AString TexturesSignature = stmt.getColumn(2);
+ Int64 DateTime = stmt.getColumn(4);
+ AString lcPlayerName = PlayerName;
+ UUID = StrToLower(MakeUUIDShort(UUID));
+ m_UUIDToProfile[StrToLower(lcPlayerName)] = sProfile(PlayerName, UUID, Textures, TexturesSignature, DateTime);
+ }
}
}
catch (const SQLite::Exception & ex)
@@ -374,26 +525,51 @@ void cMojangAPI::SaveCachesToDisk(void)
// Open up the SQLite DB:
SQLite::Database db("MojangAPI.sqlite", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
db.exec("CREATE TABLE IF NOT EXISTS PlayerNameToUUID (PlayerName, UUID, DateTime)");
+ db.exec("CREATE TABLE IF NOT EXISTS UUIDToProfile (UUID, PlayerName, Textures, TexturesSignature, DateTime)");
// Remove all entries:
db.exec("DELETE FROM PlayerNameToUUID");
+ db.exec("DELETE FROM UUIDToProfile");
- // Save all cache entries:
- SQLite::Statement stmt(db, "INSERT INTO PlayerNameToUUID(PlayerName, UUID, DateTime) VALUES (?, ?, ?)");
+ // Save all cache entries - m_PlayerNameToUUID:
Int64 LimitDateTime = time(NULL) - MAX_AGE;
- cCSLock Lock(m_CSNameToUUID);
- for (cNameToUUIDMap::const_iterator itr = m_NameToUUID.begin(), end = m_NameToUUID.end(); itr != end; ++itr)
{
- if (itr->second.m_DateTime < LimitDateTime)
+ SQLite::Statement stmt(db, "INSERT INTO PlayerNameToUUID(PlayerName, UUID, DateTime) VALUES (?, ?, ?)");
+ cCSLock Lock(m_CSNameToUUID);
+ for (cProfileMap::const_iterator itr = m_NameToUUID.begin(), end = m_NameToUUID.end(); itr != end; ++itr)
{
- // This item is too old, do not save
- continue;
+ if (itr->second.m_DateTime < LimitDateTime)
+ {
+ // This item is too old, do not save
+ continue;
+ }
+ stmt.bind(1, itr->second.m_PlayerName);
+ stmt.bind(2, itr->second.m_UUID);
+ stmt.bind(3, itr->second.m_DateTime);
+ stmt.exec();
+ stmt.reset();
+ }
+ }
+
+ // Save all cache entries - m_UUIDToProfile:
+ {
+ SQLite::Statement stmt(db, "INSERT INTO UUIDToProfile(UUID, PlayerName, Textures, TexturesSignature, DateTime) VALUES (?, ?, ?, ?, ?)");
+ cCSLock Lock(m_CSUUIDToProfile);
+ for (cProfileMap::const_iterator itr = m_UUIDToProfile.begin(), end = m_UUIDToProfile.end(); itr != end; ++itr)
+ {
+ if (itr->second.m_DateTime < LimitDateTime)
+ {
+ // This item is too old, do not save
+ continue;
+ }
+ stmt.bind(1, itr->second.m_UUID);
+ stmt.bind(2, itr->second.m_PlayerName);
+ stmt.bind(3, itr->second.m_Textures);
+ stmt.bind(4, itr->second.m_TexturesSignature);
+ stmt.bind(5, itr->second.m_DateTime);
+ stmt.exec();
+ stmt.reset();
}
- stmt.bind(1, itr->second.m_PlayerName);
- stmt.bind(2, itr->second.m_UUID);
- stmt.bind(3, itr->second.m_DateTime);
- stmt.exec();
- stmt.reset();
}
}
catch (const SQLite::Exception & ex)
@@ -487,22 +663,145 @@ void cMojangAPI::CacheNamesToUUIDs(const AStringVector & a_PlayerNames)
// Store the returned results into cache:
size_t JsonCount = root.size();
Int64 Now = time(NULL);
- cCSLock Lock(m_CSNameToUUID);
- for (size_t idx = 0; idx < JsonCount; ++idx)
{
- Json::Value & Val = root[idx];
- AString JsonName = Val.get("name", "").asString();
- AString JsonUUID = MakeUUIDShort(Val.get("id", "").asString());
- if (JsonUUID.empty())
+ cCSLock Lock(m_CSNameToUUID);
+ for (size_t idx = 0; idx < JsonCount; ++idx)
{
- continue;
- }
- AString lcName = JsonName;
- m_NameToUUID[StrToLower(lcName)] = sUUIDRecord(JsonName, JsonUUID, Now);
- } // for idx - root[]
+ Json::Value & Val = root[idx];
+ AString JsonName = Val.get("name", "").asString();
+ AString JsonUUID = StrToLower(MakeUUIDShort(Val.get("id", "").asString()));
+ if (JsonUUID.empty())
+ {
+ continue;
+ }
+ AString lcName = JsonName;
+ m_NameToUUID[StrToLower(lcName)] = sProfile(JsonName, JsonUUID, "", "", Now);
+ } // for idx - root[]
+ } // cCSLock (m_CSNameToUUID)
+
+ // Also cache the UUIDToName:
+ {
+ cCSLock Lock(m_CSUUIDToName);
+ for (size_t idx = 0; idx < JsonCount; ++idx)
+ {
+ Json::Value & Val = root[idx];
+ AString JsonName = Val.get("name", "").asString();
+ AString JsonUUID = StrToLower(MakeUUIDShort(Val.get("id", "").asString()));
+ if (JsonUUID.empty())
+ {
+ continue;
+ }
+ m_UUIDToName[JsonUUID] = sProfile(JsonName, JsonUUID, "", "", Now);
+ } // for idx - root[]
+ }
} // while (!NamesToQuery.empty())
}
+
+void cMojangAPI::CacheUUIDToProfile(const AString & a_UUID)
+{
+ ASSERT(a_UUID.size() == 32);
+
+ // Check if already present:
+ {
+ if (m_UUIDToProfile.find(a_UUID) != m_UUIDToProfile.end())
+ {
+ return;
+ }
+ }
+
+ // Create the request address:
+ AString Address = m_UUIDToProfileAddress;
+ ReplaceString(Address, "%UUID%", a_UUID);
+
+ // Create the HTTP request:
+ AString Request;
+ Request += "GET " + Address + " HTTP/1.0\r\n"; // We need to use HTTP 1.0 because we don't handle Chunked transfer encoding
+ Request += "Host: " + m_UUIDToProfileServer + "\r\n";
+ Request += "User-Agent: MCServer\r\n";
+ Request += "Connection: close\r\n";
+ Request += "Content-Length: 0\r\n";
+ Request += "\r\n";
+
+ // Get the response from the server:
+ AString Response;
+ if (!SecureRequest(m_UUIDToProfileServer, Request, Response))
+ {
+ return;
+ }
+
+ // Check the HTTP status line:
+ const AString Prefix("HTTP/1.1 200 OK");
+ AString HexDump;
+ if (Response.compare(0, Prefix.size(), Prefix))
+ {
+ LOGINFO("%s failed: bad HTTP status line received", __FUNCTION__);
+ LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
+ return;
+ }
+
+ // Erase the HTTP headers from the response:
+ size_t idxHeadersEnd = Response.find("\r\n\r\n");
+ if (idxHeadersEnd == AString::npos)
+ {
+ LOGINFO("%s failed: bad HTTP response header received", __FUNCTION__);
+ LOGD("Response: \n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
+ return;
+ }
+ Response.erase(0, idxHeadersEnd + 4);
+
+ // Parse the returned string into Json:
+ Json::Reader reader;
+ Json::Value root;
+ if (!reader.parse(Response, root, false) || !root.isObject())
+ {
+ LOGWARNING("%s failed: Cannot parse received data (NameToUUID) to JSON!", __FUNCTION__);
+ LOGD("Response body:\n%s", CreateHexDump(HexDump, Response.data(), Response.size(), 16).c_str());
+ return;
+ }
+
+ /* Example response:
+ {
+ "id": "b1caf24202a841a78055a079c460eee7",
+ "name": "xoft",
+ "properties":
+ [
+ {
+ "name": "textures",
+ "value": "eyJ0aW1lc3RhbXAiOjE0MDcwNzAzMjEyNzEsInByb2ZpbGVJZCI6ImIxY2FmMjQyMDJhODQxYTc4MDU1YTA3OWM0NjBlZWU3IiwicHJvZmlsZU5hbWUiOiJ4b2Z0IiwiaXNQdWJsaWMiOnRydWUsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS9iNzc5YmFiZjVhNTg3Zjk0OGFkNjc0N2VhOTEyNzU0MjliNjg4Mjk1YWUzYzA3YmQwZTJmNWJmNGQwNTIifX19",
+ "signature": "XCty+jGEF39hEPrPhYNnCX087kPaoCjYruzYI/DS4nkL5hbjnkSM5Rh15hnUyv/FHhC8OF5rif3D1tQjtMI19KSVaXoUFXpbJM8/+PB8GDgEbX8Fc3u9nYkzOcM/xfxdYsFAdFhLQMkvase/BZLSuPhdy9DdI+TCrO7xuSTZfYmmwVuWo3w5gCY+mSIAnqltnOzaOOTcly75xvO0WYpVk7nJdnR2tvSi0wfrQPDrIg/uzhX7p0SnDqijmBU4QaNez/TNKiFxy69dAzt0RSotlQzqkDbyVKhhv9a4eY8h3pXi4UMftKEj4FAKczxLImkukJXuOn5NN15/Q+le0rJVBC60/xjKIVzltEsMN6qjWD0lQjey7WEL+4pGhCVuWY5KzuZjFvgqszuJTFz7lo+bcHiceldJtea8/fa02eTRObZvdLxbWC9ZfFY0IhpOVKfcLdno/ddDMNMQMi5kMrJ8MZZ/PcW1w5n7MMGWPGCla1kOaC55AL0QYSMGRVEZqgU9wXI5M7sHGZKGM4mWxkbEJYBkpI/p3GyxWgV6v33ZWlsz65TqlNrR1gCLaoFCm7Sif8NqPBZUAONHYon0roXhin/DyEanS93WV6i6FC1Wisscjq2AcvnOlgTo/5nN/1QsMbjNumuMGo37sqjRqlXoPb8zEUbAhhztYuJjEfQ2Rd8="
+ }
+ ]
+ }
+ */
+
+ // Store the returned result into caches:
+ AString PlayerName = root.get("name", "").asString();
+ if (PlayerName.empty())
+ {
+ // No valid playername, bail out
+ return;
+ }
+ Json::Value Properties = root.get("properties", "");
+ Int64 Now = time(NULL);
+ {
+ cCSLock Lock(m_CSUUIDToProfile);
+ m_UUIDToProfile[a_UUID] = sProfile(PlayerName, a_UUID, Properties, Now);
+ }
+ {
+ cCSLock Lock(m_CSUUIDToName);
+ m_UUIDToName[a_UUID] = sProfile(PlayerName, a_UUID, Properties, Now);
+ }
+ {
+ AString lcPlayerName(PlayerName);
+ cCSLock Lock(m_CSNameToUUID);
+ m_NameToUUID[StrToLower(lcPlayerName)] = sProfile(PlayerName, a_UUID, Properties, Now);
+ }
+}
+
+
+
+
diff --git a/src/Protocol/MojangAPI.h b/src/Protocol/MojangAPI.h
index 7f3ef4e39..08e799c73 100644
--- a/src/Protocol/MojangAPI.h
+++ b/src/Protocol/MojangAPI.h
@@ -11,6 +11,11 @@
#include <time.h>
+namespace Json
+{
+ class Value;
+}
+
@@ -45,14 +50,24 @@ public:
Note: only checks the string's length, not the actual content. */
static AString MakeUUIDDashed(const AString & a_UUID);
+ // tolua_end
+
/** Converts a player name into a UUID.
The UUID will be empty on error.
If a_UseOnlyCached is true, the function only consults the cached values.
If a_UseOnlyCached is false and the name is not found in the cache, it is looked up online, which is a blocking
- operation, do not use this in world-tick thread! */
+ operation, do not use this in world-tick thread!
+ If you have multiple names to resolve, use the GetUUIDsFromPlayerNames() function, it uses a single request for multiple names. */
AString GetUUIDFromPlayerName(const AString & a_PlayerName, bool a_UseOnlyCached = false);
- // tolua_end
+ /** Converts a UUID into a playername.
+ The returned playername will be empty on error.
+ Both short and dashed UUID formats are accepted.
+ Uses both m_UUIDToName and m_UUIDToProfile to search for the value. Uses m_UUIDToProfile for cache.
+ If a_UseOnlyCached is true, the function only consults the cached values.
+ If a_UseOnlyCached is false and the name is not found in the cache, it is looked up online, which is a blocking
+ operation, do not use this in world-tick thread! */
+ AString GetPlayerNameFromUUID(const AString & a_UUID, bool a_UseOnlyCached = false);
/** Converts the player names into UUIDs.
a_PlayerName[idx] will be converted to UUID and returned as idx-th value
@@ -62,36 +77,61 @@ public:
operation, do not use this in world-tick thread! */
AStringVector GetUUIDsFromPlayerNames(const AStringVector & a_PlayerName, bool a_UseOnlyCached = false);
- // tolua_begin
-
/** Called by the Authenticator to add a PlayerName -> UUID mapping that it has received from
authenticating a user. This adds the cache item and "refreshes" it if existing, adjusting its datetime
stamp to now. */
void AddPlayerNameToUUIDMapping(const AString & a_PlayerName, const AString & a_UUID);
- // tolua_end
-
+ /** Called by the Authenticator to add a profile that it has received from authenticating a user. Adds
+ the profile to the respective mapping caches and updtes their datetime stamp to now. */
+ void AddPlayerProfile(const AString & a_PlayerName, const AString & a_UUID, const Json::Value & a_Properties);
+
protected:
- struct sUUIDRecord
+ /** Holds data for a single player profile. */
+ struct sProfile
{
- AString m_PlayerName; // Case-correct playername
- AString m_UUID;
- Int64 m_DateTime; // UNIXtime of the UUID lookup
+ AString m_PlayerName; // Case-correct playername
+ AString m_UUID; // Short lowercased UUID
+ AString m_Textures; // The Textures field of the profile properties
+ AString m_TexturesSignature; // The signature of the Textures field of the profile properties
+ Int64 m_DateTime; // UNIXtime of the profile lookup
- sUUIDRecord(void) :
+ /** Default constructor for the container's sake. */
+ sProfile(void) :
+ m_PlayerName(),
m_UUID(),
+ m_Textures(),
+ m_TexturesSignature(),
m_DateTime(time(NULL))
{
}
- sUUIDRecord(const AString & a_PlayerName, const AString & a_UUID, Int64 a_DateTime) :
+ /** Constructor for the storage creation. */
+ sProfile(
+ const AString & a_PlayerName,
+ const AString & a_UUID,
+ const AString & a_Textures,
+ const AString & a_TexturesSignature,
+ Int64 a_DateTime
+ ) :
m_PlayerName(a_PlayerName),
m_UUID(a_UUID),
+ m_Textures(a_Textures),
+ m_TexturesSignature(a_TexturesSignature),
m_DateTime(a_DateTime)
{
}
+
+ /** Constructor that parses the values from the Json profile. */
+ sProfile(
+ const AString & a_PlayerName,
+ const AString & a_UUID,
+ const Json::Value & a_Properties,
+ Int64 a_DateTime
+ );
};
- typedef std::map<AString, sUUIDRecord> cNameToUUIDMap; // maps Lowercased PlayerName to sUUIDRecord
+ typedef std::map<AString, sProfile> cProfileMap;
+
/** The server to connect to when converting player names to UUIDs. For example "api.mojang.com". */
AString m_NameToUUIDServer;
@@ -100,12 +140,32 @@ protected:
For example "/profiles/page/1". */
AString m_NameToUUIDAddress;
- /** Cache for the Name-to-UUID lookups. The map key is expected lowercased. Protected by m_CSNameToUUID. */
- cNameToUUIDMap m_NameToUUID;
+ /** The server to connect to when converting UUID to profile. For example "sessionserver.mojang.com". */
+ AString m_UUIDToProfileServer;
+
+ /** The URL to use for converting UUID to profile, without the server part.
+ Will replace %UUID% with the actual UUID. For example "session/minecraft/profile/%UUID%?unsigned=false". */
+ AString m_UUIDToProfileAddress;
+
+ /** Cache for the Name-to-UUID lookups. The map key is lowercased PlayerName. Protected by m_CSNameToUUID. */
+ cProfileMap m_NameToUUID;
/** Protects m_NameToUUID against simultaneous multi-threaded access. */
cCriticalSection m_CSNameToUUID;
+ /** Cache for the Name-to-UUID lookups. The map key is lowercased short UUID. Protected by m_CSUUIDToName. */
+ cProfileMap m_UUIDToName;
+
+ /** Protects m_UUIDToName against simultaneous multi-threaded access. */
+ cCriticalSection m_CSUUIDToName;
+
+ /** Cache for the UUID-to-profile lookups. The map key is lowercased short UUID.
+ Protected by m_CSUUIDToProfile. */
+ cProfileMap m_UUIDToProfile;
+
+ /** Protects m_UUIDToProfile against simultaneous multi-threaded access. */
+ cCriticalSection m_CSUUIDToProfile;
+
/** Loads the caches from a disk storage. */
void LoadCachesFromDisk(void);
@@ -113,10 +173,15 @@ protected:
/** Saves the caches to a disk storage. */
void SaveCachesToDisk(void);
- /** Makes sure all specified names are in the cache. Downloads any missing ones from Mojang API servers.
+ /** Makes sure all specified names are in the m_PlayerNameToUUID cache. Downloads any missing ones from Mojang API servers.
Names that are not valid are not added into the cache.
ASSUMEs that a_PlayerNames contains lowercased player names. */
void CacheNamesToUUIDs(const AStringVector & a_PlayerNames);
+
+ /** Makes sure the specified UUID is in the m_UUIDToProfile cache. If missing, downloads it from Mojang API servers.
+ UUIDs that are not valid will not be added into the cache.
+ ASSUMEs that a_UUID is a lowercased short UUID. */
+ void CacheUUIDToProfile(const AString & a_UUID);
} ; // tolua_export
diff --git a/src/Root.h b/src/Root.h
index 5cb82edda..1cd175ab4 100644
--- a/src/Root.h
+++ b/src/Root.h
@@ -88,7 +88,7 @@ public:
cWebAdmin * GetWebAdmin (void) { return m_WebAdmin; } // tolua_export
cPluginManager * GetPluginManager (void) { return m_PluginManager; } // tolua_export
cAuthenticator & GetAuthenticator (void) { return m_Authenticator; }
- cMojangAPI & GetMojangAPI (void) { return m_MojangAPI; } // tolua_export
+ cMojangAPI & GetMojangAPI (void) { return m_MojangAPI; }
/** Queues a console command for execution through the cServer class.
The command will be executed in the tick thread