Bancho.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. #ifdef _WIN32
  2. #include <stdio.h>
  3. #include <wbemidl.h>
  4. #include <windows.h>
  5. #include <sstream>
  6. #else
  7. #include <blkid/blkid.h>
  8. #include <linux/limits.h>
  9. #endif
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <unistd.h>
  14. #include "Bancho.h"
  15. #include "BanchoNetworking.h"
  16. #include "BanchoProtocol.h"
  17. #include "BanchoUsers.h"
  18. #include "ConVar.h"
  19. #include "Engine.h"
  20. #include "MD5.h"
  21. #include "Osu.h"
  22. #include "OsuChat.h"
  23. #include "OsuLobby.h"
  24. #include "OsuNotificationOverlay.h"
  25. #include "OsuOptionsMenu.h"
  26. #include "OsuRoom.h"
  27. #include "OsuSongBrowser.h"
  28. #include "OsuUIAvatar.h"
  29. #include "OsuUIButton.h"
  30. #include "OsuUISongBrowserUserButton.h"
  31. Bancho bancho;
  32. std::unordered_map<std::string, Channel *> chat_channels;
  33. bool print_new_channels = true;
  34. bool Bancho::submit_scores() {
  35. if(score_submission_policy == ServerPolicy::NO_PREFERENCE) {
  36. return convar->getConVarByName("submit_scores")->getBool();
  37. } else if(score_submission_policy == ServerPolicy::YES) {
  38. return true;
  39. } else {
  40. return false;
  41. }
  42. }
  43. void update_channel(UString name, UString topic, int32_t nb_members) {
  44. Channel *chan;
  45. auto name_str = std::string(name.toUtf8());
  46. auto it = chat_channels.find(name_str);
  47. if(it == chat_channels.end()) {
  48. chan = new Channel();
  49. chan->name = name;
  50. chat_channels[name_str] = chan;
  51. if(print_new_channels) {
  52. bancho.osu->m_chat->addMessage(
  53. "#osu", ChatMessage{
  54. .tms = time(NULL),
  55. .author_id = 0,
  56. .author_name = UString(""),
  57. .text = UString::format("%s (%d): %s", name.toUtf8(), nb_members, topic.toUtf8()),
  58. });
  59. }
  60. } else {
  61. chan = it->second;
  62. }
  63. chan->topic = topic;
  64. chan->nb_members = nb_members;
  65. }
  66. MD5Hash md5(uint8_t *msg, size_t msg_len) {
  67. MD5 hasher;
  68. hasher.update(msg, msg_len);
  69. hasher.finalize();
  70. uint8_t *digest = hasher.getDigest();
  71. MD5Hash out;
  72. for(int i = 0; i < 16; i++) {
  73. out.hash[i * 2] = "0123456789abcdef"[digest[i] >> 4];
  74. out.hash[i * 2 + 1] = "0123456789abcdef"[digest[i] & 0xf];
  75. }
  76. out.hash[32] = 0;
  77. return out;
  78. }
  79. UString get_disk_uuid() {
  80. #ifdef _WIN32
  81. // ChatGPT'd, this looks absolutely insane but might just be regular Windows API...
  82. CoInitializeEx(0, COINIT_MULTITHREADED);
  83. CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE,
  84. NULL);
  85. IWbemLocator *pLoc = NULL;
  86. auto hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc);
  87. if(FAILED(hres)) {
  88. debugLog("Failed to create IWbemLocator object. Error code = 0x%x\n", hres);
  89. return UString("error getting disk uuid");
  90. }
  91. IWbemServices *pSvc = NULL;
  92. BSTR bstr_root = SysAllocString(L"ROOT\\CIMV2");
  93. hres = pLoc->ConnectServer(bstr_root, NULL, NULL, 0, 0, 0, 0, &pSvc);
  94. if(FAILED(hres)) {
  95. debugLog("Could not connect. Error code = 0x%x\n", hres);
  96. pLoc->Release();
  97. SysFreeString(bstr_root);
  98. return UString("error getting disk uuid");
  99. }
  100. SysFreeString(bstr_root);
  101. hres = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL,
  102. RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
  103. if(FAILED(hres)) {
  104. debugLog("Could not set proxy blanket. Error code = 0x%x\n", hres);
  105. pSvc->Release();
  106. pLoc->Release();
  107. return UString("error getting disk uuid");
  108. }
  109. UString uuid = "";
  110. IEnumWbemClassObject *pEnumerator = NULL;
  111. BSTR bstr_wql = SysAllocString(L"WQL");
  112. BSTR bstr_sql = SysAllocString(L"SELECT * FROM Win32_DiskDrive");
  113. hres =
  114. pSvc->ExecQuery(bstr_wql, bstr_sql, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
  115. if(FAILED(hres)) {
  116. debugLog("Query for hard drive UUID failed. Error code = 0x%x\n", hres);
  117. pSvc->Release();
  118. pLoc->Release();
  119. SysFreeString(bstr_wql);
  120. SysFreeString(bstr_sql);
  121. return UString("error getting disk uuid");
  122. }
  123. SysFreeString(bstr_wql);
  124. SysFreeString(bstr_sql);
  125. IWbemClassObject *pclsObj = NULL;
  126. ULONG uReturn = 0;
  127. while(pEnumerator && uuid.length() == 0) {
  128. HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
  129. if(0 == uReturn) {
  130. break;
  131. }
  132. VARIANT vtProp;
  133. hr = pclsObj->Get(L"SerialNumber", 0, &vtProp, 0, 0);
  134. if(SUCCEEDED(hr)) {
  135. uuid = vtProp.bstrVal;
  136. VariantClear(&vtProp);
  137. }
  138. pclsObj->Release();
  139. }
  140. pSvc->Release();
  141. pLoc->Release();
  142. pEnumerator->Release();
  143. return uuid;
  144. #else
  145. blkid_cache cache;
  146. blkid_get_cache(&cache, NULL);
  147. blkid_dev device;
  148. blkid_dev_iterate iter = blkid_dev_iterate_begin(cache);
  149. while(!blkid_dev_next(iter, &device)) {
  150. const char *devname = blkid_dev_devname(device);
  151. char *uuid = blkid_get_tag_value(cache, "UUID", devname);
  152. blkid_put_cache(cache);
  153. UString w_uuid = UString(uuid);
  154. // Not sure if we own the string here or not, leak too small to matter anyway
  155. // free(uuid);
  156. return w_uuid;
  157. }
  158. blkid_put_cache(cache);
  159. return UString("error getting disk uuid");
  160. #endif
  161. }
  162. void handle_packet(Packet *packet) {
  163. if(packet->id == USER_ID) {
  164. bancho.user_id = read_int32(packet);
  165. if(bancho.user_id > 0) {
  166. debugLog("Logged in as user #%d.\n", bancho.user_id);
  167. bancho.osu->m_optionsMenu->logInButton->setText("Disconnect");
  168. bancho.osu->m_optionsMenu->logInButton->setColor(0xffff0000);
  169. bancho.osu->m_optionsMenu->logInButton->is_loading = false;
  170. convar->getConVarByName("mp_autologin")->setValue(true);
  171. ConVars::sv_cheats.setValue(false);
  172. print_new_channels = true;
  173. std::stringstream ss;
  174. ss << MCENGINE_DATA_DIR "avatars/";
  175. ss << bancho.endpoint.toUtf8();
  176. auto avatar_dir = ss.str();
  177. if(!env->directoryExists(avatar_dir)) {
  178. env->createDirectory(avatar_dir);
  179. }
  180. std::stringstream ss2;
  181. ss2 << MCENGINE_DATA_DIR "replays/";
  182. ss2 << bancho.endpoint.toUtf8();
  183. auto replays_dir = ss2.str();
  184. if(!env->directoryExists(replays_dir)) {
  185. env->createDirectory(replays_dir);
  186. }
  187. // close your eyes
  188. SAFE_DELETE(bancho.osu->m_songBrowser2->m_userButton->m_avatar);
  189. bancho.osu->m_songBrowser2->m_userButton->m_avatar = new OsuUIAvatar(bancho.user_id, 0.f, 0.f, 0.f, 0.f);
  190. bancho.osu->m_songBrowser2->m_userButton->m_avatar->on_screen = true;
  191. // XXX: We should toggle between "offline" sorting options and "online" ones
  192. // Online ones would be "Local scores", "Global", "Country", "Selected mods" etc
  193. // While offline ones would be "By score", "By pp", etc
  194. bancho.osu->m_songBrowser2->onSortScoresChange(UString("Online Leaderboard"), 0);
  195. } else {
  196. convar->getConVarByName("mp_autologin")->setValue(false);
  197. bancho.osu->m_optionsMenu->logInButton->setText("Log in");
  198. bancho.osu->m_optionsMenu->logInButton->setColor(0xff00ff00);
  199. bancho.osu->m_optionsMenu->logInButton->is_loading = false;
  200. debugLog("Failed to log in, server returned code %d.\n", bancho.user_id);
  201. UString errmsg = UString::format("Failed to log in: %s (code %d)\n", cho_token.toUtf8(), bancho.user_id);
  202. if(bancho.user_id == -2) {
  203. errmsg = "Client version is too old to connect to this server.";
  204. } else if(bancho.user_id == -3 || bancho.user_id == -4) {
  205. errmsg = "You are banned from this server.";
  206. } else if(bancho.user_id == -6) {
  207. errmsg = "You need to buy supporter to connect to this server.";
  208. } else if(bancho.user_id == -7) {
  209. errmsg = "You need to reset your password to connect to this server.";
  210. } else if(bancho.user_id == -8) {
  211. errmsg = "Open the verification link sent to your email, then log in again.";
  212. } else {
  213. if(cho_token == UString("user-already-logged-in")) {
  214. errmsg = "Already logged in on another client.";
  215. } else if(cho_token == UString("unknown-username")) {
  216. errmsg = UString::format("No account by the username '%s' exists.", bancho.username.toUtf8());
  217. } else if(cho_token == UString("incorrect-credentials")) {
  218. errmsg = "This username is not registered.";
  219. } else if(cho_token == UString("incorrect-password")) {
  220. errmsg = "Incorrect password.";
  221. } else if(cho_token == UString("contact-staff")) {
  222. errmsg = "Please contact an administrator of the server.";
  223. }
  224. }
  225. bancho.osu->getNotificationOverlay()->addNotification(errmsg);
  226. }
  227. } else if(packet->id == RECV_MESSAGE) {
  228. UString sender = read_string(packet);
  229. UString text = read_string(packet);
  230. UString recipient = read_string(packet);
  231. int32_t sender_id = read_int32(packet);
  232. bancho.osu->m_chat->addMessage(recipient, ChatMessage{
  233. .tms = time(NULL),
  234. .author_id = sender_id,
  235. .author_name = sender,
  236. .text = text,
  237. });
  238. } else if(packet->id == PONG) {
  239. // (nothing to do)
  240. } else if(packet->id == USER_STATS) {
  241. int32_t stats_user_id = read_int32(packet);
  242. uint8_t action = read_byte(packet);
  243. UserInfo *user = get_user_info(stats_user_id);
  244. user->action = (Action)action;
  245. user->info_text = read_string(packet);
  246. user->map_md5 = read_string(packet);
  247. user->mods = read_int32(packet);
  248. user->mode = (GameMode)read_byte(packet);
  249. user->map_id = read_int32(packet);
  250. user->ranked_score = read_int64(packet);
  251. user->accuracy = read_float32(packet);
  252. user->plays = read_int32(packet);
  253. user->total_score = read_int64(packet);
  254. user->global_rank = read_int32(packet);
  255. user->pp = read_short(packet);
  256. if(stats_user_id == bancho.user_id) {
  257. bancho.osu->m_songBrowser2->m_userButton->updateUserStats();
  258. }
  259. } else if(packet->id == USER_LOGOUT) {
  260. int32_t logged_out_id = read_int32(packet);
  261. read_byte(packet);
  262. if(logged_out_id == bancho.user_id) {
  263. debugLog("Logged out.\n");
  264. disconnect();
  265. }
  266. } else if(packet->id == SPECTATOR_JOINED) {
  267. int32_t spectator_id = read_int32(packet);
  268. debugLog("Spectator joined: user id %d\n", spectator_id);
  269. } else if(packet->id == SPECTATOR_LEFT) {
  270. int32_t spectator_id = read_int32(packet);
  271. debugLog("Spectator left: user id %d\n", spectator_id);
  272. } else if(packet->id == VERSION_UPDATE) {
  273. disconnect();
  274. bancho.osu->getNotificationOverlay()->addNotification("Server uses an unsupported protocol version.");
  275. } else if(packet->id == SPECTATOR_CANT_SPECTATE) {
  276. int32_t spectator_id = read_int32(packet);
  277. debugLog("Spectator can't spectate: user id %d\n", spectator_id);
  278. } else if(packet->id == GET_ATTENTION) {
  279. // (nothing to do)
  280. } else if(packet->id == NOTIFICATION) {
  281. UString notification = read_string(packet);
  282. bancho.osu->getNotificationOverlay()->addNotification(notification);
  283. // XXX: don't do McOsu style notifications, since:
  284. // 1) they can't do multiline text
  285. // 2) they don't stack, if the server sends >1 you only see the latest
  286. // Maybe log them in the chat + display in bottom right like ppy client?
  287. } else if(packet->id == ROOM_UPDATED) {
  288. auto room = Room(packet);
  289. if(bancho.osu->m_lobby->isVisible()) {
  290. bancho.osu->m_lobby->updateRoom(room);
  291. } else if(room.id == bancho.room.id) {
  292. bancho.osu->m_room->on_room_updated(room);
  293. }
  294. } else if(packet->id == ROOM_CREATED) {
  295. auto room = new Room(packet);
  296. bancho.osu->m_lobby->addRoom(room);
  297. } else if(packet->id == ROOM_CLOSED) {
  298. int32_t room_id = read_int32(packet);
  299. bancho.osu->m_lobby->removeRoom(room_id);
  300. } else if(packet->id == ROOM_JOIN_SUCCESS) {
  301. auto room = Room(packet);
  302. bancho.osu->m_room->on_room_joined(room);
  303. } else if(packet->id == ROOM_JOIN_FAIL) {
  304. bancho.osu->getNotificationOverlay()->addNotification("Failed to join room.");
  305. bancho.osu->m_lobby->on_room_join_failed();
  306. } else if(packet->id == FELLOW_SPECTATOR_JOINED) {
  307. uint32_t spectator_id = read_int32(packet);
  308. (void)spectator_id; // (spectating not implemented; nothing to do)
  309. } else if(packet->id == FELLOW_SPECTATOR_LEFT) {
  310. uint32_t spectator_id = read_int32(packet);
  311. (void)spectator_id; // (spectating not implemented; nothing to do)
  312. } else if(packet->id == MATCH_STARTED) {
  313. auto room = Room(packet);
  314. bancho.osu->m_room->on_match_started(room);
  315. } else if(packet->id == UPDATE_MATCH_SCORE || packet->id == MATCH_SCORE_UPDATED) {
  316. bancho.osu->m_room->on_match_score_updated(packet);
  317. } else if(packet->id == HOST_CHANGED) {
  318. // (nothing to do)
  319. } else if(packet->id == MATCH_ALL_PLAYERS_LOADED) {
  320. bancho.osu->m_room->on_all_players_loaded();
  321. } else if(packet->id == MATCH_PLAYER_FAILED) {
  322. int32_t slot_id = read_int32(packet);
  323. bancho.osu->m_room->on_player_failed(slot_id);
  324. } else if(packet->id == MATCH_FINISHED) {
  325. bancho.osu->m_room->on_match_finished();
  326. } else if(packet->id == MATCH_SKIP) {
  327. bancho.osu->m_room->on_all_players_skipped();
  328. } else if(packet->id == CHANNEL_JOIN_SUCCESS) {
  329. UString name = read_string(packet);
  330. bancho.osu->m_chat->addChannel(name);
  331. bancho.osu->m_chat->addMessage(name, ChatMessage{
  332. .tms = time(NULL),
  333. .author_id = 0,
  334. .author_name = UString(""),
  335. .text = UString("Joined channel."),
  336. });
  337. } else if(packet->id == CHANNEL_INFO) {
  338. UString channel_name = read_string(packet);
  339. UString channel_topic = read_string(packet);
  340. int32_t nb_members = read_int32(packet);
  341. update_channel(channel_name, channel_topic, nb_members);
  342. } else if(packet->id == LEFT_CHANNEL) {
  343. UString name = read_string(packet);
  344. bancho.osu->m_chat->removeChannel(name);
  345. } else if(packet->id == CHANNEL_AUTO_JOIN) {
  346. UString channel_name = read_string(packet);
  347. UString channel_topic = read_string(packet);
  348. int32_t nb_members = read_int32(packet);
  349. update_channel(channel_name, channel_topic, nb_members);
  350. } else if(packet->id == PRIVILEGES) {
  351. read_int32(packet);
  352. // (nothing to do)
  353. } else if(packet->id == FRIENDS_LIST) {
  354. uint16_t nb_friends = read_short(packet);
  355. for(int i = 0; i < nb_friends; i++) {
  356. auto user = get_user_info(read_int32(packet));
  357. user->is_friend = true;
  358. }
  359. } else if(packet->id == PROTOCOL_VERSION) {
  360. int protocol_version = read_int32(packet);
  361. if(protocol_version != 19) {
  362. disconnect();
  363. bancho.osu->getNotificationOverlay()->addNotification("Server uses an unsupported protocol version.");
  364. }
  365. } else if(packet->id == MAIN_MENU_ICON) {
  366. UString icon = read_string(packet);
  367. debugLog("Main menu icon: %s\n", icon.toUtf8());
  368. } else if(packet->id == MATCH_PLAYER_SKIPPED) {
  369. int32_t user_id = read_int32(packet);
  370. bancho.osu->m_room->on_player_skip(user_id);
  371. // I'm not sure the server ever sends MATCH_SKIP... So, double-checking here.
  372. bool all_players_skipped = true;
  373. for(int i = 0; i < 16; i++) {
  374. if(bancho.room.slots[i].is_player_playing()) {
  375. if(!bancho.room.slots[i].skipped) {
  376. all_players_skipped = false;
  377. }
  378. }
  379. }
  380. if(all_players_skipped) {
  381. bancho.osu->m_room->on_all_players_skipped();
  382. }
  383. } else if(packet->id == USER_PRESENCE) {
  384. int32_t presence_user_id = read_int32(packet);
  385. UString presence_username = read_string(packet);
  386. UserInfo *user = get_user_info(presence_user_id);
  387. user->name = presence_username;
  388. user->utc_offset = read_byte(packet);
  389. user->country = read_byte(packet);
  390. user->privileges = read_byte(packet);
  391. user->longitude = read_float32(packet);
  392. user->latitude = read_float32(packet);
  393. user->global_rank = read_int32(packet);
  394. } else if(packet->id == RESTART) {
  395. // XXX: wait 'ms' milliseconds before reconnecting
  396. int32_t ms = read_int32(packet);
  397. (void)ms;
  398. // Some servers send "restart" packets when password is incorrect
  399. // So, don't retry unless actually logged in
  400. if(bancho.is_online()) {
  401. reconnect();
  402. }
  403. } else if(packet->id == MATCH_INVITE) {
  404. UString sender = read_string(packet);
  405. UString text = read_string(packet);
  406. UString recipient = read_string(packet);
  407. (void)recipient;
  408. int32_t sender_id = read_int32(packet);
  409. bancho.osu->m_chat->addMessage(recipient, ChatMessage{
  410. .tms = time(NULL),
  411. .author_id = sender_id,
  412. .author_name = sender,
  413. .text = text,
  414. });
  415. } else if(packet->id == CHANNEL_INFO_END) {
  416. print_new_channels = false;
  417. bancho.osu->m_chat->join("#announce");
  418. bancho.osu->m_chat->join("#osu");
  419. } else if(packet->id == ROOM_PASSWORD_CHANGED) {
  420. UString new_password = read_string(packet);
  421. debugLog("Room changed password to %s\n", new_password.toUtf8());
  422. bancho.room.password = new_password;
  423. } else if(packet->id == SILENCE_END) {
  424. int32_t delta = read_int32(packet);
  425. debugLog("Silence ends in %d seconds.\n", delta);
  426. // XXX: Prevent user from sending messages while silenced
  427. } else if(packet->id == USER_SILENCED) {
  428. int32_t user_id = read_int32(packet);
  429. debugLog("User #%d silenced.\n", user_id);
  430. } else if(packet->id == USER_DM_BLOCKED) {
  431. read_string(packet);
  432. read_string(packet);
  433. UString blocked = read_string(packet);
  434. read_int32(packet);
  435. debugLog("Blocked %s.\n", blocked.toUtf8());
  436. } else if(packet->id == TARGET_IS_SILENCED) {
  437. read_string(packet);
  438. read_string(packet);
  439. UString blocked = read_string(packet);
  440. read_int32(packet);
  441. debugLog("Silenced %s.\n", blocked.toUtf8());
  442. } else if(packet->id == VERSION_UPDATE_FORCED) {
  443. disconnect();
  444. bancho.osu->getNotificationOverlay()->addNotification("Server uses an unsupported protocol version.");
  445. } else if(packet->id == ACCOUNT_RESTRICTED) {
  446. bancho.osu->getNotificationOverlay()->addNotification("Account restricted.");
  447. disconnect();
  448. } else if(packet->id == MATCH_ABORT) {
  449. bancho.osu->m_room->on_match_aborted();
  450. } else {
  451. debugLog("Unknown packet ID %d (%d bytes)!\n", packet->id, packet->size);
  452. }
  453. }
  454. Packet build_login_packet() {
  455. // Request format:
  456. // username\npasswd_md5\nosu_version|utc_offset|display_city|client_hashes|pm_private\n
  457. Packet packet;
  458. write_bytes(&packet, (uint8_t *)bancho.username.toUtf8(), bancho.username.lengthUtf8());
  459. write_byte(&packet, '\n');
  460. write_bytes(&packet, (uint8_t *)bancho.pw_md5.hash, 32);
  461. write_byte(&packet, '\n');
  462. write_bytes(&packet, (uint8_t *)OSU_VERSION, strlen(OSU_VERSION));
  463. write_byte(&packet, '|');
  464. // UTC offset
  465. time_t now = time(NULL);
  466. auto gmt = gmtime(&now);
  467. auto local_time = localtime(&now);
  468. int utc_offset = difftime(mktime(local_time), mktime(gmt)) / 3600;
  469. if(utc_offset < 0) {
  470. write_byte(&packet, '-');
  471. utc_offset *= -1;
  472. }
  473. write_byte(&packet, '0' + utc_offset);
  474. write_byte(&packet, '|');
  475. // Don't dox the user's city
  476. write_byte(&packet, '0');
  477. write_byte(&packet, '|');
  478. char osu_path[PATH_MAX] = {0};
  479. #ifdef _WIN32
  480. GetModuleFileName(NULL, osu_path, PATH_MAX);
  481. #else
  482. readlink("/proc/self/exe", osu_path, PATH_MAX - 1);
  483. #endif
  484. MD5Hash osu_path_md5 = md5((uint8_t *)osu_path, strlen(osu_path));
  485. // XXX: Should get MAC addresses from network adapters
  486. // NOTE: Not sure how the MD5 is computed - does it include final "." ?
  487. const char *adapters = "runningunderwine";
  488. MD5Hash adapters_md5 = md5((uint8_t *)adapters, strlen(adapters));
  489. // XXX: Should remove '|' from the disk UUID just to be safe
  490. bancho.disk_uuid = get_disk_uuid();
  491. MD5Hash disk_md5 = md5((uint8_t *)bancho.disk_uuid.toUtf8(), bancho.disk_uuid.lengthUtf8());
  492. // XXX: Not implemented, I'm lazy so just reusing disk signature
  493. bancho.install_id = bancho.disk_uuid;
  494. MD5Hash install_md5 = md5((uint8_t *)bancho.install_id.toUtf8(), bancho.install_id.lengthUtf8());
  495. ;
  496. bancho.client_hashes = UString::format("%s:%s:%s:%s:%s:", osu_path_md5.hash, adapters, adapters_md5.hash,
  497. install_md5.hash, disk_md5.hash);
  498. write_bytes(&packet, (uint8_t *)bancho.client_hashes.toUtf8(), bancho.client_hashes.lengthUtf8());
  499. // Allow PMs from strangers
  500. write_byte(&packet, '|');
  501. write_byte(&packet, '0');
  502. write_byte(&packet, '\n');
  503. return packet;
  504. }