Previous eeprom flash data will not be compatible with new one. [ADDED] Address bus outputs data rate via it's memory output (for regular wire) [FIXED] Address bus wrong undo message [FIXED] CPU compiler erroring on empty program
307 lines
7.8 KiB
Lua
307 lines
7.8 KiB
Lua
AddCSLuaFile("cl_init.lua")
|
|
AddCSLuaFile("shared.lua")
|
|
|
|
include('shared.lua')
|
|
|
|
ENT.WireDebugName = "WireHDD"
|
|
ENT.OverlayDelay = 0
|
|
|
|
function ENT:OnRemove()
|
|
self:SaveCachedBlock()
|
|
end
|
|
|
|
function ENT:Initialize()
|
|
self.Entity:PhysicsInit(SOLID_VPHYSICS)
|
|
self.Entity:SetMoveType(MOVETYPE_VPHYSICS)
|
|
self.Entity:SetSolid(SOLID_VPHYSICS)
|
|
self.Entity:SetUseType(SIMPLE_USE)
|
|
|
|
self.Outputs = Wire_CreateOutputs(self.Entity, { "Data" })
|
|
self.Inputs = Wire_CreateInputs(self.Entity, { "Clk", "AddrRead", "AddrWrite", "Data" })
|
|
|
|
self.Clk = 0
|
|
self.AWrite = 0
|
|
self.ARead = 0
|
|
self.Data = 0
|
|
self.Out = 0
|
|
|
|
self.BlockSize = 16
|
|
|
|
//Hard drive id/folder id:
|
|
self.DriveID = 0
|
|
self.PrevDriveID = -1
|
|
//Hard drive capicacity (loaded from hdd)
|
|
self.DriveCap = 0
|
|
|
|
//Cache for read/written values
|
|
self.ReadCache = {}
|
|
//Shows whether block is cached for reading
|
|
self.BlockCached = {}
|
|
|
|
//Current sector cache
|
|
self.Cache = {}
|
|
for i = 0,self.BlockSize-1 do
|
|
self.Cache[i] = 0
|
|
end
|
|
//Block that is cached
|
|
self.CachedBlock = -1
|
|
self.CacheWrites = 0
|
|
//Owners STEAMID
|
|
self.SteamID = "SINGLEPLAYER"
|
|
|
|
self:SetOverlayText("Flash memory")
|
|
end
|
|
|
|
function ENT:GetStructName(name)
|
|
return "WireFlash\\"..self.SteamID.."\\HDD"..self.DriveID.."\\"..name..".txt"
|
|
end
|
|
|
|
function ENT:GetCap()
|
|
//If hard drive exists
|
|
if (file.Exists(self:GetStructName("drive"))) then
|
|
//Read size cap
|
|
local sizecap = file.Read(self:GetStructName("drive"))
|
|
//If it is a number
|
|
if (tonumber(sizecap)) then
|
|
self.DriveCap = tonumber(sizecap)
|
|
else
|
|
file.Write(self:GetStructName("drive"), self.DriveCap)
|
|
end
|
|
else
|
|
file.Write(self:GetStructName("drive"), self.DriveCap)
|
|
end
|
|
|
|
//Can't have cap bigger than 256 in MP
|
|
if ((!SinglePlayer()) && (self.DriveCap > 256)) then
|
|
self.DriveCap = 256
|
|
end
|
|
end
|
|
|
|
function ENT:UpdateCap()
|
|
//Can't have cap bigger than 256 in MP
|
|
if ((!SinglePlayer()) && (self.DriveCap > 256)) then
|
|
self.DriveCap = 256
|
|
end
|
|
file.Write(self:GetStructName("drive"), self.DriveCap)
|
|
|
|
self:GetCap()
|
|
end
|
|
|
|
function ENT:GetFloatTable(Text)
|
|
local text = Text
|
|
local tbl = {}
|
|
local ptr = 0
|
|
while (string.len(text) > 0) do
|
|
local value = string.sub(text,1,24)
|
|
text = string.sub(text,24,string.len(text))
|
|
tbl[ptr] = tonumber(value)
|
|
ptr = ptr + 1
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
function ENT:MakeFloatTable(Table)
|
|
local text = ""
|
|
for i=0,table.Count(Table)-1 do
|
|
//Clamp size to 24 chars
|
|
local floatstr = string.sub(tostring(Table[i]),1,24)
|
|
//Make a string, and append missing spaces
|
|
floatstr = floatstr .. string.rep(" ",24-string.len(floatstr))
|
|
|
|
text = text..floatstr
|
|
end
|
|
|
|
return text
|
|
end
|
|
|
|
function ENT:ReadCell(Address)
|
|
//DriveID should be > 0, and less than 4 in MP
|
|
if ((self.DriveID < 0) || (!SinglePlayer() && (self.DriveID >= 4))) then
|
|
return nil
|
|
end
|
|
|
|
local player = self.pl
|
|
if (player:IsValid()) then
|
|
local steamid = player:SteamID()
|
|
steamid = string.gsub(steamid, ":", "_")
|
|
if (steamid ~= "UNKNOWN") then
|
|
self.SteamID = steamid
|
|
else
|
|
self.SteamID = "SINGLEPLAYER"
|
|
end
|
|
|
|
//If drive has changed, change cap
|
|
if (self.DriveID ~= self.PrevDriveID) then
|
|
self:GetCap()
|
|
self.PrevDriveID = self.DriveID
|
|
end
|
|
|
|
//Check if address is valid
|
|
if ((Address < self.DriveCap * 1024) && (Address >= 0)) then
|
|
//1. Check if this address is cached for read
|
|
if (self.ReadCache[Address]) then
|
|
return self.ReadCache[Address]
|
|
end
|
|
|
|
//2. Read sector
|
|
local block = math.floor(Address / self.BlockSize)
|
|
local blockaddress = math.floor(Address) % self.BlockSize
|
|
|
|
//If sector isn't created yet, return 0
|
|
if (!file.Exists(self:GetStructName(block))) then
|
|
for i=0,self.BlockSize-1 do
|
|
self.ReadCache[block*self.BlockSize+i] = 0
|
|
end
|
|
self.BlockCached[block] = true
|
|
return 0
|
|
end
|
|
|
|
//Read the block
|
|
local blockdata = self:GetFloatTable(file.Read(self:GetStructName(block)))
|
|
for i=0,self.BlockSize-1 do
|
|
if (blockdata[i]) then
|
|
self.ReadCache[block*self.BlockSize+i] = blockdata[i]
|
|
else
|
|
self.ReadCache[block*self.BlockSize+i] = 0
|
|
end
|
|
end
|
|
self.BlockCached[block] = true
|
|
|
|
return self.ReadCache[block*self.BlockSize+blockaddress]
|
|
else
|
|
return nil
|
|
end
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
function ENT:SaveCachedBlock()
|
|
if (self.CachedBlock != -1) then
|
|
file.Write(self:GetStructName(self.CachedBlock),self:MakeFloatTable(self.Cache))
|
|
end
|
|
self.CacheWrites = 0
|
|
end
|
|
|
|
function ENT:WriteCell(Address, value)
|
|
//DriveID should be > 0, and less than 4 in MP
|
|
if ((self.DriveID < 0) || (!SinglePlayer() && (self.DriveID >= 4))) then
|
|
return false
|
|
end
|
|
|
|
local player = self.pl
|
|
if (player:IsValid()) then
|
|
local steamid = player:SteamID()
|
|
steamid = string.gsub(steamid, ":", "_")
|
|
if (steamid ~= "UNKNOWN") then
|
|
self.SteamID = steamid
|
|
else
|
|
self.SteamID = "SINGLEPLAYER"
|
|
end
|
|
|
|
//If drive has changed, change cap
|
|
if (self.DriveID ~= self.PrevDriveID) then
|
|
self:GetCap()
|
|
self.PrevDriveID = self.DriveID
|
|
end
|
|
|
|
//Check if address is valid
|
|
if ((Address < self.DriveCap * 1024) && (Address >= 0)) then
|
|
local block = math.floor(Address / self.BlockSize)
|
|
local blockaddress = math.floor(Address) % self.BlockSize
|
|
|
|
//1. Check if this sector isn't the one which is cached
|
|
// If no, make it current block
|
|
if (self.CachedBlock != block) then
|
|
//Save previous one
|
|
self:SaveCachedBlock()
|
|
|
|
//Check if this block is cached for read
|
|
if (!self.BlockCached[block]) then
|
|
//If sector isn't created yet, return 0
|
|
if (!file.Exists(self:GetStructName(block))) then
|
|
for i=0,self.BlockSize-1 do
|
|
self.ReadCache[block*self.BlockSize+i] = 0
|
|
end
|
|
else
|
|
//Read the block
|
|
local blockdata = self:GetFloatTable(file.Read(self:GetStructName(block)))
|
|
for i=0,self.BlockSize-1 do
|
|
if (blockdata[i]) then
|
|
self.ReadCache[block*self.BlockSize+i] = blockdata[i]
|
|
else
|
|
self.ReadCache[block*self.BlockSize+i] = 0
|
|
end
|
|
end
|
|
end
|
|
self.BlockCached[block] = true
|
|
end
|
|
|
|
//Load it from readcache
|
|
for i=0,self.BlockSize-1 do
|
|
self.Cache[i] = self.ReadCache[block*self.BlockSize+i]
|
|
end
|
|
self.CachedBlock = block
|
|
end
|
|
|
|
//Write to the block
|
|
self.Cache[blockaddress] = value
|
|
self.ReadCache[Address] = value
|
|
self.CacheWrites = self.CacheWrites + 1
|
|
|
|
//If under 256 writes to same sector, dont dump sector to disk
|
|
if (self.CacheWrites > 256) then
|
|
self:SaveCachedBlock()
|
|
end
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function ENT:TriggerInput(iname, value)
|
|
if (iname == "Clk") then
|
|
self.Clk = value
|
|
if (self.Clk >= 1) then
|
|
self:WriteCell(self.AWrite, self.Data)
|
|
if (self.ARead == self.AWrite) then
|
|
local val = self:ReadCell(self.ARead)
|
|
if (val) then
|
|
Wire_TriggerOutput(self.Entity, "Data", val)
|
|
self.Out = val
|
|
end
|
|
end
|
|
end
|
|
elseif (iname == "AddrRead") then
|
|
self.ARead = value
|
|
local val = self:ReadCell(value)
|
|
if (val) then
|
|
Wire_TriggerOutput(self.Entity, "Data", val)
|
|
self.Out = val
|
|
end
|
|
elseif (iname == "AddrWrite") then
|
|
self.AWrite = value
|
|
if (self.Clk >= 1) then
|
|
self:WriteCell(self.AWrite, self.Data)
|
|
end
|
|
elseif (iname == "Data") then
|
|
self.Data = value
|
|
if (self.Clk >= 1) then
|
|
self:WriteCell(self.AWrite, self.Data)
|
|
if (self.ARead == self.AWrite) then
|
|
local val = self:ReadCell(self.ARead)
|
|
if (val) then
|
|
Wire_TriggerOutput(self.Entity, "Data", val)
|
|
self.Out = val
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
self:SetOverlayText("Flash memory - "..self.DriveCap.."kb".."\nWriteAddr:"..self.AWrite.." Data:"..self.Data.." Clock:"..self.Clk..
|
|
"\nReadAddr:"..self.ARead.." = ".. self.Out)
|
|
end
|