//////////// //SECURITY// //////////// #define UPLOAD_LIMIT 1048576 //Restricts client uploads to the server to 1MB //Could probably do with being lower. GLOBAL_LIST_INIT(blacklisted_builds, list( "1407" = "bug preventing client display overrides from working leads to clients being able to see things/mobs they shouldn't be able to see", "1408" = "bug preventing client display overrides from working leads to clients being able to see things/mobs they shouldn't be able to see", "1428" = "bug causing right-click menus to show too many verbs that's been fixed in version 1429", )) #define LIMITER_SIZE 5 #define CURRENT_SECOND 1 #define SECOND_COUNT 2 #define CURRENT_MINUTE 3 #define MINUTE_COUNT 4 #define ADMINSWARNED_AT 5 /* When somebody clicks a link in game, this Topic is called first. It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever] (if specified in the link). ie locate(hsrc).Topic() Such links can be spoofed. Because of this certain things MUST be considered whenever adding a Topic() for something: - Can it be fed harmful values which could cause runtimes? - Is the Topic call an admin-only thing? - If so, does it have checks to see if the person who called it (usr.client) is an admin? - Are the processes being called by Topic() particularly laggy? - If so, is there any protection against somebody spam-clicking a link? If you have any questions about this stuff feel free to ask. ~Carn */ /client/Topic(href, href_list, hsrc) if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null return // asset_cache if(href_list["asset_cache_confirm_arrival"]) var/job = text2num(href_list["asset_cache_confirm_arrival"]) //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us // into letting append to a list without limit. if (job && job <= last_asset_job && !(job in completed_asset_jobs)) completed_asset_jobs += job return else if (job in completed_asset_jobs) //byond bug ID:2256651 to_chat(src, "An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)") src << browse("...", "window=asset_cache_browser") var/mtl = CONFIG_GET(number/minute_topic_limit) if (!holder && mtl) var/minute = round(world.time, 600) if (!topiclimiter) topiclimiter = new(LIMITER_SIZE) if (minute != topiclimiter[CURRENT_MINUTE]) topiclimiter[CURRENT_MINUTE] = minute topiclimiter[MINUTE_COUNT] = 0 topiclimiter[MINUTE_COUNT] += 1 if (topiclimiter[MINUTE_COUNT] > mtl) var/msg = "Your previous action was ignored because you've done too many in a minute." if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them) topiclimiter[ADMINSWARNED_AT] = minute msg += " Administrators have been informed." log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") to_chat(src, "[msg]") return var/stl = CONFIG_GET(number/second_topic_limit) if (!holder && stl) var/second = round(world.time, 10) if (!topiclimiter) topiclimiter = new(LIMITER_SIZE) if (second != topiclimiter[CURRENT_SECOND]) topiclimiter[CURRENT_SECOND] = second topiclimiter[SECOND_COUNT] = 0 topiclimiter[SECOND_COUNT] += 1 if (topiclimiter[SECOND_COUNT] > stl) to_chat(src, "Your previous action was ignored because you've done too many in a second") return //Logs all hrefs, except chat pings if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2)) log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) return // CITADEL Start - Mentor PM if (citadel_client_procs(href_list)) return // CITADEL End switch(href_list["_src_"]) if("holder") hsrc = holder if("usr") hsrc = mob if("mentor") // CITADEL hsrc = mentor_datum // CITADEL END if("prefs") if (inprefs) return inprefs = TRUE . = prefs.process_link(usr,href_list) inprefs = FALSE return if("vars") return view_var_Topic(href,href_list,hsrc) if("chat") return chatOutput.Topic(href, href_list) switch(href_list["action"]) if("openLink") src << link(href_list["link"]) if (hsrc) var/datum/real_src = hsrc if(QDELETED(real_src)) return ..() //redirect to hsrc.Topic() /client/proc/is_content_unlocked() if(!prefs.unlock_content) to_chat(src, "Become a BYOND member to access member-perks and features, as well as support the engine that makes this game possible. Only 10 bucks for 3 months! Click Here to find out more.") return 0 return 1 /client/proc/handle_spam_prevention(message, mute_type) if(CONFIG_GET(flag/automute_on) && !holder && last_message == message) src.last_message_count++ if(src.last_message_count >= SPAM_TRIGGER_AUTOMUTE) to_chat(src, "You have exceeded the spam filter limit for identical messages. An auto-mute was applied.") cmd_admin_mute(src, mute_type, 1) return 1 if(src.last_message_count >= SPAM_TRIGGER_WARNING) to_chat(src, "You are nearing the spam filter limit for identical messages.") return 0 else last_message = message src.last_message_count = 0 return 0 //This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc. /client/AllowUpload(filename, filelength) if(filelength > UPLOAD_LIMIT) to_chat(src, "Error: AllowUpload(): File Upload too large. Upload Limit: [UPLOAD_LIMIT/1024]KiB.") return 0 return 1 /////////// //CONNECT// /////////// #if (PRELOAD_RSC == 0) GLOBAL_LIST_EMPTY(external_rsc_urls) #endif /client/can_vv_get(var_name) return var_name != NAMEOF(src, holder) && ..() /client/vv_edit_var(var_name, var_value) return var_name != NAMEOF(src, holder) && ..() /client/New(TopicData) world.SetConfig("APP/admin", ckey, "role=admin") //CITADEL EDIT - Allows admins to reboot in OOM situations var/tdata = TopicData //save this for later use chatOutput = new /datum/chatOutput(src) TopicData = null //Prevent calls to client.Topic from connect if(connection != "seeker" && connection != "web")//Invalid connection type. return null GLOB.clients += src GLOB.directory[ckey] = src GLOB.ahelp_tickets.ClientLogin(src) var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins. //Admin Authorisation holder = GLOB.admin_datums[ckey] var/debug_tools_allowed = FALSE //CITADEL EDIT if(holder) GLOB.admins |= src holder.owner = src connecting_admin = TRUE //CITADEL EDIT if(check_rights_for(src, R_DEBUG)) debug_tools_allowed = TRUE //END CITADEL EDIT else if(GLOB.deadmins[ckey]) verbs += /client/proc/readmin connecting_admin = TRUE if(CONFIG_GET(flag/autoadmin)) if(!GLOB.admin_datums[ckey]) var/datum/admin_rank/autorank for(var/datum/admin_rank/R in GLOB.admin_ranks) if(R.name == CONFIG_GET(string/autoadmin_rank)) autorank = R break if(!autorank) to_chat(world, "Autoadmin rank not found") else new /datum/admins(autorank, ckey) //CITADEL EDIT if(check_rights_for(src, R_DEBUG)) //check if autoadmin gave us it debug_tools_allowed = TRUE if(!debug_tools_allowed) world.SetConfig("APP/admin", ckey, null) //END CITADEL EDIT if(CONFIG_GET(flag/enable_localhost_rank) && !connecting_admin) var/localhost_addresses = list("127.0.0.1", "::1") if(isnull(address) || (address in localhost_addresses)) var/datum/admin_rank/localhost_rank = new("!localhost!", 65535, 16384, 65535) //+EVERYTHING -DBRANKS *EVERYTHING new /datum/admins(localhost_rank, ckey, 1, 1) //preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum) prefs = GLOB.preferences_datums[ckey] if(prefs) prefs.parent = src else prefs = new /datum/preferences(src) GLOB.preferences_datums[ckey] = prefs prefs.last_ip = address //these are gonna be used for banning prefs.last_id = computer_id //these are gonna be used for banning fps = prefs.clientfps if(fexists(roundend_report_file())) verbs += /client/proc/show_previous_roundend_report log_access("Login: [key_name(src)] from [address ? address : "localhost"]-[computer_id] || BYOND v[byond_version]") var/alert_mob_dupe_login = FALSE if(CONFIG_GET(flag/log_access)) for(var/I in GLOB.clients) if(!I || I == src) continue var/client/C = I if(C.key && (C.key != key) ) var/matches if( (C.address == address) ) matches += "IP ([address])" if( (C.computer_id == computer_id) ) if(matches) matches += " and " matches += "ID ([computer_id])" alert_mob_dupe_login = TRUE if(matches) if(C) message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)].") log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)].") else message_admins("Notice: [key_name_admin(src)] has the same [matches] as [key_name_admin(C)] (no longer logged in). ") log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)] (no longer logged in).") if(GLOB.player_details[ckey]) player_details = GLOB.player_details[ckey] else player_details = new GLOB.player_details[ckey] = player_details . = ..() //calls mob.Login() if (byond_version >= 512) if (!byond_build || byond_build < 1386) message_admins("[key_name(src)] has been detected as spoofing their byond version. Connection rejected.") add_system_note("Spoofed-Byond-Version", "Detected as using a spoofed byond version.") log_access("Failed Login: [key] - Spoofed byond version") qdel(src) if (num2text(byond_build) in GLOB.blacklisted_builds) log_access("Failed login: [key] - blacklisted byond version") to_chat(src, "Your version of byond is blacklisted.") to_chat(src, "Byond build [byond_build] ([byond_version].[byond_build]) has been blacklisted for the following reason: [GLOB.blacklisted_builds[num2text(byond_build)]].") to_chat(src, "Please download a new version of byond. If [byond_build] is the latest, you can go to BYOND's website to download other versions.") if(connecting_admin) to_chat(src, "As an admin, you are being allowed to continue using this version, but please consider changing byond versions") else qdel(src) return if(SSinput.initialized) set_macros() chatOutput.start() // Starts the chat if(alert_mob_dupe_login) spawn() alert(mob, "You have logged in already with another key this round, please log out of this one NOW or risk being banned!") connection_time = world.time connection_realtime = world.realtime connection_timeofday = world.timeofday winset(src, null, "command=\".configure graphics-hwmode on\"") var/cev = CONFIG_GET(number/client_error_version) var/cwv = CONFIG_GET(number/client_warn_version) if (byond_version < cev) //Out of date client. to_chat(src, "Your version of BYOND is too old:") to_chat(src, CONFIG_GET(string/client_error_message)) to_chat(src, "Your version: [byond_version]") to_chat(src, "Required version: [cev] or later") to_chat(src, "Visit BYOND's website to get the latest version of BYOND.") if (connecting_admin) to_chat(src, "Because you are an admin, you are being allowed to walk past this limitation, But it is still STRONGLY suggested you upgrade") else qdel(src) return 0 else if (byond_version < cwv) //We have words for this client. if(CONFIG_GET(flag/client_warn_popup)) var/msg = "Your version of byond may be getting out of date:
" msg += CONFIG_GET(string/client_warn_message) + "

