Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
ziqian zhang
Grasscutter
Commits
7925d1cd
Commit
7925d1cd
authored
Apr 17, 2022
by
Melledy
Browse files
Initial commit
parents
Changes
354
Show whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/net/packet/PacketWriter.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.net.packet
;
import
java.io.ByteArrayOutputStream
;
import
java.io.IOException
;
public
class
PacketWriter
{
// Little endian
private
final
ByteArrayOutputStream
baos
;
public
PacketWriter
()
{
this
.
baos
=
new
ByteArrayOutputStream
(
128
);
}
public
byte
[]
build
()
{
return
baos
.
toByteArray
();
}
// Writers
public
void
writeEmpty
(
int
i
)
{
while
(
i
>
0
)
{
baos
.
write
(
0
);
i
--;
}
}
public
void
writeMax
(
int
i
)
{
while
(
i
>
0
)
{
baos
.
write
(
0xFF
);
i
--;
}
}
public
void
writeInt8
(
byte
b
)
{
baos
.
write
(
b
);
}
public
void
writeInt8
(
int
i
)
{
baos
.
write
((
byte
)
i
);
}
public
void
writeBoolean
(
boolean
b
)
{
baos
.
write
(
b
?
1
:
0
);
}
public
void
writeUint8
(
byte
b
)
{
// Unsigned byte
baos
.
write
(
b
&
0xFF
);
}
public
void
writeUint8
(
int
i
)
{
baos
.
write
((
byte
)
i
&
0xFF
);
}
public
void
writeUint16
(
int
i
)
{
// Unsigned short
baos
.
write
((
byte
)
(
i
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
8
)
&
0xFF
));
}
public
void
writeUint24
(
int
i
)
{
// 24 bit integer
baos
.
write
((
byte
)
(
i
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
8
)
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
16
)
&
0xFF
));
}
public
void
writeInt16
(
int
i
)
{
// Signed short
baos
.
write
((
byte
)
i
);
baos
.
write
((
byte
)
(
i
>>>
8
));
}
public
void
writeUint32
(
int
i
)
{
// Unsigned int
baos
.
write
((
byte
)
(
i
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
8
)
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
16
)
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
24
)
&
0xFF
));
}
public
void
writeInt32
(
int
i
)
{
// Signed int
baos
.
write
((
byte
)
i
);
baos
.
write
((
byte
)
(
i
>>>
8
));
baos
.
write
((
byte
)
(
i
>>>
16
));
baos
.
write
((
byte
)
(
i
>>>
24
));
}
public
void
writeUint32
(
long
i
)
{
// Unsigned int (long)
baos
.
write
((
byte
)
(
i
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
8
)
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
16
)
&
0xFF
));
baos
.
write
((
byte
)
((
i
>>>
24
)
&
0xFF
));
}
public
void
writeFloat
(
float
f
){
this
.
writeUint32
(
Float
.
floatToRawIntBits
(
f
));
}
public
void
writeUint64
(
long
l
)
{
baos
.
write
((
byte
)
(
l
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
8
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
16
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
24
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
32
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
40
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
48
)
&
0xFF
));
baos
.
write
((
byte
)
((
l
>>>
56
)
&
0xFF
));
}
public
void
writeDouble
(
double
d
){
long
l
=
Double
.
doubleToLongBits
(
d
);
this
.
writeUint64
(
l
);
}
public
void
writeString16
(
String
s
)
{
if
(
s
==
null
)
{
this
.
writeUint16
(
0
);
return
;
}
this
.
writeUint16
(
s
.
length
()
*
2
);
for
(
int
i
=
0
;
i
<
s
.
length
();
i
++)
{
char
c
=
s
.
charAt
(
i
);
this
.
writeUint16
((
short
)
c
);
}
}
public
void
writeString8
(
String
s
)
{
if
(
s
==
null
)
{
this
.
writeUint16
(
0
);
return
;
}
this
.
writeUint16
(
s
.
length
());
for
(
int
i
=
0
;
i
<
s
.
length
();
i
++)
{
char
c
=
s
.
charAt
(
i
);
this
.
writeUint8
((
byte
)
c
);
}
}
public
void
writeDirectString8
(
String
s
,
int
expectedSize
)
{
if
(
s
==
null
)
{
return
;
}
for
(
int
i
=
0
;
i
<
expectedSize
;
i
++)
{
char
c
=
i
<
s
.
length
()
?
s
.
charAt
(
i
)
:
0
;
this
.
writeUint8
((
byte
)
c
);
}
}
public
void
writeBytes
(
byte
[]
bytes
)
{
try
{
baos
.
write
(
bytes
);
}
catch
(
IOException
e
)
{
// TODO Auto-generated catch block
e
.
printStackTrace
();
}
}
public
void
writeBytes
(
int
[]
bytes
)
{
byte
[]
b
=
new
byte
[
bytes
.
length
];
for
(
int
i
=
0
;
i
<
bytes
.
length
;
i
++)
b
[
i
]
=
(
byte
)
bytes
[
i
];
try
{
baos
.
write
(
b
);
}
catch
(
IOException
e
)
{
// TODO Auto-generated catch block
e
.
printStackTrace
();
}
}
}
src/main/java/emu/grasscutter/net/packet/Retcode.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.net.packet
;
public
class
Retcode
{
public
static
final
int
SUCCESS
=
0
;
public
static
final
int
FAIL
=
1
;
}
src/main/java/emu/grasscutter/netty/MihoyoKcpChannel.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.netty
;
import
emu.grasscutter.Grasscutter
;
import
io.jpower.kcp.netty.UkcpChannel
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.ByteBufUtil
;
import
io.netty.buffer.Unpooled
;
import
io.netty.channel.ChannelHandlerContext
;
import
io.netty.channel.ChannelInboundHandlerAdapter
;
public
abstract
class
MihoyoKcpChannel
extends
ChannelInboundHandlerAdapter
{
private
UkcpChannel
kcpChannel
;
private
ChannelHandlerContext
ctx
;
private
boolean
isActive
;
public
UkcpChannel
getChannel
()
{
return
kcpChannel
;
}
public
boolean
isActive
()
{
return
this
.
isActive
;
}
@Override
public
void
channelActive
(
ChannelHandlerContext
ctx
)
throws
Exception
{
this
.
kcpChannel
=
(
UkcpChannel
)
ctx
.
channel
();
this
.
ctx
=
ctx
;
this
.
isActive
=
true
;
this
.
onConnect
();
}
@Override
public
void
channelInactive
(
ChannelHandlerContext
ctx
)
throws
Exception
{
this
.
isActive
=
false
;
this
.
onDisconnect
();
}
@Override
public
void
channelRead
(
ChannelHandlerContext
ctx
,
Object
msg
)
{
ByteBuf
data
=
(
ByteBuf
)
msg
;
onMessage
(
ctx
,
data
);
}
@Override
public
void
channelReadComplete
(
ChannelHandlerContext
ctx
)
{
ctx
.
flush
();
}
@Override
public
void
exceptionCaught
(
ChannelHandlerContext
ctx
,
Throwable
cause
)
{
cause
.
printStackTrace
();
close
();
}
protected
void
send
(
byte
[]
data
)
{
if
(!
isActive
())
{
return
;
}
ByteBuf
packet
=
Unpooled
.
wrappedBuffer
(
data
);
kcpChannel
.
writeAndFlush
(
packet
);
}
public
void
close
()
{
if
(
getChannel
()
!=
null
)
{
getChannel
().
close
();
}
}
/*
protected void logPacket(ByteBuffer buf) {
ByteBuf b = Unpooled.wrappedBuffer(buf.array());
logPacket(b);
}
*/
protected
void
logPacket
(
ByteBuf
buf
)
{
Grasscutter
.
getLogger
().
info
(
"Received: \n"
+
ByteBufUtil
.
prettyHexDump
(
buf
));
}
// Events
protected
abstract
void
onConnect
();
protected
abstract
void
onDisconnect
();
public
abstract
void
onMessage
(
ChannelHandlerContext
ctx
,
ByteBuf
data
);
}
src/main/java/emu/grasscutter/netty/MihoyoKcpHandshaker.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.netty
;
import
java.net.SocketAddress
;
import
java.nio.channels.SelectableChannel
;
import
java.util.List
;
import
io.netty.channel.Channel
;
import
io.netty.channel.ChannelConfig
;
import
io.netty.channel.ChannelMetadata
;
import
io.netty.channel.ChannelOutboundBuffer
;
import
io.netty.channel.nio.AbstractNioMessageChannel
;
public
class
MihoyoKcpHandshaker
extends
AbstractNioMessageChannel
{
protected
MihoyoKcpHandshaker
(
Channel
parent
,
SelectableChannel
ch
,
int
readInterestOp
)
{
super
(
parent
,
ch
,
readInterestOp
);
}
@Override
public
ChannelConfig
config
()
{
// TODO Auto-generated method stub
return
null
;
}
@Override
public
boolean
isActive
()
{
// TODO Auto-generated method stub
return
false
;
}
@Override
public
ChannelMetadata
metadata
()
{
// TODO Auto-generated method stub
return
null
;
}
@Override
protected
int
doReadMessages
(
List
<
Object
>
buf
)
throws
Exception
{
// TODO Auto-generated method stub
return
0
;
}
@Override
protected
boolean
doWriteMessage
(
Object
msg
,
ChannelOutboundBuffer
in
)
throws
Exception
{
// TODO Auto-generated method stub
return
false
;
}
@Override
protected
boolean
doConnect
(
SocketAddress
remoteAddress
,
SocketAddress
localAddress
)
throws
Exception
{
// TODO Auto-generated method stub
return
false
;
}
@Override
protected
void
doFinishConnect
()
throws
Exception
{
// TODO Auto-generated method stub
}
@Override
protected
SocketAddress
localAddress0
()
{
// TODO Auto-generated method stub
return
null
;
}
@Override
protected
SocketAddress
remoteAddress0
()
{
// TODO Auto-generated method stub
return
null
;
}
@Override
protected
void
doBind
(
SocketAddress
localAddress
)
throws
Exception
{
// TODO Auto-generated method stub
}
@Override
protected
void
doDisconnect
()
throws
Exception
{
// TODO Auto-generated method stub
}
}
src/main/java/emu/grasscutter/netty/MihoyoKcpServer.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.netty
;
import
java.net.InetSocketAddress
;
import
emu.grasscutter.Grasscutter
;
import
io.jpower.kcp.netty.ChannelOptionHelper
;
import
io.jpower.kcp.netty.UkcpChannelOption
;
import
io.jpower.kcp.netty.UkcpServerChannel
;
import
io.netty.bootstrap.UkcpServerBootstrap
;
import
io.netty.channel.ChannelFuture
;
import
io.netty.channel.ChannelInitializer
;
import
io.netty.channel.EventLoopGroup
;
import
io.netty.channel.nio.NioEventLoopGroup
;
@SuppressWarnings
(
"rawtypes"
)
public
class
MihoyoKcpServer
extends
Thread
{
private
EventLoopGroup
group
;
private
UkcpServerBootstrap
bootstrap
;
private
ChannelInitializer
serverInitializer
;
private
InetSocketAddress
address
;
public
MihoyoKcpServer
(
InetSocketAddress
address
)
{
this
.
address
=
address
;
this
.
setName
(
"Netty Server Thread"
);
}
public
InetSocketAddress
getAddress
()
{
return
this
.
address
;
}
public
ChannelInitializer
getServerInitializer
()
{
return
serverInitializer
;
}
public
void
setServerInitializer
(
ChannelInitializer
serverInitializer
)
{
this
.
serverInitializer
=
serverInitializer
;
}
@Override
public
void
run
()
{
if
(
getServerInitializer
()
==
null
)
{
this
.
setServerInitializer
(
new
MihoyoKcpServerInitializer
());
}
try
{
group
=
new
NioEventLoopGroup
();
bootstrap
=
new
UkcpServerBootstrap
();
bootstrap
.
group
(
group
)
.
channel
(
UkcpServerChannel
.
class
)
.
childHandler
(
this
.
getServerInitializer
());
ChannelOptionHelper
.
nodelay
(
bootstrap
,
true
,
20
,
2
,
true
)
.
childOption
(
UkcpChannelOption
.
UKCP_MTU
,
1200
);
// Start handler
this
.
onStart
();
// Start the server.
ChannelFuture
f
=
bootstrap
.
bind
(
getAddress
()).
sync
();
// Start finish handler
this
.
onStartFinish
();
// Wait until the server socket is closed.
f
.
channel
().
closeFuture
().
sync
();
}
catch
(
Exception
e
)
{
// TODO Auto-generated catch block
e
.
printStackTrace
();
}
finally
{
// Close
finish
();
}
}
public
void
onStart
()
{
}
public
void
onStartFinish
()
{
}
private
void
finish
()
{
try
{
group
.
shutdownGracefully
();
}
catch
(
Exception
e
)
{
}
Grasscutter
.
getLogger
().
info
(
"Game Server closed"
);
}
}
src/main/java/emu/grasscutter/netty/MihoyoKcpServerInitializer.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.netty
;
import
io.jpower.kcp.netty.UkcpChannel
;
import
io.netty.channel.ChannelInitializer
;
import
io.netty.channel.ChannelPipeline
;
@SuppressWarnings
(
"unused"
)
public
class
MihoyoKcpServerInitializer
extends
ChannelInitializer
<
UkcpChannel
>
{
@Override
protected
void
initChannel
(
UkcpChannel
ch
)
throws
Exception
{
ChannelPipeline
pipeline
=
ch
.
pipeline
();
}
}
src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.util.Collections
;
import
com.sun.net.httpserver.HttpExchange
;
import
com.sun.net.httpserver.HttpHandler
;
public
class
DispatchHttpJsonHandler
implements
HttpHandler
{
private
final
String
response
;
public
DispatchHttpJsonHandler
(
String
response
)
{
this
.
response
=
response
;
}
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
}
src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.io.UnsupportedEncodingException
;
import
java.net.InetSocketAddress
;
import
java.net.URI
;
import
java.net.URLDecoder
;
import
java.security.KeyStore
;
import
java.util.Base64
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Map
;
import
javax.net.ssl.KeyManagerFactory
;
import
javax.net.ssl.SSLContext
;
import
com.google.gson.Gson
;
import
com.google.gson.GsonBuilder
;
import
com.google.protobuf.ByteString
;
import
com.sun.net.httpserver.HttpExchange
;
import
com.sun.net.httpserver.HttpHandler
;
import
com.sun.net.httpserver.HttpsConfigurator
;
import
com.sun.net.httpserver.HttpsServer
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.Account
;
import
emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp
;
import
emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp
;
import
emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo
;
import
emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo
;
import
emu.grasscutter.server.dispatch.json.ComboTokenReqJson
;
import
emu.grasscutter.server.dispatch.json.ComboTokenResJson
;
import
emu.grasscutter.server.dispatch.json.LoginAccountRequestJson
;
import
emu.grasscutter.server.dispatch.json.LoginResultJson
;
import
emu.grasscutter.server.dispatch.json.LoginTokenRequestJson
;
import
emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
com.sun.net.httpserver.HttpServer
;
public
class
DispatchServer
{
private
HttpsServer
server
;
private
final
InetSocketAddress
address
;
private
final
Gson
gson
;
private
QueryCurrRegionHttpRsp
currRegion
;
public
String
regionListBase64
;
public
String
regionCurrentBase64
;
public
static
String
query_region_list
=
""
;
public
static
String
query_cur_region
=
""
;
public
DispatchServer
()
{
this
.
address
=
new
InetSocketAddress
(
Grasscutter
.
getConfig
().
DispatchServerIp
,
Grasscutter
.
getConfig
().
DispatchServerPort
);
this
.
gson
=
new
GsonBuilder
().
create
();
this
.
loadQueries
();
this
.
initRegion
();
}
public
InetSocketAddress
getAddress
()
{
return
address
;
}
public
Gson
getGsonFactory
()
{
return
gson
;
}
public
QueryCurrRegionHttpRsp
getCurrRegion
()
{
return
currRegion
;
}
public
void
loadQueries
()
{
File
file
;
file
=
new
File
(
Grasscutter
.
getConfig
().
DATA_FOLDER
+
"query_region_list.txt"
);
if
(
file
.
exists
())
{
query_region_list
=
new
String
(
FileUtils
.
read
(
file
));
}
else
{
Grasscutter
.
getLogger
().
warn
(
"query_region_list not found! Using default region list."
);
}
file
=
new
File
(
Grasscutter
.
getConfig
().
DATA_FOLDER
+
"query_cur_region.txt"
);
if
(
file
.
exists
())
{
query_cur_region
=
new
String
(
FileUtils
.
read
(
file
));
}
else
{
Grasscutter
.
getLogger
().
warn
(
"query_cur_region not found! Using default current region."
);
}
}
private
void
initRegion
()
{
try
{
byte
[]
decoded
=
Base64
.
getDecoder
().
decode
(
query_region_list
);
QueryRegionListHttpRsp
rl
=
QueryRegionListHttpRsp
.
parseFrom
(
decoded
);
byte
[]
decoded2
=
Base64
.
getDecoder
().
decode
(
query_cur_region
);
QueryCurrRegionHttpRsp
regionQuery
=
QueryCurrRegionHttpRsp
.
parseFrom
(
decoded2
);
RegionSimpleInfo
server
=
RegionSimpleInfo
.
newBuilder
()
.
setName
(
"os_usa"
)
.
setTitle
(
Grasscutter
.
getConfig
().
GameServerName
)
.
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
"https://"
+
Grasscutter
.
getConfig
().
DispatchServerIp
+
":"
+
getAddress
().
getPort
()
+
"/query_cur_region"
)
.
build
();
RegionSimpleInfo
serverTest2
=
RegionSimpleInfo
.
newBuilder
()
.
setName
(
"os_euro"
)
.
setTitle
(
"Grasscutter"
)
.
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
"https://"
+
Grasscutter
.
getConfig
().
DispatchServerIp
+
":"
+
getAddress
().
getPort
()
+
"/query_cur_region"
)
.
build
();
QueryRegionListHttpRsp
regionList
=
QueryRegionListHttpRsp
.
newBuilder
()
.
addServers
(
server
)
.
addServers
(
serverTest2
)
.
setClientSecretKey
(
rl
.
getClientSecretKey
())
.
setClientCustomConfigEncrypted
(
rl
.
getClientCustomConfigEncrypted
())
.
setEnableLoginPc
(
true
)
.
build
();
RegionInfo
currentRegion
=
regionQuery
.
getRegionInfo
().
toBuilder
()
.
setIp
(
Grasscutter
.
getConfig
().
GameServerIp
)
.
setPort
(
Grasscutter
.
getConfig
().
GameServerPort
)
.
setSecretKey
(
ByteString
.
copyFrom
(
FileUtils
.
read
(
Grasscutter
.
getConfig
().
KEY_FOLDER
+
"dispatchSeed.bin"
)))
.
build
();
QueryCurrRegionHttpRsp
parsedRegionQuery
=
regionQuery
.
toBuilder
().
setRegionInfo
(
currentRegion
).
build
();
this
.
regionListBase64
=
Base64
.
getEncoder
().
encodeToString
(
regionList
.
toByteString
().
toByteArray
());
this
.
regionCurrentBase64
=
Base64
.
getEncoder
().
encodeToString
(
parsedRegionQuery
.
toByteString
().
toByteArray
());
this
.
currRegion
=
parsedRegionQuery
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
public
void
start
()
throws
Exception
{
server
=
HttpsServer
.
create
(
getAddress
(),
0
);
SSLContext
sslContext
=
SSLContext
.
getInstance
(
"TLS"
);
try
(
FileInputStream
fis
=
new
FileInputStream
(
Grasscutter
.
getConfig
().
DispatchServerKeystorePath
))
{
char
[]
keystorePassword
=
Grasscutter
.
getConfig
().
DispatchServerKeystorePassword
.
toCharArray
();
KeyStore
ks
=
KeyStore
.
getInstance
(
"PKCS12"
);
ks
.
load
(
fis
,
keystorePassword
);
KeyManagerFactory
kmf
=
KeyManagerFactory
.
getInstance
(
"SunX509"
);
kmf
.
init
(
ks
,
keystorePassword
);
sslContext
.
init
(
kmf
.
getKeyManagers
(),
null
,
null
);
server
.
setHttpsConfigurator
(
new
HttpsConfigurator
(
sslContext
));
}
catch
(
Exception
e
)
{
Grasscutter
.
getLogger
().
error
(
"No SSL cert found!"
);
return
;
}
server
.
createContext
(
"/"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
//Create a response form the request query parameters
String
response
=
"Hello"
;
//Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
//Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
// Dispatch
server
.
createContext
(
"/query_region_list"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Log
Grasscutter
.
getLogger
().
info
(
"Client request: query_region_list"
);
// Create a response form the request query parameters
String
response
=
regionListBase64
;
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
server
.
createContext
(
"/query_cur_region"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Log
Grasscutter
.
getLogger
().
info
(
"Client request: query_cur_region"
);
// Create a response form the request query parameters
URI
uri
=
t
.
getRequestURI
();
String
response
=
"CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="
;
if
(
uri
.
getQuery
()
!=
null
&&
uri
.
getQuery
().
length
()
>
0
)
{
response
=
regionCurrentBase64
;
}
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
// Login via account
server
.
createContext
(
"/hk4e_global/mdk/shield/api/login"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Get post data
LoginAccountRequestJson
requestData
=
null
;
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
LoginAccountRequestJson
.
class
);
}
catch
(
Exception
e
)
{
}
// Create response json
if
(
requestData
==
null
)
{
return
;
}
LoginResultJson
responseData
=
new
LoginResultJson
();
// Login
Account
account
=
DatabaseHelper
.
getAccountByName
(
requestData
.
account
);
// Test
if
(
account
==
null
)
{
responseData
.
retcode
=
-
201
;
responseData
.
message
=
"Username not found."
;
}
else
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
account
.
uid
=
account
.
getId
();
responseData
.
data
.
account
.
token
=
account
.
generateSessionKey
();
responseData
.
data
.
account
.
email
=
account
.
getEmail
();
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
// Login via token
server
.
createContext
(
"/hk4e_global/mdk/shield/api/verify"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Get post data
LoginTokenRequestJson
requestData
=
null
;
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
LoginTokenRequestJson
.
class
);
}
catch
(
Exception
e
)
{
}
// Create response json
if
(
requestData
==
null
)
{
return
;
}
LoginResultJson
responseData
=
new
LoginResultJson
();
// Login
Account
account
=
DatabaseHelper
.
getAccountById
(
requestData
.
uid
);
// Test
if
(
account
==
null
||
!
account
.
getSessionKey
().
equals
(
requestData
.
token
))
{
responseData
.
retcode
=
-
111
;
responseData
.
message
=
"Game account cache information error"
;
}
else
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
account
.
uid
=
requestData
.
uid
;
responseData
.
data
.
account
.
token
=
requestData
.
token
;
responseData
.
data
.
account
.
email
=
account
.
getEmail
();
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
// Exchange for combo token
server
.
createContext
(
"/hk4e_global/combo/granter/login/v2/login"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
// Get post data
ComboTokenReqJson
requestData
=
null
;
try
{
String
body
=
Utils
.
toString
(
t
.
getRequestBody
());
requestData
=
getGsonFactory
().
fromJson
(
body
,
ComboTokenReqJson
.
class
);
}
catch
(
Exception
e
)
{
}
// Create response json
if
(
requestData
==
null
||
requestData
.
data
==
null
)
{
return
;
}
LoginTokenData
loginData
=
getGsonFactory
().
fromJson
(
requestData
.
data
,
LoginTokenData
.
class
);
// Get login data
ComboTokenResJson
responseData
=
new
ComboTokenResJson
();
// Login
Account
account
=
DatabaseHelper
.
getAccountById
(
loginData
.
uid
);
// Test
if
(
account
==
null
||
!
account
.
getSessionKey
().
equals
(
loginData
.
token
))
{
responseData
.
retcode
=
-
201
;
responseData
.
message
=
"Wrong session key."
;
}
else
{
responseData
.
message
=
"OK"
;
responseData
.
data
.
open_id
=
loginData
.
uid
;
responseData
.
data
.
combo_id
=
"157795300"
;
responseData
.
data
.
combo_token
=
account
.
generateLoginToken
();
}
// Create a response
String
response
=
getGsonFactory
().
toJson
(
responseData
);
// Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"application/json"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
// Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
// Agreement and Protocol
server
.
createContext
(
// hk4e-sdk-os.hoyoverse.com
"/hk4e_global/mdk/agreement/api/getAgreementInfos"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}"
)
);
server
.
createContext
(
// hk4e-sdk-os.hoyoverse.com
"/hk4e_global/combo/granter/api/compareProtocolVersion"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}"
)
);
// Game data
server
.
createContext
(
// hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAlertPic"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"
)
);
server
.
createContext
(
// hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAlertAnn"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}"
)
);
server
.
createContext
(
// hk4e-api-os.hoyoverse.com
"/common/hk4e_global/announcement/api/getAnnList"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0,\"type_list\":[],\"alert\":false,\"alert_id\":0,\"timezone\":0,\"t\":\""
+
System
.
currentTimeMillis
()
+
"\"}}"
)
);
server
.
createContext
(
// hk4e-api-os-static.hoyoverse.com
"/common/hk4e_global/announcement/api/getAnnContent"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"list\":[],\"total\":0}}"
)
);
server
.
createContext
(
// hk4e-sdk-os.hoyoverse.com
"/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"
)
);
// Captcha
server
.
createContext
(
// api-account-os.hoyoverse.com
"/account/risky/api/check"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"c8820f246a5241ab9973f71df3ddd791\",\"action\":\"\",\"geetest\":{\"challenge\":\"\",\"gt\":\"\",\"new_captcha\":0,\"success\":1}}}"
)
);
// Config
server
.
createContext
(
// sdk-os-static.hoyoverse.com
"/combo/box/api/config/sdk/combo"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}"
)
);
server
.
createContext
(
// hk4e-sdk-os-static.hoyoverse.com
"/hk4e_global/combo/granter/api/getConfig"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}"
)
);
server
.
createContext
(
// hk4e-sdk-os-static.hoyoverse.com
"/hk4e_global/mdk/shield/api/loadConfig"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}"
)
);
// Test api?
server
.
createContext
(
// abtest-api-data-sg.hoyoverse.com
"/data_abtest_api/config/experiment/list"
,
new
DispatchHttpJsonHandler
(
"{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}"
)
);
// Log Server
server
.
createContext
(
// log-upload-os.mihoyo.com
"/log/sdk/upload"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
server
.
createContext
(
// log-upload-os.mihoyo.com
"/sdk/upload"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
// Start server
server
.
start
();
Grasscutter
.
getLogger
().
info
(
"Dispatch server started on port "
+
getAddress
().
getPort
());
// Logging servers
HttpServer
overseaLogServer
=
HttpServer
.
create
(
new
InetSocketAddress
(
Grasscutter
.
getConfig
().
DispatchServerIp
,
8888
),
0
);
overseaLogServer
.
createContext
(
// overseauspider.yuanshen.com
"/log"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
overseaLogServer
.
start
();
Grasscutter
.
getLogger
().
info
(
"Log server (overseauspider) started on port "
+
8888
);
HttpServer
uploadLogServer
=
HttpServer
.
create
(
new
InetSocketAddress
(
Grasscutter
.
getConfig
().
DispatchServerIp
,
80
),
0
);
uploadLogServer
.
createContext
(
// log-upload-os.mihoyo.com
"/crash/dataUpload"
,
new
DispatchHttpJsonHandler
(
"{\"code\":0}"
)
);
uploadLogServer
.
createContext
(
"/gacha"
,
new
HttpHandler
()
{
@Override
public
void
handle
(
HttpExchange
t
)
throws
IOException
{
//Create a response form the request query parameters
String
response
=
"<!doctype html><html lang=\"en\"><head><title>Gacha</title></head><body></body></html>"
;
//Set the response header status and length
t
.
getResponseHeaders
().
put
(
"Content-Type"
,
Collections
.
singletonList
(
"text/html; charset=UTF-8"
));
t
.
sendResponseHeaders
(
200
,
response
.
getBytes
().
length
);
//Write the response string
OutputStream
os
=
t
.
getResponseBody
();
os
.
write
(
response
.
getBytes
());
os
.
close
();
}
});
uploadLogServer
.
start
();
Grasscutter
.
getLogger
().
info
(
"Log server (log-upload-os) started on port "
+
80
);
}
private
Map
<
String
,
String
>
parseQueryString
(
String
qs
)
{
Map
<
String
,
String
>
result
=
new
HashMap
<>();
if
(
qs
==
null
)
return
result
;
int
last
=
0
,
next
,
l
=
qs
.
length
();
while
(
last
<
l
)
{
next
=
qs
.
indexOf
(
'&'
,
last
);
if
(
next
==
-
1
)
next
=
l
;
if
(
next
>
last
)
{
int
eqPos
=
qs
.
indexOf
(
'='
,
last
);
try
{
if
(
eqPos
<
0
||
eqPos
>
next
)
result
.
put
(
URLDecoder
.
decode
(
qs
.
substring
(
last
,
next
),
"utf-8"
),
""
);
else
result
.
put
(
URLDecoder
.
decode
(
qs
.
substring
(
last
,
eqPos
),
"utf-8"
),
URLDecoder
.
decode
(
qs
.
substring
(
eqPos
+
1
,
next
),
"utf-8"
));
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
RuntimeException
(
e
);
// will never happen, utf-8 support is mandatory for java
}
}
last
=
next
+
1
;
}
return
result
;
}
}
src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch.json
;
public
class
ComboTokenReqJson
{
public
int
app_id
;
public
int
channel_id
;
public
String
data
;
public
String
device
;
public
String
sign
;
public
class
LoginTokenData
{
public
String
uid
;
public
String
token
;
public
boolean
guest
;
}
}
src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch.json
;
public
class
ComboTokenResJson
{
public
String
message
;
public
int
retcode
;
public
LoginData
data
=
new
LoginData
();
public
class
LoginData
{
public
int
account_type
=
1
;
public
boolean
heartbeat
;
public
String
combo_id
;
public
String
combo_token
;
public
String
open_id
;
public
String
data
=
"{\"guest\":false}"
;
public
String
fatigue_remind
=
null
;
// ?
}
}
src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch.json
;
public
class
LoginAccountRequestJson
{
public
String
account
;
public
String
password
;
public
boolean
is_crypto
;
}
src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch.json
;
public
class
LoginResultJson
{
public
String
message
;
public
int
retcode
;
public
VerifyData
data
=
new
VerifyData
();
public
class
VerifyData
{
public
VerifyAccountData
account
=
new
VerifyAccountData
();
public
boolean
device_grant_required
=
false
;
public
String
realname_operation
=
"NONE"
;
public
boolean
realperson_required
=
false
;
public
boolean
safe_mobile_required
=
false
;
}
public
class
VerifyAccountData
{
public
String
uid
;
public
String
name
=
""
;
public
String
email
;
public
String
mobile
=
""
;
public
String
is_email_verify
=
"0"
;
public
String
realname
=
""
;
public
String
identity_card
=
""
;
public
String
token
;
public
String
safe_mobile
=
""
;
public
String
facebook_name
=
""
;
public
String
twitter_name
=
""
;
public
String
game_center_name
=
""
;
public
String
google_name
=
""
;
public
String
apple_name
=
""
;
public
String
sony_name
=
""
;
public
String
tap_name
=
""
;
public
String
country
=
"US"
;
public
String
reactivate_ticket
=
""
;
public
String
area_code
=
"**"
;
public
String
device_grant_ticket
=
""
;
}
}
src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.dispatch.json
;
public
class
LoginTokenRequestJson
{
public
String
uid
;
public
String
token
;
}
src/main/java/emu/grasscutter/server/game/GameServer.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.game
;
import
java.net.InetSocketAddress
;
import
java.util.ArrayList
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Timer
;
import
java.util.TimerTask
;
import
java.util.concurrent.ConcurrentHashMap
;
import
emu.grasscutter.GenshinConstants
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.GenshinPlayer
;
import
emu.grasscutter.game.dungeons.DungeonManager
;
import
emu.grasscutter.game.gacha.GachaManager
;
import
emu.grasscutter.game.managers.ChatManager
;
import
emu.grasscutter.game.managers.InventoryManager
;
import
emu.grasscutter.game.managers.MultiplayerManager
;
import
emu.grasscutter.game.shop.ShopManager
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail
;
import
emu.grasscutter.netty.MihoyoKcpServer
;
public
class
GameServer
extends
MihoyoKcpServer
{
private
final
InetSocketAddress
address
;
private
final
GameServerPacketHandler
packetHandler
;
private
final
Timer
gameLoop
;
private
final
Map
<
Integer
,
GenshinPlayer
>
players
;
private
final
ChatManager
chatManager
;
private
final
InventoryManager
inventoryManager
;
private
final
GachaManager
gachaManager
;
private
final
ShopManager
shopManager
;
private
final
MultiplayerManager
multiplayerManager
;
private
final
DungeonManager
dungeonManager
;
public
GameServer
(
InetSocketAddress
address
)
{
super
(
address
);
this
.
setServerInitializer
(
new
GameServerInitializer
(
this
));
this
.
address
=
address
;
this
.
packetHandler
=
new
GameServerPacketHandler
(
PacketHandler
.
class
);
this
.
players
=
new
ConcurrentHashMap
<>();
this
.
chatManager
=
new
ChatManager
(
this
);
this
.
inventoryManager
=
new
InventoryManager
(
this
);
this
.
gachaManager
=
new
GachaManager
(
this
);
this
.
shopManager
=
new
ShopManager
(
this
);
this
.
multiplayerManager
=
new
MultiplayerManager
(
this
);
this
.
dungeonManager
=
new
DungeonManager
(
this
);
// Ticker
this
.
gameLoop
=
new
Timer
();
this
.
gameLoop
.
scheduleAtFixedRate
(
new
TimerTask
()
{
@Override
public
void
run
()
{
try
{
onTick
();
}
catch
(
Exception
e
)
{
// TODO Auto-generated catch block
e
.
printStackTrace
();
}
}
},
new
Date
(),
1000L
);
// Shutdown hook
Runtime
.
getRuntime
().
addShutdownHook
(
new
Thread
(
this
::
onServerShutdown
));
}
public
GameServerPacketHandler
getPacketHandler
()
{
return
packetHandler
;
}
public
Map
<
Integer
,
GenshinPlayer
>
getPlayers
()
{
return
players
;
}
public
ChatManager
getChatManager
()
{
return
chatManager
;
}
public
InventoryManager
getInventoryManager
()
{
return
inventoryManager
;
}
public
GachaManager
getGachaManager
()
{
return
gachaManager
;
}
public
ShopManager
getShopManager
()
{
return
shopManager
;
}
public
MultiplayerManager
getMultiplayerManager
()
{
return
multiplayerManager
;
}
public
DungeonManager
getDungeonManager
()
{
return
dungeonManager
;
}
public
void
registerPlayer
(
GenshinPlayer
player
)
{
getPlayers
().
put
(
player
.
getId
(),
player
);
}
public
GenshinPlayer
getPlayerById
(
int
id
)
{
return
this
.
getPlayers
().
get
(
id
);
}
public
GenshinPlayer
forceGetPlayerById
(
int
id
)
{
// Console check
if
(
id
==
GenshinConstants
.
SERVER_CONSOLE_UID
)
{
return
null
;
}
// Get from online players
GenshinPlayer
player
=
this
.
getPlayerById
(
id
);
// Check database if character isnt here
if
(
player
==
null
)
{
player
=
DatabaseHelper
.
getPlayerById
(
id
);
}
return
player
;
}
public
SocialDetail
.
Builder
getSocialDetailById
(
int
id
)
{
// Get from online players
GenshinPlayer
player
=
this
.
forceGetPlayerById
(
id
);
if
(
player
==
null
)
{
return
null
;
}
return
player
.
getSocialDetail
();
}
public
void
onTick
()
throws
Exception
{
for
(
GenshinPlayer
player
:
this
.
getPlayers
().
values
())
{
player
.
onTick
();
}
}
@Override
public
void
onStartFinish
()
{
Grasscutter
.
getLogger
().
info
(
"Game Server started on port "
+
address
.
getPort
());
}
public
void
onServerShutdown
()
{
// Kick and save all players
List
<
GenshinPlayer
>
list
=
new
ArrayList
<>(
this
.
getPlayers
().
size
());
list
.
addAll
(
this
.
getPlayers
().
values
());
for
(
GenshinPlayer
player
:
list
)
{
player
.
getSession
().
close
();
}
}
}
src/main/java/emu/grasscutter/server/game/GameServerInitializer.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.game
;
import
emu.grasscutter.netty.MihoyoKcpServerInitializer
;
import
io.jpower.kcp.netty.UkcpChannel
;
import
io.netty.channel.ChannelPipeline
;
public
class
GameServerInitializer
extends
MihoyoKcpServerInitializer
{
private
GameServer
server
;
public
GameServerInitializer
(
GameServer
server
)
{
this
.
server
=
server
;
}
@Override
protected
void
initChannel
(
UkcpChannel
ch
)
throws
Exception
{
ChannelPipeline
pipeline
=
ch
.
pipeline
();
GameSession
session
=
new
GameSession
(
server
);
pipeline
.
addLast
(
session
);
}
}
src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.game
;
import
java.util.Set
;
import
org.reflections.Reflections
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.server.game.GameSession.SessionState
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
;
public
class
GameServerPacketHandler
{
private
final
Int2ObjectMap
<
PacketHandler
>
handlers
;
public
GameServerPacketHandler
(
Class
<?
extends
PacketHandler
>
handlerClass
)
{
this
.
handlers
=
new
Int2ObjectOpenHashMap
<>();
this
.
registerHandlers
(
handlerClass
);
}
public
void
registerHandlers
(
Class
<?
extends
PacketHandler
>
handlerClass
)
{
Reflections
reflections
=
new
Reflections
(
"emu.grasscutter.server.packet"
);
Set
<?>
handlerClasses
=
reflections
.
getSubTypesOf
(
handlerClass
);
for
(
Object
obj
:
handlerClasses
)
{
Class
<?>
c
=
(
Class
<?>)
obj
;
try
{
Opcodes
opcode
=
c
.
getAnnotation
(
Opcodes
.
class
);
if
(
opcode
==
null
||
opcode
.
disabled
()
||
opcode
.
value
()
<=
0
)
{
continue
;
}
PacketHandler
packetHandler
=
(
PacketHandler
)
c
.
newInstance
();
this
.
handlers
.
put
(
opcode
.
value
(),
packetHandler
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
// Debug
Grasscutter
.
getLogger
().
info
(
"Registered "
+
this
.
handlers
.
size
()
+
" "
+
handlerClass
.
getSimpleName
()
+
"s"
);
}
public
void
handle
(
GameSession
session
,
int
opcode
,
byte
[]
header
,
byte
[]
payload
)
{
PacketHandler
handler
=
null
;
handler
=
this
.
handlers
.
get
(
opcode
);
if
(
handler
!=
null
)
{
try
{
// Make sure session is ready for packets
SessionState
state
=
session
.
getState
();
if
(
opcode
==
PacketOpcodes
.
PingReq
)
{
// Always continue if packet is ping request
}
else
if
(
opcode
==
PacketOpcodes
.
GetPlayerTokenReq
)
{
if
(
state
!=
SessionState
.
WAITING_FOR_TOKEN
)
{
return
;
}
}
else
if
(
opcode
==
PacketOpcodes
.
PlayerLoginReq
)
{
if
(
state
!=
SessionState
.
WAITING_FOR_LOGIN
)
{
return
;
}
}
else
if
(
opcode
==
PacketOpcodes
.
SetPlayerBornDataReq
)
{
if
(
state
!=
SessionState
.
PICKING_CHARACTER
)
{
return
;
}
}
else
{
if
(
state
!=
SessionState
.
ACTIVE
)
{
return
;
}
}
// Handle
handler
.
handle
(
session
,
header
,
payload
);
}
catch
(
Exception
ex
)
{
// TODO Remove this when no more needed
ex
.
printStackTrace
();
}
return
;
// Packet successfully handled
}
// Log unhandled packets
if
(
Grasscutter
.
getConfig
().
LOG_PACKETS
)
{
//Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + PacketOpcodesUtil.getOpcodeName(opcode));
}
}
}
src/main/java/emu/grasscutter/server/game/GameSession.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.game
;
import
java.io.File
;
import
java.net.InetSocketAddress
;
import
java.nio.ByteBuffer
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.game.Account
;
import
emu.grasscutter.game.GenshinPlayer
;
import
emu.grasscutter.net.packet.GenshinPacket
;
import
emu.grasscutter.net.packet.PacketOpcodesUtil
;
import
emu.grasscutter.netty.MihoyoKcpChannel
;
import
emu.grasscutter.utils.Crypto
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.Unpooled
;
import
io.netty.channel.ChannelHandlerContext
;
public
class
GameSession
extends
MihoyoKcpChannel
{
private
GameServer
server
;
private
Account
account
;
private
GenshinPlayer
player
;
private
boolean
useSecretKey
;
private
SessionState
state
;
private
int
clientTime
;
private
long
lastPingTime
;
private
int
lastClientSeq
=
10
;
public
GameSession
(
GameServer
server
)
{
this
.
server
=
server
;
this
.
state
=
SessionState
.
WAITING_FOR_TOKEN
;
this
.
lastPingTime
=
System
.
currentTimeMillis
();
}
public
GameServer
getServer
()
{
return
server
;
}
public
InetSocketAddress
getAddress
()
{
if
(
this
.
getChannel
()
==
null
)
{
return
null
;
}
return
this
.
getChannel
().
remoteAddress
();
}
public
boolean
useSecretKey
()
{
return
useSecretKey
;
}
public
Account
getAccount
()
{
return
account
;
}
public
void
setAccount
(
Account
account
)
{
this
.
account
=
account
;
}
public
String
getAccountId
()
{
return
this
.
getAccount
().
getId
();
}
public
GenshinPlayer
getPlayer
()
{
return
player
;
}
public
synchronized
void
setPlayer
(
GenshinPlayer
player
)
{
this
.
player
=
player
;
this
.
player
.
setSession
(
this
);
this
.
player
.
setAccount
(
this
.
getAccount
());
}
public
SessionState
getState
()
{
return
state
;
}
public
void
setState
(
SessionState
state
)
{
this
.
state
=
state
;
}
public
boolean
isLoggedIn
()
{
return
this
.
getPlayer
()
!=
null
;
}
public
void
setUseSecretKey
(
boolean
useSecretKey
)
{
this
.
useSecretKey
=
useSecretKey
;
}
public
int
getClientTime
()
{
return
this
.
clientTime
;
}
public
long
getLastPingTime
()
{
return
lastPingTime
;
}
public
void
updateLastPingTime
(
int
clientTime
)
{
this
.
clientTime
=
clientTime
;
this
.
lastPingTime
=
System
.
currentTimeMillis
();
}
public
int
getNextClientSequence
()
{
return
++
lastClientSeq
;
}
@Override
protected
void
onConnect
()
{
Grasscutter
.
getLogger
().
info
(
"Client connected from "
+
getAddress
().
getHostString
().
toLowerCase
());
}
@Override
protected
synchronized
void
onDisconnect
()
{
// Synchronize so we dont add character at the same time
Grasscutter
.
getLogger
().
info
(
"Client disconnected from "
+
getAddress
().
getHostString
().
toLowerCase
());
// Set state so no more packets can be handled
this
.
setState
(
SessionState
.
INACTIVE
);
// Save after disconnecting
if
(
this
.
isLoggedIn
())
{
// Save
getPlayer
().
onLogout
();
// Remove from gameserver
getServer
().
getPlayers
().
remove
(
getPlayer
().
getId
());
}
}
protected
void
logPacket
(
ByteBuffer
buf
)
{
ByteBuf
b
=
Unpooled
.
wrappedBuffer
(
buf
.
array
());
logPacket
(
b
);
}
public
void
replayPacket
(
int
opcode
,
String
name
)
{
String
filePath
=
Grasscutter
.
getConfig
().
PACKETS_FOLDER
+
name
;
File
p
=
new
File
(
filePath
);
if
(!
p
.
exists
())
return
;
byte
[]
packet
=
FileUtils
.
read
(
p
);
GenshinPacket
genshinPacket
=
new
GenshinPacket
(
opcode
);
genshinPacket
.
setData
(
packet
);
// Log
logPacket
(
genshinPacket
.
getOpcode
());
send
(
genshinPacket
);
}
public
void
send
(
GenshinPacket
genshinPacket
)
{
// Test
if
(
genshinPacket
.
getOpcode
()
<=
0
)
{
Grasscutter
.
getLogger
().
warn
(
"Tried to send packet with missing cmd id!"
);
return
;
}
// Header
if
(
genshinPacket
.
shouldBuildHeader
())
{
genshinPacket
.
buildHeader
(
this
.
getNextClientSequence
());
}
// Build packet
byte
[]
data
=
genshinPacket
.
build
();
// Log
if
(
Grasscutter
.
getConfig
().
LOG_PACKETS
)
{
logPacket
(
genshinPacket
);
}
// Send
send
(
data
);
}
private
void
logPacket
(
int
opcode
)
{
//Grasscutter.getLogger().info("SEND: " + PacketOpcodesUtil.getOpcodeName(opcode));
//System.out.println(Utils.bytesToHex(genshinPacket.getData()));
}
private
void
logPacket
(
GenshinPacket
genshinPacket
)
{
Grasscutter
.
getLogger
().
info
(
"SEND: "
+
PacketOpcodesUtil
.
getOpcodeName
(
genshinPacket
.
getOpcode
())
+
" ("
+
genshinPacket
.
getOpcode
()
+
")"
);
System
.
out
.
println
(
Utils
.
bytesToHex
(
genshinPacket
.
getData
()));
}
@Override
public
void
onMessage
(
ChannelHandlerContext
ctx
,
ByteBuf
data
)
{
// Decrypt and turn back into a packet
byte
[]
byteData
=
Utils
.
byteBufToArray
(
data
);
Crypto
.
xor
(
byteData
,
useSecretKey
()
?
Crypto
.
ENCRYPT_KEY
:
Crypto
.
DISPATCH_KEY
);
ByteBuf
packet
=
Unpooled
.
wrappedBuffer
(
byteData
);
// Log
//logPacket(packet);
// Handle
try
{
while
(
packet
.
readableBytes
()
>
0
)
{
// Length
if
(
packet
.
readableBytes
()
<
12
)
{
return
;
}
// Packet sanity check
int
const1
=
packet
.
readShort
();
if
(
const1
!=
17767
)
{
return
;
// Bad packet
}
// Data
int
opcode
=
packet
.
readShort
();
int
headerLength
=
packet
.
readShort
();
int
payloadLength
=
packet
.
readInt
();
byte
[]
header
=
new
byte
[
headerLength
];
byte
[]
payload
=
new
byte
[
payloadLength
];
packet
.
readBytes
(
header
);
packet
.
readBytes
(
payload
);
// Sanity check #2
int
const2
=
packet
.
readShort
();
if
(
const2
!=
-
30293
)
{
return
;
// Bad packet
}
// Log packet
if
(
Grasscutter
.
getConfig
().
LOG_PACKETS
)
{
Grasscutter
.
getLogger
().
info
(
"RECV: "
+
PacketOpcodesUtil
.
getOpcodeName
(
opcode
)
+
" ("
+
opcode
+
")"
);
System
.
out
.
println
(
Utils
.
bytesToHex
(
payload
));
}
// Handle
getServer
().
getPacketHandler
().
handle
(
this
,
opcode
,
header
,
payload
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
finally
{
packet
.
release
();
}
}
public
enum
SessionState
{
INACTIVE
,
WAITING_FOR_TOKEN
,
WAITING_FOR_LOGIN
,
PICKING_CHARACTER
,
ACTIVE
;
}
}
src/main/java/emu/grasscutter/server/packet/recv/Handler.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.packet.recv
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.server.game.GameSession
;
@Opcodes
(
PacketOpcodes
.
NONE
)
public
class
Handler
extends
PacketHandler
{
@Override
public
void
handle
(
GameSession
session
,
byte
[]
header
,
byte
[]
payload
)
throws
Exception
{
// Auto template
}
}
src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.packet.recv
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.proto.AbilityInvocationsNotifyOuterClass.AbilityInvocationsNotify
;
import
emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.server.game.GameSession
;
@Opcodes
(
PacketOpcodes
.
AbilityInvocationsNotify
)
public
class
HandlerAbilityInvocationsNotify
extends
PacketHandler
{
@Override
public
void
handle
(
GameSession
session
,
byte
[]
header
,
byte
[]
payload
)
throws
Exception
{
AbilityInvocationsNotify
notif
=
AbilityInvocationsNotify
.
parseFrom
(
payload
);
for
(
AbilityInvokeEntry
entry
:
notif
.
getInvokesList
())
{
//System.out.println(entry.getArgumentType() + ": " + Utils.bytesToHex(entry.getAbilityData().toByteArray()));
session
.
getPlayer
().
getAbilityInvokeHandler
().
addEntry
(
entry
.
getForwardType
(),
entry
);
}
if
(
notif
.
getInvokesList
().
size
()
>
0
)
{
session
.
getPlayer
().
getAbilityInvokeHandler
().
update
(
session
.
getPlayer
());
}
}
}
src/main/java/emu/grasscutter/server/packet/recv/HandlerAskAddFriendReq.java
0 → 100644
View file @
7925d1cd
package
emu.grasscutter.server.packet.recv
;
import
emu.grasscutter.net.packet.Opcodes
;
import
emu.grasscutter.net.packet.PacketOpcodes
;
import
emu.grasscutter.net.proto.AskAddFriendReqOuterClass.AskAddFriendReq
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.server.game.GameSession
;
@Opcodes
(
PacketOpcodes
.
AskAddFriendReq
)
public
class
HandlerAskAddFriendReq
extends
PacketHandler
{
@Override
public
void
handle
(
GameSession
session
,
byte
[]
header
,
byte
[]
payload
)
throws
Exception
{
AskAddFriendReq
req
=
AskAddFriendReq
.
parseFrom
(
payload
);
session
.
getPlayer
().
getFriendsList
().
sendFriendRequest
(
req
.
getTargetUid
());
}
}
Prev
1
…
3
4
5
6
7
8
9
10
11
…
18
Next
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