Files
gen1recomp/src/core/game3/rtc.lua
T
bryanthaboi 690f0cd9a1 TOUCHDOWN
2026-09-27 16:07:31 -04:00

367 lines
9.8 KiB
Lua

local bit = require("bit")
local Profile = require("src.core.game3.profile")
local Rtc = {}
-- pokeemerald/include/rtc.h:6
Rtc.ERR = {
INIT_ERROR = 0x0001,
INIT_WARNING = 0x0002,
TWELVE_HOUR_CLOCK = 0x0010,
POWER_FAILURE = 0x0020,
INVALID_YEAR = 0x0040,
INVALID_MONTH = 0x0080,
INVALID_DAY = 0x0100,
INVALID_HOUR = 0x0200,
INVALID_MINUTE = 0x0400,
INVALID_SECOND = 0x0800,
FLAG_MASK = 0x0FF0,
}
Rtc.SAVE_FIELDS = { "localTimeOffset", "lastBerryTreeUpdate", "rtcSkew" }
-- pokeemerald/src/rtc.c:19
local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
local SECONDS_PER_DAY = 86400
local state = {
errorStatus = nil,
fixedBase = nil,
advanced = 0,
host = nil,
}
Rtc.localTime = { days = 0, hours = 0, minutes = 0, seconds = 0 }
local function s8(v)
v = math.floor(v) % 256
if v >= 128 then v = v - 256 end
return v
end
local function s16(v)
v = math.floor(v) % 65536
if v >= 32768 then v = v - 65536 end
return v
end
local function u16(v)
return math.floor(v) % 65536
end
Rtc.s8, Rtc.s16, Rtc.u16 = s8, s16, u16
function Rtc.newTime(days, hours, minutes, seconds)
return {
days = s16(tonumber(days) or 0),
hours = s8(tonumber(hours) or 0),
minutes = s8(tonumber(minutes) or 0),
seconds = s8(tonumber(seconds) or 0),
}
end
function Rtc.copyTime(t)
t = type(t) == "table" and t or {}
return Rtc.newTime(t.days, t.hours, t.minutes, t.seconds)
end
function Rtc.enabled(session)
local ok, row = pcall(Profile.forSession, session)
if not ok or type(row) ~= "table" then return false end
return type(row.clock) == "table" and row.clock.rtc == true
end
-- pokeemerald/src/rtc.c:57
function Rtc.isLeapYear(year)
return (year % 4 == 0 and year % 100 ~= 0) or year % 400 == 0
end
-- pokeemerald/src/rtc.c:65
function Rtc.dayCount(year, month, day)
local count = 0
for i = year - 1, 0, -1 do
count = count + 365
if Rtc.isLeapYear(i) then count = count + 1 end
end
for i = 1, month - 1 do
count = count + (DAYS_IN_MONTH[i] or 0)
end
if month > 2 and Rtc.isLeapYear(year) then count = count + 1 end
count = count + day
return u16(count)
end
-- pokeemerald/src/rtc.c:89
function Rtc.getDayCount(info)
return Rtc.dayCount(info.year, info.month, info.day)
end
local function parseFixed(spec)
if type(spec) ~= "string" then return nil end
local y, mo, d, h, mi, se = spec:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)[T ](%d%d):(%d%d):?(%d?%d?)$")
if not y then
y, mo, d = spec:match("^(%d%d%d%d)%-(%d%d)%-(%d%d)$")
h, mi, se = "0", "0", "0"
end
if not y then return nil end
return {
year = tonumber(y), month = tonumber(mo), day = tonumber(d),
hour = tonumber(h), minute = tonumber(mi), second = tonumber(se) ~= nil and tonumber(se) or 0,
}
end
local function hostFields()
if state.host then return state.host() end
local t = os.date("*t")
return { year = t.year, month = t.month, day = t.day, hour = t.hour, minute = t.min, second = t.sec }
end
local function linearOf(f)
local y = f.year - 2000
local dc = 0
if y > 0 then
for i = y - 1, 0, -1 do
dc = dc + 365
if Rtc.isLeapYear(i) then dc = dc + 1 end
end
elseif y < 0 then
for i = y, -1 do
dc = dc - 365
if Rtc.isLeapYear(i) then dc = dc - 1 end
end
end
for i = 1, f.month - 1 do dc = dc + (DAYS_IN_MONTH[i] or 0) end
if f.month > 2 and Rtc.isLeapYear(y) then dc = dc + 1 end
dc = dc + f.day - 1
return dc * SECONDS_PER_DAY + f.hour * 3600 + f.minute * 60 + f.second
end
local function fieldsOf(total)
local dc = math.floor(total / SECONDS_PER_DAY)
local rem = total - dc * SECONDS_PER_DAY
local y = 0
while dc < 0 do
y = y - 1
dc = dc + (Rtc.isLeapYear(y) and 366 or 365)
end
while true do
local len = Rtc.isLeapYear(y) and 366 or 365
if dc < len then break end
dc = dc - len
y = y + 1
end
local month = 1
while true do
local len = DAYS_IN_MONTH[month]
if month == 2 and Rtc.isLeapYear(y) then len = len + 1 end
if dc < len then break end
dc = dc - len
month = month + 1
end
return {
year = y + 2000, month = month, day = dc + 1,
hour = math.floor(rem / 3600), minute = math.floor(rem / 60) % 60, second = rem % 60,
}
end
Rtc._linearOf, Rtc._fieldsOf = linearOf, fieldsOf
local function envFixed()
local spec = os.getenv and os.getenv("POKEPORT_RTC") or nil
if spec == nil or spec == "" then return nil end
local f = parseFixed(spec)
if not f then error("POKEPORT_RTC must be YYYY-MM-DD[THH:MM[:SS]], got " .. tostring(spec), 0) end
return linearOf(f)
end
local function nowLinear(session)
local base = state.fixedBase
if base == nil then
base = envFixed()
if base ~= nil then state.fixedBase = base end
end
if base == nil then base = linearOf(hostFields()) end
local skew = type(session) == "table" and tonumber(session.rtcSkew) or 0
return base + state.advanced + skew
end
function Rtc.hostInfo(session)
local f = fieldsOf(nowLinear(session))
return {
year = f.year - 2000, month = f.month, day = f.day,
hour = f.hour, minute = f.minute, second = f.second,
}
end
local function checkInfo(info)
local err = 0
if info.year < 0 or info.year > 99 then err = err + Rtc.ERR.INVALID_YEAR end
return err
end
-- pokeemerald/src/rtc.c:97
function Rtc.init()
local forced = os.getenv and os.getenv("POKEPORT_RTC_ERROR") or nil
if forced and forced ~= "" then
state.errorStatus = tonumber(forced) or tonumber(forced, 16) or 0
return state.errorStatus
end
state.errorStatus = checkInfo(Rtc.hostInfo(nil))
return state.errorStatus
end
-- pokeemerald/src/rtc.c:121
function Rtc.errorStatus()
if state.errorStatus == nil then Rtc.init() end
return state.errorStatus
end
-- pokeemerald/src/rtc.c:126
function Rtc.getInfo(session)
if bit.band(Rtc.errorStatus(), Rtc.ERR.FLAG_MASK) ~= 0 then
return { year = 0, month = 1, day = 1, hour = 0, minute = 0, second = 0 }
end
return Rtc.hostInfo(session)
end
local function borrow(r)
if r.seconds < 0 then
r.seconds = r.seconds + 60
r.minutes = r.minutes - 1
end
if r.minutes < 0 then
r.minutes = r.minutes + 60
r.hours = r.hours - 1
end
if r.hours < 0 then
r.hours = r.hours + 24
r.days = r.days - 1
end
return Rtc.newTime(r.days, r.hours, r.minutes, r.seconds)
end
-- pokeemerald/src/rtc.c:263
function Rtc.calcTimeDifferenceRtc(info, t)
t = t or {}
return borrow({
seconds = s8(info.second - (t.seconds or 0)),
minutes = s8(info.minute - (t.minutes or 0)),
hours = s8(info.hour - (t.hours or 0)),
days = s16(Rtc.getDayCount(info) - (t.days or 0)),
})
end
-- pokeemerald/src/rtc.c:311
function Rtc.calcTimeDifference(t1, t2)
t1, t2 = t1 or {}, t2 or {}
return borrow({
seconds = s8((t2.seconds or 0) - (t1.seconds or 0)),
minutes = s8((t2.minutes or 0) - (t1.minutes or 0)),
hours = s8((t2.hours or 0) - (t1.hours or 0)),
days = s16((t2.days or 0) - (t1.days or 0)),
})
end
local function offsetOf(session)
if type(session) ~= "table" then return Rtc.newTime(0, 0, 0, 0) end
if type(session.localTimeOffset) ~= "table" then
session.localTimeOffset = Rtc.newTime(0, 0, 0, 0)
end
return session.localTimeOffset
end
-- pokeemerald/src/rtc.c:290
function Rtc.calcLocalTime(session)
Rtc._info = Rtc.getInfo(session)
Rtc.localTime = Rtc.calcTimeDifferenceRtc(Rtc._info, offsetOf(session))
return Rtc.copyTime(Rtc.localTime)
end
-- pokeemerald/src/rtc.c:301
function Rtc.calcLocalTimeOffset(session, days, hours, minutes, seconds)
Rtc.localTime = Rtc.newTime(days, hours, minutes, seconds)
Rtc._info = Rtc.getInfo(session)
local off = Rtc.calcTimeDifferenceRtc(Rtc._info, Rtc.localTime)
if type(session) == "table" then session.localTimeOffset = off end
return Rtc.copyTime(off)
end
-- pokeemerald/src/rtc.c:296
function Rtc.initLocalTimeOffset(session, hour, minute)
return Rtc.calcLocalTimeOffset(session, 0, hour, minute, 0)
end
local function toBcd(v)
return math.floor(v / 10) * 16 + v % 10
end
-- pokeemerald/src/rtc.c:337
function Rtc.minuteCount(session)
local info = Rtc.getInfo(session)
Rtc._info = info
return 24 * 60 * Rtc.getDayCount(info) + 60 * toBcd(info.hour) + toBcd(info.minute)
end
-- pokeemerald/src/rtc.c:343
function Rtc.localDayCount()
local info = Rtc._info or Rtc.getInfo(nil)
return Rtc.getDayCount(info)
end
function Rtc.timeMinutes(t)
t = t or {}
return 24 * 60 * (t.days or 0) + 60 * (t.hours or 0) + (t.minutes or 0)
end
function Rtc.anchorToLastUpdate(session)
if type(session) ~= "table" then return 0 end
local last = session.lastBerryTreeUpdate
if type(last) ~= "table" then return tonumber(session.rtcSkew) or 0 end
local off = offsetOf(session)
session.rtcSkew = 0
local host = Rtc.getInfo(session)
local target = (last.days + off.days) * SECONDS_PER_DAY
+ (last.hours + off.hours) * 3600 + (last.minutes + off.minutes) * 60
+ (last.seconds + off.seconds)
local cur = Rtc.getDayCount(host) * SECONDS_PER_DAY + host.hour * 3600 + host.minute * 60 + host.second
session.rtcSkew = target - cur
return session.rtcSkew
end
function Rtc.setFixed(spec)
if spec == nil then
state.fixedBase = nil
return
end
local f = type(spec) == "table" and spec or parseFixed(spec)
if not f then error("Rtc.setFixed: bad spec " .. tostring(spec), 2) end
state.fixedBase = linearOf(f)
state.errorStatus = nil
end
function Rtc.advance(minutes, seconds)
state.advanced = state.advanced + (tonumber(minutes) or 0) * 60 + (tonumber(seconds) or 0)
return state.advanced
end
function Rtc.setHostClock(fn)
state.host = fn
state.errorStatus = nil
end
function Rtc.reset()
state.errorStatus = nil
state.fixedBase = nil
state.advanced = 0
state.host = nil
Rtc._info = nil
Rtc.localTime = Rtc.newTime(0, 0, 0, 0)
end
function Rtc.fixedActive()
return state.fixedBase ~= nil or (os.getenv and (os.getenv("POKEPORT_RTC") or "") ~= "")
end
return Rtc