summaryrefslogtreecommitdiff
path: root/launcher.lua (plain)
blob: 0d1b97ca6ac861efe7d5b0fdde9aff761023ad7e
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)