]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/mainmenu/pkgmgr.lua
ce5862ec2f00c4be2f7d3bee78741519f4dd807e
[dragonfireclient.git] / builtin / mainmenu / pkgmgr.lua
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 --------------------------------------------------------------------------------
19 local function get_last_folder(text,count)
20         local parts = text:split(DIR_DELIM)
21
22         if count == nil then
23                 return parts[#parts]
24         end
25
26         local retval = ""
27         for i=1,count,1 do
28                 retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
29         end
30
31         return retval
32 end
33
34 local function cleanup_path(temppath)
35
36         local parts = temppath:split("-")
37         temppath = ""
38         for i=1,#parts,1 do
39                 if temppath ~= "" then
40                         temppath = temppath .. "_"
41                 end
42                 temppath = temppath .. parts[i]
43         end
44
45         parts = temppath:split(".")
46         temppath = ""
47         for i=1,#parts,1 do
48                 if temppath ~= "" then
49                         temppath = temppath .. "_"
50                 end
51                 temppath = temppath .. parts[i]
52         end
53
54         parts = temppath:split("'")
55         temppath = ""
56         for i=1,#parts,1 do
57                 if temppath ~= "" then
58                         temppath = temppath .. ""
59                 end
60                 temppath = temppath .. parts[i]
61         end
62
63         parts = temppath:split(" ")
64         temppath = ""
65         for i=1,#parts,1 do
66                 if temppath ~= "" then
67                         temppath = temppath
68                 end
69                 temppath = temppath .. parts[i]
70         end
71
72         return temppath
73 end
74
75 local function load_texture_packs(txtpath, retval)
76         local list = core.get_dir_list(txtpath, true)
77         local current_texture_path = core.settings:get("texture_path")
78
79         for _, item in ipairs(list) do
80                 if item ~= "base" then
81                         local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
82                         local conf = Settings(path .. "texture_pack.conf")
83                         local enabled = path == current_texture_path
84
85                         local title = conf:get("title") or item
86
87                         -- list_* is only used if non-nil, else the regular versions are used.
88                         retval[#retval + 1] = {
89                                 name = item,
90                                 title = title,
91                                 list_name = enabled and fgettext("$1 (Enabled)", item) or nil,
92                                 list_title = enabled and fgettext("$1 (Enabled)", title) or nil,
93                                 author = conf:get("author"),
94                                 release = tonumber(conf:get("release")) or 0,
95                                 type = "txp",
96                                 path = path,
97                                 enabled = enabled,
98                         }
99                 end
100         end
101 end
102
103 function get_mods(path, virtual_path, retval, modpack)
104         local mods = core.get_dir_list(path, true)
105
106         for _, name in ipairs(mods) do
107                 if name:sub(1, 1) ~= "." then
108                         local mod_path = path .. DIR_DELIM .. name
109                         local mod_virtual_path = virtual_path .. "/" .. name
110                         local toadd = {
111                                 dir_name = name,
112                                 parent_dir = path,
113                         }
114                         retval[#retval + 1] = toadd
115
116                         -- Get config file
117                         local mod_conf
118                         local modpack_conf = io.open(mod_path .. DIR_DELIM .. "modpack.conf")
119                         if modpack_conf then
120                                 toadd.is_modpack = true
121                                 modpack_conf:close()
122
123                                 mod_conf = Settings(mod_path .. DIR_DELIM .. "modpack.conf"):to_table()
124                                 if mod_conf.name then
125                                         name = mod_conf.name
126                                         toadd.is_name_explicit = true
127                                 end
128                         else
129                                 mod_conf = Settings(mod_path .. DIR_DELIM .. "mod.conf"):to_table()
130                                 if mod_conf.name then
131                                         name = mod_conf.name
132                                         toadd.is_name_explicit = true
133                                 end
134                         end
135
136                         -- Read from config
137                         toadd.name = name
138                         toadd.title = mod_conf.title
139                         toadd.author = mod_conf.author
140                         toadd.release = tonumber(mod_conf.release) or 0
141                         toadd.path = mod_path
142                         toadd.virtual_path = mod_virtual_path
143                         toadd.type = "mod"
144
145                         -- Check modpack.txt
146                         -- Note: modpack.conf is already checked above
147                         local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
148                         if modpackfile then
149                                 modpackfile:close()
150                                 toadd.is_modpack = true
151                         end
152
153                         -- Deal with modpack contents
154                         if modpack and modpack ~= "" then
155                                 toadd.modpack = modpack
156                         elseif toadd.is_modpack then
157                                 toadd.type = "modpack"
158                                 toadd.is_modpack = true
159                                 get_mods(mod_path, mod_virtual_path, retval, name)
160                         end
161                 end
162         end
163 end
164
165 --modmanager implementation
166 pkgmgr = {}
167
168 function pkgmgr.get_texture_packs()
169         local txtpath = core.get_texturepath()
170         local txtpath_system = core.get_texturepath_share()
171         local retval = {}
172
173         load_texture_packs(txtpath, retval)
174         -- on portable versions these two paths coincide. It avoids loading the path twice
175         if txtpath ~= txtpath_system then
176                 load_texture_packs(txtpath_system, retval)
177         end
178
179         table.sort(retval, function(a, b)
180                 return a.name > b.name
181         end)
182
183         return retval
184 end
185
186 --------------------------------------------------------------------------------
187 function pkgmgr.get_folder_type(path)
188         local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
189         if testfile ~= nil then
190                 testfile:close()
191                 return { type = "mod", path = path }
192         end
193
194         testfile = io.open(path .. DIR_DELIM .. "modpack.conf","r")
195         if testfile ~= nil then
196                 testfile:close()
197                 return { type = "modpack", path = path }
198         end
199
200         testfile = io.open(path .. DIR_DELIM .. "modpack.txt","r")
201         if testfile ~= nil then
202                 testfile:close()
203                 return { type = "modpack", path = path }
204         end
205
206         testfile = io.open(path .. DIR_DELIM .. "game.conf","r")
207         if testfile ~= nil then
208                 testfile:close()
209                 return { type = "game", path = path }
210         end
211
212         testfile = io.open(path .. DIR_DELIM .. "texture_pack.conf","r")
213         if testfile ~= nil then
214                 testfile:close()
215                 return { type = "txp", path = path }
216         end
217
218         return nil
219 end
220
221 -------------------------------------------------------------------------------
222 function pkgmgr.get_base_folder(temppath)
223         if temppath == nil then
224                 return { type = "invalid", path = "" }
225         end
226
227         local ret = pkgmgr.get_folder_type(temppath)
228         if ret then
229                 return ret
230         end
231
232         local subdirs = core.get_dir_list(temppath, true)
233         if #subdirs == 1 then
234                 ret = pkgmgr.get_folder_type(temppath .. DIR_DELIM .. subdirs[1])
235                 if ret then
236                         return ret
237                 else
238                         return { type = "invalid", path = temppath .. DIR_DELIM .. subdirs[1] }
239                 end
240         end
241
242         return nil
243 end
244
245 --------------------------------------------------------------------------------
246 function pkgmgr.isValidModname(modpath)
247         if modpath:find("-") ~= nil then
248                 return false
249         end
250
251         return true
252 end
253
254 --------------------------------------------------------------------------------
255 function pkgmgr.parse_register_line(line)
256         local pos1 = line:find("\"")
257         local pos2 = nil
258         if pos1 ~= nil then
259                 pos2 = line:find("\"",pos1+1)
260         end
261
262         if pos1 ~= nil and pos2 ~= nil then
263                 local item = line:sub(pos1+1,pos2-1)
264
265                 if item ~= nil and
266                         item ~= "" then
267                         local pos3 = item:find(":")
268
269                         if pos3 ~= nil then
270                                 local retval = item:sub(1,pos3-1)
271                                 if retval ~= nil and
272                                         retval ~= "" then
273                                         return retval
274                                 end
275                         end
276                 end
277         end
278         return nil
279 end
280
281 --------------------------------------------------------------------------------
282 function pkgmgr.parse_dofile_line(modpath,line)
283         local pos1 = line:find("\"")
284         local pos2 = nil
285         if pos1 ~= nil then
286                 pos2 = line:find("\"",pos1+1)
287         end
288
289         if pos1 ~= nil and pos2 ~= nil then
290                 local filename = line:sub(pos1+1,pos2-1)
291
292                 if filename ~= nil and
293                         filename ~= "" and
294                         filename:find(".lua") then
295                         return pkgmgr.identify_modname(modpath,filename)
296                 end
297         end
298         return nil
299 end
300
301 --------------------------------------------------------------------------------
302 function pkgmgr.identify_modname(modpath,filename)
303         local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
304         if testfile ~= nil then
305                 local line = testfile:read()
306
307                 while line~= nil do
308                         local modname = nil
309
310                         if line:find("minetest.register_tool") then
311                                 modname = pkgmgr.parse_register_line(line)
312                         end
313
314                         if line:find("minetest.register_craftitem") then
315                                 modname = pkgmgr.parse_register_line(line)
316                         end
317
318
319                         if line:find("minetest.register_node") then
320                                 modname = pkgmgr.parse_register_line(line)
321                         end
322
323                         if line:find("dofile") then
324                                 modname = pkgmgr.parse_dofile_line(modpath,line)
325                         end
326
327                         if modname ~= nil then
328                                 testfile:close()
329                                 return modname
330                         end
331
332                         line = testfile:read()
333                 end
334                 testfile:close()
335         end
336
337         return nil
338 end
339 --------------------------------------------------------------------------------
340 function pkgmgr.render_packagelist(render_list, use_technical_names)
341         if not render_list then
342                 if not pkgmgr.global_mods then
343                         pkgmgr.refresh_globals()
344                 end
345                 render_list = pkgmgr.global_mods
346         end
347
348         local list = render_list:get_list()
349         local retval = {}
350         for i, v in ipairs(list) do
351                 local color = ""
352                 if v.is_modpack then
353                         local rawlist = render_list:get_raw_list()
354                         color = mt_color_dark_green
355
356                         for j = 1, #rawlist, 1 do
357                                 if rawlist[j].modpack == list[i].name and
358                                                 not rawlist[j].enabled then
359                                         -- Modpack not entirely enabled so showing as grey
360                                         color = mt_color_grey
361                                         break
362                                 end
363                         end
364                 elseif v.is_game_content or v.type == "game" then
365                         color = mt_color_blue
366                 elseif v.enabled or v.type == "txp" then
367                         color = mt_color_green
368                 end
369
370                 retval[#retval + 1] = color
371                 if v.modpack ~= nil or v.loc == "game" then
372                         retval[#retval + 1] = "1"
373                 else
374                         retval[#retval + 1] = "0"
375                 end
376
377                 if use_technical_names then
378                         retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
379                 else
380                         retval[#retval + 1] = core.formspec_escape(v.list_title or v.list_name or v.title or v.name)
381                 end
382         end
383
384         return table.concat(retval, ",")
385 end
386
387 --------------------------------------------------------------------------------
388 function pkgmgr.get_dependencies(path)
389         if path == nil then
390                 return {}, {}
391         end
392
393         local info = core.get_content_info(path)
394         return info.depends or {}, info.optional_depends or {}
395 end
396
397 ----------- tests whether all of the mods in the modpack are enabled -----------
398 function pkgmgr.is_modpack_entirely_enabled(data, name)
399         local rawlist = data.list:get_raw_list()
400         for j = 1, #rawlist do
401                 if rawlist[j].modpack == name and not rawlist[j].enabled then
402                         return false
403                 end
404         end
405         return true
406 end
407
408 local function disable_all_by_name(list, name, except)
409         for i=1, #list do
410                 if list[i].name == name and list[i] ~= except then
411                         list[i].enabled = false
412                 end
413         end
414 end
415
416 ---------- toggles or en/disables a mod or modpack and its dependencies --------
417 local function toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
418         if not mod.is_modpack then
419                 -- Toggle or en/disable the mod
420                 if toset == nil then
421                         toset = not mod.enabled
422                 end
423                 if mod.enabled ~= toset then
424                         toggled_mods[#toggled_mods+1] = mod.name
425                 end
426                 if toset then
427                         -- Mark this mod for recursive dependency traversal
428                         enabled_mods[mod.name] = true
429
430                         -- Disable other mods with the same name
431                         disable_all_by_name(list, mod.name, mod)
432                 end
433                 mod.enabled = toset
434         else
435                 -- Toggle or en/disable every mod in the modpack,
436                 -- interleaved unsupported
437                 for i = 1, #list do
438                         if list[i].modpack == mod.name then
439                                 toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, list[i])
440                         end
441                 end
442         end
443 end
444
445 function pkgmgr.enable_mod(this, toset)
446         local list = this.data.list:get_list()
447         local mod = list[this.data.selected_mod]
448
449         -- Game mods can't be enabled or disabled
450         if mod.is_game_content then
451                 return
452         end
453
454         local toggled_mods = {}
455         local enabled_mods = {}
456         toggle_mod_or_modpack(list, toggled_mods, enabled_mods, toset, mod)
457         toset = mod.enabled -- Update if toggled
458
459         if not toset then
460                 -- Mod(s) were disabled, so no dependencies need to be enabled
461                 table.sort(toggled_mods)
462                 core.log("info", "Following mods were disabled: " ..
463                         table.concat(toggled_mods, ", "))
464                 return
465         end
466
467         -- Enable mods' depends after activation
468
469         -- Make a list of mod ids indexed by their names. Among mods with the
470         -- same name, enabled mods take precedence, after which game mods take
471         -- precedence, being last in the mod list.
472         local mod_ids = {}
473         for id, mod2 in pairs(list) do
474                 if mod2.type == "mod" and not mod2.is_modpack then
475                         local prev_id = mod_ids[mod2.name]
476                         if not prev_id or not list[prev_id].enabled then
477                                 mod_ids[mod2.name] = id
478                         end
479                 end
480         end
481
482         -- to_enable is used as a DFS stack with sp as stack pointer
483         local to_enable = {}
484         local sp = 0
485         for name in pairs(enabled_mods) do
486                 local depends = pkgmgr.get_dependencies(list[mod_ids[name]].path)
487                 for i = 1, #depends do
488                         local dependency_name = depends[i]
489                         if not enabled_mods[dependency_name] then
490                                 sp = sp+1
491                                 to_enable[sp] = dependency_name
492                         end
493                 end
494         end
495
496         -- If sp is 0, every dependency is already activated
497         while sp > 0 do
498                 local name = to_enable[sp]
499                 sp = sp-1
500
501                 if not enabled_mods[name] then
502                         enabled_mods[name] = true
503                         local mod_to_enable = list[mod_ids[name]]
504                         if not mod_to_enable then
505                                 core.log("warning", "Mod dependency \"" .. name ..
506                                         "\" not found!")
507                         else
508                                 if not mod_to_enable.enabled then
509                                         mod_to_enable.enabled = true
510                                         toggled_mods[#toggled_mods+1] = mod_to_enable.name
511                                 end
512                                 -- Push the dependencies of the dependency onto the stack
513                                 local depends = pkgmgr.get_dependencies(mod_to_enable.path)
514                                 for i = 1, #depends do
515                                         if not enabled_mods[depends[i]] then
516                                                 sp = sp+1
517                                                 to_enable[sp] = depends[i]
518                                         end
519                                 end
520                         end
521                 end
522         end
523
524         -- Log the list of enabled mods
525         table.sort(toggled_mods)
526         core.log("info", "Following mods were enabled: " ..
527                 table.concat(toggled_mods, ", "))
528 end
529
530 --------------------------------------------------------------------------------
531 function pkgmgr.get_worldconfig(worldpath)
532         local filename = worldpath ..
533                                 DIR_DELIM .. "world.mt"
534
535         local worldfile = Settings(filename)
536
537         local worldconfig = {}
538         worldconfig.global_mods = {}
539         worldconfig.game_mods = {}
540
541         for key,value in pairs(worldfile:to_table()) do
542                 if key == "gameid" then
543                         worldconfig.id = value
544                 elseif key:sub(0, 9) == "load_mod_" then
545                         -- Compatibility: Check against "nil" which was erroneously used
546                         -- as value for fresh configured worlds
547                         worldconfig.global_mods[key] = value ~= "false" and value ~= "nil"
548                                 and value
549                 else
550                         worldconfig[key] = value
551                 end
552         end
553
554         --read gamemods
555         local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
556         pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
557
558         return worldconfig
559 end
560
561 --------------------------------------------------------------------------------
562 function pkgmgr.install_dir(type, path, basename, targetpath)
563         local basefolder = pkgmgr.get_base_folder(path)
564
565         -- There's no good way to detect a texture pack, so let's just assume
566         -- it's correct for now.
567         if type == "txp" then
568                 if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
569                         return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
570                 end
571
572                 local from = basefolder and basefolder.path or path
573                 if targetpath then
574                         core.delete_dir(targetpath)
575                 else
576                         targetpath = core.get_texturepath() .. DIR_DELIM .. basename
577                 end
578                 if not core.copy_dir(from, targetpath, false) then
579                         return nil,
580                                 fgettext("Failed to install $1 to $2", basename, targetpath)
581                 end
582                 return targetpath, nil
583
584         elseif not basefolder then
585                 return nil, fgettext("Unable to find a valid mod or modpack")
586         end
587
588         --
589         -- Get destination
590         --
591         if basefolder.type == "modpack" then
592                 if type ~= "mod" then
593                         return nil, fgettext("Unable to install a modpack as a $1", type)
594                 end
595
596                 -- Get destination name for modpack
597                 if targetpath then
598                         core.delete_dir(targetpath)
599                 else
600                         local clean_path = nil
601                         if basename ~= nil then
602                                 clean_path = basename
603                         end
604                         if not clean_path then
605                                 clean_path = get_last_folder(cleanup_path(basefolder.path))
606                         end
607                         if clean_path then
608                                 targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
609                         else
610                                 return nil,
611                                         fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
612                                         path)
613                         end
614                 end
615         elseif basefolder.type == "mod" then
616                 if type ~= "mod" then
617                         return nil, fgettext("Unable to install a mod as a $1", type)
618                 end
619
620                 if targetpath then
621                         core.delete_dir(targetpath)
622                 else
623                         local targetfolder = basename
624                         if targetfolder == nil then
625                                 targetfolder = pkgmgr.identify_modname(basefolder.path, "init.lua")
626                         end
627
628                         -- If heuristic failed try to use current foldername
629                         if targetfolder == nil then
630                                 targetfolder = get_last_folder(basefolder.path)
631                         end
632
633                         if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
634                                 targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
635                         else
636                                 return nil, fgettext("Install Mod: Unable to find real mod name for: $1", path)
637                         end
638                 end
639
640         elseif basefolder.type == "game" then
641                 if type ~= "game" then
642                         return nil, fgettext("Unable to install a game as a $1", type)
643                 end
644
645                 if targetpath then
646                         core.delete_dir(targetpath)
647                 else
648                         targetpath = core.get_gamepath() .. DIR_DELIM .. basename
649                 end
650         end
651
652         -- Copy it
653         if not core.copy_dir(basefolder.path, targetpath, false) then
654                 return nil,
655                         fgettext("Failed to install $1 to $2", basename, targetpath)
656         end
657
658         if basefolder.type == "game" then
659                 pkgmgr.update_gamelist()
660         else
661                 pkgmgr.refresh_globals()
662         end
663
664         return targetpath, nil
665 end
666
667 --------------------------------------------------------------------------------
668 function pkgmgr.preparemodlist(data)
669         local retval = {}
670
671         local global_mods = {}
672         local game_mods = {}
673
674         --read global mods
675         local modpaths = core.get_modpaths()
676         for key, modpath in pairs(modpaths) do
677                 get_mods(modpath, key, global_mods)
678         end
679
680         for i=1,#global_mods,1 do
681                 global_mods[i].type = "mod"
682                 global_mods[i].loc = "global"
683                 global_mods[i].enabled = false
684                 retval[#retval + 1] = global_mods[i]
685         end
686
687         --read game mods
688         local gamespec = pkgmgr.find_by_gameid(data.gameid)
689         pkgmgr.get_game_mods(gamespec, game_mods)
690
691         if #game_mods > 0 then
692                 -- Add title
693                 retval[#retval + 1] = {
694                         type = "game",
695                         is_game_content = true,
696                         name = fgettext("$1 mods", gamespec.name),
697                         path = gamespec.path
698                 }
699         end
700
701         for i=1,#game_mods,1 do
702                 game_mods[i].type = "mod"
703                 game_mods[i].loc = "game"
704                 game_mods[i].is_game_content = true
705                 retval[#retval + 1] = game_mods[i]
706         end
707
708         if data.worldpath == nil then
709                 return retval
710         end
711
712         --read world mod configuration
713         local filename = data.worldpath ..
714                                 DIR_DELIM .. "world.mt"
715
716         local worldfile = Settings(filename)
717         for key, value in pairs(worldfile:to_table()) do
718                 if key:sub(1, 9) == "load_mod_" then
719                         key = key:sub(10)
720                         local mod_found = false
721
722                         local fallback_found = false
723                         local fallback_mod = nil
724
725                         for i=1, #retval do
726                                 if retval[i].name == key and
727                                                 not retval[i].is_modpack then
728                                         if core.is_yes(value) or retval[i].virtual_path == value then
729                                                 retval[i].enabled = true
730                                                 mod_found = true
731                                                 break
732                                         elseif fallback_found then
733                                                 -- Only allow fallback if only one mod matches
734                                                 fallback_mod = nil
735                                         else
736                                                 fallback_found = true
737                                                 fallback_mod = retval[i]
738                                         end
739                                 end
740                         end
741
742                         if not mod_found then
743                                 if fallback_mod and value:find("/") then
744                                         fallback_mod.enabled = true
745                                 else
746                                         core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
747                                 end
748                         end
749                 end
750         end
751
752         return retval
753 end
754
755 function pkgmgr.compare_package(a, b)
756         return a and b and a.name == b.name and a.path == b.path
757 end
758
759 --------------------------------------------------------------------------------
760 function pkgmgr.comparemod(elem1,elem2)
761         if elem1 == nil or elem2 == nil then
762                 return false
763         end
764         if elem1.name ~= elem2.name then
765                 return false
766         end
767         if elem1.is_modpack ~= elem2.is_modpack then
768                 return false
769         end
770         if elem1.type ~= elem2.type then
771                 return false
772         end
773         if elem1.modpack ~= elem2.modpack then
774                 return false
775         end
776
777         if elem1.path ~= elem2.path then
778                 return false
779         end
780
781         return true
782 end
783
784 --------------------------------------------------------------------------------
785 function pkgmgr.mod_exists(basename)
786
787         if pkgmgr.global_mods == nil then
788                 pkgmgr.refresh_globals()
789         end
790
791         if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
792                 return true
793         end
794
795         return false
796 end
797
798 --------------------------------------------------------------------------------
799 function pkgmgr.get_global_mod(idx)
800
801         if pkgmgr.global_mods == nil then
802                 return nil
803         end
804
805         if idx == nil or idx < 1 or
806                 idx > pkgmgr.global_mods:size() then
807                 return nil
808         end
809
810         return pkgmgr.global_mods:get_list()[idx]
811 end
812
813 --------------------------------------------------------------------------------
814 function pkgmgr.refresh_globals()
815         local function is_equal(element,uid) --uid match
816                 if element.name == uid then
817                         return true
818                 end
819         end
820         pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
821                         pkgmgr.comparemod, is_equal, nil, {})
822         pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
823         pkgmgr.global_mods:set_sortmode("alphabetic")
824 end
825
826 --------------------------------------------------------------------------------
827 function pkgmgr.find_by_gameid(gameid)
828         for i=1,#pkgmgr.games,1 do
829                 if pkgmgr.games[i].id == gameid then
830                         return pkgmgr.games[i], i
831                 end
832         end
833         return nil, nil
834 end
835
836 --------------------------------------------------------------------------------
837 function pkgmgr.get_game_mods(gamespec, retval)
838         if gamespec ~= nil and
839                 gamespec.gamemods_path ~= nil and
840                 gamespec.gamemods_path ~= "" then
841                 get_mods(gamespec.gamemods_path, ("games/%s/mods"):format(gamespec.id), retval)
842         end
843 end
844
845 --------------------------------------------------------------------------------
846 function pkgmgr.get_game_modlist(gamespec)
847         local retval = ""
848         local game_mods = {}
849         pkgmgr.get_game_mods(gamespec, game_mods)
850         for i=1,#game_mods,1 do
851                 if retval ~= "" then
852                         retval = retval..","
853                 end
854                 retval = retval .. game_mods[i].name
855         end
856         return retval
857 end
858
859 --------------------------------------------------------------------------------
860 function pkgmgr.get_game(index)
861         if index > 0 and index <= #pkgmgr.games then
862                 return pkgmgr.games[index]
863         end
864
865         return nil
866 end
867
868 --------------------------------------------------------------------------------
869 function pkgmgr.update_gamelist()
870         pkgmgr.games = core.get_games()
871 end
872
873 --------------------------------------------------------------------------------
874 function pkgmgr.gamelist()
875         local retval = ""
876         if #pkgmgr.games > 0 then
877                 retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
878
879                 for i=2,#pkgmgr.games,1 do
880                         retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
881                 end
882         end
883         return retval
884 end
885
886 --------------------------------------------------------------------------------
887 -- read initial data
888 --------------------------------------------------------------------------------
889 pkgmgr.update_gamelist()