mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-10 02:08:29 -04:00
367 lines
9.8 KiB
Lua
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
|