local recipes = {} local Reader = {} Reader.__index = Reader function Reader.new(data) return setmetatable({ data = data, pos = 1, len = #data }, Reader) end function Reader:u8() local b = string.byte(self.data, self.pos) if not b then error("recipes.lua: unexpected end of data at byte " .. self.pos) end self.pos = self.pos + 1 return b end function Reader:bytes(n) local s = string.sub(self.data, self.pos, self.pos + n - 1) if #s < n then error("recipes.lua: unexpected end of data at byte " .. self.pos) end self.pos = self.pos + n return s end function Reader:varuint() local result = 0 local shift = 0 while true do local b = self:u8() result = result + (b % 128) * (2 ^ shift) if b < 128 then break end shift = shift + 7 end return math.floor(result) end function Reader:string() local n = self:varuint() return self:bytes(n) end function recipes.decode(data) local r = Reader.new(data) local magic = r:bytes(3) if magic ~= "RC1" then error("recipes.lua: not an RC1 recipe pack (bad magic bytes)") end local poolCount = r:varuint() local pool = {} for i = 1, poolCount do pool[i] = r:string() end local function decodeOptions(rr) local n = rr:varuint() local opts = {} for i = 1, n do local t = rr:varuint() if t == 0 then opts[i] = false elseif t == 2 then opts[i] = { tag = rr:string() } else opts[i] = { item = pool[t - 2] } end end return opts end local function decodeCell(rr, cellIdx) if cellIdx == 0 then return false elseif cellIdx == 1 then return { options = decodeOptions(rr) } elseif cellIdx == 2 then return { tag = rr:string() } else return { item = pool[cellIdx - 2] } end end local recipeCount = r:varuint() local list = {} local byResult = {} for i = 1, recipeCount do local resultIdx = r:varuint() local resultName = pool[resultIdx + 1] local count = r:varuint() local flags = r:u8() local rec = { result = resultName, count = count } if flags == 1 then local width = r:u8() local height = r:u8() local grid = {} for cell = 1, width * height do local cellIdx = r:varuint() grid[cell] = decodeCell(r, cellIdx) end rec.shaped = true rec.width = width rec.height = height rec.grid = grid else local ingCount = r:varuint() local ingredients = {} for j = 1, ingCount do local cellIdx = r:varuint() ingredients[j] = decodeCell(r, cellIdx) end rec.shaped = false rec.ingredients = ingredients end list[i] = rec byResult[resultName] = byResult[resultName] or {} table.insert(byResult[resultName], rec) end return { list = list, byResult = byResult } end function recipes.load(path) local file = fs and fs.open and fs.open(path, "rb") if file then local data = file.readAll() file.close() return recipes.decode(data) end local f, err = io.open(path, "rb") if not f then error("recipes.lua: could not open " .. path .. ": " .. tostring(err)) end local data = f:read("*a") f:close() return recipes.decode(data) end return recipes