diff --git a/mods/fs_fat/init.lua b/mods/fs_fat/init.lua new file mode 100644 index 0000000..2cb0578 --- /dev/null +++ b/mods/fs_fat/init.lua @@ -0,0 +1 @@ +--#include "msdosfs.lua" diff --git a/mods/fs_fat/msdosfs.lua b/mods/fs_fat/msdosfs.lua new file mode 100644 index 0000000..4b6af79 --- /dev/null +++ b/mods/fs_fat/msdosfs.lua @@ -0,0 +1,1172 @@ +--[[ +This will not support FAT32. +1. Because FAT16 won't even properly fit on a tape or max sized file. +2. Because FAT32's structure is incompatible with FAT12 and FAT16 +--]] +--local vcomp = require("vcomponent") +local fs = {} +--local io = require("io") + +function fs.canonical(path) + local result = table.concat(segments(path), "/") + if unicode.sub(path, 1, 1) == "/" then + return "/" .. result + else + return result + end +end + +function fs.segments(path) + local parts = {} + for part in path:gmatch("[^\\/]+") do + local current, up = part:find("^%.?%.$") + if current then + if up == 2 then + table.remove(parts) + end + else + table.insert(parts, part) + end + end + return parts +end + +local NUL = '\0' + +local msdos = {} +local _msdos = {} + +function _msdos.readRawString(file, size) + local str = "" + while #str < size do + str = str .. file:read(size - #str) + end + return str +end + +if string.pack then + function _msdos.string2number(data) + return string.unpack(" 12 then + return false + end + local filename, ext = name:match("(.*)%.(.+)") + if #filename > 8 or filename:find(".",nil,true) or #ext > 3 then + return false + end + else + if #name > 8 then + return false + end + end + return true +end + +function _msdos.spacetrim(data) + while true do + if data:sub(-1,-1) ~= " " and data:sub(-1,-1) ~= NUL then + break + end + data = data:sub(1,-2) + end + return data +end + +function _msdos.cleanPath(path) + return table.concat(fs.segments(path), "/"):lower() +end + +function _msdos.readDirEntry(fatset,block,count) + local entry = {} + local filename = _msdos.spacetrim(block:sub(1,8)) + local ext = _msdos.spacetrim(block:sub(9,11)) + entry.rawfilename = filename .. (ext ~= "" and "." or "") .. ext + entry.filename = string.lower(entry.rawfilename) + entry.attrib = _msdos.string2number(block:sub(12,12)) + entry.modifyT = _msdos.string2number(block:sub(23,24)) + entry.modifyD = _msdos.string2number(block:sub(25,26)) + local cluster + if fatset.fatsize == 12 then + cluster = _msdos.string2number(block:sub(27,28)) % 0x1000 + else + cluster = _msdos.string2number(block:sub(27,28)) + end + entry.cluster = cluster + entry.size = _msdos.string2number(block:sub(29,32)) + return entry +end + +function _msdos.readDirBlock(fatset, block) + local list = {} + for i = 0, (#block / 32) - 1 do + local data = _msdos.readDirEntry(fatset, block:sub(i * 32 + 1, (i + 1) * 32),i) + table.insert(list, data) + end + return list +end + +function _msdos.cluster2block(fatset, cluster) + return fatset.rb + (fatset.fatc * fatset.fatbc) + (fatset.rdec * 32 / fatset.bps) + ((cluster - 2) * fatset.spc) +end + +function _msdos.fatclusterlookup(fatset, cluster) + return (fatset.bps * fatset.rb) + (cluster * 2) +end + +function _msdos.getFATEntry12(fatset, cluster) + cluster = cluster % 0x1000 + local fatTable = fatset.fatCache.fatTable + if cluster % 2 == 0 then + return bit32.band(_msdos.string2number(fatTable:sub((cluster * 1.5) + 1, (cluster * 1.5) + 2)), 0x0FFF) + else + return bit32.rshift(_msdos.string2number(fatTable:sub(math.floor(cluster * 1.5) + 1, math.floor(cluster * 1.5) + 2)), 4) + end +end + +function _msdos.getFATEntry16(fatset, cluster) + cluster = cluster % 0x10000 + local fatTable = fatset.fatCache.fatTable + return _msdos.string2number(fatTable:sub((cluster * 2) + 1, (cluster * 2) + 2)) +end + +function _msdos.getFATEntry(fatset, cluster) + if fatset.fatsize == 12 then + return _msdos.getFATEntry12(fatset, cluster) + else + return _msdos.getFATEntry16(fatset, cluster) + end +end + +function _msdos.setFATEntry12(fatset, file, cluster, data) + cluster = cluster % 0x1000 + if cluster < 2 then + error("Attempted to modify cluster " .. cluster .. "\n" .. debug.traceback()) + end + local fatTable = fatset.fatCache.fatTable + if cluster % 2 == 0 then + local fatcrap = _msdos.string2number(fatTable:sub((cluster * 1.5) + 1, (cluster * 1.5) + 2)) + fatcrap = _msdos.number2string(bit32.band(fatcrap,0xF000) + bit32.band(data,0x0FFF), 2) + + for i = 0,fatset.fatc - 1 do + file:seek("set", (fatset.bps * fatset.rb) + (i * fatset.bps * fatset.fatbc) + (cluster * 1.5)) + file:write(fatcrap) + end + fatTable = fatTable:sub(1, (cluster * 1.5)) .. fatcrap .. fatTable:sub((cluster * 1.5) + 3) + else + local fatcrap = _msdos.string2number(fatTable:sub(math.floor(cluster * 1.5) + 1, math.floor(cluster * 1.5) + 2)) + fatcrap = _msdos.number2string(bit32.lshift(data,4) + bit32.band(fatcrap,0x000F), 2) + + for i = 0,fatset.fatc - 1 do + file:seek("set", (fatset.bps * fatset.rb) + math.floor(cluster * 1.5)) + file:write(fatcrap) + end + fatTable = fatTable:sub(1, math.floor(cluster * 1.5)) .. fatcrap .. fatTable:sub(math.floor(cluster * 1.5) + 3) + end + fatset.fatCache.fatTable = fatTable +end + +function _msdos.setFATEntry16(fatset, file, cluster, data) + cluster = cluster % 0x10000 + if cluster < 2 then + error("Attempted to modify cluster " .. cluster .. "\n" .. debug.traceback()) + end + local fatTable = fatset.fatCache.fatTable + local fatcrap = _msdos.number2string(data, 2) + for i = 0,fatset.fatc - 1 do + file:seek("set", (fatset.bps * fatset.rb) + (i * fatset.bps * fatset.fatbc) + (cluster * 2)) + file:write(fatcrap) + end + fatTable = fatTable:sub(1, (cluster * 2)) .. fatcrap .. fatTable:sub((cluster * 2) + 3) + fatset.fatCache.fatTable = fatTable +end + +function _msdos.setFATEntry(fatset, file, cluster, data) + if fatset.fatsize == 12 then + _msdos.setFATEntry12(fatset, file, cluster, data) + else + _msdos.setFATEntry16(fatset, file, cluster, data) + end +end + +function _msdos.getClusterChain(fatset, startcluster) + local cache = {[startcluster] = true} + local chain = {startcluster} + local nextcluster = startcluster + local highcluster + if fatset.fatsize == 12 then + highcluster = 0x0FF7 + else + highcluster = 0xFFF7 + end + if nextcluster <= 0x0002 or nextcluster >= highcluster then + return chain + end + while true do + if fatset.fatsize == 12 then + nextcluster = _msdos.getFATEntry12(fatset, nextcluster) + else + nextcluster = _msdos.getFATEntry16(fatset, nextcluster) + end + table.insert(chain, nextcluster) + if nextcluster <= 0x0002 or nextcluster >= highcluster or cache[nextcluster] == true then + if nextcluster <= highcluster then + print("msdos: Bad cluster chain, " .. startcluster) + print(table.concat(chain, ",")) + end + break + end + cache[nextcluster] = true + end + return chain +end + +function _msdos.findFreeCluster(fatset) + local fatTable = fatset.fatCache.fatTable + for i = 0, fatset.tnoc - 1 do + if not fatset.reserved[i] then + local entry + if fatset.fatsize == 12 then + entry = _msdos.getFATEntry12(fatset, i) + else + entry = _msdos.getFATEntry16(fatset, i) + end + if entry == 0 then + return i + end + end + end +end + +function _msdos.readEntireEntry(fatset, file, startcluster) + local list = _msdos.getClusterChain(fatset, startcluster) + local data = "" + for i = 1,#list - 1 do + file:seek("set", _msdos.cluster2block(fatset, list[i]) * fatset.bps) + data = data .. _msdos.readRawString(file, fatset.bps * fatset.spc) + end + return data +end + +function _msdos.searchDirectoryLists(fatset, file, path) + local pathsplit = {} + for dir in path:gmatch("[^/]+") do + if not _msdos.validateName(dir) then + return false + end + table.insert(pathsplit, dir) + end + if fatset.fatCache.directoryLists[path] ~= nil then + return true, fatset.fatCache.directoryLists[path].pos, fatset.fatCache.directoryLists[path].cluster + end + local blockpos = (fatset.rb + (fatset.fatc * fatset.fatbc)) + local entrycluster + local found = true + for i = 1,#pathsplit do + local block + if i == 1 then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + local dirlist = _msdos.readDirBlock(fatset, block) + found = false + for _,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename ~= "." and data.filename ~= ".." then + if bit32.band(data.attrib,0x10) ~= 0 then + -- Cache folders and their cluster as well + local cacheName = "" + for j = 1, i - 1 do + cacheName = cacheName .. pathsplit[j] .. "/" + end + cacheName = cacheName .. data.filename + fatset.fatCache.directoryLists[cacheName] = {pos = _msdos.cluster2block(fatset, data.cluster), cluster = data.cluster} + end + end + end + for _,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename ~= "." and data.filename ~= ".." then + if data.filename == pathsplit[i] then + blockpos = _msdos.cluster2block(fatset, data.cluster) + entrycluster = data.cluster + found = true + break + end + end + end + if found == false then + break + end + end + if found == true then + fatset.fatCache.directoryLists[path] = {pos = blockpos, cluster = entrycluster} + end + return found, blockpos, entrycluster +end + +function _msdos.doSomethingForFile(fatset, file, path, something) + local name = path:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(name) then + return false + end + path = fs.canonical(path .. "/..") + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, path) + if found == false then + return false + end + file:seek("set", fatset.bps * blockpos) + local block + if entrycluster == nil then + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + local dirlist = _msdos.readDirBlock(fatset, block) + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename ~= "." and data.filename ~= ".." then + if name == data.filename then + something(data, index, blockpos, entrycluster) + return true + end + end + end + return false +end + +function _msdos.recursiveKill(fatset, file, entrycluster) + local killed = {} + local block = _msdos.readEntireEntry(fatset, file, entrycluster) + local dirlist = _msdos.readDirBlock(fatset, block) + found = false + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 then + if bit32.band(data.attrib,0x10) ~= 0 and data.filename ~= "." and data.filename ~= ".." then + local kill = _msdos.recursiveKill(fatset, file, data.cluster) + for i = 1,#kill do + table.insert(killed, kill[i]) + end + table.insert(killed, data.cluster) + elseif bit32.band(data.attrib,0x10) == 0 and data.size > 0 then + local kill = _msdos.getClusterChain(fatset, data.cluster) + for i = 1, #kill - 1 do + table.insert(killed, kill[i]) + end + end + end + end + return killed +end + +function msdos.proxy(fatfile, fatsize) + checkArg(1,fatfile,"table") + checkArg(2,fatsize,"nil","number") + + if fatsize == 28 then fatsize = 32 end -- Allow for FAT28 + if fatsize ~= nil and fatsize ~= 12 and fatsize ~= 16 and fatsize ~= 32 then + error("Invalid FAT size",2) + end + if fatsize == 32 then + error("FAT32 is unsupported by this driver.",2) + end + + --if not fs.exists(fatfile) then + -- error("No such file",2) + --end + ----local file = io.open(fatfile,"rb") + if file == nil then + error("Needs a block device") + end + local pos, err = file:seek("set", 0x36) + if pos == nil then + error("Seeking failed: " .. err) + end + local bbs = _msdos.readRawString(file, 8) + local fatset = {} + file:seek("set", 0) + local boot_block = _msdos.readRawString(file, 62) + fatset.bps = _msdos.string2number(boot_block:sub(0x0C, 0x0D)) + fatset.spc = _msdos.string2number(boot_block:sub(0x0E, 0x0E)) + fatset.rb = _msdos.string2number(boot_block:sub(0x0F, 0x10)) + fatset.fatc = _msdos.string2number(boot_block:sub(0x11, 0x11)) + fatset.rdec = _msdos.string2number(boot_block:sub(0x12, 0x13)) + fatset.fatbc = _msdos.string2number(boot_block:sub(0x17, 0x18)) + fatset.hbc = _msdos.string2number(boot_block:sub(0x1D, 0x20)) + fatset.vsn = _msdos.string2number(boot_block:sub(0x28, 0x2B)) + fatset.bpblabel = boot_block:sub(0x2C, 0x36) + fatset.label = _msdos.spacetrim(fatset.bpblabel) + fatset.ident = boot_block:sub(0x37, 0x3E) + local tnos = _msdos.string2number(boot_block:sub(0x14, 0x15)) + if tnos == 0 then + tnos = _msdos.string2number(boot_block:sub(0x21, 0x24)) + end + fatset.tnos = tnos + fatset.tnoc = math.floor(tnos / fatset.spc) + if fatsize == nil then + print("msdos: Detecting FAT size ...") + print("msdos: Ident suggests: " .. fatset.ident) + if fatset.tnoc < 4085 then + print("msdos: Detected FAT size as FAT12") + fatsize = 12 + elseif fatset.tnoc < 65525 then + print("msdos: Detected FAT size as FAT16") + fatsize = 16 + else + print("msdos: Detected FAT size as FAT32") + fatsize = 32 + end + end + if fatsize == 32 then + error("FAT32 is unsupported by this driver.",2) + end + fatset.fatsize = fatsize + fatset.fatCache = {} + fatset.fatCache.directoryLists = {} + -- fatset.fatCache. -- TODO: Where was I going with this? >_> + file:seek("set", fatset.bps * fatset.rb) + local fats = {} + for i = 1,fatset.fatc do + fats[i] = _msdos.readRawString(file, fatset.bps * fatset.fatbc) + end + --file:close() + if fatset.fatc > 1 then -- Validate FATs are the same + for i = 1,fatset.fatc - 1 do + if fats[i] ~= fats[i+1] then + print("msdos: FAT tables are inconsistent") + end + end + end + + fatset.fatCache.fatTable = fats[1] + fats = nil + local filedescript = {} + local writeBuff = {} -- TODO: Use + fatset.readBuff = {} -- TODO: Use? + fatset.reserved = {} -- Clusters reserved but not used + local proxyObj = {} + proxyObj.type = "filesystem" + proxyObj.address = string.format("%04x",math.floor(fatset.vsn/65536)) .. "-" .. string.format("%04x",fatset.vsn % 0x10000) -- FAT Serial Number + local file = fatfile + proxyObj.isDirectory = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + return true + end + local isDirectory + ----local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, path, function(data) isDirectory = bit32.band(data.attrib,0x10) ~= 0 end) + ----file:close() + if not found then + return false + end + return isDirectory + end + proxyObj.lastModified = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + -- No modification date for root directory + return 0 + end + local modifyT, modifyD + ----local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, path, function(data) modifyT, modifyD = data.modifyT, data.modifyD end) + ----file:close() + if not found then + return 0 + end + local year = bit32.rshift(bit32.band(modifyD, 0xFE00), 9) + 1980 + local month = bit32.rshift(bit32.band(modifyD, 0x01E0), 5) + local day = bit32.band(modifyD, 0x001F) + local hour = bit32.rshift(bit32.band(modifyT, 0xF800), 11) + local min = bit32.rshift(bit32.band(modifyT, 0x07E0), 5) + local sec = bit32.band(modifyT, 0x001F) * 2 + return os.time{year=year, month=month, day=day, hour=hour, min=min, sec=sec} + end + proxyObj.list = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + --local file = io.open(fatfile,"rb") + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, path) + if found == false then + ----file:close() + return nil, "no such file or directory" + end + local block + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + ----file:close() + local dirlist = _msdos.readDirBlock(fatset, block) + local fslist = {} + for _,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename ~= "." and data.filename ~= ".." then + if bit32.band(data.attrib,0x10) ~= 0 then + table.insert(fslist, data.filename .. "/") + -- Cache folders and their cluster as well + local cacheName = path .. (path ~= "" and "/" or "") .. data.filename + fatset.fatCache.directoryLists[cacheName] = {pos = _msdos.cluster2block(fatset, data.cluster), cluster = data.cluster} + else + table.insert(fslist, data.filename) + end + end + end + fslist.n = #fslist + return fslist + end + proxyObj.spaceTotal = function() + return fatset.tnoc * fatset.spc * fatset.bps + end + proxyObj.exists = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + return true + end + ----local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, path, function() end) + ----file:close() + return found + end + proxyObj.open = function(path, mode) + checkArg(1,path,"string") + checkArg(2,mode,"string") + if mode ~= "r" and mode ~= "rb" and mode ~= "w" and mode ~= "wb" and mode ~= "a" and mode ~= "ab" then + error("unsupported mode",2) + end + path = _msdos.cleanPath(path) + if path == "" then + return nil, "file not found" + end + local filecluster, filesize + ----local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, path, function(data) filecluster, filesize = data.cluster, data.size end) + if not found then + if mode:sub(1,1) ~= "w" and mode:sub(1,1) ~= "a" then + --file:close() + return nil, "file not found" + else + -- Allocate file. + local newname = path:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(newname) then + return nil, "Invalid file path" + end + + dirpath = fs.canonical(path .. "/..") + + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, dirpath) + + local block + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + + local dirlist = _msdos.readDirBlock(fatset, block) + local found = false + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag == NUL or fileflag == string.char(0xE5) then + found = true + local filename, ext + if newname:find(".",nil,true) ~= nil then + filename, ext = newname:match("(.*)%.(.+)") + ext = ext .. string.rep(" ", 3 - #ext) + else + filename = newname + ext = " " + end + filename = filename .. string.rep(" ", 8 - #filename) + + local curDate = os.date("*t") + local createT = bit32.lshift(curDate.hour, 11) + bit32.lshift(curDate.min, 5) + math.floor(curDate.sec/2) + local createD = bit32.lshift(math.max(curDate.year - 1980,0), 9) + bit32.lshift(curDate.month, 5) + curDate.day + if curDate.year - 1980 < 0 then + print("msdos: WARNING: Current year before 1980, year will be invalid") + end + local entry = filename .. ext .. string.char(0x20) .. string.rep(NUL, 10) .. _msdos.number2string(createT,2) .. _msdos.number2string(createD,2) .. string.rep(NUL, 6) + if entrycluster == nil then + ----file:close() + --file = io.open(fatfile,"ab") + file:seek("set", fatset.bps * blockpos + ((index - 1) * 32)) + file:write(entry) + else + local list = _msdos.getClusterChain(fatset, entrycluster) + ----file:close() + local clusterList = math.floor((index - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (index - 1) % (fatset.bps * fatset.spc / 32) + --file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + --file:write(entry) + end + ----file:close() + --file = io.open(fatfile,"rb") + filecluster, filesize = 0, 0 + break + end + end + -- TODO: Attempt to find, clean, and add a cluster + if not found then + print("msdos: No available entry") + return nil, "not enough space" + end + end + end + while true do + local rnddescrpt = math.random(1000000000,9999999999) + if filedescript[rnddescrpt] == nil then + filedescript[rnddescrpt] = { + seek = 0, + mode = mode:sub(1,1) == "r" and "r" or "w", + buffer = "", + size = filesize, + path = path + } + if filecluster == 0 then + filedescript[rnddescrpt].chain = {} + else + filedescript[rnddescrpt].chain = _msdos.getClusterChain(fatset, filecluster) + end + if mode:sub(1,1) == "a" then + -- TODO: Loading the entire file is bad. + -- Do a little check + if filecluster == 0 and filesize ~= 0 then + print("msdos: Zero cluster with non zero file size") + print(path) + elseif filecluster ~= 0 and filesize == 0 then + print("msdos: Non zero cluster with zero file size") + print(path) + elseif filecluster ~= 0 then -- Don't attempt to load nothing + filedescript[rnddescrpt].buffer = _msdos.readEntireEntry(fatset, file, filecluster) + filedescript[rnddescrpt].buffer = filedescript[rnddescrpt].buffer:sub(1, filesize) + end + end + ----file:close() + return rnddescrpt + end + end + end + proxyObj.remove = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + -- TODO: Simply clear the root directory and the fat table. + return false + end + local name = path:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(name) then + return false + end + path = fs.canonical(path .. "/..") + ----local file = io.open(fatfile,"rb") + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, path) + if found == false then + --file:close() + return false + end + local block + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + local dirlist = _msdos.readDirBlock(fatset, block) + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename == name then + local chainlist = {} + if bit32.band(data.attrib,0x10) == 0 and data.size > 0 then + chainlist = _msdos.getClusterChain(fatset, data.cluster) + chainlist[#chainlist] = nil + end + if bit32.band(data.attrib,0x10) ~= 0 then + table.insert(chainlist, data.cluster) + local murder = _msdos.recursiveKill(fatset, file, data.cluster) + for i = 1, #murder do + table.insert(chainlist, murder[i]) + end + end + if entrycluster == nil then + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", fatset.bps * blockpos + ((index - 1) * 32)) + file:write(string.char(0xE5)) + else + local list = _msdos.getClusterChain(fatset, entrycluster) + local clusterList = math.floor((index - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (index - 1) % (fatset.bps * fatset.spc / 32) + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + file:write(string.char(0xE5)) + end + local fakefile = {seek = function() end, write = function() end} + for i = 1, #chainlist do + _msdos.setFATEntry(fatset, fakefile, chainlist[i], 0x0000) + end + file:seek("set", fatset.bps * fatset.rb) + for i = 1, fatset.fatc do + file:write(fatset.fatCache.fatTable) + end + --file:close() + -- Invalidate list cache entries + local listCacheKill = {} + local preppath = "" + if path ~= "" then + preppath = path .. "/" + end + for k,v in pairs(fatset.fatCache.directoryLists) do + if k == preppath .. name or k:sub(1, #preppath + #name + 1) == preppath .. name .. "/" then + table.insert(listCacheKill, k) + end + end + for i = 1, #listCacheKill do + fatset.fatCache.directoryLists[listCacheKill[i]] = nil + end + return true + end + end + --file:close() + return false + end + proxyObj.rename = function(path, newpath) + checkArg(1,path,"string") + checkArg(2,newpath,"string") + path = _msdos.cleanPath(path) + newpath = _msdos.cleanPath(newpath) + if path == "" or newpath == "" then + return false + elseif path == newpath then + return true + end + local name = path:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(name) then + return false + end + local newname = newpath:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(newname) then + return false + end + path = fs.canonical(path .. "/..") + newpath = fs.canonical(newpath .. "/..") + --local file = io.open(fatfile,"rb") + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, path) + if found == false then + --file:close() + return false + end + found, blockpos2, entrycluster2 = _msdos.searchDirectoryLists(fatset, file, newpath) + if found == false then + --file:close() + return false + end + local block + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + local dirlist = _msdos.readDirBlock(fatset, block) + local entry, firstindex + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag ~= NUL and fileflag ~= string.char(0xE5) and bit32.band(data.attrib,0x08) == 0 and data.filename == name then + firstindex = index + local filename, ext + if newname:find(".",nil,true) ~= nil then + filename = newname:match("(.*)%..*") + ext = newname:match(".*%.(.*)") + else + filename = newname + ext = "" + end + filename = filename .. string.rep(" ", 8 - #filename) + ext = ext .. string.rep(" ", 3 - #ext) + entry = filename .. ext .. _msdos.number2string(data.attrib, 1) .. string.rep(NUL, 10) .. _msdos.number2string(data.modifyT, 2) .. _msdos.number2string(data.modifyD, 2) .. _msdos.number2string(data.cluster, 2) .. _msdos.number2string(data.size, 4) + break + end + end + if entry == nil then + --file:close() + return false + end + if entrycluster2 == nil then + file:seek("set", fatset.bps * blockpos2) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster2) + end + local dirlist = _msdos.readDirBlock(fatset, block) + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag == NUL or fileflag == string.char(0xE5) then + if entrycluster2 == nil then + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", fatset.bps * blockpos2 + ((index - 1) * 32)) + file:write(entry) + else + local list = _msdos.getClusterChain(fatset, entrycluster2) + --file:close() + local clusterList = math.floor((index - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (index - 1) % (fatset.bps * fatset.spc / 32) + file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + file:write(entry) + end + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos + ((firstindex - 1) * 32)) + file:write(string.rep(NUL, 32)) + else + --file:close() + file = io.open(fatfile,"rb") + local list = _msdos.getClusterChain(fatset, entrycluster) + local clusterList = math.floor((firstindex - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (firstindex - 1) % (fatset.bps * fatset.spc / 32) + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + file:write(string.rep(NUL, 32)) + end + --file:close() + return true + end + end + -- TODO: Attempt to find, clean, and add a cluster + return false + end + proxyObj.read = function(fd, count) + count = count or 1 + checkArg(1,fd,"number") + checkArg(2,count,"number") + if filedescript[fd] == nil or filedescript[fd].mode ~= "r" then + return nil, "bad file descriptor" + end + if #filedescript[fd].buffer >= filedescript[fd].size and filedescript[fd].seek > filedescript[fd].size then + return nil + end + count = math.min(count,8192) + if filedescript[fd].seek + count > filedescript[fd].size then + count = filedescript[fd].size - filedescript[fd].seek + end + if count == 0 then + return nil + end + while #filedescript[fd].buffer < filedescript[fd].seek + count do + local nextchain = (#filedescript[fd].buffer / fatset.bps / fatset.spc) + 1 + if filedescript[fd].chain[nextchain] == nil then + return nil + end + local block = _msdos.cluster2block(fatset, filedescript[fd].chain[nextchain]) + --local file = io.open(fatfile,"rb") + file:seek("set", block * fatset.bps) + local data = _msdos.readRawString(file, fatset.bps * fatset.spc) + --file:close() + filedescript[fd].buffer = filedescript[fd].buffer .. data + end + filedescript[fd].buffer = filedescript[fd].buffer:sub(1,filedescript[fd].size) + local data = filedescript[fd].buffer:sub(filedescript[fd].seek + 1, filedescript[fd].seek + count) + filedescript[fd].seek = filedescript[fd].seek + #data + return data + end + proxyObj.close = function(fd) + checkArg(1,fd,"number") + if filedescript[fd] == nil then + return nil, "bad file descriptor" + end + if filedescript[fd].mode == "w" then + local clusters = math.ceil(#filedescript[fd].buffer / fatset.bps / fatset.spc) + local chain = filedescript[fd].chain + if clusters ~= #chain then + print("msdos: Number of allocated clusters doesn't match buffer size") + print("AC: " .. #chain .. " BS: " .. clusters) + clusters = math.min(clusters, #chain) + end + if fatset.fatsize == 12 then + chain[clusters + 1] = 0xFFF + else + chain[clusters + 1] = 0xFFFF + end + local bpc = fatset.bps * fatset.spc + --local file = io.open(fatfile,"ab") + for i = 1, clusters do + local block = _msdos.cluster2block(fatset, filedescript[fd].chain[i]) + file:seek("set", block * fatset.bps) + file:write(filedescript[fd].buffer:sub(((i - 1) * bpc) + 1, i * bpc)) + end + local fakefile = {seek = function() end, write = function() end} + + for i = 1, clusters do + _msdos.setFATEntry(fatset, fakefile, filedescript[fd].chain[i], filedescript[fd].chain[i + 1]) + end + + file:seek("set", fatset.bps * fatset.rb) + for i = 1, fatset.fatc do + file:write(fatset.fatCache.fatTable) + end + + --file:close() + + local data, index, blockpos, entrycluster + --local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, filedescript[fd].path, function(data1, index1, blockpos1, entrycluster1) data, index, blockpos, entrycluster = data1, index1, blockpos1, entrycluster1 end) + local filename, ext + if data.filename:find(".",nil,true) ~= nil then + filename = data.filename:match("(.*)%..*") + ext = data.filename:match(".*%.(.*)") + else + filename = data.filename + ext = "" + end + filename = filename .. string.rep(" ", 8 - #filename) + ext = ext .. string.rep(" ", 3 - #ext) + local curDate = os.date("*t") + local createT = bit32.lshift(curDate.hour, 11) + bit32.lshift(curDate.min, 5) + math.floor(curDate.sec/2) + local createD = bit32.lshift(math.max(curDate.year - 1980,0), 9) + bit32.lshift(curDate.month, 5) + curDate.day + if curDate.year - 1980 < 0 then + print("msdos: WARNING: Current year before 1980, year will be invalid") + end + local entry = filename .. ext .. _msdos.number2string(data.attrib, 1) .. string.rep(NUL, 10) .. _msdos.number2string(createT, 2) .. _msdos.number2string(createD, 2) .. _msdos.number2string(clusters > 0 and filedescript[fd].chain[1] or 0, 2) .. _msdos.number2string(#filedescript[fd].buffer, 4) + if entrycluster == nil then + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", fatset.bps * blockpos + ((index - 1) * 32)) + file:write(entry) + else + local list = _msdos.getClusterChain(fatset, entrycluster) + --file:close() + local clusterList = math.floor((index - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (index - 1) % (fatset.bps * fatset.spc / 32) + file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + file:write(entry) + end + --file:close() + end + filedescript[fd] = nil + end + proxyObj.getLabel = function() + return fatset.label + end + proxyObj.seek = function(fd,kind,offset) + checkArg(1,fd,"number") + checkArg(2,kind,"string") + checkArg(3,offset,"number") + if filedescript[fd] == nil then + return nil, "bad file descriptor" + end + if kind ~= "set" and kind ~= "cur" and kind ~= "end" then + error("invalid mode",2) + end + if offset < 0 then + return nil, "Negative seek offset" + end + local newpos + if kind == "set" then + newpos = offset + elseif kind == "cur" then + newpos = filedescript[fd].seek + offset + elseif kind == "end" then + if filedescript[fd].mode == "r" then + newpos = filedescript[fd].size + offset + else + newpos = #filedescript[fd].buffer + offset + end + end + return filedescript[fd].seek + end + proxyObj.size = function(path) + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + return 0 + end + local filesize + --local file = io.open(fatfile,"rb") + local found = _msdos.doSomethingForFile(fatset, file, path, function(data) filesize = data.size end) + --file:close() + if not found then + return 0 + end + return filesize + end + proxyObj.isReadOnly = function() + return false -- TODO: Check if file is readonly + end + proxyObj.setLabel = function(newlabel) + checkArg(1,newlabel,"string") + -- TODO: Check readonly status + newlabel = newlabel:sub(1,11) + origlabel = newlabel + fatset.label = newlabel + if #newlabel < 11 then + newlabel = newlabel .. string.rep(" ", 11 - #newlabel) + end + fatset.bpblabel = newlabel + --local file = io.open(fatfile,"ab") + file:seek("set", 0x2B) + file:write(newlabel) + --file:close() + return origlabel + end + proxyObj.makeDirectory = function(path) + -- TODO: Recursively make folders + checkArg(1,path,"string") + path = _msdos.cleanPath(path) + if path == "" then + return false + end + local name = path:match(".-([^\\/]-%.?)$") + if not _msdos.validateName(name) then + return false + end + local filename, ext + if name:find(".",nil,true) then + filename, ext = name:match("(.*)%.(.+)") + else + filename = name + ext = "" + end + filename = filename .. string.rep(" ", 8 - #filename) + ext = ext .. string.rep(" ", 3 - #ext) + local freeCluster = _msdos.findFreeCluster(fatset) + if freeCluster == nil then + return false + end + path = fs.canonical(path .. "/..") + --local file = io.open(fatfile,"rb") + found, blockpos, entrycluster = _msdos.searchDirectoryLists(fatset, file, path) + if found == false then + --file:close() + return false + end + local block + if entrycluster == nil then + file:seek("set", fatset.bps * blockpos) + block = _msdos.readRawString(file, fatset.rdec * 32) + else + block = _msdos.readEntireEntry(fatset, file, entrycluster) + end + local dirlist = _msdos.readDirBlock(fatset, block) + for index,data in ipairs(dirlist) do + local fileflag = data.filename:sub(1,1) + if fileflag == "" then fileflag = NUL end + if fileflag == NUL or fileflag == string.char(0xE5) then + local curDate = os.date("*t") + local createT = bit32.lshift(curDate.hour, 11) + bit32.lshift(curDate.min, 5) + math.floor(curDate.sec/2) + local createD = bit32.lshift(math.max(curDate.year - 1980,0), 9) + bit32.lshift(curDate.month, 5) + curDate.day + if curDate.year - 1980 < 0 then + print("msdos: WARNING: Current year before 1980, year will be invalid") + end + local entry = filename .. ext .. string.char(0x30) .. string.rep(NUL, 10) .. _msdos.number2string(createT,2) .. _msdos.number2string(createD,2) .. _msdos.number2string(freeCluster, 2) .. string.rep(NUL, 4) + if entrycluster == nil then + --file:close() + file = io.open(fatfile,"ab") + file:seek("set", fatset.bps * blockpos + ((index - 1) * 32)) + file:write(entry) + else + local list = _msdos.getClusterChain(fatset, entrycluster) + --file:close() + local clusterList = math.floor((index - 1) / (fatset.bps * fatset.spc / 32)) + local clusterPos = (index - 1) % (fatset.bps * fatset.spc / 32) + file = io.open(fatfile,"ab") + file:seek("set", (fatset.bps * _msdos.cluster2block(fatset, list[clusterList + 1])) + (clusterPos * 32)) + file:write(entry) + end + file:seek("set", _msdos.cluster2block(fatset, freeCluster) * fatset.bps) + -- TODO: Write dot entries + file:write(string.rep(NUL, fatset.bps * fatset.spc)) + _msdos.setFATEntry(fatset, file, freeCluster, 0xFFFF) + --file:close() + return true + end + end + -- TODO: Attempt to find, clean, and add a cluster + --file:close() + return false + end + proxyObj.spaceUsed = function() + local count = 0 + for i = 0, fatset.tnoc - 1 do + local entry = _msdos.getFATEntry(fatset, i) + if entry ~= 0 then + count = count + 1 + end + end + return count * (fatset.spc * fatset.bps) + end + proxyObj.write = function(fd,data) + checkArg(1,fd,"number") + checkArg(2,data,"string") + if filedescript[fd] == nil or filedescript[fd].mode ~= "w" then + return nil, "bad file descriptor" + end + -- Check if we need more clusters + if math.ceil(#filedescript[fd].buffer / (fatset.spc * fatset.bps)) < math.ceil((#filedescript[fd].buffer + #data) / (fatset.spc * fatset.bps)) then + local freeCluster = _msdos.findFreeCluster(fatset) + if freeCluster == nil then + print("msdos: No more clusters") + return nil, "not enough space" + end + local chain = filedescript[fd].chain + chain[#chain + 1] = freeCluster + fatset.reserved[freeCluster] = true + end + if #filedescript[fd].buffer < filedescript[fd].seek then + filedescript[fd].buffer = filedescript[fd].buffer .. string.rep(NUL, filedescript[fd].seek - #filedescript[fd].buffer) + end + filedescript[fd].buffer = filedescript[fd].buffer:sub(1,filedescript[fd].seek) .. data .. filedescript[fd].buffer:sub(filedescript[fd].seek + #data + 1) + filedescript[fd].seek = filedescript[fd].seek + #data + return true + end + proxyObj.fat = fatset + --vcomp.register(proxyObj.address, proxyObj.type, proxyObj) + return proxyObj +end +return msdos diff --git a/mods/loader_osdi/osdiboot.lua b/mods/loader_osdi/osdiboot.lua new file mode 100644 index 0000000..b1e2f77 --- /dev/null +++ b/mods/loader_osdi/osdiboot.lua @@ -0,0 +1,7 @@ +local zy = require("zorya") +local formats = { + ["msdos\0\0\0"] = "fs_fat", + ["FoX FSys"] = "fs_foxfs", + ["linux\0\0\0"] = "fs_ext2" +} + diff --git a/mods/util_blkdev/hdd.lua b/mods/util_blkdev/hdd.lua new file mode 100644 index 0000000..8b0b4a9 --- /dev/null +++ b/mods/util_blkdev/hdd.lua @@ -0,0 +1,64 @@ +do + local cproxy = component.proxy + local hdd = {} + function hdd.open(addr) + return {pos=1, dev=cproxy(addr)} + end + + function hdd.size(blk) + return blk.dev.getCapacity() + end + + function hdd.seek(blk, amt) + blk.pos = blk.pos + amt + if (blk.pos < 1) then + blk.pos = 1 + elseif (blk.pos < hdd.size(blk)) then + blk.pos = hdd.size(blk) + end + return blk.pos + end + + function hdd.setpos(blk, pos) + blk.pos = pos + if (blk.pos < 1) then + blk.pos = 1 + elseif (blk.pos < hdd.size(blk)) then + blk.pos = hdd.size(blk) + end + return blk.pos + end + + local function hd_read(dev, pos, amt) + local start_sec = ((pos-1) // 512)+1 + local start_byte = ((pos-1) % 512)+1 + local end_sec = ((pos+amt-1) // 512)+1 + local buf = "" + for i=0, end_sec-start_sec do + buf = buf .. dev.readSector(start_sec+i) + end + return buf:sub(start_byte, start_byte+amt-1) + end + + function hdd.read(blk, amt) + blk.pos = hdd.seek(blk, amt) + return hd_read(blk.dev, blk.pos, amt) + end + + function hdd.write(blk, data) + local pos = blk.pos + local amt = #data + local start_sec = ((pos-1) // 512)+1 + local start_byte = ((pos-1) % 512)+1 + local end_sec = ((pos+amt-1) // 512)+1 + local end_byte = ((pos+amt-1) % 512)+1 + local s_sec = blk.dev.readSector(start_sec) + local e_sec = blk.dev.readSector(end_sec) + local dat = s_sec:sub(1, start_byte-1)..data..e_sec:sub(end_byte) + for i=0, end_sec-start_sec do + blk.dev.writeSector(start_sec+i, dat:sub((i*512)+1, (i+1)*512)) + end + end + + blkdev.register("hdd", hdd) +end \ No newline at end of file diff --git a/mods/util_blkdev/init.lua b/mods/util_blkdev/init.lua new file mode 100644 index 0000000..fe34f86 --- /dev/null +++ b/mods/util_blkdev/init.lua @@ -0,0 +1,57 @@ +local blkdev = {} + +do + local protos = {} + local blk = {} + + function blkdev.proxy(name, ...) + return setmetatable({udat=protos[name].open(...),type=name}, {__index=function(t, i) + if (blk[i]) then + return blk[i] + elseif (protos[t.name].hasmethod(t.udat, i)) then + return function(b, ...) + return protos[b.type](b.udat, i, ...) + end + end + end}) + end + + function blkdev.register(name, proto) + protos[name] = proto + end + + function blk:read(amt) + return protos[self.type].read(self.udat, amt) + end + + function blk:write(data) + return protos[self.type].write(self.udat, data) + end + + function blk:blktype() + return self.type + end + + function blk:seek(whence, amt) + whence = whence or 0 + if (type(whence) == "number") then + amt = whence + whence = "cur" + end + if (whence == "cur") then + return protos[self.type].seek(self.udat, amt) + elseif (whence == "set") then + return protos[self.type].setpos(self.udat, amt) + elseif (whence == "end") then + return protos[self.type].setpos(self.udat, protos[self.type].size(self.udat)+amt) + end + end +end + +--#include "hdd.lua" +--#include "prom.lua" +--#include "osdi.lua" +---#include "mtpart.lua" +---#include "mbr.lua" + +return blkdev \ No newline at end of file diff --git a/mods/util_blkdev/osdi.lua b/mods/util_blkdev/osdi.lua new file mode 100644 index 0000000..55ebd81 --- /dev/null +++ b/mods/util_blkdev/osdi.lua @@ -0,0 +1,79 @@ +do + local osdi = {} + + local sig = string.pack("