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