--[[ The server side lazy loader for pac3 costumes PAC3 outfits are not downloaded until they are needed, we can keep the inital download to join the server pretty small this way. The downside is that the game might lag a little when someone wears something that is rare/new and everyone has to download it. Console Commands: artery_reload_pacs The server will cache PAC's so it dosen't need to read form disk every time. If you're live editing pac's on the server, and are wondering why your changes aren't showing, use this command. There is no command to clear client cache, because applying a pac also sends a hash of the pac to the client, and the client re-downloads if the hashes are different. Functions: entity:ApplyPac(String costume) :: nil Find the file /data/pacs/.pac (on the server) and tell clients to apply it to ent. This will automatically download the pac to any clients nessessary. entity:RemovePac(String costume) :: nil Remove the pac from the entity. entity:GetPacs() :: table Retreives a list of the pacs an entity is wearing, as strings Network Strings: "artery_getworldpacs", "artery_giveworldpacs", "artery_applypac", "artery_removepac", "artery_requestpac", "artery_downloadpac" ]] local p3 = {} local nwstrings = { "artery_getworldpacs", "artery_giveworldpacs", "artery_applypac", "artery_removepac", "artery_requestpac", "artery_downloadpac" } for _,v in pairs(nwstrings) do util.AddNetworkString(v) end --If the server has pac installed, restrict player's from putting on their own pacs hook.Add("PrePACConfigApply", "stoppacs", function(ply, outfit_data) if not ply:IsAdmin() then return false, "You don't have permission to do that!" end end) local pacsources = { ["data/artery/pacs/"] = "GAME" } function p3.AddPacSource(filepath,from) pacsources[filepath] = from end --When the server starts, get all the pacs and calculate their hashes so we can index them quickly without haveing to read from disk each time. local pachashes = {} local function loadhashes() for path,part in pairs(pacsources) do local files,_ = file.Find(path.."*",part) for _,v in ipairs(files) do local filepath = string.format("%s%s",path,v) local filetext = file.Read(filepath,part) local filehash = util.CRC(filetext) pachashes[string.StripExtension(v)] = tonumber(filehash) end end end loadhashes() concommand.Add("artery_reload_pac_hashes",loadhashes) concommand.Add("artery_print_pac_hashes",function(ply,cmd,args) PrintTable(pachashes) end) local appliedpacs = {} function p3.ApplyPac(what, name) print("Applying pac", name, "to",what) assert(pachashes[name],string.format("Tried to apply pac %s which didn't have a hash. Pac hashes are:%s",name,table.ToString(pachashes,"pachashes",true))) appliedpacs[what] = appliedpacs[what] or {} appliedpacs[what][name] = pachashes[name] net.Start("artery_applypac") net.WriteEntity(what) net.WriteString(name) --If this pac is from an addon that was loaded after this file was, we may need to reload hashes. if not pachashes[name] then loadhashes() end net.WriteUInt(pachashes[name],32) net.Broadcast() end function p3.RemovePac(what, name) print("Removeing pac",what,"from",name) assert(appliedpacs[what][name],"Attempted to remove a pac that an entity is not wearing!") appliedpacs[what][name] = nil if #appliedpacs[what] == 0 then appliedpacs[what] = nil end net.Start("artery_removepac") net.WriteEntity(what) net.WriteString(name) net.WriteUInt(pachashes[name],32) net.Broadcast() end function p3.GetPacs(what) return appliedpacs[what] or {} end --If a player joins the server, tell them all about the pacs that are applied net.Receive("artery_getworldpacs",function(ln,ply) net.Start("artery_giveworldpacs") net.WriteTable(appliedpacs) net.Send(ply) end) local max_pacs_in_cache = 10 local pacs_in_cache = 0 local pac_cache = {} --Load something from our cache local function cacheload(key) --If it's already in the cache, just update the time it was last used and return the pac. if pac_cache[key] ~= nil then print("Pac was already in the cache.") pac_cache[key].time = CurTime() if pac_cache[key].pac == nil then PrintTable(pac_cache) error("Pac was loaded, but the txt was nil!") end return pac_cache[key].pac end --Otherwise, we need to load it. local pacpath = string.format("data/artery/pacs/%s.txt",key) local pacfile = file.Read(pacpath,"GAME") print("Pac was not in cache, reloading, pac txt is",pacfile) --If we haven't reached max cache yet, just put it in if pacs_in_cache < max_pacs_in_cache then pac_cache[key] = { ["pac"] = pacfile, ["time"] = CurTime() } pacs_in_cache = pacs_in_cache + 1 return pacfile else --We have max pac's, delete the oldest one, and put the new one in. local oldest,oldstr = CurTime(),"" for k,v in pairs(pac_cache) do if v.time < oldest then oldest = v.time oldstr = k end end pac_cache[oldstr] = nil pac_cache[key] = { ["pac"] = pacfile, ["time"] = CurTime() } return pacfile end end net.Receive("artery_requestpac",function(ln,ply) local pac_name = net.ReadString() --Double check that we're not executing a directory traversal attack https://www.owasp.org/index.php/Path_Traversal if string.find(pac_name,"..",1,true) then Report(string.format("Directory traversal attack attempted by %s:%s using artery_requestpac string %q",ply:Nick(),ply:SteamID64(),pac_name)) end local pac_txt = cacheload(pac_name) assert(pac_name,"Pac's name was nil!") assert(pac_txt, "Pac's txt was nil (from the cache)") net.Start("artery_downloadpac") net.WriteString(pac_name) net.WriteString(pac_txt) net.WriteUInt(pachashes[pac_name],32) net.Send(ply) end) --Does all the things needed to edit pac's live concommand.Add("artery_reload_pacs",function() pac_cache = {} pacs_in_cache = 0 loadhashes() end) return p3