diff --git a/modlib/License.txt b/modlib/License.txt new file mode 100644 index 0000000..8532a89 --- /dev/null +++ b/modlib/License.txt @@ -0,0 +1,7 @@ +Copyright 2019 - 2021 Lars Mueller alias LMD or appguru(eu) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/modlib/binary.lua b/modlib/binary.lua new file mode 100644 index 0000000..41362dc --- /dev/null +++ b/modlib/binary.lua @@ -0,0 +1,256 @@ +-- Localize globals +local assert, math_huge, math_frexp, math_floor + = assert, math.huge, math.frexp, math.floor + +local negative_nan = 0/0 +local positive_nan = negative_nan ^ 1 + +--"returns nearest 32-bit single precision float representation of a number" (or something) +function mtul.binary.fround(number) + if number == 0 or number ~= number then + return number + end + local sign = 1 + if number < 0 then + sign = -1 + number = -number + end + local _, exp = math.frexp(number) + exp = exp - 1 -- we want 2^exponent >= number > 2^(exponent-1) + local powexp = 2 ^ math.max(-126, math.min(exp, 127)) + local leading = exp <= -127 and 0 or 1 -- subnormal number? + local mantissa = math.floor((number / powexp - leading) * 0x800000 + 0.5) + if + mantissa > 0x800000 -- doesn't fit in mantissa + or (exp >= 127 and mantissa == 0x800000) -- fits if the exponent can be increased + then + return sign * inf + end + return sign * powexp * (leading + mantissa / 0x800000) +end + + +-- All little endian +--+ Reads an IEEE 754 single-precision floating point number (f32) +function mtul.binary.read_single(read_byte) + -- First read the mantissa + local mantissa = read_byte() / 0x100 + mantissa = (mantissa + read_byte()) / 0x100 + + -- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent + local exponent_byte = read_byte() + local sign_byte = read_byte() + local sign = 1 + if sign_byte >= 0x80 then + sign = -1 + sign_byte = sign_byte - 0x80 + end + local exponent = sign_byte * 2 + if exponent_byte >= 0x80 then + exponent = exponent + 1 + exponent_byte = exponent_byte - 0x80 + end + mantissa = (mantissa + exponent_byte) / 0x80 + if exponent == 0xFF then + if mantissa == 0 then + return sign * math_huge + end + -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it + return sign == 1 and positive_nan or negative_nan + end + assert(mantissa < 1) + if exponent == 0 then + -- subnormal value + return sign * 2^-126 * mantissa + end + return sign * 2 ^ (exponent - 127) * (1 + mantissa) +end + +--+ Reads an IEEE 754 double-precision floating point number (f64) +function mtul.binary.read_double(read_byte) + -- First read the mantissa + local mantissa = 0 + for _ = 1, 6 do + mantissa = (mantissa + read_byte()) / 0x100 + end + -- Second and first byte in big endian: last 4 bits of exponent + 4 bits of mantissa; sign bit + 7 bits of exponent + local exponent_byte = read_byte() + local sign_byte = read_byte() + local sign = 1 + if sign_byte >= 0x80 then + sign = -1 + sign_byte = sign_byte - 0x80 + end + local exponent = sign_byte * 0x10 + local mantissa_bits = exponent_byte % 0x10 + exponent = exponent + (exponent_byte - mantissa_bits) / 0x10 + mantissa = (mantissa + mantissa_bits) / 0x10 + if exponent == 0x7FF then + if mantissa == 0 then + return sign * math_huge + end + -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it + return sign == 1 and positive_nan or negative_nan + end + assert(mantissa < 1) + if exponent == 0 then + -- subnormal value + return sign * 2^-1022 * mantissa + end + return sign * 2 ^ (exponent - 1023) * (1 + mantissa) +end + +--+ Reads doubles (f64) or floats (f32) +--: double reads an f64 if true, f32 otherwise +function mtul.binary.read_float(read_byte, double) + return (double and mtul.binary.read_double or mtul.binary.read_single)(read_byte) +end + +function mtul.binary.read_uint(read_byte, bytes) + local factor = 1 + local uint = 0 + for _ = 1, bytes do + uint = uint + read_byte() * factor + factor = factor * 0x100 + end + return uint +end + +function mtul.binary.read_int(read_byte, bytes) + local uint = mtul.binary.read_uint(read_byte, bytes) + local max = 0x100 ^ bytes + if uint >= max / 2 then + return uint - max + end + return uint +end + +function mtul.binary.write_uint(write_byte, uint, bytes) + for _ = 1, bytes do + write_byte(uint % 0x100) + uint = math_floor(uint / 0x100) + end + assert(uint == 0) +end + +function mtul.binary.write_int(write_byte, int, bytes) + local max = 0x100 ^ bytes + if int < 0 then + assert(-int <= max / 2) + int = max + int + else + assert(int < max / 2) + end + return mtul.binary.write_uint(write_byte, int, bytes) +end + +function mtul.binary.write_single(write_byte, number) + if number ~= number then -- nan: all ones + for _ = 1, 4 do write_byte(0xFF) end + return + end + + local sign_byte, exponent_byte, mantissa_byte_1, mantissa_byte_2 + + local sign_bit = 0 + if number < 0 then + number = -number + sign_bit = 0x80 + end + + if number == math_huge then -- inf: exponent = all 1, mantissa = all 0 + sign_byte, exponent_byte, mantissa_byte_1, mantissa_byte_2 = sign_bit + 0x7F, 0x80, 0, 0 + else -- real number + local mantissa, exponent = math_frexp(number) + if exponent <= -126 or number == 0 then -- must write a subnormal number + mantissa = mantissa * 2 ^ (exponent + 126) + exponent = 0 + else -- normal numbers are stored as 1. + mantissa = mantissa * 2 - 1 + exponent = exponent - 1 + 127 -- mantissa << 1 <=> exponent-- + assert(exponent < 0xFF) + end + + local exp_lowest_bit = exponent % 2 + + sign_byte = sign_bit + (exponent - exp_lowest_bit) / 2 + + mantissa = mantissa * 0x80 + exponent_byte = exp_lowest_bit * 0x80 + math_floor(mantissa) + mantissa = mantissa % 1 + + mantissa = mantissa * 0x100 + mantissa_byte_1 = math_floor(mantissa) + mantissa = mantissa % 1 + + mantissa = mantissa * 0x100 + mantissa_byte_2 = math_floor(mantissa) + mantissa = mantissa % 1 + + assert(mantissa == 0) -- no truncation allowed: round numbers properly using modlib.math.fround + end + + write_byte(mantissa_byte_2) + write_byte(mantissa_byte_1) + write_byte(exponent_byte) + write_byte(sign_byte) +end + +function mtul.binary.write_double(write_byte, number) + if number ~= number then -- nan: all ones + for _ = 1, 8 do write_byte(0xFF) end + return + end + + local sign_byte, exponent_byte, mantissa_bytes + + local sign_bit = 0 + if number < 0 then + number = -number + sign_bit = 0x80 + end + + if number == math_huge then -- inf: exponent = all 1, mantissa = all 0 + sign_byte, exponent_byte, mantissa_bytes = sign_bit + 0x7F, 0xF0, {0, 0, 0, 0, 0, 0} + else -- real number + local mantissa, exponent = math_frexp(number) + if exponent <= -1022 or number == 0 then -- must write a subnormal number + mantissa = mantissa * 2 ^ (exponent + 1022) + exponent = 0 + else -- normal numbers are stored as 1. + mantissa = mantissa * 2 - 1 + exponent = exponent - 1 + 1023 -- mantissa << 1 <=> exponent-- + assert(exponent < 0x7FF) + end + + local exp_low_nibble = exponent % 0x10 + + sign_byte = sign_bit + (exponent - exp_low_nibble) / 0x10 + + mantissa = mantissa * 0x10 + exponent_byte = exp_low_nibble * 0x10 + math_floor(mantissa) + mantissa = mantissa % 1 + + mantissa_bytes = {} + for i = 1, 6 do + mantissa = mantissa * 0x100 + mantissa_bytes[i] = math_floor(mantissa) + mantissa = mantissa % 1 + end + assert(mantissa == 0) + end + + for i = 6, 1, -1 do + write_byte(mantissa_bytes[i]) + end + write_byte(exponent_byte) + write_byte(sign_byte) +end + +--: on_write function(double) +--: double true - f64, false - f32 +function mtul.binary.write_float(write_byte, number, double) + (double and mtul.binary.write_double or mtul.binary.write_single)(write_byte, number) +end + +-- Export environment \ No newline at end of file diff --git a/modlib/mod_utils.lua b/modlib/mod_utils.lua new file mode 100644 index 0000000..8515751 --- /dev/null +++ b/modlib/mod_utils.lua @@ -0,0 +1,104 @@ + +function mtul.utils.get_resource(modname, resource, ...) + if not resource then + resource = modname + modname = minetest.get_current_modname() + end + return table.concat({minetest.get_modpath(modname), resource, ...}, "/") +end +local function trim_spacing(text) + return text:match"^%s*(.-)%s*$" +end +--I will add a file reading lib eventually... +local read_file = function(mod, filename) + local filepath = mtul.utils.get_resource(mod, filename) + local file, err = io.open(filename, "r") + if file == nil then return nil, err end + local content = file:read"*a" + file:close() +end + +local mod_info +function mtul.utils.get_mod_info() + if mod_info then return mod_info end + mod_info = {} + -- TODO validate modnames + local modnames = minetest.get_modnames() + for _, mod in pairs(modnames) do + local info + local mod_conf = Settings(mtul.utils.get_resource(mod, "mod.conf")) + if mod_conf then + info = {} + mod_conf = mod_conf:to_table() + local function read_depends(field) + local depends = {} + for depend in (mod_conf[field] or ""):gmatch"[^,]+" do + depends[trim_spacing(depend)] = true + end + info[field] = depends + end + read_depends"depends" + read_depends"optional_depends" + else + info = { + description = read_file(mod, "description.txt"), + depends = {}, + optional_depends = {} + } + local depends_txt = read_file(mod, "depends.txt") + if depends_txt then + local trimmed = {} + for key, value in pairs(string.split(depends_txt or "", "\n")) do + trimmed[key] = trim_spacing(value) + end + for _, dependency in ipairs(trimmed) do + local modname, is_optional = dependency:match"(.+)(%??)" + table.insert(is_optional == "" and info.depends or info.optional_depends, modname) + end + end + end + if info.name == nil then + info.name = mod + end + mod_info[mod] = info + end + return mod_info +end +local mod_load_order +function mtul.utils.get_mod_load_order() + if mod_load_order then return mod_load_order end + mod_load_order = {} + local mod_info = mtul.utils.get_mod_info() + -- If there are circular soft dependencies, it is possible that a mod is loaded, but not in the right order + -- TODO somehow maximize the number of soft dependencies fulfilled in case of circular soft dependencies + local function load(mod) + if mod.status == "loaded" then + return true + end + if mod.status == "loading" then + return false + end + -- TODO soft/vs hard loading status, reset? + mod.status = "loading" + -- Try hard dependencies first. These must be fulfilled. + for depend in pairs(mod.depends) do + if not load(mod_info[depend]) then + return false + end + end + -- Now, try soft dependencies. + for depend in pairs(mod.optional_depends) do + -- Mod may not exist + if mod_info[depend] then + load(mod_info[depend]) + end + end + mod.status = "loaded" + table.insert(mod_load_order, mod) + return true + end + for _, mod in pairs(mod_info) do + assert(load(mod)) + end + return mod_load_order +end \ No newline at end of file diff --git a/modlib/mod_utils_media.lua b/modlib/mod_utils_media.lua new file mode 100644 index 0000000..040977e --- /dev/null +++ b/modlib/mod_utils_media.lua @@ -0,0 +1,67 @@ +--felt this was big enough that it practically deserved it's own file. +-- TODO support for server texture packs (and possibly client TPs in singleplayer?) +local media_foldernames = {"textures", "sounds", "media", "models", "locale"} +local media_extensions = { + -- Textures + "png", "jpg", "bmp", "tga", "pcx", "ppm", "psd", "wal", "rgb"; + -- Sounds + "ogg"; + -- Models + "x", "b3d", "md2", "obj"; + -- Translations + "tr"; +} +local function split_extension(filename) + return filename:match"^(.*)%.(.*)$" +end +--mmmm yes, according to modlib we should make this loop it's own global function apart of modlib. Foolish me thinking we can just make case specific +for i, v in pairs(media_extensions) do + media_extensions[v] = true +end +local function collect_media(modname) + local media = {} + local function traverse(folderpath) + -- Traverse files (collect media) + local filenames = minetest.get_dir_list(folderpath, false) + for _, filename in pairs(filenames) do + local _, ext = split_extension(filename) + if media_extensions[ext] then + media[filename] = table.concat({folderpath, filename}, "/") + end + end + -- Traverse subfolders + local foldernames = minetest.get_dir_list(folderpath, true) + for _, foldername in pairs(foldernames) do + if not foldername:match"^[_%.]" then -- ignore hidden subfolders / subfolders starting with `_` + traverse(table.concat({folderpath, foldername}, "/")) + end + end + end + for _, foldername in ipairs(media_foldernames) do -- order matters! + traverse(mtul.utils.get_resource(modname, foldername)) + end + return media +end + +-- TODO clean this up eventually +local paths = {} +local mods = {} +local overridden_paths = {} +local overridden_mods = {} +for _, mod in ipairs(mtul.utils.get_mod_load_order()) do + local mod_media = collect_media(mod.name) + for medianame, path in pairs(mod_media) do + if paths[medianame] then + overridden_paths[medianame] = overridden_paths[medianame] or {} + table.insert(overridden_paths[medianame], paths[medianame]) + overridden_mods[medianame] = overridden_mods[medianame] or {} + table.insert(overridden_mods[medianame], mods[medianame]) + end + paths[medianame] = path + mods[medianame] = mod.name + end +end +mtul.media_paths = paths +mtul.overriden_media_paths = paths +mtul.modname_by_media = paths +mtul.overriden_modnames_by_media = paths \ No newline at end of file diff --git a/modlib/table.lua b/modlib/table.lua new file mode 100644 index 0000000..8b8fe18 --- /dev/null +++ b/modlib/table.lua @@ -0,0 +1,18 @@ + +--adds a list to the end of another list without ovewriting data. +function mtul.tbl.append(table, other_table) + local length = #table + for index, value in ipairs(other_table) do + table[length + index] = value + end + return table +end + +--this... I guess lists keys by their order??? I have no idea, depended on by b3d standalone. +function mtul.tbl.keys(table) + local keys = {} + for key, _ in pairs(table) do + keys[#keys + 1] = key + end + return keys +end \ No newline at end of file