修discordRPC加载失败会崩溃

This commit is contained in:
MrZ_26
2024-09-27 03:37:36 +08:00
parent 423d502aa4
commit 4768df6867

View File

@@ -1,95 +1,23 @@
local appId='1288557386700951554'
local ffi=require"ffi" local ffi=require"ffi"
local RPC_C
-- Get the host os to load correct lib if SYSTEM=='Windows' then
local osname=love.system.getOS() local suc
local discordRPClib=nil suc,RPC_C=pcall(ffi.load,"discord-rpc")
if not (suc and RPC_C) then
-- FFI requires the libraries really be files just sitting in the filesystem. It print("Failed to load Discord-RPC lib",RPC_C)
-- can't load libraries from a .love archive, nor a fused executable on Windows. MES.new('error',"Failed to load Discord-RPC lib")
-- Merely using love.filesystem.getSource() only works when running LOVE with RPC_C=nil
-- the game unarchived from command line, like "love .". end
--
-- The code here setting "source" will set the directory where the game was run
-- from, so FFI can load discordRPC. We assume that the discordRPC library's
-- libs directory is in the same directory as the .love archive; if it's
-- missing, it just won't load.
local source=love.filesystem.getSource()
if string.sub(source, -5)==".love" or love.filesystem.isFused() then
source=love.filesystem.getSourceBaseDirectory()
end end
if osname=="Linux" then local RPC
discordRPClib=ffi.load(source.."/libs/discord-rpc.so") if RPC_C then
elseif osname=="OS X" then RPC={}
discordRPClib=ffi.load(source.."/libs/discord-rpc.dylib")
elseif osname=="Windows" then
-- I would strongly advise never touching this. It was not trivial to get correct. -nightmareci
ffi.cdef[[ ffi.cdef[[
typedef uint32_t DWORD; typedef struct DiscordRichPresence {
typedef char CHAR;
typedef CHAR *LPSTR;
typedef const CHAR *LPCSTR;
typedef wchar_t WCHAR;
typedef WCHAR *LPWSTR;
typedef LPWSTR PWSTR;
typedef const WCHAR *LPCWSTR;
static const DWORD CP_UTF8 = 65001;
int32_t MultiByteToWideChar(
DWORD CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int32_t cbMultiByte,
LPWSTR lpWideCharStr,
int32_t cchWideChar
);
int32_t WideCharToMultiByte(
DWORD CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int32_t cchWideChar,
LPSTR lpMultiByteStr,
int32_t cbMultiByte,
void* lpDefaultChar,
void* lpUsedDefaultChar
);
DWORD GetShortPathNameW(
LPCWSTR lpszLongPath,
LPWSTR lpszShortPath,
DWORD cchBuffer
);
]]
local originalWideSize=ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, nil, 0)
local originalWide=ffi.new('WCHAR[?]', originalWideSize)
ffi.C.MultiByteToWideChar(ffi.C.CP_UTF8, 0, source, -1, originalWide, originalWideSize)
local sourceSize=ffi.C.GetShortPathNameW(originalWide, nil, 0)
local sourceWide=ffi.new('WCHAR[?]', sourceSize)
ffi.C.GetShortPathNameW(originalWide, sourceWide, sourceSize)
local sourceChar=ffi.new('char[?]', sourceSize)
ffi.C.WideCharToMultiByte(ffi.C.CP_UTF8, 0, sourceWide, sourceSize, sourceChar, sourceSize, nil, nil)
discordRPClib=ffi.load("discord-rpc.dll")
-- source = ffi.string(sourceChar)
-- if jit.arch == "x86" then
-- discordRPClib = ffi.load(source.."/libs/discord-rpc_x86.dll")
-- elseif jit.arch == "x64" then
-- discordRPClib = ffi.load(source.."/libs/discord-rpc_x64.dll")
-- end
else
-- Else it crashes later on
error(string.format("Discord rpc not supported on platform (%s)", osname))
end
ffi.cdef[[
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */ const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */ const char* details; /* max 128 bytes */
int64_t startTimestamp; int64_t startTimestamp;
@@ -105,132 +33,121 @@ typedef struct DiscordRichPresence {
const char* joinSecret; /* max 128 bytes */ const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */ const char* spectateSecret; /* max 128 bytes */
int8_t instance; int8_t instance;
} DiscordRichPresence; } DiscordRichPresence;
typedef struct DiscordUser { typedef struct DiscordUser {
const char* userId; const char* userId;
const char* username; const char* username;
const char* discriminator; const char* discriminator;
const char* avatar; const char* avatar;
} DiscordUser; } DiscordUser;
typedef void (*readyPtr)(const DiscordUser* request); typedef void (*readyPtr)(const DiscordUser* request);
typedef void (*disconnectedPtr)(int errorCode, const char* message); typedef void (*disconnectedPtr)(int errorCode, const char* message);
typedef void (*erroredPtr)(int errorCode, const char* message); typedef void (*erroredPtr)(int errorCode, const char* message);
typedef void (*joinGamePtr)(const char* joinSecret); typedef void (*joinGamePtr)(const char* joinSecret);
typedef void (*spectateGamePtr)(const char* spectateSecret); typedef void (*spectateGamePtr)(const char* spectateSecret);
typedef void (*joinRequestPtr)(const DiscordUser* request); typedef void (*joinRequestPtr)(const DiscordUser* request);
typedef struct DiscordEventHandlers { typedef struct DiscordEventHandlers {
readyPtr ready; readyPtr ready;
disconnectedPtr disconnected; disconnectedPtr disconnected;
erroredPtr errored; erroredPtr errored;
joinGamePtr joinGame; joinGamePtr joinGame;
spectateGamePtr spectateGame; spectateGamePtr spectateGame;
joinRequestPtr joinRequest; joinRequestPtr joinRequest;
} DiscordEventHandlers; } DiscordEventHandlers;
void Discord_Initialize(const char* applicationId, void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers, DiscordEventHandlers* handlers,
int autoRegister, int autoRegister,
const char* optionalSteamId); const char* optionalSteamId);
void Discord_Shutdown(void);
void Discord_RunCallbacks(void);
void Discord_UpdatePresence(const DiscordRichPresence* presence);
void Discord_ClearPresence(void);
void Discord_Respond(const char* userid, int reply);
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
]]
void Discord_Shutdown(void); local function unpackDiscordUser(request)
void Discord_RunCallbacks(void);
void Discord_UpdatePresence(const DiscordRichPresence* presence);
void Discord_ClearPresence(void);
void Discord_Respond(const char* userid, int reply);
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
]]
local RPC={} -- module table
-- proxy to detect garbage collection of the module
RPC.gcDummy=newproxy(true)
local function unpackDiscordUser(request)
return ffi.string(request.userId),ffi.string(request.username), return ffi.string(request.userId),ffi.string(request.username),
ffi.string(request.discriminator),ffi.string(request.avatar) ffi.string(request.discriminator),ffi.string(request.avatar)
end end
-- callback proxies -- callback proxies
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them -- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
-- luajit.org/ext_ffi_semantics.html -- luajit.org/ext_ffi_semantics.html
local ready_proxy=ffi.cast("readyPtr", function(request) local ready_proxy=ffi.cast("readyPtr",function(request)
if RPC.ready then if RPC.ready then
RPC.ready(unpackDiscordUser(request)) RPC.ready(unpackDiscordUser(request))
end end
end) end)
local disconnected_proxy=ffi.cast("disconnectedPtr", function(errorCode,message) local disconnected_proxy=ffi.cast("disconnectedPtr",function(errorCode,message)
if RPC.disconnected then if RPC.disconnected then
RPC.disconnected(errorCode, ffi.string(message)) RPC.disconnected(errorCode,ffi.string(message))
end end
end) end)
local errored_proxy=ffi.cast("erroredPtr", function(errorCode,message) local errored_proxy=ffi.cast("erroredPtr",function(errorCode,message)
if RPC.errored then if RPC.errored then
RPC.errored(errorCode, ffi.string(message)) RPC.errored(errorCode,ffi.string(message))
end end
end) end)
local joinGame_proxy=ffi.cast("joinGamePtr", function(joinSecret) local joinGame_proxy=ffi.cast("joinGamePtr",function(joinSecret)
if RPC.joinGame then if RPC.joinGame then
RPC.joinGame(ffi.string(joinSecret)) RPC.joinGame(ffi.string(joinSecret))
end end
end) end)
local spectateGame_proxy=ffi.cast("spectateGamePtr", function(spectateSecret) local spectateGame_proxy=ffi.cast("spectateGamePtr",function(spectateSecret)
if RPC.spectateGame then if RPC.spectateGame then
RPC.spectateGame(ffi.string(spectateSecret)) RPC.spectateGame(ffi.string(spectateSecret))
end end
end) end)
local joinRequest_proxy=ffi.cast("joinRequestPtr", function(request) local joinRequest_proxy=ffi.cast("joinRequestPtr",function(request)
if RPC.joinRequest then if RPC.joinRequest then
RPC.joinRequest(unpackDiscordUser(request)) RPC.joinRequest(unpackDiscordUser(request))
end end
end) end)
-- helpers -- helpers
local function checkArg(arg,argType,argName,func,maybeNil) local function checkArg(arg,argType,argName,func,maybeNil)
assert(type(arg)==argType or (maybeNil and arg==nil), assert(type(arg)==argType or (maybeNil and arg==nil),
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"", string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
argName, func, argType)) argName,func,argType))
end end
local function checkStrArg(arg,maxLen,argName,func,maybeNil) local function checkStrArg(arg,maxLen,argName,func,maybeNil)
if maxLen then if maxLen then
assert(type(arg)=="string" and arg:len()<=maxLen or (maybeNil and arg==nil), assert(type(arg)=="string" and arg:len()<=maxLen or (maybeNil and arg==nil),
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d", string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
argName, func, maxLen)) argName,func,maxLen))
else else
checkArg(arg, "string", argName, func, true) checkArg(arg,"string",argName,func,true)
end
end end
end
local function checkIntArg(arg,maxBits,argName,func,maybeNil) local function checkIntArg(arg,maxBits,argName,func,maybeNil)
maxBits=math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53 maxBits=math.min(maxBits or 32,52) -- lua number (double) can only store integers < 2^53
local maxVal=2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use local maxVal=2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
assert(type(arg)=="number" and math.floor(arg)==arg assert(type(arg)=="number" and math.floor(arg)==arg
and arg<maxVal and arg>=-maxVal and arg<maxVal and arg>=-maxVal
or (maybeNil and arg==nil), or (maybeNil and arg==nil),
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d", string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
argName, func, maxVal)) argName,func,maxVal))
end end
-- function wrappers -- function wrappers
function RPC.initialize(applicationId,autoRegister,optionalSteamId) function RPC.initialize(applicationId,autoRegister,optionalSteamId)
local func="discordRPC.Initialize" local func="discordRPC.Initialize"
checkStrArg(applicationId, nil, "applicationId", func) checkStrArg(applicationId,nil,"applicationId",func)
checkArg(autoRegister, "boolean", "autoRegister", func) checkArg(autoRegister,"boolean","autoRegister",func)
if optionalSteamId~=nil then if optionalSteamId~=nil then
checkStrArg(optionalSteamId, nil, "optionalSteamId", func) checkStrArg(optionalSteamId,nil,"optionalSteamId",func)
end end
local eventHandlers=ffi.new("struct DiscordEventHandlers") local eventHandlers=ffi.new("struct DiscordEventHandlers")
@@ -241,52 +158,52 @@ function RPC.initialize(applicationId,autoRegister,optionalSteamId)
eventHandlers.spectateGame=spectateGame_proxy eventHandlers.spectateGame=spectateGame_proxy
eventHandlers.joinRequest=joinRequest_proxy eventHandlers.joinRequest=joinRequest_proxy
discordRPClib.Discord_Initialize(applicationId, eventHandlers, RPC_C.Discord_Initialize(applicationId,eventHandlers,
autoRegister and 1 or 0, optionalSteamId) autoRegister and 1 or 0,optionalSteamId)
end end
function RPC.shutdown() function RPC.shutdown()
discordRPClib.Discord_Shutdown() RPC_C.Discord_Shutdown()
end end
function RPC.runCallbacks() function RPC.runCallbacks()
discordRPClib.Discord_RunCallbacks() RPC_C.Discord_RunCallbacks()
end end
-- http://luajit.org/ext_ffi_semantics.html#callback : -- http://luajit.org/ext_ffi_semantics.html#callback :
-- It is not allowed, to let an FFI call into a C function (runCallbacks) -- It is not allowed, to let an FFI call into a C function (runCallbacks)
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready). -- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
-- Usually this attempt is caught by the interpreter first and the C function -- Usually this attempt is caught by the interpreter first and the C function
-- is blacklisted for compilation. -- is blacklisted for compilation.
-- solution: -- solution:
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for -- "Then you'll need to manually turn off JIT-compilation with jit.off() for
-- the surrounding Lua function that invokes such a message polling function." -- the surrounding Lua function that invokes such a message polling function."
jit.off(RPC.runCallbacks) jit.off(RPC.runCallbacks)
function RPC.updatePresence(presence) function RPC.updatePresence(presence)
local func="discordRPC.updatePresence" local func="discordRPC.updatePresence"
checkArg(presence, "table", "presence", func) checkArg(presence,"table","presence",func)
-- -1 for string length because of 0-termination -- -1 for string length because of 0-termination
checkStrArg(presence.state, 127, "presence.state", func, true) checkStrArg(presence.state,127,"presence.state",func,true)
checkStrArg(presence.details, 127, "presence.details", func, true) checkStrArg(presence.details,127,"presence.details",func,true)
checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true) checkIntArg(presence.startTimestamp,64,"presence.startTimestamp",func,true)
checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true) checkIntArg(presence.endTimestamp,64,"presence.endTimestamp",func,true)
checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true) checkStrArg(presence.largeImageKey,31,"presence.largeImageKey",func,true)
checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true) checkStrArg(presence.largeImageText,127,"presence.largeImageText",func,true)
checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true) checkStrArg(presence.smallImageKey,31,"presence.smallImageKey",func,true)
checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true) checkStrArg(presence.smallImageText,127,"presence.smallImageText",func,true)
checkStrArg(presence.partyId, 127, "presence.partyId", func, true) checkStrArg(presence.partyId,127,"presence.partyId",func,true)
checkIntArg(presence.partySize, 32, "presence.partySize", func, true) checkIntArg(presence.partySize,32,"presence.partySize",func,true)
checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true) checkIntArg(presence.partyMax,32,"presence.partyMax",func,true)
checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true) checkStrArg(presence.matchSecret,127,"presence.matchSecret",func,true)
checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true) checkStrArg(presence.joinSecret,127,"presence.joinSecret",func,true)
checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true) checkStrArg(presence.spectateSecret,127,"presence.spectateSecret",func,true)
checkIntArg(presence.instance, 8, "presence.instance", func, true) checkIntArg(presence.instance,8,"presence.instance",func,true)
local cpresence=ffi.new("struct DiscordRichPresence") local cpresence=ffi.new("struct DiscordRichPresence")
cpresence.state=presence.state cpresence.state=presence.state
@@ -305,28 +222,29 @@ function RPC.updatePresence(presence)
cpresence.spectateSecret=presence.spectateSecret cpresence.spectateSecret=presence.spectateSecret
cpresence.instance=presence.instance or 0 cpresence.instance=presence.instance or 0
discordRPClib.Discord_UpdatePresence(cpresence) RPC_C.Discord_UpdatePresence(cpresence)
end end
function RPC.clearPresence() function RPC.clearPresence()
discordRPClib.Discord_ClearPresence() RPC_C.Discord_ClearPresence()
end end
local replyMap={ local replyMap={
no=0, no=0,
yes=1, yes=1,
ignore=2, ignore=2,
} }
-- maybe let reply take ints too (0, 1, 2) and add constants to the module -- maybe let reply take ints too (0, 1, 2) and add constants to the module
function RPC.respond(userId,reply) function RPC.respond(userId,reply)
checkStrArg(userId, nil, "userId", "discordRPC.respond") checkStrArg(userId,nil,"userId","discordRPC.respond")
assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"") assert(replyMap[reply],"Argument 'reply' to discordRPC.respond must be 'yes'|'no'|'ignore'")
discordRPClib.Discord_Respond(userId, replyMap[reply]) RPC_C.Discord_Respond(userId,replyMap[reply])
end end
-- garbage collection callback -- garbage collection callback
getmetatable(RPC.gcDummy).__gc=function() RPC.gcDummy=newproxy(true)
getmetatable(RPC.gcDummy).__gc=function()
RPC.shutdown() RPC.shutdown()
ready_proxy:free() ready_proxy:free()
disconnected_proxy:free() disconnected_proxy:free()
@@ -334,10 +252,32 @@ getmetatable(RPC.gcDummy).__gc=function()
joinGame_proxy:free() joinGame_proxy:free()
spectateGame_proxy:free() spectateGame_proxy:free()
joinRequest_proxy:free() joinRequest_proxy:free()
end
function RPC.ready(userId,username,discriminator,avatar)
print(string.format("Discord: ready (%s,%s,%s,%s)",userId,username,discriminator,avatar))
end
function RPC.disconnected(errorCode,message)
print(string.format("Discord: disconnected (%d: %s)",errorCode,message))
end
function RPC.errored(errorCode,message)
print(string.format("Discord: error (%d: %s)",errorCode,message))
end
function RPC.joinGame(joinSecret)
print(string.format("Discord: join (%s)",joinSecret))
end
function RPC.spectateGame(spectateSecret)
print(string.format("Discord: spectate (%s)",spectateSecret))
end
function RPC.joinRequest(userId,username,discriminator,avatar)
print(string.format("Discord: join request (%s,%s,%s,%s)",userId,username,discriminator,avatar))
RPC.respond(userId,'yes')
end
RPC.initialize(appId,true)
end end
local MyRPC={ local MyRPC={
appId='1288557386700951554', C=RPC_C,
RPC=RPC, RPC=RPC,
presence={ presence={
startTimestamp=os.time(), startTimestamp=os.time(),
@@ -349,51 +289,28 @@ local MyRPC={
smallImageText="", smallImageText="",
}, },
} }
if RPC then
function RPC.ready(userId,username,discriminator,avatar)
print(string.format("Discord: ready (%s,%s,%s,%s)",userId,username,discriminator,avatar))
end
function RPC.disconnected(errorCode,message)
print(string.format("Discord: disconnected (%d: %s)",errorCode,message))
end
function RPC.errored(errorCode,message)
print(string.format("Discord: error (%d: %s)",errorCode,message))
end
function RPC.joinGame(joinSecret)
print(string.format("Discord: join (%s)",joinSecret))
end
function RPC.spectateGame(spectateSecret)
print(string.format("Discord: spectate (%s)",spectateSecret))
end
function RPC.joinRequest(userId,username,discriminator,avatar)
print(string.format("Discord: join request (%s,%s,%s,%s)",userId,username,discriminator,avatar))
RPC.respond(userId,'yes')
end
RPC.initialize(MyRPC.appId,true)
else
print("DiscordRPC loading error")
print(RPC)
end
---@class DiscordRPC.presence ---@class DiscordRPC.presence
---@field startTimestamp? number
---@field details? string
---@field state? string ---@field state? string
---@field details? string
---@field startTimestamp? number
---@field endTimestamp? number
---@field largeImageKey? string ---@field largeImageKey? string
---@field largeImageText? string ---@field largeImageText? string
---@field smallImageKey? string ---@field smallImageKey? string
---@field smallImageText? string ---@field smallImageText? string
---@field partyId? string
---@field partySize? number
---@field partyMax? number
---@field matchSecret? string
---@field joinSecret? string
---@field spectateSecret? string
---@field instance? number
---@overload fun()
---@overload fun(state: DiscordRPC.presence)
---@param state string ---@param state string
---@param details string ---@param details string
---@overload fun(new: DiscordRPC.presence)
---@overload fun()
function MyRPC.update(state,details) function MyRPC.update(state,details)
if state then if state then
for k,v in next, for k,v in next,