1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
--- 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.
--@server sv_pac.lua
--@alias p3
--[[
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/<costume>.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 log = nrequire("log.lua")
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)
hook.Add( "PrePACEditorOpen", "stoppaceditor", function( ply )
return ply:IsSuperAdmin(), "This is accessable only to superadmins"
end )
local pacsources = {
["data/artery/pacs/"] = "GAME"
}
--- Add a resource that we can load pacs from.
-- Automatically tries to reload when a pac can't be found
-- Starts out with "data/artery/pacs/" = "GAME"
--@tparam string filepath the path to search for pacs
--@tparam string|bool from the game path ("GAME" | "LUA" | "DATA" | "MOD"), can also be a boolean (true="GAME" | false="DATA")
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()
---Reloads the hashes.
-- Run this if you changed a pac and want to reload it. 
--@concommand artery_reload_pac_hashes
concommand.Add("artery_reload_pac_hashes",function(ply,cmd,args)
if not ply:IsAdmin() then return end
loadhashes()
end)
---Print the pac hashes.
-- Prints all the pac hashes that are loaded (for debugging).
--@concommand artery_print_pac_hashes
concommand.Add("artery_print_pac_hashes",function(ply,cmd,args)
PrintTable(pachashes)
end)
local appliedpacs = {}
---Apply a pac to an entity.
-- Applies a pac to an entity, pac should be a file name (not path!) without the .txt
function p3.ApplyPac(what, name)
log.debug(string.format("Applying pac %q to %q",name,tostring(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
---Remove a pac to an entity.
-- Removes a pac to an entity, pac should be a file name without the .txt
function p3.RemovePac(what, name)
log.debug(string.format("Removeing pac %q from %q",name,tostring(what)))
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
---Find the pacs applied to an entity.
-- Returns an array of the names of all the pacs applied to a certain entity
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()
log.debug(string.format("Player %q requested pac %q",tostring(ply),pac_name))
--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
log.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)
---Reload pacs.
-- Does all the things needed to edit pac's live
concommand.Add("artery_reload_pacs",function(ply,cmd,args)
if not ply:IsAdmin() then return end
pac_cache = {}
pacs_in_cache = 0
loadhashes()
end)
return p3
|