//////////// //SECURITY// //////////// #define UPLOAD_LIMIT 1048576 //Restricts client uploads to the server to 1MB //Could probably do with being lower. #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("[key_name_admin(src)] [ADMIN_FLW(usr)] [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)) WRITE_FILE(GLOB.world_href_log, "[time_stamp(show_ds = TRUE)] [src] (usr:[usr]\[[COORD(usr)]\]) || [hsrc ? "[hsrc] " : ""][href]
") // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) return switch(href_list["_src_"]) if("holder") hsrc = holder if("usr") hsrc = mob 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"]) ..() //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/New(TopicData) 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 var/localhost_addresses = list("127.0.0.1", "::1") if(address && (address in localhost_addresses)) var/datum/admin_rank/localhost_rank = new("!localhost!", 65535) if(localhost_rank) var/datum/admins/localhost_holder = new(localhost_rank, ckey) localhost_holder.associate(src) 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 var/datum/admins/D = new(autorank, ckey) GLOB.admin_datums[ckey] = D holder = GLOB.admin_datums[ckey] if(holder) GLOB.admins |= src holder.owner = src connecting_admin = TRUE else if(GLOB.deadmins[ckey]) verbs += /client/proc/readmin connecting_admin = TRUE //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 = 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 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(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 http://www.byond.com/download/ 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 http://www.byond.com/download/ 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 http://www.byond.com/download/ 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, "[url_encode(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 :(",\ "My nipples are so stiff, but Zelda ain't here. :(",\ "Leon senpai, play more Spessmans. :(",\ "If only Serdy were here...",\ "Panic bunker can't keep my love for you out.",\ "Cebu needs to Awoo herself back into my heart.",\ "I don't even have a Turry to snuggle viciously here.",\ "MOM, WHERE ARE YOU??? D:",\ "It's a beautiful day outside. Birds are singing, flowers are blooming. On days like this...kids like you...SHOULD BE BURNING IN HELL.",\ "Sometimes when I have sex, I think about putting an entire peanut butter and jelly sandwich in the VCR.",\ "Oh good, no-one around to watch me lick Goofball's nipples. :D",\ "I've replaced Beepsky with a fidget spinner, glory be autism abuse.",\ "i shure hop dere are no PRED arund!!!!",\ "NO PRED CAN eVER CATCH MI",\ "help, the clown is honking his horn in front of dorms and its interrupting everyones erp"\ ) 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]], " 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]], " 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()) 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, "Sorry but the server is currently not accepting connections from never before seen players.") 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(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()) return if(!account_join_date) account_join_date = "Error" account_age = -1 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()) 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()) return if(query_datediff.NextRow()) account_age = text2num(query_datediff.item[1]) 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()) return 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() 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/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(10) //browse is queued, we don't want them to disconnect before getting the browse() command. qdel(src) return TRUE if (oldcid != computer_id) //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 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] if (computer_id != lastcid) cidcheck[ckey] = computer_id tokens[ckey] = cid_check_reconnect() sleep(10) //browse is queued, we don't want them to disconnect before getting the browse() command. 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() var/const/adminckey = "CID-Error" 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 = '[adminckey]' AND timestamp + INTERVAL 1 DAY < NOW() AND deleted = 0") if(!query_get_notes.Execute()) return if(query_get_notes.NextRow()) return //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()) return if(query_get_notes.NextRow()) if (query_get_notes.item[1] == adminckey) return create_message("note", sql_ckey, adminckey, "Detected as using a cid randomizer.", 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/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 TOPIC_SPAM_DELAY #undef UPLOAD_LIMIT #undef MIN_CLIENT_VERSION //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 // Byond seemingly calls stat, each tick. // Calling things each tick can get expensive real quick. // So we slow this down a little. // See: http://www.byond.com/docs/ref/info.html#/client/proc/Stat /client/Stat() . = ..() if (holder) stoplag(1) else stoplag(5) //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) //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.") view = new_size apply_clickcatcher() if (isliving(mob)) var/mob/living/M = mob M.update_damage_hud() /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)