mirror of
https://github.com/KubaPro010/rdsspy-lua-host.git
synced 2026-08-01 01:39:22 +02:00
Compare commits
24
Commits
d8b152a2ba
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67f0941dbd
|
||
|
|
41d9bba1d1
|
||
|
|
1d4cf16399
|
||
|
|
38ae640f97 | ||
|
|
6245cdc66d | ||
|
|
b2130441cf | ||
|
|
7bd8970434
|
||
|
|
71e2d4c863
|
||
|
|
19a5483acb
|
||
|
|
ebfb628ab3
|
||
|
|
db4794dd38
|
||
|
|
c43f2f33c7
|
||
|
|
b2479a7f42
|
||
|
|
c496d2cfd6
|
||
|
|
62e012e867
|
||
|
|
1c33dd63da
|
||
|
|
b67634ef41
|
||
|
|
31ba79e74c
|
||
|
|
c444a854c8
|
||
|
|
0a7917a751
|
||
|
|
9ea35829ac
|
||
|
|
c5224d5a53
|
||
|
|
3da022a4c2
|
||
|
|
0066252626
|
@@ -1,2 +1,3 @@
|
|||||||
*.dll
|
*.dll
|
||||||
liblua
|
liblua
|
||||||
|
liblua.native
|
||||||
@@ -4,7 +4,7 @@ liblua:
|
|||||||
rm *.o
|
rm *.o
|
||||||
|
|
||||||
build: liblua
|
build: liblua
|
||||||
gcc -O2 -shared -static -o luahost.dll plugin.c liblua -lgdi32 -luser32 -lshell32 \
|
gcc -O2 -shared -static -o luahost.dll src/*.c liblua -lgdi32 -luser32 -lshell32 -Ilua \
|
||||||
-Wl,--add-stdcall-alias \
|
-Wl,--add-stdcall-alias \
|
||||||
-ffunction-sections -fdata-sections
|
-ffunction-sections -fdata-sections
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
# Lua host
|
# Lua host
|
||||||
This is an WIP lua host for rds spy, a plugin
|
This is an WIP lua host for rds spy, a plugin
|
||||||
|
|
||||||
Compile this under MINGW32, not 64, RDS Spy lives in the past where men who couldn't count to 4294967295 still walked the planet
|
Compile this under MINGW32, not 64, RDS Spy lives in the past where men who couldn't count to 4294967296 in a CPU word still walked the planet
|
||||||
|
|
||||||
Project stared 1st of January, 2026 (or 7 haha)
|
Project started 1st of January, 2026
|
||||||
|
|
||||||
Aside from all that, this it loads and executes the script at "%LOCALAPPDATA%\RDS Spy\script.lua", with the api in the `plugin.lua` meta-file
|
Aside from all that, this loads and executes the script at "%LOCALAPPDATA%\RDS Spy\script.lua", with the api in the `plugin.lua` meta-file
|
||||||
|
|
||||||
## Why?
|
## 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). Yes, no eRT decoding is really why is this, because why decode RDS in C (or even delphi ughhh) when you can just do it in lua!
|
||||||
|
|
||||||
|
Also RDS Surveyor doesn't take line-in MPX input
|
||||||
|
|
||||||
|
# Scripts
|
||||||
|
|
||||||
|
Inside of the examples folder you have `basic.lua` which mostly replaces the `basic.dll` plugin in RDS Spy
|
||||||
|
|
||||||
|
I have also included `large_pi.lua`, while `basic.lua` already shows the PS and PI on a large font, this does only the large PI and PS but in a even larger font. event 1 toggles stickyness
|
||||||
|
|
||||||
|
# Console
|
||||||
|
|
||||||
|
You have a console window generated by this plugin which has as of now 8 buttons where the first if you double-click the "Reload" button, I don't know what it does but maybe it reloads the script?
|
||||||
|
|
||||||
|
The other 7 send events to the Lua script, you can use this with the console mode:true to display diffrent "menus" on the console, double clicking also sends a unique event
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
set_console_mode(true)
|
set_console_mode(true)
|
||||||
|
|
||||||
local last_pi = 0
|
|
||||||
local last_super_pi = 0
|
|
||||||
|
|
||||||
local ert_string = string.rep("_", 128)
|
local ert_string = string.rep("_", 128)
|
||||||
local rt_a = string.rep("_", 64)
|
local rt_a = string.rep("_", 64)
|
||||||
local rt_b = string.rep("_", 64)
|
local rt_b = string.rep("_", 64)
|
||||||
@@ -19,8 +16,8 @@ local rta_display = ""
|
|||||||
local rtb_display = ""
|
local rtb_display = ""
|
||||||
local lps_display = ""
|
local lps_display = ""
|
||||||
|
|
||||||
|
|
||||||
local current_menu = 1
|
local current_menu = 1
|
||||||
|
local menu_extended = false
|
||||||
|
|
||||||
local pty_rds = {
|
local pty_rds = {
|
||||||
"None", "News", "Current Affairs",
|
"None", "News", "Current Affairs",
|
||||||
@@ -96,7 +93,7 @@ local pi_ecc_to_country = {
|
|||||||
e1 = {
|
e1 = {
|
||||||
"Encoder fault", -- 0
|
"Encoder fault", -- 0
|
||||||
"Hellenic Republic (Greece)", -- 1
|
"Hellenic Republic (Greece)", -- 1
|
||||||
"Libya", -- 2
|
"Republic of Cyprus", -- 2
|
||||||
"Republic of San Marino", -- 3
|
"Republic of San Marino", -- 3
|
||||||
"Swiss Confederation", -- 4
|
"Swiss Confederation", -- 4
|
||||||
"Hashemite Kingdom of Jordan", -- 5
|
"Hashemite Kingdom of Jordan", -- 5
|
||||||
@@ -107,7 +104,7 @@ local pi_ecc_to_country = {
|
|||||||
"United Kingdom of Great Britain and Northern Ireland (Gibraltar)", -- 10 A
|
"United Kingdom of Great Britain and Northern Ireland (Gibraltar)", -- 10 A
|
||||||
"Republic of Iraq", -- 11 B
|
"Republic of Iraq", -- 11 B
|
||||||
"United Kingdom of Great Britain and Northern Ireland", -- 12 C
|
"United Kingdom of Great Britain and Northern Ireland", -- 12 C
|
||||||
"Unknown", -- 13 D
|
"Libya", -- 13 D
|
||||||
"Romania", -- 14 E
|
"Romania", -- 14 E
|
||||||
"Republic of France", -- 15 F wish i could nuke this
|
"Republic of France", -- 15 F wish i could nuke this
|
||||||
},
|
},
|
||||||
@@ -183,6 +180,24 @@ local pi_ecc_to_country = {
|
|||||||
"Unknown", -- 14 E
|
"Unknown", -- 14 E
|
||||||
"Unknown", -- 15 F
|
"Unknown", -- 15 F
|
||||||
},
|
},
|
||||||
|
e6 = {
|
||||||
|
"Encoder fault", -- 0
|
||||||
|
"Unknown", -- 1
|
||||||
|
"Unknown", -- 2
|
||||||
|
"Republic of West Poland", -- 3 coming soon
|
||||||
|
"Unknown", -- 4
|
||||||
|
"Unknown", -- 5
|
||||||
|
"Unknown", -- 6
|
||||||
|
"Unknown", -- 7
|
||||||
|
"Unknown", -- 8
|
||||||
|
"Unknown", -- 9
|
||||||
|
"Unknown", -- 10 A
|
||||||
|
"Unknown", -- 11 B
|
||||||
|
"Unknown", -- 12 C
|
||||||
|
"Unknown", -- 13 D
|
||||||
|
"Unknown", -- 14 E
|
||||||
|
"Unknown", -- 15 F
|
||||||
|
},
|
||||||
a0 = {
|
a0 = {
|
||||||
"Encoder fault", -- 0
|
"Encoder fault", -- 0
|
||||||
"United States of America", -- 1
|
"United States of America", -- 1
|
||||||
@@ -218,7 +233,7 @@ local pi_ecc_to_country = {
|
|||||||
"Canada", -- 12 C
|
"Canada", -- 12 C
|
||||||
"Canada", -- 13 D
|
"Canada", -- 13 D
|
||||||
"Cananda", -- 14 E
|
"Cananda", -- 14 E
|
||||||
"Greenland", -- 15 F
|
"Kingdom of Denmark (Greenland)", -- 15 F
|
||||||
},
|
},
|
||||||
a2 = {
|
a2 = {
|
||||||
"Encoder fault", -- 0
|
"Encoder fault", -- 0
|
||||||
@@ -292,6 +307,24 @@ local pi_ecc_to_country = {
|
|||||||
"United Mexican States", -- 14 E
|
"United Mexican States", -- 14 E
|
||||||
"United Mexican States", -- 15 F
|
"United Mexican States", -- 15 F
|
||||||
},
|
},
|
||||||
|
a6 = {
|
||||||
|
"Encoder fault", -- 0
|
||||||
|
"Unknown", -- 1
|
||||||
|
"Unknown", -- 2
|
||||||
|
"Unknown", -- 3
|
||||||
|
"Unknown", -- 4
|
||||||
|
"Unknown", -- 5
|
||||||
|
"Unknown", -- 6
|
||||||
|
"Unknown", -- 7
|
||||||
|
"Unknown", -- 8
|
||||||
|
"Unknown", -- 9
|
||||||
|
"Unknown", -- 10 A
|
||||||
|
"Unknown", -- 11 B
|
||||||
|
"Unknown", -- 12 C
|
||||||
|
"Unknown", -- 13 D
|
||||||
|
"Unknown", -- 14 E
|
||||||
|
"Republic of France (Saint Pierre and Miquelon)", -- 15 F
|
||||||
|
},
|
||||||
d0 = {
|
d0 = {
|
||||||
"Encoder fault", -- 0
|
"Encoder fault", -- 0
|
||||||
"Republic of Cameroon", -- 1
|
"Republic of Cameroon", -- 1
|
||||||
@@ -458,57 +491,103 @@ local pi_ecc_to_country = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local pi_coverage = {
|
local pi_coverage = {
|
||||||
"Local",
|
"Local", "International", "National",
|
||||||
"International",
|
"Supra-regional", "Regional 4", "Regional 5",
|
||||||
"National",
|
"Regional 5", "Regional 7", "Regional 8",
|
||||||
"Supra-regional",
|
"Regional 9", "Regional A", "Regional B",
|
||||||
"Regional 4",
|
"Regional C", "Regional D", "Regional E",
|
||||||
"Regional 5",
|
|
||||||
"Regional 5",
|
|
||||||
"Regional 7",
|
|
||||||
"Regional 8",
|
|
||||||
"Regional 9",
|
|
||||||
"Regional A",
|
|
||||||
"Regional B",
|
|
||||||
"Regional C",
|
|
||||||
"Regional D",
|
|
||||||
"Regional E",
|
|
||||||
"Regional F",
|
"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 last_render_hash = 0
|
||||||
local function crc(data)
|
local function crc(data)
|
||||||
local crc = 0xFF
|
local sum = 0xFF
|
||||||
|
|
||||||
for i = 1, #data do
|
for i = 1, #data do
|
||||||
crc = crc ~ data:byte(i)
|
sum = sum ~ data:byte(i)
|
||||||
|
|
||||||
for _ = 1, 8 do
|
for _ = 1, 8 do
|
||||||
if (crc & 0x80) ~= 0 then crc = (crc << 1) ~ 0x7
|
if (sum & 0x80) ~= 0 then sum = (sum << 1) ~ 0x7
|
||||||
else crc = crc << 1 end
|
else sum = sum << 1 end
|
||||||
crc = crc & 0xFF
|
sum = sum & 0xFF
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return crc
|
return sum
|
||||||
end
|
end
|
||||||
|
|
||||||
function render_menu()
|
local function render_menu()
|
||||||
out = string.format("Menu %d\r\n------\r\n", current_menu)
|
local out = string.format("Menu %d%s\r\n------\r\n", current_menu, menu_extended and " (extended)" or "")
|
||||||
set_font_size(26)
|
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)
|
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 "--------")
|
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("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("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("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[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)
|
out = out .. string.format("RT[B]: %s%s\r\n\r\n", (not last_rt) and ">" or " ", rtb_display)
|
||||||
elseif current_menu == 3 then
|
elseif current_menu == 2 and menu_extended then
|
||||||
local country_id = (last_pi & 0xF000) >> 12
|
local current_rt = rta_display
|
||||||
local coverage_id = (last_pi & 0xF00) >> 8
|
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 and not menu_extended then
|
||||||
|
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)
|
out = out .. string.format("LPS: %s\r\n\r\n", lps_display)
|
||||||
|
|
||||||
@@ -520,14 +599,21 @@ function render_menu()
|
|||||||
out = out .. string.format("Coverage: %s\r\n", pi_coverage[coverage_id+1])
|
out = out .. string.format("Coverage: %s\r\n", pi_coverage[coverage_id+1])
|
||||||
out = out .. string.format("Country: %s (%X)\r\n\r\n", country_name, ecc)
|
out = out .. string.format("Country: %s (%X)\r\n\r\n", country_name, ecc)
|
||||||
|
|
||||||
out = out .. string.format("ERT: %s\r\n\r\n", ert_display)
|
|
||||||
|
|
||||||
local oda_string = ""
|
local oda_string = ""
|
||||||
for grp, data in pairs(odas) do
|
for grp, data in pairs(odas) do
|
||||||
local ver_char = (data.version == 0) and "A" or "B"
|
local ver_char = (data.version == 0) and "A" or "B"
|
||||||
oda_string = oda_string .. string.format("%d%s - %04X | ", grp, ver_char, data.aid)
|
oda_string = oda_string .. string.format("%d%s - %04X | ", grp, ver_char, data.aid)
|
||||||
end
|
end
|
||||||
out = out .. string.format("ODA: %s\r\n", oda_string:sub(1, #oda_string-2))
|
out = out .. string.format("ODA: %s\r\n", oda_string:sub(1, #oda_string-2))
|
||||||
|
elseif current_menu == 3 and menu_extended then
|
||||||
|
out = out .. string.format("ERT: %s\r\n\r\n", ert_display)
|
||||||
|
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
|
end
|
||||||
|
|
||||||
local hash = crc(out)
|
local hash = crc(out)
|
||||||
@@ -538,8 +624,15 @@ function render_menu()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function event(event)
|
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()
|
render_menu()
|
||||||
|
last_render_hash = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ternary(cond, a, b)
|
local function ternary(cond, a, b)
|
||||||
@@ -560,13 +653,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("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("ECC", string.format("%X", ecc))
|
||||||
db.add_value("LPS", lps_display)
|
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
|
elseif cmd:lower() == "resetdata" then
|
||||||
ert_string = string.rep("_", 128)
|
ert_string = string.rep("_", 128)
|
||||||
rt_a = string.rep("_", 64)
|
rt_a = string.rep("_", 64)
|
||||||
rt_b = string.rep("_", 64)
|
rt_b = string.rep("_", 64)
|
||||||
odas = {}
|
odas = {}
|
||||||
last_pi = 0
|
|
||||||
last_super_pi = 0
|
|
||||||
pty = 0
|
pty = 0
|
||||||
tp = false
|
tp = false
|
||||||
ta = false
|
ta = false
|
||||||
@@ -575,8 +677,20 @@ function command(cmd, param)
|
|||||||
ptyn_toggle = false
|
ptyn_toggle = false
|
||||||
ecc = 0
|
ecc = 0
|
||||||
lps = string.rep("_", 32)
|
lps = string.rep("_", 32)
|
||||||
elseif cmd:lower() == "superpi" then
|
time_display_utc = "-"
|
||||||
if #param <= 4 then last_super_pi = tonumber(param, 16) end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -587,12 +701,89 @@ local function findODAByAID(t, targetAID)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
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
|
if stream ~= 0 and a ~= 0 then return
|
||||||
elseif stream ~= 0 and not db.load_boolean("rdsspy.ini", "General", "Tunnelling", false) then return end
|
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()
|
render_menu()
|
||||||
|
|
||||||
if b_corr or b < 0 then return end
|
if b_corr or b < 0 then return end
|
||||||
@@ -610,8 +801,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
|
if odas[target_group] == nil then odas[target_group] = { aid = d, version = (b & 1) } end
|
||||||
else
|
else
|
||||||
local ert_grp, ert_ver = findODAByAID(odas, 0x6552)
|
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 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 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 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
|
local start_pos = (ert_state * 4) + 1
|
||||||
@@ -620,6 +813,16 @@ function group(stream, b_corr, a, b, c, d)
|
|||||||
local carriage = ert_string:find("\r", 1, true)
|
local carriage = ert_string:find("\r", 1, true)
|
||||||
if carriage then ert_display = ert_string:sub(1, carriage - 1)
|
if carriage then ert_display = ert_string:sub(1, carriage - 1)
|
||||||
else ert_display = ert_string:gsub("%s+$", "") end
|
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
|
else
|
||||||
if group_type == 0 then
|
if group_type == 0 then
|
||||||
ta = ((b & 0x10) >> 4) ~= 0
|
ta = ((b & 0x10) >> 4) ~= 0
|
||||||
@@ -627,6 +830,45 @@ function group(stream, b_corr, a, b, c, d)
|
|||||||
local segment = b & 0x3
|
local segment = b & 0x3
|
||||||
if di_bit and segment == 0 then dpty = true
|
if di_bit and segment == 0 then dpty = true
|
||||||
elseif segment == 0 then dpty = false end
|
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
|
elseif group_type == 1 and group_version == 0 then
|
||||||
if d < 0 then return end
|
if d < 0 then return end
|
||||||
local variant = (c & 0x7000) >> 12
|
local variant = (c & 0x7000) >> 12
|
||||||
@@ -696,6 +938,35 @@ function group(stream, b_corr, a, b, c, d)
|
|||||||
local carriage = lps:find("\r", 1, true)
|
local carriage = lps:find("\r", 1, true)
|
||||||
if carriage then lps_display = lps:sub(1, carriage - 1)
|
if carriage then lps_display = lps:sub(1, carriage - 1)
|
||||||
else lps_display = lps:gsub("%s+$", "") end
|
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
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
set_console_mode(true)
|
||||||
|
set_font_size(148)
|
||||||
|
|
||||||
|
local last_pi = "----"
|
||||||
|
local last_ps = "--------"
|
||||||
|
function group(...)
|
||||||
|
local pi = db.read_value("PI") or "----"
|
||||||
|
local ps = db.read_value("PS") or "--------"
|
||||||
|
if last_pi ~= pi or last_ps ~= ps then
|
||||||
|
set_console(string.format("%s\r\n%s", pi, ps))
|
||||||
|
last_pi = pi
|
||||||
|
last_ps = ps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function event(event)
|
||||||
|
if event == 1 then
|
||||||
|
set_window_stick(not get_window_stick())
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,666 +0,0 @@
|
|||||||
#include <windows.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <shlobj.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "lua/lua.h"
|
|
||||||
#include "lua/lualib.h"
|
|
||||||
#include "lua/lauxlib.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint16_t Year;
|
|
||||||
uint8_t Month;
|
|
||||||
uint8_t Day;
|
|
||||||
uint8_t Hour;
|
|
||||||
uint8_t Minutes;
|
|
||||||
uint8_t Second;
|
|
||||||
uint8_t Centisecond;
|
|
||||||
uint16_t RFU;
|
|
||||||
int32_t Blk1;
|
|
||||||
int32_t Blk2;
|
|
||||||
int32_t Blk3;
|
|
||||||
int32_t Blk4;
|
|
||||||
} TRDSGroup;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
// fuckass delphi
|
|
||||||
uint8_t len;
|
|
||||||
uint8_t data[255];
|
|
||||||
} ShortString;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ShortString Key;
|
|
||||||
ShortString Value;
|
|
||||||
} TRecord;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t Count;
|
|
||||||
TRecord Records[255];
|
|
||||||
} TDB;
|
|
||||||
|
|
||||||
static TRDSGroup Group;
|
|
||||||
|
|
||||||
static HWND hWnd = NULL;
|
|
||||||
static HINSTANCE hInst = NULL;
|
|
||||||
|
|
||||||
static lua_State* L = NULL;
|
|
||||||
|
|
||||||
static HWND hEditControl = NULL;
|
|
||||||
static TDB* g_DBPointer = NULL;
|
|
||||||
|
|
||||||
static HFONT g_hCurrentFont = NULL;
|
|
||||||
#define FONT_NAME "Segoe UI"
|
|
||||||
|
|
||||||
#define IDC_MAIN_BUTTON 101
|
|
||||||
|
|
||||||
#define WINDOW_HEIGHT 380
|
|
||||||
#define WINDOW_WIDTH 620
|
|
||||||
#define BUTTON_COUNT 7
|
|
||||||
|
|
||||||
#define STRINGIFY(x) #x
|
|
||||||
#define TOSTRING(x) STRINGIFY(x)
|
|
||||||
|
|
||||||
#define EVENT_BUTTON(I) \
|
|
||||||
HWND hButton_event##I = CreateWindowEx( \
|
|
||||||
0, "BUTTON", "Event " TOSTRING(I), \
|
|
||||||
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_NOTIFY, \
|
|
||||||
10 + (75 * I), WINDOW_HEIGHT-62, \
|
|
||||||
70, 30, hWnd, \
|
|
||||||
(HMENU)(IDC_MAIN_BUTTON + I), hInst, NULL \
|
|
||||||
);
|
|
||||||
|
|
||||||
static const unsigned char EBU[127] = {
|
|
||||||
0xE1, 'a', 0xE9, 'e', 0xED, 'i', 0xF3, 'o', 0xFA, 'u', 'N', 0xC7, 0xAA, 0xDF, 'I', 0x00,
|
|
||||||
0xE2, 0xE4, 'e', 0xEB, 0xEE, 'i', 0xF4, 0xF6, 'u', 0xFC, 'n', 0xE7, 0xBA, 0x00, 'i', 0x00,
|
|
||||||
0x00, 0x00, 0xA9, 0x89, 0x00, 0xEC, 0xF2, 0xF5, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0xB1, 'I', 0xF1, 0xFB, 0xB5, '?', 0xF7, 0xB0, 0x00, 0x00, 0x00, 0xA7, 0x00,
|
|
||||||
0xC1, 'A', 0xC9, 'E', 0xCD, 'I', 0xD3, 'O', 0xDA, 'U', 0xD8, 0xC8, 0x8A, 0x8E, 0xD0, 'L',
|
|
||||||
0xC2, 0xC4, 'E', 0xCB, 0xCE, 'I', 0xD4, 0xD6, 'U', 0xDC, 0xF8, 0xE8, 0x9A, 0x9E, 0xF0, 'l',
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC6, 0x8C, 0x8F, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE6, 0x9C, 0x9F, 0x00
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned int console_mode = 0;
|
|
||||||
|
|
||||||
const char* int_to_string(int value) {
|
|
||||||
static char buffer[16];
|
|
||||||
snprintf(buffer, sizeof(buffer), "%d", value);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char CharConv(unsigned char Ch) {
|
|
||||||
if (Ch >= 128 && Ch < 255) {
|
|
||||||
unsigned char conv = EBU[Ch - 128];
|
|
||||||
if (conv != 0) return conv;
|
|
||||||
}
|
|
||||||
return Ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitLua();
|
|
||||||
void lua_event(int event);
|
|
||||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
||||||
{
|
|
||||||
switch (msg)
|
|
||||||
{
|
|
||||||
case WM_CLOSE:
|
|
||||||
ShowWindow(hwnd, SW_HIDE);
|
|
||||||
return 0;
|
|
||||||
case WM_COMMAND:
|
|
||||||
if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)
|
|
||||||
{
|
|
||||||
int controlId = LOWORD(wParam);
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
if(HIWORD(wParam) == BN_DOUBLECLICKED) offset = BUTTON_COUNT;
|
|
||||||
|
|
||||||
if (controlId == IDC_MAIN_BUTTON) InitLua();
|
|
||||||
else if (controlId > IDC_MAIN_BUTTON && controlId <= IDC_MAIN_BUTTON + BUTTON_COUNT) lua_event((controlId - IDC_MAIN_BUTTON) + offset);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WM_DESTROY:
|
|
||||||
hWnd = NULL;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatePluginWindow(HWND hOwner) {
|
|
||||||
WNDCLASS wc = {0};
|
|
||||||
wc.lpfnWndProc = WndProc;
|
|
||||||
wc.hInstance = hInst;
|
|
||||||
wc.lpszClassName = "LuaHostPlugin";
|
|
||||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
||||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
||||||
RegisterClass(&wc);
|
|
||||||
|
|
||||||
hWnd = CreateWindowEx(
|
|
||||||
0,
|
|
||||||
"LuaHostPlugin",
|
|
||||||
"Lua Host Console",
|
|
||||||
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
|
|
||||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
||||||
WINDOW_WIDTH, WINDOW_HEIGHT,
|
|
||||||
hOwner,
|
|
||||||
NULL,
|
|
||||||
hInst,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
hEditControl = CreateWindowEx(
|
|
||||||
WS_EX_CLIENTEDGE, "EDIT", "",
|
|
||||||
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
|
|
||||||
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
|
|
||||||
10, 10, WINDOW_WIDTH-25, WINDOW_HEIGHT-75,
|
|
||||||
hWnd, NULL, hInst, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
HWND hButton = CreateWindowEx(
|
|
||||||
0, "BUTTON", "Reload",
|
|
||||||
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
|
|
||||||
10, WINDOW_HEIGHT-62,
|
|
||||||
70, 30, hWnd,
|
|
||||||
(HMENU)IDC_MAIN_BUTTON, hInst, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
EVENT_BUTTON(1)
|
|
||||||
EVENT_BUTTON(2)
|
|
||||||
EVENT_BUTTON(3)
|
|
||||||
EVENT_BUTTON(4)
|
|
||||||
EVENT_BUTTON(5)
|
|
||||||
EVENT_BUTTON(6)
|
|
||||||
EVENT_BUTTON(7)
|
|
||||||
|
|
||||||
HFONT hFont = CreateFont(18, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, FONT_NAME);
|
|
||||||
SendMessage(hEditControl, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event1, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event2, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event3, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event4, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event5, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event6, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
SendMessage(hButton_event7, WM_SETFONT, (WPARAM)hFont, TRUE);
|
|
||||||
|
|
||||||
HICON hIconBig = (HICON)SendMessage(hOwner, WM_GETICON, ICON_BIG, 0);
|
|
||||||
HICON hIconSmall = (HICON)SendMessage(hOwner, WM_GETICON, ICON_SMALL, 0);
|
|
||||||
if (!hIconBig) hIconBig = (HICON)GetClassLongPtr(hOwner, GCLP_HICON);
|
|
||||||
if (!hIconSmall) hIconSmall = (HICON)GetClassLongPtr(hOwner, GCLP_HICONSM);
|
|
||||||
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIconBig);
|
|
||||||
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall);
|
|
||||||
|
|
||||||
UpdateWindow(hWnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendText(const char* text) {
|
|
||||||
if (hEditControl != NULL) {
|
|
||||||
int len = GetWindowTextLength(hEditControl);
|
|
||||||
SendMessage(hEditControl, EM_SETSEL, len, len);
|
|
||||||
SendMessageA(hEditControl, EM_REPLACESEL, FALSE, (LPARAM)text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetText(const char* text) {
|
|
||||||
if (hEditControl != NULL) SetWindowTextA(hEditControl, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_log(lua_State* localL) {
|
|
||||||
if(console_mode != 0) return luaL_error(localL, "Invalid log");
|
|
||||||
const char* data = luaL_checkstring(localL, 1);
|
|
||||||
AppendText(data);
|
|
||||||
AppendText("\r\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_set_console(lua_State* localL) {
|
|
||||||
if(console_mode != 1) return luaL_error(localL, "Invalid log");
|
|
||||||
const char* data = luaL_checkstring(localL, 1);
|
|
||||||
SetText(data);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_set_console_mode(lua_State* localL) {
|
|
||||||
if (!lua_isboolean(localL, 1)) return luaL_error(localL, "boolean expected, got %s", luaL_typename(localL, 1));
|
|
||||||
int mode = lua_toboolean(localL, 1);
|
|
||||||
SetText("");
|
|
||||||
console_mode = mode;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_set_font_size(lua_State* localL) {
|
|
||||||
int size = luaL_checkinteger(localL, 1);
|
|
||||||
|
|
||||||
HFONT hNewFont = CreateFont(size, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, FONT_NAME);
|
|
||||||
|
|
||||||
if (hNewFont && hEditControl) {
|
|
||||||
SendMessage(hEditControl, WM_SETFONT, (WPARAM)hNewFont, TRUE);
|
|
||||||
|
|
||||||
if (g_hCurrentFont != NULL) DeleteObject(g_hCurrentFont);
|
|
||||||
|
|
||||||
g_hCurrentFont = hNewFont;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_MessageBox(lua_State* localL) {
|
|
||||||
const char* data = luaL_checkstring(localL, 1);
|
|
||||||
const char* title = luaL_checkstring(localL, 2);
|
|
||||||
MessageBoxA(NULL, data, title, MB_OK | MB_TOPMOST);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_ReadValue(lua_State* localL) {
|
|
||||||
if (g_DBPointer == NULL) {
|
|
||||||
lua_pushnil(localL);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* key = luaL_checkstring(localL, 1);
|
|
||||||
ShortString skey;
|
|
||||||
skey.len = strlen(key);
|
|
||||||
if (skey.len > 255) skey.len = 255;
|
|
||||||
memcpy(skey.data, key, skey.len);
|
|
||||||
|
|
||||||
int count = g_DBPointer->Count;
|
|
||||||
if (count > 255) count = 255;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
if (g_DBPointer->Records[i].Key.len == skey.len &&
|
|
||||||
memcmp(g_DBPointer->Records[i].Key.data, skey.data, skey.len) == 0) {
|
|
||||||
lua_pushlstring(localL, g_DBPointer->Records[i].Value.data, g_DBPointer->Records[i].Value.len);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushnil(localL);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_ReadRecord(lua_State* localL) {
|
|
||||||
if (g_DBPointer == NULL) {
|
|
||||||
lua_pushnil(localL);
|
|
||||||
lua_pushnil(localL);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = luaL_checkinteger(localL, 1);
|
|
||||||
|
|
||||||
if (index < 0 || index >= g_DBPointer->Count || index >= 255) {
|
|
||||||
lua_pushnil(localL);
|
|
||||||
lua_pushnil(localL);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushlstring(localL, g_DBPointer->Records[index].Key.data, g_DBPointer->Records[index].Key.len);
|
|
||||||
lua_pushlstring(localL, g_DBPointer->Records[index].Value.data, g_DBPointer->Records[index].Value.len);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_AddValue(lua_State* localL) {
|
|
||||||
if (g_DBPointer == NULL) return 0;
|
|
||||||
|
|
||||||
const char* key = luaL_checkstring(localL, 1);
|
|
||||||
const char* value = luaL_checkstring(localL, 2);
|
|
||||||
|
|
||||||
ShortString skey, svalue;
|
|
||||||
skey.len = strlen(key);
|
|
||||||
if (skey.len > 255) skey.len = 255;
|
|
||||||
memcpy(skey.data, key, skey.len);
|
|
||||||
|
|
||||||
svalue.len = strlen(value);
|
|
||||||
if (svalue.len > 252) {
|
|
||||||
svalue.len = 252;
|
|
||||||
memcpy(svalue.data, value, 252);
|
|
||||||
memcpy(svalue.data + 252, "...", 3);
|
|
||||||
svalue.len = 255;
|
|
||||||
} else memcpy(svalue.data, value, svalue.len);
|
|
||||||
|
|
||||||
int count = g_DBPointer->Count;
|
|
||||||
if (count > 255) count = 255;
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
if (g_DBPointer->Records[i].Key.len == skey.len &&
|
|
||||||
memcmp(g_DBPointer->Records[i].Key.data, skey.data, skey.len) == 0) {
|
|
||||||
g_DBPointer->Records[i].Value = svalue;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 254) {
|
|
||||||
g_DBPointer->Records[count].Key = skey;
|
|
||||||
g_DBPointer->Records[count].Value = svalue;
|
|
||||||
g_DBPointer->Count = count + 1;
|
|
||||||
} else if (_stricmp(key, "COMMAND") == 0) {
|
|
||||||
g_DBPointer->Records[254].Key = skey;
|
|
||||||
g_DBPointer->Records[254].Value = svalue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_ResetValues(lua_State* localL) {
|
|
||||||
if (g_DBPointer == NULL) return 0;
|
|
||||||
g_DBPointer->Count = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_CountRecords(lua_State* localL) {
|
|
||||||
if (g_DBPointer == NULL) {
|
|
||||||
lua_pushinteger(localL, 0);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = g_DBPointer->Count;
|
|
||||||
if (count > 255) count = 255;
|
|
||||||
lua_pushinteger(localL, count);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int lua_CharConv(lua_State* localL) {
|
|
||||||
int ch = luaL_checkinteger(localL, 1);
|
|
||||||
lua_pushinteger(localL, CharConv((unsigned char)ch));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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 (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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void lua_call_command(const char* Cmd, const char* Param) {
|
|
||||||
lua_getglobal(L, "command");
|
|
||||||
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
lua_pushstring(L, Cmd);
|
|
||||||
lua_pushstring(L, Param);
|
|
||||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
|
||||||
char msg_buffer[255];
|
|
||||||
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error: %s at '%s'\n", lua_tostring(L, -1), "command");
|
|
||||||
AppendText(msg_buffer);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
} else lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lua_call_group() {
|
|
||||||
lua_getglobal(L, "group");
|
|
||||||
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
lua_pushinteger(L, Group.RFU & 3); // TODO: find out if fuckass pira.cz meant msb or lsb, i have no clue what does "Bits: 0-1" mean
|
|
||||||
lua_pushboolean(L, (Group.RFU & 0x100) >> 8); // just do lsb for now i guess
|
|
||||||
lua_pushinteger(L, Group.Blk1);
|
|
||||||
lua_pushinteger(L, Group.Blk2);
|
|
||||||
lua_pushinteger(L, Group.Blk3);
|
|
||||||
lua_pushinteger(L, Group.Blk4);
|
|
||||||
if (lua_pcall(L, 6, 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);
|
|
||||||
}
|
|
||||||
} else lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lua_event(int event) {
|
|
||||||
lua_getglobal(L, "event");
|
|
||||||
|
|
||||||
if (lua_isfunction(L, -1)) {
|
|
||||||
lua_pushinteger(L, event);
|
|
||||||
if (lua_pcall(L, 1, 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), "event");
|
|
||||||
AppendText(msg_buffer);
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
} else lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(dllexport) void __stdcall RDSGroup(TRDSGroup* PRDSGroup) {
|
|
||||||
if (PRDSGroup == NULL) return;
|
|
||||||
|
|
||||||
Group = *PRDSGroup;
|
|
||||||
lua_call_group();
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(dllexport) void __stdcall Command(const char* Cmd, const char* Param) {
|
|
||||||
if (Cmd == NULL) return;
|
|
||||||
if (_stricmp(Cmd, "EXIT") == 0) {
|
|
||||||
if (hWnd != NULL) {
|
|
||||||
DestroyWindow(hWnd);
|
|
||||||
hWnd = NULL;
|
|
||||||
}
|
|
||||||
if(L != NULL) {
|
|
||||||
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, "OPENWORKSPACE") == 0) {
|
|
||||||
if(hWnd != NULL) {
|
|
||||||
int value = GetPrivateProfileIntA("luahost", "Visible", 0, Param);
|
|
||||||
if(value == 1) ShowWindow(hWnd, SW_SHOW);
|
|
||||||
else if(value == 0) ShowWindow(hWnd, SW_HIDE);
|
|
||||||
int x = GetPrivateProfileIntA("luahost", "Left", 320, Param);
|
|
||||||
int y = GetPrivateProfileIntA("luahost", "Top", 240, Param);
|
|
||||||
SetWindowPos(hWnd, NULL, x, y, 0, 0, SWP_NOSIZE);
|
|
||||||
}
|
|
||||||
lua_call_command(Cmd, Param); // still call
|
|
||||||
} else if (_stricmp(Cmd, "SAVEWORKSPACE") == 0) {
|
|
||||||
if(hWnd != NULL) {
|
|
||||||
RECT rect; // get rect
|
|
||||||
if (GetWindowRect(hWnd, &rect)) {
|
|
||||||
WritePrivateProfileStringA("luahost", "Left", int_to_string(rect.left), Param);
|
|
||||||
WritePrivateProfileStringA("luahost", "Top", int_to_string(rect.top), Param);
|
|
||||||
}
|
|
||||||
WritePrivateProfileStringA("luahost", "Visible", IsWindowVisible(hWnd) ? "1" : "0", Param);
|
|
||||||
}
|
|
||||||
lua_call_command(Cmd, Param); // still call
|
|
||||||
} else if (_stricmp(Cmd, "LUASCRIPT") == 0) { // custom
|
|
||||||
char msg_buffer[255];
|
|
||||||
if (luaL_loadfile(L, Param) != LUA_OK) {
|
|
||||||
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error loading file: %s\r\n", lua_tostring(L, -1));
|
|
||||||
AppendText(msg_buffer);
|
|
||||||
lua_pop(L, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else lua_call_command(Cmd, Param);
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(dllexport) const char* __stdcall PluginName(void) { return "Lua Host"; }
|
|
||||||
|
|
||||||
void InitLua() {
|
|
||||||
if(L != NULL) {
|
|
||||||
lua_close(L);
|
|
||||||
L = NULL;
|
|
||||||
}
|
|
||||||
L = luaL_newstate();
|
|
||||||
luaL_openlibs(L);
|
|
||||||
lua_register(L, "set_font_size", lua_set_font_size);
|
|
||||||
lua_register(L, "message_box", lua_MessageBox);
|
|
||||||
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_newtable(L);
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_ReadValue);
|
|
||||||
lua_setfield(L, -2, "read_value");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_ReadRecord);
|
|
||||||
lua_setfield(L, -2, "read_record");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_AddValue);
|
|
||||||
lua_setfield(L, -2, "add_value");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_ResetValues);
|
|
||||||
lua_setfield(L, -2, "reset_values");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_CountRecords);
|
|
||||||
lua_setfield(L, -2, "count_records");
|
|
||||||
|
|
||||||
lua_pushcfunction(L, lua_CharConv);
|
|
||||||
lua_setfield(L, -2, "char_conv");
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
char path[MAX_PATH];
|
|
||||||
char fullPath[MAX_PATH];
|
|
||||||
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\script.lua", path);
|
|
||||||
else {
|
|
||||||
MessageBoxA(NULL, "Could not get the local app data path", "Error", MB_ICONERROR | MB_OK | MB_TOPMOST);
|
|
||||||
AppendText("Could not get the local app data path\r\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char msg_buffer[255];
|
|
||||||
if (luaL_loadfile(L, fullPath) != LUA_OK) {
|
|
||||||
snprintf(msg_buffer, sizeof(msg_buffer), "Lua error loading file: %s\r\n", lua_tostring(L, -1));
|
|
||||||
AppendText(msg_buffer);
|
|
||||||
lua_pop(L, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__declspec(dllexport) int __stdcall Initialize(HANDLE hHandle, TDB* DBPointer) {
|
|
||||||
CreatePluginWindow(hHandle);
|
|
||||||
AppendText(LUA_COPYRIGHT);
|
|
||||||
AppendText("\r\n");
|
|
||||||
g_DBPointer = DBPointer;
|
|
||||||
InitLua();
|
|
||||||
|
|
||||||
return (int)hWnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
|
||||||
{
|
|
||||||
if (fdwReason == DLL_PROCESS_ATTACH) hInst = hinstDLL;
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
+24
-31
@@ -1,11 +1,23 @@
|
|||||||
---@meta
|
---@meta
|
||||||
|
|
||||||
|
---@type integer
|
||||||
|
event_count = 0
|
||||||
|
|
||||||
---This function should be defined by the user in the script
|
---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 cmd string
|
||||||
---@param param string
|
---@param param string
|
||||||
function command(cmd, param) end
|
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
|
---This function should be defined by the user in the script
|
||||||
---@param stream integer
|
---@param stream integer
|
||||||
---@param block_b_correction boolean
|
---@param block_b_correction boolean
|
||||||
@@ -13,7 +25,8 @@ function command(cmd, param) end
|
|||||||
---@param b integer
|
---@param b integer
|
||||||
---@param c integer
|
---@param c integer
|
||||||
---@param d 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
|
---This function should be defined by the user in the script
|
||||||
---@param event integer
|
---@param event integer
|
||||||
@@ -38,6 +51,12 @@ function set_console(data) end
|
|||||||
---@param mode boolean
|
---@param mode boolean
|
||||||
function set_console_mode(mode) end
|
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 = {}
|
db = {}
|
||||||
|
|
||||||
---@param key string
|
---@param key string
|
||||||
@@ -52,7 +71,7 @@ function db.read_record(index) end
|
|||||||
---@param value string
|
---@param value string
|
||||||
function db.add_value(key, value) end
|
function db.add_value(key, value) end
|
||||||
|
|
||||||
function db.reset_values(key, value) end
|
function db.reset_values() end
|
||||||
|
|
||||||
---@return integer
|
---@return integer
|
||||||
function db.count_records(key, value) end
|
function db.count_records(key, value) end
|
||||||
@@ -61,41 +80,15 @@ function db.count_records(key, value) end
|
|||||||
---@return integer
|
---@return integer
|
||||||
function db.char_conv(ch) end
|
function db.char_conv(ch) end
|
||||||
|
|
||||||
---@param filename string
|
---@param filename string|nil nil means workspace file
|
||||||
---@param section string
|
---@param section string
|
||||||
---@param key string
|
---@param key string
|
||||||
---@param value string
|
---@param value string
|
||||||
function db.save_string(filename, section, key, value) end
|
function db.save_string(filename, section, key, value) end
|
||||||
|
|
||||||
---@param filename string
|
---@param filename string|nil nil means workspace file
|
||||||
---@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 section string
|
---@param section string
|
||||||
---@param key string
|
---@param key string
|
||||||
---@param defaultValue string
|
---@param defaultValue string
|
||||||
---@return string
|
---@return string
|
||||||
function db.load_string(filename, section, key, defaultValue) end
|
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
|
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
static const unsigned char EBU[127] = {
|
||||||
|
0xE1, 'a', 0xE9, 'e', 0xED, 'i', 0xF3, 'o', 0xFA, 'u', 'N', 0xC7, 0xAA, 0xDF, 'I', 0x00,
|
||||||
|
0xE2, 0xE4, 'e', 0xEB, 0xEE, 'i', 0xF4, 0xF6, 'u', 0xFC, 'n', 0xE7, 0xBA, 0x00, 'i', 0x00,
|
||||||
|
0x00, 0x00, 0xA9, 0x89, 0x00, 0xEC, 0xF2, 0xF5, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xB1, 'I', 0xF1, 0xFB, 0xB5, '?', 0xF7, 0xB0, 0x00, 0x00, 0x00, 0xA7, 0x00,
|
||||||
|
0xC1, 'A', 0xC9, 'E', 0xCD, 'I', 0xD3, 'O', 0xDA, 'U', 0xD8, 0xC8, 0x8A, 0x8E, 0xD0, 'L',
|
||||||
|
0xC2, 0xC4, 'E', 0xCB, 0xCE, 'I', 0xD4, 0xD6, 'U', 0xDC, 0xF8, 0xE8, 0x9A, 0x9E, 0xF0, 'l',
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC6, 0x8C, 0x8F, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE6, 0x9C, 0x9F, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char CharConv(unsigned char Ch) {
|
||||||
|
if (Ch >= 128 && Ch < 255) {
|
||||||
|
unsigned char conv = EBU[Ch - 128];
|
||||||
|
if (conv != 0) return conv;
|
||||||
|
}
|
||||||
|
return Ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_log(lua_State* localL) {
|
||||||
|
if(console_mode != 0) return luaL_error(localL, "Invalid log");
|
||||||
|
AppendText(luaL_checkstring(localL, 1));
|
||||||
|
AppendText("\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_set_console(lua_State* localL) {
|
||||||
|
if(console_mode != 1) return luaL_error(localL, "Invalid log");
|
||||||
|
if (hEditControl != NULL) SetWindowTextA(hEditControl, luaL_checkstring(localL, 1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_set_console_mode(lua_State* localL) {
|
||||||
|
if (!lua_isboolean(localL, 1)) return luaL_typeerror(localL, 1, lua_typename(localL, LUA_TBOOLEAN));
|
||||||
|
int mode = lua_toboolean(localL, 1);
|
||||||
|
if (hEditControl != NULL) SetWindowTextA(hEditControl, "");
|
||||||
|
console_mode = mode;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_set_window_stick(lua_State* localL) {
|
||||||
|
if (!lua_isboolean(localL, 1)) return luaL_typeerror(localL, 1, lua_typename(localL, LUA_TBOOLEAN));
|
||||||
|
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);
|
||||||
|
|
||||||
|
HFONT hNewFont = CreateFont(size, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, FONT_NAME);
|
||||||
|
|
||||||
|
if (hNewFont && hEditControl) {
|
||||||
|
SendMessage(hEditControl, WM_SETFONT, (WPARAM)hNewFont, TRUE);
|
||||||
|
|
||||||
|
if (g_hCurrentFont != NULL) DeleteObject(g_hCurrentFont);
|
||||||
|
|
||||||
|
g_hCurrentFont = hNewFont;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_MessageBox(lua_State* localL) {
|
||||||
|
MessageBoxA(NULL, luaL_checkstring(localL, 1), luaL_checkstring(localL, 2), MB_OK | MB_TOPMOST);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_ReadValue(lua_State* localL) {
|
||||||
|
if (g_DBPointer == NULL) {
|
||||||
|
lua_pushnil(localL);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* key = luaL_checkstring(localL, 1);
|
||||||
|
ShortString skey;
|
||||||
|
skey.len = strlen(key);
|
||||||
|
if (skey.len > 255) skey.len = 255;
|
||||||
|
memcpy(skey.data, key, skey.len);
|
||||||
|
|
||||||
|
int count = g_DBPointer->Count;
|
||||||
|
if (count > 255) count = 255;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (g_DBPointer->Records[i].Key.len == skey.len &&
|
||||||
|
memcmp(g_DBPointer->Records[i].Key.data, skey.data, skey.len) == 0) {
|
||||||
|
lua_pushlstring(localL, g_DBPointer->Records[i].Value.data, g_DBPointer->Records[i].Value.len);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushnil(localL);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_ReadRecord(lua_State* localL) {
|
||||||
|
if (g_DBPointer == NULL) {
|
||||||
|
lua_pushnil(localL);
|
||||||
|
lua_pushnil(localL);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = luaL_checkinteger(localL, 1);
|
||||||
|
|
||||||
|
if (index < 0 || index >= g_DBPointer->Count || index >= 255) {
|
||||||
|
lua_pushnil(localL);
|
||||||
|
lua_pushnil(localL);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushlstring(localL, g_DBPointer->Records[index].Key.data, g_DBPointer->Records[index].Key.len);
|
||||||
|
lua_pushlstring(localL, g_DBPointer->Records[index].Value.data, g_DBPointer->Records[index].Value.len);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_AddValue(lua_State* localL) {
|
||||||
|
if (g_DBPointer == NULL) return 0;
|
||||||
|
|
||||||
|
const char* key = luaL_checkstring(localL, 1);
|
||||||
|
const char* value = luaL_checkstring(localL, 2);
|
||||||
|
|
||||||
|
ShortString skey, svalue;
|
||||||
|
skey.len = strlen(key);
|
||||||
|
if (skey.len > 255) skey.len = 255;
|
||||||
|
memcpy(skey.data, key, skey.len);
|
||||||
|
|
||||||
|
svalue.len = strlen(value);
|
||||||
|
if (svalue.len > 252) {
|
||||||
|
svalue.len = 252;
|
||||||
|
memcpy(svalue.data, value, 252);
|
||||||
|
memcpy(svalue.data + 252, "...", 3);
|
||||||
|
svalue.len = 255;
|
||||||
|
} else memcpy(svalue.data, value, svalue.len);
|
||||||
|
|
||||||
|
int count = g_DBPointer->Count;
|
||||||
|
if (count > 255) count = 255;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (g_DBPointer->Records[i].Key.len == skey.len &&
|
||||||
|
memcmp(g_DBPointer->Records[i].Key.data, skey.data, skey.len) == 0) {
|
||||||
|
g_DBPointer->Records[i].Value = svalue;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 254) {
|
||||||
|
g_DBPointer->Records[count].Key = skey;
|
||||||
|
g_DBPointer->Records[count].Value = svalue;
|
||||||
|
g_DBPointer->Count = count + 1;
|
||||||
|
} else if (_stricmp(key, "COMMAND") == 0) {
|
||||||
|
g_DBPointer->Records[254].Key = skey;
|
||||||
|
g_DBPointer->Records[254].Value = svalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_ResetValues(lua_State* localL) {
|
||||||
|
if (g_DBPointer == NULL) return 0;
|
||||||
|
g_DBPointer->Count = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_CountRecords(lua_State* localL) {
|
||||||
|
if (g_DBPointer == NULL) {
|
||||||
|
lua_pushinteger(localL, 0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = g_DBPointer->Count;
|
||||||
|
if (count > 255) count = 255;
|
||||||
|
lua_pushinteger(localL, count);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_CharConv(lua_State* localL) {
|
||||||
|
int ch = luaL_checkinteger(localL, 1);
|
||||||
|
lua_pushinteger(localL, CharConv((unsigned char)ch));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_SaveString(lua_State* localL) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else if(lua_isnil(localL, 1)) WritePrivateProfileStringA(section, key, value, workspaceFile);
|
||||||
|
else return luaL_typeerror(localL, 1, lua_typename(localL, LUA_TSTRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
int lua_LoadString(lua_State* localL) {
|
||||||
|
const char* section = luaL_checkstring(localL, 2);
|
||||||
|
const char* key = luaL_checkstring(localL, 3);
|
||||||
|
const char* defaultValue = luaL_optstring(localL, 4, "");
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
return 1;
|
||||||
|
} else return luaL_typeerror(localL, 1, lua_typename(localL, LUA_TSTRING));
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include "lua.h"
|
||||||
|
#include "lualib.h"
|
||||||
|
#include "lauxlib.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
extern HFONT g_hCurrentFont;
|
||||||
|
#define FONT_NAME "Segoe UI"
|
||||||
|
|
||||||
|
extern HWND hEditControl;
|
||||||
|
extern uint8_t console_mode;
|
||||||
|
extern uint8_t sticky;
|
||||||
|
extern TDB* g_DBPointer;
|
||||||
|
extern unsigned char workspaceFile[MAX_PATH];
|
||||||
|
|
||||||
|
int lua_log(lua_State* localL);
|
||||||
|
int lua_set_console(lua_State* localL);
|
||||||
|
int lua_set_console_mode(lua_State* localL);
|
||||||
|
int lua_set_window_stick(lua_State* localL);
|
||||||
|
int lua_get_window_stick(lua_State* localL);
|
||||||
|
int lua_set_font_size(lua_State* localL);
|
||||||
|
int lua_MessageBox(lua_State* localL);
|
||||||
|
int lua_ReadValue(lua_State* localL);
|
||||||
|
int lua_ReadRecord(lua_State* localL);
|
||||||
|
int lua_AddValue(lua_State* localL);
|
||||||
|
int lua_ResetValues(lua_State* localL);
|
||||||
|
int lua_CountRecords(lua_State* localL);
|
||||||
|
int lua_CharConv(lua_State* localL);
|
||||||
|
int lua_SaveString(lua_State* localL);
|
||||||
|
int lua_LoadString(lua_State* localL);
|
||||||
|
|
||||||
|
extern void AppendText(const char* text);
|
||||||
+384
@@ -0,0 +1,384 @@
|
|||||||
|
#include "api.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static TRDSGroup Group;
|
||||||
|
|
||||||
|
static HWND hWnd = NULL;
|
||||||
|
static HINSTANCE hInst = NULL;
|
||||||
|
|
||||||
|
static lua_State* L = NULL;
|
||||||
|
|
||||||
|
HWND hEditControl = NULL;
|
||||||
|
TDB* g_DBPointer = NULL;
|
||||||
|
|
||||||
|
HFONT g_hCurrentFont = NULL;
|
||||||
|
|
||||||
|
#define IDC_MAIN_BUTTON 101
|
||||||
|
|
||||||
|
#define WINDOW_HEIGHT 380
|
||||||
|
#define WINDOW_WIDTH 620
|
||||||
|
#define BUTTON_COUNT 7
|
||||||
|
|
||||||
|
#define STRINGIFY(x) #x
|
||||||
|
#define TOSTRING(x) STRINGIFY(x)
|
||||||
|
|
||||||
|
#define EVENT_BUTTON(I) \
|
||||||
|
HWND hButton_event##I = CreateWindowEx( \
|
||||||
|
0, "BUTTON", "Event " TOSTRING(I), \
|
||||||
|
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_NOTIFY, \
|
||||||
|
10 + (75 * I), WINDOW_HEIGHT-62, \
|
||||||
|
70, 30, hWnd, \
|
||||||
|
(HMENU)(IDC_MAIN_BUTTON + I), hInst, NULL \
|
||||||
|
);
|
||||||
|
|
||||||
|
uint8_t console_mode = 0;
|
||||||
|
uint8_t stop_execution = 0;
|
||||||
|
uint8_t sticky = 0;
|
||||||
|
unsigned char workspaceFile[MAX_PATH] = "";
|
||||||
|
|
||||||
|
const char* int_to_string(int value) {
|
||||||
|
static char buffer[16];
|
||||||
|
snprintf(buffer, sizeof(buffer), "%d", value);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitLua();
|
||||||
|
void lua_event(int event);
|
||||||
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WM_CLOSE:
|
||||||
|
ShowWindow(hwnd, SW_HIDE);
|
||||||
|
return 0;
|
||||||
|
case WM_COMMAND:
|
||||||
|
if (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED)
|
||||||
|
{
|
||||||
|
int controlId = LOWORD(wParam);
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
if(HIWORD(wParam) == BN_DOUBLECLICKED) offset = BUTTON_COUNT;
|
||||||
|
|
||||||
|
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;
|
||||||
|
case WM_DESTROY:
|
||||||
|
hWnd = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreatePluginWindow(HWND hOwner) {
|
||||||
|
WNDCLASS wc = {0};
|
||||||
|
wc.lpfnWndProc = WndProc;
|
||||||
|
wc.hInstance = hInst;
|
||||||
|
wc.lpszClassName = "LuaHostPlugin";
|
||||||
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||||
|
RegisterClass(&wc);
|
||||||
|
|
||||||
|
hWnd = CreateWindowEx(
|
||||||
|
0,
|
||||||
|
"LuaHostPlugin",
|
||||||
|
"Lua Host Console",
|
||||||
|
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
|
||||||
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||||
|
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||||
|
hOwner,
|
||||||
|
NULL,
|
||||||
|
hInst,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
hEditControl = CreateWindowEx(
|
||||||
|
WS_EX_CLIENTEDGE, "EDIT", "",
|
||||||
|
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
|
||||||
|
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
|
||||||
|
10, 10, WINDOW_WIDTH-25, WINDOW_HEIGHT-75,
|
||||||
|
hWnd, NULL, hInst, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
HWND hButton = CreateWindowEx(
|
||||||
|
0, "BUTTON", "Reload",
|
||||||
|
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_NOTIFY,
|
||||||
|
10, WINDOW_HEIGHT-62,
|
||||||
|
70, 30, hWnd,
|
||||||
|
(HMENU)IDC_MAIN_BUTTON, hInst, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
EVENT_BUTTON(1)
|
||||||
|
EVENT_BUTTON(2)
|
||||||
|
EVENT_BUTTON(3)
|
||||||
|
EVENT_BUTTON(4)
|
||||||
|
EVENT_BUTTON(5)
|
||||||
|
EVENT_BUTTON(6)
|
||||||
|
EVENT_BUTTON(7)
|
||||||
|
|
||||||
|
HFONT hFont = CreateFont(18, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, FONT_NAME);
|
||||||
|
SendMessage(hEditControl, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event1, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event2, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event3, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event4, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event5, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event6, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
SendMessage(hButton_event7, WM_SETFONT, (WPARAM)hFont, TRUE);
|
||||||
|
|
||||||
|
HICON hIconBig = (HICON)SendMessage(hOwner, WM_GETICON, ICON_BIG, 0);
|
||||||
|
HICON hIconSmall = (HICON)SendMessage(hOwner, WM_GETICON, ICON_SMALL, 0);
|
||||||
|
if (!hIconBig) hIconBig = (HICON)GetClassLongPtr(hOwner, GCLP_HICON);
|
||||||
|
if (!hIconSmall) hIconSmall = (HICON)GetClassLongPtr(hOwner, GCLP_HICONSM);
|
||||||
|
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIconBig);
|
||||||
|
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall);
|
||||||
|
|
||||||
|
UpdateWindow(hWnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendText(const char* text) {
|
||||||
|
if (hEditControl != NULL) {
|
||||||
|
int len = GetWindowTextLength(hEditControl);
|
||||||
|
SendMessage(hEditControl, EM_SETSEL, len, len);
|
||||||
|
SendMessageA(hEditControl, EM_REPLACESEL, FALSE, (LPARAM)text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lua_call_command(const char* Cmd, const char* Param) {
|
||||||
|
if(stop_execution != 0) return;
|
||||||
|
lua_getglobal(L, "command");
|
||||||
|
|
||||||
|
if (lua_isfunction(L, -1)) {
|
||||||
|
lua_pushstring(L, Cmd);
|
||||||
|
lua_pushstring(L, Param);
|
||||||
|
if (lua_pcall(L, 2, 0, 0) != LUA_OK) {
|
||||||
|
char msg_buffer[255];
|
||||||
|
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)) {
|
||||||
|
lua_pushinteger(L, Group.RFU & 3);
|
||||||
|
lua_pushboolean(L, (Group.RFU & 0x100) >> 8);
|
||||||
|
lua_pushinteger(L, Group.Blk1);
|
||||||
|
lua_pushinteger(L, Group.Blk2);
|
||||||
|
lua_pushinteger(L, Group.Blk3);
|
||||||
|
lua_pushinteger(L, Group.Blk4);
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
lua_pushinteger(L, event);
|
||||||
|
if (lua_pcall(L, 1, 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), "event");
|
||||||
|
AppendText(msg_buffer);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
stop_execution = 1;
|
||||||
|
}
|
||||||
|
} else lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
__declspec(dllexport) void WINAPI RDSGroup(TRDSGroup* PRDSGroup) {
|
||||||
|
if (PRDSGroup == NULL) return;
|
||||||
|
Group = *PRDSGroup;
|
||||||
|
lua_call_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
__declspec(dllexport) void WINAPI Command(const char* Cmd, const char* Param) {
|
||||||
|
if (Cmd == NULL) return;
|
||||||
|
if (_stricmp(Cmd, "EXIT") == 0) {
|
||||||
|
if(L != NULL) {
|
||||||
|
lua_close(L);
|
||||||
|
L = NULL;
|
||||||
|
}
|
||||||
|
if (hWnd != NULL) {
|
||||||
|
DestroyWindow(hWnd);
|
||||||
|
hWnd = NULL;
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
if(value == 1) ShowWindow(hWnd, SW_SHOW);
|
||||||
|
else if(value == 0) ShowWindow(hWnd, SW_HIDE);
|
||||||
|
int x = GetPrivateProfileIntA("luahost", "Left", 320, 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);
|
||||||
|
} else if (_stricmp(Cmd, "SAVEWORKSPACE") == 0) {
|
||||||
|
if(hWnd != NULL) {
|
||||||
|
RECT rect;
|
||||||
|
if (GetWindowRect(hWnd, &rect)) {
|
||||||
|
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);
|
||||||
|
} else if (_stricmp(Cmd, "LUASCRIPT") == 0) { // custom
|
||||||
|
char msg_buffer[255];
|
||||||
|
if (luaL_loadfile(L, Param) != LUA_OK) {
|
||||||
|
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* WINAPI PluginName(void) { return "Lua Host"; }
|
||||||
|
|
||||||
|
void InitLua() {
|
||||||
|
if(L != NULL) {
|
||||||
|
lua_close(L);
|
||||||
|
L = NULL;
|
||||||
|
}
|
||||||
|
L = luaL_newstate();
|
||||||
|
luaL_openlibs(L);
|
||||||
|
lua_register(L, "set_font_size", lua_set_font_size);
|
||||||
|
lua_register(L, "message_box", lua_MessageBox);
|
||||||
|
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);
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_ReadValue);
|
||||||
|
lua_setfield(L, -2, "read_value");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_ReadRecord);
|
||||||
|
lua_setfield(L, -2, "read_record");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_AddValue);
|
||||||
|
lua_setfield(L, -2, "add_value");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_ResetValues);
|
||||||
|
lua_setfield(L, -2, "reset_values");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_CountRecords);
|
||||||
|
lua_setfield(L, -2, "count_records");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_CharConv);
|
||||||
|
lua_setfield(L, -2, "char_conv");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_SaveString);
|
||||||
|
lua_setfield(L, -2, "save_string");
|
||||||
|
|
||||||
|
lua_pushcfunction(L, lua_LoadString);
|
||||||
|
lua_setfield(L, -2, "load_string");
|
||||||
|
|
||||||
|
lua_setglobal(L, "db");
|
||||||
|
|
||||||
|
console_mode = 0;
|
||||||
|
stop_execution = 0;
|
||||||
|
|
||||||
|
char path[MAX_PATH];
|
||||||
|
char fullPath[MAX_PATH];
|
||||||
|
|
||||||
|
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, path))) snprintf(fullPath, MAX_PATH, "%s\\RDS Spy\\script.lua", path);
|
||||||
|
else {
|
||||||
|
MessageBoxA(NULL, "Could not get the local app data path", "Error", MB_ICONERROR | MB_OK | MB_TOPMOST);
|
||||||
|
AppendText("Could not get the local app data path\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char msg_buffer[255];
|
||||||
|
if (luaL_loadfile(L, fullPath) != LUA_OK) {
|
||||||
|
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 WINAPI Initialize(HANDLE hHandle, TDB* DBPointer) {
|
||||||
|
CreatePluginWindow(hHandle);
|
||||||
|
AppendText(LUA_COPYRIGHT);
|
||||||
|
AppendText("\r\n");
|
||||||
|
g_DBPointer = DBPointer;
|
||||||
|
InitLua();
|
||||||
|
|
||||||
|
return (int)hWnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
|
||||||
|
if (fdwReason == DLL_PROCESS_ATTACH) hInst = hinstDLL;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t len;
|
||||||
|
uint8_t data[255];
|
||||||
|
} ShortString;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ShortString Key;
|
||||||
|
ShortString Value;
|
||||||
|
} TRecord;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t Count;
|
||||||
|
TRecord Records[255];
|
||||||
|
} TDB;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t Year;
|
||||||
|
uint8_t Month;
|
||||||
|
uint8_t Day;
|
||||||
|
uint8_t Hour;
|
||||||
|
uint8_t Minute;
|
||||||
|
uint8_t Second;
|
||||||
|
uint8_t Centisecond;
|
||||||
|
uint16_t RFU;
|
||||||
|
int32_t Blk1;
|
||||||
|
int32_t Blk2;
|
||||||
|
int32_t Blk3;
|
||||||
|
int32_t Blk4;
|
||||||
|
} TRDSGroup;
|
||||||
Reference in New Issue
Block a user