" msg += "Your version: [byond_version]
" msg += "Required version to remove this message: [cwv] or later
" msg += "Visit BYOND's website to get the latest version of BYOND.
" src << browse(msg, "window=warning_popup") else to_chat(src, "Your version of byond may be getting out of date:") to_chat(src, CONFIG_GET(string/client_warn_message)) to_chat(src, "Your version: [byond_version]") to_chat(src, "Required version to remove this message: [cwv] or later") to_chat(src, "Visit BYOND's website to get the latest version of BYOND.") if (connection == "web" && !connecting_admin) if (!CONFIG_GET(flag/allow_webclient)) to_chat(src, "Web client is disabled") qdel(src) return 0 if (CONFIG_GET(flag/webclient_only_byond_members) && !IsByondMember()) to_chat(src, "Sorry, but the web client is restricted to byond members only.") qdel(src) return 0 if( (world.address == address || !address) && !GLOB.host ) GLOB.host = key world.update_status() if(holder) add_admin_verbs() to_chat(src, get_message_output("memo")) adminGreet() add_verbs_from_config() var/cached_player_age = set_client_age_from_db(tdata) //we have to cache this because other shit may change it and we need it's current value now down below. if (isnum(cached_player_age) && cached_player_age == -1) //first connection player_age = 0 var/nnpa = CONFIG_GET(number/notify_new_player_age) if (isnum(cached_player_age) && cached_player_age == -1) //first connection if (nnpa >= 0) message_admins("New user: [key_name_admin(src)] is connecting here for the first time.") if (CONFIG_GET(flag/irc_first_connection_alert)) send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!") else if (isnum(cached_player_age) && cached_player_age < nnpa) message_admins("New user: [key_name_admin(src)] just connected with an age of [cached_player_age] day[(player_age==1?"":"s")]") if(CONFIG_GET(flag/use_account_age_for_jobs) && account_age >= 0) player_age = account_age if(account_age >= 0 && account_age < nnpa) message_admins("[key_name_admin(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") if (CONFIG_GET(flag/irc_first_connection_alert)) send2irc_adminless_only("new_byond_user", "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age==1?"":"s")] old, created on [account_join_date].") get_message_output("watchlist entry", ckey) check_ip_intel() send_resources() generate_clickcatcher() apply_clickcatcher() if(prefs.lastchangelog != GLOB.changelog_hash) //bolds the changelog button on the interface so we know there are updates. to_chat(src, "You have unread updates in the changelog.") if(CONFIG_GET(flag/aggressive_changelog)) changelog() else winset(src, "infowindow.changelog", "font-style=bold") if(ckey in GLOB.clientmessages) for(var/message in GLOB.clientmessages[ckey]) to_chat(src, message) GLOB.clientmessages.Remove(ckey) if(CONFIG_GET(flag/autoconvert_notes)) convert_notes_sql(ckey) to_chat(src, get_message_output("message", ckey)) if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them. to_chat(src, "Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.") //This is down here because of the browse() calls in tooltip/New() if(!tooltips) tooltips = new /datum/tooltip(src) var/list/topmenus = GLOB.menulist[/datum/verbs/menu] for (var/thing in topmenus) var/datum/verbs/menu/topmenu = thing var/topmenuname = "[topmenu]" if (topmenuname == "[topmenu.type]") var/list/tree = splittext(topmenuname, "/") topmenuname = tree[tree.len] winset(src, "[topmenu.type]", "parent=menu;name=[url_encode(topmenuname)]") var/list/entries = topmenu.Generate_list(src) for (var/child in entries) winset(src, "[child]", "[entries[child]]") if (!ispath(child, /datum/verbs/menu)) var/atom/verb/verbpath = child if (copytext(verbpath.name,1,2) != "@") new child(src) for (var/thing in prefs.menuoptions) var/datum/verbs/menu/menuitem = GLOB.menulist[thing] if (menuitem) menuitem.Load_checked(src) hook_vr("client_new",list(src)) // CIT CHANGE - hook for client/New() changes Master.UpdateTickRate() ////////////// //DISCONNECT// ////////////// /client/Del() if(credits) QDEL_LIST(credits) log_access("Logout: [key_name(src)]") if(holder) adminGreet(1) holder.owner = null GLOB.admins -= src if (!GLOB.admins.len && SSticker.IsRoundInProgress()) //Only report this stuff if we are currently playing. var/cheesy_message = pick( "I have no admins online!",\ "I'm all alone :(",\ "I'm feeling lonely :(",\ "I'm so lonely :(",\ "Why does nobody love me? :(",\ "I want a man :(",\ "Where has everyone gone?",\ "I need a hug :(",\ "Someone come hold me :(",\ "I need someone on me :(",\ "What happened? Where has everyone gone?",\ "Forever alone :("\ ) send2irc("Server", "[cheesy_message] (No admins online)") GLOB.ahelp_tickets.ClientLogout(src) GLOB.directory -= ckey GLOB.clients -= src if(movingmob != null) movingmob.client_mobs_in_contents -= mob UNSETEMPTY(movingmob.client_mobs_in_contents) Master.UpdateTickRate() return ..() /client/Destroy() return QDEL_HINT_HARDDEL_NOW /client/proc/set_client_age_from_db(connectiontopic) if (IsGuestKey(src.key)) return if(!SSdbcore.Connect()) return var/sql_ckey = sanitizeSQL(src.ckey) var/datum/DBQuery/query_get_related_ip = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = INET_ATON('[address]') AND ckey != '[sql_ckey]'") query_get_related_ip.Execute() related_accounts_ip = "" while(query_get_related_ip.NextRow()) related_accounts_ip += "[query_get_related_ip.item[1]], " qdel(query_get_related_ip) var/datum/DBQuery/query_get_related_cid = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]' AND ckey != '[sql_ckey]'") if(!query_get_related_cid.Execute()) return related_accounts_cid = "" while (query_get_related_cid.NextRow()) related_accounts_cid += "[query_get_related_cid.item[1]], " qdel(query_get_related_cid) var/admin_rank = "Player" if (src.holder && src.holder.rank) admin_rank = src.holder.rank.name else if (!GLOB.deadmins[ckey] && check_randomizer(connectiontopic)) return var/sql_ip = sanitizeSQL(address) var/sql_computerid = sanitizeSQL(computer_id) var/sql_admin_rank = sanitizeSQL(admin_rank) var/new_player var/datum/DBQuery/query_client_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") if(!query_client_in_db.Execute()) qdel(query_client_in_db) return if(!query_client_in_db.NextRow()) if (CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey]) log_access("Failed Login: [key] - New account attempting to connect during panic bunker") message_admins("Failed Login: [key] - New account attempting to connect during panic bunker") to_chat(src, "You must first join the Discord to verify your account before joining this server. Please ping an admin once you've joined and read the rules. https://discord.gg/E6SQuhz") //CIT CHANGE - makes the panic bunker disconnect message point to the discord var/list/connectiontopic_a = params2list(connectiontopic) var/list/panic_addr = CONFIG_GET(string/panic_server_address) if(panic_addr && !connectiontopic_a["redirect"]) var/panic_name = CONFIG_GET(string/panic_server_name) to_chat(src, "Sending you to [panic_name ? panic_name : panic_addr].") winset(src, null, "command=.options") src << link("[panic_addr]?redirect=1") qdel(query_client_in_db) qdel(src) return new_player = 1 account_join_date = sanitizeSQL(findJoinDate()) var/datum/DBQuery/query_add_player = SSdbcore.NewQuery("INSERT INTO [format_table_name("player")] (`ckey`, `firstseen`, `firstseen_round_id`, `lastseen`, `lastseen_round_id`, `ip`, `computerid`, `lastadminrank`, `accountjoindate`) VALUES ('[sql_ckey]', Now(), '[GLOB.round_id]', Now(), '[GLOB.round_id]', INET_ATON('[sql_ip]'), '[sql_computerid]', '[sql_admin_rank]', [account_join_date ? "'[account_join_date]'" : "NULL"])") if(!query_add_player.Execute()) qdel(query_client_in_db) qdel(query_add_player) return qdel(query_add_player) if(!account_join_date) account_join_date = "Error" account_age = -1 qdel(query_client_in_db) var/datum/DBQuery/query_get_client_age = SSdbcore.NewQuery("SELECT firstseen, DATEDIFF(Now(),firstseen), accountjoindate, DATEDIFF(Now(),accountjoindate) FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") if(!query_get_client_age.Execute()) qdel(query_get_client_age) return if(query_get_client_age.NextRow()) player_join_date = query_get_client_age.item[1] player_age = text2num(query_get_client_age.item[2]) if(!account_join_date) account_join_date = query_get_client_age.item[3] account_age = text2num(query_get_client_age.item[4]) if(!account_age) account_join_date = sanitizeSQL(findJoinDate()) if(!account_join_date) account_age = -1 else var/datum/DBQuery/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[account_join_date]')") if(!query_datediff.Execute()) qdel(query_datediff) return if(query_datediff.NextRow()) account_age = text2num(query_datediff.item[1]) qdel(query_datediff) qdel(query_get_client_age) if(!new_player) var/datum/DBQuery/query_log_player = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastseen = Now(), lastseen_round_id = '[GLOB.round_id]', ip = INET_ATON('[sql_ip]'), computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]', accountjoindate = [account_join_date ? "'[account_join_date]'" : "NULL"] WHERE ckey = '[sql_ckey]'") if(!query_log_player.Execute()) qdel(query_log_player) return qdel(query_log_player) if(!account_join_date) account_join_date = "Error" var/datum/DBQuery/query_log_connection = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')),'[world.port]','[GLOB.round_id]','[sql_ckey]',INET_ATON('[sql_ip]'),'[sql_computerid]')") query_log_connection.Execute() qdel(query_log_connection) if(new_player) player_age = -1 . = player_age /client/proc/findJoinDate() var/list/http = world.Export("http://byond.com/members/[ckey]?format=text") if(!http) log_world("Failed to connect to byond age check for [ckey]") return var/F = file2text(http["CONTENT"]) if(F) var/regex/R = regex("joined = \"(\\d{4}-\\d{2}-\\d{2})\"") if(R.Find(F)) . = R.group[1] else CRASH("Age check regex failed for [src.ckey]") /client/proc/check_randomizer(topic) . = FALSE if (connection != "seeker") return topic = params2list(topic) if (!CONFIG_GET(flag/check_randomizer)) return var/static/cidcheck = list() var/static/tokens = list() var/static/cidcheck_failedckeys = list() //to avoid spamming the admins if the same guy keeps trying. var/static/cidcheck_spoofckeys = list() var/sql_ckey = sanitizeSQL(ckey) var/datum/DBQuery/query_cidcheck = SSdbcore.NewQuery("SELECT computerid FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'") query_cidcheck.Execute() var/lastcid if (query_cidcheck.NextRow()) lastcid = query_cidcheck.item[1] qdel(query_cidcheck) var/oldcid = cidcheck[ckey] if (oldcid) if (!topic || !topic["token"] || !tokens[ckey] || topic["token"] != tokens[ckey]) if (!cidcheck_spoofckeys[ckey]) message_admins("[key_name(src)] appears to have attempted to spoof a cid randomizer check.") cidcheck_spoofckeys[ckey] = TRUE cidcheck[ckey] = computer_id tokens[ckey] = cid_check_reconnect() sleep(15 SECONDS) //Longer sleep here since this would trigger if a client tries to reconnect manually because the inital reconnect failed //we sleep after telling the client to reconnect, so if we still exist something is up log_access("Forced disconnect: [key] [computer_id] [address] - CID randomizer check") qdel(src) return TRUE if (oldcid != computer_id && computer_id != lastcid) //IT CHANGED!!! cidcheck -= ckey //so they can try again after removing the cid randomizer. to_chat(src, "Connection Error:") to_chat(src, "Invalid ComputerID(spoofed). Please remove the ComputerID spoofer from your byond installation and try again.") if (!cidcheck_failedckeys[ckey]) message_admins("[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been detected as using a cid randomizer. Connection rejected.") cidcheck_failedckeys[ckey] = TRUE note_randomizer_user() log_access("Failed Login: [key] [computer_id] [address] - CID randomizer confirmed (oldcid: [oldcid])") qdel(src) return TRUE else if (cidcheck_failedckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after showing they removed their cid randomizer") send2irc_adminless_only("CidRandomizer", "[key_name(src)] has been allowed to connect after showing they removed their cid randomizer.") cidcheck_failedckeys -= ckey if (cidcheck_spoofckeys[ckey]) message_admins("[key_name_admin(src)] has been allowed to connect after appearing to have attempted to spoof a cid randomizer check because it appears they aren't spoofing one this time") cidcheck_spoofckeys -= ckey cidcheck -= ckey else if (computer_id != lastcid) cidcheck[ckey] = computer_id tokens[ckey] = cid_check_reconnect() sleep(5 SECONDS) //browse is queued, we don't want them to disconnect before getting the browse() command. //we sleep after telling the client to reconnect, so if we still exist something is up log_access("Forced disconnect: [key] [computer_id] [address] - CID randomizer check") qdel(src) return TRUE /client/proc/cid_check_reconnect() var/token = md5("[rand(0,9999)][world.time][rand(0,9999)][ckey][rand(0,9999)][address][rand(0,9999)][computer_id][rand(0,9999)]") . = token log_access("Failed Login: [key] [computer_id] [address] - CID randomizer check") var/url = winget(src, null, "url") //special javascript to make them reconnect under a new window. src << browse({"byond://[url]?token=[token]"}, "border=0;titlebar=0;size=1x1;window=redirect") to_chat(src, {"You will be automatically taken to the game, if not, click here to be taken manually"}) /client/proc/note_randomizer_user() add_system_note("CID-Error", "Detected as using a cid randomizer.") /client/proc/add_system_note(system_ckey, message) var/sql_system_ckey = sanitizeSQL(system_ckey) var/sql_ckey = sanitizeSQL(ckey) //check to see if we noted them in the last day. var/datum/DBQuery/query_get_notes = SSdbcore.NewQuery("SELECT id FROM [format_table_name("messages")] WHERE type = 'note' AND targetckey = '[sql_ckey]' AND adminckey = '[sql_system_ckey]' AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0") if(!query_get_notes.Execute()) qdel(query_get_notes) return if(query_get_notes.NextRow()) qdel(query_get_notes) return qdel(query_get_notes) //regardless of above, make sure their last note is not from us, as no point in repeating the same note over and over. query_get_notes = SSdbcore.NewQuery("SELECT adminckey FROM [format_table_name("messages")] WHERE targetckey = '[sql_ckey]' AND deleted = 0 ORDER BY timestamp DESC LIMIT 1") if(!query_get_notes.Execute()) qdel(query_get_notes) return if(query_get_notes.NextRow()) if (query_get_notes.item[1] == system_ckey) qdel(query_get_notes) return qdel(query_get_notes) create_message("note", ckey, system_ckey, message, null, null, 0, 0) /client/proc/check_ip_intel() set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep if (CONFIG_GET(string/ipintel_email)) var/datum/ipintel/res = get_ip_intel(address) if (res.intel >= CONFIG_GET(number/ipintel_rating_bad)) message_admins("Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.") ip_intel = res.intel /client/Click(atom/object, atom/location, control, params) var/ab = FALSE var/list/L = params2list(params) if (object && object == middragatom && L["left"]) ab = max(0, 5 SECONDS-(world.time-middragtime)*0.1) var/mcl = CONFIG_GET(number/minute_click_limit) if (!holder && mcl) var/minute = round(world.time, 600) if (!clicklimiter) clicklimiter = new(LIMITER_SIZE) if (minute != clicklimiter[CURRENT_MINUTE]) clicklimiter[CURRENT_MINUTE] = minute clicklimiter[MINUTE_COUNT] = 0 clicklimiter[MINUTE_COUNT] += 1+(ab) if (clicklimiter[MINUTE_COUNT] > mcl) var/msg = "Your previous click was ignored because you've done too many in a minute." if (minute != clicklimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them) clicklimiter[ADMINSWARNED_AT] = minute msg += " Administrators have been informed." if (ab) log_game("[key_name(src)] is using the middle click aimbot exploit") message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] is using the middle click aimbot exploit") add_system_note("aimbot", "Is using the middle click aimbot exploit") log_game("[key_name(src)] Has hit the per-minute click limit of [mcl] clicks in a given game minute") message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] Has hit the per-minute click limit of [mcl] clicks in a given game minute") to_chat(src, "[msg]") return var/scl = CONFIG_GET(number/second_click_limit) if (!holder && scl) var/second = round(world.time, 10) if (!clicklimiter) clicklimiter = new(LIMITER_SIZE) if (second != clicklimiter[CURRENT_SECOND]) clicklimiter[CURRENT_SECOND] = second clicklimiter[SECOND_COUNT] = 0 clicklimiter[SECOND_COUNT] += 1+(!!ab) if (clicklimiter[SECOND_COUNT] > scl) to_chat(src, "Your previous click was ignored because you've done too many in a second") return if (prefs.hotkeys) // If hotkey mode is enabled, then clicking the map will automatically // unfocus the text bar. This removes the red color from the text bar // so that the visual focus indicator matches reality. winset(src, null, "input.background-color=[COLOR_INPUT_DISABLED]") ..() /client/proc/add_verbs_from_config() if(CONFIG_GET(flag/see_own_notes)) verbs += /client/proc/self_notes if(CONFIG_GET(flag/use_exp_tracking)) verbs += /client/proc/self_playtime #undef UPLOAD_LIMIT //checks if a client is afk //3000 frames = 5 minutes /client/proc/is_afk(duration = CONFIG_GET(number/inactivity_period)) if(inactivity > duration) return inactivity return FALSE //send resources to the client. It's here in its own proc so we can move it around easiliy if need be /client/proc/send_resources() #if (PRELOAD_RSC == 0) var/static/next_external_rsc = 0 if(GLOB.external_rsc_urls && GLOB.external_rsc_urls.len) next_external_rsc = WRAP(next_external_rsc+1, 1, GLOB.external_rsc_urls.len+1) preload_rsc = GLOB.external_rsc_urls[next_external_rsc] #endif //get the common files getFiles( 'html/search.js', 'html/panels.css', 'html/browser/common.css', 'html/browser/scannernew.css', 'html/browser/playeroptions.css', ) spawn (10) //removing this spawn causes all clients to not get verbs. //Precache the client with all other assets slowly, so as to not block other browse() calls getFilesSlow(src, SSassets.preload, register_asset = FALSE) #if (PRELOAD_RSC == 0) for (var/name in GLOB.vox_sounds) var/file = GLOB.vox_sounds[name] Export("##action=load_rsc", file) stoplag() #endif //Hook, override it to run code when dir changes //Like for /atoms, but clients are their own snowflake FUCK /client/proc/setDir(newdir) dir = newdir /client/vv_edit_var(var_name, var_value) switch (var_name) if ("holder") return FALSE if ("ckey") return FALSE if ("key") return FALSE if("view") change_view(var_value) return TRUE . = ..() /client/proc/rescale_view(change, min, max) var/viewscale = getviewsize(view) var/x = viewscale[1] var/y = viewscale[2] x = CLAMP(x+change, min, max) y = CLAMP(y+change, min,max) change_view("[x]x[y]") /client/proc/change_view(new_size) if (isnull(new_size)) CRASH("change_view called without argument.") //CIT CHANGES START HERE - makes change_view change DEFAULT_VIEW to 15x15 depending on preferences if(prefs && CONFIG_GET(string/default_view)) if(!prefs.widescreenpref && new_size == CONFIG_GET(string/default_view)) new_size = "15x15" //END OF CIT CHANGES view = new_size apply_clickcatcher() mob.reload_fullscreen() if (isliving(mob)) var/mob/living/M = mob M.update_damage_hud() if (prefs.auto_fit_viewport) fit_viewport() /client/proc/generate_clickcatcher() if(!void) void = new() screen += void /client/proc/apply_clickcatcher() generate_clickcatcher() var/list/actualview = getviewsize(view) void.UpdateGreed(actualview[1],actualview[2]) /client/proc/AnnouncePR(announcement) if(prefs && prefs.chat_toggles & CHAT_PULLR) to_chat(src, announcement)