boot.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. --[[
  2. Copyright (c) 2006-2015 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 string.gsub(p, "\\", "/")
  25. end
  26. -- Makes sure there is a slash at the end
  27. -- of a path.
  28. function love.path.endslash(p)
  29. if string.sub(p, string.len(p)-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 string.find(tmp, "/") == 1 then
  40. return true
  41. end
  42. -- Path is absolute if it starts with a
  43. -- letter followed by a colon.
  44. if string.find(tmp, "%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 = string.find(p, "/", a+1)
  70. if a then
  71. last = string.sub(p, 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]
  86. end
  87. love.arg.options = {
  88. console = { a = 0 },
  89. fused = {a = 0 },
  90. game = { a = 1 }
  91. }
  92. function love.arg.parse_option(m, i)
  93. m.set = true
  94. if m.a > 0 then
  95. m.arg = {}
  96. for j=i,i+m.a-1 do
  97. table.insert(m.arg, arg[j])
  98. i = j
  99. end
  100. end
  101. return i
  102. end
  103. function love.arg.parse_options()
  104. local game
  105. local argc = #arg
  106. for i=1,argc do
  107. -- Look for options.
  108. local s, e, m = string.find(arg[i], "%-%-(.+)")
  109. if m and love.arg.options[m] then
  110. i = love.arg.parse_option(love.arg.options[m], i+1)
  111. elseif not game then
  112. game = i
  113. end
  114. end
  115. if not love.arg.options.game.set then
  116. love.arg.parse_option(love.arg.options.game, game or 0)
  117. end
  118. end
  119. function love.createhandlers()
  120. -- Standard callback handlers.
  121. love.handlers = setmetatable({
  122. keypressed = function (b,s,r)
  123. if love.keypressed then return love.keypressed(b,s,r) end
  124. end,
  125. keyreleased = function (b,s)
  126. if love.keyreleased then return love.keyreleased(b,s) end
  127. end,
  128. textinput = function (t)
  129. if love.textinput then return love.textinput(t) end
  130. end,
  131. textedited = function (t,s,l)
  132. if love.textedited then return love.textedited(t,s,l) end
  133. end,
  134. mousemoved = function (x,y,dx,dy,t)
  135. if love.mousemoved then return love.mousemoved(x,y,dx,dy,t) end
  136. end,
  137. mousepressed = function (x,y,b,t)
  138. if love.mousepressed then return love.mousepressed(x,y,b,t) end
  139. end,
  140. mousereleased = function (x,y,b,t)
  141. if love.mousereleased then return love.mousereleased(x,y,b,t) end
  142. end,
  143. wheelmoved = function (x,y)
  144. if love.wheelmoved then return love.wheelmoved(x,y) end
  145. end,
  146. touchpressed = function (id,x,y,dx,dy,p)
  147. if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p) end
  148. end,
  149. touchreleased = function (id,x,y,dx,dy,p)
  150. if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p) end
  151. end,
  152. touchmoved = function (id,x,y,dx,dy,p)
  153. if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p) end
  154. end,
  155. joystickpressed = function (j,b)
  156. if love.joystickpressed then return love.joystickpressed(j,b) end
  157. end,
  158. joystickreleased = function (j,b)
  159. if love.joystickreleased then return love.joystickreleased(j,b) end
  160. end,
  161. joystickaxis = function (j,a,v)
  162. if love.joystickaxis then return love.joystickaxis(j,a,v) end
  163. end,
  164. joystickhat = function (j,h,v)
  165. if love.joystickhat then return love.joystickhat(j,h,v) end
  166. end,
  167. gamepadpressed = function (j,b)
  168. if love.gamepadpressed then return love.gamepadpressed(j,b) end
  169. end,
  170. gamepadreleased = function (j,b)
  171. if love.gamepadreleased then return love.gamepadreleased(j,b) end
  172. end,
  173. gamepadaxis = function (j,a,v)
  174. if love.gamepadaxis then return love.gamepadaxis(j,a,v) end
  175. end,
  176. joystickadded = function (j)
  177. if love.joystickadded then return love.joystickadded(j) end
  178. end,
  179. joystickremoved = function (j)
  180. if love.joystickremoved then return love.joystickremoved(j) end
  181. end,
  182. focus = function (f)
  183. if love.focus then return love.focus(f) end
  184. end,
  185. mousefocus = function (f)
  186. if love.mousefocus then return love.mousefocus(f) end
  187. end,
  188. visible = function (v)
  189. if love.visible then return love.visible(v) end
  190. end,
  191. quit = function ()
  192. return
  193. end,
  194. threaderror = function (t, err)
  195. if love.threaderror then return love.threaderror(t, err) end
  196. end,
  197. resize = function (w, h)
  198. if love.resize then return love.resize(w, h) end
  199. end,
  200. filedropped = function (f)
  201. if love.filedropped then return love.filedropped(f) end
  202. end,
  203. directorydropped = function (dir)
  204. if love.directorydropped then return love.directorydropped(dir) end
  205. end,
  206. lowmemory = function ()
  207. collectgarbage()
  208. if love.lowmemory then return love.lowmemory() end
  209. end,
  210. }, {
  211. __index = function(self, name)
  212. error("Unknown event: " .. name)
  213. end,
  214. })
  215. end
  216. local function uridecode(s)
  217. return s:gsub("%%%x%x", function(str)
  218. return string.char(tonumber(str:sub(2), 16))
  219. end)
  220. end
  221. local no_game_code = false
  222. -- This can't be overriden.
  223. function love.boot()
  224. -- This is absolutely needed.
  225. require("love.filesystem")
  226. love.arg.parse_options()
  227. local o = love.arg.options
  228. local arg0 = love.arg.getLow(arg)
  229. love.filesystem.init(arg0)
  230. local exepath = love.filesystem.getExecutablePath()
  231. if #exepath == 0 then
  232. -- This shouldn't happen, but just in case we'll fall back to arg0.
  233. exepath = arg0
  234. end
  235. -- Is this one of those fancy "fused" games?
  236. local can_has_game = pcall(love.filesystem.setSource, exepath)
  237. local is_fused_game = can_has_game or love.arg.options.fused.set
  238. love.filesystem.setFused(is_fused_game)
  239. local identity = ""
  240. if not can_has_game and o.game.set and o.game.arg[1] then
  241. local nouri = o.game.arg[1]
  242. if nouri:sub(1, 7) == "file://" then
  243. nouri = uridecode(nouri:sub(8))
  244. end
  245. local full_source = love.path.getfull(nouri)
  246. can_has_game = pcall(love.filesystem.setSource, full_source)
  247. -- Use the name of the source .love as the identity for now.
  248. identity = love.path.leaf(full_source)
  249. else
  250. -- Use the name of the exe as the identity for now.
  251. identity = love.path.leaf(exepath)
  252. end
  253. -- Try to use the archive containing main.lua as the identity name. It
  254. -- might not be available, in which case the fallbacks above are used.
  255. local realdir = love.filesystem.getRealDirectory("main.lua")
  256. if realdir then
  257. identity = love.path.leaf(realdir)
  258. end
  259. identity = identity:gsub("^([%.]+)", "") -- strip leading "."'s
  260. identity = identity:gsub("%.([^%.]+)$", "") -- strip extension
  261. identity = identity:gsub("%.", "_") -- replace remaining "."'s with "_"
  262. identity = #identity > 0 and identity or "lovegame"
  263. -- When conf.lua is initially loaded, the main source should be checked
  264. -- before the save directory (the identity should be appended.)
  265. pcall(love.filesystem.setIdentity, identity, true)
  266. if can_has_game and not (love.filesystem.isFile("main.lua") or love.filesystem.isFile("conf.lua")) then
  267. no_game_code = true
  268. end
  269. if not can_has_game then
  270. local nogame = require("love.nogame")
  271. nogame()
  272. end
  273. end
  274. function love.init()
  275. -- Create default configuration settings.
  276. -- NOTE: Adding a new module to the modules list
  277. -- will NOT make it load, see below.
  278. local c = {
  279. title = "Untitled",
  280. version = love._version,
  281. window = {
  282. width = 800,
  283. height = 600,
  284. x = nil,
  285. y = nil,
  286. minwidth = 1,
  287. minheight = 1,
  288. fullscreen = false,
  289. fullscreentype = "desktop",
  290. display = 1,
  291. vsync = true,
  292. msaa = 0,
  293. borderless = false,
  294. resizable = false,
  295. centered = true,
  296. highdpi = false,
  297. },
  298. modules = {
  299. event = true,
  300. keyboard = true,
  301. mouse = true,
  302. timer = true,
  303. joystick = true,
  304. touch = true,
  305. image = true,
  306. graphics = true,
  307. audio = true,
  308. math = true,
  309. physics = true,
  310. sound = true,
  311. system = true,
  312. font = true,
  313. thread = true,
  314. window = true,
  315. video = true,
  316. },
  317. console = false, -- Only relevant for windows.
  318. identity = false,
  319. appendidentity = false,
  320. useexternalstorage = false, -- Only relevant for Android.
  321. accelerometerjoystick = true, -- Only relevant for Android / iOS.
  322. gammacorrect = false,
  323. }
  324. -- Console hack, part 1.
  325. local openedconsole = false
  326. if love.arg.options.console.set and love._openConsole then
  327. love._openConsole()
  328. openedconsole = true
  329. end
  330. -- If config file exists, load it and allow it to update config table.
  331. local confok, conferr
  332. if (not love.conf) and love.filesystem and love.filesystem.isFile("conf.lua") then
  333. confok, conferr = pcall(require, "conf")
  334. end
  335. -- Yes, conf.lua might not exist, but there are other ways of making
  336. -- love.conf appear, so we should check for it anyway.
  337. if love.conf then
  338. confok, conferr = pcall(love.conf, c)
  339. -- If love.conf errors, we'll trigger the error after loading modules so
  340. -- the error message can be displayed in the window.
  341. end
  342. -- Console hack, part 2.
  343. if c.console and love._openConsole and not openedconsole then
  344. love._openConsole()
  345. end
  346. -- Hack for disabling accelerometer-as-joystick on Android / iOS.
  347. if love._setAccelerometerAsJoystick then
  348. love._setAccelerometerAsJoystick(c.accelerometerjoystick)
  349. end
  350. if love._setGammaCorrect then
  351. love._setGammaCorrect(c.gammacorrect)
  352. end
  353. -- Gets desired modules.
  354. for k,v in ipairs{
  355. "thread",
  356. "timer",
  357. "event",
  358. "keyboard",
  359. "joystick",
  360. "mouse",
  361. "touch",
  362. "sound",
  363. "system",
  364. "audio",
  365. "image",
  366. "video",
  367. "font",
  368. "window",
  369. "graphics",
  370. "math",
  371. "physics",
  372. } do
  373. if c.modules[v] then
  374. require("love." .. v)
  375. end
  376. end
  377. if love.event then
  378. love.createhandlers()
  379. end
  380. -- Check the version
  381. c.version = tostring(c.version)
  382. if not love.isVersionCompatible(c.version) then
  383. local major, minor, revision = c.version:match("^(%d+)%.(%d+)%.(%d+)$")
  384. if (not major or not minor or not revision) or (major ~= love._version_major and minor ~= love._version_minor) then
  385. local msg = ("This game indicates it was made for version '%s' of LOVE.\n"..
  386. "It may not be compatible with the running version (%s)."):format(c.version, love._version)
  387. print(msg)
  388. if love.window then
  389. love.window.showMessageBox("Compatibility Warning", msg, "warning")
  390. end
  391. end
  392. end
  393. if not confok and conferr then
  394. error(conferr)
  395. end
  396. -- Setup window here.
  397. if c.window and c.modules.window then
  398. assert(love.window.setMode(c.window.width, c.window.height,
  399. {
  400. fullscreen = c.window.fullscreen,
  401. fullscreentype = c.window.fullscreentype,
  402. vsync = c.window.vsync,
  403. msaa = c.window.msaa,
  404. resizable = c.window.resizable,
  405. minwidth = c.window.minwidth,
  406. minheight = c.window.minheight,
  407. borderless = c.window.borderless,
  408. centered = c.window.centered,
  409. display = c.window.display,
  410. highdpi = c.window.highdpi,
  411. x = c.window.x,
  412. y = c.window.y,
  413. }), "Could not set window mode")
  414. love.window.setTitle(c.window.title or c.title)
  415. if c.window.icon then
  416. assert(love.image, "If an icon is set in love.conf, love.image must be loaded!")
  417. love.window.setIcon(love.image.newImageData(c.window.icon))
  418. end
  419. end
  420. -- Our first timestep, because window creation can take some time
  421. if love.timer then
  422. love.timer.step()
  423. end
  424. if love.filesystem then
  425. love.filesystem.setAndroidSaveExternal(c.useexternalstorage)
  426. love.filesystem.setIdentity(c.identity or love.filesystem.getIdentity(), c.appendidentity)
  427. if love.filesystem.isFile("main.lua") then
  428. require("main")
  429. end
  430. end
  431. if no_game_code then
  432. error("No code to run\nYour game might be packaged incorrectly\nMake sure main.lua is at the top level of the zip")
  433. end
  434. end
  435. function love.run()
  436. if love.math then
  437. love.math.setRandomSeed(os.time())
  438. end
  439. if love.load then love.load(arg) end
  440. -- We don't want the first frame's dt to include time taken by love.load.
  441. if love.timer then love.timer.step() end
  442. local dt = 0
  443. -- Main loop time.
  444. while true do
  445. -- Process events.
  446. if love.event then
  447. love.event.pump()
  448. for name, a,b,c,d,e,f in love.event.poll() do
  449. if name == "quit" then
  450. if not love.quit or not love.quit() then
  451. return a
  452. end
  453. end
  454. love.handlers[name](a,b,c,d,e,f)
  455. end
  456. end
  457. -- Update dt, as we'll be passing it to update
  458. if love.timer then
  459. love.timer.step()
  460. dt = love.timer.getDelta()
  461. end
  462. -- Call update and draw
  463. if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
  464. if love.graphics and love.graphics.isActive() then
  465. love.graphics.clear(love.graphics.getBackgroundColor())
  466. love.graphics.origin()
  467. if love.draw then love.draw() end
  468. love.graphics.present()
  469. end
  470. if love.timer then love.timer.sleep(0.001) end
  471. end
  472. end
  473. -----------------------------------------------------------
  474. -- Error screen.
  475. -----------------------------------------------------------
  476. local debug, print = debug, print
  477. local function error_printer(msg, layer)
  478. print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
  479. end
  480. function love.errhand(msg)
  481. msg = tostring(msg)
  482. error_printer(msg, 2)
  483. if not love.window or not love.graphics or not love.event then
  484. return
  485. end
  486. if not love.graphics.isCreated() or not love.window.isOpen() then
  487. local success, status = pcall(love.window.setMode, 800, 600)
  488. if not success or not status then
  489. return
  490. end
  491. end
  492. -- Reset state.
  493. if love.mouse then
  494. love.mouse.setVisible(true)
  495. love.mouse.setGrabbed(false)
  496. love.mouse.setRelativeMode(false)
  497. end
  498. if love.joystick then
  499. -- Stop all joystick vibrations.
  500. for i,v in ipairs(love.joystick.getJoysticks()) do
  501. v:setVibration()
  502. end
  503. end
  504. if love.audio then love.audio.stop() end
  505. love.graphics.reset()
  506. local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
  507. love.graphics.setBackgroundColor(89, 157, 220)
  508. love.graphics.setColor(255, 255, 255, 255)
  509. local trace = debug.traceback()
  510. love.graphics.clear(love.graphics.getBackgroundColor())
  511. love.graphics.origin()
  512. local err = {}
  513. table.insert(err, "Error\n")
  514. table.insert(err, msg.."\n\n")
  515. for l in string.gmatch(trace, "(.-)\n") do
  516. if not string.match(l, "boot.lua") then
  517. l = string.gsub(l, "stack traceback:", "Traceback\n")
  518. table.insert(err, l)
  519. end
  520. end
  521. local p = table.concat(err, "\n")
  522. p = string.gsub(p, "\t", "")
  523. p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
  524. local function draw()
  525. local pos = love.window.toPixels(70)
  526. love.graphics.clear(love.graphics.getBackgroundColor())
  527. love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
  528. love.graphics.present()
  529. end
  530. while true do
  531. love.event.pump()
  532. for e, a, b, c in love.event.poll() do
  533. if e == "quit" then
  534. return
  535. elseif e == "keypressed" and a == "escape" then
  536. return
  537. elseif e == "touchpressed" then
  538. local name = love.window.getTitle()
  539. if #name == 0 or name == "Untitled" then name = "Game" end
  540. local buttons = {"OK", "Cancel"}
  541. local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
  542. if pressed == 1 then
  543. return
  544. end
  545. end
  546. end
  547. draw()
  548. if love.timer then
  549. love.timer.sleep(0.1)
  550. end
  551. end
  552. end
  553. local function deferErrhand(...)
  554. local handler = love.errhand or error_printer
  555. return handler(...)
  556. end
  557. -----------------------------------------------------------
  558. -- The root of all calls.
  559. -----------------------------------------------------------
  560. return function()
  561. local result = xpcall(love.boot, error_printer)
  562. if not result then return 1 end
  563. local result = xpcall(love.init, deferErrhand)
  564. if not result then return 1 end
  565. local result, retval = xpcall(love.run, deferErrhand)
  566. if not result then return 1 end
  567. return tonumber(retval) or 0
  568. end