ppsspp/Core/HLE/proAdhocServer.cpp

2103 lines
57 KiB
C++

// Copyright (c) 2014- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// proAdhocServer
// This is a direct port of Coldbird's code from http://code.google.com/p/aemu/
// All credit goes to him!
#include "ppsspp_config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#if defined(HAVE_LIBNX) || PPSSPP_PLATFORM(SWITCH)
#include <netdb.h>
#include <switch.h>
// Missing include, *shrugs*
extern "C" struct hostent *gethostbyname(const char *name);
#endif // defined(HAVE_LIBNX) || PPSSPP_PLATFORM(SWITCH)
#if !defined(__APPLE__)
#include <stdlib.h>
#endif
#include <sys/types.h>
// Net stuff
#if defined(_WIN32)
#include <WinSock2.h>
#include <WS2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#endif
#include <fcntl.h>
#include <errno.h>
//#include <sqlite3.h>
#ifndef MSG_NOSIGNAL
// Default value to 0x00 (do nothing) in systems where it's not supported.
#define MSG_NOSIGNAL 0x00
#endif
#include "Common/Data/Text/I18n.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/File/FileUtil.h"
#include "Common/TimeUtil.h"
#include "Core/Util/PortManager.h"
#include "Core/Instance.h"
#include "Core/Core.h"
#include "Core/Host.h"
#include "Core/HLE/proAdhocServer.h"
// User Count
uint32_t _db_user_count = 0;
// User Database
SceNetAdhocctlUserNode * _db_user = NULL;
// Game Database
SceNetAdhocctlGameNode * _db_game = NULL;
// Server Status
std::atomic<bool> adhocServerRunning(false);
std::thread adhocServerThread;
// Crosslink database for cross region Adhoc play
std::vector<db_crosslink> crosslinks;
static const db_crosslink default_crosslinks[] = {
// Ace Combat X2 - Joint Assault
{ "ULES01408", "ULUS10511" },
{ "NPJH50263", "ULUS10511" },
// Armored Core 3 Portable
{ "ULJM05492", "NPUH10023" },
// BlazBlue - Continuum Shift 2
{ "NPJH50401", "ULUS10579" },
// Blood Bowl
{ "ULES01230", "ULUS10516" },
// Bomberman
{ "ULJM05034", "ULUS10121" },
{ "ULES00469", "ULUS10121" },
{ "ULJM05316", "ULUS10121" },
// Bomberman Land
{ "ULJM05181", "ULUS10319" },
{ "ULJM05319", "ULUS10319" },
{ "ULES00959", "ULUS10319" },
// Call of Duty - Roads to Victory
{ "ULES00643", "ULUS10218" },
// Dissidia 012 Duodecim Final Fantasy
{ "ULES01505", "ULUS10566" },
{ "NPJH50377", "ULUS10566" },
// Dissidia Final Fantasy
{ "ULES01270", "ULUS10437" },
{ "ULJM05262", "ULUS10437" },
// Dragon Ball Z - Shin Budokai
{ "ULJS00049", "ULUS10081" },
{ "ULKS46085", "ULUS10081" },
{ "ULES00309", "ULUS10081" },
// Dragon Ball Z - Shin Budokai 2
{ "ULJS00107", "ULUS10234" },
{ "ULES00789", "ULUS10234" },
// Dragon Ball Z - Tenkaichi Tag Team
{ "ULES01456", "ULUS10537" },
// Dungeon Siege - Throne of Agony
{ "ULES00569", "ULUS10177" },
// Everybody's Tennis
{ "UCJS10101", "UCUS98701" },
{ "UCES01420", "UCUS98701" },
// Fat Princess - Fistful of Cake
{ "UCES01312", "UCUS98740" },
{ "NPHG00025", "UCUS98740" },
// God Eater Burst
{ "ULES01519", "ULUS10563" },
{ "NPJH50352", "ULUS10563" },
// Gran Turismo
{ "UCES01245", "UCUS98632" },
{ "UCES00543", "UCUS98645" },
// Gundam VS Gundam - Next Plus
{ "ULJS00250", "NPJH50107" },
{ "ULJS19048", "NPJH50107" },
// Hatsune Miku - Project Diva Extend
{ "NPJH50465", "ULJM05933" },
// Hot Pixel
{ "ULES00642", "ULUS10298" },
// Lord of Arcana
{ "ULJM05767", "ULES01507" },
{ "ULUS10479", "ULES01507" },
// M.A.C.H. - Modified Air Combat Heroes
{ "ULES00565", "ULUS10180" },
{ "ULES00566", "ULUS10180" },
{ "ULJM05202", "ULUS10180" },
// Metal Gear Solid - Peace Walker
{ "ULES01372", "NPJH50045" },
{ "ULUS10509", "NPJH50045" },
// Metal Gear Solid - Portable Ops
{ "ULES00645", "ULUS10202" },
{ "ULJM05193", "ULUS10202" },
// Metal Gear Solid - Portable Ops +
{ "ULES01003", "ULUS10290" },
{ "ULJM05261", "ULUS10290" },
// Midnight Club - LA Remix
{ "ULES01144", "ULUS10383" },
{ "ULJS00180", "ULUS10383" },
// Mod Nation Racers
{ "UCES01327", "UCUS98741" },
{ "UCJS10112", "UCUS98741" },
{ "UCAS40306", "UCUS98741" },
// Monster Hunter Freedom
{ "ULJM05066", "ULUS10084" },
{ "ULES00318", "ULUS10084" },
// Monster Hunter Freedom 2
{ "ULJM05156", "ULUS10266" },
{ "ULES00851", "ULUS10266" },
// Monster Hunter Freedom Unite
{ "ULES01213", "ULUS10391" },
{ "ULJM05500", "ULUS10391" },
// N+
{ "ULES01026", "ULUS10340" },
// Need for Speed - Undercover
{ "ULJM05403", "ULUS10376" },
{ "ULJM05612", "ULUS10376" },
{ "ULES01145", "ULUS10376" },
// Outrun 2006 - Coast 2 Coast
{ "ULES00262", "ULUS10064" },
// Pangya! - Fantasy Golf
{ "ULJM05440", "ULUS10438" },
{ "ULKS46164", "ULUS10438" },
// PRO Evolution Soccer 2012
{ "ULES01540", "ULUS10586" },
{ "ULES01541", "ULUS10586" },
{ "ULES01542", "ULUS10586" },
{ "ULAS42289", "ULUS10586" },
// Patapon 2
{ "UCJS10089", "UCUS98732" },
{ "PSPJ30000", "UCUS98732" },
{ "UCES01177", "UCUS98732" },
{ "UCJS18036", "UCUS98732" },
// Patapon 3
{ "UCES01421", "UCUS98751" },
{ "NPJG00122", "UCUS98751" },
// Phantasy Star Portable
{ "ULJM05309", "ULUS10410" },
{ "ULES01218", "ULUS10410" },
{ "ULJM08023", "ULUS10410" },
// Phantasy Star Portable 2
{ "ULJM05493", "ULUS10529" },
{ "ULJM08030", "ULUS10529" },
{ "ULES01439", "ULUS10529" },
// Resistance - Retribution
{ "UCES01184", "UCJS10090" },
{ "UCUS98668", "UCJS10090" },
// Rocky Balboa
{ "ULUS10233", "ULES00670" },
// SOCOM - Fireteam Bravo
{ "UCES00038", "UCUS98615" },
{ "UCJS10102", "UCUS98615" },
// SOCOM - Fireteam Bravo 3
{ "UCES01242", "UCUS98716" },
{ "NPJG00035", "UCUS98716" },
// Shrek - Smash and Crash Racing
{ "ULES00618", "ULUS10194" },
// Smash Court Tennis 3
{ "ULJS00098", "UCES00758" },
{ "ULUS10269", "UCES00758" },
// Soul Calibur - Broken Destiny
{ "ULES01298", "ULUS10457" },
{ "ULJS00202", "ULUS10457" },
// Split Second - Velocity
{ "ULES01402", "ULUS10513" },
{ "ULJM05812", "ULUS10513" },
// Street Fighter Alpha 3 MAX
{ "ULJM05082", "ULUS10062" },
{ "ULES00235", "ULUS10062" },
{ "ULJM05225", "ULUS10062" },
// Taiko no Tatsujin Portable DX"
{ "ULJS00383", "NPJH50426" },
// Tekken 6
{ "ULES01376", "ULUS10466" },
{ "NPJH50184", "ULUS10466" },
{ "ULJS00224", "ULUS10466" },
// TRON - Evolution
{ "ULES01495", "ULUS10548" },
// Untold Legends - Brotherhood of the Blade
{ "ULES00046", "ULUS10003" },
{ "ULJM05087", "ULUS10003" },
{ "ULKS46015", "ULUS10003" },
// Untold Legends - The Warrior's Code
{ "ULES00301", "ULUS10086" },
{ "ULJM05179", "ULUS10086" },
{ "ULKS46069", "ULUS10086" },
// Virtua Tennis 3
{ "ULES00763", "ULUS10246" },
// World Series of Poker 2008 - Battle for the Bracelets
{ "ULES00991", "ULUS10321" },
// Worms Battle Islands
{ "NPEH00019", "NPUH10045" },
// Worms Open Warfare
{ "ULES00268", "ULUS10065" },
// Worms Open Warfare 2
{ "ULES00819", "ULUS10260" },
// Yu-Gi-Oh! 5D's Tag Force 5
{ "ULUS10555", "ULJM05734" },
{ "ULES01474", "ULJM05734" },
};
std::vector<db_productid> productids;
static const db_productid default_productids[] = {
{ "ULUS10511", "Ace Combat X2 - Joint Assault" },
{ "ULUS10245", "Alien Syndrome" },
{ "NPUH10023", "Armored Core 3 Portable" },
{ "ULES00719", "Asphalt - Urban GT 2" },
{ "ULUS10579", "BlazBlue - Continuum Shift 2" },
{ "ULUS10519", "BlazBlue Calamity Trigger" },
{ "UCJS10110", "Bleach Heat The Soul 7" },
{ "ULUS10516", "Blood Bowl" },
{ "ULUS10121", "Bomberman" },
{ "ULUS10319", "Bomberman Land" },
{ "ULES00703", "Burnout Dominator" },
{ "ULES00125", "Burnout Legends" },
{ "ULJM05538", "Busou Shinki - Battle Masters" },
{ "ULUS10057", "Bust A Move Deluxe" },
{ "ULUS10218", "Call of Duty - Roads to Victory" },
{ "ULUS10351", "Code Lyoko - Quest for Infinity" },
{ "NPJH50583", "Conception - Please have my children!" },
{ "ULUS10044", "Crash Tag Team Racing" },
{ "ULUS10100", "Def Jam Fight For NY - The Takeover" },
{ "NPJH50588", "Digimon World Re:Digitize" },
{ "ULUS10566", "Dissidia 012 Duodecim Final Fantasy" },
{ "ULUS10437", "Dissidia Final Fantasy" },
{ "ULUS10081", "Dragon Ball Z - Shin Budokai" },
{ "ULUS10234", "Dragon Ball Z - Shin Budokai 2" },
{ "ULUS10537", "Dragon Ball Z - Tenkaichi Tag Team" },
//maybe we can crosslinks this 2 region to ULUS10537 not having the game to test
{ "ULJS00311", "Dragon Ball Z - Tenkaichi Tag Team" },
{ "NPJH90135", "Dragon Ball Z - Tenkaichi Tag Team" },
{ "ULJM05127", "Dragon Quest & Final Fantasy in Itadaki Street Special" },
{ "ULES00847", "Dungeon Explorer - Warriors of Ancient Arts" },
{ "ULUS10177", "Dungeon Siege - Throne of Agony" },
{ "ULUS10170", "Dynasty Warrior 2" },
//looks like can be crosslinked too
{ "ULES01221", "Dynasty Warriors - Strike Force" },
{ "ULUS10416", "Dynasty Warriors - Strike Force" },
{ "UCUS98701", "Everybody's Tennis" },
{ "UCUS98740", "Fat Princess - Fistful of Cake" },
{ "ULJM05360", "Fate Tiger Colosseum Upper" },
{ "ULUS10297", "Final Fantasy Tactics - The War of the Lions" },
{ "ULES00850", "Final Fantasy Tactics - War of the Lions" },
{ "NPJH50443", "Final Fantasy Type 0" },
{ "NPJH50468", "Frontier Gate" },
{ "NPJH50721", "Frontier Gate Boost+" },
{ "ULES01432", "Full Metal Alchemist - Brotherhood" },
{ "ULUS10490", "GTA Chinatown Wars" },
{ "ULUS10160", "GTA Vice City Stories" },
{ "ULUS10210", "Ghost Rider" },
{ "ULJS00237", "God Eater" },
{ "NPJH50832", "God Eater 2" },
{ "ULUS10563", "God Eater Burst" },
{ "UCUS98632", "Gran Turismo" },
{ "NPJH50107", "Gundam VS Gundam - Next Plus" },
{ "ULJM05933", "Hatsune Miku - Project Diva Extend" },
{ "ULUS10298", "Hot Pixel" },
{ "ULJM05709", "K-ON! Houkago Live" },
{ "NPJH50221", "Kateikyoushi Hitman Reborn! Kizuna no Tag Battle" },
{ "ULJS00165", "Kidou Senshi Gundam - Gundam vs. Gundam" },
{ "UCUS98646", "Killzone Liberation" },
{ "ULJM05775", "Kingdom Hearts - Birth by Sleep Final Mix" },
{ "ULUS10487", "LEGO Indiana Jones 2" },
{ "NPJH50503", "Lord of Apocalypse" },
{ "ULES01507", "Lord of Arcana" },
{ "ULUS10180", "M.A.C.H. - Modified Air Combat Heroes" },
{ "UCUS98758", "MLB11 - The Show" },
{ "ULUS10581", "Madden NFL 12" },
{ "ULJS00385", "Mahou Shoujo Nanoha A's Portable - The Gears of Destiny" },
{ "ULUS10408", "Mana Khemia Student Alliance" },
{ "ULUS10141", "Medal Of Honor Heroes" },
{ "NPJH50045", "Metal Gear Solid - Peace Walker" },
{ "ULUS10202", "Metal Gear Solid - Portable Ops" },
{ "ULUS10290", "Metal Gear Solid - Portable Ops +" },
{ "ULUS10154", "Metal Slug Anthology" },
{ "ULUS10495", "Metal Slug XX" },
{ "ULES01429", "Metal Slug XX" },
{ "ULES00368", "Micro Machines V4" },
{ "ULUS10383", "Midnight Club - LA Remix" },
{ "UCUS98741", "Mod Nation Racers" },
{ "ULUS10084", "Monster Hunter Freedom" },
{ "ULUS10266", "Monster Hunter Freedom 2" },
{ "ULUS10391", "Monster Hunter Freedom Unite" },
{ "ULJM05800", "Monster Hunter Portable 3rd" },
{ "ULJM06097", "Musou Orochi 2 Special" },
{ "ULUS10340", "N+" },
{ "ULES01578", "NBA 2K13" },
{ "ULUS10598", "NBA 2K13" },
{ "ULUS10349", "Naruto - Ultimate Ninja Heroes 2" },
{ "ULUS10518", "Naruto - Ultimate Ninja Heroes 3" },
{ "ULJS00236", "Naruto - Accel 3" },
{ "ULUS10582", "Naruto Shippuden - Ultimate Ninja Impact" },
{ "ULES01537", "Naruto Shippuden - Ultimate Ninja Impact" },
{ "ULUS10571", "Naruto Shippuden - Kizuna Drive" },
{ "ULES00196", "Need For Speed - Most Wanted" },
{ "ULUS10036", "Need For Speed - Most Wanted" },
{ "ULUS10376", "Need for Speed - Undercover" },
{ "ULKS46004", "Need for Speed - Underground Rivals" },
{ "ULES01340", "Obscure - The Aftermath" },
{ "ULUS10064", "Outrun 2006 - Coast 2 Coast" },
{ "ULUS10586", "PRO Evolution Soccer 2012" },
{ "ULUS10149", "Pac Man - World Rally" },
{ "ULUS10438", "Pangya! - Fantasy Golf" },
{ "UCUS98732", "Patapon 2" },
{ "UCUS98751", "Patapon 3" },
{ "ULUS10410", "Phantasy Star Portable" },
{ "ULUS10529", "Phantasy Star Portable 2" },
//looks like this japan version can crosslink to ULUS10529
{ "NPJH50332", "Phantasy Star Portable 2" },
{ "ULJM05732", "Phantasy Star Portable 2 - Infinity" },
{ "ULES01596", "Pro Evolution Soccer 2014" },
{ "ULES01595", "Pro Evolution Soccer 2015" },
{ "NPJH50520", "Pro Yakyuu Spirits 2012" },
{ "NPJH50838", "Pro Yakyuu Spirits 2014" },
{ "NPJH50492", "Puyo Puyo!! 20th Anniversary" },
{ "ULUS10292", "Renegrade Squadron" },
{ "UCJS10090", "Resistance - Retribution" },
{ "ULES00670", "Rocky Balboa" },
{ "ULJS00360", "Rurouni Kenshin - Meiji Kenkaku Romantan Saisen" },
{ "UCUS98615", "SOCOM - Fireteam Bravo" },
{ "UCUS98645", "SOCOM - Fireteam Bravo 2" },
{ "UCUS98716", "SOCOM - Fireteam Bravo 3" },
{ "NPJH50460", "Sengoku Basara - Chronicles Heroes" },
{ "ULJM05436", "Sengoku Basara - Battle Heroes" },
{ "ULJM05637", "Shin Sangoku Musou - Multi Raid 2" },
{ "ULJM05035", "Shinobido - Tales of the Ninja" },
{ "ULUS10194", "Shrek - Smash and Crash Racing" },
{ "UCES00758", "Smash Court Tennis 3" },
{ "ULUS10195", "Sonic Rivals" },
{ "ULUS10457", "Soul Calibur - Broken Destiny" },
{ "ULUS10513", "Split Second - Velocity" },
{ "ULES00183", "Star Wars Battle Front 2" },
{ "ULUS10062", "Street Fighter Alpha 3 MAX" },
{ "NPUH10020", "Strikers 1945 Plus Portable" },
{ "ULUS10548", "TRON - Evolution" },
{ "NPJH50426", "Taiko no Tatsujin Portable DX" },
{ "ULUS10466", "Tekken 6" },
{ "NPJH50691", "Tokusatsu University" },
//looks like can be crosslinked
{ "ULUS10445", "Tom Clancy's Ghost Recon - Predator" },
{ "ULES01350", "Tom Clancy's Ghost Recon - Predator" },
{ "NPJH50789", "Toukiden" },
{ "NPJH50878", "Toukiden - Kiwami" },
{ "UCUS98601", "Twisted Metal - Head On" },
{ "ULUS10508", "UFC Undisputed 2010" },
{ "ULJS00069", "Ultraman Fighting Evo Zero" },
{ "ULUS10003", "Untold Legends - Brotherhood of the Blade" },
{ "ULUS10086", "Untold Legends - The Warrior's Code" },
{ "ULUS10515", "Valkryia Chronicles 2" },
{ "ULUS10087", "Viewtiful Joe" },
{ "ULUS10246", "Virtua Tennis 3" },
{ "ULUS82741", "WWE 2K14" },
{ "ULUS10543", "WWE Smackdown vs. Raw 2011" },
{ "ULUS10423", "Warriors Orochi 2" },
{ "ULJM05553", "Warship Gunner 2 Portable" },
{ "ULJS00155", "Way Of The Samurai" },
{ "UCES00465", "Wipeout Pulse" },
{ "ULUS10321", "World Series of Poker 2008 - Battle for the Bracelets" },
{ "NPUH10045", "Worms Battle Islands" },
{ "ULUS10065", "Worms Open Warfare" },
{ "ULUS10260", "Worms Open Warfare 2" },
{ "ULJM05734", "Yu-Gi-Oh! 5D's Tag Force 5" },
{ "ULJM05940", "Yu-Gi-Oh! 5D's Tag Force 6" },
{ "NPJH00142", "Yu-Gi-Oh! Arc-V Tag Force" },
{ "ULJM05151", "Yu-Gi-Oh! GX Tag Force" },
{ "ULJM05373", "Yu-Gi-Oh! GX Tag Force 3" },
{ "NPUG80086", "flOw" },
};
// Function Prototypes
const char * strcpyxml(char * out, const char * in, uint32_t size);
// Function Prototypes
void interrupt(int sig);
void enable_address_reuse(int fd);
void enable_keepalive(int fd);
void change_nodelay_mode(int fd, int flag);
void change_blocking_mode(int fd, int nonblocking);
int create_listen_socket(uint16_t port);
int server_loop(int server);
void __AdhocServerInit() {
// Database Product name will update if new game region played on my server to list possible crosslinks
productids = std::vector<db_productid>(default_productids, default_productids + ARRAY_SIZE(default_productids));
crosslinks = std::vector<db_crosslink>(default_crosslinks, default_crosslinks + ARRAY_SIZE(default_crosslinks));
}
/**
* Login User into Database (Stream)
* @param fd Socket
* @param ip IP Address (Network Order)
*/
void login_user_stream(int fd, uint32_t ip)
{
// Enough Space available
if(_db_user_count < SERVER_USER_MAXIMUM)
{
// Check IP Duplication
SceNetAdhocctlUserNode * u = _db_user;
while(u != NULL && u->resolver.ip != ip) u = u->next;
if (u != NULL) { // IP Already existed
WARN_LOG(SCENET, "AdhocServer: Already Existing IP: %s\n", ip2str(*(in_addr*)&u->resolver.ip).c_str());
}
// Unique IP Address
else //if(u == NULL)
{
// Allocate User Node Memory
SceNetAdhocctlUserNode * user = (SceNetAdhocctlUserNode *)malloc(sizeof(SceNetAdhocctlUserNode));
// Allocated User Node Memory
if(user != NULL)
{
// Clear Memory
memset(user, 0, sizeof(SceNetAdhocctlUserNode));
// Save Socket
user->stream = fd;
// Save IP
user->resolver.ip = ip;
// Link into User List
user->next = _db_user;
if(_db_user != NULL) _db_user->prev = user;
_db_user = user;
// Initialize Death Clock
user->last_recv = time(NULL);
// Notify User
INFO_LOG(SCENET, "AdhocServer: New Connection from %s", ip2str(*(in_addr*)&user->resolver.ip).c_str());
// Fix User Counter
_db_user_count++;
// Update Status Log
update_status();
// Exit Function
return;
}
}
}
// Duplicate IP, Allocation Error or not enough space - Close Stream
closesocket(fd);
}
/**
* Login User into Database (Login Data)
* @param user User Node
* @param data Login Packet
*/
void login_user_data(SceNetAdhocctlUserNode * user, SceNetAdhocctlLoginPacketC2S * data)
{
// Product Code Check
int valid_product_code = 1;
// Iterate Characters
int i = 0; for(; i < PRODUCT_CODE_LENGTH && valid_product_code == 1; i++)
{
// Valid Characters
if(!((data->game.data[i] >= 'A' && data->game.data[i] <= 'Z') || (data->game.data[i] >= '0' && data->game.data[i] <= '9'))) valid_product_code = 0;
}
// Valid Packet Data
if(valid_product_code == 1 && memcmp(&data->mac, "\xFF\xFF\xFF\xFF\xFF\xFF", sizeof(data->mac)) != 0 && memcmp(&data->mac, "\x00\x00\x00\x00\x00\x00", sizeof(data->mac)) != 0 && data->name.data[0] != 0)
{
// Check for duplicated MAC as most games identify Players by MAC
SceNetAdhocctlUserNode* u = _db_user;
while (u != NULL && !IsMatch(u->resolver.mac, data->mac)) u = u->next;
if (u != NULL) { // MAC Already existed
WARN_LOG(SCENET, "AdhocServer: Already Existing MAC: %s [%s]\n", mac2str(&data->mac).c_str(), ip2str(*(in_addr*)&u->resolver.ip).c_str());
}
// Game Product Override
game_product_override(&data->game);
// Find existing Game
SceNetAdhocctlGameNode * game = _db_game;
while(game != NULL && strncmp(game->game.data, data->game.data, PRODUCT_CODE_LENGTH) != 0) game = game->next;
// Game not found
if(game == NULL)
{
// Allocate Game Node Memory
game = (SceNetAdhocctlGameNode *)malloc(sizeof(SceNetAdhocctlGameNode));
// Allocated Game Node Memory
if(game != NULL)
{
// Clear Memory
memset(game, 0, sizeof(SceNetAdhocctlGameNode));
// Save Game Product ID
game->game = data->game;
// Link into Game List
game->next = _db_game;
if(_db_game != NULL) _db_game->prev = game;
_db_game = game;
}
}
// Game now available
if(game != NULL)
{
// Save MAC
user->resolver.mac = data->mac;
// Save Nickname
user->resolver.name = data->name;
// Increase Player Count in Game Node
game->playercount++;
// Link Game to Player
user->game = game;
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, game->game.data, PRODUCT_CODE_LENGTH);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) started playing %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr);
// Update Status Log
update_status();
// Leave Function
return;
}
}
// Invalid Packet Data
else
{
// Notify User
WARN_LOG(SCENET, "AdhocServer: Invalid Login Packet Contents from %s", ip2str(*(in_addr*)&user->resolver.ip).c_str());
}
// Logout User - Out of Memory or Invalid Arguments
logout_user(user);
}
/**
* Logout User from Database
* @param user User Node
*/
void logout_user(SceNetAdhocctlUserNode * user)
{
// Disconnect from Group
if(user->group != NULL) disconnect_user(user);
// Unlink Leftside (Beginning)
if(user->prev == NULL) _db_user = user->next;
// Unlink Leftside (Other)
else user->prev->next = user->next;
// Unlink Rightside
if(user->next != NULL) user->next->prev = user->prev;
// Close Stream
closesocket(user->stream);
// Playing User
if(user->game != NULL)
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) stopped playing %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr);
// Fix Game Player Count
user->game->playercount--;
// Empty Game Node
if(user->game->playercount == 0)
{
// Unlink Leftside (Beginning)
if(user->game->prev == NULL) _db_game = user->game->next;
// Unlink Leftside (Other)
else user->game->prev->next = user->game->next;
// Unlink Rightside
if(user->game->next != NULL) user->game->next->prev = user->game->prev;
// Free Game Node Memory
free(user->game);
}
}
// Unidentified User
else
{
// Notify User
WARN_LOG(SCENET, "AdhocServer: Dropped Connection to %s", ip2str(*(in_addr*)&user->resolver.ip).c_str());
}
// Free Memory
free(user);
// Fix User Counter
_db_user_count--;
// Update Status Log
update_status();
}
/**
* Free Database Memory
*/
void free_database()
{
// There are users playing
if(_db_user_count > 0)
{
// Send Shutdown Notice
spread_message(NULL, SERVER_SHUTDOWN_MESSAGE);
}
// Iterate Users for Deletion
SceNetAdhocctlUserNode * user = _db_user;
while(user != NULL)
{
// Next User (for safe delete)
SceNetAdhocctlUserNode * next = user->next;
// Logout User
logout_user(user);
// Move Pointer
user = next;
}
}
/**
* Connect User to Game Group
* @param user User Node
* @param group Group Name
*/
void connect_user(SceNetAdhocctlUserNode * user, SceNetAdhocctlGroupName * group)
{
// Group Name Check
int valid_group_name = 1;
{
// Iterate Characters
int i = 0; for(; i < ADHOCCTL_GROUPNAME_LEN && valid_group_name == 1; i++)
{
// End of Name
if(group->data[i] == 0) break;
// A - Z
if(group->data[i] >= 'A' && group->data[i] <= 'Z') continue;
// a - z
if(group->data[i] >= 'a' && group->data[i] <= 'z') continue;
// 0 - 9
if(group->data[i] >= '0' && group->data[i] <= '9') continue;
// Invalid Symbol
valid_group_name = 0;
}
}
// Valid Group Name
if(valid_group_name == 1)
{
// User is disconnected
if(user->group == NULL)
{
// Find Group in Game Node
SceNetAdhocctlGroupNode * g = user->game->group;
while(g != NULL && strncmp((char *)g->group.data, (char *)group->data, ADHOCCTL_GROUPNAME_LEN) != 0) g = g->next;
// BSSID Packet
SceNetAdhocctlConnectBSSIDPacketS2C bssid;
// Set BSSID Opcode
bssid.base.opcode = OPCODE_CONNECT_BSSID;
// Set Default BSSID
bssid.mac = user->resolver.mac;
// No Group found
if(g == NULL)
{
// Allocate Group Memory
g = (SceNetAdhocctlGroupNode *)malloc(sizeof(SceNetAdhocctlGroupNode));
// Allocated Group Memory
if(g != NULL)
{
// Clear Memory
memset(g, 0, sizeof(SceNetAdhocctlGroupNode));
// Link Game Node
g->game = user->game;
// Link Group Node
g->next = g->game->group;
if(g->game->group != NULL) g->game->group->prev = g;
g->game->group = g;
// Copy Group Name
g->group = *group;
// Increase Group Counter for Game
g->game->groupcount++;
}
}
// Group now available
if(g != NULL)
{
// Iterate remaining Group Players
SceNetAdhocctlUserNode * peer = g->player;
while(peer != NULL)
{
// Connect Packet
SceNetAdhocctlConnectPacketS2C packet;
// Clear Memory
// memset(&packet, 0, sizeof(packet));
// Set Connect Opcode
packet.base.opcode = OPCODE_CONNECT;
// Set Player Name
packet.name = user->resolver.name;
// Set Player MAC
packet.mac = user->resolver.mac;
// Set Player IP
packet.ip = user->resolver.ip;
// Send Data
int iResult = send(peer->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: connect_user[send peer] (Socket error %d)", errno);
// Set Player Name
packet.name = peer->resolver.name;
// Set Player MAC
packet.mac = peer->resolver.mac;
// Set Player IP
packet.ip = peer->resolver.ip;
// Send Data
iResult = send(user->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: connect_user[send user] (Socket error %d)", errno);
// Set BSSID
if(peer->group_next == NULL) bssid.mac = peer->resolver.mac;
// Move Pointer
peer = peer->group_next;
}
// Link User to Group
user->group_next = g->player;
if(g->player != NULL) g->player->group_prev = user;
g->player = user;
// Link Group to User
user->group = g;
// Increase Player Count
g->playercount++;
// Send Network BSSID to User
int iResult = send(user->stream, (const char*)&bssid, sizeof(bssid), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: connect_user[send user bssid] (Socket error %d)", errno);
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)user->group->group.data, ADHOCCTL_GROUPNAME_LEN);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) joined %s group %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr, safegroupstr);
// Update Status Log
update_status();
// Exit Function
return;
}
}
// Already connected to another group
else
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)group->data, ADHOCCTL_GROUPNAME_LEN);
char safegroupstr2[9];
memset(safegroupstr2, 0, sizeof(safegroupstr2));
strncpy(safegroupstr2, (char *)user->group->group.data, ADHOCCTL_GROUPNAME_LEN);
WARN_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) attempted to join %s group %s without disconnecting from %s first", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr, safegroupstr, safegroupstr2);
}
}
// Invalid Group Name
else
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)group->data, ADHOCCTL_GROUPNAME_LEN);
WARN_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) attempted to join invalid %s group %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr, safegroupstr);
}
// Invalid State, Out of Memory or Invalid Group Name
logout_user(user);
}
/**
* Disconnect User from Game Group
* @param user User Node
*/
void disconnect_user(SceNetAdhocctlUserNode * user)
{
// User is connected
if(user->group != NULL)
{
// Unlink Leftside (Beginning)
if(user->group_prev == NULL) user->group->player = user->group_next;
// Unlink Leftside (Other)
else user->group_prev->group_next = user->group_next;
// Unlink Rightside
if(user->group_next != NULL) user->group_next->group_prev = user->group_prev;
// Fix Player Count
user->group->playercount--;
// Iterate remaining Group Players
SceNetAdhocctlUserNode * peer = user->group->player;
while(peer != NULL)
{
// Disconnect Packet
SceNetAdhocctlDisconnectPacketS2C packet;
// Clear Memory
// memset(&packet, 0, sizeof(packet));
// Set Disconnect Opcode
packet.base.opcode = OPCODE_DISCONNECT;
// Set User IP
packet.ip = user->resolver.ip;
// Send Data
int iResult = send(peer->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: disconnect_user[send peer] (Socket error %d)", errno);
// Move Pointer
peer = peer->group_next;
}
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)user->group->group.data, ADHOCCTL_GROUPNAME_LEN);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) left %s group %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr, safegroupstr);
// Empty Group
if(user->group->playercount == 0)
{
// Unlink Leftside (Beginning)
if(user->group->prev == NULL) user->group->game->group = user->group->next;
// Unlink Leftside (Other)
else user->group->prev->next = user->group->next;
// Unlink Rightside
if(user->group->next != NULL) user->group->next->prev = user->group->prev;
// Free Group Memory
free(user->group);
// Decrease Group Counter in Game Node
user->game->groupcount--;
}
// Unlink from Group
user->group = NULL;
user->group_next = NULL;
user->group_prev = NULL;
// Update Status Log
update_status();
// Exit Function
return;
}
// Not in a game group
else
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
WARN_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) attempted to leave %s group without joining one first", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr);
}
// Delete User
logout_user(user);
}
/**
* Send Game Group List
* @param user User Node
*/
void send_scan_results(SceNetAdhocctlUserNode * user)
{
// User is disconnected
if(user->group == NULL)
{
// Iterate Groups
SceNetAdhocctlGroupNode * group = user->game->group;
for(; group != NULL; group = group->next)
{
// Scan Result Packet
SceNetAdhocctlScanPacketS2C packet;
// Clear Memory
// memset(&packet, 0, sizeof(packet));
// Set Opcode
packet.base.opcode = OPCODE_SCAN;
// Set Group Name
packet.group = group->group;
// Iterate Players in Network Group
SceNetAdhocctlUserNode * peer = group->player;
for(; peer != NULL; peer = peer->group_next)
{
// Found Network Founder
if(peer->group_next == NULL)
{
// Set Group Host MAC
packet.mac = peer->resolver.mac;
}
}
// Send Group Packet
int iResult = send(user->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: send_scan_result[send user] (Socket error %d)", errno);
}
// Notify Player of End of Scan
uint8_t opcode = OPCODE_SCAN_COMPLETE;
int iResult = send(user->stream, (const char*)&opcode, 1, MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: send_scan_result[send peer complete] (Socket error %d)", errno);
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) requested information on %d %s groups", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), user->game->groupcount, safegamestr);
// Exit Function
return;
}
// User in a game group
else
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)user->group->group.data, ADHOCCTL_GROUPNAME_LEN);
WARN_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) attempted to scan for %s groups without disconnecting from %s first", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr, safegroupstr);
}
// Delete User
logout_user(user);
}
/**
* Spread Chat Message in P2P Network
* @param user Sender User Node
* @param message Chat Message
*/
void spread_message(SceNetAdhocctlUserNode *user, const char *message)
{
// Global Notice
if(user == NULL)
{
// Iterate Players
for(user = _db_user; user != NULL; user = user->next)
{
// Player has access to chat
if(user->group != NULL)
{
// Chat Packet
SceNetAdhocctlChatPacketS2C packet;
// Clear Memory
memset(&packet, 0, sizeof(packet));
// Set Chat Opcode
packet.base.base.opcode = OPCODE_CHAT;
// Set Chat Message
strcpy(packet.base.message, message);
// Send Data
int iResult = send(user->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: spread_message[send user chat] (Socket error %d)", errno);
}
}
// Prevent NULL Error
return;
}
// User is connected
else if(user->group != NULL)
{
// Broadcast Range Counter
uint32_t counter = 0;
// Iterate Group Players
SceNetAdhocctlUserNode * peer = user->group->player;
while(peer != NULL)
{
// Skip Self
if(peer == user)
{
// Move Pointer
peer = peer->group_next;
// Continue Loop
continue;
}
// Chat Packet
SceNetAdhocctlChatPacketS2C packet;
// Set Chat Opcode
packet.base.base.opcode = OPCODE_CHAT;
// Set Chat Message
strcpy(packet.base.message, message);
// Set Sender Nickname
packet.name = user->resolver.name;
// Send Data
int iResult = send(peer->stream, (const char*)&packet, sizeof(packet), MSG_NOSIGNAL);
if (iResult < 0) ERROR_LOG(SCENET, "AdhocServer: spread_message[send peer chat] (Socket error %d)", errno);
// Move Pointer
peer = peer->group_next;
// Increase Broadcast Range Counter
counter++;
}
// Message Sent
if(counter > 0)
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
char safegroupstr[9];
memset(safegroupstr, 0, sizeof(safegroupstr));
strncpy(safegroupstr, (char *)user->group->group.data, ADHOCCTL_GROUPNAME_LEN);
INFO_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) sent \"%s\" to %d players in %s group %s", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), message, counter, safegamestr, safegroupstr);
}
// Exit Function
return;
}
// User not in a game group
else
{
// Notify User
char safegamestr[10];
memset(safegamestr, 0, sizeof(safegamestr));
strncpy(safegamestr, user->game->game.data, PRODUCT_CODE_LENGTH);
WARN_LOG(SCENET, "AdhocServer: %s (MAC: %s - IP: %s) attempted to send a text message without joining a %s group first", (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str(), safegamestr);
}
// Delete User
logout_user(user);
}
/**
* Get User State
* @param user User Node
*/
int get_user_state(SceNetAdhocctlUserNode * user)
{
// Timeout Status
if((time(NULL) - user->last_recv) >= SERVER_USER_TIMEOUT) return USER_STATE_TIMED_OUT;
// Waiting Status
if(user->game == NULL) return USER_STATE_WAITING;
// Logged-In Status
return USER_STATE_LOGGED_IN;
}
/**
* Clear RX Buffer
* @param user User Node
* @param clear Number of Bytes to clear (-1 for all)
*/
void clear_user_rxbuf(SceNetAdhocctlUserNode * user, int clear)
{
// Fix Clear Length
if(clear == -1 || clear > (int)user->rxpos) clear = user->rxpos;
// Move Buffer
memmove(user->rx, user->rx + clear, sizeof(user->rx) - clear);
// Fix RX Buffer Pointer
user->rxpos -= clear;
}
/**
* Patch Game Product Code
* @param product To-be-patched Product Code
* @param from If the Product Code matches this...
* @param to ... then change it to this one.
*/
void game_product_relink(SceNetAdhocctlProductCode * product, char * from, char * to)
{
// Relink Region Code
if(strncmp(product->data, from, PRODUCT_CODE_LENGTH) == 0) strncpy(product->data, to, PRODUCT_CODE_LENGTH);
}
/**
* Game Product Override (used for mixing multi-region games)
* @param product IN: Source Product OUT: Override Product
*/
void game_product_override(SceNetAdhocctlProductCode * product)
{
// Safe Product Code
char productid[PRODUCT_CODE_LENGTH + 1];
// Prepare Safe Product Code
strncpy(productid, product->data, PRODUCT_CODE_LENGTH);
productid[PRODUCT_CODE_LENGTH] = 0;
// Database Handle
//sqlite3 * db = NULL;
// Open Database
//if(sqlite3_open(SERVER_DATABASE, &db) == SQLITE_OK)
{
// Crosslinked Flag
int crosslinked = 0;
// Exists Flag
int exists = 0;
// SQL Statements
/*const char * sql = "SELECT id_to FROM crosslinks WHERE id_from=?;";
const char * sql2 = "SELECT * FROM productids WHERE id=?;";
const char * sql3 = "INSERT INTO productids(id, name) VALUES(?, ?);";
// Prepared SQL Statement
sqlite3_stmt * statement = NULL;
// Prepare SQL Statement
if(sqlite3_prepare_v2(db, sql, strlen(sql) + 1, &statement, NULL) == SQLITE_OK)
{
// Bind SQL Statement Data
if(sqlite3_bind_text(statement, 1, productid, strlen(productid), SQLITE_STATIC) == SQLITE_OK)
{
// Found Matching Row
if(sqlite3_step(statement) == SQLITE_ROW)
{
// Grab Crosslink ID
const char * crosslink = (const char *)sqlite3_column_text(statement, 0);
// Crosslink Product Code
strncpy(product->data, crosslink, PRODUCT_CODE_LENGTH);
// Log Crosslink
INFO_LOG(SCENET, "Crosslinked %s to %s", productid, crosslink);
// Set Crosslinked Flag
crosslinked = 1;
}
}
// Destroy Prepared SQL Statement
sqlite3_finalize(statement);
}*/
for (std::vector<db_crosslink>::iterator it = crosslinks.begin(); it != crosslinks.end(); it++) {
if (IsMatch(it->id_from, productid)) {
// Grab Crosslink ID
char crosslink[PRODUCT_CODE_LENGTH + 1];
strncpy(crosslink, it->id_to, PRODUCT_CODE_LENGTH);
crosslink[PRODUCT_CODE_LENGTH] = 0; // null terminated
// Crosslink Product Code
strncpy(product->data, it->id_to, PRODUCT_CODE_LENGTH);
// Log Crosslink
INFO_LOG(SCENET, "AdhocServer: Crosslinked %s to %s", productid, crosslink);
// Set Crosslinked Flag
crosslinked = 1;
break;
}
}
// Not Crosslinked
if(!crosslinked)
{
// Prepare SQL Statement
/*if(sqlite3_prepare_v2(db, sql2, strlen(sql2) + 1, &statement, NULL) == SQLITE_OK)
{
// Bind SQL Statement Data
if(sqlite3_bind_text(statement, 1, productid, strlen(productid), SQLITE_STATIC) == SQLITE_OK)
{
// Found Matching Row
if(sqlite3_step(statement) == SQLITE_ROW)
{
// Set Exists Flag
exists = 1;
}
}
// Destroy Prepare SQL Statement
sqlite3_finalize(statement);
}*/
for (std::vector<db_productid>::iterator it = productids.begin(); it != productids.end(); it++) {
if (IsMatch(it->id, productid)) {
// Set Exists Flag
exists = 1;
break;
}
}
// Game doesn't exist in Database
if(!exists)
{
// Prepare SQL Statement
/*if(sqlite3_prepare_v2(db, sql3, strlen(sql3) + 1, &statement, NULL) == SQLITE_OK)
{
// Bind SQL Statement Data
if(sqlite3_bind_text(statement, 1, productid, strlen(productid), SQLITE_STATIC) == SQLITE_OK && sqlite3_bind_text(statement, 2, productid, strlen(productid), SQLITE_STATIC) == SQLITE_OK)
{
// Save Product ID to Database
if(sqlite3_step(statement) == SQLITE_DONE)
{
// Log Addition
INFO_LOG(SCENET, "Added Unknown Product ID %s to Database", productid);
}
}
// Destroy Prepare SQL Statement
sqlite3_finalize(statement);
}*/
db_productid unkproduct;
strncpy(unkproduct.id, productid, sizeof(unkproduct.id));
strncpy(unkproduct.name, productid, sizeof(productid));
productids.push_back(unkproduct); //productids[productids.size()] = unkproduct;
// Log Addition
INFO_LOG(SCENET, "AdhocServer: Added Unknown Product ID %s to Database", productid);
}
}
// Close Database
//sqlite3_close(db);
}
}
/**
* Update Status Logfile
*/
void update_status()
{
// Open Logfile
FILE * log = File::OpenCFile(Path(SERVER_STATUS_XMLOUT), "w");
// Opened Logfile
if(log != NULL)
{
// Write XML Header
fprintf(log, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
// Write XSL Processor Information
fprintf(log, "<?xml-stylesheet type=\"text/xsl\" href=\"status.xsl\"?>\n");
// Output Root Tag + User Count
fprintf(log, "<prometheus usercount=\"%u\">\n", _db_user_count);
// Database Handle
//sqlite3 * db = NULL;
// Open Database
//if(sqlite3_open(SERVER_DATABASE, &db) == SQLITE_OK)
{
// Iterate Games
SceNetAdhocctlGameNode * game = _db_game; for(; game != NULL; game = game->next)
{
// Safe Product ID
char productid[PRODUCT_CODE_LENGTH + 1];
strncpy(productid, game->game.data, PRODUCT_CODE_LENGTH);
productid[PRODUCT_CODE_LENGTH] = 0;
// Display Name
char displayname[128];
memset(displayname, 0, sizeof(displayname));
// SQL Statement
//const char * sql = "SELECT name FROM productids WHERE id=?;";
// Prepared SQL Statement
//sqlite3_stmt * statement = NULL;
// Prepare SQL Statement
/*if(sqlite3_prepare_v2(db, sql, strlen(sql) + 1, &statement, NULL) == SQLITE_OK)
{
// Bind SQL Statement Data
if(sqlite3_bind_text(statement, 1, productid, strlen(productid), SQLITE_STATIC) == SQLITE_OK)
{
// Found Matching Row
if(sqlite3_step(statement) == SQLITE_ROW)
{
// Fetch Game Name from Database
const char * gamename = (const char *)sqlite3_column_text(statement, 0);
// Copy Game Name
strcpyxml(displayname, gamename, sizeof(displayname));
}
// Game not in Database
else
{
// Use Product Code as Name
strcpyxml(displayname, productid, sizeof(displayname));
}
}
// Destroy Prepared SQL Statement
sqlite3_finalize(statement);
}*/
//db_productid *foundid = NULL;
bool found = false;
for (std::vector<db_productid>::iterator it = productids.begin(); it != productids.end(); it++) {
if (IsMatch(it->id, productid)) {
// Copy Game Name
strcpyxml(displayname, it->name, sizeof(displayname));
found = true;
break;
}
}
if (!found) {
// Use Product Code as Name
strcpyxml(displayname, productid, sizeof(displayname));
}
// Output Game Tag + Game Name
fprintf(log, "\t<game name=\"%s\" usercount=\"%u\">\n", displayname, game->playercount);
// Activate User Count
uint32_t activecount = 0;
// Iterate Game Groups
SceNetAdhocctlGroupNode * group = game->group; for(; group != NULL; group = group->next)
{
// Safe Group Name
char groupname[ADHOCCTL_GROUPNAME_LEN + 1];
strncpy(groupname, (const char *)group->group.data, ADHOCCTL_GROUPNAME_LEN);
groupname[ADHOCCTL_GROUPNAME_LEN] = 0;
// Output Group Tag + Group Name + User Count
fprintf(log, "\t\t<group name=\"%s\" usercount=\"%u\">\n", strcpyxml(displayname, groupname, sizeof(displayname)), group->playercount);
// Iterate Users
SceNetAdhocctlUserNode * user = group->player; for(; user != NULL; user = user->group_next)
{
// Output User Tag + Username
fprintf(log, "\t\t\t<user>%s</user>\n", strcpyxml(displayname, (const char *)user->resolver.name.data, sizeof(displayname)));
}
// Output Closing Group Tag
fprintf(log, "\t\t</group>\n");
// Increase Active Game User Count
activecount += group->playercount;
}
// Output Idle Game Group
if(game->playercount > activecount)
{
// Output Group Tag + Group Name + Idle User Count
fprintf(log, "\t\t<group name=\"Groupless\" usercount=\"%u\" />\n", game->playercount - activecount);
}
// Output Closing Game Tag
fprintf(log, "\t</game>\n");
}
// Close Database
//sqlite3_close(db);
}
// Output Closing Root Tag
fprintf(log, "</prometheus>");
// Close Logfile
fclose(log);
}
}
/**
* Escape XML Sequences to avoid malformed XML files.
* @param out Out Buffer
* @param in In Buffer
* @param size Size of Out Buffer
* @return Reference to Out Buffer
*/
const char * strcpyxml(char * out, const char * in, uint32_t size)
{
// Valid Arguments
if(out != NULL && in != NULL && size > 0)
{
// Clear Memory
memset(out, 0, size);
// Written Size Pointer
uint32_t written = 0;
// Iterate In-Buffer Symbols
uint32_t i = 0; for(; i < strlen(in); i++)
{
// " Symbol
if(in[i] == '"')
{
// Enough Space in Out-Buffer (6B for &quot;)
if((size - written) > 6)
{
// Write Escaped Sequence
strcpy(out + written, "&quot;");
// Move Pointer
written += 6;
}
// Truncate required
else break;
}
// < Symbol
else if(in[i] == '<')
{
// Enough Space in Out-Buffer (4B for &lt;)
if((size - written) > 4)
{
// Write Escaped Sequence
strcpy(out + written, "&lt;");
// Move Pointer
written += 4;
}
// Truncate required
else break;
}
// > Symbol
else if(in[i] == '>')
{
// Enough Space in Out-Buffer (4B for &gt;)
if((size - written) > 4)
{
// Write Escaped Sequence
strcpy(out + written, "&gt;");
// Move Pointer
written += 4;
}
// Truncate required
else break;
}
// & Symbol
else if(in[i] == '&')
{
// Enough Space in Out-Buffer (5B for &amp;)
if((size - written) > 5)
{
// Write Escaped Sequence
strcpy(out + written, "&amp;");
// Move Pointer
written += 5;
}
// Truncate required
else break;
}
// Normal Character
else
{
// Enough Space in Out-Buffer (1B)
if((size - written) > 1)
{
// Write Character
out[written++] = in[i];
}
}
}
// Return Reference
return out;
}
// Invalid Arguments
return NULL;
}
/**
* Server Entry Point
* @param argc Number of Arguments
* @param argv Arguments
* @return OS Error Code
*/
int proAdhocServerThread(int port) // (int argc, char * argv[])
{
SetCurrentThreadName("AdhocServer");
// Result
int result = 0;
INFO_LOG(SCENET, "AdhocServer: Begin of AdhocServer Thread");
// Create Signal Receiver for CTRL + C
//signal(SIGINT, interrupt);
// Create Signal Receiver for kill / killall
//signal(SIGTERM, interrupt);
// Create Listening Socket
int server = create_listen_socket(port); //SERVER_PORT
// Created Listening Socket
if(server != SOCKET_ERROR)
{
// Notify User
INFO_LOG(SCENET, "AdhocServer: Listening for Connections on TCP Port %u", port); //SERVER_PORT
// Port forward
UPnP_Add(IP_PROTOCOL_TCP, port); // g_PortManager.Add(IP_PROTOCOL_TCP, port);
// Enter Server Loop
result = server_loop(server);
// Remove Port mapping
UPnP_Remove(IP_PROTOCOL_TCP, port); // g_PortManager.Remove(IP_PROTOCOL_TCP, port);
// Notify User
INFO_LOG(SCENET, "AdhocServer: Shutdown complete");
}
//_status = 0;
adhocServerRunning = false;
INFO_LOG(SCENET, "AdhocServer: End of AdhocServer Thread");
// Return Result
return result;
}
/**
* Server Shutdown Request Handler
* @param sig Captured Signal
*/
void interrupt(int sig)
{
// Notify User
INFO_LOG(SCENET, "AdhocServer: Shutting down... please wait");
// Trigger Shutdown
//_status = 0;
adhocServerRunning = false;
}
/**
* Enable Address Reuse on Socket
* @param fd Socket
*/
void enable_address_reuse(int fd)
{
// Enable Port Reuse
setSockReuseAddrPort(fd);
}
/**
* Enable KeepAlive on Socket
* @param fd Socket
*/
void enable_keepalive(int fd)
{
// Enable Value
int on = 1;
// Enable Port Reuse
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(on));
}
/**
* Change TCP Socket TCP_NODELAY (Nagle Algo) mode
* @param fd Socket
* @param nonblocking 1 for Nonblocking, 0 for Blocking
*/
void change_nodelay_mode(int fd, int flag)
{
int opt = flag;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&opt, sizeof(opt));
}
/**
* Change Socket Blocking Mode
* @param fd Socket
* @param nonblocking 1 for Nonblocking, 0 for Blocking
*/
void change_blocking_mode(int fd, int nonblocking)
{
unsigned long on = 1;
unsigned long off = 0;
#ifdef _WIN32
if (nonblocking){
// Change to Non-Blocking Mode
ioctlsocket(fd, FIONBIO, &on);
}
else {
// Change to Blocking Mode
ioctlsocket(fd, FIONBIO, &off);
}
#else
// Change to Non-Blocking Mode
if(nonblocking) fcntl(fd, F_SETFL, O_NONBLOCK);
// Change to Blocking Mode
else
{
// Get Flags
int flags = fcntl(fd, F_GETFL);
// Remove Non-Blocking Flag
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
}
#endif
}
/**
* Create Port-Bound Listening Socket
* @param port TCP Port
* @return Socket Descriptor
*/
int create_listen_socket(uint16_t port)
{
// Create Socket
int fd = (int)socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Created Socket
if(fd != -1)
{
// Ignore SIGPIPE when supported (ie. BSD/MacOS)
setSockNoSIGPIPE(fd, 1);
// Enable KeepAlive
enable_keepalive(fd);
// Enable Address Reuse
enable_address_reuse(fd); // Shouldn't Reuse the port for built-in AdhocServer to prevent conflict with Dedicated AdhocServer
// Make Socket Nonblocking
change_blocking_mode(fd, 1);
// Make TCP Socket send immediately
change_nodelay_mode(fd, 1);
// Prepare Local Address Information
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(port);
// Should only bind to specific IP for the 2nd or more instance of PPSSPP to prevent communication interference issue when sharing the same port. (ie. Capcom Classics Collection Remixed)
if (PPSSPP_ID > 1) {
local.sin_addr = g_localhostIP.in.sin_addr;
}
// Bind Local Address to Socket
int bindresult = bind(fd, (struct sockaddr *)&local, sizeof(local));
// Bound Local Address to Socket
if(bindresult != SOCKET_ERROR)
{
// Switch Socket into Listening Mode
listen(fd, SERVER_LISTEN_BACKLOG);
// Return Socket
return fd;
}
// Notify User
else {
ERROR_LOG(SCENET, "AdhocServer: Bind returned %i (Socket error %d)", bindresult, errno);
auto n = GetI18NCategory("Networking");
host->NotifyUserMessage(std::string(n->T("AdhocServer Failed to Bind Port")) + " " + std::to_string(port), 3.0, 0x0000ff);
}
// Close Socket
closesocket(fd);
}
// Notify User
else ERROR_LOG(SCENET, "AdhocServer: Socket returned %i (Socket error %d)", fd, errno);
// Return Error
return -1;
}
/**
* Server Main Loop
* @param server Server Listening Socket
* @return OS Error Code
*/
int server_loop(int server)
{
// Set Running Status
//_status = 1;
adhocServerRunning = true;
// Create Empty Status Logfile
update_status();
// Handling Loop
while (adhocServerRunning) //(_status == 1)
{
// Login Block
{
// Login Result
int loginresult = 0;
// Login Processing Loop
do
{
// Prepare Address Structure
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, sizeof(addr));
// Accept Login Requests
// loginresult = accept4(server, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
// Alternative Accept Approach (some Linux Kernel don't support the accept4 Syscall... wtf?)
loginresult = accept(server, (struct sockaddr *)&addr, &addrlen);
if(loginresult != -1)
{
// Switch Socket into Non-Blocking Mode
change_blocking_mode(loginresult, 1);
}
// Login User (Stream)
if (loginresult != -1) {
u32_le sip = addr.sin_addr.s_addr;
/* // Replacing 127.0.0.x with Ethernet IP will cause issue with multiple-instance of localhost (127.0.0.x)
if (sip == 0x0100007f) { //127.0.0.1 should be replaced with LAN/WAN IP whenever available
char str[100];
gethostname(str, 100);
u8 *pip = (u8*)&sip;
if (gethostbyname(str)->h_addrtype == AF_INET && gethostbyname(str)->h_addr_list[0] != NULL) pip = (u8*)gethostbyname(str)->h_addr_list[0];
sip = *(u32_le*)pip;
WARN_LOG(SCENET, "AdhocServer: Replacing IP %s with %s", inet_ntoa(addr.sin_addr), inet_ntoa(*(in_addr*)&pip));
}
*/
login_user_stream(loginresult, sip);
}
} while(loginresult != -1);
}
// Receive Data from Users
SceNetAdhocctlUserNode * user = _db_user;
while(user != NULL)
{
// Next User (for safe delete)
SceNetAdhocctlUserNode * next = user->next;
// Receive Data from User
int recvresult = recv(user->stream, (char*)user->rx + user->rxpos, sizeof(user->rx) - user->rxpos, MSG_NOSIGNAL);
// Connection Closed or Timed Out
if(recvresult == 0 || (recvresult == -1 && errno != EAGAIN && errno != EWOULDBLOCK) || get_user_state(user) == USER_STATE_TIMED_OUT)
{
// Logout User
logout_user(user);
}
// Received Data (or leftovers in RX-Buffer)
else if(recvresult > 0 || user->rxpos > 0)
{
// New Incoming Data
if(recvresult > 0)
{
// Move RX Pointer
user->rxpos += recvresult;
// Update Death Clock
user->last_recv = time(NULL);
}
// Waiting for Login Packet
if(get_user_state(user) == USER_STATE_WAITING)
{
// Valid Opcode
if(user->rx[0] == OPCODE_LOGIN)
{
// Enough Data available
if(user->rxpos >= sizeof(SceNetAdhocctlLoginPacketC2S))
{
// Clone Packet
SceNetAdhocctlLoginPacketC2S packet = *(SceNetAdhocctlLoginPacketC2S *)user->rx;
// Remove Packet from RX Buffer
clear_user_rxbuf(user, sizeof(SceNetAdhocctlLoginPacketC2S));
// Login User (Data)
login_user_data(user, &packet);
}
}
// Invalid Opcode
else
{
// Notify User
WARN_LOG(SCENET, "AdhocServer: Invalid Opcode 0x%02X in Waiting State from %s", user->rx[0], ip2str(*(in_addr*)&user->resolver.ip).c_str());
// Logout User
logout_user(user);
}
}
// Logged-In User
else if(get_user_state(user) == USER_STATE_LOGGED_IN)
{
// Ping Packet
if(user->rx[0] == OPCODE_PING)
{
// Delete Packet from RX Buffer
clear_user_rxbuf(user, 1);
}
// Group Connect Packet
else if(user->rx[0] == OPCODE_CONNECT)
{
// Enough Data available
if(user->rxpos >= sizeof(SceNetAdhocctlConnectPacketC2S))
{
// Cast Packet
SceNetAdhocctlConnectPacketC2S * packet = (SceNetAdhocctlConnectPacketC2S *)user->rx;
// Clone Group Name
SceNetAdhocctlGroupName group = packet->group;
// Remove Packet from RX Buffer
clear_user_rxbuf(user, sizeof(SceNetAdhocctlConnectPacketC2S));
// Change Game Group
connect_user(user, &group);
}
}
// Group Disconnect Packet
else if(user->rx[0] == OPCODE_DISCONNECT)
{
// Remove Packet from RX Buffer
clear_user_rxbuf(user, 1);
// Leave Game Group
disconnect_user(user);
}
// Network Scan Packet
else if(user->rx[0] == OPCODE_SCAN)
{
// Remove Packet from RX Buffer
clear_user_rxbuf(user, 1);
// Send Network List
send_scan_results(user);
}
// Chat Text Packet
else if(user->rx[0] == OPCODE_CHAT)
{
// Enough Data available
if(user->rxpos >= sizeof(SceNetAdhocctlChatPacketC2S))
{
// Cast Packet
SceNetAdhocctlChatPacketC2S * packet = (SceNetAdhocctlChatPacketC2S *)user->rx;
// Clone Buffer for Message
char message[64];
memset(message, 0, sizeof(message));
strncpy(message, packet->message, sizeof(message) - 1);
// Remove Packet from RX Buffer
clear_user_rxbuf(user, sizeof(SceNetAdhocctlChatPacketC2S));
// Spread Chat Message
spread_message(user, message);
}
}
// Invalid Opcode
else
{
// Notify User
WARN_LOG(SCENET, "AdhocServer: Invalid Opcode 0x%02X in Logged-In State from %s (MAC: %s - IP: %s)", user->rx[0], (char *)user->resolver.name.data, mac2str(&user->resolver.mac).c_str(), ip2str(*(in_addr*)&user->resolver.ip).c_str());
// Logout User
logout_user(user);
}
}
}
// Move Pointer
user = next;
}
// Prevent needless CPU Overload (1ms Sleep)
sleep_ms(10);
// Don't do anything if it's paused, otherwise the log will be flooded
while (adhocServerRunning && Core_IsStepping() && coreState != CORE_POWERDOWN) sleep_ms(10);
}
// Free User Database Memory
free_database();
// Close Server Socket
closesocket(server);
// Return Success
return 0;
}