pkgmgr.lua 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. --Minetest
  2. --Copyright (C) 2013 sapier
  3. --
  4. --This program is free software; you can redistribute it and/or modify
  5. --it under the terms of the GNU Lesser General Public License as published by
  6. --the Free Software Foundation; either version 2.1 of the License, or
  7. --(at your option) any later version.
  8. --
  9. --This program is distributed in the hope that it will be useful,
  10. --but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. --GNU Lesser General Public License for more details.
  13. --
  14. --You should have received a copy of the GNU Lesser General Public License along
  15. --with this program; if not, write to the Free Software Foundation, Inc.,
  16. --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. --------------------------------------------------------------------------------
  18. function get_mods(path,retval,modpack)
  19. local mods = core.get_dir_list(path, true)
  20. for _, name in ipairs(mods) do
  21. if name:sub(1, 1) ~= "." then
  22. local prefix = path .. DIR_DELIM .. name
  23. local toadd = {
  24. dir_name = name,
  25. parent_dir = path,
  26. }
  27. retval[#retval + 1] = toadd
  28. -- Get config file
  29. local mod_conf
  30. local modpack_conf = io.open(prefix .. DIR_DELIM .. "modpack.conf")
  31. if modpack_conf then
  32. toadd.is_modpack = true
  33. modpack_conf:close()
  34. mod_conf = Settings(prefix .. DIR_DELIM .. "modpack.conf"):to_table()
  35. if mod_conf.name then
  36. name = mod_conf.name
  37. toadd.is_name_explicit = true
  38. end
  39. else
  40. mod_conf = Settings(prefix .. DIR_DELIM .. "mod.conf"):to_table()
  41. if mod_conf.name then
  42. name = mod_conf.name
  43. toadd.is_name_explicit = true
  44. end
  45. end
  46. -- Read from config
  47. toadd.name = name
  48. toadd.author = mod_conf.author
  49. toadd.release = tonumber(mod_conf.release or "0")
  50. toadd.path = prefix
  51. toadd.type = "mod"
  52. -- Check modpack.txt
  53. -- Note: modpack.conf is already checked above
  54. local modpackfile = io.open(prefix .. DIR_DELIM .. "modpack.txt")
  55. if modpackfile then
  56. modpackfile:close()
  57. toadd.is_modpack = true
  58. end
  59. -- Deal with modpack contents
  60. if modpack and modpack ~= "" then
  61. toadd.modpack = modpack
  62. elseif toadd.is_modpack then
  63. toadd.type = "modpack"
  64. toadd.is_modpack = true
  65. get_mods(prefix, retval, name)
  66. end
  67. end
  68. end
  69. end
  70. --modmanager implementation
  71. pkgmgr = {}
  72. function pkgmgr.get_texture_packs()
  73. local txtpath = core.get_texturepath()
  74. local list = core.get_dir_list(txtpath, true)
  75. local retval = {}
  76. local current_texture_path = core.settings:get("texture_path")
  77. for _, item in ipairs(list) do
  78. if item ~= "base" then
  79. local name = item
  80. local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
  81. if path == current_texture_path then
  82. name = fgettext("$1 (Enabled)", name)
  83. end
  84. local conf = Settings(path .. "texture_pack.conf")
  85. retval[#retval + 1] = {
  86. name = item,
  87. author = conf:get("author"),
  88. release = tonumber(conf:get("release") or "0"),
  89. list_name = name,
  90. type = "txp",
  91. path = path,
  92. enabled = path == current_texture_path,
  93. }
  94. end
  95. end
  96. table.sort(retval, function(a, b)
  97. return a.name > b.name
  98. end)
  99. return retval
  100. end
  101. --------------------------------------------------------------------------------
  102. function pkgmgr.extract(modfile)
  103. if modfile.type == "zip" then
  104. local tempfolder = os.tempfolder()
  105. if tempfolder ~= nil and
  106. tempfolder ~= "" then
  107. core.create_dir(tempfolder)
  108. if core.extract_zip(modfile.name,tempfolder) then
  109. return tempfolder
  110. end
  111. end
  112. end
  113. return nil
  114. end
  115. function pkgmgr.get_folder_type(path)
  116. local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
  117. if testfile ~= nil then
  118. testfile:close()
  119. return { type = "mod", path = path }
  120. end
  121. testfile = io.open(path .. DIR_DELIM .. "modpack.conf","r")
  122. if testfile ~= nil then
  123. testfile:close()
  124. return { type = "modpack", path = path }
  125. end
  126. testfile = io.open(path .. DIR_DELIM .. "modpack.txt","r")
  127. if testfile ~= nil then
  128. testfile:close()
  129. return { type = "modpack", path = path }
  130. end
  131. testfile = io.open(path .. DIR_DELIM .. "game.conf","r")
  132. if testfile ~= nil then
  133. testfile:close()
  134. return { type = "game", path = path }
  135. end
  136. testfile = io.open(path .. DIR_DELIM .. "texture_pack.conf","r")
  137. if testfile ~= nil then
  138. testfile:close()
  139. return { type = "txp", path = path }
  140. end
  141. return nil
  142. end
  143. -------------------------------------------------------------------------------
  144. function pkgmgr.get_base_folder(temppath)
  145. if temppath == nil then
  146. return { type = "invalid", path = "" }
  147. end
  148. local ret = pkgmgr.get_folder_type(temppath)
  149. if ret then
  150. return ret
  151. end
  152. local subdirs = core.get_dir_list(temppath, true)
  153. if #subdirs == 1 then
  154. ret = pkgmgr.get_folder_type(temppath .. DIR_DELIM .. subdirs[1])
  155. if ret then
  156. return ret
  157. else
  158. return { type = "invalid", path = temppath .. DIR_DELIM .. subdirs[1] }
  159. end
  160. end
  161. return nil
  162. end
  163. --------------------------------------------------------------------------------
  164. function pkgmgr.isValidModname(modpath)
  165. if modpath:find("-") ~= nil then
  166. return false
  167. end
  168. return true
  169. end
  170. --------------------------------------------------------------------------------
  171. function pkgmgr.parse_register_line(line)
  172. local pos1 = line:find("\"")
  173. local pos2 = nil
  174. if pos1 ~= nil then
  175. pos2 = line:find("\"",pos1+1)
  176. end
  177. if pos1 ~= nil and pos2 ~= nil then
  178. local item = line:sub(pos1+1,pos2-1)
  179. if item ~= nil and
  180. item ~= "" then
  181. local pos3 = item:find(":")
  182. if pos3 ~= nil then
  183. local retval = item:sub(1,pos3-1)
  184. if retval ~= nil and
  185. retval ~= "" then
  186. return retval
  187. end
  188. end
  189. end
  190. end
  191. return nil
  192. end
  193. --------------------------------------------------------------------------------
  194. function pkgmgr.parse_dofile_line(modpath,line)
  195. local pos1 = line:find("\"")
  196. local pos2 = nil
  197. if pos1 ~= nil then
  198. pos2 = line:find("\"",pos1+1)
  199. end
  200. if pos1 ~= nil and pos2 ~= nil then
  201. local filename = line:sub(pos1+1,pos2-1)
  202. if filename ~= nil and
  203. filename ~= "" and
  204. filename:find(".lua") then
  205. return pkgmgr.identify_modname(modpath,filename)
  206. end
  207. end
  208. return nil
  209. end
  210. --------------------------------------------------------------------------------
  211. function pkgmgr.identify_modname(modpath,filename)
  212. local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
  213. if testfile ~= nil then
  214. local line = testfile:read()
  215. while line~= nil do
  216. local modname = nil
  217. if line:find("minetest.register_tool") then
  218. modname = pkgmgr.parse_register_line(line)
  219. end
  220. if line:find("minetest.register_craftitem") then
  221. modname = pkgmgr.parse_register_line(line)
  222. end
  223. if line:find("minetest.register_node") then
  224. modname = pkgmgr.parse_register_line(line)
  225. end
  226. if line:find("dofile") then
  227. modname = pkgmgr.parse_dofile_line(modpath,line)
  228. end
  229. if modname ~= nil then
  230. testfile:close()
  231. return modname
  232. end
  233. line = testfile:read()
  234. end
  235. testfile:close()
  236. end
  237. return nil
  238. end
  239. --------------------------------------------------------------------------------
  240. function pkgmgr.render_packagelist(render_list)
  241. local retval = ""
  242. if render_list == nil then
  243. if pkgmgr.global_mods == nil then
  244. pkgmgr.refresh_globals()
  245. end
  246. render_list = pkgmgr.global_mods
  247. end
  248. local list = render_list:get_list()
  249. local last_modpack = nil
  250. local retval = {}
  251. for i, v in ipairs(list) do
  252. local color = ""
  253. if v.is_modpack then
  254. local rawlist = render_list:get_raw_list()
  255. color = mt_color_dark_green
  256. for j = 1, #rawlist, 1 do
  257. if rawlist[j].modpack == list[i].name and
  258. not rawlist[j].enabled then
  259. -- Modpack not entirely enabled so showing as grey
  260. color = mt_color_grey
  261. break
  262. end
  263. end
  264. elseif v.is_game_content or v.type == "game" then
  265. color = mt_color_blue
  266. elseif v.enabled or v.type == "txp" then
  267. color = mt_color_green
  268. end
  269. retval[#retval + 1] = color
  270. if v.modpack ~= nil or v.loc == "game" then
  271. retval[#retval + 1] = "1"
  272. else
  273. retval[#retval + 1] = "0"
  274. end
  275. retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
  276. end
  277. return table.concat(retval, ",")
  278. end
  279. --------------------------------------------------------------------------------
  280. function pkgmgr.get_dependencies(path)
  281. if path == nil then
  282. return {}, {}
  283. end
  284. local info = core.get_content_info(path)
  285. return info.depends or {}, info.optional_depends or {}
  286. end
  287. ----------- tests whether all of the mods in the modpack are enabled -----------
  288. function pkgmgr.is_modpack_entirely_enabled(data, name)
  289. local rawlist = data.list:get_raw_list()
  290. for j = 1, #rawlist do
  291. if rawlist[j].modpack == name and not rawlist[j].enabled then
  292. return false
  293. end
  294. end
  295. return true
  296. end
  297. ---------- toggles or en/disables a mod or modpack -----------------------------
  298. function pkgmgr.enable_mod(this, toset)
  299. local mod = this.data.list:get_list()[this.data.selected_mod]
  300. -- game mods can't be enabled or disabled
  301. if mod.is_game_content then
  302. return
  303. end
  304. -- toggle or en/disable the mod
  305. if not mod.is_modpack then
  306. if toset == nil then
  307. mod.enabled = not mod.enabled
  308. else
  309. mod.enabled = toset
  310. end
  311. return
  312. end
  313. -- toggle or en/disable every mod in the modpack, interleaved unsupported
  314. local list = this.data.list:get_raw_list()
  315. for i = 1, #list do
  316. if list[i].modpack == mod.name then
  317. if toset == nil then
  318. toset = not list[i].enabled
  319. end
  320. list[i].enabled = toset
  321. end
  322. end
  323. end
  324. --------------------------------------------------------------------------------
  325. function pkgmgr.get_worldconfig(worldpath)
  326. local filename = worldpath ..
  327. DIR_DELIM .. "world.mt"
  328. local worldfile = Settings(filename)
  329. local worldconfig = {}
  330. worldconfig.global_mods = {}
  331. worldconfig.game_mods = {}
  332. for key,value in pairs(worldfile:to_table()) do
  333. if key == "gameid" then
  334. worldconfig.id = value
  335. elseif key:sub(0, 9) == "load_mod_" then
  336. -- Compatibility: Check against "nil" which was erroneously used
  337. -- as value for fresh configured worlds
  338. worldconfig.global_mods[key] = value ~= "false" and value ~= "nil"
  339. and value
  340. else
  341. worldconfig[key] = value
  342. end
  343. end
  344. --read gamemods
  345. local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
  346. pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
  347. return worldconfig
  348. end
  349. --------------------------------------------------------------------------------
  350. function pkgmgr.install_dir(type, path, basename, targetpath)
  351. local basefolder = pkgmgr.get_base_folder(path)
  352. -- There's no good way to detect a texture pack, so let's just assume
  353. -- it's correct for now.
  354. if type == "txp" then
  355. if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
  356. return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
  357. end
  358. local from = basefolder and basefolder.path or path
  359. if targetpath then
  360. core.delete_dir(targetpath)
  361. core.create_dir(targetpath)
  362. else
  363. targetpath = core.get_texturepath() .. DIR_DELIM .. basename
  364. end
  365. if not core.copy_dir(from, targetpath) then
  366. return nil,
  367. fgettext("Failed to install $1 to $2", basename, targetpath)
  368. end
  369. return targetpath, nil
  370. elseif not basefolder then
  371. return nil, fgettext("Unable to find a valid mod or modpack")
  372. end
  373. --
  374. -- Get destination
  375. --
  376. if basefolder.type == "modpack" then
  377. if type ~= "mod" then
  378. return nil, fgettext("Unable to install a modpack as a $1", type)
  379. end
  380. -- Get destination name for modpack
  381. if targetpath then
  382. core.delete_dir(targetpath)
  383. core.create_dir(targetpath)
  384. else
  385. local clean_path = nil
  386. if basename ~= nil then
  387. clean_path = basename
  388. end
  389. if not clean_path then
  390. clean_path = get_last_folder(cleanup_path(basefolder.path))
  391. end
  392. if clean_path then
  393. targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
  394. else
  395. return nil,
  396. fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
  397. modfilename)
  398. end
  399. end
  400. elseif basefolder.type == "mod" then
  401. if type ~= "mod" then
  402. return nil, fgettext("Unable to install a mod as a $1", type)
  403. end
  404. if targetpath then
  405. core.delete_dir(targetpath)
  406. core.create_dir(targetpath)
  407. else
  408. local targetfolder = basename
  409. if targetfolder == nil then
  410. targetfolder = pkgmgr.identify_modname(basefolder.path, "init.lua")
  411. end
  412. -- If heuristic failed try to use current foldername
  413. if targetfolder == nil then
  414. targetfolder = get_last_folder(basefolder.path)
  415. end
  416. if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
  417. targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
  418. else
  419. return nil, fgettext("Install Mod: Unable to find real mod name for: $1", modfilename)
  420. end
  421. end
  422. elseif basefolder.type == "game" then
  423. if type ~= "game" then
  424. return nil, fgettext("Unable to install a game as a $1", type)
  425. end
  426. if targetpath then
  427. core.delete_dir(targetpath)
  428. core.create_dir(targetpath)
  429. else
  430. targetpath = core.get_gamepath() .. DIR_DELIM .. basename
  431. end
  432. end
  433. -- Copy it
  434. if not core.copy_dir(basefolder.path, targetpath) then
  435. return nil,
  436. fgettext("Failed to install $1 to $2", basename, targetpath)
  437. end
  438. if basefolder.type == "game" then
  439. pkgmgr.update_gamelist()
  440. else
  441. pkgmgr.refresh_globals()
  442. end
  443. return targetpath, nil
  444. end
  445. --------------------------------------------------------------------------------
  446. function pkgmgr.install(type, modfilename, basename, dest)
  447. local archive_info = pkgmgr.identify_filetype(modfilename)
  448. local path = pkgmgr.extract(archive_info)
  449. if path == nil then
  450. return nil,
  451. fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
  452. fgettext("Install: Unsupported file type \"$1\" or broken archive",
  453. archive_info.type)
  454. end
  455. local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
  456. core.delete_dir(path)
  457. return targetpath, msg
  458. end
  459. --------------------------------------------------------------------------------
  460. function pkgmgr.preparemodlist(data)
  461. local retval = {}
  462. local global_mods = {}
  463. local game_mods = {}
  464. --read global mods
  465. local modpath = core.get_modpath()
  466. if modpath ~= nil and
  467. modpath ~= "" then
  468. get_mods(modpath,global_mods)
  469. end
  470. for i=1,#global_mods,1 do
  471. global_mods[i].type = "mod"
  472. global_mods[i].loc = "global"
  473. retval[#retval + 1] = global_mods[i]
  474. end
  475. --read game mods
  476. local gamespec = pkgmgr.find_by_gameid(data.gameid)
  477. pkgmgr.get_game_mods(gamespec, game_mods)
  478. if #game_mods > 0 then
  479. -- Add title
  480. retval[#retval + 1] = {
  481. type = "game",
  482. is_game_content = true,
  483. name = fgettext("$1 mods", gamespec.name),
  484. path = gamespec.path
  485. }
  486. end
  487. for i=1,#game_mods,1 do
  488. game_mods[i].type = "mod"
  489. game_mods[i].loc = "game"
  490. game_mods[i].is_game_content = true
  491. retval[#retval + 1] = game_mods[i]
  492. end
  493. if data.worldpath == nil then
  494. return retval
  495. end
  496. --read world mod configuration
  497. local filename = data.worldpath ..
  498. DIR_DELIM .. "world.mt"
  499. local worldfile = Settings(filename)
  500. for key,value in pairs(worldfile:to_table()) do
  501. if key:sub(1, 9) == "load_mod_" then
  502. key = key:sub(10)
  503. local element = nil
  504. for i=1,#retval,1 do
  505. if retval[i].name == key and
  506. not retval[i].is_modpack then
  507. element = retval[i]
  508. break
  509. end
  510. end
  511. if element ~= nil then
  512. element.enabled = value ~= "false" and value ~= "nil" and value
  513. else
  514. core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
  515. end
  516. end
  517. end
  518. return retval
  519. end
  520. function pkgmgr.compare_package(a, b)
  521. return a and b and a.name == b.name and a.path == b.path
  522. end
  523. --------------------------------------------------------------------------------
  524. function pkgmgr.comparemod(elem1,elem2)
  525. if elem1 == nil or elem2 == nil then
  526. return false
  527. end
  528. if elem1.name ~= elem2.name then
  529. return false
  530. end
  531. if elem1.is_modpack ~= elem2.is_modpack then
  532. return false
  533. end
  534. if elem1.type ~= elem2.type then
  535. return false
  536. end
  537. if elem1.modpack ~= elem2.modpack then
  538. return false
  539. end
  540. if elem1.path ~= elem2.path then
  541. return false
  542. end
  543. return true
  544. end
  545. --------------------------------------------------------------------------------
  546. function pkgmgr.mod_exists(basename)
  547. if pkgmgr.global_mods == nil then
  548. pkgmgr.refresh_globals()
  549. end
  550. if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
  551. return true
  552. end
  553. return false
  554. end
  555. --------------------------------------------------------------------------------
  556. function pkgmgr.get_global_mod(idx)
  557. if pkgmgr.global_mods == nil then
  558. return nil
  559. end
  560. if idx == nil or idx < 1 or
  561. idx > pkgmgr.global_mods:size() then
  562. return nil
  563. end
  564. return pkgmgr.global_mods:get_list()[idx]
  565. end
  566. --------------------------------------------------------------------------------
  567. function pkgmgr.refresh_globals()
  568. local function is_equal(element,uid) --uid match
  569. if element.name == uid then
  570. return true
  571. end
  572. end
  573. pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
  574. pkgmgr.comparemod, is_equal, nil, {})
  575. pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
  576. pkgmgr.global_mods:set_sortmode("alphabetic")
  577. end
  578. --------------------------------------------------------------------------------
  579. function pkgmgr.identify_filetype(name)
  580. if name:sub(-3):lower() == "zip" then
  581. return {
  582. name = name,
  583. type = "zip"
  584. }
  585. end
  586. if name:sub(-6):lower() == "tar.gz" or
  587. name:sub(-3):lower() == "tgz"then
  588. return {
  589. name = name,
  590. type = "tgz"
  591. }
  592. end
  593. if name:sub(-6):lower() == "tar.bz2" then
  594. return {
  595. name = name,
  596. type = "tbz"
  597. }
  598. end
  599. if name:sub(-2):lower() == "7z" then
  600. return {
  601. name = name,
  602. type = "7z"
  603. }
  604. end
  605. return {
  606. name = name,
  607. type = "ukn"
  608. }
  609. end
  610. --------------------------------------------------------------------------------
  611. function pkgmgr.find_by_gameid(gameid)
  612. for i=1,#pkgmgr.games,1 do
  613. if pkgmgr.games[i].id == gameid then
  614. return pkgmgr.games[i], i
  615. end
  616. end
  617. return nil, nil
  618. end
  619. --------------------------------------------------------------------------------
  620. function pkgmgr.get_game_mods(gamespec, retval)
  621. if gamespec ~= nil and
  622. gamespec.gamemods_path ~= nil and
  623. gamespec.gamemods_path ~= "" then
  624. get_mods(gamespec.gamemods_path, retval)
  625. end
  626. end
  627. --------------------------------------------------------------------------------
  628. function pkgmgr.get_game_modlist(gamespec)
  629. local retval = ""
  630. local game_mods = {}
  631. pkgmgr.get_game_mods(gamespec, game_mods)
  632. for i=1,#game_mods,1 do
  633. if retval ~= "" then
  634. retval = retval..","
  635. end
  636. retval = retval .. game_mods[i].name
  637. end
  638. return retval
  639. end
  640. --------------------------------------------------------------------------------
  641. function pkgmgr.get_game(index)
  642. if index > 0 and index <= #pkgmgr.games then
  643. return pkgmgr.games[index]
  644. end
  645. return nil
  646. end
  647. --------------------------------------------------------------------------------
  648. function pkgmgr.update_gamelist()
  649. pkgmgr.games = core.get_games()
  650. end
  651. --------------------------------------------------------------------------------
  652. function pkgmgr.gamelist()
  653. local retval = ""
  654. if #pkgmgr.games > 0 then
  655. retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
  656. for i=2,#pkgmgr.games,1 do
  657. retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
  658. end
  659. end
  660. return retval
  661. end
  662. --------------------------------------------------------------------------------
  663. -- read initial data
  664. --------------------------------------------------------------------------------
  665. pkgmgr.update_gamelist()