modmgr.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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 .. DIR_DELIM
  23. local toadd = {}
  24. retval[#retval + 1] = toadd
  25. local mod_conf = Settings(prefix .. "mod.conf"):to_table()
  26. if mod_conf.name then
  27. name = mod_conf.name
  28. end
  29. toadd.name = name
  30. toadd.path = prefix
  31. if modpack ~= nil and modpack ~= "" then
  32. toadd.modpack = modpack
  33. else
  34. local modpackfile = io.open(prefix .. "modpack.txt")
  35. if modpackfile then
  36. modpackfile:close()
  37. toadd.is_modpack = true
  38. get_mods(prefix, retval, name)
  39. end
  40. end
  41. end
  42. end
  43. end
  44. --modmanager implementation
  45. modmgr = {}
  46. --------------------------------------------------------------------------------
  47. function modmgr.extract(modfile)
  48. if modfile.type == "zip" then
  49. local tempfolder = os.tempfolder()
  50. if tempfolder ~= nil and
  51. tempfolder ~= "" then
  52. core.create_dir(tempfolder)
  53. if core.extract_zip(modfile.name,tempfolder) then
  54. return tempfolder
  55. end
  56. end
  57. end
  58. return nil
  59. end
  60. -------------------------------------------------------------------------------
  61. function modmgr.getbasefolder(temppath)
  62. if temppath == nil then
  63. return {
  64. type = "invalid",
  65. path = ""
  66. }
  67. end
  68. local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r")
  69. if testfile ~= nil then
  70. testfile:close()
  71. return {
  72. type="mod",
  73. path=temppath
  74. }
  75. end
  76. testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
  77. if testfile ~= nil then
  78. testfile:close()
  79. return {
  80. type="modpack",
  81. path=temppath
  82. }
  83. end
  84. local subdirs = core.get_dir_list(temppath, true)
  85. --only single mod or modpack allowed
  86. if #subdirs ~= 1 then
  87. return {
  88. type = "invalid",
  89. path = ""
  90. }
  91. end
  92. testfile =
  93. io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
  94. if testfile ~= nil then
  95. testfile:close()
  96. return {
  97. type="mod",
  98. path= temppath .. DIR_DELIM .. subdirs[1]
  99. }
  100. end
  101. testfile =
  102. io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
  103. if testfile ~= nil then
  104. testfile:close()
  105. return {
  106. type="modpack",
  107. path=temppath .. DIR_DELIM .. subdirs[1]
  108. }
  109. end
  110. return {
  111. type = "invalid",
  112. path = ""
  113. }
  114. end
  115. --------------------------------------------------------------------------------
  116. function modmgr.isValidModname(modpath)
  117. if modpath:find("-") ~= nil then
  118. return false
  119. end
  120. return true
  121. end
  122. --------------------------------------------------------------------------------
  123. function modmgr.parse_register_line(line)
  124. local pos1 = line:find("\"")
  125. local pos2 = nil
  126. if pos1 ~= nil then
  127. pos2 = line:find("\"",pos1+1)
  128. end
  129. if pos1 ~= nil and pos2 ~= nil then
  130. local item = line:sub(pos1+1,pos2-1)
  131. if item ~= nil and
  132. item ~= "" then
  133. local pos3 = item:find(":")
  134. if pos3 ~= nil then
  135. local retval = item:sub(1,pos3-1)
  136. if retval ~= nil and
  137. retval ~= "" then
  138. return retval
  139. end
  140. end
  141. end
  142. end
  143. return nil
  144. end
  145. --------------------------------------------------------------------------------
  146. function modmgr.parse_dofile_line(modpath,line)
  147. local pos1 = line:find("\"")
  148. local pos2 = nil
  149. if pos1 ~= nil then
  150. pos2 = line:find("\"",pos1+1)
  151. end
  152. if pos1 ~= nil and pos2 ~= nil then
  153. local filename = line:sub(pos1+1,pos2-1)
  154. if filename ~= nil and
  155. filename ~= "" and
  156. filename:find(".lua") then
  157. return modmgr.identify_modname(modpath,filename)
  158. end
  159. end
  160. return nil
  161. end
  162. --------------------------------------------------------------------------------
  163. function modmgr.identify_modname(modpath,filename)
  164. local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
  165. if testfile ~= nil then
  166. local line = testfile:read()
  167. while line~= nil do
  168. local modname = nil
  169. if line:find("minetest.register_tool") then
  170. modname = modmgr.parse_register_line(line)
  171. end
  172. if line:find("minetest.register_craftitem") then
  173. modname = modmgr.parse_register_line(line)
  174. end
  175. if line:find("minetest.register_node") then
  176. modname = modmgr.parse_register_line(line)
  177. end
  178. if line:find("dofile") then
  179. modname = modmgr.parse_dofile_line(modpath,line)
  180. end
  181. if modname ~= nil then
  182. testfile:close()
  183. return modname
  184. end
  185. line = testfile:read()
  186. end
  187. testfile:close()
  188. end
  189. return nil
  190. end
  191. --------------------------------------------------------------------------------
  192. function modmgr.render_modlist(render_list)
  193. local retval = ""
  194. if render_list == nil then
  195. if modmgr.global_mods == nil then
  196. modmgr.refresh_globals()
  197. end
  198. render_list = modmgr.global_mods
  199. end
  200. local list = render_list:get_list()
  201. local last_modpack = nil
  202. local retval = {}
  203. for i, v in ipairs(list) do
  204. local color = ""
  205. if v.is_modpack then
  206. local rawlist = render_list:get_raw_list()
  207. color = mt_color_dark_green
  208. for j = 1, #rawlist, 1 do
  209. if rawlist[j].modpack == list[i].name and
  210. rawlist[j].enabled ~= true then
  211. -- Modpack not entirely enabled so showing as grey
  212. color = mt_color_grey
  213. break
  214. end
  215. end
  216. elseif v.is_game_content then
  217. color = mt_color_blue
  218. elseif v.enabled then
  219. color = mt_color_green
  220. end
  221. retval[#retval + 1] = color
  222. if v.modpack ~= nil or v.typ == "game_mod" then
  223. retval[#retval + 1] = "1"
  224. else
  225. retval[#retval + 1] = "0"
  226. end
  227. retval[#retval + 1] = core.formspec_escape(v.name)
  228. end
  229. return table.concat(retval, ",")
  230. end
  231. --------------------------------------------------------------------------------
  232. function modmgr.get_dependencies(modfolder)
  233. local toadd_hard = ""
  234. local toadd_soft = ""
  235. if modfolder ~= nil then
  236. local filename = modfolder ..
  237. DIR_DELIM .. "depends.txt"
  238. local hard_dependencies = {}
  239. local soft_dependencies = {}
  240. local dependencyfile = io.open(filename,"r")
  241. if dependencyfile then
  242. local dependency = dependencyfile:read("*l")
  243. while dependency do
  244. dependency = dependency:gsub("\r", "")
  245. if string.sub(dependency, -1, -1) == "?" then
  246. table.insert(soft_dependencies, string.sub(dependency, 1, -2))
  247. else
  248. table.insert(hard_dependencies, dependency)
  249. end
  250. dependency = dependencyfile:read()
  251. end
  252. dependencyfile:close()
  253. end
  254. toadd_hard = table.concat(hard_dependencies, ",")
  255. toadd_soft = table.concat(soft_dependencies, ",")
  256. end
  257. return toadd_hard, toadd_soft
  258. end
  259. --------------------------------------------------------------------------------
  260. function modmgr.get_worldconfig(worldpath)
  261. local filename = worldpath ..
  262. DIR_DELIM .. "world.mt"
  263. local worldfile = Settings(filename)
  264. local worldconfig = {}
  265. worldconfig.global_mods = {}
  266. worldconfig.game_mods = {}
  267. for key,value in pairs(worldfile:to_table()) do
  268. if key == "gameid" then
  269. worldconfig.id = value
  270. elseif key:sub(0, 9) == "load_mod_" then
  271. worldconfig.global_mods[key] = core.is_yes(value)
  272. else
  273. worldconfig[key] = value
  274. end
  275. end
  276. --read gamemods
  277. local gamespec = gamemgr.find_by_gameid(worldconfig.id)
  278. gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
  279. return worldconfig
  280. end
  281. --------------------------------------------------------------------------------
  282. function modmgr.installmod(modfilename,basename)
  283. local modfile = modmgr.identify_filetype(modfilename)
  284. local modpath = modmgr.extract(modfile)
  285. if modpath == nil then
  286. gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
  287. fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
  288. return
  289. end
  290. local basefolder = modmgr.getbasefolder(modpath)
  291. if basefolder.type == "modpack" then
  292. local clean_path = nil
  293. if basename ~= nil then
  294. clean_path = "mp_" .. basename
  295. end
  296. if clean_path == nil then
  297. clean_path = get_last_folder(cleanup_path(basefolder.path))
  298. end
  299. if clean_path ~= nil then
  300. local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
  301. if not core.copy_dir(basefolder.path,targetpath) then
  302. gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
  303. end
  304. else
  305. gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
  306. end
  307. end
  308. if basefolder.type == "mod" then
  309. local targetfolder = basename
  310. if targetfolder == nil then
  311. targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
  312. end
  313. --if heuristic failed try to use current foldername
  314. if targetfolder == nil then
  315. targetfolder = get_last_folder(basefolder.path)
  316. end
  317. if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
  318. local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
  319. core.copy_dir(basefolder.path,targetpath)
  320. else
  321. gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
  322. end
  323. end
  324. core.delete_dir(modpath)
  325. modmgr.refresh_globals()
  326. end
  327. --------------------------------------------------------------------------------
  328. function modmgr.preparemodlist(data)
  329. local retval = {}
  330. local global_mods = {}
  331. local game_mods = {}
  332. --read global mods
  333. local modpath = core.get_modpath()
  334. if modpath ~= nil and
  335. modpath ~= "" then
  336. get_mods(modpath,global_mods)
  337. end
  338. for i=1,#global_mods,1 do
  339. global_mods[i].typ = "global_mod"
  340. retval[#retval + 1] = global_mods[i]
  341. end
  342. --read game mods
  343. local gamespec = gamemgr.find_by_gameid(data.gameid)
  344. gamemgr.get_game_mods(gamespec, game_mods)
  345. if #game_mods > 0 then
  346. -- Add title
  347. retval[#retval + 1] = {
  348. typ = "game",
  349. is_game_content = true,
  350. name = fgettext("Subgame Mods")
  351. }
  352. end
  353. for i=1,#game_mods,1 do
  354. game_mods[i].typ = "game_mod"
  355. game_mods[i].is_game_content = true
  356. retval[#retval + 1] = game_mods[i]
  357. end
  358. if data.worldpath == nil then
  359. return retval
  360. end
  361. --read world mod configuration
  362. local filename = data.worldpath ..
  363. DIR_DELIM .. "world.mt"
  364. local worldfile = Settings(filename)
  365. for key,value in pairs(worldfile:to_table()) do
  366. if key:sub(1, 9) == "load_mod_" then
  367. key = key:sub(10)
  368. local element = nil
  369. for i=1,#retval,1 do
  370. if retval[i].name == key and
  371. not retval[i].is_modpack then
  372. element = retval[i]
  373. break
  374. end
  375. end
  376. if element ~= nil then
  377. element.enabled = core.is_yes(value)
  378. else
  379. core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
  380. end
  381. end
  382. end
  383. return retval
  384. end
  385. --------------------------------------------------------------------------------
  386. function modmgr.comparemod(elem1,elem2)
  387. if elem1 == nil or elem2 == nil then
  388. return false
  389. end
  390. if elem1.name ~= elem2.name then
  391. return false
  392. end
  393. if elem1.is_modpack ~= elem2.is_modpack then
  394. return false
  395. end
  396. if elem1.typ ~= elem2.typ then
  397. return false
  398. end
  399. if elem1.modpack ~= elem2.modpack then
  400. return false
  401. end
  402. if elem1.path ~= elem2.path then
  403. return false
  404. end
  405. return true
  406. end
  407. --------------------------------------------------------------------------------
  408. function modmgr.mod_exists(basename)
  409. if modmgr.global_mods == nil then
  410. modmgr.refresh_globals()
  411. end
  412. if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
  413. return true
  414. end
  415. return false
  416. end
  417. --------------------------------------------------------------------------------
  418. function modmgr.get_global_mod(idx)
  419. if modmgr.global_mods == nil then
  420. return nil
  421. end
  422. if idx == nil or idx < 1 or
  423. idx > modmgr.global_mods:size() then
  424. return nil
  425. end
  426. return modmgr.global_mods:get_list()[idx]
  427. end
  428. --------------------------------------------------------------------------------
  429. function modmgr.refresh_globals()
  430. modmgr.global_mods = filterlist.create(
  431. modmgr.preparemodlist, --refresh
  432. modmgr.comparemod, --compare
  433. function(element,uid) --uid match
  434. if element.name == uid then
  435. return true
  436. end
  437. end,
  438. nil, --filter
  439. {}
  440. )
  441. modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
  442. modmgr.global_mods:set_sortmode("alphabetic")
  443. end
  444. --------------------------------------------------------------------------------
  445. function modmgr.identify_filetype(name)
  446. if name:sub(-3):lower() == "zip" then
  447. return {
  448. name = name,
  449. type = "zip"
  450. }
  451. end
  452. if name:sub(-6):lower() == "tar.gz" or
  453. name:sub(-3):lower() == "tgz"then
  454. return {
  455. name = name,
  456. type = "tgz"
  457. }
  458. end
  459. if name:sub(-6):lower() == "tar.bz2" then
  460. return {
  461. name = name,
  462. type = "tbz"
  463. }
  464. end
  465. if name:sub(-2):lower() == "7z" then
  466. return {
  467. name = name,
  468. type = "7z"
  469. }
  470. end
  471. return {
  472. name = name,
  473. type = "ukn"
  474. }
  475. end