mirror of https://github.com/OpenTTD/OpenTTD
(svn r1322) -Add: banning system (mostly tnx to guru3)
A server can ban people via ClientList Both server and dedicated can do it via console: 'ban', 'unban', 'banlist'.release/0.4.5
parent
5d06584545
commit
a11f46fed4
|
@ -144,6 +144,87 @@ DEF_CONSOLE_CMD(ConScrollToTile)
|
||||||
// ********************************* //
|
// ********************************* //
|
||||||
#ifdef ENABLE_NETWORK
|
#ifdef ENABLE_NETWORK
|
||||||
|
|
||||||
|
DEF_CONSOLE_CMD(ConBan)
|
||||||
|
{
|
||||||
|
NetworkClientInfo *ci;
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
uint32 index = atoi(argv[1]);
|
||||||
|
if (index == NETWORK_SERVER_INDEX && !_network_dedicated) {
|
||||||
|
IConsolePrint(_iconsole_color_default, "Silly boy, you can not ban yourself!");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (index == 0) {
|
||||||
|
IConsoleError("Invalid Client-ID");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ci = NetworkFindClientInfoFromIndex(index);
|
||||||
|
|
||||||
|
if (ci != NULL) {
|
||||||
|
uint i;
|
||||||
|
/* Add user to ban-list */
|
||||||
|
for (i = 0; i < lengthof(_network_ban_list); i++) {
|
||||||
|
if (_network_ban_list[i] == NULL || _network_ban_list[i][0] == '\0') {
|
||||||
|
_network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ci->client_ip));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SEND_COMMAND(PACKET_SERVER_ERROR)(NetworkFindClientStateFromIndex(index), NETWORK_ERROR_KICKED);
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
IConsoleError("Client-ID not found");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: ban <client-id>. For client-ids, see 'clients'.");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_CONSOLE_CMD(ConUnBan)
|
||||||
|
{
|
||||||
|
if (argc == 2) {
|
||||||
|
uint i;
|
||||||
|
for (i = 0; i < lengthof(_network_ban_list); i++) {
|
||||||
|
if (_network_ban_list[i] == NULL || _network_ban_list[i][0] == '\0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strncmp(_network_ban_list[i], argv[1], strlen(_network_ban_list[i])) == 0) {
|
||||||
|
_network_ban_list[i][0] = '\0';
|
||||||
|
IConsolePrint(_iconsole_color_default, "IP unbanned.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IConsolePrint(_iconsole_color_default, "IP not in ban-list.");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
IConsolePrint(_iconsole_color_default, "Unknown usage. Usage: unban <ip>.");
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_CONSOLE_CMD(ConBanList)
|
||||||
|
{
|
||||||
|
uint i;
|
||||||
|
|
||||||
|
IConsolePrint(_iconsole_color_default, "Banlist: ");
|
||||||
|
|
||||||
|
for (i = 0; i < lengthof(_network_ban_list); i++) {
|
||||||
|
if (_network_ban_list[i] == NULL || _network_ban_list[i][0] == '\0')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
IConsolePrintF(_iconsole_color_default, " %d) %s", i + 1, _network_ban_list[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
DEF_CONSOLE_CMD(ConStatus)
|
DEF_CONSOLE_CMD(ConStatus)
|
||||||
{
|
{
|
||||||
const char *status;
|
const char *status;
|
||||||
|
@ -978,6 +1059,13 @@ void IConsoleStdLibRegister(void)
|
||||||
IConsoleCmdHook("status", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient);
|
IConsoleCmdHook("status", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient);
|
||||||
IConsoleCmdHook("resetengines", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetwork);
|
IConsoleCmdHook("resetengines", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetwork);
|
||||||
|
|
||||||
|
IConsoleCmdRegister("ban", ConBan);
|
||||||
|
IConsoleCmdHook("ban", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient);
|
||||||
|
IConsoleCmdRegister("unban", ConUnBan);
|
||||||
|
IConsoleCmdHook("unban", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient);
|
||||||
|
IConsoleCmdRegister("banlist", ConBanList);
|
||||||
|
IConsoleCmdHook("banlist", ICONSOLE_HOOK_ACCESS, ConCmdHookNoNetClient);
|
||||||
|
|
||||||
IConsoleAliasRegister("clean_company", "reset_company %A");
|
IConsoleAliasRegister("clean_company", "reset_company %A");
|
||||||
|
|
||||||
IConsoleVarRegister("net_frame_freq", &_network_frame_freq, ICONSOLE_VAR_UINT8);
|
IConsoleVarRegister("net_frame_freq", &_network_frame_freq, ICONSOLE_VAR_UINT8);
|
||||||
|
|
|
@ -1343,6 +1343,7 @@ STR_NETWORK_ERR_SERVER_ERROR :{WHITE} A protocol-error was made and the conn
|
||||||
STR_NETWORK_ERR_WRONG_REVISION :{WHITE} The revision of this client does not match the server's revision
|
STR_NETWORK_ERR_WRONG_REVISION :{WHITE} The revision of this client does not match the server's revision
|
||||||
STR_NETWORK_ERR_WRONG_PASSWORD :{WHITE} Wrong password
|
STR_NETWORK_ERR_WRONG_PASSWORD :{WHITE} Wrong password
|
||||||
STR_NETWORK_ERR_SERVER_FULL :{WHITE} The server is full
|
STR_NETWORK_ERR_SERVER_FULL :{WHITE} The server is full
|
||||||
|
STR_NETWORK_ERR_SERVER_BANNED :{WHITE} You are banned from this server
|
||||||
STR_NETWORK_ERR_KICKED :{WHITE} You were kicked out of the game
|
STR_NETWORK_ERR_KICKED :{WHITE} You were kicked out of the game
|
||||||
STR_NETWORK_ERR_CHEATER :{WHITE} Cheating is not allowed on this server
|
STR_NETWORK_ERR_CHEATER :{WHITE} Cheating is not allowed on this server
|
||||||
|
|
||||||
|
|
44
network.c
44
network.c
|
@ -219,7 +219,8 @@ static void NetworkClientError(byte res, NetworkClientState *cs) {
|
||||||
default: errorno = NETWORK_ERROR_GENERAL;
|
default: errorno = NETWORK_ERROR_GENERAL;
|
||||||
}
|
}
|
||||||
// This means we fucked up and the server closed the connection
|
// This means we fucked up and the server closed the connection
|
||||||
if (res != NETWORK_RECV_STATUS_SERVER_ERROR && res != NETWORK_RECV_STATUS_SERVER_FULL) {
|
if (res != NETWORK_RECV_STATUS_SERVER_ERROR && res != NETWORK_RECV_STATUS_SERVER_FULL &&
|
||||||
|
res != NETWORK_RECV_STATUS_SERVER_BANNED) {
|
||||||
SEND_COMMAND(PACKET_CLIENT_ERROR)(errorno);
|
SEND_COMMAND(PACKET_CLIENT_ERROR)(errorno);
|
||||||
|
|
||||||
// Dequeue all commands before closing the socket
|
// Dequeue all commands before closing the socket
|
||||||
|
@ -615,6 +616,8 @@ static void NetworkAcceptClients(void)
|
||||||
#else
|
#else
|
||||||
LONG sin_len; // for some reason we need a 'LONG' under MorphOS
|
LONG sin_len; // for some reason we need a 'LONG' under MorphOS
|
||||||
#endif
|
#endif
|
||||||
|
int i;
|
||||||
|
bool banned;
|
||||||
|
|
||||||
// Should never ever happen.. is it possible??
|
// Should never ever happen.. is it possible??
|
||||||
assert(_listensocket != INVALID_SOCKET);
|
assert(_listensocket != INVALID_SOCKET);
|
||||||
|
@ -639,6 +642,33 @@ static void NetworkAcceptClients(void)
|
||||||
{int b = 1; setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b));}
|
{int b = 1; setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const char*)&b, sizeof(b));}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Check if the client is banned */
|
||||||
|
banned = false;
|
||||||
|
for (i = 0; i < lengthof(_network_ban_list); i++) {
|
||||||
|
if (_network_ban_list[i] == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sin.sin_addr.s_addr == inet_addr(_network_ban_list[i])) {
|
||||||
|
Packet *p = NetworkSend_Init(PACKET_SERVER_BANNED);
|
||||||
|
|
||||||
|
DEBUG(net, 1)("[NET] Banned ip tried to join (%s), refused", _network_ban_list[i]);
|
||||||
|
|
||||||
|
p->buffer[0] = p->size & 0xFF;
|
||||||
|
p->buffer[1] = p->size >> 8;
|
||||||
|
|
||||||
|
send(s, p->buffer, p->size, 0);
|
||||||
|
closesocket(s);
|
||||||
|
|
||||||
|
free(p);
|
||||||
|
|
||||||
|
banned = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* If this client is banned, continue with next client */
|
||||||
|
if (banned)
|
||||||
|
continue;
|
||||||
|
|
||||||
cs = NetworkAllocClient(s);
|
cs = NetworkAllocClient(s);
|
||||||
if (cs == NULL) {
|
if (cs == NULL) {
|
||||||
// no more clients allowed?
|
// no more clients allowed?
|
||||||
|
@ -844,15 +874,15 @@ void NetworkAddServer(const byte *b)
|
||||||
* by the function that generates the config file. */
|
* by the function that generates the config file. */
|
||||||
void NetworkRebuildHostList()
|
void NetworkRebuildHostList()
|
||||||
{
|
{
|
||||||
int i=0;
|
int i = 0;
|
||||||
NetworkGameList *item = _network_game_list;
|
NetworkGameList *item = _network_game_list;
|
||||||
while (item != NULL && i!=lengthof(_network_host_list)) {
|
while (item != NULL && i != lengthof(_network_host_list)) {
|
||||||
if (item->manually)
|
if (item->manually)
|
||||||
_network_host_list[i++] = strdup(item->info.hostname);
|
_network_host_list[i++] = strdup(item->info.hostname);
|
||||||
item = item->next;
|
item = item->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i<lengthof(_network_host_list); i++) {
|
for (; i < lengthof(_network_host_list); i++) {
|
||||||
_network_host_list[i] = strdup("");
|
_network_host_list[i] = strdup("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1262,10 +1292,10 @@ void NetworkStartUp(void)
|
||||||
DEBUG(net, 3) ("[NET][Core] Starting network...");
|
DEBUG(net, 3) ("[NET][Core] Starting network...");
|
||||||
|
|
||||||
#if defined(__MORPHOS__) || defined(__AMIGA__)
|
#if defined(__MORPHOS__) || defined(__AMIGA__)
|
||||||
/*
|
/*
|
||||||
* IMPORTANT NOTE: SocketBase needs to be initialized before we use _any_
|
* IMPORTANT NOTE: SocketBase needs to be initialized before we use _any_
|
||||||
* network related function, else: crash.
|
* network related function, else: crash.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
DEBUG(misc,3) ("[NET][Core] Loading bsd socket library");
|
DEBUG(misc,3) ("[NET][Core] Loading bsd socket library");
|
||||||
if (!(SocketBase = OpenLibrary("bsdsocket.library", 4))) {
|
if (!(SocketBase = OpenLibrary("bsdsocket.library", 4))) {
|
||||||
|
@ -1291,7 +1321,7 @@ void NetworkStartUp(void)
|
||||||
#endif // __AMIGA__
|
#endif // __AMIGA__
|
||||||
}
|
}
|
||||||
#endif // __MORPHOS__ / __AMIGA__
|
#endif // __MORPHOS__ / __AMIGA__
|
||||||
|
|
||||||
// Network is available
|
// Network is available
|
||||||
_network_available = true;
|
_network_available = true;
|
||||||
_network_dedicated = false;
|
_network_dedicated = false;
|
||||||
|
|
|
@ -194,6 +194,8 @@ NetworkGameList *NetworkQueryServer(const byte* host, unsigned short port, bool
|
||||||
// Those variables must always be registered!
|
// Those variables must always be registered!
|
||||||
#define MAX_SAVED_SERVERS 10
|
#define MAX_SAVED_SERVERS 10
|
||||||
VARDEF char *_network_host_list[MAX_SAVED_SERVERS];
|
VARDEF char *_network_host_list[MAX_SAVED_SERVERS];
|
||||||
|
#define MAX_BANS 25
|
||||||
|
VARDEF char *_network_ban_list[MAX_BANS];
|
||||||
VARDEF bool _networking;
|
VARDEF bool _networking;
|
||||||
VARDEF bool _network_available; // is network mode available?
|
VARDEF bool _network_available; // is network mode available?
|
||||||
VARDEF bool _network_server; // network-server is active
|
VARDEF bool _network_server; // network-server is active
|
||||||
|
|
|
@ -263,6 +263,15 @@ DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_FULL)
|
||||||
return NETWORK_RECV_STATUS_SERVER_FULL;
|
return NETWORK_RECV_STATUS_SERVER_FULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_BANNED)
|
||||||
|
{
|
||||||
|
// We try to join a server where we are banned
|
||||||
|
_switch_mode_errorstr = STR_NETWORK_ERR_SERVER_BANNED;
|
||||||
|
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, 0);
|
||||||
|
|
||||||
|
return NETWORK_RECV_STATUS_SERVER_BANNED;
|
||||||
|
}
|
||||||
|
|
||||||
DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO)
|
DEF_CLIENT_RECEIVE_COMMAND(PACKET_SERVER_COMPANY_INFO)
|
||||||
{
|
{
|
||||||
byte company_info_version;
|
byte company_info_version;
|
||||||
|
@ -737,6 +746,7 @@ typedef NetworkRecvStatus NetworkClientPacket(Packet *p);
|
||||||
// packet is found.
|
// packet is found.
|
||||||
static NetworkClientPacket* const _network_client_packet[] = {
|
static NetworkClientPacket* const _network_client_packet[] = {
|
||||||
RECEIVE_COMMAND(PACKET_SERVER_FULL),
|
RECEIVE_COMMAND(PACKET_SERVER_FULL),
|
||||||
|
RECEIVE_COMMAND(PACKET_SERVER_BANNED),
|
||||||
NULL, /*PACKET_CLIENT_JOIN,*/
|
NULL, /*PACKET_CLIENT_JOIN,*/
|
||||||
RECEIVE_COMMAND(PACKET_SERVER_ERROR),
|
RECEIVE_COMMAND(PACKET_SERVER_ERROR),
|
||||||
NULL, /*PACKET_CLIENT_COMPANY_INFO,*/
|
NULL, /*PACKET_CLIENT_COMPANY_INFO,*/
|
||||||
|
|
|
@ -67,6 +67,7 @@ typedef enum {
|
||||||
NETWORK_RECV_STATUS_MALFORMED_PACKET,
|
NETWORK_RECV_STATUS_MALFORMED_PACKET,
|
||||||
NETWORK_RECV_STATUS_SERVER_ERROR, // The server told us we made an error
|
NETWORK_RECV_STATUS_SERVER_ERROR, // The server told us we made an error
|
||||||
NETWORK_RECV_STATUS_SERVER_FULL,
|
NETWORK_RECV_STATUS_SERVER_FULL,
|
||||||
|
NETWORK_RECV_STATUS_SERVER_BANNED,
|
||||||
NETWORK_RECV_STATUS_CLOSE_QUERY, // Done quering the server
|
NETWORK_RECV_STATUS_CLOSE_QUERY, // Done quering the server
|
||||||
} NetworkRecvStatus;
|
} NetworkRecvStatus;
|
||||||
|
|
||||||
|
@ -130,6 +131,7 @@ typedef struct NetworkClientState {
|
||||||
// is the respond to a wrong revision)
|
// is the respond to a wrong revision)
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PACKET_SERVER_FULL,
|
PACKET_SERVER_FULL,
|
||||||
|
PACKET_SERVER_BANNED,
|
||||||
PACKET_CLIENT_JOIN,
|
PACKET_CLIENT_JOIN,
|
||||||
PACKET_SERVER_ERROR,
|
PACKET_SERVER_ERROR,
|
||||||
PACKET_CLIENT_COMPANY_INFO,
|
PACKET_CLIENT_COMPANY_INFO,
|
||||||
|
|
|
@ -953,10 +953,21 @@ static void ClientList_Kick(byte client_no)
|
||||||
SEND_COMMAND(PACKET_SERVER_ERROR)(&_clients[client_no], NETWORK_ERROR_KICKED);
|
SEND_COMMAND(PACKET_SERVER_ERROR)(&_clients[client_no], NETWORK_ERROR_KICKED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static void ClientList_Ban(byte client_no)
|
static void ClientList_Ban(byte client_no)
|
||||||
{
|
{
|
||||||
// TODO
|
uint i;
|
||||||
}*/
|
uint32 ip = NetworkFindClientInfo(client_no)->client_ip;
|
||||||
|
|
||||||
|
for (i = 0; i < lengthof(_network_ban_list); i++) {
|
||||||
|
if (_network_ban_list[i] == NULL || _network_ban_list[i][0] == '\0') {
|
||||||
|
_network_ban_list[i] = strdup(inet_ntoa(*(struct in_addr *)&ip));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_no < MAX_PLAYERS)
|
||||||
|
SEND_COMMAND(PACKET_SERVER_ERROR)(&_clients[client_no], NETWORK_ERROR_KICKED);
|
||||||
|
}
|
||||||
|
|
||||||
static void ClientList_GiveMoney(byte client_no)
|
static void ClientList_GiveMoney(byte client_no)
|
||||||
{
|
{
|
||||||
|
@ -1090,8 +1101,8 @@ static Window *PopupClientList(Window *w, int client_no, int x, int y)
|
||||||
GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK);
|
GetString(_clientlist_action[i], STR_NETWORK_CLIENTLIST_KICK);
|
||||||
_clientlist_proc[i++] = &ClientList_Kick;
|
_clientlist_proc[i++] = &ClientList_Kick;
|
||||||
|
|
||||||
/* sprintf(clientlist_action[i],"Ban");
|
sprintf(_clientlist_action[i],"Ban");
|
||||||
clientlist_proc[i++] = &ClientList_Ban;*/
|
_clientlist_proc[i++] = &ClientList_Ban;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
|
|
|
@ -1069,6 +1069,7 @@ typedef void NetworkServerPacket(NetworkClientState *cs, Packet *p);
|
||||||
// packet is found.
|
// packet is found.
|
||||||
static NetworkServerPacket* const _network_server_packet[] = {
|
static NetworkServerPacket* const _network_server_packet[] = {
|
||||||
NULL, /*PACKET_SERVER_FULL,*/
|
NULL, /*PACKET_SERVER_FULL,*/
|
||||||
|
NULL, /*PACKET_SERVER_BANNED,*/
|
||||||
RECEIVE_COMMAND(PACKET_CLIENT_JOIN),
|
RECEIVE_COMMAND(PACKET_CLIENT_JOIN),
|
||||||
NULL, /*PACKET_SERVER_ERROR,*/
|
NULL, /*PACKET_SERVER_ERROR,*/
|
||||||
RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO),
|
RECEIVE_COMMAND(PACKET_CLIENT_COMPANY_INFO),
|
||||||
|
|
|
@ -123,7 +123,7 @@ static IniGroup *ini_group_alloc(IniFile *ini, const char *grpt, int len)
|
||||||
IniGroup *grp = pool_alloc(&ini->pool, sizeof(IniGroup));
|
IniGroup *grp = pool_alloc(&ini->pool, sizeof(IniGroup));
|
||||||
grp->ini = ini;
|
grp->ini = ini;
|
||||||
grp->name = pool_strdup(&ini->pool, grpt, len);
|
grp->name = pool_strdup(&ini->pool, grpt, len);
|
||||||
if(!strcmp(grp->name, "newgrf") || !strcmp(grp->name, "servers") )
|
if(!strcmp(grp->name, "newgrf") || !strcmp(grp->name, "servers") || !strcmp(grp->name, "bans") )
|
||||||
grp->type = IGT_LIST;
|
grp->type = IGT_LIST;
|
||||||
else
|
else
|
||||||
grp->type = IGT_VARIABLES;
|
grp->type = IGT_VARIABLES;
|
||||||
|
@ -983,6 +983,7 @@ void LoadFromConfig()
|
||||||
HandleSettingDescs(ini, load_setting_desc);
|
HandleSettingDescs(ini, load_setting_desc);
|
||||||
LoadList(ini, "newgrf", _newgrf_files, lengthof(_newgrf_files));
|
LoadList(ini, "newgrf", _newgrf_files, lengthof(_newgrf_files));
|
||||||
LoadList(ini, "servers", _network_host_list, lengthof(_network_host_list));
|
LoadList(ini, "servers", _network_host_list, lengthof(_network_host_list));
|
||||||
|
LoadList(ini, "bans", _network_ban_list, lengthof(_network_ban_list));
|
||||||
ini_free(ini);
|
ini_free(ini);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -991,6 +992,7 @@ void SaveToConfig()
|
||||||
IniFile *ini = ini_load(_config_file);
|
IniFile *ini = ini_load(_config_file);
|
||||||
HandleSettingDescs(ini, save_setting_desc);
|
HandleSettingDescs(ini, save_setting_desc);
|
||||||
SaveList(ini, "servers", _network_host_list, lengthof(_network_host_list));
|
SaveList(ini, "servers", _network_host_list, lengthof(_network_host_list));
|
||||||
|
SaveList(ini, "bans", _network_ban_list, lengthof(_network_ban_list));
|
||||||
ini_save(_config_file, ini);
|
ini_save(_config_file, ini);
|
||||||
ini_free(ini);
|
ini_free(ini);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue