/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.modules; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; import jpcsp.Clock.TimeNanos; import jpcsp.Emulator; import jpcsp.HLE.BufferInfo; import jpcsp.HLE.BufferInfo.Usage; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.Modules; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.TPointer64; import jpcsp.HLE.kernel.types.ScePspDateTime; import org.apache.log4j.Logger; public class sceRtc extends HLEModule { public static Logger log = Modules.getLogger("sceRtc"); final static int PSP_TIME_INVALID_YEAR = -1; final static int PSP_TIME_INVALID_MONTH = -2; final static int PSP_TIME_INVALID_DAY = -3; final static int PSP_TIME_INVALID_HOUR = -4; final static int PSP_TIME_INVALID_MINUTES = -5; final static int PSP_TIME_INVALID_SECONDS = -6; final static int PSP_TIME_INVALID_MICROSECONDS = -7; // Statics verified on PSP. final static int PSP_TIME_SECONDS_IN_MINUTE = 60; final static int PSP_TIME_SECONDS_IN_HOUR = 3600; final static int PSP_TIME_SECONDS_IN_DAY = 86400; final static int PSP_TIME_SECONDS_IN_WEEK = 604800; final static int PSP_TIME_SECONDS_IN_MONTH = 2629743; final static int PSP_TIME_SECONDS_IN_YEAR = 31556926; private long rtcMagicOffset = 62135596800000000L; protected static SimpleDateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); protected long hleGetCurrentTick() { TimeNanos timeNanos = Emulator.getClock().currentTimeNanos(); return (timeNanos.micros + timeNanos.millis * 1000) + timeNanos.seconds * 1000000L + rtcMagicOffset; } /** 64 bit addend */ protected int hleRtcTickAdd64(TPointer64 dstPtr, TPointer64 srcPtr, long value, long multiplier) { if (log.isDebugEnabled()) { log.debug(String.format("hleRtcTickAdd64 dstPtr=%s, srcPtr=%s(%d), %d * %d", dstPtr, srcPtr, srcPtr.getValue(), value, multiplier)); } long src = srcPtr.getValue(); dstPtr.setValue(src + multiplier * value); return 0; } /** 32 bit addend */ protected int hleRtcTickAdd32(TPointer64 dstPtr, TPointer64 srcPtr, int value, long multiplier) { log.debug("hleRtcTickAdd32 " + multiplier + " * " + value); long src = srcPtr.getValue(); dstPtr.setValue(src + multiplier * value); return 0; } protected Date getDateFromTick(long tick) { return new Date((tick - rtcMagicOffset) / 1000L); } protected String formatRFC3339(Date date) { String result = rfc3339.format(date); // SimpleDateFormat outputs the timezone offset in the format "hhmm" // instead of "hh:mm" as required by RFC3339. result = result.replaceFirst("(\\d\\d)(\\d\\d)$", "$1:$2"); return result; } protected TimeZone getLocalTimeZone() { return TimeZone.getDefault(); } /** * Obtains the Tick Resolution. * * @param processor * * @return The Tick Resolution in microseconds. */ @HLEFunction(nid = 0xC41C2853, version = 150) public int sceRtcGetTickResolution() { return 1000000; } @HLEFunction(nid = 0x3F7AD767, version = 150) public int sceRtcGetCurrentTick(TPointer64 currentTick) { currentTick.setValue(hleGetCurrentTick()); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetCurrentTick returning %d", currentTick.getValue())); } return 0; } @HLEFunction(nid = 0x011F03C1, version = 150) public long sceRtcGetAccumulativeTime() { // Returns the difference between the last reincarnated time and the current tick. // Just return our current tick, since there's no need to mimick such behaviour. return hleGetCurrentTick(); } @HLEFunction(nid = 0x029CA3B3, version = 150) public long sceRtcGetAccumlativeTime() { // Typo. Same as sceRtcGetAccumulativeTime. return hleGetCurrentTick(); } @HLEFunction(nid = 0x4CFA57B0, version = 150) public int sceRtcGetCurrentClock(TPointer addr, int tz) { ScePspDateTime pspTime = new ScePspDateTime(tz); pspTime.write(addr); return 0; } @HLEFunction(nid = 0xE7C27D1B, version = 150) public int sceRtcGetCurrentClockLocalTime(TPointer addr) { ScePspDateTime pspTime = new ScePspDateTime(); pspTime.write(addr); return 0; } @HLEFunction(nid = 0x34885E0D, version = 150) public int sceRtcConvertUtcToLocalTime(TPointer64 utcPtr, TPointer64 localPtr) { // Add the offset of the local time zone to UTC TimeZone localTimeZone = getLocalTimeZone(); hleRtcTickAdd64(localPtr, utcPtr, localTimeZone.getRawOffset(), 1000L); return 0; } @HLEFunction(nid = 0x779242A2, version = 150) public int sceRtcConvertLocalTimeToUTC(TPointer64 localPtr, TPointer64 utcPtr) { // Subtract the offset of the local time zone to UTC TimeZone localTimeZone = getLocalTimeZone(); hleRtcTickAdd64(utcPtr, localPtr, -localTimeZone.getRawOffset(), 1000L); return 0; } @HLEFunction(nid = 0x42307A17, version = 150) public boolean sceRtcIsLeapYear(int year) { return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); } @HLEFunction(nid = 0x05EF322C, version = 150) public int sceRtcGetDaysInMonth(int year, int month) { Calendar cal = new GregorianCalendar(year, month - 1, 1); int days = cal.getActualMaximum(Calendar.DAY_OF_MONTH); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetDaysInMonth returning %d", days)); } return days; } /** * Returns the day of the week. * 0 = sunday, 1 = monday, 2 = tuesday, 3 = wednesday, 4 = thursday, 5 = friday, 6 = saturnday * * @param year * @param month * @param day * * @return The day of the week. */ @HLEFunction(nid = 0x57726BC1, version = 150) public int sceRtcGetDayOfWeek(int year, int month, int day) { Calendar cal = Calendar.getInstance(); cal.set(year, month - 1, day); int dayOfWeekNumber = cal.get(Calendar.DAY_OF_WEEK); dayOfWeekNumber = (dayOfWeekNumber - 1 + 7) % 7; if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetDayOfWeek returning %d", dayOfWeekNumber)); } return dayOfWeekNumber; } /** * Validate pspDate component ranges * * @param date - pointer to pspDate struct to be checked * @return 0 on success, one of PSP_TIME_INVALID_* on error */ @HLEFunction(nid = 0x4B1B5E82, version = 150) public int sceRtcCheckValid(ScePspDateTime time) { Calendar cal = new GregorianCalendar( time.year, time.month - 1, time.day, time.hour, time.minute, time.second ); int result = 0; if (time.year < 1582 || time.year > 3000) { // What are valid years? result = PSP_TIME_INVALID_YEAR; } else if (time.month < 1 || time.month > 12) { result = PSP_TIME_INVALID_MONTH; } else if (time.day < 1 || time.day > 31) { result = PSP_TIME_INVALID_DAY; } else if (time.hour < 0 || time.hour > 23) { result = PSP_TIME_INVALID_HOUR; } else if (time.minute < 0 || time.minute > 59) { result = PSP_TIME_INVALID_MINUTES; } else if (time.second < 0 || time.second > 59) { result = PSP_TIME_INVALID_SECONDS; } else if (time.microsecond < 0 || time.microsecond >= 1000000) { result = PSP_TIME_INVALID_MICROSECONDS; } else if (cal.get(Calendar.DAY_OF_MONTH) != time.day) { // Check if this is a valid day of the month result = PSP_TIME_INVALID_DAY; } if (log.isDebugEnabled()) { log.debug(String.format("sceRtcCheckValid time=%s, cal=%s, returning 0x%08X", time, cal, result)); } return result; } @HLEFunction(nid = 0x3A807CC8, version = 150) public int sceRtcSetTime_t(TPointer dateAddr, int time) { ScePspDateTime dateTime = ScePspDateTime.fromUnixTime(time); dateTime.write(dateAddr); return 0; } @HLEFunction(nid = 0x27C4594C, version = 150) public int sceRtcGetTime_t(ScePspDateTime dateTime, TPointer32 timeAddr) { Calendar cal = Calendar.getInstance(); cal.set(dateTime.year, dateTime.month - 1, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second); int unixtime = (int)(cal.getTime().getTime() / 1000L); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetTime_t returning %d", unixtime)); } timeAddr.setValue(unixtime); return 0; } @HLEFunction(nid = 0xF006F264, version = 150) public int sceRtcSetDosTime(TPointer dateAddr, int time) { ScePspDateTime dateTime = ScePspDateTime.fromMSDOSTime(time); dateTime.write(dateAddr); return 0; } @HLEFunction(nid = 0x36075567, version = 150) public int sceRtcGetDosTime(ScePspDateTime dateTime, TPointer32 timeAddr) { Calendar cal = Calendar.getInstance(); cal.set(dateTime.year, dateTime.month - 1, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second); int dostime = (int)(cal.getTime().getTime() / 1000L); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetDosTime returning %d", dostime)); } timeAddr.setValue(dostime); return 0; } @HLEFunction(nid = 0x7ACE4C04, version = 150) public int sceRtcSetWin32FileTime(TPointer dateAddr, long time) { ScePspDateTime dateTime = ScePspDateTime.fromFILETIMETime(time); dateTime.write(dateAddr); return 0; } @HLEFunction(nid = 0xCF561893, version = 150) public int sceRtcGetWin32FileTime(ScePspDateTime dateTime, TPointer64 timeAddr) { Calendar cal = Calendar.getInstance(); cal.set(dateTime.year, dateTime.month - 1, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second); int filetimetime = (int)(cal.getTime().getTime() / 1000L); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetWin32FileTime returning %d", filetimetime)); } timeAddr.setValue(filetimetime); return 0; } /** Set a pspTime struct based on ticks. */ @HLEFunction(nid = 0x7ED29E40, version = 150) public int sceRtcSetTick(TPointer timeAddr, TPointer64 ticksAddr) { long ticks = ticksAddr.getValue() - rtcMagicOffset; ScePspDateTime time = ScePspDateTime.fromMicros(ticks); time.write(timeAddr); return 0; } /** Set ticks based on a pspTime struct. */ @HLEFunction(nid = 0x6FF40ACC, version = 150) public int sceRtcGetTick(ScePspDateTime time, TPointer64 ticksAddr) { // use java library to convert a date to seconds, then multiply it by the tick resolution Calendar cal = new GregorianCalendar(time.year, time.month - 1, time.day, time.hour, time.minute, time.second); cal.set(Calendar.MILLISECOND, time.microsecond / 1000); cal.setTimeZone(ScePspDateTime.GMT); long ticks = rtcMagicOffset + (cal.getTimeInMillis() * 1000L) + (time.microsecond % 1000); ticksAddr.setValue(ticks); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetTick returning %d", ticks)); } return 0; } @HLEFunction(nid = 0x9ED0AE87, version = 150) public int sceRtcCompareTick(TPointer64 firstPtr, TPointer64 secondPtr) { long tick1 = firstPtr.getValue(); long tick2 = secondPtr.getValue(); if (tick1 < tick2) { return -1; } if (tick1 > tick2) { return 1; } return 0; } @HLEFunction(nid = 0x44F45E05, version = 150) public int sceRtcTickAddTicks(TPointer64 dstPtr, TPointer64 srcPtr, long value) { log.debug("sceRtcTickAddTicks redirecting to hleRtcTickAdd64(1)"); return hleRtcTickAdd64(dstPtr, srcPtr, value, 1); } @HLEFunction(nid = 0x26D25A5D, version = 150) public int sceRtcTickAddMicroseconds(TPointer64 dstPtr, TPointer64 srcPtr, long value) { log.debug("sceRtcTickAddMicroseconds redirecting to hleRtcTickAdd64(1)"); return hleRtcTickAdd64(dstPtr, srcPtr, value, 1); } @HLEFunction(nid = 0xF2A4AFE5, version = 150) public int sceRtcTickAddSeconds(TPointer64 dstPtr, TPointer64 srcPtr, long value) { log.debug("sceRtcTickAddSeconds redirecting to hleRtcTickAdd64(1000000)"); return hleRtcTickAdd64(dstPtr, srcPtr, value, 1000000L); } @HLEFunction(nid = 0xE6605BCA, version = 150) public int sceRtcTickAddMinutes(TPointer64 dstPtr, TPointer64 srcPtr, long value) { log.debug("sceRtcTickAddMinutes redirecting to hleRtcTickAdd64(60*1000000)"); return hleRtcTickAdd64(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_MINUTE * 1000000L); } @HLEFunction(nid = 0x26D7A24A, version = 150) public int sceRtcTickAddHours(TPointer64 dstPtr, TPointer64 srcPtr, int value) { log.debug("sceRtcTickAddHours redirecting to hleRtcTickAdd32(60*60*1000000)"); return hleRtcTickAdd32(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_HOUR * 1000000L); } @HLEFunction(nid = 0xE51B4B7A, version = 150) public int sceRtcTickAddDays(TPointer64 dstPtr, TPointer64 srcPtr, int value) { log.debug("sceRtcTickAddDays redirecting to hleRtcTickAdd32(24*60*60*1000000)"); return hleRtcTickAdd32(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_DAY * 1000000L); } @HLEFunction(nid = 0xCF3A2CA8, version = 150) public int sceRtcTickAddWeeks(TPointer64 dstPtr, TPointer64 srcPtr, int value) { log.debug("sceRtcTickAddWeeks redirecting to hleRtcTickAdd32(7*24*60*60*1000000)"); return hleRtcTickAdd32(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_WEEK * 1000000L); } @HLEFunction(nid = 0xDBF74F1B, version = 150) public int sceRtcTickAddMonths(TPointer64 dstPtr, TPointer64 srcPtr, int value) { log.debug("sceRtcTickAddMonths redirecting to hleRtcTickAdd32(30*24*60*60*1000000)"); return hleRtcTickAdd32(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_MONTH * 1000000L); } @HLEFunction(nid = 0x42842C77, version = 150) public int sceRtcTickAddYears(TPointer64 dstPtr, TPointer64 srcPtr, int value) { log.debug("sceRtcTickAddYears redirecting to hleRtcTickAdd32(365*24*60*60*1000000)"); return hleRtcTickAdd32(dstPtr, srcPtr, value, PSP_TIME_SECONDS_IN_YEAR * 1000000L); } @HLEUnimplemented @HLEFunction(nid = 0xC663B3B9, version = 150) public int sceRtcFormatRFC2822() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x7DE6711B, version = 150) public int sceRtcFormatRFC2822LocalTime() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x0498FB3C, version = 150) public int sceRtcFormatRFC3339() { return 0; } @HLEFunction(nid = 0x27F98543, version = 150) public int sceRtcFormatRFC3339LocalTime(TPointer resultString, TPointer64 srcPtr) { Date date = getDateFromTick(srcPtr.getValue()); String result = formatRFC3339(date); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcFormatRFC3339LocalTime src=%d, returning '%s'", srcPtr.getValue(), result)); } resultString.setStringZ(result); return 0; } @HLEUnimplemented @HLEFunction(nid = 0xDFBC5F16, version = 150) public int sceRtcParseDateTime() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x28E1E988, version = 150) public int sceRtcParseRFC3339() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x7D1FBED3, version = 150) public int sceRtcSetAlarmTick(@CanBeNull TPointer64 srcPtr) { if (log.isDebugEnabled() && srcPtr.isNotNull()) { log.debug(String.format("sceRtcSetAlarmTick src=0x%X", srcPtr.getValue())); } return 0; } @HLEFunction(nid = 0x203CEB0D, version = 200) public int sceRtcGetLastReincarnatedTime(TPointer64 tickAddr) { // Returns the last tick that was saved upon a battery shutdown. // Just return our current tick, since there's no need to mimick such behavior. tickAddr.setValue(hleGetCurrentTick()); return 0; } @HLEFunction(nid = 0x62685E98, version = 200) public int sceRtcGetLastAdjustedTime(TPointer64 tickAddr) { // Returns the last time that was manually set by the user. // Just return our current tick, since there's no need to mimick such behavior. tickAddr.setValue(hleGetCurrentTick()); return 0; } @HLEFunction(nid = 0x1909C99B, version = 200) public int sceRtcSetTime64_t(TPointer dateAddr, long time) { ScePspDateTime dateTime = ScePspDateTime.fromUnixTime(time); dateTime.write(dateAddr); return 0; } @HLEFunction(nid = 0xE1C93E47, version = 200) public int sceRtcGetTime64_t(ScePspDateTime dateTime, TPointer64 timeAddr) { Calendar cal = Calendar.getInstance(); cal.set(dateTime.year, dateTime.month - 1, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second); long unixtime = cal.getTime().getTime() / 1000L; if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetTime64_t psptime=%s returning unixtime=%d", dateTime, unixtime)); } timeAddr.setValue(unixtime); return 0; } @HLEUnimplemented @HLEFunction(nid = 0xFB3B18CD, version = 271) public int sceRtcRegisterCallback(int callbackId) { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x6A676D2D, version = 271) public int sceRtcUnregisterCallback(int callbackId) { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xF5FCC995 , version = 150) public int sceRtcGetCurrentNetworkTick(TPointer64 networkTick) { networkTick.setValue(hleGetCurrentTick()); if (log.isDebugEnabled()) { log.debug(String.format("sceRtcGetCurrentNetworkTick returning %d", networkTick.getValue())); } return 0; } @HLEUnimplemented @HLEFunction(nid = 0xC2DDBEB5, version = 150) public int sceRtcGetAlarmTick(TPointer64 alarmTick) { alarmTick.setValue(0L); return 0; } @HLEUnimplemented @HLEFunction(nid = 0xE09880CF, version = 660) public int sceRtcSetAlarmTick_660(@CanBeNull TPointer64 srcPtr) { return sceRtcSetAlarmTick(srcPtr); } @HLEUnimplemented @HLEFunction(nid = 0xCEEF238F, version = 150) public int sceRtcGetCurrentSecureTick(TPointer64 currentTick) { return sceRtcGetCurrentTick(currentTick); } @HLEUnimplemented @HLEFunction(nid = 0x759937C5, version = 150) public int sceRtcSetConf(int unknown1, int unknown2, int unknown3, int unknown4) { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xDFF30673, version = 660) public int sceRtcSetConf_660(int unknown1, int unknown2, int unknown3, int unknown4) { return sceRtcSetConf(unknown1, unknown2, unknown3, unknown4); } @HLEUnimplemented @HLEFunction(nid = 0x508BA64B, version = 150) public int sceRtc_508BA64B(@CanBeNull @BufferInfo(usage=Usage.in) TPointer64 unknown) { return 0; } }