diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/librtc.c | 174 | ||||
| -rw-r--r-- | src/rtc_util.c | 335 | ||||
| -rw-r--r-- | src/string_util.c | 7 | 
3 files changed, 423 insertions, 93 deletions
| diff --git a/src/librtc.c b/src/librtc.c index a64c5af89..b87862a8f 100644 --- a/src/librtc.c +++ b/src/librtc.c @@ -1,29 +1,22 @@ -#include "global.h" - -struct RtcInfo -{ -    u8 year; -    u8 month; -    u8 dayOfMonth; -    u8 dayOfWeek; -    u8 hour; -    u8 minute; -    u8 second; -    u8 control; -    u8 unknown1; -    u8 unknown2; -}; - -#define OFFSET_YEAR         offsetof(struct RtcInfo, year) -#define OFFSET_MONTH        offsetof(struct RtcInfo, month) -#define OFFSET_DAY_OF_MONTH offsetof(struct RtcInfo, dayOfMonth) -#define OFFSET_DAY_OF_WEEK  offsetof(struct RtcInfo, dayOfWeek) -#define OFFSET_HOUR         offsetof(struct RtcInfo, hour) -#define OFFSET_MINUTE       offsetof(struct RtcInfo, minute) -#define OFFSET_SECOND       offsetof(struct RtcInfo, second) -#define OFFSET_CONTROL      offsetof(struct RtcInfo, control) -#define OFFSET_UNKNOWN1     offsetof(struct RtcInfo, unknown1) -#define OFFSET_UNKNOWN2     offsetof(struct RtcInfo, unknown2) +#include "gba/gba.h" +#include "rtc.h" + +#define RTC_CTRL_UNK1          0x02 // unknown +#define RTC_CTRL_IRQ_ENABLE    0x08 // per-minute IRQ enable +#define RTC_CTRL_UNK2          0x20 // unknown +#define RTC_CTRL_24HOUR        0x40 // 0: 12-hour mode, 1: 24-hour mode +#define RTC_CTRL_POWER_FAILURE 0x80 // power failure occurred + +#define OFFSET_YEAR        offsetof(struct RtcInfo, year) +#define OFFSET_MONTH       offsetof(struct RtcInfo, month) +#define OFFSET_DAY         offsetof(struct RtcInfo, day) +#define OFFSET_DAY_OF_WEEK offsetof(struct RtcInfo, dayOfWeek) +#define OFFSET_HOUR        offsetof(struct RtcInfo, hour) +#define OFFSET_MINUTE      offsetof(struct RtcInfo, minute) +#define OFFSET_SECOND      offsetof(struct RtcInfo, second) +#define OFFSET_CONTROL     offsetof(struct RtcInfo, control) +#define OFFSET_UNKNOWN1    offsetof(struct RtcInfo, unknown1) +#define OFFSET_UNKNOWN2    offsetof(struct RtcInfo, unknown2)  #define RTC_BUF(info, index) (*((u8 *)(info) + (index))) @@ -33,57 +26,56 @@ struct RtcInfo  #define RTC_TIME_BUF(info, index) (*((u8 *)(info) + OFFSET_HOUR + (index)))  #define RTC_TIME_BUF_LEN (OFFSET_SECOND - OFFSET_HOUR + 1) +#define RTC_CMD_RESET       0x60 +#define RTC_CMD_WR_CONTROL  0x62 +#define RTC_CMD_RD_CONTROL  0x63 +#define RTC_CMD_WR_DATETIME 0x64 +#define RTC_CMD_RD_DATETIME 0x65 +#define RTC_CMD_WR_TIME     0x66 +#define RTC_CMD_RD_TIME     0x67 +#define RTC_CMD_WR_UNKNOWN  0x68 +  extern vu16 GPIOPortData;  extern vu16 GPIOPortDirection; -extern vu16 GPIOPortReadWrite; +extern vu16 GPIOPortReadEnable;  extern bool8 gRtcLocked; -void RTC_SetReadWrite(); -void RTC_SetReadOnly(); -u8 RTC_Init(); -bool8 RTC_Reset(); -bool8 RTC_GetControlReg(struct RtcInfo *rtc); -bool8 RTC_SetControlReg(struct RtcInfo *rtc); -bool8 RTC_GetDateTime(struct RtcInfo *rtc); -bool8 RTC_SetDateTime(struct RtcInfo *rtc); -bool8 RTC_GetTime(struct RtcInfo *rtc); -bool8 RTC_SetTime(struct RtcInfo *rtc); -bool8 RTC_SetUnknownData(struct RtcInfo *rtc); -s32 RTC_WriteByte(u8 value); -s32 RTC_WriteByteReversed(u8 value); -u8 RTC_ReadByte(); -void RTC_SetReadWriteInternal(); -void RTC_SetReadOnlyInternal(); - -void RTC_SetReadWrite() +s32 RTC_WriteCommand(u8 value); +s32 RTC_WriteData(u8 value); +u8 RTC_ReadData(); +void RTC_EnableGpioPortRead(); +void RTC_DisableGpioPortRead(); + +void RTC_Unprotect()  { -    RTC_SetReadWriteInternal(); +    RTC_EnableGpioPortRead();      gRtcLocked = FALSE;  } -void RTC_SetReadOnly() +void RTC_Protect()  { -    RTC_SetReadOnlyInternal(); +    RTC_DisableGpioPortRead();      gRtcLocked = TRUE;  } -u8 RTC_Init() +u8 RTC_Probe()  { -    u8 v2; +    u8 errorCode;      struct RtcInfo rtc; -    if (!RTC_GetControlReg(&rtc)) +    if (!RTC_GetControl(&rtc))          return 0; -    v2 = 0; +    errorCode = 0; -    if ((rtc.control & 0xC0) == 0x80 || !(rtc.control & 0xC0)) +    if ((rtc.control & (RTC_INFO_CTRL_POWER_FAILURE | RTC_INFO_CTRL_24HOUR)) == RTC_INFO_CTRL_POWER_FAILURE +     || (rtc.control & (RTC_INFO_CTRL_POWER_FAILURE | RTC_INFO_CTRL_24HOUR)) == 0)      {          if (!RTC_Reset())              return 0; -        v2++; +        errorCode++;      }      RTC_GetTime(&rtc); @@ -91,12 +83,12 @@ u8 RTC_Init()      if (rtc.second & 0x80)      {          if (!RTC_Reset()) -            return (v2 << 4) & 0xF0; +            return (errorCode << 4) & 0xF0; -        v2++; +        errorCode++;      } -    return (v2 << 4) | 1; +    return (errorCode << 4) | 1;  }  bool8 RTC_Reset() @@ -114,21 +106,21 @@ bool8 RTC_Reset()      GPIOPortDirection = 7; -    RTC_WriteByte(0x60); +    RTC_WriteCommand(RTC_CMD_RESET);      GPIOPortData = 1;      GPIOPortData = 1;      gRtcLocked = FALSE; -    rtc.control = 0x40; +    rtc.control = RTC_INFO_CTRL_24HOUR; -    result = RTC_SetControlReg(&rtc); +    result = RTC_SetControl(&rtc);      return result;  } -bool8 RTC_GetControlReg(struct RtcInfo *rtc) +bool8 RTC_GetControl(struct RtcInfo *rtc)  {      u8 controlData; @@ -142,13 +134,16 @@ bool8 RTC_GetControlReg(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x63); +    RTC_WriteCommand(RTC_CMD_RD_CONTROL);      GPIOPortDirection = 5; -    controlData = RTC_ReadByte(); +    controlData = RTC_ReadData(); -    rtc->control = (controlData & 0xC0) | ((controlData & 0x20) >> 3) | ((controlData & 8) >> 2) | ((controlData & 2) >> 1); +    rtc->control = (controlData & (RTC_CTRL_POWER_FAILURE | RTC_CTRL_24HOUR)) +                 | ((controlData & RTC_CTRL_UNK2) >> 3) +                 | ((controlData & RTC_CTRL_IRQ_ENABLE) >> 2) +                 | ((controlData & RTC_CTRL_UNK1) >> 1);      GPIOPortData = 1;      GPIOPortData = 1; @@ -158,7 +153,7 @@ bool8 RTC_GetControlReg(struct RtcInfo *rtc)      return TRUE;  } -bool8 RTC_SetControlReg(struct RtcInfo *rtc) +bool8 RTC_SetControl(struct RtcInfo *rtc)  {      u8 controlData; @@ -170,13 +165,16 @@ bool8 RTC_SetControlReg(struct RtcInfo *rtc)      GPIOPortData = 1;      GPIOPortData = 5; -    controlData = ((rtc->control & 4) << 3) | (1 << 6) | ((rtc->control & 2) << 2) | ((rtc->control & 1) << 1); +    controlData = RTC_CTRL_24HOUR +                | ((rtc->control & RTC_INFO_CTRL_UNK2) << 3) +                | ((rtc->control & RTC_INFO_CTRL_IRQ_ENABLE) << 2) +                | ((rtc->control & RTC_INFO_CTRL_UNK1) << 1);      GPIOPortDirection = 7; -    RTC_WriteByte(0x62); +    RTC_WriteCommand(RTC_CMD_WR_CONTROL); -    RTC_WriteByteReversed(controlData); +    RTC_WriteData(controlData);      GPIOPortData = 1;      GPIOPortData = 1; @@ -200,12 +198,12 @@ bool8 RTC_GetDateTime(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x65); +    RTC_WriteCommand(RTC_CMD_RD_DATETIME);      GPIOPortDirection = 5;      for (i = 0; i < RTC_DATETIME_BUF_LEN; i++) -        RTC_DATETIME_BUF(rtc, i) = RTC_ReadByte(); +        RTC_DATETIME_BUF(rtc, i) = RTC_ReadData();      RTC_BUF(rtc, OFFSET_HOUR) &= 0x7F; @@ -231,10 +229,10 @@ bool8 RTC_SetDateTime(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x64); +    RTC_WriteCommand(RTC_CMD_WR_DATETIME);      for (i = 0; i < RTC_DATETIME_BUF_LEN; i++) -        RTC_WriteByteReversed(RTC_DATETIME_BUF(rtc, i)); +        RTC_WriteData(RTC_DATETIME_BUF(rtc, i));      GPIOPortData = 1;      GPIOPortData = 1; @@ -258,12 +256,12 @@ bool8 RTC_GetTime(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x67); +    RTC_WriteCommand(RTC_CMD_RD_TIME);      GPIOPortDirection = 5;      for (i = 0; i < RTC_TIME_BUF_LEN; i++) -        RTC_TIME_BUF(rtc, i) = RTC_ReadByte(); +        RTC_TIME_BUF(rtc, i) = RTC_ReadData();      RTC_BUF(rtc, OFFSET_HOUR) &= 0x7F; @@ -289,10 +287,10 @@ bool8 RTC_SetTime(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x66); +    RTC_WriteCommand(RTC_CMD_WR_TIME);      for (i = 0; i < RTC_TIME_BUF_LEN; i++) -        RTC_WriteByteReversed(RTC_TIME_BUF(rtc, i)); +        RTC_WriteData(RTC_TIME_BUF(rtc, i));      GPIOPortData = 1;      GPIOPortData = 1; @@ -312,9 +310,13 @@ bool8 RTC_SetUnknownData(struct RtcInfo *rtc)      gRtcLocked = TRUE; +    // unknown1 appears to be a BCD number in the range 0-11, +    // so it may be an hour in 12-hour format. +    // Months are normally 1-12. +      a[0] = ((rtc->unknown1 & 0xF) + 10 * ((rtc->unknown1 >> 4) & 0xF)); -    if (a[0] <= 0xB) +    if (a[0] < 12)          a[0] = rtc->unknown1;      else          a[0] = rtc->unknown1 | 0x80; @@ -326,10 +328,10 @@ bool8 RTC_SetUnknownData(struct RtcInfo *rtc)      GPIOPortDirection = 7; -    RTC_WriteByte(0x68); +    RTC_WriteCommand(RTC_CMD_WR_UNKNOWN);      for (i = 0; i < 2; i++) -        RTC_WriteByteReversed(a[i]); +        RTC_WriteData(a[i]);      GPIOPortData = 1;      GPIOPortData = 1; @@ -339,7 +341,7 @@ bool8 RTC_SetUnknownData(struct RtcInfo *rtc)      return TRUE;  } -s32 RTC_WriteByte(u8 value) +s32 RTC_WriteCommand(u8 value)  {      u8 i;      u8 temp; @@ -356,7 +358,7 @@ s32 RTC_WriteByte(u8 value)      // control reaches end of non-void function  } -s32 RTC_WriteByteReversed(u8 value) +s32 RTC_WriteData(u8 value)  {      u8 i;      u8 temp; @@ -373,7 +375,7 @@ s32 RTC_WriteByteReversed(u8 value)      // control reaches end of non-void function  } -u8 RTC_ReadByte() +u8 RTC_ReadData()  {      u8 i;      u8 temp; @@ -395,12 +397,12 @@ u8 RTC_ReadByte()      return value;  } -void RTC_SetReadWriteInternal() +void RTC_EnableGpioPortRead()  { -    GPIOPortReadWrite = 1; +    GPIOPortReadEnable = 1;  } -void RTC_SetReadOnlyInternal() +void RTC_DisableGpioPortRead()  { -    GPIOPortReadWrite = 0; +    GPIOPortReadEnable = 0;  } diff --git a/src/rtc_util.c b/src/rtc_util.c new file mode 100644 index 000000000..b279ec753 --- /dev/null +++ b/src/rtc_util.c @@ -0,0 +1,335 @@ +#include "global.h" +#include "rtc.h" +#include "rtc_util.h" +#include "string_util.h" + +extern const struct RtcInfo gDefaultRtcInfo; +extern const s32 gNumDaysInMonths[]; + +extern u16 gRtcErrorFlags; +extern struct RtcInfo gRtcInfo; +extern u8 gRtcProbeResult; +extern u16 gRtcSavedIme; + +extern struct Time gLocalTime; + +void RtcDisableInterrupts(); +void RtcRestoreInterrupts(); +u32 ConvertBcdToBinary(u8 bcd); +bool8 IsLeapYear(u8 year); +u16 ConvertDateToDayCount(u8 year, u8 month, u8 day); +u16 RtcGetDayCount(struct RtcInfo *rtc); +void RtcGetInfo(struct RtcInfo *rtc); +void RtcGetDateTime(struct RtcInfo *rtc); +void RtcGetControl(struct RtcInfo *rtc); +void RtcGetRawInfo(struct RtcInfo *rtc); +u16 RtcCheckInfo(struct RtcInfo *rtc); +void RtcCalcTimeDifference(struct RtcInfo *rtc, struct Time *result, struct Time *t); + +void RtcDisableInterrupts() +{ +    gRtcSavedIme = REG_IME; +    REG_IME = 0; +} + +void RtcRestoreInterrupts() +{ +    REG_IME = gRtcSavedIme; +} + +u32 ConvertBcdToBinary(u8 bcd) +{ +    if (bcd > 0x9F) +        return 0xFF; + +    if ((bcd & 0xF) <= 9) +        return (10 * ((bcd >> 4) & 0xF)) + (bcd & 0xF); +    else +        return 0xFF; +} + +bool8 IsLeapYear(u8 year) +{ +    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) +        return TRUE; + +    return FALSE; +} + +u16 ConvertDateToDayCount(u8 year, u8 month, u8 day) +{ +    s32 i; +    u16 dayCount = 0; + +    for (i = year - 1; i > 0; i--) +    { +        dayCount += 365; + +        if (IsLeapYear(i) == TRUE) +            dayCount++; +    } + +    for (i = 0; i < month - 1; i++) +        dayCount += gNumDaysInMonths[i]; + +    if (month > MONTH_FEB && IsLeapYear(year) == TRUE) +        dayCount++; + +    dayCount += day; + +    return dayCount; +} + +u16 RtcGetDayCount(struct RtcInfo *rtc) +{ +    u8 year = ConvertBcdToBinary(rtc->year); +    u8 month = ConvertBcdToBinary(rtc->month); +    u8 day = ConvertBcdToBinary(rtc->day); +    return ConvertDateToDayCount(year, month, day); +} + +void RtcInit() +{ +    gRtcErrorFlags = 0; + +    RtcDisableInterrupts(); +    RTC_Unprotect(); +    gRtcProbeResult = RTC_Probe(); +    RtcRestoreInterrupts(); + +    if (!(gRtcProbeResult & 0xF)) +    { +        gRtcErrorFlags = 1; +        return; +    } + +    if (gRtcProbeResult & 0xF0) +        gRtcErrorFlags = 2; +    else +        gRtcErrorFlags = 0; + +    RtcGetRawInfo(&gRtcInfo); +    gRtcErrorFlags = RtcCheckInfo(&gRtcInfo); +} + +u16 RtcGetErrorFlags() +{ +    return gRtcErrorFlags; +} + +void RtcGetInfo(struct RtcInfo *rtc) +{ +    if (gRtcErrorFlags & 0xFF0) +        *rtc = gDefaultRtcInfo; +    else +        RtcGetRawInfo(rtc); +} + +void RtcGetDateTime(struct RtcInfo *rtc) +{ +    RtcDisableInterrupts(); +    RTC_GetDateTime(rtc); +    RtcRestoreInterrupts(); +} + +void RtcGetControl(struct RtcInfo *rtc) +{ +    RtcDisableInterrupts(); +    RTC_GetControl(rtc); +    RtcRestoreInterrupts(); +} + +void RtcGetRawInfo(struct RtcInfo *rtc) +{ +    RtcGetControl(rtc); +    RtcGetDateTime(rtc); +} + +u16 RtcCheckInfo(struct RtcInfo *rtc) +{ +    u16 errorFlags = 0; +    s32 year; +    s32 month; +    s32 value; + +    if (rtc->control & RTC_INFO_CTRL_POWER_FAILURE) +        errorFlags |= 0x20; + +    if (!(rtc->control & RTC_INFO_CTRL_24HOUR)) +        errorFlags |= 0x10; + +    year = ConvertBcdToBinary(rtc->year); + +    if (year == 0xFF) +        errorFlags |= 0x40; + +    month = ConvertBcdToBinary(rtc->month); + +    if (month == 0xFF || month == 0 || month > 12) +        errorFlags |= 0x80; + +    value = ConvertBcdToBinary(rtc->day); + +    if (value == 0xFF) +        errorFlags |= 0x100; + +    if (month == MONTH_FEB) +    { +        if (value > IsLeapYear(year) + gNumDaysInMonths[month - 1]) +            errorFlags |= 0x100; +    } +    else +    { +        if (value > gNumDaysInMonths[month - 1]) +            errorFlags |= 0x100; +    } + +    value = ConvertBcdToBinary(rtc->hour); + +    if (value > 24) +        errorFlags |= 0x200; + +    value = ConvertBcdToBinary(rtc->minute); + +    if (value > 60) +        errorFlags |= 0x400; + +    value = ConvertBcdToBinary(rtc->second); + +    if (value > 60) +        errorFlags |= 0x800; + +    return errorFlags; +} + +void RtcReset() +{ +    RtcDisableInterrupts(); +    RTC_Reset(); +    RtcRestoreInterrupts(); +} + +void FormatDecimalTime(u8 *dest, s32 hour, s32 minute, s32 second) +{ +    dest = ConvertIntToDecimalStringN(dest, hour, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_COLON; +    dest = ConvertIntToDecimalStringN(dest, minute, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_COLON; +    dest = ConvertIntToDecimalStringN(dest, second, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest = EOS; +} + +void FormatHexTime(u8 *dest, s32 hour, s32 minute, s32 second) +{ +    dest = ConvertIntToHexStringN(dest, hour, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_COLON; +    dest = ConvertIntToHexStringN(dest, minute, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_COLON; +    dest = ConvertIntToHexStringN(dest, second, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest = EOS; +} + +void FormatHexRtcTime(u8 *dest) +{ +    FormatHexTime(dest, gRtcInfo.hour, gRtcInfo.minute, gRtcInfo.second); +} + +void FormatDecimalDate(u8 *dest, s32 year, s32 month, s32 day) +{ +    dest = ConvertIntToDecimalStringN(dest, year, STR_CONV_MODE_LEADING_ZEROS, 4); +    *dest++ = CHAR_HYPHEN; +    dest = ConvertIntToDecimalStringN(dest, month, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_HYPHEN; +    dest = ConvertIntToDecimalStringN(dest, day, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest = EOS; +} + +void FormatHexDate(u8 *dest, s32 year, s32 month, s32 day) +{ +    dest = ConvertIntToHexStringN(dest, year, STR_CONV_MODE_LEADING_ZEROS, 4); +    *dest++ = CHAR_HYPHEN; +    dest = ConvertIntToHexStringN(dest, month, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest++ = CHAR_HYPHEN; +    dest = ConvertIntToHexStringN(dest, day, STR_CONV_MODE_LEADING_ZEROS, 2); +    *dest = EOS; +} + +void RtcCalcTimeDifference(struct RtcInfo *rtc, struct Time *result, struct Time *t) +{ +    u16 days = RtcGetDayCount(rtc); +    result->seconds = ConvertBcdToBinary(rtc->second) - t->seconds; +    result->minutes = ConvertBcdToBinary(rtc->minute) - t->minutes; +    result->hours = ConvertBcdToBinary(rtc->hour) - t->hours; +    result->days = days - t->days; + +    if (result->seconds < 0) +    { +        result->seconds += 60; +        --result->minutes; +    } + +    if (result->minutes < 0) +    { +        result->minutes += 60; +        --result->hours; +    } + +    if (result->hours < 0) +    { +        result->hours += 24; +        --result->days; +    } +} + +void RtcCalcLocalTime() +{ +    RtcGetInfo(&gRtcInfo); +    RtcCalcTimeDifference(&gRtcInfo, &gLocalTime, &gSaveBlock2.localTimeOffset); +} + +void RtcInitLocalTimeOffset(s32 hours, s32 minutes) +{ +    RtcCalcLocalTimeOffset(0, hours, minutes, 0); +} + +void RtcCalcLocalTimeOffset(s32 days, s32 hours, s32 minutes, s32 seconds) +{ +  gLocalTime.days = days; +  gLocalTime.hours = hours; +  gLocalTime.minutes = minutes; +  gLocalTime.seconds = seconds; +  RtcGetInfo(&gRtcInfo); +  RtcCalcTimeDifference(&gRtcInfo, &gSaveBlock2.localTimeOffset, &gLocalTime); +} + +void CalcTimeDifference(struct Time *result, struct Time *t1, struct Time *t2) +{ +    result->seconds = t2->seconds - t1->seconds; +    result->minutes = t2->minutes - t1->minutes; +    result->hours = t2->hours - t1->hours; +    result->days = t2->days - t1->days; + +    if (result->seconds < 0) +    { +        result->seconds += 60; +        --result->minutes; +    } + +    if (result->minutes < 0) +    { +        result->minutes += 60; +        --result->hours; +    } + +    if (result->hours < 0) +    { +        result->hours += 24; +        --result->days; +    } +} + +u32 RtcGetMinuteCount() +{ +    RtcGetInfo(&gRtcInfo); +    return (24 * 60) * RtcGetDayCount(&gRtcInfo) + 60 * gRtcInfo.hour + gRtcInfo.minute; +} diff --git a/src/string_util.c b/src/string_util.c index 221319ae3..a2d2d9cd2 100644 --- a/src/string_util.c +++ b/src/string_util.c @@ -1,13 +1,6 @@  #include "global.h"  #include "string_util.h" -#define CHAR_SPACE         0x00 -#define CHAR_QUESTION_MARK 0xAC - -#define EXT_CTRL_CODE_BEGIN 0xFC // extended control code -#define PLACEHOLDER_BEGIN   0xFD // string placeholder -#define EOS                 0xFF // end of string -  #define MAX_PLACEHOLDER_ID 0xD  typedef u8 *(*ExpandPlaceholderFunc)(); | 
