boot.lua 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. --[[
  2. Copyright (c) 2006-2019 LOVE Development Team
  3. This software is provided 'as-is', without any express or implied
  4. warranty. In no event will the authors be held liable for any damages
  5. arising from the use of this software.
  6. Permission is granted to anyone to use this software for any purpose,
  7. including commercial applications, and to alter it and redistribute it
  8. freely, subject to the following restrictions:
  9. 1. The origin of this software must not be misrepresented; you must not
  10. claim that you wrote the original software. If you use this software
  11. in a product, an acknowledgment in the product documentation would be
  12. appreciated but is not required.
  13. 2. Altered source versions must be plainly marked as such, and must not be
  14. misrepresented as being the original software.
  15. 3. This notice may not be removed or altered from any source distribution.
  16. --]]
  17. -- Make sure love exists.
  18. local love = require("love")
  19. -- Used for setup:
  20. love.path = {}
  21. love.arg = {}
  22. -- Replace any \ with /.
  23. function love.path.normalslashes(p)
  24. return p:gsub("\\", "/")
  25. end
  26. -- Makes sure there is a slash at the end
  27. -- of a path.
  28. function love.path.endslash(p)
  29. if p:sub(-1) ~= "/" then
  30. return p .. "/"
  31. else
  32. return p
  33. end
  34. end
  35. -- Checks whether a path is absolute or not.
  36. function love.path.abs(p)
  37. local tmp = love.path.normalslashes(p)
  38. -- Path is absolute if it starts with a "/".
  39. if tmp:find("/") == 1 then
  40. return true
  41. end
  42. -- Path is absolute if it starts with a
  43. -- letter followed by a colon.
  44. if tmp:find("%a:") == 1 then
  45. return true
  46. end
  47. -- Relative.
  48. return false
  49. end
  50. -- Converts any path into a full path.
  51. function love.path.getFull(p)
  52. if love.path.abs(p) then
  53. return love.path.normalslashes(p)
  54. end
  55. local cwd = love.filesystem.getWorkingDirectory()
  56. cwd = love.path.normalslashes(cwd)
  57. cwd = love.path.endslash(cwd)
  58. -- Construct a full path.
  59. local full = cwd .. love.path.normalslashes(p)
  60. -- Remove trailing /., if applicable
  61. return full:match("(.-)/%.$") or full
  62. end
  63. -- Returns the leaf of a full path.
  64. function love.path.leaf(p)
  65. p = love.path.normalslashes(p)
  66. local a = 1
  67. local last = p
  68. while a do
  69. a = p:find("/", a+1)
  70. if a then
  71. last = p:sub(a+1)
  72. end
  73. end
  74. return last
  75. end
  76. -- Finds the key in the table with the lowest integral index. The lowest
  77. -- will typically the executable, for instance "lua5.1.exe".
  78. function love.arg.getLow(a)
  79. local m = math.huge
  80. for k,v in pairs(a) do
  81. if k < m then
  82. m = k
  83. end
  84. end
  85. return a[m], m
  86. end
  87. love.arg.options = {
  88. console = { a = 0 },
  89. fused = {a = 0 },
  90. game = { a = 1 }
  91. }
  92. love.arg.optionIndices = {}
  93. function love.arg.parseOption(m, i)
  94. m.set = true
  95. if m.a > 0 then
  96. m.arg = {}
  97. for j=i,i+m.a-1 do
  98. love.arg.optionIndices[j] = true
  99. table.insert(m.arg, arg[j])
  100. end
  101. end
  102. return m.a
  103. end
  104. function love.arg.parseOptions()
  105. local game
  106. local argc = #arg
  107. local i = 1
  108. while i <= argc do
  109. -- Look for options.
  110. local m = arg[i]:match("^%-%-(.*)")
  111. if m and m ~= "" and love.arg.options[m] and not love.arg.options[m].set then
  112. love.arg.optionIndices[i] = true
  113. i = i + love.arg.parseOption(love.arg.options[m], i+1)
  114. elseif m == "" then -- handle '--' as an option
  115. love.arg.optionIndices[i] = true
  116. if not game then -- handle '--' followed by game name
  117. game = i + 1
  118. end
  119. break
  120. elseif not game then
  121. game = i
  122. end
  123. i = i + 1
  124. end
  125. if not love.arg.options.game.set then
  126. love.arg.parseOption(love.arg.options.game, game or 0)
  127. end
  128. end
  129. -- Returns the arguments that are passed to your game via love.load()
  130. -- arguments that were parsed as options are skipped.
  131. function love.arg.parseGameArguments(a)
  132. local out = {}
  133. local _, lowindex = love.arg.getLow(a)
  134. local o = lowindex
  135. for i=lowindex, #a do
  136. if not love.arg.optionIndices[i] then
  137. out[o] = a[i]
  138. o = o + 1
  139. end
  140. end
  141. return out
  142. end
  143. function love.createhandlers()
  144. -- Standard callback handlers.
  145. love.handlers = setmetatable({
  146. keypressed = function (b,s,r)
  147. if love.keypressed then return love.keypressed(b,s,r) end
  148. end,
  149. keyreleased = function (b,s)
  150. if love.keyreleased then return love.keyreleased(b,s) end
  151. end,
  152. textinput = function (t)
  153. if love.textinput then return love.textinput(t) end
  154. end,
  155. textedited = function (t,s,l)
  156. if love.textedited then return love.textedited(t,s,l) end
  157. end,
  158. mousemoved = function (x,y,dx,dy,t)
  159. if love.mousemoved then return love.mousemoved(x,y,dx,dy,t) end
  160. end,
  161. mousepressed = function (x,y,b,t,c)
  162. if love.mousepressed then return love.mousepressed(x,y,b,t,c) end
  163. end,
  164. mousereleased = function (x,y,b,t,c)
  165. if love.mousereleased then return love.mousereleased(x,y,b,t,c) end
  166. end,
  167. wheelmoved = function (x,y)
  168. if love.wheelmoved then return love.wheelmoved(x,y) end
  169. end,
  170. touchpressed = function (id,x,y,dx,dy,p)
  171. if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p) end
  172. end,
  173. touchreleased = function (id,x,y,dx,dy,p)
  174. if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p) end
  175. end,
  176. touchmoved = function (id,x,y,dx,dy,p)
  177. if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p) end
  178. end,
  179. joystickpressed = function (j,b)
  180. if love.joystickpressed then return love.joystickpressed(j,b) end
  181. end,
  182. joystickreleased = function (j,b)
  183. if love.joystickreleased then return love.joystickreleased(j,b) end
  184. end,
  185. joystickaxis = function (j,a,v)
  186. if love.joystickaxis then return love.joystickaxis(j,a,v) end
  187. end,
  188. joystickhat = function (j,h,v)
  189. if love.joystickhat then return love.joystickhat(j,h,v) end
  190. end,
  191. gamepadpressed = function (j,b)
  192. if love.gamepadpressed then return love.gamepadpressed(j,b) end
  193. end,
  194. gamepadreleased = function (j,b)
  195. if love.gamepadreleased then return love.gamepadreleased(j,b) end
  196. end,
  197. gamepadaxis = function (j,a,v)
  198. if love.gamepadaxis then return love.gamepadaxis(j,a,v) end
  199. end,
  200. joystickadded = function (j)
  201. if love.joystickadded then return love.joystickadded(j) end
  202. end,
  203. joystickremoved = function (j)
  204. if love.joystickremoved then return love.joystickremoved(j) end
  205. end,
  206. focus = function (f)
  207. if love.focus then return love.focus(f) end
  208. end,
  209. mousefocus = function (f)
  210. if love.mousefocus then return love.mousefocus(f) end
  211. end,
  212. visible = function (v)
  213. if love.visible then return love.visible(v) end
  214. end,
  215. quit = function ()
  216. return
  217. end,
  218. threaderror = function (t, err)
  219. if love.threaderror then return love.threaderror(t, err) end
  220. end,
  221. resize = function (w, h)
  222. if love.resize then return love.resize(w, h) end
  223. end,
  224. filedropped = function (f)
  225. if love.filedropped then return love.filedropped(f) end
  226. end,
  227. directorydropped = function (dir)
  228. if love.directorydropped then return love.directorydropped(dir) end
  229. end,
  230. lowmemory = function ()
  231. if love.lowmemory then love.lowmemory() end
  232. collectgarbage()
  233. collectgarbage()
  234. end,
  235. displayrotated = function (display, orient)
  236. if love.displayrotated then return love.displayrotated(display, orient) end
  237. end,
  238. }, {
  239. __index = function(self, name)
  240. error("Unknown event: " .. name)
  241. end,
  242. })
  243. end
  244. local function uridecode(s)
  245. return s:gsub("%%%x%x", function(str)
  246. return string.char(tonumber(str:sub(2), 16))
  247. end)
  248. end
  249. local no_game_code = false
  250. local invalid_game_path = nil
  251. -- This can't be overridden.
  252. function love.boot()
  253. -- This is absolutely needed.
  254. require("love.filesystem")
  255. local arg0 = love.arg.getLow(arg)
  256. love.filesystem.init(arg0)
  257. local exepath = love.filesystem.getExecutablePath()
  258. if #exepath == 0 then
  259. -- This shouldn't happen, but just in case we'll fall back to arg0.
  260. exepath = arg0
  261. end
  262. no_game_code = false
  263. invalid_game_path = nil
  264. -- Is this one of those fancy "fused" games?
  265. local can_has_game = pcall(love.filesystem.setSource, exepath)
  266. -- It's a fused game, don't parse --game argument
  267. if can_has_game then
  268. love.arg.options.game.set = true
  269. end
  270. -- Parse options now that we know which options we're looking for.
  271. love.arg.parseOptions()
  272. local o = love.arg.options
  273. local is_fused_game = can_has_game or love.arg.options.fused.set
  274. love.filesystem.setFused(is_fused_game)
  275. love.setDeprecationOutput(not love.filesystem.isFused())
  276. local identity = ""
  277. if not can_has_game and o.game.set and o.game.arg[1] then
  278. local nouri = o.game.arg[1]
  279. if nouri:sub(1, 7) == "file://" then
  280. nouri = uridecode(nouri:sub(8))
  281. end
  282. local full_source = love.path.getFull(nouri)
  283. can_has_game = pcall(love.filesystem.setSource, full_source)
  284. if not can_has_game then
  285. invalid_game_path = full_source
  286. end
  287. -- Use the name of the source .love as the identity for now.
  288. identity = love.path.leaf(full_source)
  289. else
  290. -- Use the name of the exe as the identity for now.
  291. identity = love.path.leaf(exepath)
  292. end
  293. -- Try to use the archive containing main.lua as the identity name. It
  294. -- might not be available, in which case the fallbacks above are used.
  295. local realdir = love.filesystem.getRealDirectory("main.lua")
  296. if realdir then
  297. identity = love.path.leaf(realdir)
  298. end
  299. identity = identity:gsub("^([%.]+)", "") -- strip leading "."'s
  300. identity = identity:gsub("%.([^%.]+)$", "") -- strip extension
  301. identity = identity:gsub("%.", "_") -- replace remaining "."'s with "_"
  302. identity = #identity > 0 and identity or "lovegame"
  303. -- When conf.lua is initially loaded, the main source should be checked
  304. -- before the save directory (the identity should be appended.)
  305. pcall(love.filesystem.setIdentity, identity, true)
  306. if can_has_game and not (love.filesystem.getInfo("main.lua") or love.filesystem.getInfo("conf.lua")) then
  307. no_game_code = true
  308. end
  309. if not can_has_game then
  310. local nogame = require("love.nogame")
  311. nogame()
  312. end
  313. end
  314. function love.init()
  315. -- Create default configuration settings.
  316. -- NOTE: Adding a new module to the modules list
  317. -- will NOT make it load, see below.
  318. local c = {
  319. title = "Untitled",
  320. version = love._version,
  321. window = {
  322. width = 800,
  323. height = 600,
  324. x = nil,
  325. y = nil,
  326. minwidth = 1,
  327. minheight = 1,
  328. fullscreen = false,
  329. fullscreentype = "desktop",
  330. display = 1,
  331. vsync = 1,
  332. msaa = 0,
  333. borderless = false,
  334. resizable = false,
  335. centered = true,
  336. highdpi = false,
  337. usedpiscale = true,
  338. },
  339. modules = {
  340. data = true,
  341. event = true,
  342. keyboard = true,
  343. mouse = true,
  344. timer = true,
  345. joystick = true,
  346. touch = true,
  347. image = true,
  348. graphics = true,
  349. audio = true,
  350. math = true,
  351. physics = true,
  352. sound = true,
  353. system = true,
  354. font = true,
  355. thread = true,
  356. window = true,
  357. video = true,
  358. },
  359. audio = {
  360. mixwithsystem = true, -- Only relevant for Android / iOS.
  361. mic = false, -- Only relevant for Android.
  362. },
  363. console = false, -- Only relevant for windows.
  364. identity = false,
  365. appendidentity = false,
  366. externalstorage = false, -- Only relevant for Android.
  367. accelerometerjoystick = true, -- Only relevant for Android / iOS.
  368. gammacorrect = false,
  369. }
  370. -- Console hack, part 1.
  371. local openedconsole = false
  372. if love.arg.options.console.set and love._openConsole then
  373. love._openConsole()
  374. openedconsole = true
  375. end
  376. -- If config file exists, load it and allow it to update config table.
  377. local confok, conferr
  378. if (not love.conf) and love.filesystem and love.filesystem.getInfo("conf.lua") then
  379. confok, conferr = pcall(require, "conf")
  380. end
  381. -- Yes, conf.lua might not exist, but there are other ways of making
  382. -- love.conf appear, so we should check for it anyway.
  383. if love.conf then
  384. confok, conferr = pcall(love.conf, c)
  385. -- If love.conf errors, we'll trigger the error after loading modules so
  386. -- the error message can be displayed in the window.
  387. end
  388. -- Console hack, part 2.
  389. if c.console and love._openConsole and not openedconsole then
  390. love._openConsole()
  391. end
  392. -- Hack for disabling accelerometer-as-joystick on Android / iOS.
  393. if love._setAccelerometerAsJoystick then
  394. love._setAccelerometerAsJoystick(c.accelerometerjoystick)
  395. end
  396. if love._setGammaCorrect then
  397. love._setGammaCorrect(c.gammacorrect)
  398. end
  399. if love._setAudioMixWithSystem then
  400. love._setAudioMixWithSystem(c.audio and c.audio.mixwithsystem)
  401. end
  402. if love._requestRecordingPermission then
  403. love._requestRecordingPermission(c.audio and c.audio.mic)
  404. end
  405. -- Gets desired modules.
  406. for k,v in ipairs{
  407. "data",
  408. "thread",
  409. "timer",
  410. "event",
  411. "keyboard",
  412. "joystick",
  413. "mouse",
  414. "touch",
  415. "sound",
  416. "system",
  417. "audio",
  418. "image",
  419. "video",
  420. "font",
  421. "window",
  422. "graphics",
  423. "math",
  424. "physics",
  425. } do
  426. if c.modules[v] then
  427. require("love." .. v)
  428. end
  429. end
  430. if love.event then
  431. love.createhandlers()
  432. end
  433. -- Check the version
  434. c.version = tostring(c.version)
  435. if not love.isVersionCompatible(c.version) then
  436. local major, minor, revision = c.version:match("^(%d+)%.(%d+)%.(%d+)$")
  437. if (not major or not minor or not revision) or (major ~= love._version_major and minor ~= love._version_minor) then
  438. local msg = ("This game indicates it was made for version '%s' of LOVE.\n"..
  439. "It may not be compatible with the running version (%s)."):format(c.version, love._version)
  440. print(msg)
  441. if love.window then
  442. love.window.showMessageBox("Compatibility Warning", msg, "warning")
  443. end
  444. end
  445. end
  446. if not confok and conferr then
  447. error(conferr)
  448. end
  449. -- Setup window here.
  450. if c.window and c.modules.window then
  451. assert(love.window.setMode(c.window.width, c.window.height,
  452. {
  453. fullscreen = c.window.fullscreen,
  454. fullscreentype = c.window.fullscreentype,
  455. vsync = c.window.vsync,
  456. msaa = c.window.msaa,
  457. stencil = c.window.stencil,
  458. depth = c.window.depth,
  459. resizable = c.window.resizable,
  460. minwidth = c.window.minwidth,
  461. minheight = c.window.minheight,
  462. borderless = c.window.borderless,
  463. centered = c.window.centered,
  464. display = c.window.display,
  465. highdpi = c.window.highdpi,
  466. usedpiscale = c.window.usedpiscale,
  467. x = c.window.x,
  468. y = c.window.y,
  469. }), "Could not set window mode")
  470. love.window.setTitle(c.window.title or c.title)
  471. if c.window.icon then
  472. assert(love.image, "If an icon is set in love.conf, love.image must be loaded!")
  473. love.window.setIcon(love.image.newImageData(c.window.icon))
  474. end
  475. end
  476. -- Our first timestep, because window creation can take some time
  477. if love.timer then
  478. love.timer.step()
  479. end
  480. if love.filesystem then
  481. love.filesystem._setAndroidSaveExternal(c.externalstorage)
  482. love.filesystem.setIdentity(c.identity or love.filesystem.getIdentity(), c.appendidentity)
  483. if love.filesystem.getInfo("main.lua") then
  484. require("main")
  485. end
  486. end
  487. if no_game_code then
  488. error("No code to run\nYour game might be packaged incorrectly.\nMake sure main.lua is at the top level of the zip.")
  489. elseif invalid_game_path then
  490. error("Cannot load game at path '" .. invalid_game_path .. "'.\nMake sure a folder exists at the specified path.")
  491. end
  492. end
  493. -----------------------------------------------------------
  494. -- Default callbacks.
  495. -----------------------------------------------------------
  496. function love.run()
  497. if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
  498. -- We don't want the first frame's dt to include time taken by love.load.
  499. if love.timer then love.timer.step() end
  500. local dt = 0
  501. -- Main loop time.
  502. return function()
  503. -- Process events.
  504. if love.event then
  505. love.event.pump()
  506. for name, a,b,c,d,e,f in love.event.poll() do
  507. if name == "quit" then
  508. if not love.quit or not love.quit() then
  509. return a or 0
  510. end
  511. end
  512. love.handlers[name](a,b,c,d,e,f)
  513. end
  514. end
  515. -- Update dt, as we'll be passing it to update
  516. if love.timer then dt = love.timer.step() end
  517. -- Call update and draw
  518. if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
  519. if love.graphics and love.graphics.isActive() then
  520. love.graphics.origin()
  521. love.graphics.clear(love.graphics.getBackgroundColor())
  522. if love.draw then love.draw() end
  523. love.graphics.present()
  524. end
  525. if love.timer then love.timer.sleep(0.001) end
  526. end
  527. end
  528. local debug, print, error = debug, print, error
  529. function love.threaderror(t, err)
  530. error("Thread error ("..tostring(t)..")\n\n"..err, 0)
  531. end
  532. local utf8 = require("utf8")
  533. local function error_printer(msg, layer)
  534. print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
  535. end
  536. function love.errhand(msg)
  537. msg = tostring(msg)
  538. error_printer(msg, 2)
  539. if not love.window or not love.graphics or not love.event then
  540. return
  541. end
  542. if not love.graphics.isCreated() or not love.window.isOpen() then
  543. local success, status = pcall(love.window.setMode, 800, 600)
  544. if not success or not status then
  545. return
  546. end
  547. end
  548. -- Reset state.
  549. if love.mouse then
  550. love.mouse.setVisible(true)
  551. love.mouse.setGrabbed(false)
  552. love.mouse.setRelativeMode(false)
  553. if love.mouse.isCursorSupported() then
  554. love.mouse.setCursor()
  555. end
  556. end
  557. if love.joystick then
  558. -- Stop all joystick vibrations.
  559. for i,v in ipairs(love.joystick.getJoysticks()) do
  560. v:setVibration()
  561. end
  562. end
  563. if love.audio then love.audio.stop() end
  564. love.graphics.reset()
  565. local font = love.graphics.setNewFont(14)
  566. love.graphics.setColor(1, 1, 1, 1)
  567. local trace = debug.traceback()
  568. love.graphics.origin()
  569. local sanitizedmsg = {}
  570. for char in msg:gmatch(utf8.charpattern) do
  571. table.insert(sanitizedmsg, char)
  572. end
  573. sanitizedmsg = table.concat(sanitizedmsg)
  574. local err = {}
  575. table.insert(err, "Error\n")
  576. table.insert(err, sanitizedmsg)
  577. if #sanitizedmsg ~= #msg then
  578. table.insert(err, "Invalid UTF-8 string in error message.")
  579. end
  580. table.insert(err, "\n")
  581. for l in trace:gmatch("(.-)\n") do
  582. if not l:match("boot.lua") then
  583. l = l:gsub("stack traceback:", "Traceback\n")
  584. table.insert(err, l)
  585. end
  586. end
  587. local p = table.concat(err, "\n")
  588. p = p:gsub("\t", "")
  589. p = p:gsub("%[string \"(.-)\"%]", "%1")
  590. local function draw()
  591. local pos = 70
  592. love.graphics.clear(89/255, 157/255, 220/255)
  593. love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
  594. love.graphics.present()
  595. end
  596. local fullErrorText = p
  597. local function copyToClipboard()
  598. if not love.system then return end
  599. love.system.setClipboardText(fullErrorText)
  600. p = p .. "\nCopied to clipboard!"
  601. draw()
  602. end
  603. if love.system then
  604. p = p .. "\n\nPress Ctrl+C or tap to copy this error"
  605. end
  606. return function()
  607. love.event.pump()
  608. for e, a, b, c in love.event.poll() do
  609. if e == "quit" then
  610. return 1
  611. elseif e == "keypressed" and a == "escape" then
  612. return 1
  613. elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
  614. copyToClipboard()
  615. elseif e == "touchpressed" then
  616. local name = love.window.getTitle()
  617. if #name == 0 or name == "Untitled" then name = "Game" end
  618. local buttons = {"OK", "Cancel"}
  619. if love.system then
  620. buttons[3] = "Copy to clipboard"
  621. end
  622. local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
  623. if pressed == 1 then
  624. return 1
  625. elseif pressed == 3 then
  626. copyToClipboard()
  627. end
  628. end
  629. end
  630. draw()
  631. if love.timer then
  632. love.timer.sleep(0.1)
  633. end
  634. end
  635. end
  636. -----------------------------------------------------------
  637. -- The root of all calls.
  638. -----------------------------------------------------------
  639. return function()
  640. local func
  641. local inerror = false
  642. local function deferErrhand(...)
  643. local errhand = love.errorhandler or love.errhand
  644. local handler = (not inerror and errhand) or error_printer
  645. inerror = true
  646. func = handler(...)
  647. end
  648. local function earlyinit()
  649. -- If love.boot fails, return 1 and finish immediately
  650. local result = xpcall(love.boot, error_printer)
  651. if not result then return 1 end
  652. -- If love.init or love.run fails, don't return a value,
  653. -- as we want the error handler to take over
  654. result = xpcall(love.init, deferErrhand)
  655. if not result then return end
  656. -- NOTE: We can't assign to func directly, as we'd
  657. -- overwrite the result of deferErrhand with nil on error
  658. local main
  659. result, main = xpcall(love.run, deferErrhand)
  660. if result then
  661. func = main
  662. end
  663. end
  664. func = earlyinit
  665. while func do
  666. local _, retval = xpcall(func, deferErrhand)
  667. if retval then return retval end
  668. coroutine.yield()
  669. end
  670. return 1
  671. end