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
|
local settings = settings or { } -- filled in generate.lua
assert(settings.product)
_WIN32 = not not package.cpath:match('dll$')
local _SOUND = false -- TODO initialize sound
local envHome = os.getenv('HOME')
require 'lfs'
require 'SDL'
-- remove unwanted parts of standard libraries
if not settings.debug then
if jit then jit.util = nil end
debug.getfenv, debug.getlocal, debug.getmetatable, debug.getupvalue,
debug.setfenv, debug.setlocal, debug.setmetatable, debug.setupvalue,
debug.getregistry = nil
end
local rff, md, rd, rm = SDL.RWFromFile, lfs.mkdir, lfs.rmdir, os.remove
io.input, io.output, io.lines, io.open, io.popen, io.tmpfile = nil
os.execute, os.getenv, os.rename, os.remove, os.tmpname = nil
-- lfs.chdir could be used to change meaning of userDir
lfs.lock, lfs.unlock, lfs.chdir, lfs.mkdir, lfs.rmdir, lfs.touch = nil
package.loaders = { package.loaders[1] }; package.loadlib = nil
package.loaded = nil -- could be used to make require() load SDL again
SDL.RWFromFile = function (f) return rff(f, 'rb') end
-- parse command-line arguments
local userDir, dataDir, run
local i = 1; while i <= #arg do
local a, v = arg[i], arg[i]:gsub('^[^=]*=', '')
if a == '--' then break
elseif a:sub(1, 10) == '--datadir=' then dataDir = v; table.remove(arg, i)
elseif a:sub(1, 10) == '--userdir=' then userDir = v; table.remove(arg, i)
elseif a:sub(1, 6) == '--run=' then run = v; table.remove(arg, i)
else
i = i + 1
end
end
if _WIN32 then
-- avoid paths with multi-byte characters at all costs
-- (for example, lfs.* and SDL.RWFromFile expect args in different charsets)
userDir = userDir or './userdata'
dataDir = dataDir or './data'
end
-- find user directory
if not userDir then
local appdataDir = require'win'.appdata()
if appdataDir then
if lfs.attributes(appdataDir) then
userDir = appdataDir..'/'..settings.product
else
io.write("warning: can't open directory '"..appdataDir.."'\n")
end
elseif envHome then
if lfs.attributes(envHome) then
userDir = envHome..'/.'..settings.product:lower()
else
io.write("warning: can't open directory '"..envHome.."'\n")
end
end
end
if not userDir then
userDir = './userdata'
io.write("warning: couldn't find user's data dir, using ./userdata\n")
end
require 'FS'
local function checkValidUserPath(path)
-- user dir completely disabled?
if userDir == '' then return false end
-- contains \ (incorrect directory separator)?
-- contains a non-ASCII character?
-- contains '..' (up one level)?
path = '/'..path..'/'
return not (path:match('\\') or path:match('[^ -~]') or
path:match('/%.%./'))
end
local userMkdirParent; function userMkdirParent(path)
if not lfs.attributes(userDir, 'mode') then
io.write("creating user data directory: "..userDir.."\n")
assert(md(userDir))
end
path = path:gsub('/*[^/]*/*$', '')
if path == '' or path == '/' then return end
if not lfs.attributes(userDir..'/'..path, 'mode') then
userMkdirParent(path); assert(md(userDir..'/'..path))
end
end
function FS.write(path, body)
collectgarbage'collect' -- if path is open for reading, try to close it
assert(checkValidUserPath(path), "malformed or forbidden path")
userMkdirParent(path)
local rw = assert(rff(userDir..'/'..path, 'wb'))
assert(rw:write(body)); rw:close()
end
function FS.remove(path)
collectgarbage'collect'
assert(checkValidUserPath(path), "malformed or forbidden path")
if lfs.attributes(userDir..'/'..path) then
assert(rm(userDir..'/'..path))
end
end
-- TODO consider using rd (rmdir)
function Report(e) if e then io.write(debug.traceback(e, 2)..'\n') end end
local function protectedLauncher()
FS.DataDirectory:new{ id = 'user', path = userDir }:appendToActiveList()
if not dataDir then
-- find data directory (paths are in reverse priority order)
local try = { settings.datadir, arg[0]:gsub('[^/\\]*$', '')..'data' }
for _,dir in ipairs(try) do
if lfs.attributes(dir..'/manifest.lua') then dataDir = dir end end
assert(dataDir, "can't find data files in: "..table.concat(try, ", "))
end
FS.DataDirectory:new{ id = 'data', path = dataDir }:appendToActiveList()
if run ~= '-' then require(run or 'manifest') end
require'start'
end
xpcall(protectedLauncher, Report)
|