-
Notifications
You must be signed in to change notification settings - Fork 0
/
proxy.hs
143 lines (132 loc) · 5.66 KB
/
proxy.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{-# LANGUAGE Rank2Types, ScopedTypeVariables, ViewPatterns #-}
import Prelude hiding ((.), catch)
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.UTF8 as L8
import Control.Concurrent
import Control.Exception
import Network
import System.IO
import System.Environment
import Data.Binary
import Data.Binary.Get
import Data.Char
import Control.Monad
import MinecraftProxy.Packet
import MinecraftProxy.Packets
import MinecraftProxy.AnyPacket
type PacketFilter = Packet -> ([Packet], [Packet])
serverPacketFilter :: PacketFilter
serverPacketFilter packet = inspect p
where
p :: AnyPacket t => Maybe t
p = fromPacket packet
inspect :: (forall t. AnyPacket t => Maybe t) -> ([Packet], [Packet])
inspect otherPacket = ([packet], [])
type PacketHandler = Packet -> IO ()
clientPacketFilter :: PacketFilter
clientPacketFilter packet = inspect p
where
passThrough = ([packet], [])
p :: AnyPacket t => Maybe t
p = fromPacket packet
inspect :: (forall t. AnyPacket t => Maybe t) -> ([Packet], [Packet])
inspect (Just (PacketChat (PrefixString ('/':str)))) = checkSlashCommand str
inspect otherPacket = passThrough
checkSlashCommand str =
let (cmd, dropWhile isSpace -> arg) = break isSpace str
in case cmd of
"send" -> readSendPacket ("Packet" ++ arg)
"return" -> readReturnPacket ("Packet" ++ arg)
"take" -> parseItemRequest arg
_ -> passThrough
readSendPacket s = case readPacket s of
Left err -> ([], [err])
Right newPacket -> ([newPacket], [])
readReturnPacket s = case readPacket s of
Left err -> ([], [err])
Right newPacket -> ([], [newPacket])
readPacket s = case reads s of
[(newPacket, "")] -> Right newPacket
_ -> Left (Packet . PacketChat . PrefixString $ "error: Packet: no parse")
parseItemRequest str = ([], [makePacket])
where
makePacket = if itemID /= -1
then Packet $ PacketAddToInventory itemID count damage
else Packet . PacketChat . PrefixString $ "error: take: no item id"
(itemID, count, damage) = parseNums
parseNums = case reads str of
[] -> (-1, 0, 0)
[(itemID, rest)] -> case reads rest of
[] -> (itemID, 1, 0)
[(count, rest')] -> case reads rest' of
[] -> (itemID, count, 0)
[(damage, rest'')] -> (itemID, count, damage)
clientListener, serverListener :: Handle -> PacketHandler -> PacketHandler -> Chan (Maybe String) -> IO ()
clientListener client toClnt toSrv consoleChan = connectionListener clientPacketFilter "client" client toSrv toClnt consoleChan
serverListener server toClnt toSrv consoleChan = connectionListener serverPacketFilter "server" server toClnt toSrv consoleChan
connectionListener :: PacketFilter -> String -> Handle -> PacketHandler -> PacketHandler -> Chan (Maybe String) -> IO ()
connectionListener handler prefix handle onHandler backHandler consoleChan = do
say "running"
readData `catch` report
writeChan consoleChan Nothing
where
report :: SomeException -> IO ()
report e = say (show e)
readData = L.hGetContents handle >>= dealWith
dealWith str
| L.null str = return ()
| otherwise = do
result <- try . evaluate $ runGetState (get :: Get Packet) str 0
case result of
Left (e :: SomeException) -> do
report e
-- this might force the interleaved IO when printed to stdout,
-- possibly hanging?
say $ "buffer contents: " ++ show (L8.toString str)
Right (p, rest, consumed) -> do
checkPacket (L.take consumed str) p
handlePacket p
dealWith rest
say = writeChan consoleChan . Just . ((prefix ++ ": ") ++)
handlePacket p = do
let (onwards, back) = handler p
forM onwards onHandler
forM back backHandler
checkPacket chunk p = do
let recoded = encode p
when (chunk /= recoded) $ do
say "parse error:"
say (show p)
say ("received: " ++ show chunk)
say ("recoded: " ++ show recoded)
main :: IO ()
main = withSocketsDo $ do
(\x -> case x of
[hostName, portNumber] -> [hostName, portNumber]
[hostName] -> [hostName, "25565"]
-> [hostName, portNumber]) <- getArgs
listener <- listenOn (PortNumber 25565)
(client, _, _) <- accept listener
sClose listener
server <- connectTo hostName (PortNumber . fromIntegral . read $ portNumber)
hSetBuffering client NoBuffering
hSetBuffering server NoBuffering
(consoleChan :: Chan (Maybe String)) <- newChan
(clientChan :: Chan Packet) <- newChan
(serverChan :: Chan Packet) <- newChan
serverThread <- forkIO $ serverListener server (writeChan clientChan) (writeChan serverChan) consoleChan
clientThread <- forkIO $ clientListener client (writeChan clientChan) (writeChan serverChan) consoleChan
forkIO $ sendLoop clientChan client
forkIO $ sendLoop serverChan server
sayLoop consoleChan
killThread serverThread
killThread clientThread
where
sendLoop chan handle = forever $ do
packet <- readChan chan
L.hPut handle (encode packet)
sayLoop chan =
let go = readChan chan >>= \msg -> case msg of
Just s -> putStrLn (take 160 s) >> hFlush stdout >> go
Nothing -> putStrLn "exitting"
in go