03de41af3f
- updated mojosetup for upcoming release
592 lines
21 KiB
Lua
592 lines
21 KiB
Lua
-- MojoSetup; a portable, flexible installation application.
|
|
--
|
|
-- Please see the file LICENSE.txt in the source's root directory.
|
|
--
|
|
-- This file written by Ryan C. Gordon.
|
|
|
|
|
|
-- !!! FIXME: go through the schema and ditch loki_setup things we don't want.
|
|
|
|
-- These are various things that need to be exposed to Lua, or are just
|
|
-- better written in Lua than C. All work will be done in the "MojoSetup"
|
|
-- table (for generic functionality) or the "Setup" table (for config file
|
|
-- elements), so the rest of the namespace is available to end-user code,
|
|
-- minus what the Lua runtime claims for itself.
|
|
--
|
|
-- This file is loaded and executed at MojoSetup startup. The Base Archive,
|
|
-- GUI, and Lua are initialized, the MojoSetup table is created and has some
|
|
-- initial CFunctions and such inserted. The localization script runs, and
|
|
-- then this code executes to do the heavy lifting. All Lua state should be
|
|
-- sane for the rest of the app once this script successfully completes.
|
|
|
|
local _ = MojoSetup.translate
|
|
|
|
-- Set up the garbage counter. Things that are done in possibly long-running
|
|
-- loops that create a lot of junk can optionally set a threshold and call
|
|
-- MojoSetup.incrementgarbagecounter() on each iteration. Whenever you hit
|
|
-- the threshold, garbage collection runs and resets the counter. This keeps
|
|
-- memory usage from spiraling out of control for pathological cases.
|
|
-- Note that the counter resets in MojoSetup.collectgarbage() itself, so you
|
|
-- don't have to worry about the counter accumulating and firing off extra
|
|
-- unnecessary collections if you happen to hit a loop at the wrong time.
|
|
MojoSetup.garbagecounter = 0
|
|
MojoSetup.garbagethreshold = 500
|
|
|
|
function MojoSetup.incrementgarbagecount()
|
|
MojoSetup.garbagecounter = MojoSetup.garbagecounter + 1
|
|
if MojoSetup.garbagecounter >= MojoSetup.garbagethreshold then
|
|
MojoSetup.collectgarbage()
|
|
end
|
|
end
|
|
|
|
-- Returns three elements: protocol, host, path
|
|
function MojoSetup.spliturl(url)
|
|
return string.match(url, "^(.+://)(.-)/(.*)")
|
|
end
|
|
|
|
|
|
-- This is MojoSetup.stringtofile() for string tables.
|
|
function MojoSetup.stringtabletofile(t, dest, perms, len, callback)
|
|
-- !!! FIXME: We could do this entirely in C to avoid a concatenated
|
|
-- !!! FIXME: string on the Lua heap.
|
|
return MojoSetup.stringtofile(table.concat(t), dest, perms, len, callback)
|
|
end
|
|
|
|
|
|
-- This is handy for debugging.
|
|
function MojoSetup.dumptable(tabname, tab, depth)
|
|
if depth == nil then -- first call, before any recursion?
|
|
local loglevel = MojoSetup.info.loglevel
|
|
if (loglevel ~= "debug") and (loglevel ~= "everything") then
|
|
return -- don't spend time on this if there's no output...
|
|
end
|
|
depth = 1
|
|
end
|
|
|
|
if tabname ~= nil then
|
|
if tab == nil then
|
|
MojoSetup.logdebug(tabname .. " = nil")
|
|
return
|
|
else
|
|
MojoSetup.logdebug(tabname .. " = {")
|
|
end
|
|
end
|
|
|
|
local depthstr = ""
|
|
for i=1,(depth*4) do
|
|
depthstr = depthstr .. " "
|
|
end
|
|
|
|
if tab.MOJOSETUP_DUMPTABLE_ITERATED then
|
|
MojoSetup.logdebug(depthstr .. "(...circular reference...)")
|
|
else
|
|
tab.MOJOSETUP_DUMPTABLE_ITERATED = true
|
|
for k,v in pairs(tab) do
|
|
if type(v) == "table" then
|
|
MojoSetup.logdebug(depthstr .. tostring(k) .. " = {")
|
|
MojoSetup.dumptable(nil, v, depth + 1)
|
|
MojoSetup.logdebug(depthstr .. "}")
|
|
else
|
|
if k ~= "MOJOSETUP_DUMPTABLE_ITERATED" then
|
|
MojoSetup.logdebug(depthstr .. tostring(k) .. " = " .. tostring(v))
|
|
end
|
|
end
|
|
end
|
|
tab.MOJOSETUP_DUMPTABLE_ITERATED = nil
|
|
end
|
|
|
|
if tabname ~= nil then
|
|
MojoSetup.logdebug("}")
|
|
end
|
|
end
|
|
|
|
|
|
-- Our namespace for config API...
|
|
Setup = {}
|
|
|
|
local function schema_assert(test, fnname, elem, errstr)
|
|
if not test then
|
|
local msg = MojoSetup.format(_("BUG: Config %0 %1"),
|
|
fnname .. "::" .. elem, errstr)
|
|
MojoSetup.fatal(msg)
|
|
end
|
|
end
|
|
|
|
local function mustExist(fnname, elem, val)
|
|
schema_assert(val ~= nil, fnname, elem, _("must be explicitly specified"))
|
|
end
|
|
|
|
local function mustBeSomething(fnname, elem, val, elemtype)
|
|
-- Can be nil...please use mustExist if this is a problem!
|
|
if val ~= nil then
|
|
if type(val) ~= elemtype then
|
|
local msg = MojoSetup.format(_("must be %0"),
|
|
MojoSetup.translate(elemtype))
|
|
schema_assert(false, fnname, elem, msg)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function mustBeString(fnname, elem, val)
|
|
mustBeSomething(fnname, elem, val, "string")
|
|
end
|
|
|
|
local function mustBeBool(fnname, elem, val)
|
|
mustBeSomething(fnname, elem, val, "boolean")
|
|
end
|
|
|
|
local function mustBeNumber(fnname, elem, val)
|
|
mustBeSomething(fnname, elem, val, "number")
|
|
end
|
|
|
|
local function mustBeFunction(fnname, elem, val)
|
|
mustBeSomething(fnname, elem, val, "function")
|
|
end
|
|
|
|
local function mustBeTable(fnname, elem, val)
|
|
mustBeSomething(fnname, elem, val, "table")
|
|
end
|
|
|
|
local function cantBeEmpty(fnname, elem, val)
|
|
-- Can be nil...please use mustExist if this is a problem!
|
|
if val ~= nil then
|
|
schema_assert(val ~= "", fnname, elem, _("can't be empty string"))
|
|
end
|
|
end
|
|
|
|
local function mustBeStringOrTableOfStrings(fnname, elem, val)
|
|
-- Can be nil...please use mustExist if this is a problem!
|
|
if val ~= nil then
|
|
local err = _("must be string or table of strings")
|
|
if type(val) == "string" then
|
|
val = { val }
|
|
end
|
|
schema_assert(type(val) == "table", fnname, elem, err)
|
|
for k,v in pairs(val) do
|
|
schema_assert(type(v) == "string", fnname, elem, err)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function mustBeStringOrNumber(fnname, elem, val)
|
|
-- Can be nil...please use mustExist if this is a problem!
|
|
if val ~= nil then
|
|
local t = type(val)
|
|
schema_assert((t == "string") or (t == "number"), fnname, elem,
|
|
_("must be a string or number"))
|
|
end
|
|
end
|
|
|
|
local function mustBeUrl(fnname, elem, val)
|
|
mustBeString(fnname, elem, val)
|
|
cantBeEmpty(fnname, elem, val)
|
|
if (val ~= nil) then
|
|
local prot,host,path = MojoSetup.spliturl(val)
|
|
schema_assert(prot ~= nil,fnname,elem,_("URL doesn't have protocol"))
|
|
schema_assert(host ~= nil,fnname,elem,_("URL doesn't have host"))
|
|
schema_assert(path ~= nil,fnname,elem,_("URL doesn't have path"))
|
|
prot = string.gsub(prot, "://$", "")
|
|
local supported = MojoSetup.info.supportedurls[prot]
|
|
schema_assert(supported,fnname,elem,_("URL protocol is unsupported"))
|
|
end
|
|
end
|
|
|
|
local function mustBeSplashPosition(fnname, elem, val)
|
|
mustBeString(fnname, elem, val)
|
|
local valid = (val == nil) or (val == "right") or (val == "bottom") or
|
|
(val == "left") or (val == "top") or (val == "background");
|
|
schema_assert(valid, fnname, elem, _("Splash position is invalid"))
|
|
end
|
|
|
|
local function mustBePerms(fnname, elem, val)
|
|
mustBeString(fnname, elem, val)
|
|
local valid = MojoSetup.isvalidperms(val)
|
|
schema_assert(valid, fnname, elem, _("Permission string is invalid"))
|
|
end
|
|
|
|
local function mustBeValidProductKeyFormat(fnname, elem, val)
|
|
-- Can be nil...please use mustExist if this is a problem!
|
|
if val ~= nil then
|
|
mustBeString(fnname, elem, val)
|
|
cantBeEmpty(fnname, elem, val)
|
|
local valid = (string.match(val, "^[X#%*%? %-]*$") ~= nil)
|
|
schema_assert(valid, fnname, elem, _("invalid string"))
|
|
end
|
|
end
|
|
|
|
local function sanitize(fnname, tab, elems)
|
|
mustBeTable(fnname, "", tab)
|
|
tab._type_ = string.lower(fnname) .. "s" -- "Eula" becomes "eulas".
|
|
for i,elem in ipairs(elems) do
|
|
local child = elem[1]
|
|
local defval = elem[2]
|
|
|
|
if tab[child] == nil and defval ~= nil then
|
|
tab[child] = defval
|
|
end
|
|
local j = 3
|
|
while elem[j] do
|
|
elem[j](fnname, child, tab[child]) -- will assert on problem.
|
|
j = j + 1
|
|
end
|
|
end
|
|
|
|
local notvaliderr = _("is not a valid property")
|
|
for k,v in pairs(tab) do
|
|
local found = false
|
|
if k == "_type_" then
|
|
found = true
|
|
elseif (type(k) == "number") and (type(v) == "table") then
|
|
found = true
|
|
else
|
|
for i,elem in ipairs(elems) do
|
|
local child = elem[1]
|
|
if (child == k) then
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
schema_assert(found, fnname, k, notvaliderr)
|
|
end
|
|
|
|
return tab
|
|
end
|
|
|
|
local function reform_schema_table(tab)
|
|
local killlist = {}
|
|
for k,v in pairs(tab) do
|
|
local ktype = type(k)
|
|
local vtype = type(v)
|
|
if (ktype == "number") and (vtype == "table") and (v._type_ ~= nil) then
|
|
-- add element to proper named array.
|
|
local typestr = v._type_
|
|
v._type_ = nil
|
|
MojoSetup.logdebug("schema: reforming '" .. typestr .. "', '" .. k .. "'")
|
|
if tab[typestr] == nil then
|
|
tab[typestr] = { v }
|
|
else
|
|
table.insert(tab[typestr], v)
|
|
end
|
|
-- can't just set tab[k] to nil here, since it screws up pairs()...
|
|
killlist[#killlist+1] = k
|
|
elseif vtype == "table" then
|
|
tab[k] = reform_schema_table(v)
|
|
end
|
|
end
|
|
|
|
for i,v in ipairs(killlist) do
|
|
tab[v] = nil
|
|
end
|
|
|
|
return tab
|
|
end
|
|
|
|
|
|
-- Actual schema elements are below...
|
|
|
|
function Setup.Package(tab)
|
|
-- !!! FIXME: allow_uninstall
|
|
-- !!! FIXME: write_manifest
|
|
-- !!! FIXME: allow_uninstall must check write_manifest, write_manifest
|
|
-- !!! FIXME: must check for Lua parser support...or something like that.
|
|
|
|
tab = sanitize("Package", tab,
|
|
{
|
|
{ "vendor", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "id", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "disabled", nil, mustBeBool },
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "version", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "destination", nil, mustBeString, cantBeEmpty },
|
|
{ "dataprefix", "data/", mustBeString},
|
|
{ "recommended_destinations", nil, mustBeStringOrTableOfStrings },
|
|
{ "precheck", nil, mustBeFunction },
|
|
{ "preflight", nil, mustBeFunction },
|
|
{ "preinstall", nil, mustBeFunction },
|
|
{ "postinstall", nil, mustBeFunction },
|
|
{ "splash", nil, mustBeString, cantBeEmpty },
|
|
{ "splashpos", nil, mustBeSplashPosition },
|
|
{ "url", nil, mustBeString, cantBeEmpty },
|
|
{ "once", true, mustBeBool },
|
|
{ "category", "Games", mustBeString, cantBeEmpty },
|
|
{ "promptoverwrite", true, mustBeBool },
|
|
{ "binarypath", nil, mustBeString, cantBeEmpty },
|
|
{ "updateurl", nil, mustBeUrl },
|
|
{ "superuser", false, mustBeBool },
|
|
{ "write_manifest", true, mustBeBool },
|
|
{ "support_uninstall", true, mustBeBool },
|
|
{ "preuninstall", nil, mustBeFunction },
|
|
{ "postuninstall", nil, mustBeFunction },
|
|
{ "postexec", nil, mustBeString},
|
|
{ "delete_error_is_fatal", true, mustBeBool}
|
|
})
|
|
|
|
if MojoSetup.installs == nil then
|
|
MojoSetup.installs = {}
|
|
end
|
|
|
|
tab._type_ = nil
|
|
tab = reform_schema_table(tab)
|
|
table.insert(MojoSetup.installs, tab)
|
|
return tab
|
|
|
|
--[[
|
|
promptbinaries When set to "yes", setup will create a checkbox
|
|
to allow the user whether or not to create
|
|
a symbolic link to the binaries.
|
|
|
|
This setting has no effect if nobinaries is "yes".
|
|
|
|
meta When this attribute is set to "yes", then setup will act as
|
|
a meta-installer, i.e. it will allow the user to select a
|
|
product and set-up will respawn itself for the selected
|
|
product installation.
|
|
See the section "About Meta-Installation" below.
|
|
|
|
component When this attribute is present, its value is the name of the component
|
|
that will created by this installer. This means that the files will be added
|
|
to an existing installation of the product, and that basic configuration parameters
|
|
such as the installation path will be used from the previous installation.
|
|
Currently setup is not able to update an existing component, thus if the component
|
|
is detected as already installed the installation will fail.
|
|
Important: This attribute can't be mixed with <component> elements.
|
|
|
|
appbundle (CARBON ONLY) If this is "yes", the destination folder does not include the product
|
|
name as part of the path. An application bundle is typically installed much like
|
|
a single file would be...and so is treated as such.
|
|
|
|
The app bundle path usually specified in the "path" attribute of the "files" element,
|
|
or it is specified in the archives directly.
|
|
|
|
manpages If set to "yes", then the user will be prompted for the install pages installation path.
|
|
Should be used when using the MANPAGE element described below.
|
|
|
|
appbundleid (CARBON ONLY) This string means that you are installing new files into an existing
|
|
Application Bundle. If the bundle isn't found, the installation aborts, otherwise, all
|
|
files are added relative to the base of the app bundle. The string specified here is
|
|
the app bundle's CFBundleIdentifier entry in its Info.plist file. LaunchServices are
|
|
used to find the bundle and the user can be prompted to manually locate the product
|
|
if LaunchServices fails to find it. This is all MacOS-specific, and unrelated to the
|
|
"component" attribute.
|
|
|
|
appbundledesc (CARBON ONLY) This string is used with the "appbundleid" attribute...it's a human
|
|
readable text describing the app bundle in question.
|
|
]]--
|
|
|
|
end
|
|
|
|
function Setup.Eula(tab)
|
|
return sanitize("Eula", tab,
|
|
{
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "source", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "accept_not_needed", nil, mustBeBool },
|
|
})
|
|
end
|
|
|
|
function Setup.Readme(tab)
|
|
return sanitize("Readme", tab,
|
|
{
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "source", nil, mustExist, mustBeString, cantBeEmpty },
|
|
})
|
|
end
|
|
|
|
function Setup.ProductKey(tab)
|
|
return sanitize("ProductKey", tab,
|
|
{
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "format", nil, mustBeValidProductKeyFormat },
|
|
{ "verify", nil, mustBeFunction },
|
|
{ "destination", nil, mustBeString, cantBeEmpty },
|
|
})
|
|
end
|
|
|
|
function Setup.Media(tab)
|
|
return sanitize("Media", tab,
|
|
{
|
|
{ "id", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "uniquefile", nil, mustExist, mustBeString, cantBeEmpty },
|
|
})
|
|
end
|
|
|
|
function Setup.File(tab)
|
|
return sanitize("File", tab,
|
|
{
|
|
{ "source", nil, mustBeUrl },
|
|
{ "destination", nil, mustBeString, cantBeEmpty },
|
|
{ "wildcards", nil, mustBeStringOrTableOfStrings },
|
|
{ "filter", nil, mustBeFunction },
|
|
{ "allowoverwrite", nil, mustBeBool },
|
|
{ "permissions", nil, mustBePerms },
|
|
})
|
|
end
|
|
|
|
function Setup.Option(tab)
|
|
return sanitize("Option", tab,
|
|
{
|
|
{ "value", false, mustBeBool },
|
|
{ "required", false, mustBeBool },
|
|
{ "disabled", false, mustBeBool },
|
|
{ "bytes", nil, mustExist, mustBeNumber },
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "tooltip", nil, mustBeString, cantBeEmpty },
|
|
})
|
|
end
|
|
|
|
function Setup.OptionGroup(tab)
|
|
return sanitize("OptionGroup", tab,
|
|
{
|
|
{ "disabled", nil, mustBeBool },
|
|
{ "description", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "tooltip", nil, mustBeString, cantBeEmpty },
|
|
})
|
|
end
|
|
|
|
function Setup.DesktopMenuItem(tab)
|
|
return sanitize("DesktopMenuItem", tab,
|
|
{
|
|
{ "disabled", nil, mustBeBool },
|
|
{ "name", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "genericname", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "tooltip", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "builtin_icon", nil, mustBeBool },
|
|
{ "icon", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "commandline", nil, mustExist, mustBeString, cantBeEmpty },
|
|
{ "workingdir", nil, mustBeString, cantBeEmpty },
|
|
{ "category", nil, mustExist, mustBeStringOrTableOfStrings },
|
|
{ "mimetype", nil, mustBeStringOrTableOfStrings },
|
|
})
|
|
end
|
|
|
|
local function prepare_localization()
|
|
-- Map some legacy language identifiers into updated equivalents.
|
|
local lang_remap =
|
|
{
|
|
no = "nb", -- "Norwegian" split into "Bokmal" (nb) and "Nynorsk" (nn)
|
|
}
|
|
|
|
local function sanity_check_localization_entry(str, translations)
|
|
local maxval = -1;
|
|
|
|
for val in string.gmatch(str, "%%.") do
|
|
val = string.sub(val, 2)
|
|
if string.match(val, "^[^%%0-9]$") ~= nil then
|
|
MojoSetup.fatal("BUG: localization key ['" .. str .. "'] has invalid escape sequence.")
|
|
end
|
|
if val ~= "%" then
|
|
local num = tonumber(val)
|
|
if num > maxval then
|
|
maxval = num
|
|
end
|
|
end
|
|
end
|
|
|
|
for k,v in pairs(translations) do
|
|
for val in string.gmatch(v, "%%.") do
|
|
val = string.sub(val, 2)
|
|
if string.match(val, "^[^%%0-9]$") ~= nil then
|
|
MojoSetup.fatal("BUG: '" .. k .. "' localization ['" .. v .. "'] has invalid escape sequence for translation of ['" .. str .. "'].")
|
|
end
|
|
if val ~= "%" then
|
|
if tonumber(val) > maxval then
|
|
MojoSetup.fatal("BUG: '" .. k .. "' localization ['" .. v .. "'] has escape sequence > max for translation of ['" .. str .. "'].")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Build the translations table from the localizations table supplied in
|
|
-- localizations.lua...
|
|
if type(MojoSetup.localization) ~= "table" then
|
|
MojoSetup.localization = nil
|
|
end
|
|
|
|
-- Merge the applocalization table into localization.
|
|
if type(MojoSetup.applocalization) == "table" then
|
|
if MojoSetup.localization == nil then
|
|
MojoSetup.localization = {}
|
|
end
|
|
for k,v in pairs(MojoSetup.applocalization) do
|
|
if MojoSetup.localization[k] == nil then
|
|
MojoSetup.localization[k] = v -- just take the whole table as-is.
|
|
else
|
|
-- This can add or overwrite entries...
|
|
for lang,str in pairs(v) do
|
|
MojoSetup.localization[k][lang] = str
|
|
end
|
|
end
|
|
end
|
|
end
|
|
MojoSetup.applocalization = nil -- done with this; garbage collect it.
|
|
|
|
if MojoSetup.localization ~= nil then
|
|
local at_least_one = false
|
|
local locale = MojoSetup.info.locale
|
|
local lang = string.gsub(locale, "_%w+", "", 1) -- make "en_US" into "en"
|
|
|
|
if lang_remap[lang] ~= nil then
|
|
lang = lang_remap[lang]
|
|
end
|
|
|
|
MojoSetup.translations = {}
|
|
for k,v in pairs(MojoSetup.localization) do
|
|
if MojoSetup.translations[k] ~= nil then
|
|
MojoSetup.fatal("BUG: Duplicate localization key ['" .. k .. "']")
|
|
end
|
|
if type(v) == "table" then
|
|
sanity_check_localization_entry(k, v)
|
|
if v[locale] ~= nil then
|
|
MojoSetup.translations[k] = v[locale]
|
|
at_least_one = true
|
|
elseif v[lang] ~= nil then
|
|
MojoSetup.translations[k] = v[lang]
|
|
at_least_one = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Delete the table if there's no actual useful translations for this run.
|
|
if (not at_least_one) then
|
|
MojoSetup.translations = nil
|
|
end
|
|
|
|
-- This is eligible for garbage collection now. We're done with it.
|
|
MojoSetup.localization = nil
|
|
end
|
|
end
|
|
|
|
|
|
-- Mainline...
|
|
|
|
MojoSetup.loginfo("MojoSetup Lua initialization at " .. MojoSetup.date())
|
|
MojoSetup.loginfo("buildver: " .. MojoSetup.info.buildver)
|
|
MojoSetup.loginfo("locale: " .. MojoSetup.info.locale)
|
|
MojoSetup.loginfo("platform: " .. MojoSetup.info.platform)
|
|
MojoSetup.loginfo("arch: " .. MojoSetup.info.arch)
|
|
MojoSetup.loginfo("ostype: " .. MojoSetup.info.ostype)
|
|
MojoSetup.loginfo("osversion: " .. MojoSetup.info.osversion)
|
|
MojoSetup.loginfo("ui: " .. MojoSetup.info.ui)
|
|
MojoSetup.loginfo("loglevel: " .. MojoSetup.info.loglevel)
|
|
|
|
MojoSetup.loginfo("command line:")
|
|
for i,v in ipairs(MojoSetup.info.argv) do
|
|
MojoSetup.loginfo(" " .. i .. ": " .. v)
|
|
end
|
|
|
|
--MojoSetup.loginfo(MojoSetup.info.license)
|
|
--MojoSetup.loginfo(MojoSetup.info.lualicense)
|
|
|
|
-- These scripts are optional, but hopefully exist...
|
|
MojoSetup.runfile("localization")
|
|
MojoSetup.runfile("app_localization")
|
|
prepare_localization()
|
|
|
|
-- okay, we're initialized!
|
|
|
|
-- end of mojosetup_init.lua ...
|
|
|