Compare commits

..

15 Commits

6 changed files with 421 additions and 183 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.dll
liblua
liblua
liblua.native

View File

@@ -2,11 +2,20 @@ liblua:
gcc -c lua/*.c
ar rcs liblua *.o
rm *.o
liblua-native:
gcc -c lua/*.c -march=native
ar rcs liblua.native *.o
rm *.o
build: liblua
gcc -O2 -shared -static -o luahost.dll plugin.c liblua -lgdi32 -luser32 -lshell32 \
gcc -O2 -shared -static -o luahost-x86.dll plugin.c liblua -lgdi32 -luser32 -lshell32 \
-Wl,--add-stdcall-alias \
-ffunction-sections -fdata-sections
install: build
build-native: liblua
gcc -O2 -shared -static -o luahost.dll plugin.c liblua.native -lgdi32 -luser32 -lshell32 -march=native \
-Wl,--add-stdcall-alias \
-ffunction-sections -fdata-sections
install: build-native
cp luahost.dll "/c/Program Files (x86)/RDS Spy/plugins"

View File

@@ -9,4 +9,11 @@ Aside from all that, this it loads and executes the script at "%LOCALAPPDATA%\RD
## Why?
Ask Jan Kolar why his funny little rds decoder doesn't have Enchanced RT (excluding the 0x6552 to ERT mapping of course)
Ask Jan Kolar why his funny little rds decoder doesn't have Enchanced RT (excluding the 0x6552 to ERT mapping of course)
# Scripts
Inside of the examples folder you have `basic.lua` which mostly replaced the `basic.dll` plugin in RDS Spy
Note that you can double click on the buttons, if you try it
Another note is that on menu 0 you have the PI and PS in a large font, why? democracy? or something like that ([forum post](https://pira.cz/forum/index.php?topic=1124.0))

View File

@@ -1,8 +1,5 @@
set_console_mode(true)
local last_pi = 0
local last_super_pi = 0
local ert_string = string.rep("_", 128)
local rt_a = string.rep("_", 64)
local rt_b = string.rep("_", 64)
@@ -19,8 +16,8 @@ local rta_display = ""
local rtb_display = ""
local lps_display = ""
local current_menu = 1
local menu_extended = false
local pty_rds = {
"None", "News", "Current Affairs",
@@ -458,57 +455,103 @@ local pi_ecc_to_country = {
}
local pi_coverage = {
"Local",
"International",
"National",
"Supra-regional",
"Regional 4",
"Regional 5",
"Regional 5",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional A",
"Regional B",
"Regional C",
"Regional D",
"Regional E",
"Local", "International", "National",
"Supra-regional", "Regional 4", "Regional 5",
"Regional 5", "Regional 7", "Regional 8",
"Regional 9", "Regional A", "Regional B",
"Regional C", "Regional D", "Regional E",
"Regional F",
}
local time_display_utc = "-"
local time_display_local = "-"
local time_display_offset = 0
local rtp_len1 = 0
local rtp_len2 = 0
local rtp_type1 = 0
local rtp_type2 = 0
local rtp_start1 = 0
local rtp_start2 = 0
local rtp_toggle = false
local rtp_running = false
local rtp_types = {
"DUMMY_CLASS",
"Item.Title", "Item.Album", "Item.Tracknumber",
"Item.Artist", "Item.Composition", "Item.Movement",
"Item.Conductor", "Item.Composer", "Item.Band",
"Item.Comment", "Item.Genre", "Info.News",
"Info.News.Local", "Info.Stockmarket", "Info.Sport",
"Info.Lottery", "Info.Horoscope", "Info.Daily_Diversion",
"Info.Health", "Info.Event", "Info.Scene",
"Info.Cinema", "Info.Tv", "Info.Date_time",
"Info.Weather", "Info.Traffic", "Info.Alarm",
"Info.Advertisement", "Info.URL", "Info.Other",
"Stationame.Short", "Stationname.Long", "Program.Now",
"Program.Next", "Program.Part", "Program.Host",
"Program.Editorial_Staff", "Program.Frequency", "Program.Homepage",
"Program.Subchannel", "Phone.Hotline", "Phone.Studio",
"Phone.Other", "SMS.Studio", "SMS.Other",
"Email.Hotline", "Email.Studio", "Email.Other",
"MMS.Other", "Chat", "Chat.Center",
"Vote.Question", "Vote.Center", "Rfu_1",
"Rfu_2", "Private_1", "Private_2",
"Private_3", "Place", "Appointment",
"Identifier", "Purchase", "GET_DATA"
}
local expected_afs = 0
local afs = {}
local state_af_am_follows = false
local last_render_hash = 0
local function crc(data)
local crc = 0xFF
local sum = 0xFF
for i = 1, #data do
crc = crc ~ data:byte(i)
sum = sum ~ data:byte(i)
for _ = 1, 8 do
if (crc & 0x80) ~= 0 then crc = (crc << 1) ~ 0x7
else crc = crc << 1 end
crc = crc & 0xFF
if (sum & 0x80) ~= 0 then sum = (sum << 1) ~ 0x7
else sum = sum << 1 end
sum = sum & 0xFF
end
end
return crc
return sum
end
function render_menu()
out = string.format("Menu %d\r\n------\r\n", current_menu)
local function render_menu()
local out = string.format("Menu %d%s\r\n------\r\n", current_menu, menu_extended and " (extended)" or "")
set_font_size(26)
if current_menu == 1 then
if current_menu == 1 and not menu_extended then
set_font_size(72) -- largest as i can do, this is directly from the public's wants (https://pira.cz/forum/index.php?topic=1124.0)
out = out .. string.format("PI: %X (SPI: %X)\r\n", last_pi, last_super_pi)
out = out .. string.format("PI: %s\r\n", db.read_value("PI") or "----")
out = out .. string.format("PS: %s", db.read_value("PS") or "--------")
elseif current_menu == 2 then
elseif current_menu == 1 and menu_extended then
out = out .. string.format("AF: (%d)\r\n\t", expected_afs)
for _, freq in ipairs(afs) do
out = out .. string.format("%.1f\r\n\t", freq)
end
elseif current_menu == 2 and not menu_extended then
out = out .. string.format("PTY: %d (%s / %s)\r\n", pty, pty_rds[pty+1], pty_rbds[pty+1])
out = out .. string.format("TP %s | TA %s | DPTY %s\r\n", tp and "+" or "-", ta and "+" or "-", dpty and "+" or "-")
out = out .. string.format("PTYN: %s\r\n\r\n", ptyn)
out = out .. string.format("RT[A]: %s%s\r\n", last_rt and ">" or " ", rta_display)
out = out .. string.format("RT[B]: %s%s\r\n\r\n", (not last_rt) and ">" or " ", rtb_display)
elseif current_menu == 2 and menu_extended then
local current_rt = rta_display
if not last_rt then current_rt = rtb_display end
out = out .. string.format("RTP\r\n\tRUNNING %s | TOGGLE %s\r\n\t", rtp_running and "+" or "-", rtp_toggle and "+" or "-")
if rtp_running then
out = out .. string.format("%s - %s\r\n\t", rtp_types[rtp_type1+1], current_rt:sub(rtp_start1+1, rtp_start1+rtp_len1+1))
out = out .. string.format("%s - %s\r\n\t", rtp_types[rtp_type2+1], current_rt:sub(rtp_start2+1, rtp_start2+rtp_len2+1))
else out = out .. "-\r\n\t-\r\n\t" end
out = out .. string.format("RAW %d,%d,%d,%d,%d,%d\r\n", rtp_type1, rtp_start1, rtp_len1, rtp_type2, rtp_start2, rtp_len2)
elseif current_menu == 3 then
local country_id = (last_pi & 0xF000) >> 12
local coverage_id = (last_pi & 0xF00) >> 8
local pi_code = tonumber(db.read_value("PI") or "0000", 16)
local country_id = (pi_code & 0xF000) >> 12
local coverage_id = (pi_code & 0xF00) >> 8
out = out .. string.format("LPS: %s\r\n\r\n", lps_display)
@@ -528,6 +571,13 @@ function render_menu()
oda_string = oda_string .. string.format("%d%s - %04X | ", grp, ver_char, data.aid)
end
out = out .. string.format("ODA: %s\r\n", oda_string:sub(1, #oda_string-2))
elseif current_menu == 4 then
if time_display_offset > 2 then out = out .. string.format("RDS-System time offset: %d seconds\r\n", time_display_offset)
else out = out .. string.format("RDS-System time offset: ~0\r\n") end
out = out .. string.format("Local time: %s\r\n", time_display_local)
out = out .. string.format("UTC time: %s\r\n", time_display_utc)
elseif current_menu == event_count and menu_extended then
out = out .. "Toggled windows stickness"
end
local hash = crc(out)
@@ -538,7 +588,13 @@ function render_menu()
end
function event(event)
current_menu = event
if event > event_count then
menu_extended = true
if event-event_count == event_count then set_window_stick(not get_window_stick()) end
else
menu_extended = false
current_menu = event
end
render_menu()
end
@@ -560,13 +616,22 @@ function command(cmd, param)
db.add_value("PTY.Name", string.format("%s / %s", pty_rds[pty+1], pty_rbds[pty+1]))
db.add_value("ECC", string.format("%X", ecc))
db.add_value("LPS", lps_display)
db.add_value("LocalTime", time_display_local)
db.add_value("UTCTime", time_display_utc)
-- TODO time error
db.add_value("RTP.RawData", string.format("%d,%d,%d,%d,%d,%d\r\n", rtp_type1, rtp_start1, rtp_len1, rtp_type2, rtp_start2, rtp_len2))
local current_rt = rta_display
if not last_rt then current_rt = rtb_display end
db.add_value("RTP.Tag1", current_rt:sub(rtp_start1+1, rtp_start1+rtp_len1+1))
db.add_value("RTP.Tag2", current_rt:sub(rtp_start2+1, rtp_start2+rtp_len2+1))
db.add_value("RTP.Tag1Type", rtp_types[rtp_type1+1])
db.add_value("RTP.Tag2Type", rtp_types[rtp_type2+1])
elseif cmd:lower() == "resetdata" then
ert_string = string.rep("_", 128)
rt_a = string.rep("_", 64)
rt_b = string.rep("_", 64)
odas = {}
last_pi = 0
last_super_pi = 0
pty = 0
tp = false
ta = false
@@ -575,8 +640,20 @@ function command(cmd, param)
ptyn_toggle = false
ecc = 0
lps = string.rep("_", 32)
elseif cmd:lower() == "superpi" then
if #param <= 4 then last_super_pi = tonumber(param, 16) end
time_display_utc = "-"
time_display_local = "-"
time_display_offset = 0
rtp_running = false
rtp_toggle = false
rtp_type1 = 0
rtp_start1 = 0
rtp_len1 = 0
rtp_type2 = 0
rtp_start2 = 0
rtp_len2 = 0
expected_afs = 0
afs = {}
state_af_am_follows = false
end
end
@@ -587,12 +664,89 @@ local function findODAByAID(t, targetAID)
return nil, nil
end
function group(stream, b_corr, a, b, c, d)
local function dateToEpoch(year, month, day)
local daysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
local function isLeapYear(y)
return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0)
end
if year < 2025 or month < 1 or month > 12 or day < 1 or day > 31 then error(string.format("%d %d %d", year, month, day), 2) end
local totalDays = 0
for y = 2025, year - 1 do
totalDays = totalDays + (isLeapYear(y) and 366 or 365)
end
for m = 1, month - 1 do
local days = daysInMonth[m]
if m == 2 and isLeapYear(year) then days = 29 end
totalDays = totalDays + days
end
totalDays = totalDays + (day - 1)
return totalDays * 86400
end
local function epochToDate(epochSeconds)
local totalDays = math.floor(epochSeconds / 86400)
local remainingSeconds = epochSeconds % 86400
local hour = math.floor(remainingSeconds / 3600)
local minute = math.floor((remainingSeconds % 3600) / 60)
local year = 2025
local function isLeapYear(y)
return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0)
end
while true do
local daysInYear = isLeapYear(year) and 366 or 365
if totalDays < daysInYear then break end
totalDays = totalDays - daysInYear
year = year + 1
end
local daysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
if isLeapYear(year) then daysInMonth[2] = 29 end
local month = 1
for m = 1, 12 do
if totalDays < daysInMonth[m] then break end
totalDays = totalDays - daysInMonth[m]
month = month + 1
end
return year, month, totalDays + 1, hour, minute
end
local function getDayOfWeek(year, month, day)
if month < 3 then
month = month + 12
year = year - 1
end
local K = year % 100
local J = math.floor(year / 100)
local h = (day + math.floor(13 * (month + 1) / 5) + K + math.floor(K / 4) + math.floor(J / 4) - 2 * J) % 7
return ((h + 5) % 7) + 1
end
---@param stream integer
---@param b_corr boolean
---@param a integer
---@param b integer
---@param c integer
---@param d integer
---@param time timetable
function group(stream, b_corr, a, b, c, d, time)
if stream ~= 0 and a ~= 0 then return
elseif stream ~= 0 and not db.load_boolean("rdsspy.ini", "General", "Tunnelling", false) then return end
if a > 0 then last_pi = a end
render_menu()
if b_corr or b < 0 then return end
@@ -610,8 +764,10 @@ function group(stream, b_corr, a, b, c, d)
if odas[target_group] == nil then odas[target_group] = { aid = d, version = (b & 1) } end
else
local ert_grp, ert_ver = findODAByAID(odas, 0x6552)
local rtp_grp, rtp_ver = findODAByAID(odas, 0x4BD7)
if ert_grp and group_type == ert_grp and group_version == ert_ver then
if d < 0 or c < 0 then return end
local ert_state = b & 0x1f
local new_chars = string.char((c >> 8) & 0xff) .. string.char(c & 0xff) .. string.char((d >> 8) & 0xff) .. string.char(d & 0xff)
local start_pos = (ert_state * 4) + 1
@@ -620,6 +776,16 @@ function group(stream, b_corr, a, b, c, d)
local carriage = ert_string:find("\r", 1, true)
if carriage then ert_display = ert_string:sub(1, carriage - 1)
else ert_display = ert_string:gsub("%s+$", "") end
elseif rtp_grp and group_type == rtp_grp and group_version == rtp_ver then
if d < 0 or c < 0 then return end
rtp_toggle = ((b & 0x10) >> 4) ~= 0
rtp_running = ((b & 8) >> 3) ~= 0
rtp_type1 = (b & 7) << 3 | (c & 0xe000) >> 13
rtp_start1 = (c & 0x1f80) >> 7
rtp_len1 = (c >> 1) & 63
rtp_type2 = ((c & 1) << 6) | (d >> 11) & 63
rtp_start2 = (d >> 5) & 63
rtp_len2 = d & 0x1f
else
if group_type == 0 then
ta = ((b & 0x10) >> 4) ~= 0
@@ -627,6 +793,45 @@ function group(stream, b_corr, a, b, c, d)
local segment = b & 0x3
if di_bit and segment == 0 then dpty = true
elseif segment == 0 then dpty = false end
if c < 0 or group_version == 1 then return end
local af_high = c >> 8
local af_low = c & 0xff
if af_high >= 224 and af_high <= (224+25) then
local run_new = false
expected_afs = af_high - 224
if af_low ~= 205 and af_low ~= 250 and #afs ~= 0 and afs[1] ~= (af_low+875)/10 then run_new = true end
if #afs ~= expected_afs or run_new then
afs = {}
state_af_am_follows = false
if af_low ~= 205 and af_low ~= 250 then table.insert(afs, (af_low+875)/10)
elseif af_low == 250 then state_af_am_follows = true end
end
elseif #afs ~= expected_afs then
if not (af_high == 250 or state_af_am_follows) then
local freq = (af_high+875)/10
local found
for _, value in ipairs(afs) do
if value == freq then
found = true
break
end
end
if not found then table.insert(afs, freq) end
end
if state_af_am_follows then state_af_am_follows = false end
if not (af_high == 250 or af_low == 205 or af_low == 250) then
local freq = (af_low+875)/10
local found
for _, value in ipairs(afs) do
if value == freq then
found = true
break
end
end
if not found then table.insert(afs, freq) end
end
if af_low == 250 then state_af_am_follows = true end
end
elseif group_type == 1 and group_version == 0 then
if d < 0 then return end
local variant = (c & 0x7000) >> 12
@@ -696,6 +901,35 @@ function group(stream, b_corr, a, b, c, d)
local carriage = lps:find("\r", 1, true)
if carriage then lps_display = lps:sub(1, carriage - 1)
else lps_display = lps:gsub("%s+$", "") end
elseif group_type == 4 and group_version == 0 then
if d < 0 or c < 0 then return end
local mjd = ((b & 7) << 15) | c >> 1
local year = math.floor((mjd - 15078.2) / 365.25)
local month = math.floor((mjd - 14956.1 - math.floor(year * 365.25)) / 30.6001)
local day = mjd - 14956 - math.floor(year * 365.25) - math.floor(month * 30.6001)
local k = 0
if month == 14 or month == 15 then k = 1 end
year = year + 1900 + k
month = month - 1 - k * 12
local hour = (c & 1) << 4 | (d & 0xf000) >> 12
local minute = (d & 0xfc0) >> 6
local offset_sign = (d & 32) >> 5 -- 0 = +, i have no clue why
local offset = d & 31 -- 2 = hour, meaning one means 30 minutes of offset
local epoch = dateToEpoch(year, month, day) + (hour * 3600) + (minute * 60)
local utc_year, utc_month, utc_day, utc_hour, utc_minute = epochToDate(epoch)
local weekday_table = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
time_display_utc = string.format("%d/%02d/%02d (%s) - %02d:%02d", utc_year, utc_month, utc_day, weekday_table[getDayOfWeek(utc_year, utc_month, utc_day)], utc_hour, utc_minute)
if offset_sign == 0 then epoch = epoch + (offset*1800)
else epoch = epoch - (offset*1800) end
local systemepoch = dateToEpoch(time.year, time.month, time.day) + (time.hour * 3600) + (time.minute * 60) + time.second + (time.centisecond * 0.01)
time_display_offset = math.floor(math.abs(epoch - systemepoch))
local local_year, local_month, local_day, local_hour, local_minute = epochToDate(epoch)
time_display_local = string.format("%d/%02d/%02d (%s) - %02d:%02d", local_year, local_month, local_day, weekday_table[getDayOfWeek(local_year, local_month, local_day)], local_hour, local_minute)
end
end
end

208
plugin.c
View File

@@ -12,7 +12,7 @@ typedef struct {
uint8_t Month;
uint8_t Day;
uint8_t Hour;
uint8_t Minutes;
uint8_t Minute;
uint8_t Second;
uint8_t Centisecond;
uint16_t RFU;
@@ -80,7 +80,10 @@ static const unsigned char EBU[127] = {
0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE6, 0x9C, 0x9F, 0x00
};
static unsigned int console_mode = 0;
static unsigned short console_mode = 0;
static unsigned short stop_execution = 0;
static unsigned short sticky = 0;
static unsigned char workspaceFile[MAX_PATH] = "";
const char* int_to_string(int value) {
static char buffer[16];
@@ -113,7 +116,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
int offset = 0;
if(HIWORD(wParam) == BN_DOUBLECLICKED) offset = BUTTON_COUNT;
if (controlId == IDC_MAIN_BUTTON) InitLua();
if (controlId == IDC_MAIN_BUTTON && HIWORD(wParam) == BN_DOUBLECLICKED) InitLua();
else if (controlId > IDC_MAIN_BUTTON && controlId <= IDC_MAIN_BUTTON + BUTTON_COUNT) lua_event((controlId - IDC_MAIN_BUTTON) + offset);
}
break;
@@ -156,7 +159,7 @@ void CreatePluginWindow(HWND hOwner) {
HWND hButton = CreateWindowEx(
0, "BUTTON", "Reload",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_NOTIFY,
10, WINDOW_HEIGHT-62,
70, 30, hWnd,
(HMENU)IDC_MAIN_BUTTON, hInst, NULL
@@ -226,6 +229,17 @@ int lua_set_console_mode(lua_State* localL) {
return 0;
}
int lua_set_window_stick(lua_State* localL) {
if (!lua_isboolean(localL, 1)) return luaL_error(localL, "boolean expected, got %s", luaL_typename(localL, 1));
sticky = lua_toboolean(localL, 1);
return 0;
}
int lua_get_window_stick(lua_State* localL) {
lua_pushboolean(localL, sticky);
return 1;
}
int lua_set_font_size(lua_State* localL) {
int size = luaL_checkinteger(localL, 1);
@@ -363,106 +377,50 @@ int lua_CharConv(lua_State* localL) {
}
int lua_SaveString(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
const char* value = luaL_checkstring(localL, 4);
if (lua_isstring(localL, 1)) {
const char* filename = luaL_checkstring(localL, 1);
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
WritePrivateProfileStringA(section, key, value, fullPath);
}
return 0;
}
int lua_SaveInteger(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
int value = luaL_checkinteger(localL, 4);
char valueStr[32];
snprintf(valueStr, 32, "%d", value);
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
WritePrivateProfileStringA(section, key, valueStr, fullPath);
}
return 0;
}
int lua_SaveBoolean(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
int value = lua_toboolean(localL, 4);
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
WritePrivateProfileStringA(section, key, value ? "1" : "0", fullPath);
}
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
WritePrivateProfileStringA(section, key, value, fullPath);
}
} else if(lua_isnil(localL, 1)) WritePrivateProfileStringA(section, key, value, workspaceFile);
else luaL_typeerror(L, 1, lua_typename(L, LUA_TSTRING));
return 0;
}
int lua_LoadString(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
const char* defaultValue = luaL_optstring(localL, 4, "");
char buffer[1024];
char path[MAX_PATH];
if (lua_isstring(localL, 1)) {
const char* filename = luaL_checkstring(localL, 1);
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
GetPrivateProfileStringA(section, key, defaultValue, buffer, 1024, fullPath);
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
GetPrivateProfileStringA(section, key, defaultValue, buffer, 1024, fullPath);
lua_pushstring(localL, buffer);
} else lua_pushstring(localL, defaultValue);
return 1;
} else if(lua_isnil(localL, 1)) {
GetPrivateProfileStringA(section, key, defaultValue, buffer, 1024, workspaceFile);
lua_pushstring(localL, buffer);
} else lua_pushstring(localL, defaultValue);
return 1;
}
int lua_LoadInteger(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
int defaultValue = luaL_optinteger(localL, 4, 0);
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
int value = GetPrivateProfileIntA(section, key, defaultValue, fullPath);
lua_pushinteger(localL, value);
} else lua_pushinteger(localL, defaultValue);
return 1;
}
int lua_LoadBoolean(lua_State* localL) {
const char* filename = luaL_checkstring(localL, 1);
const char* section = luaL_checkstring(localL, 2);
const char* key = luaL_checkstring(localL, 3);
int defaultValue = lua_toboolean(localL, 4);
char buffer[16];
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) {
char fullPath[MAX_PATH];
snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\%s", path, filename);
GetPrivateProfileStringA(section, key, defaultValue ? "1" : "0", buffer, 16, fullPath);
lua_pushboolean(localL, atoi(buffer) != 0);
} else lua_pushboolean(localL, defaultValue);
return 1;
return 1;
} else luaL_typeerror(L, 1, lua_typename(L, LUA_TSTRING));
return 0;
}
void lua_call_command(const char* Cmd, const char* Param) {
if(stop_execution != 0) return;
lua_getglobal(L, "command");
if (lua_isfunction(L, -1)) {
@@ -473,11 +431,13 @@ void lua_call_command(const char* Cmd, const char* Param) {
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error: %s at '%s'\n", lua_tostring(L, -1), "command");
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
}
} else lua_pop(L, 1);
}
void lua_call_group() {
if(stop_execution != 0) return;
lua_getglobal(L, "group");
if (lua_isfunction(L, -1)) {
@@ -487,16 +447,35 @@ void lua_call_group() {
lua_pushinteger(L, Group.Blk2);
lua_pushinteger(L, Group.Blk3);
lua_pushinteger(L, Group.Blk4);
if (lua_pcall(L, 6, 0, 0) != LUA_OK) {
lua_newtable(L);
lua_pushinteger(L, Group.Year);
lua_setfield(L, -2, "year");
lua_pushinteger(L, Group.Month);
lua_setfield(L, -2, "month");
lua_pushinteger(L, Group.Day);
lua_setfield(L, -2, "day");
lua_pushinteger(L, Group.Hour);
lua_setfield(L, -2, "hour");
lua_pushinteger(L, Group.Minute);
lua_setfield(L, -2, "minute");
lua_pushinteger(L, Group.Second);
lua_setfield(L, -2, "second");
lua_pushinteger(L, Group.Centisecond);
lua_setfield(L, -2, "centisecond");
if (lua_pcall(L, 7, 0, 0) != LUA_OK) {
char msg_buffer[255];
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error: %s at '%s'\r\n", lua_tostring(L, -1), "group");
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
}
} else lua_pop(L, 1);
}
void lua_event(int event) {
if(stop_execution != 0) return;
lua_getglobal(L, "event");
if (lua_isfunction(L, -1)) {
@@ -506,18 +485,19 @@ void lua_event(int event) {
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error: %s at '%s'\r\n", lua_tostring(L, -1), "event");
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
}
} else lua_pop(L, 1);
}
__declspec(dllexport) void __stdcall RDSGroup(TRDSGroup* PRDSGroup) {
__declspec(dllexport) void WINAPI RDSGroup(TRDSGroup* PRDSGroup) {
if (PRDSGroup == NULL) return;
Group = *PRDSGroup;
lua_call_group();
}
__declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param) {
__declspec(dllexport) void WINAPI Command(const char* Cmd, const char* Param) {
if (Cmd == NULL) return;
if (_stricmp(Cmd, "EXIT") == 0) {
if (hWnd != NULL) {
@@ -528,10 +508,12 @@ __declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param)
lua_close(L);
L = NULL;
}
} else if (_stricmp(Cmd, "CONFIGURE") == 0) ShowWindow(hWnd, SW_SHOW);
else if (_stricmp(Cmd, "SHOW") == 0) ShowWindow(hWnd, SW_SHOW);
else if (_stricmp(Cmd, "MINIMIZE") == 0) ShowWindow(hWnd, SW_MINIMIZE);
else if (_stricmp(Cmd, "RESTORE") == 0) ShowWindow(hWnd, SW_RESTORE);
} else if (_stricmp(Cmd, "CONFIGURE") == 0 || _stricmp(Cmd, "SHOW") == 0 || _stricmp(Cmd, "RESTORE") == 0) ShowWindow(hWnd, SW_SHOW);
else if (_stricmp(Cmd, "MINIMIZE") == 0) ShowWindow(hWnd, SW_HIDE);
else if (_stricmp(Cmd, "SHOWHIDE") == 0) {
if(IsWindowVisible(hWnd)) ShowWindow(hWnd, SW_HIDE);
else ShowWindow(hWnd, SW_SHOW);
}
else if (_stricmp(Cmd, "OPENWORKSPACE") == 0) {
if(hWnd != NULL) {
int value = GetPrivateProfileIntA("luahost", "Visible", 0, Param);
@@ -541,6 +523,7 @@ __declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param)
int y = GetPrivateProfileIntA("luahost", "Top", 240, Param);
SetWindowPos(hWnd, NULL, x, y, 0, 0, SWP_NOSIZE);
}
sticky = GetPrivateProfileIntA("luahost", "Stick", 0, Param);
lua_call_command(Cmd, Param); // still call
} else if (_stricmp(Cmd, "SAVEWORKSPACE") == 0) {
if(hWnd != NULL) {
@@ -549,8 +532,11 @@ __declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param)
WritePrivateProfileStringA("luahost", "Left", int_to_string(rect.left), Param);
WritePrivateProfileStringA("luahost", "Top", int_to_string(rect.top), Param);
}
WritePrivateProfileStringA("luahost", "Stick", (sticky != 0) ? "1" : "0", Param);
WritePrivateProfileStringA("luahost", "Visible", IsWindowVisible(hWnd) ? "1" : "0", Param);
}
memcpy(workspaceFile, Param, MAX_PATH);
workspaceFile[MAX_PATH-1] = 0;
lua_call_command(Cmd, Param); // still call
} else if (_stricmp(Cmd, "LUASCRIPT") == 0) { // custom
char msg_buffer[255];
@@ -558,17 +544,29 @@ __declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param)
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error loading file: %s\r\n", lua_tostring(L, -1));
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
} else {
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
snprintf(msg_buffer, sizeof(msg_buffer), "Init error: %s\r\n", lua_tostring(L, -1));
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
}
}
} else if(_stricmp(Cmd, "MOVEX") == 0) {
if(sticky != 0) {
RECT rect; // get rect
if (GetWindowRect(hWnd, &rect)) SetWindowPos(hWnd, NULL, rect.left+atoi(Param), rect.top, 0, 0, SWP_NOSIZE);
}
} else if(_stricmp(Cmd, "MOVEY") == 0) {
if(sticky != 0) {
RECT rect; // get rect
if (GetWindowRect(hWnd, &rect)) SetWindowPos(hWnd, NULL, rect.left, rect.top+atoi(Param), 0, 0, SWP_NOSIZE);
}
} else lua_call_command(Cmd, Param);
}
__declspec(dllexport) const char* __stdcall PluginName(void) { return "Lua Host"; }
__declspec(dllexport) const char* WINAPI PluginName(void) { return "Lua Host"; }
void InitLua() {
if(L != NULL) {
@@ -582,6 +580,11 @@ void InitLua() {
lua_register(L, "log", lua_log);
lua_register(L, "set_console", lua_set_console);
lua_register(L, "set_console_mode", lua_set_console_mode);
lua_register(L, "set_window_stick", lua_set_window_stick);
lua_register(L, "get_window_stick", lua_get_window_stick);
lua_pushinteger(L, BUTTON_COUNT);
lua_setglobal(L, "event_count");
lua_newtable(L);
@@ -606,24 +609,13 @@ void InitLua() {
lua_pushcfunction(L, lua_SaveString);
lua_setfield(L, -2, "save_string");
lua_pushcfunction(L, lua_SaveInteger);
lua_setfield(L, -2, "save_integer");
lua_pushcfunction(L, lua_SaveBoolean);
lua_setfield(L, -2, "save_boolean");
lua_pushcfunction(L, lua_LoadString);
lua_setfield(L, -2, "load_string");
lua_pushcfunction(L, lua_LoadInteger);
lua_setfield(L, -2, "load_integer");
lua_pushcfunction(L, lua_LoadBoolean);
lua_setfield(L, -2, "load_boolean");
lua_setglobal(L, "db");
console_mode = 0;
stop_execution = 0;
char path[MAX_PATH];
char fullPath[MAX_PATH];
@@ -640,16 +632,18 @@ void InitLua() {
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error loading file: %s\r\n", lua_tostring(L, -1));
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
} else {
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error: %s\r\n", lua_tostring(L, -1));
AppendText(msg_buffer);
lua_pop(L, 1);
stop_execution = 1;
}
}
}
__declspec(dllexport) int __stdcall Initialize(HANDLE hHandle, TDB* DBPointer) {
__declspec(dllexport) int WINAPI Initialize(HANDLE hHandle, TDB* DBPointer) {
CreatePluginWindow(hHandle);
AppendText(LUA_COPYRIGHT);
AppendText("\r\n");

View File

@@ -1,11 +1,23 @@
---@meta
---@type integer
event_count = 0
---This function should be defined by the user in the script
---EXIT, CONFIGURE, SHOW, MINIMIZE, RESTORE commands are not sent to the script
---EXIT, CONFIGURE, SHOW, MINIMIZE, RESTORE, MOVEX, MOVEY commands are not sent to the script
---@param cmd string
---@param param string
function command(cmd, param) end
---@class timetable
---@field year integer
---@field month integer
---@field day integer
---@field hour integer
---@field minute integer
---@field second integer
---@field centisecond integer
---This function should be defined by the user in the script
---@param stream integer
---@param block_b_correction boolean
@@ -13,7 +25,8 @@ function command(cmd, param) end
---@param b integer
---@param c integer
---@param d integer
function group(stream, block_b_correction, a, b, c, d) end
---@param time timetable
function group(stream, block_b_correction, a, b, c, d, time) end
---This function should be defined by the user in the script
---@param event integer
@@ -38,6 +51,12 @@ function set_console(data) end
---@param mode boolean
function set_console_mode(mode) end
---Settings this on makes the window move with the main RDS Spy window regardless the console is
---@param stick boolean
function set_window_stick(stick) end
---@return boolean
function get_window_stick() end
db = {}
---@param key string
@@ -61,41 +80,15 @@ function db.count_records(key, value) end
---@return integer
function db.char_conv(ch) end
---@param filename string
---@param filename string|nil nil means workspace file
---@param section string
---@param key string
---@param value string
function db.save_string(filename, section, key, value) end
---@param filename string
---@param section string
---@param key string
---@param value integer
function db.save_integer(filename, section, key, value) end
---@param filename string
---@param section string
---@param key string
---@param value boolean
function db.save_boolean(filename, section, key, value) end
---@param filename string
---@param filename string|nil nil means workspace file
---@param section string
---@param key string
---@param defaultValue string
---@return string
function db.load_string(filename, section, key, defaultValue) end
---@param filename string
---@param section string
---@param key string
---@param defaultValue integer|nil optional
---@return integer
function db.load_integer(filename, section, key, defaultValue) end
---@param filename string
---@param section string
---@param key string
---@param defaultValue boolean|nil optional
---@return boolean
function db.load_boolean(filename, section, key, defaultValue) end
function db.load_string(filename, section, key, defaultValue) end