diff options
-rw-r--r-- | main.lua | 52 | ||||
-rw-r--r-- | metadata.xml | 8 | ||||
-rw-r--r-- | websocket.lua | 249 |
3 files changed, 309 insertions, 0 deletions
diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..fe9ea57 --- /dev/null +++ b/main.lua | |||
@@ -0,0 +1,52 @@ | |||
1 | local funsaac = RegisterMod("funsaac", 1) | ||
2 | local json = require("json") | ||
3 | |||
4 | requestServerInfo = { | ||
5 | Id = 1, | ||
6 | ClientName = "Funsaac", | ||
7 | MessageVersion = 1 | ||
8 | } | ||
9 | |||
10 | -- | ||
11 | -- local client = require("websocket").new("192.168.178.28", 12345) | ||
12 | local client = require("websocket").new("127.0.0.1", 12345) | ||
13 | -- local msg = '"{ RequestServerInfo": ' .. json.encode(requestServerInfo) .. "}" | ||
14 | -- client:send(msg) | ||
15 | -- print(client:read()) | ||
16 | -- function client:onopen() | ||
17 | -- client:update() | ||
18 | -- res = client:read() | ||
19 | -- print(res) | ||
20 | -- local msg = '[{"RequestServerInfo": ' .. json.encode(requestServerInfo) .. "}]" | ||
21 | -- print(msg) | ||
22 | -- client:send(msg) | ||
23 | -- -- local msg = '"[{ RequestServerInfo": ' .. json.encode(requestServerInfo) .. "}]" | ||
24 | -- -- client:send(msg) | ||
25 | -- -- print(client:read()) | ||
26 | -- -- client:send("") | ||
27 | -- end | ||
28 | --client:update() | ||
29 | |||
30 | local function onEnemyDamage(_, entity, amount, damage_flags, source, countdown_frames) | ||
31 | print(reqestServerInfo) | ||
32 | client:update() | ||
33 | local msg = '[{"RequestServerInfo": ' .. json.encode(requestServerInfo) .. "}]" | ||
34 | print(msg) | ||
35 | client:send(msg) | ||
36 | print(client:read()) | ||
37 | client:update() | ||
38 | end | ||
39 | |||
40 | local function onStart() | ||
41 | client:update() | ||
42 | res = client:read() | ||
43 | -- print(res) | ||
44 | local msg = '[{"RequestServerInfo": ' .. json.encode(requestServerInfo) .. "}]" | ||
45 | print(msg) | ||
46 | client:send(msg) | ||
47 | local t = client:read() | ||
48 | print(t) | ||
49 | end | ||
50 | |||
51 | funsaac:AddCallback(ModCallbacks.MC_ENTITY_TAKE_DMG, onEnemyDamage) | ||
52 | funsaac:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, onStart) | ||
diff --git a/metadata.xml b/metadata.xml new file mode 100644 index 0000000..a63fa30 --- /dev/null +++ b/metadata.xml | |||
@@ -0,0 +1,8 @@ | |||
1 | <metadata> | ||
2 | <name>funsaac</name> | ||
3 | <directory>funsaac</directory> | ||
4 | <description/> | ||
5 | <version>1.0</version> | ||
6 | <visibility/> | ||
7 | </metadata> | ||
8 | |||
diff --git a/websocket.lua b/websocket.lua new file mode 100644 index 0000000..670eff5 --- /dev/null +++ b/websocket.lua | |||
@@ -0,0 +1,249 @@ | |||
1 | --[[ | ||
2 | websocket client pure lua implement for love2d | ||
3 | by flaribbit | ||
4 | |||
5 | usage: | ||
6 | local client = require("websocket").new("127.0.0.1", 5000) | ||
7 | function client:onmessage(s) print(s) end | ||
8 | function client:onopen() self:send("hello from love2d") end | ||
9 | function client:onclose() print("closed") end | ||
10 | |||
11 | function love.update() | ||
12 | client:update() | ||
13 | end | ||
14 | ]] | ||
15 | |||
16 | local socket = require"socket" | ||
17 | local seckey = "osT3F7mvlojIvf3/8uIsJQ==" | ||
18 | |||
19 | local OPCODE = { | ||
20 | CONTINUE = 0, | ||
21 | TEXT = 1, | ||
22 | BINARY = 2, | ||
23 | CLOSE = 8, | ||
24 | PING = 9, | ||
25 | PONG = 10, | ||
26 | } | ||
27 | |||
28 | local STATUS = { | ||
29 | CONNECTING = 0, | ||
30 | OPEN = 1, | ||
31 | CLOSING = 2, | ||
32 | CLOSED = 3, | ||
33 | TCPOPENING = 4, | ||
34 | } | ||
35 | |||
36 | ---@class wsclient | ||
37 | ---@field socket table | ||
38 | ---@field url table | ||
39 | ---@field _head integer|nil | ||
40 | local _M = { | ||
41 | OPCODE = OPCODE, | ||
42 | STATUS = STATUS, | ||
43 | } | ||
44 | _M.__index = _M | ||
45 | function _M:onopen() end | ||
46 | function _M:onmessage(message) end | ||
47 | function _M:onerror(error) end | ||
48 | function _M:onclose(code, reason) end | ||
49 | |||
50 | ---create websocket connection | ||
51 | ---@param host string | ||
52 | ---@param port integer | ||
53 | ---@param path string | ||
54 | ---@return wsclient | ||
55 | function _M.new(host, port, path) | ||
56 | local m = { | ||
57 | url = { | ||
58 | host = host, | ||
59 | port = port, | ||
60 | path = path or "/", | ||
61 | }, | ||
62 | _continue = "", | ||
63 | _buffer = "", | ||
64 | _length = 0, | ||
65 | _head = nil, | ||
66 | status = STATUS.TCPOPENING, | ||
67 | socket = socket.tcp(), | ||
68 | } | ||
69 | m.socket:settimeout(0) | ||
70 | m.socket:connect(host, port) | ||
71 | setmetatable(m, _M) | ||
72 | return m | ||
73 | end | ||
74 | |||
75 | local mask_key = {1, 14, 5, 14} | ||
76 | local function send(sock, opcode, message) | ||
77 | -- message type | ||
78 | sock:send(string.char((0x80 | opcode))) | ||
79 | |||
80 | -- empty message | ||
81 | if not message then | ||
82 | sock:send(string.char(0x80, table.unpack(mask_key))) | ||
83 | return 0 | ||
84 | end | ||
85 | |||
86 | -- message length | ||
87 | local length = #message | ||
88 | if length>65535 then | ||
89 | sock:send(string.char(127 & 0x80), | ||
90 | 0, 0, 0, 0, | ||
91 | ((length >> 24) & 0xff), | ||
92 | ((length >> 16) & 0xff), | ||
93 | ((length >> 8) & 0xff), | ||
94 | (length & 0xff)) | ||
95 | elseif length>125 then | ||
96 | sock:send(string.char((126 | 0x80), | ||
97 | ((length >> 8) & 0xff), | ||
98 | (length & 0xff))) | ||
99 | else | ||
100 | sock:send(string.char((length | 0x80))) | ||
101 | end | ||
102 | |||
103 | -- message | ||
104 | sock:send(string.char(table.unpack(mask_key))) | ||
105 | local msgbyte = {message:byte(1, length)} | ||
106 | for i = 1, length do | ||
107 | msgbyte[i] = (msgbyte[i] ~ mask_key[(i-1)%4+1]) | ||
108 | end | ||
109 | return sock:send(string.char(table.unpack(msgbyte))) | ||
110 | end | ||
111 | |||
112 | ---read a message | ||
113 | ---@return string|nil res message | ||
114 | ---@return number|nil head websocket frame header | ||
115 | ---@return string|nil err error message | ||
116 | function _M:read() | ||
117 | local res, err, part | ||
118 | ::RECIEVE:: | ||
119 | res, err, part = self.socket:receive(self._length-#self._buffer) | ||
120 | if err=="closed" then return nil, nil, err end | ||
121 | if part or res then | ||
122 | self._buffer = self._buffer..(part or res) | ||
123 | else | ||
124 | return nil, nil, nil | ||
125 | end | ||
126 | if not self._head then | ||
127 | if #self._buffer<2 then | ||
128 | return nil, nil, "buffer length less than 2" | ||
129 | end | ||
130 | local length = (self._buffer:byte(2) & 0x7f) | ||
131 | if length==126 then | ||
132 | if self._length==2 then self._length = 4 goto RECIEVE end | ||
133 | if #self._buffer<4 then | ||
134 | return nil, nil, "buffer length less than 4" | ||
135 | end | ||
136 | local b1, b2 = self._buffer:byte(3, 4) | ||
137 | self._length = (b1 << 8) + b2 | ||
138 | elseif length==127 then | ||
139 | if self._length==2 then self._length = 10 goto RECIEVE end | ||
140 | if #self._buffer<10 then | ||
141 | return nil, nil, "buffer length less than 10" | ||
142 | end | ||
143 | local b5, b6, b7, b8 = self._buffer:byte(7, 10) | ||
144 | self._length = (b5 << 24) + (b6 << 16) + (b7 << 8) + b8 | ||
145 | else | ||
146 | self._length = length | ||
147 | end | ||
148 | self._head, self._buffer = self._buffer:byte(1), "" | ||
149 | if length>0 then goto RECIEVE end | ||
150 | end | ||
151 | if #self._buffer>=self._length then | ||
152 | local ret, head = self._buffer, self._head | ||
153 | self._length, self._buffer, self._head = 2, "", nil | ||
154 | return ret, head, nil | ||
155 | else | ||
156 | return nil, nil, "buffer length less than "..self._length | ||
157 | end | ||
158 | end | ||
159 | |||
160 | ---send a message | ||
161 | ---@param message string | ||
162 | function _M:send(message) | ||
163 | send(self.socket, OPCODE.TEXT, message) | ||
164 | end | ||
165 | |||
166 | ---send a ping message | ||
167 | ---@param message string | ||
168 | function _M:ping(message) | ||
169 | send(self.socket, OPCODE.PING, message) | ||
170 | end | ||
171 | |||
172 | ---send a pong message (no need) | ||
173 | ---@param message any | ||
174 | function _M:pong(message) | ||
175 | send(self.socket, OPCODE.PONG, message) | ||
176 | end | ||
177 | |||
178 | ---update client status | ||
179 | function _M:update() | ||
180 | local sock = self.socket | ||
181 | if self.status==STATUS.TCPOPENING then | ||
182 | local url = self.url | ||
183 | local _, err = sock:connect(url.host, url.port) | ||
184 | self._length = self._length+1 | ||
185 | if err=="already connected" then | ||
186 | sock:send( | ||
187 | "GET "..url.path.." HTTP/1.1\r\n".. | ||
188 | "Host: "..url.host..":"..url.port.."\r\n".. | ||
189 | "Connection: Upgrade\r\n".. | ||
190 | "Upgrade: websocket\r\n".. | ||
191 | "Sec-WebSocket-Version: 13\r\n".. | ||
192 | "Sec-WebSocket-Key: "..seckey.."\r\n\r\n") | ||
193 | self.status = STATUS.CONNECTING | ||
194 | self._length = 2 | ||
195 | elseif self._length>600 then | ||
196 | self:onerror("connection failed") | ||
197 | self.status = STATUS.CLOSED | ||
198 | end | ||
199 | elseif self.status==STATUS.CONNECTING then | ||
200 | local res = sock:receive("*l") | ||
201 | if res then | ||
202 | repeat res = sock:receive("*l") until res=="" | ||
203 | self:onopen() | ||
204 | self.status = STATUS.OPEN | ||
205 | end | ||
206 | elseif self.status==STATUS.OPEN or self.status==STATUS.CLOSING then | ||
207 | while true do | ||
208 | local res, head, err = self:read() | ||
209 | if err=="closed" then | ||
210 | self.status = STATUS.CLOSED | ||
211 | return | ||
212 | elseif res==nil then | ||
213 | return | ||
214 | end | ||
215 | local opcode = (head & 0x0f) | ||
216 | local fin = (head & 0x80)==0x80 | ||
217 | if opcode==OPCODE.CLOSE then | ||
218 | if res~="" then | ||
219 | local code = (res:byte(1) << 8) + res:byte(2) | ||
220 | self:onclose(code, res:sub(3)) | ||
221 | else | ||
222 | self:onclose(1005, "") | ||
223 | end | ||
224 | sock:close() | ||
225 | self.status = STATUS.CLOSED | ||
226 | elseif opcode==OPCODE.PING then self:pong(res) | ||
227 | elseif opcode==OPCODE.CONTINUE then | ||
228 | self._continue = self._continue..res | ||
229 | if fin then self:onmessage(self._continue) end | ||
230 | else | ||
231 | if fin then self:onmessage(res) else self._continue = res end | ||
232 | end | ||
233 | end | ||
234 | end | ||
235 | end | ||
236 | |||
237 | ---close websocket connection | ||
238 | ---@param code integer|nil | ||
239 | ---@param message string|nil | ||
240 | function _M:close(code, message) | ||
241 | if code and message then | ||
242 | send(self.socket, OPCODE.CLOSE, string.char((code >> 8), (code & 0xff))..message) | ||
243 | else | ||
244 | send(self.socket, OPCODE.CLOSE, nil) | ||
245 | end | ||
246 | self.status = STATUS.CLOSING | ||
247 | end | ||
248 | |||
249 | return _M | ||