Files
4ab234f10c maploader / overmaps update - struct system, overmaps location bindings, misc backend improvements (#6403)
tl;dr level collections that can easily be logically restructured/moved
as necessary

will eventually be used for radio and overmaps location resolution.

this is the last part of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP/pull/4841
that has not yet been integrated.
this will be a draft for a while.

---------

Co-authored-by: LetterN <24603524+LetterN@users.noreply.github.com>
2025-11-06 17:49:23 -08:00

171 lines
7.5 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
/**
* Obfuscation module
*
* todo: should this be mapping? this is a lot more important than just mapping
*
* * Generates **mangled** IDs; these are either persistently-unique or round-local IDs that are separate per map / map template.
* * Generates **obfuscated** IDs; these are mangled and obfuscated IDs that are safe to reveal to players.
*
* Mangling keys are based on the **load instance** the mapping subsystem is in.
* * It is crucial to call these ID transformation procs during preloading_from_mapload(), not Initialize().
* * It is usually already too late by the time Initialize() fires.
* * By default, /datum/map (not /datum/map_level), /datum/map_template, /datum/shuttle_template are the three things that form new mangling boundaries/contexts.
*/
/**
* Called at init first thing to setup mangling data
*/
/datum/controller/subsystem/mapping/proc/init_obfuscation_data()
// no (real) chance of collisions
var/hex_string = "[num2hex(world.realtime)]"
var/list/built = list()
for(var/i in 1 to ceil(length(hex_string) / 4))
built += copytext(hex_string, 1 + (i - 1) * 4, 1 + (i) * 4)
round_global_descriptor = jointext(built, "-")
/**
* Get a short hash for a map specific ID.
*/
/datum/controller/subsystem/mapping/proc/hash_for_mangling_id(id, short)
if(isnull(round_local_mangling_cache[id]))
// generate hash
var/increment = 0
var/generated
do
generated = "[id][increment++]"
if(short)
// 4 characters; short is usually used when we'll be mangled with a round-global id anyways,
// which provides a massive amount of collision / guesswork resistance
// (in theory; players can always guess for current round)
// (you aren't using round-global ids for no good reason, right?)
generated = copytext(md5(generated), 1, 5)
else
// 8 characters - we only need 4 to realistically be low-collision,
// but it's this long to prevent players from being funny and guessing shit
generated = copytext(md5(generated), 1, 9)
// "1234-5678" it just to be pretty
generated = "[copytext(generated, 1, 5)]-[copytext(generated, 5, 9)]"
while(round_local_mangling_reverse_cache[generated] || round_local_obfuscation_reverse_cache[generated])
round_local_mangling_cache[id] = generated
round_local_mangling_reverse_cache[generated] = id
return round_local_mangling_cache[id]
/**
* This must be called in **preloading_from_mapload()**, not Initialize(), unlike what usual ss13 init logic says.
*
* * This is a direct maploader hook to generate a mangled (not obfuscated) ID.
* * This will be unique to the load instance it's being called from.
* * This is not globally (cross-round) unique.
* * This generates human-readable IDs
* * This does not generate IDs that should be player accessible.
* * This generates IDs with a different namespace than other mangling/obfuscation procs.
*
* @params
* * id - original id
* * with_mangling_id - mangling id provided to preloading_from_mapload()
*/
/datum/controller/subsystem/mapping/proc/mangled_round_local_id(id, with_mangling_id)
if(!id)
return id
return "[id]-[hash_for_mangling_id(with_mangling_id)]"
/**
* This must be called in **preloading_from_mapload()**, not Initialize(), unlike what usual ss13 init logic says.
*
* * This is a direct maploader hook to generate a mangled (not obfuscated) ID.
* * This will be unique to the load instance it's being called from.
* * This will be globally (cross-round) unique.
* * This generates human-readable IDs
* * This does not generate IDs that should be player accessible.
* * This generates IDs with a different namespace than other mangling/obfuscation procs.
*
* @params
* * id - original id
* * with_mangling_id - mangling id provided to preloading_from_mapload()
*/
/datum/controller/subsystem/mapping/proc/mangled_persistent_id(id, with_mangling_id)
if(!id)
return id
return "[id]-[hash_for_mangling_id(with_mangling_id, TRUE)]-[round_global_descriptor]"
/**
* Generates an obfuscated ID.
*
* * This is not globally (cross-round) unique.
* * This does not necessarily generate human-readable IDs, but tries its best!
* * This generates IDs that may be player accessible.
* * Better results are obtained by calling this in preloading_from_mapload(), but it is not mandatory.
* * This only mangles the ID if called with_mangling_id in preloading_from_mapload. Please be aware of that.
* * This generates IDs with a different namespace than other mangling/obfuscation procs.
*
* @params
* * id - original id
* * with_mangling_id - provide the mangling id if being called in preloading_from_mapload()
* * with_visible_key - provide and we'll include it at the start so players know what a key is for
*/
/datum/controller/subsystem/mapping/proc/obfuscated_round_local_id(id, with_mangling_id, with_visible_key)
if(!id)
return id
var/cache_key
if(with_mangling_id)
var/mangling_hash = hash_for_mangling_id("[with_mangling_id]", TRUE)
cache_key = "[id]-[mangling_hash]"
else
cache_key = "[id]"
if(isnull(round_local_obfuscation_cache[cache_key]))
// tl;dr we md5 it again with the id to ensure the level's mangling id isn't accessible directly
var/generation
var/notch = 0
do
generation = md5("[cache_key]-[notch]")
generation = "[copytext(generation, 1, 5)]-[copytext(generation, 5, 9)]"
if(with_visible_key)
generation = "[with_visible_key]-[generation]"
while(round_local_obfuscation_reverse_cache[generation] || round_local_mangling_reverse_cache[generation])
round_local_obfuscation_cache[cache_key] = generation
round_local_obfuscation_reverse_cache[generation] = cache_key
return round_local_obfuscation_cache[cache_key]
/**
* Generates an obfuscated ID.
*
* * This will be globally (cross-round) unique.
* * This does not necessarily generate human-readable IDs, but tries its best!
* * This generates IDs that may be player accessible.
* * Better results are obtained by calling this in preloading_from_mapload(), but it is not mandatory.
* * This only mangles the ID if called with_mangling_id in preloading_from_mapload. Please be aware of that.
* * This generates IDs with a different namespace than other mangling/obfuscation procs.
*
* @params
* * id - original id
* * with_mangling_id - provide the mangling id if being called in preloading_from_mapload()
* * with_visible_key - provide and we'll include it at the start so players know what a key is for
*/
/datum/controller/subsystem/mapping/proc/obfuscated_persistent_id(id, with_mangling_id, with_visible_key)
if(!id)
return id
var/cache_key
if(with_mangling_id)
var/mangling_hash = hash_for_mangling_id("[with_mangling_id]", TRUE)
cache_key = "[id]-[mangling_hash]"
else
cache_key = "[id]"
if(isnull(round_local_obfuscation_cache[cache_key]))
// tl;dr we md5 it again with the id to ensure the level's mangling id isn't accessible directly
var/generation
var/notch = 0
do
generation = md5("[cache_key]-[notch]")
generation = "[copytext(generation, 1, 5)]-[copytext(generation, 5, 9)]"
if(with_visible_key)
generation = "[with_visible_key]-[generation]"
// unsecure, yes, but without a SQL unique lookup we need a verifiable way of ensuring no collision property.
generation = "[generation]-[round_global_descriptor]"
while(round_local_obfuscation_reverse_cache[generation] || round_local_mangling_reverse_cache[generation])
round_local_obfuscation_cache[cache_key] = generation
round_local_obfuscation_reverse_cache[generation] = cache_key
return round_local_obfuscation_cache[cache_key]