Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
ygopro-router
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
ygopro-router
Commits
8ef0f9c9
Commit
8ef0f9c9
authored
Jun 20, 2025
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix STOC_ERROR_MSG handling
parent
36e5a990
Pipeline
#37878
passed with stages
in 2 minutes and 35 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
58 additions
and
19 deletions
+58
-19
main.py
main.py
+58
-19
No files found.
main.py
View file @
8ef0f9c9
import
asyncio
import
struct
from
asyncio
import
StreamReader
,
StreamWriter
from
typing
import
List
,
Tuple
from
typing
import
List
,
Tuple
,
Union
import
yaml
import
ipaddress
...
...
@@ -80,6 +80,8 @@ STOC_HS_PLAYER_ENTER = 0x20
async
def
handle_client
(
reader
:
StreamReader
,
writer
:
StreamWriter
):
remote_reader
:
StreamReader
=
None
remote_writer
:
StreamWriter
=
None
async
def
stoc_send
(
proto
:
int
,
data
:
bytes
):
"""Send a STOC packet to the client."""
packet
=
prepare_message
(
proto
,
data
)
...
...
@@ -116,15 +118,27 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
return
packet_id
,
payload
logger
.
debug
(
f
"Received unexpected packet ID {packet_id}, expected one of {accepted_ids}. Ignoring."
)
class
State
:
established
=
False
state
=
State
()
async
def
close_connection
():
try
:
await
stoc_send
(
STOC_ERROR_MSG
,
struct
.
pack
(
'<BBBBI'
,
1
,
0
,
0
,
0
,
9
))
if
not
state
.
established
:
await
stoc_send
(
STOC_ERROR_MSG
,
struct
.
pack
(
'<BBBBI'
,
1
,
0
,
0
,
0
,
9
))
except
Exception
as
e
:
logger
.
warning
(
f
"Failed to send error message before closing: {e}"
)
writer
.
close
()
await
writer
.
wait_closed
()
if
remote_writer
:
remote_writer
.
close
()
try
:
await
asyncio
.
gather
(
writer
.
wait_closed
(),
remote_writer
.
wait_closed
()
if
remote_writer
else
asyncio
.
sleep
(
0
))
except
Exception
as
e
:
logger
.
warning
(
f
"Error closing connections for {player_descriptor}: {e}"
)
peer_ip
=
writer
.
get_extra_info
(
"peername"
)[
0
]
player_descriptor
=
f
"Unknown Player ({peer_ip})"
is_proxy
=
is_trusted
(
peer_ip
)
try
:
...
...
@@ -138,10 +152,15 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
external_address_payload
=
first_payload
_
,
player_info_payload
=
await
asyncio
.
wait_for
(
ctos_read_filter
([
CTOS_PLAYER_INFO
],
40
,
40
),
5.0
)
pre_packets
.
append
((
CTOS_PLAYER_INFO
,
player_info_payload
))
else
:
elif
first_packet_id
==
CTOS_PLAYER_INFO
:
player_info_payload
=
first_payload
pre_packets
.
append
((
CTOS_PLAYER_INFO
,
first_payload
))
# make a dummy external_address_payload
external_address_payload
=
struct
.
pack
(
"<I"
,
0
)
external_address_payload
=
struct
.
pack
(
">I"
,
0
)
else
:
raise
ValueError
(
f
"Unexpected first packet ID: {first_packet_id}"
)
player_name
=
parse_utf16_string
(
player_info_payload
)
_
,
join_game_payload
=
await
asyncio
.
wait_for
(
ctos_read_filter
([
CTOS_JOIN_GAME
],
48
,
48
),
5.0
)
pre_packets
.
append
((
CTOS_JOIN_GAME
,
join_game_payload
))
...
...
@@ -155,19 +174,22 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
client_ip
=
real_ip_str
else
:
if
not
is_proxy
and
real_ip_int
!=
0
:
logger
.
warning
(
f
"Untrusted
IP {peer_ip}
tried to spoof real_ip={real_ip_str}"
)
logger
.
warning
(
f
"Untrusted
client {player_name} ({peer_ip})
tried to spoof real_ip={real_ip_str}"
)
client_ip
=
peer_ip
entry
=
match_route
(
hostname
)
player_descriptor
=
f
"{player_name} ({client_ip})"
if
entry
is
None
:
logger
.
warning
(
f
"No route found for hostname: {hostname} from {
client_ip
}"
)
logger
.
warning
(
f
"No route found for hostname: {hostname} from {
player_descriptor
}"
)
await
send_chat
(
f
"404 Not Found: Host [{hostname}] not found"
,
player_type
=
11
)
await
close_connection
()
return
async
def
open_menu
(
menu
,
menu_chain
=
[]):
logger
.
info
(
f
"{client_ip} requested {hostname} → Opening menu: {menu.get('welcome', 'Unknown')}"
)
state
.
established
=
True
logger
.
info
(
f
"{player_descriptor} requested {hostname} → Opening menu: {menu.get('welcome', 'Unknown')}"
)
use_tag
=
len
(
menu
[
'options'
])
>
2
async
def
send_info
():
...
...
@@ -220,14 +242,14 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
target_host
,
target_port
=
await
parse_entry
(
entry
)
logger
.
info
(
f
"{
client_ip
} requested {hostname} → forwarding to {target_host}:{target_port}"
)
logger
.
info
(
f
"{
player_descriptor
} requested {hostname} → forwarding to {target_host}:{target_port}"
)
# Connect to target server
try
:
remote_reader
,
remote_writer
=
await
asyncio
.
wait_for
(
asyncio
.
open_connection
(
target_host
,
target_port
),
timeout
=
5.0
)
except
Exception
as
e
:
logger
.
warning
(
f
"Failed to connect to {target_host}:{target_port} for client {
client_ip
}: {e}"
)
logger
.
warning
(
f
"Failed to connect to {target_host}:{target_port} for client {
player_descriptor
}: {e}"
)
await
send_chat
(
f
"502 Bad Gateway: Host [{hostname}] cannot be connected"
,
player_type
=
11
)
await
close_connection
()
return
...
...
@@ -242,26 +264,43 @@ async def handle_client(reader: StreamReader, writer: StreamWriter):
remote_writer
.
write
(
message
)
await
remote_writer
.
drain
()
async
def
pipe
(
src
,
dst
):
async
def
pipe
(
src
:
StreamReader
,
dst
:
StreamWriter
,
direction
:
Union
[
'STOC'
,
'CTOS'
]
):
try
:
while
not
src
.
at_eof
():
data
=
await
src
.
read
(
4096
)
if
not
data
:
break
dst
.
write
(
data
)
header
=
await
src
.
readexactly
(
3
)
length
=
struct
.
unpack
(
'<H'
,
header
[:
2
])[
0
]
packet_id
=
header
[
2
]
logger
.
info
(
f
"Received {direction} packet ID {packet_id} with length {length} for {player_descriptor}"
)
payload
=
await
src
.
readexactly
(
length
-
1
)
if
direction
==
'STOC'
:
if
packet_id
==
STOC_JOIN_GAME
:
state
.
established
=
True
logger
.
info
(
f
"Connection established for {player_descriptor} to {target_host}:{target_port}"
)
if
packet_id
==
STOC_ERROR_MSG
and
state
.
established
:
error_payload
=
struct
.
unpack
(
'<BBBBI'
,
payload
)
msg
=
error_payload
[
0
]
code
=
error_payload
[
4
]
if
msg
==
1
or
msg
==
4
:
# ERRMSG_JOINERR or ERRMSG_VERERROR
logger
.
warning
(
f
"Received error message for {player_descriptor}: {msg} {code}"
)
await
close_connection
()
return
dst
.
write
(
header
)
dst
.
write
(
payload
)
await
dst
.
drain
()
except
Exception
:
except
Exception
as
e
:
logger
.
warning
(
f
"Error in pipe {direction} for {player_descriptor}: {e}"
)
pass
finally
:
logger
.
info
(
f
"Closing pipe {direction} for {player_descriptor}"
)
dst
.
close
()
await
asyncio
.
gather
(
pipe
(
reader
,
remote_writer
),
pipe
(
remote_reader
,
writer
)
pipe
(
reader
,
remote_writer
,
'CTOS'
),
pipe
(
remote_reader
,
writer
,
'STOC'
)
)
except
Exception
as
e
:
logger
.
error
(
f
"Error handling client {p
eer_ip
}: {e}"
)
logger
.
error
(
f
"Error handling client {p
layer_descriptor
}: {e}"
)
try
:
await
stoc_send
(
STOC_ERROR_MSG
,
struct
.
pack
(
'<BBBBI'
,
1
,
0
,
0
,
0
,
9
))
except
Exception
as
send_error
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment