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
a8293102
Commit
a8293102
authored
Jun 07, 2022
by
Melledy
Committed by
GitHub
Jun 07, 2022
Browse files
Merge branch 'development' into stable
parents
304b9cb8
ecf7a81a
Changes
410
Hide whitespace changes
Inline
Side-by-side
src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java
deleted
100644 → 0
View file @
304b9cb8
package
emu.grasscutter.server.dispatch.http
;
import
java.io.File
;
import
java.io.IOException
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.Account
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.http.HttpContextHandler
;
import
express.http.Request
;
import
express.http.Response
;
public
final
class
GachaRecordHandler
implements
HttpContextHandler
{
String
render_template
;
public
GachaRecordHandler
()
{
File
template
=
new
File
(
Utils
.
toFilePath
(
Grasscutter
.
getConfig
().
DATA_FOLDER
+
"/gacha_records.html"
));
if
(
template
.
exists
())
{
// Load from cache
render_template
=
new
String
(
FileUtils
.
read
(
template
));
}
else
{
render_template
=
"{{REPLACE_RECORD}}"
;
}
}
@Override
public
void
handle
(
Request
req
,
Response
res
)
throws
IOException
{
// Grasscutter.getLogger().info( req.query().toString() );
String
sessionKey
=
req
.
query
(
"s"
);
int
page
=
0
;
int
gachaType
=
0
;
if
(
req
.
query
(
"p"
)
!=
null
)
{
page
=
Integer
.
valueOf
(
req
.
query
(
"p"
));
}
if
(
req
.
query
(
"gachaType"
)
!=
null
)
{
gachaType
=
Integer
.
valueOf
(
req
.
query
(
"gachaType"
));
}
Account
account
=
DatabaseHelper
.
getAccountBySessionKey
(
sessionKey
);
if
(
account
!=
null
)
{
String
records
=
DatabaseHelper
.
getGachaRecords
(
account
.
getPlayerUid
(),
page
,
gachaType
).
toString
();
// Grasscutter.getLogger().info(records);
String
response
=
render_template
.
replace
(
"{{REPLACE_RECORD}}"
,
records
)
.
replace
(
"{{REPLACE_MAXPAGE}}"
,
String
.
valueOf
(
DatabaseHelper
.
getGachaRecordsMaxPage
(
account
.
getPlayerUid
(),
page
,
gachaType
)));
res
.
send
(
response
);
}
else
{
res
.
send
(
"No account found."
);
}
}
}
src/main/java/emu/grasscutter/server/event/game/CommandResponseEvent.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.event.game
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.server.event.types.GameEvent
;
import
emu.grasscutter.server.event.types.ServerEvent
;
public
class
CommandResponseEvent
extends
ServerEvent
{
private
String
message
;
private
Player
player
;
public
CommandResponseEvent
(
Type
type
,
Player
player
,
String
message
)
{
super
(
type
);
this
.
message
=
message
;
this
.
player
=
player
;
}
public
String
getMessage
()
{
return
message
;
}
public
Player
getPlayer
()
{
return
player
;
}
}
src/main/java/emu/grasscutter/server/event/internal/ServerLogEvent.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.event.internal
;
import
ch.qos.logback.classic.spi.ILoggingEvent
;
import
ch.qos.logback.core.AppenderBase
;
import
emu.grasscutter.server.event.types.ServerEvent
;
public
class
ServerLogEvent
extends
ServerEvent
{
ILoggingEvent
loggingEvent
;
String
consoleMessage
;
public
ServerLogEvent
(
Type
type
,
ILoggingEvent
loggingEvent
,
String
consoleMessage
)
{
super
(
type
);
this
.
loggingEvent
=
loggingEvent
;
this
.
consoleMessage
=
consoleMessage
;
}
public
ILoggingEvent
getLoggingEvent
()
{
return
loggingEvent
;
}
public
String
getConsoleMessage
()
{
return
consoleMessage
;
}
}
src/main/java/emu/grasscutter/server/game/GameServer.java
View file @
a8293102
...
@@ -10,11 +10,14 @@ import emu.grasscutter.game.drop.DropManager;
...
@@ -10,11 +10,14 @@ import emu.grasscutter.game.drop.DropManager;
import
emu.grasscutter.game.dungeons.DungeonManager
;
import
emu.grasscutter.game.dungeons.DungeonManager
;
import
emu.grasscutter.game.expedition.ExpeditionManager
;
import
emu.grasscutter.game.expedition.ExpeditionManager
;
import
emu.grasscutter.game.gacha.GachaManager
;
import
emu.grasscutter.game.gacha.GachaManager
;
import
emu.grasscutter.game.managers.ChatManager
;
import
emu.grasscutter.game.managers.ChatManager.ChatManager
;
import
emu.grasscutter.game.managers.ChatManager.ChatManagerHandler
;
import
emu.grasscutter.game.managers.InventoryManager
;
import
emu.grasscutter.game.managers.InventoryManager
;
import
emu.grasscutter.game.managers.MultiplayerManager
;
import
emu.grasscutter.game.managers.MultiplayerManager
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.game.quest.ServerQuestHandler
;
import
emu.grasscutter.game.shop.ShopManager
;
import
emu.grasscutter.game.shop.ShopManager
;
import
emu.grasscutter.game.tower.TowerScheduleManager
;
import
emu.grasscutter.game.world.World
;
import
emu.grasscutter.game.world.World
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.packet.PacketHandler
;
import
emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail
;
import
emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail
;
...
@@ -24,25 +27,25 @@ import emu.grasscutter.server.event.game.ServerTickEvent;
...
@@ -24,25 +27,25 @@ import emu.grasscutter.server.event.game.ServerTickEvent;
import
emu.grasscutter.server.event.internal.ServerStartEvent
;
import
emu.grasscutter.server.event.internal.ServerStartEvent
;
import
emu.grasscutter.server.event.internal.ServerStopEvent
;
import
emu.grasscutter.server.event.internal.ServerStopEvent
;
import
emu.grasscutter.task.TaskMap
;
import
emu.grasscutter.task.TaskMap
;
import
emu.grasscutter.BuildConfig
;
import
java.net.InetSocketAddress
;
import
java.net.InetSocketAddress
;
import
java.time.OffsetDateTime
;
import
java.time.OffsetDateTime
;
import
java.util.*
;
import
java.util.*
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.ScheduledExecutorService
;
import
java.util.concurrent.TimeUnit
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
static
emu
.
grasscutter
.
Configuration
.*;
public
final
class
GameServer
extends
KcpServer
{
public
final
class
GameServer
extends
KcpServer
{
private
final
InetSocketAddress
address
;
private
final
InetSocketAddress
address
;
private
final
GameServerPacketHandler
packetHandler
;
private
final
GameServerPacketHandler
packetHandler
;
private
final
ServerQuestHandler
questHandler
;
private
final
Map
<
Integer
,
Player
>
players
;
private
final
Map
<
Integer
,
Player
>
players
;
private
final
Set
<
World
>
worlds
;
private
final
Set
<
World
>
worlds
;
private
final
ChatManager
chatManager
;
private
ChatManager
Handler
chatManager
;
private
final
InventoryManager
inventoryManager
;
private
final
InventoryManager
inventoryManager
;
private
final
GachaManager
gachaManager
;
private
final
GachaManager
gachaManager
;
private
final
ShopManager
shopManager
;
private
final
ShopManager
shopManager
;
...
@@ -54,20 +57,30 @@ public final class GameServer extends KcpServer {
...
@@ -54,20 +57,30 @@ public final class GameServer extends KcpServer {
private
final
DropManager
dropManager
;
private
final
DropManager
dropManager
;
private
final
CombineManger
combineManger
;
private
final
CombineManger
combineManger
;
private
final
TowerScheduleManager
towerScheduleManager
;
private
static
InetSocketAddress
getAdapterInetSocketAddress
(){
InetSocketAddress
inetSocketAddress
=
null
;
if
(
GAME_INFO
.
bindAddress
.
equals
(
""
)){
inetSocketAddress
=
new
InetSocketAddress
(
GAME_INFO
.
bindPort
);
}
else
{
inetSocketAddress
=
new
InetSocketAddress
(
GAME_INFO
.
bindAddress
,
GAME_INFO
.
bindPort
);
}
return
inetSocketAddress
;
}
public
GameServer
()
{
public
GameServer
()
{
this
(
new
InetSocketAddress
(
this
(
getAdapterInetSocketAddress
());
Grasscutter
.
getConfig
().
getGameServerOptions
().
Ip
,
Grasscutter
.
getConfig
().
getGameServerOptions
().
Port
));
}
}
public
GameServer
(
InetSocketAddress
address
)
{
public
GameServer
(
InetSocketAddress
address
)
{
super
(
address
);
super
(
address
);
this
.
setServerInitializer
(
new
GameServerInitializer
(
this
));
this
.
setServerInitializer
(
new
GameServerInitializer
(
this
));
this
.
address
=
address
;
this
.
address
=
address
;
this
.
packetHandler
=
new
GameServerPacketHandler
(
PacketHandler
.
class
);
this
.
packetHandler
=
new
GameServerPacketHandler
(
PacketHandler
.
class
);
this
.
questHandler
=
new
ServerQuestHandler
();
this
.
players
=
new
ConcurrentHashMap
<>();
this
.
players
=
new
ConcurrentHashMap
<>();
this
.
worlds
=
Collections
.
synchronizedSet
(
new
HashSet
<>());
this
.
worlds
=
Collections
.
synchronizedSet
(
new
HashSet
<>());
...
@@ -82,7 +95,7 @@ public final class GameServer extends KcpServer {
...
@@ -82,7 +95,7 @@ public final class GameServer extends KcpServer {
this
.
dropManager
=
new
DropManager
(
this
);
this
.
dropManager
=
new
DropManager
(
this
);
this
.
expeditionManager
=
new
ExpeditionManager
(
this
);
this
.
expeditionManager
=
new
ExpeditionManager
(
this
);
this
.
combineManger
=
new
CombineManger
(
this
);
this
.
combineManger
=
new
CombineManger
(
this
);
this
.
towerScheduleManager
=
new
TowerScheduleManager
(
this
);
// Hook into shutdown event.
// Hook into shutdown event.
Runtime
.
getRuntime
().
addShutdownHook
(
new
Thread
(
this
::
onServerShutdown
));
Runtime
.
getRuntime
().
addShutdownHook
(
new
Thread
(
this
::
onServerShutdown
));
}
}
...
@@ -91,6 +104,10 @@ public final class GameServer extends KcpServer {
...
@@ -91,6 +104,10 @@ public final class GameServer extends KcpServer {
return
packetHandler
;
return
packetHandler
;
}
}
public
ServerQuestHandler
getQuestHandler
()
{
return
questHandler
;
}
public
Map
<
Integer
,
Player
>
getPlayers
()
{
public
Map
<
Integer
,
Player
>
getPlayers
()
{
return
players
;
return
players
;
}
}
...
@@ -99,9 +116,13 @@ public final class GameServer extends KcpServer {
...
@@ -99,9 +116,13 @@ public final class GameServer extends KcpServer {
return
worlds
;
return
worlds
;
}
}
public
ChatManager
getChatManager
()
{
public
ChatManager
Handler
getChatManager
()
{
return
chatManager
;
return
chatManager
;
}
}
public
void
setChatManager
(
ChatManagerHandler
chatManager
)
{
this
.
chatManager
=
chatManager
;
}
public
InventoryManager
getInventoryManager
()
{
public
InventoryManager
getInventoryManager
()
{
return
inventoryManager
;
return
inventoryManager
;
...
@@ -139,6 +160,10 @@ public final class GameServer extends KcpServer {
...
@@ -139,6 +160,10 @@ public final class GameServer extends KcpServer {
return
this
.
combineManger
;
return
this
.
combineManger
;
}
}
public
TowerScheduleManager
getTowerScheduleManager
()
{
return
towerScheduleManager
;
}
public
TaskMap
getTaskMap
()
{
public
TaskMap
getTaskMap
()
{
return
this
.
taskMap
;
return
this
.
taskMap
;
}
}
...
@@ -166,12 +191,17 @@ public final class GameServer extends KcpServer {
...
@@ -166,12 +191,17 @@ public final class GameServer extends KcpServer {
// Check database if character isnt here
// Check database if character isnt here
if
(
player
==
null
)
{
if
(
player
==
null
)
{
player
=
DatabaseHelper
.
getPlayerBy
I
d
(
id
);
player
=
DatabaseHelper
.
getPlayerBy
Ui
d
(
id
);
}
}
return
player
;
return
player
;
}
}
public
Player
getPlayerByAccountId
(
String
accountId
)
{
Optional
<
Player
>
playerOpt
=
getPlayers
().
values
().
stream
().
filter
(
player
->
player
.
getAccount
().
getId
().
equals
(
accountId
)).
findFirst
();
return
playerOpt
.
orElse
(
null
);
}
public
SocialDetail
.
Builder
getSocialDetailByUid
(
int
id
)
{
public
SocialDetail
.
Builder
getSocialDetailByUid
(
int
id
)
{
// Get from online players
// Get from online players
Player
player
=
this
.
getPlayerByUid
(
id
,
true
);
Player
player
=
this
.
getPlayerByUid
(
id
,
true
);
...
...
src/main/java/emu/grasscutter/server/game/GameServerInitializer.java
View file @
a8293102
...
@@ -13,8 +13,10 @@ public class GameServerInitializer extends KcpServerInitializer {
...
@@ -13,8 +13,10 @@ public class GameServerInitializer extends KcpServerInitializer {
@Override
@Override
protected
void
initChannel
(
UkcpChannel
ch
)
throws
Exception
{
protected
void
initChannel
(
UkcpChannel
ch
)
throws
Exception
{
ChannelPipeline
pipeline
=
ch
.
pipeline
();
ChannelPipeline
pipeline
=
null
;
GameSession
session
=
new
GameSession
(
server
);
if
(
ch
!=
null
){
pipeline
.
addLast
(
session
);
pipeline
=
ch
.
pipeline
();
}
new
GameSession
(
server
,
pipeline
);
}
}
}
}
src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java
View file @
a8293102
...
@@ -14,6 +14,8 @@ import emu.grasscutter.server.game.GameSession.SessionState;
...
@@ -14,6 +14,8 @@ import emu.grasscutter.server.game.GameSession.SessionState;
import
it.unimi.dsi.fastutil.ints.Int2ObjectMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
;
import
static
emu
.
grasscutter
.
Configuration
.*;
@SuppressWarnings
(
"unchecked"
)
@SuppressWarnings
(
"unchecked"
)
public
class
GameServerPacketHandler
{
public
class
GameServerPacketHandler
{
private
final
Int2ObjectMap
<
PacketHandler
>
handlers
;
private
final
Int2ObjectMap
<
PacketHandler
>
handlers
;
...
@@ -92,7 +94,7 @@ public class GameServerPacketHandler {
...
@@ -92,7 +94,7 @@ public class GameServerPacketHandler {
}
}
// Log unhandled packets
// Log unhandled packets
if
(
Grasscutter
.
getConfig
().
DebugMode
==
ServerDebugMode
.
MISSING
)
{
if
(
SERVER
.
debugLevel
==
ServerDebugMode
.
MISSING
)
{
Grasscutter
.
getLogger
().
info
(
"Unhandled packet ("
+
opcode
+
"): "
+
emu
.
grasscutter
.
net
.
packet
.
PacketOpcodesUtil
.
getOpcodeName
(
opcode
));
Grasscutter
.
getLogger
().
info
(
"Unhandled packet ("
+
opcode
+
"): "
+
emu
.
grasscutter
.
net
.
packet
.
PacketOpcodesUtil
.
getOpcodeName
(
opcode
));
}
}
}
}
...
...
src/main/java/emu/grasscutter/server/game/GameSession.java
View file @
a8293102
...
@@ -3,7 +3,8 @@ package emu.grasscutter.server.game;
...
@@ -3,7 +3,8 @@ package emu.grasscutter.server.game;
import
java.io.File
;
import
java.io.File
;
import
java.net.InetSocketAddress
;
import
java.net.InetSocketAddress
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.util.HashSet
;
import
java.util.Iterator
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.Set
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.Grasscutter
;
...
@@ -18,14 +19,17 @@ import emu.grasscutter.server.event.game.SendPacketEvent;
...
@@ -18,14 +19,17 @@ import emu.grasscutter.server.event.game.SendPacketEvent;
import
emu.grasscutter.utils.Crypto
;
import
emu.grasscutter.utils.Crypto
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
emu.grasscutter.utils.Utils
;
import
io.jpower.kcp.netty.UkcpChannel
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.Unpooled
;
import
io.netty.buffer.Unpooled
;
import
io.netty.channel.ChannelHandlerContext
;
import
io.netty.channel.ChannelHandlerContext
;
import
io.netty.channel.ChannelPipeline
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
static
emu
.
grasscutter
.
Configuration
.*;
public
class
GameSession
extends
KcpChannel
{
public
class
GameSession
extends
KcpChannel
{
private
GameServer
server
;
private
final
GameServer
server
;
private
Account
account
;
private
Account
account
;
private
Player
player
;
private
Player
player
;
...
@@ -36,11 +40,30 @@ public class GameSession extends KcpChannel {
...
@@ -36,11 +40,30 @@ public class GameSession extends KcpChannel {
private
int
clientTime
;
private
int
clientTime
;
private
long
lastPingTime
;
private
long
lastPingTime
;
private
int
lastClientSeq
=
10
;
private
int
lastClientSeq
=
10
;
private
final
ChannelPipeline
pipeline
;
@Override
public
void
close
()
{
setState
(
SessionState
.
INACTIVE
);
//send disconnection pack in case of reconnection
try
{
send
(
new
BasePacket
(
PacketOpcodes
.
ServerDisconnectClientNotify
));
}
catch
(
Throwable
ignore
){
}
super
.
close
();
}
public
GameSession
(
GameServer
server
)
{
public
GameSession
(
GameServer
server
)
{
this
(
server
,
null
);
}
public
GameSession
(
GameServer
server
,
ChannelPipeline
pipeline
)
{
this
.
server
=
server
;
this
.
server
=
server
;
this
.
state
=
SessionState
.
WAITING_FOR_TOKEN
;
this
.
state
=
SessionState
.
WAITING_FOR_TOKEN
;
this
.
lastPingTime
=
System
.
currentTimeMillis
();
this
.
lastPingTime
=
System
.
currentTimeMillis
();
this
.
pipeline
=
pipeline
;
if
(
pipeline
!=
null
)
{
pipeline
.
addLast
(
this
);
}
}
}
public
GameServer
getServer
()
{
public
GameServer
getServer
()
{
...
@@ -124,13 +147,17 @@ public class GameSession extends KcpChannel {
...
@@ -124,13 +147,17 @@ public class GameSession extends KcpChannel {
// Set state so no more packets can be handled
// Set state so no more packets can be handled
this
.
setState
(
SessionState
.
INACTIVE
);
this
.
setState
(
SessionState
.
INACTIVE
);
// Save after disconnecting
// Save after disconnecting
if
(
this
.
isLoggedIn
())
{
if
(
this
.
isLoggedIn
())
{
Player
player
=
getPlayer
();
// Call logout event.
// Call logout event.
getPlayer
().
onLogout
();
player
.
onLogout
();
// Remove from server.
}
getServer
().
getPlayers
().
remove
(
getPlayer
().
getUid
());
try
{
pipeline
.
remove
(
this
);
}
catch
(
Throwable
ignore
)
{
}
}
}
}
...
@@ -140,7 +167,7 @@ public class GameSession extends KcpChannel {
...
@@ -140,7 +167,7 @@ public class GameSession extends KcpChannel {
}
}
public
void
replayPacket
(
int
opcode
,
String
name
)
{
public
void
replayPacket
(
int
opcode
,
String
name
)
{
String
filePath
=
Grasscutter
.
getConfig
().
PACKETS_FOLDER
+
name
;
String
filePath
=
PACKET
(
name
)
;
File
p
=
new
File
(
filePath
);
File
p
=
new
File
(
filePath
);
if
(!
p
.
exists
())
return
;
if
(!
p
.
exists
())
return
;
...
@@ -172,7 +199,7 @@ public class GameSession extends KcpChannel {
...
@@ -172,7 +199,7 @@ public class GameSession extends KcpChannel {
}
}
// Log
// Log
if
(
Grasscutter
.
getConfig
().
DebugMode
==
ServerDebugMode
.
ALL
)
{
if
(
SERVER
.
debugLevel
==
ServerDebugMode
.
ALL
)
{
logPacket
(
packet
);
logPacket
(
packet
);
}
}
...
@@ -239,7 +266,7 @@ public class GameSession extends KcpChannel {
...
@@ -239,7 +266,7 @@ public class GameSession extends KcpChannel {
}
}
// Log packet
// Log packet
if
(
Grasscutter
.
getConfig
().
DebugMode
==
ServerDebugMode
.
ALL
)
{
if
(
SERVER
.
debugLevel
==
ServerDebugMode
.
ALL
)
{
if
(!
loopPacket
.
contains
(
opcode
))
{
if
(!
loopPacket
.
contains
(
opcode
))
{
Grasscutter
.
getLogger
().
info
(
"RECV: "
+
PacketOpcodesUtil
.
getOpcodeName
(
opcode
)
+
" ("
+
opcode
+
")"
);
Grasscutter
.
getLogger
().
info
(
"RECV: "
+
PacketOpcodesUtil
.
getOpcodeName
(
opcode
)
+
" ("
+
opcode
+
")"
);
System
.
out
.
println
(
Utils
.
bytesToHex
(
payload
));
System
.
out
.
println
(
Utils
.
bytesToHex
(
payload
));
...
@@ -252,6 +279,7 @@ public class GameSession extends KcpChannel {
...
@@ -252,6 +279,7 @@ public class GameSession extends KcpChannel {
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
e
.
printStackTrace
();
}
finally
{
}
finally
{
data
.
release
();
packet
.
release
();
packet
.
release
();
}
}
}
}
...
...
src/main/java/emu/grasscutter/server/http/HttpServer.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.Grasscutter.ServerDebugMode
;
import
emu.grasscutter.utils.FileUtils
;
import
express.Express
;
import
express.http.MediaType
;
import
io.javalin.Javalin
;
import
org.eclipse.jetty.server.Server
;
import
org.eclipse.jetty.server.ServerConnector
;
import
org.eclipse.jetty.util.ssl.SslContextFactory
;
import
java.io.File
;
import
java.io.IOException
;
import
static
emu
.
grasscutter
.
Configuration
.*;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
/**
* Manages all HTTP-related classes.
* (including dispatch, announcements, gacha, etc.)
*/
public
final
class
HttpServer
{
private
final
Express
express
;
/**
* Configures the Express application.
*/
public
HttpServer
()
{
this
.
express
=
new
Express
(
config
->
{
// Set the Express HTTP server.
config
.
server
(
HttpServer:
:
createServer
);
// Configure encryption/HTTPS/SSL.
config
.
enforceSsl
=
HTTP_ENCRYPTION
.
useEncryption
;
// Configure HTTP policies.
if
(
HTTP_POLICIES
.
cors
.
enabled
)
{
var
allowedOrigins
=
HTTP_POLICIES
.
cors
.
allowedOrigins
;
if
(
allowedOrigins
.
length
>
0
)
config
.
enableCorsForOrigin
(
allowedOrigins
);
else
config
.
enableCorsForAllOrigins
();
}
// Configure debug logging.
if
(
SERVER
.
debugLevel
==
ServerDebugMode
.
ALL
)
config
.
enableDevLogging
();
// Disable compression on static files.
config
.
precompressStaticFiles
=
false
;
});
}
/**
* Creates an HTTP(S) server.
* @return A server instance.
*/
@SuppressWarnings
(
"resource"
)
private
static
Server
createServer
()
{
Server
server
=
new
Server
();
ServerConnector
serverConnector
=
new
ServerConnector
(
server
);
if
(
HTTP_ENCRYPTION
.
useEncryption
)
{
var
sslContextFactory
=
new
SslContextFactory
.
Server
();
var
keystoreFile
=
new
File
(
HTTP_ENCRYPTION
.
keystore
);
if
(!
keystoreFile
.
exists
())
{
HTTP_ENCRYPTION
.
useEncryption
=
false
;
HTTP_ENCRYPTION
.
useInRouting
=
false
;
Grasscutter
.
getLogger
().
warn
(
translate
(
"messages.dispatch.keystore.no_keystore_error"
));
}
else
try
{
sslContextFactory
.
setKeyStorePath
(
keystoreFile
.
getPath
());
sslContextFactory
.
setKeyStorePassword
(
HTTP_ENCRYPTION
.
keystorePassword
);
}
catch
(
Exception
ignored
)
{
Grasscutter
.
getLogger
().
warn
(
translate
(
"messages.dispatch.keystore.password_error"
));
try
{
sslContextFactory
.
setKeyStorePath
(
keystoreFile
.
getPath
());
sslContextFactory
.
setKeyStorePassword
(
"123456"
);
Grasscutter
.
getLogger
().
warn
(
translate
(
"messages.dispatch.keystore.default_password"
));
}
catch
(
Exception
exception
)
{
Grasscutter
.
getLogger
().
warn
(
translate
(
"messages.dispatch.keystore.general_error"
),
exception
);
}
}
finally
{
serverConnector
=
new
ServerConnector
(
server
,
sslContextFactory
);
}
}
serverConnector
.
setPort
(
HTTP_INFO
.
bindPort
);
server
.
setConnectors
(
new
ServerConnector
[]{
serverConnector
});
return
server
;
}
/**
* Returns the handle for the Express application.
* @return A Javalin instance.
*/
public
Javalin
getHandle
()
{
return
this
.
express
.
raw
();
}
/**
* Initializes the provided class.
* @param router The router class.
* @return Method chaining.
*/
@SuppressWarnings
(
"UnusedReturnValue"
)
public
HttpServer
addRouter
(
Class
<?
extends
Router
>
router
,
Object
...
args
)
{
// Get all constructor parameters.
Class
<?>[]
types
=
new
Class
<?>[
args
.
length
];
for
(
var
argument
:
args
)
types
[
args
.
length
-
1
]
=
argument
.
getClass
();
try
{
// Create a router instance & apply routes.
var
constructor
=
router
.
getDeclaredConstructor
(
types
);
// Get the constructor.
var
routerInstance
=
constructor
.
newInstance
(
args
);
// Create instance.
routerInstance
.
applyRoutes
(
this
.
express
,
this
.
getHandle
());
// Apply routes.
}
catch
(
Exception
exception
)
{
Grasscutter
.
getLogger
().
warn
(
translate
(
"messages.dispatch.router_error"
),
exception
);
}
return
this
;
}
/**
* Starts listening on the HTTP server.
*/
public
void
start
()
{
// Attempt to start the HTTP server.
if
(
HTTP_INFO
.
bindAddress
.
equals
(
""
)){
this
.
express
.
listen
(
HTTP_INFO
.
bindPort
);
}
else
{
this
.
express
.
listen
(
HTTP_INFO
.
bindAddress
,
HTTP_INFO
.
bindPort
);
}
// Log bind information.
Grasscutter
.
getLogger
().
info
(
translate
(
"messages.dispatch.port_bind"
,
Integer
.
toString
(
this
.
express
.
raw
().
port
())));
}
/**
* Handles the '/' (index) endpoint on the Express application.
*/
public
static
class
DefaultRequestRouter
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
express
.
get
(
"/"
,
(
request
,
response
)
->
{
File
file
=
new
File
(
HTTP_STATIC_FILES
.
indexFile
);
if
(!
file
.
exists
())
response
.
send
(
"""
<!DOCTYPE html>
<html>
<head>
<meta charset="
utf8
">
</head>
<body>%s</body>
</html>
"""
.
formatted
(
translate
(
"messages.status.welcome"
)));
else
{
final
var
filePath
=
file
.
getPath
();
final
MediaType
fromExtension
=
MediaType
.
getByExtension
(
filePath
.
substring
(
filePath
.
lastIndexOf
(
"."
)
+
1
));
response
.
type
((
fromExtension
!=
null
)
?
fromExtension
.
getMIME
()
:
"text/plain"
)
.
send
(
FileUtils
.
read
(
filePath
));
}
});
}
}
/**
* Handles unhandled endpoints on the Express application.
*/
public
static
class
UnhandledRequestRouter
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
handle
.
error
(
404
,
context
->
{
if
(
SERVER
.
debugLevel
==
ServerDebugMode
.
MISSING
)
Grasscutter
.
getLogger
().
info
(
translate
(
"messages.dispatch.unhandled_request_error"
,
context
.
method
(),
context
.
url
()));
context
.
contentType
(
"text/html"
);
File
file
=
new
File
(
HTTP_STATIC_FILES
.
errorFile
);
if
(!
file
.
exists
())
context
.
result
(
"""
<!DOCTYPE html>
<html>
<head>
<meta charset="
utf8
">
</head>
<body>
<img src="
https:
//http.cat/404" />
</
body
>
</
html
>
""");
else {
final var filePath = file.getPath();
final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf("
.
") + 1));
context.contentType((fromExtension != null) ? fromExtension.getMIME() : "
text
/
plain
"
)
.
result
(
FileUtils
.
read
(
filePath
));
}
});
}
}
}
src/main/java/emu/grasscutter/server/http/Router.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http
;
import
express.Express
;
import
io.javalin.Javalin
;
/**
* Defines routes for an {@link Express} instance.
*/
public
interface
Router
{
/**
* Called when the router is initialized by Express.
* @param express An Express instance.
*/
void
applyRoutes
(
Express
express
,
Javalin
handle
);
}
src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.dispatch
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.auth.AuthenticationSystem
;
import
emu.grasscutter.auth.OAuthAuthenticator
;
import
emu.grasscutter.auth.OAuthAuthenticator.ClientType
;
import
emu.grasscutter.server.http.Router
;
import
emu.grasscutter.server.http.objects.*
;
import
emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData
;
import
emu.grasscutter.utils.Utils
;
import
express.Express
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
/**
* Handles requests related to authentication. (aka dispatch)
*/
public
final
class
DispatchHandler
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
// Username & Password login (from client).
express
.
post
(
"/hk4e_global/mdk/shield/api/login"
,
DispatchHandler:
:
clientLogin
);
// Cached token login (from registry).
express
.
post
(
"/hk4e_global/mdk/shield/api/verify"
,
DispatchHandler:
:
tokenLogin
);
// Combo token login (from session key).
express
.
post
(
"/hk4e_global/combo/granter/login/v2/login"
,
DispatchHandler:
:
sessionKeyLogin
);
// External login (from other clients).
express
.
get
(
"/authentication/type"
,
(
request
,
response
)
->
response
.
send
(
Grasscutter
.
getAuthenticationSystem
().
getClass
().
getSimpleName
()));
express
.
post
(
"/authentication/login"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getExternalAuthenticator
()
.
handleLogin
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
)));
express
.
post
(
"/authentication/register"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getExternalAuthenticator
()
.
handleAccountCreation
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
)));
express
.
post
(
"/authentication/change_password"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getExternalAuthenticator
()
.
handlePasswordReset
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
)));
// External login (from OAuth2).
express
.
post
(
"/hk4e_global/mdk/shield/api/loginByThirdparty"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getOAuthAuthenticator
()
.
handleLogin
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
)));
express
.
get
(
"/authentication/openid/redirect"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getOAuthAuthenticator
()
.
handleTokenProcess
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
)));
express
.
get
(
"/Api/twitter_login"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getOAuthAuthenticator
()
.
handleRedirection
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
),
ClientType
.
DESKTOP
));
express
.
get
(
"/sdkTwitterLogin.html"
,
(
request
,
response
)
->
Grasscutter
.
getAuthenticationSystem
().
getOAuthAuthenticator
()
.
handleRedirection
(
AuthenticationSystem
.
fromExternalRequest
(
request
,
response
),
ClientType
.
MOBILE
));
}
/**
* @route /hk4e_global/mdk/shield/api/login
*/
private
static
void
clientLogin
(
Request
request
,
Response
response
)
{
// Parse body data.
String
rawBodyData
=
request
.
ctx
().
body
();
var
bodyData
=
Utils
.
jsonDecode
(
rawBodyData
,
LoginAccountRequestJson
.
class
);
// Validate body data.
if
(
bodyData
==
null
)
return
;
// Pass data to authentication handler.
var
responseData
=
Grasscutter
.
getAuthenticationSystem
()
.
getPasswordAuthenticator
()
.
authenticate
(
AuthenticationSystem
.
fromPasswordRequest
(
request
,
bodyData
));
// Send response.
response
.
send
(
responseData
);
// Log to console.
Grasscutter
.
getLogger
().
info
(
translate
(
"messages.dispatch.account.login_attempt"
,
request
.
ip
()));
}
/**
* @route /hk4e_global/mdk/shield/api/verify
*/
private
static
void
tokenLogin
(
Request
request
,
Response
response
)
{
// Parse body data.
String
rawBodyData
=
request
.
ctx
().
body
();
var
bodyData
=
Utils
.
jsonDecode
(
rawBodyData
,
LoginTokenRequestJson
.
class
);
// Validate body data.
if
(
bodyData
==
null
)
return
;
// Pass data to authentication handler.
var
responseData
=
Grasscutter
.
getAuthenticationSystem
()
.
getTokenAuthenticator
()
.
authenticate
(
AuthenticationSystem
.
fromTokenRequest
(
request
,
bodyData
));
// Send response.
response
.
send
(
responseData
);
// Log to console.
Grasscutter
.
getLogger
().
info
(
translate
(
"messages.dispatch.account.login_attempt"
,
request
.
ip
()));
}
/**
* @route /hk4e_global/combo/granter/login/v2/login
*/
private
static
void
sessionKeyLogin
(
Request
request
,
Response
response
)
{
// Parse body data.
String
rawBodyData
=
request
.
ctx
().
body
();
var
bodyData
=
Utils
.
jsonDecode
(
rawBodyData
,
ComboTokenReqJson
.
class
);
// Validate body data.
if
(
bodyData
==
null
||
bodyData
.
data
==
null
)
return
;
// Decode additional body data.
var
tokenData
=
Utils
.
jsonDecode
(
bodyData
.
data
,
LoginTokenData
.
class
);
// Pass data to authentication handler.
var
responseData
=
Grasscutter
.
getAuthenticationSystem
()
.
getSessionKeyAuthenticator
()
.
authenticate
(
AuthenticationSystem
.
fromComboTokenRequest
(
request
,
bodyData
,
tokenData
));
// Send response.
response
.
send
(
responseData
);
// Log to console.
Grasscutter
.
getLogger
().
info
(
translate
(
"messages.dispatch.account.login_attempt"
,
request
.
ip
()));
}
}
src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.dispatch
;
import
com.google.protobuf.ByteString
;
import
com.google.protobuf.InvalidProtocolBufferException
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.Grasscutter.ServerRunMode
;
import
emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.*
;
import
emu.grasscutter.net.proto.RegionInfoOuterClass
;
import
emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo
;
import
emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo
;
import
emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent
;
import
emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent
;
import
emu.grasscutter.server.http.Router
;
import
emu.grasscutter.utils.Crypto
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.Express
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
import
java.io.File
;
import
java.util.ArrayList
;
import
java.util.Base64
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
static
emu
.
grasscutter
.
Configuration
.*;
import
static
emu
.
grasscutter
.
net
.
proto
.
QueryRegionListHttpRspOuterClass
.*;
/**
* Handles requests related to region queries.
*/
public
final
class
RegionHandler
implements
Router
{
private
static
final
Map
<
String
,
RegionData
>
regions
=
new
ConcurrentHashMap
<>();
private
static
String
regionListResponse
;
public
RegionHandler
()
{
try
{
// Read & initialize region data.
this
.
initialize
();
}
catch
(
Exception
exception
)
{
Grasscutter
.
getLogger
().
error
(
"Failed to initialize region data."
,
exception
);
}
}
/**
* Configures region data according to configuration.
*/
private
void
initialize
()
{
String
dispatchDomain
=
"http"
+
(
HTTP_ENCRYPTION
.
useInRouting
?
"s"
:
""
)
+
"://"
+
lr
(
HTTP_INFO
.
accessAddress
,
HTTP_INFO
.
bindAddress
)
+
":"
+
lr
(
HTTP_INFO
.
accessPort
,
HTTP_INFO
.
bindPort
);
// Create regions.
List
<
RegionSimpleInfo
>
servers
=
new
ArrayList
<>();
List
<
String
>
usedNames
=
new
ArrayList
<>();
// List to check for potential naming conflicts.
var
configuredRegions
=
new
ArrayList
<>(
List
.
of
(
DISPATCH_INFO
.
regions
));
if
(
SERVER
.
runMode
!=
ServerRunMode
.
HYBRID
&&
configuredRegions
.
size
()
==
0
)
{
Grasscutter
.
getLogger
().
error
(
"[Dispatch] There are no game servers available. Exiting due to unplayable state."
);
System
.
exit
(
1
);
}
else
if
(
configuredRegions
.
size
()
==
0
)
configuredRegions
.
add
(
new
Region
(
"os_usa"
,
DISPATCH_INFO
.
defaultName
,
lr
(
GAME_INFO
.
accessAddress
,
GAME_INFO
.
bindAddress
),
lr
(
GAME_INFO
.
accessPort
,
GAME_INFO
.
bindPort
)));
configuredRegions
.
forEach
(
region
->
{
if
(
usedNames
.
contains
(
region
.
Name
))
{
Grasscutter
.
getLogger
().
error
(
"Region name already in use."
);
return
;
}
// Create a region identifier.
var
identifier
=
RegionSimpleInfo
.
newBuilder
()
.
setName
(
region
.
Name
).
setTitle
(
region
.
Title
).
setType
(
"DEV_PUBLIC"
)
.
setDispatchUrl
(
dispatchDomain
+
"/query_cur_region/"
+
region
.
Name
)
.
build
();
usedNames
.
add
(
region
.
Name
);
servers
.
add
(
identifier
);
// Create a region info object.
var
regionInfo
=
RegionInfo
.
newBuilder
()
.
setGateserverIp
(
region
.
Ip
).
setGateserverPort
(
region
.
Port
)
.
setSecretKey
(
ByteString
.
copyFrom
(
Crypto
.
DISPATCH_SEED
))
.
build
();
// Create an updated region query.
var
updatedQuery
=
QueryCurrRegionHttpRsp
.
newBuilder
().
setRegionInfo
(
regionInfo
).
build
();
regions
.
put
(
region
.
Name
,
new
RegionData
(
updatedQuery
,
Utils
.
base64Encode
(
updatedQuery
.
toByteString
().
toByteArray
())));
});
// Create a config object.
byte
[]
customConfig
=
"{\"sdkenv\":\"2\",\"checkdevice\":\"false\",\"loadPatch\":\"false\",\"showexception\":\"false\",\"regionConfig\":\"pm|fk|add\",\"downloadMode\":\"0\"}"
.
getBytes
();
Crypto
.
xor
(
customConfig
,
Crypto
.
DISPATCH_KEY
);
// XOR the config with the key.
// Create an updated region list.
QueryRegionListHttpRsp
updatedRegionList
=
QueryRegionListHttpRsp
.
newBuilder
()
.
addAllRegionList
(
servers
)
.
setClientSecretKey
(
ByteString
.
copyFrom
(
Crypto
.
DISPATCH_SEED
))
.
setClientCustomConfigEncrypted
(
ByteString
.
copyFrom
(
customConfig
))
.
setEnableLoginPc
(
true
).
build
();
// Set the region list response.
regionListResponse
=
Utils
.
base64Encode
(
updatedRegionList
.
toByteString
().
toByteArray
());
}
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
express
.
get
(
"/query_region_list"
,
RegionHandler:
:
queryRegionList
);
express
.
get
(
"/query_cur_region/:region"
,
RegionHandler:
:
queryCurrentRegion
);
}
/**
* @route /query_region_list
*/
private
static
void
queryRegionList
(
Request
request
,
Response
response
)
{
// Invoke event.
QueryAllRegionsEvent
event
=
new
QueryAllRegionsEvent
(
regionListResponse
);
event
.
call
();
// Respond with event result.
response
.
send
(
event
.
getRegionList
());
// Log to console.
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"[Dispatch] Client %s request: query_region_list"
,
request
.
ip
()));
}
/**
* @route /query_cur_region/:region
*/
private
static
void
queryCurrentRegion
(
Request
request
,
Response
response
)
{
// Get region to query.
String
regionName
=
request
.
params
(
"region"
);
// Get region data.
String
regionData
=
"CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="
;
if
(
request
.
query
().
values
().
size
()
>
0
)
{
var
region
=
regions
.
get
(
regionName
);
if
(
region
!=
null
)
regionData
=
region
.
getBase64
();
}
// Invoke event.
QueryCurrentRegionEvent
event
=
new
QueryCurrentRegionEvent
(
regionData
);
event
.
call
();
// Respond with event result.
response
.
send
(
event
.
getRegionInfo
());
// Log to console.
Grasscutter
.
getLogger
().
info
(
String
.
format
(
"Client %s request: query_cur_region/%s"
,
request
.
ip
(),
regionName
));
}
/**
* Region data container.
*/
public
static
class
RegionData
{
private
final
QueryCurrRegionHttpRsp
regionQuery
;
private
final
String
base64
;
public
RegionData
(
QueryCurrRegionHttpRsp
prq
,
String
b64
)
{
this
.
regionQuery
=
prq
;
this
.
base64
=
b64
;
}
public
QueryCurrRegionHttpRsp
getRegionQuery
()
{
return
this
.
regionQuery
;
}
public
String
getBase64
()
{
return
this
.
base64
;
}
}
/**
* Gets the current region query.
* @return A {@link QueryCurrRegionHttpRsp} object.
*/
public
static
QueryCurrRegionHttpRsp
getCurrentRegion
()
{
return
SERVER
.
runMode
==
ServerRunMode
.
HYBRID
?
regions
.
get
(
"os_usa"
).
getRegionQuery
()
:
null
;
}
}
src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.documentation
;
import
express.http.Request
;
import
express.http.Response
;
interface
DocumentationHandler
{
void
handle
(
Request
request
,
Response
response
);
}
src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.documentation
;
import
emu.grasscutter.server.http.Router
;
import
express.Express
;
import
io.javalin.Javalin
;
public
final
class
DocumentationServerHandler
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
final
RootRequestHandler
root
=
new
RootRequestHandler
();
final
HandbookRequestHandler
handbook
=
new
HandbookRequestHandler
();
final
GachaMappingRequestHandler
gachaMapping
=
new
GachaMappingRequestHandler
();
express
.
get
(
"/documentation/handbook"
,
handbook:
:
handle
);
express
.
get
(
"/documentation/gachamapping"
,
gachaMapping:
:
handle
);
express
.
get
(
"/documentation"
,
root:
:
handle
);
}
}
src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.documentation
;
import
static
emu
.
grasscutter
.
Configuration
.
RESOURCE
;
import
com.google.gson.reflect.TypeToken
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.data.excels.AvatarData
;
import
emu.grasscutter.data.excels.ItemData
;
import
emu.grasscutter.utils.Utils
;
import
static
emu
.
grasscutter
.
Configuration
.
DOCUMENT_LANGUAGE
;
import
express.http.Request
;
import
express.http.Response
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.InputStreamReader
;
import
java.nio.charset.StandardCharsets
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
final
class
GachaMappingRequestHandler
implements
DocumentationHandler
{
private
Map
<
Long
,
String
>
map
;
GachaMappingRequestHandler
()
{
final
String
textMapFile
=
"TextMap/TextMap"
+
DOCUMENT_LANGUAGE
+
".json"
;
try
(
InputStreamReader
fileReader
=
new
InputStreamReader
(
new
FileInputStream
(
Utils
.
toFilePath
(
RESOURCE
(
textMapFile
))),
StandardCharsets
.
UTF_8
))
{
map
=
Grasscutter
.
getGsonFactory
().
fromJson
(
fileReader
,
new
TypeToken
<
Map
<
Long
,
String
>>()
{
}.
getType
());
}
catch
(
IOException
e
)
{
Grasscutter
.
getLogger
().
warn
(
"Resource does not exist: "
+
textMapFile
);
map
=
new
HashMap
<>();
}
}
@Override
public
void
handle
(
Request
request
,
Response
response
)
{
if
(
map
.
isEmpty
())
{
response
.
status
(
500
);
}
else
{
response
.
set
(
"Content-Type"
,
"application/json"
)
.
ctx
()
.
result
(
createGachaMappingJson
());
}
}
private
String
createGachaMappingJson
()
{
List
<
Integer
>
list
;
final
StringBuilder
sb
=
new
StringBuilder
();
list
=
new
ArrayList
<>(
GameData
.
getAvatarDataMap
().
keySet
());
Collections
.
sort
(
list
);
final
String
newLine
=
System
.
lineSeparator
();
// if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us"
// since it's the fallback language and there will be no difference in the gacha record page.
// The enduser can still modify the `gacha_mappings.js` directly to enable multilingual for the gacha record system.
sb
.
append
(
"{"
).
append
(
newLine
);
// Avatars
boolean
first
=
true
;
for
(
Integer
id
:
list
)
{
AvatarData
data
=
GameData
.
getAvatarDataMap
().
get
(
id
);
int
avatarID
=
data
.
getId
();
if
(
avatarID
>=
11000000
)
{
// skip test avatar
continue
;
}
if
(
first
)
{
// skip adding comma for the first element
first
=
false
;
}
else
{
sb
.
append
(
","
);
}
String
color
;
switch
(
data
.
getQualityType
())
{
case
"QUALITY_PURPLE"
:
color
=
"purple"
;
break
;
case
"QUALITY_ORANGE"
:
color
=
"yellow"
;
break
;
case
"QUALITY_BLUE"
:
default
:
color
=
"blue"
;
}
// Got the magic number 4233146695 from manually search in the json file
sb
.
append
(
"\""
)
.
append
(
avatarID
%
1000
+
1000
)
.
append
(
"\" : [\""
)
.
append
(
map
.
get
(
data
.
getNameTextMapHash
()))
.
append
(
"("
)
.
append
(
map
.
get
(
4233146695L
))
.
append
(
")\", \""
)
.
append
(
color
)
.
append
(
"\"]"
)
.
append
(
newLine
);
}
list
=
new
ArrayList
<>(
GameData
.
getItemDataMap
().
keySet
());
Collections
.
sort
(
list
);
// Weapons
for
(
Integer
id
:
list
)
{
ItemData
data
=
GameData
.
getItemDataMap
().
get
(
id
);
if
(
data
.
getId
()
<=
11101
||
data
.
getId
()
>=
20000
)
{
continue
;
//skip non weapon items
}
String
color
;
switch
(
data
.
getRankLevel
())
{
case
3
:
color
=
"blue"
;
break
;
case
4
:
color
=
"purple"
;
break
;
case
5
:
color
=
"yellow"
;
break
;
default
:
continue
;
// skip unnecessary entries
}
// Got the magic number 4231343903 from manually search in the json file
sb
.
append
(
",\""
)
.
append
(
data
.
getId
())
.
append
(
"\" : [\""
)
.
append
(
map
.
get
(
data
.
getNameTextMapHash
()).
replaceAll
(
"\""
,
""
))
.
append
(
"("
)
.
append
(
map
.
get
(
4231343903L
))
.
append
(
")\",\""
)
.
append
(
color
)
.
append
(
"\"]"
)
.
append
(
newLine
);
}
sb
.
append
(
",\"200\": \""
)
.
append
(
map
.
get
(
332935371L
))
.
append
(
"\", \"301\": \""
)
.
append
(
map
.
get
(
2272170627L
))
.
append
(
"\", \"302\": \""
)
.
append
(
map
.
get
(
2864268523L
))
.
append
(
"\""
)
.
append
(
"}\n}"
)
.
append
(
newLine
);
return
sb
.
toString
();
}
}
src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.documentation
;
import
static
emu
.
grasscutter
.
Configuration
.*;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
com.google.gson.reflect.TypeToken
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.command.CommandMap
;
import
emu.grasscutter.data.GameData
;
import
emu.grasscutter.data.excels.AvatarData
;
import
emu.grasscutter.data.excels.ItemData
;
import
emu.grasscutter.data.excels.MonsterData
;
import
emu.grasscutter.data.excels.SceneData
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.http.Request
;
import
express.http.Response
;
import
it.unimi.dsi.fastutil.ints.Int2ObjectMap
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.InputStreamReader
;
import
java.nio.charset.StandardCharsets
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
final
class
HandbookRequestHandler
implements
DocumentationHandler
{
private
final
String
template
;
private
Map
<
Long
,
String
>
map
;
public
HandbookRequestHandler
()
{
final
File
templateFile
=
new
File
(
Utils
.
toFilePath
(
DATA
(
"documentation/handbook.html"
)));
if
(
templateFile
.
exists
())
{
template
=
new
String
(
FileUtils
.
read
(
templateFile
),
StandardCharsets
.
UTF_8
);
}
else
{
Grasscutter
.
getLogger
().
warn
(
"File does not exist: "
+
templateFile
);
template
=
null
;
}
final
String
textMapFile
=
"TextMap/TextMap"
+
DOCUMENT_LANGUAGE
+
".json"
;
try
(
InputStreamReader
fileReader
=
new
InputStreamReader
(
new
FileInputStream
(
Utils
.
toFilePath
(
RESOURCE
(
textMapFile
))),
StandardCharsets
.
UTF_8
))
{
map
=
Grasscutter
.
getGsonFactory
()
.
fromJson
(
fileReader
,
new
TypeToken
<
Map
<
Long
,
String
>>()
{
}.
getType
());
}
catch
(
IOException
e
)
{
Grasscutter
.
getLogger
().
warn
(
"Resource does not exist: "
+
textMapFile
);
map
=
new
HashMap
<>();
}
}
@Override
public
void
handle
(
Request
request
,
Response
response
)
{
if
(
template
==
null
)
{
response
.
status
(
500
);
return
;
}
final
CommandMap
cmdMap
=
new
CommandMap
(
true
);
final
Int2ObjectMap
<
AvatarData
>
avatarMap
=
GameData
.
getAvatarDataMap
();
final
Int2ObjectMap
<
ItemData
>
itemMap
=
GameData
.
getItemDataMap
();
final
Int2ObjectMap
<
SceneData
>
sceneMap
=
GameData
.
getSceneDataMap
();
final
Int2ObjectMap
<
MonsterData
>
monsterMap
=
GameData
.
getMonsterDataMap
();
// Add translated title etc. to the page.
String
content
=
template
.
replace
(
"{{TITLE}}"
,
translate
(
"documentation.handbook.title"
))
.
replace
(
"{{TITLE_COMMANDS}}"
,
translate
(
"documentation.handbook.title_commands"
))
.
replace
(
"{{TITLE_AVATARS}}"
,
translate
(
"documentation.handbook.title_avatars"
))
.
replace
(
"{{TITLE_ITEMS}}"
,
translate
(
"documentation.handbook.title_items"
))
.
replace
(
"{{TITLE_SCENES}}"
,
translate
(
"documentation.handbook.title_scenes"
))
.
replace
(
"{{TITLE_MONSTERS}}"
,
translate
(
"documentation.handbook.title_monsters"
))
.
replace
(
"{{HEADER_ID}}"
,
translate
(
"documentation.handbook.header_id"
))
.
replace
(
"{{HEADER_COMMAND}}"
,
translate
(
"documentation.handbook.header_command"
))
.
replace
(
"{{HEADER_DESCRIPTION}}"
,
translate
(
"documentation.handbook.header_description"
))
.
replace
(
"{{HEADER_AVATAR}}"
,
translate
(
"documentation.handbook.header_avatar"
))
.
replace
(
"{{HEADER_ITEM}}"
,
translate
(
"documentation.handbook.header_item"
))
.
replace
(
"{{HEADER_SCENE}}"
,
translate
(
"documentation.handbook.header_scene"
))
.
replace
(
"{{HEADER_MONSTER}}"
,
translate
(
"documentation.handbook.header_monster"
))
// Commands table
.
replace
(
"{{COMMANDS_TABLE}}"
,
cmdMap
.
getAnnotationsAsList
()
.
stream
()
.
map
(
cmd
->
"<tr><td><code>"
+
cmd
.
label
()
+
"</code></td><td>"
+
cmd
.
description
()
+
"</td></tr>"
)
.
collect
(
Collectors
.
joining
(
"\n"
)))
// Avatars table
.
replace
(
"{{AVATARS_TABLE}}"
,
GameData
.
getAvatarDataMap
().
keySet
()
.
intStream
()
.
sorted
()
.
mapToObj
(
avatarMap:
:
get
)
.
map
(
data
->
"<tr><td><code>"
+
data
.
getId
()
+
"</code></td><td>"
+
map
.
get
(
data
.
getNameTextMapHash
())
+
"</td></tr>"
)
.
collect
(
Collectors
.
joining
(
"\n"
)))
// Items table
.
replace
(
"{{ITEMS_TABLE}}"
,
GameData
.
getItemDataMap
().
keySet
()
.
intStream
()
.
sorted
()
.
mapToObj
(
itemMap:
:
get
)
.
map
(
data
->
"<tr><td><code>"
+
data
.
getId
()
+
"</code></td><td>"
+
map
.
get
(
data
.
getNameTextMapHash
())
+
"</td></tr>"
)
.
collect
(
Collectors
.
joining
(
"\n"
)))
// Scenes table
.
replace
(
"{{SCENES_TABLE}}"
,
GameData
.
getSceneDataMap
().
keySet
()
.
intStream
()
.
sorted
()
.
mapToObj
(
sceneMap:
:
get
)
.
map
(
data
->
"<tr><td><code>"
+
data
.
getId
()
+
"</code></td><td>"
+
data
.
getScriptData
()
+
"</td></tr>"
)
.
collect
(
Collectors
.
joining
(
"\n"
)))
.
replace
(
"{{MONSTERS_TABLE}}"
,
GameData
.
getMonsterDataMap
().
keySet
()
.
intStream
()
.
sorted
()
.
mapToObj
(
monsterMap:
:
get
)
.
map
(
data
->
"<tr><td><code>"
+
data
.
getId
()
+
"</code></td><td>"
+
map
.
get
(
data
.
getNameTextMapHash
())
+
"</td></tr>"
)
.
collect
(
Collectors
.
joining
(
"\n"
)));
response
.
send
(
content
);
}
}
src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.documentation
;
import
static
emu
.
grasscutter
.
Configuration
.
DATA
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.ResourceLoader
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.http.Request
;
import
express.http.Response
;
import
java.io.File
;
import
java.nio.charset.StandardCharsets
;
final
class
RootRequestHandler
implements
DocumentationHandler
{
private
final
String
template
;
public
RootRequestHandler
()
{
final
File
templateFile
=
new
File
(
Utils
.
toFilePath
(
DATA
(
"documentation/index.html"
)));
if
(
templateFile
.
exists
())
{
template
=
new
String
(
FileUtils
.
read
(
templateFile
),
StandardCharsets
.
UTF_8
);
}
else
{
Grasscutter
.
getLogger
().
warn
(
"File does not exist: "
+
templateFile
);
template
=
null
;
}
}
@Override
public
void
handle
(
Request
request
,
Response
response
)
{
if
(
template
==
null
)
{
response
.
status
(
500
);
return
;
}
String
content
=
template
.
replace
(
"{{TITLE}}"
,
translate
(
"documentation.index.title"
))
.
replace
(
"{{ITEM_HANDBOOK}}"
,
translate
(
"documentation.index.handbook"
))
.
replace
(
"{{ITEM_GACHA_MAPPING}}"
,
translate
(
"documentation.index.gacha_mapping"
));
response
.
send
(
content
);
}
}
src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.handlers
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.data.DataLoader
;
import
emu.grasscutter.server.http.objects.HttpJsonResponse
;
import
emu.grasscutter.server.http.Router
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.Express
;
import
express.http.MediaType
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.nio.charset.StandardCharsets
;
import
java.util.Objects
;
import
static
emu
.
grasscutter
.
Configuration
.*;
/**
* Handles requests related to the announcements page.
*/
public
final
class
AnnouncementsHandler
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
// hk4e-api-os.hoyoverse.com
express
.
all
(
"/common/hk4e_global/announcement/api/getAlertPic"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}"
));
// hk4e-api-os.hoyoverse.com
express
.
all
(
"/common/hk4e_global/announcement/api/getAlertAnn"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}"
));
// hk4e-api-os.hoyoverse.com
express
.
all
(
"/common/hk4e_global/announcement/api/getAnnList"
,
AnnouncementsHandler:
:
getAnnouncement
);
// hk4e-api-os-static.hoyoverse.com
express
.
all
(
"/common/hk4e_global/announcement/api/getAnnContent"
,
AnnouncementsHandler:
:
getAnnouncement
);
// hk4e-sdk-os.hoyoverse.com
express
.
all
(
"/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}"
));
express
.
get
(
"/hk4e/announcement/*"
,
AnnouncementsHandler:
:
getPageResources
);
}
private
static
void
getAnnouncement
(
Request
request
,
Response
response
)
{
String
data
=
""
;
if
(
Objects
.
equals
(
request
.
baseUrl
(),
"/common/hk4e_global/announcement/api/getAnnContent"
))
{
try
{
data
=
FileUtils
.
readToString
(
DataLoader
.
load
(
"GameAnnouncement.json"
));
}
catch
(
Exception
e
)
{
if
(
e
.
getClass
()
==
IOException
.
class
)
{
Grasscutter
.
getLogger
().
info
(
"Unable to read file 'GameAnnouncementList.json'. \n"
+
e
);
}
}
}
else
if
(
Objects
.
equals
(
request
.
baseUrl
(),
"/common/hk4e_global/announcement/api/getAnnList"
))
{
try
{
data
=
FileUtils
.
readToString
(
DataLoader
.
load
(
"GameAnnouncementList.json"
));
}
catch
(
Exception
e
)
{
if
(
e
.
getClass
()
==
IOException
.
class
)
{
Grasscutter
.
getLogger
().
info
(
"Unable to read file 'GameAnnouncementList.json'. \n"
+
e
);
}
}
}
else
{
response
.
send
(
"{\"retcode\":404,\"message\":\"Unknown request path\"}"
);
}
if
(
data
.
isEmpty
())
{
response
.
send
(
"{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}"
);
return
;
}
String
dispatchDomain
=
"http"
+
(
HTTP_ENCRYPTION
.
useInRouting
?
"s"
:
""
)
+
"://"
+
lr
(
HTTP_INFO
.
accessAddress
,
HTTP_INFO
.
bindAddress
)
+
":"
+
lr
(
HTTP_INFO
.
accessPort
,
HTTP_INFO
.
bindPort
);
data
=
data
.
replace
(
"{{DISPATCH_PUBLIC}}"
,
dispatchDomain
)
.
replace
(
"{{SYSTEM_TIME}}"
,
String
.
valueOf
(
System
.
currentTimeMillis
()));
response
.
send
(
"{\"retcode\":0,\"message\":\"OK\",\"data\": "
+
data
+
"}"
);
}
private
static
void
getPageResources
(
Request
request
,
Response
response
)
{
try
(
InputStream
filestream
=
DataLoader
.
load
(
request
.
path
()))
{
String
possibleFilename
=
Utils
.
toFilePath
(
DATA
(
request
.
path
()));
MediaType
fromExtension
=
MediaType
.
getByExtension
(
possibleFilename
.
substring
(
possibleFilename
.
lastIndexOf
(
"."
)
+
1
));
response
.
type
((
fromExtension
!=
null
)
?
fromExtension
.
getMIME
()
:
"application/octet-stream"
);
response
.
send
(
filestream
.
readAllBytes
());
}
catch
(
Exception
e
)
{
Grasscutter
.
getLogger
().
warn
(
"File does not exist: "
+
request
.
path
());
response
.
status
(
404
);
}
}
}
src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.handlers
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.database.DatabaseHelper
;
import
emu.grasscutter.game.Account
;
import
emu.grasscutter.game.gacha.GachaBanner
;
import
emu.grasscutter.game.gacha.GachaManager
;
import
emu.grasscutter.game.player.Player
;
import
emu.grasscutter.server.http.Router
;
import
emu.grasscutter.tools.Tools
;
import
emu.grasscutter.utils.FileUtils
;
import
emu.grasscutter.utils.Utils
;
import
express.Express
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
import
io.javalin.http.staticfiles.Location
;
import
java.io.File
;
import
java.nio.charset.StandardCharsets
;
import
java.util.Arrays
;
import
java.util.LinkedHashSet
;
import
java.util.Set
;
import
static
emu
.
grasscutter
.
Configuration
.
DATA
;
import
static
emu
.
grasscutter
.
utils
.
Language
.
translate
;
/**
* Handles all gacha-related HTTP requests.
*/
public
final
class
GachaHandler
implements
Router
{
public
static
final
String
gachaMappings
=
DATA
(
Utils
.
toFilePath
(
"gacha/mappings.js"
));
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
express
.
get
(
"/gacha"
,
GachaHandler:
:
gachaRecords
);
express
.
get
(
"/gacha/details"
,
GachaHandler:
:
gachaDetails
);
express
.
useStaticFallback
(
"/gacha/mappings"
,
gachaMappings
,
Location
.
EXTERNAL
);
}
private
static
void
gachaRecords
(
Request
request
,
Response
response
)
{
File
recordsTemplate
=
new
File
(
Utils
.
toFilePath
(
DATA
(
"gacha/records.html"
)));
if
(!
recordsTemplate
.
exists
())
{
Grasscutter
.
getLogger
().
warn
(
"File does not exist: "
+
recordsTemplate
);
response
.
status
(
500
);
return
;
}
String
sessionKey
=
request
.
query
(
"s"
);
Account
account
=
DatabaseHelper
.
getAccountBySessionKey
(
sessionKey
);
if
(
account
==
null
)
{
response
.
status
(
403
).
send
(
"Requested account was not found"
);
return
;
}
Player
player
=
Grasscutter
.
getGameServer
().
getPlayerByAccountId
(
account
.
getId
());
if
(
player
==
null
)
{
response
.
status
(
403
).
send
(
"No player associated with requested account"
);
return
;
}
int
page
=
0
,
gachaType
=
0
;
if
(
request
.
query
(
"p"
)
!=
null
)
page
=
Integer
.
parseInt
(
request
.
query
(
"p"
));
if
(
request
.
query
(
"gachaType"
)
!=
null
)
gachaType
=
Integer
.
parseInt
(
request
.
query
(
"gachaType"
));
String
records
=
DatabaseHelper
.
getGachaRecords
(
player
.
getUid
(),
page
,
gachaType
).
toString
();
long
maxPage
=
DatabaseHelper
.
getGachaRecordsMaxPage
(
player
.
getUid
(),
page
,
gachaType
);
String
template
=
new
String
(
FileUtils
.
read
(
recordsTemplate
),
StandardCharsets
.
UTF_8
)
.
replace
(
"{{REPLACE_RECORDS}}"
,
records
)
.
replace
(
"{{REPLACE_MAXPAGE}}"
,
String
.
valueOf
(
maxPage
))
.
replace
(
"{{LANGUAGE}}"
,
Utils
.
getLanguageCode
(
account
.
getLocale
()));
response
.
send
(
template
);
}
private
static
void
gachaDetails
(
Request
request
,
Response
response
)
{
File
detailsTemplate
=
new
File
(
Utils
.
toFilePath
(
DATA
(
"gacha/details.html"
)));
if
(!
detailsTemplate
.
exists
())
{
Grasscutter
.
getLogger
().
warn
(
"File does not exist: "
+
detailsTemplate
);
response
.
status
(
500
);
return
;
}
String
sessionKey
=
request
.
query
(
"s"
);
Account
account
=
DatabaseHelper
.
getAccountBySessionKey
(
sessionKey
);
if
(
account
==
null
)
{
response
.
status
(
403
).
send
(
"Requested account was not found"
);
return
;
}
Player
player
=
Grasscutter
.
getGameServer
().
getPlayerByAccountId
(
account
.
getId
());
if
(
player
==
null
)
{
response
.
status
(
403
).
send
(
"No player associated with requested account"
);
return
;
}
String
template
=
new
String
(
FileUtils
.
read
(
detailsTemplate
),
StandardCharsets
.
UTF_8
);
// Add translated title etc. to the page.
template
=
template
.
replace
(
"{{TITLE}}"
,
translate
(
player
,
"gacha.details.title"
))
.
replace
(
"{{AVAILABLE_FIVE_STARS}}"
,
translate
(
player
,
"gacha.details.available_five_stars"
))
.
replace
(
"{{AVAILABLE_FOUR_STARS}}"
,
translate
(
player
,
"gacha.details.available_four_stars"
))
.
replace
(
"{{AVAILABLE_THREE_STARS}}"
,
translate
(
player
,
"gacha.details.available_three_stars"
))
.
replace
(
"{{LANGUAGE}}"
,
Utils
.
getLanguageCode
(
account
.
getLocale
()));
// Get the banner info for the banner we want.
int
scheduleId
=
Integer
.
parseInt
(
request
.
query
(
"scheduleId"
));
GachaManager
manager
=
Grasscutter
.
getGameServer
().
getGachaManager
();
GachaBanner
banner
=
manager
.
getGachaBanners
().
get
(
scheduleId
);
// Add 5-star items.
Set
<
String
>
fiveStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
banner
.
getRateUpItems5
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
banner
.
getFallbackItems5Pool1
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
banner
.
getFallbackItems5Pool2
()).
forEach
(
i
->
fiveStarItems
.
add
(
Integer
.
toString
(
i
)));
template
=
template
.
replace
(
"{{FIVE_STARS}}"
,
"["
+
String
.
join
(
","
,
fiveStarItems
)
+
"]"
);
// Add 4-star items.
Set
<
String
>
fourStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
banner
.
getRateUpItems4
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
banner
.
getFallbackItems4Pool1
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
Arrays
.
stream
(
banner
.
getFallbackItems4Pool2
()).
forEach
(
i
->
fourStarItems
.
add
(
Integer
.
toString
(
i
)));
template
=
template
.
replace
(
"{{FOUR_STARS}}"
,
"["
+
String
.
join
(
","
,
fourStarItems
)
+
"]"
);
// Add 3-star items.
Set
<
String
>
threeStarItems
=
new
LinkedHashSet
<>();
Arrays
.
stream
(
banner
.
getFallbackItems3
()).
forEach
(
i
->
threeStarItems
.
add
(
Integer
.
toString
(
i
)));
template
=
template
.
replace
(
"{{THREE_STARS}}"
,
"["
+
String
.
join
(
","
,
threeStarItems
)
+
"]"
);
// Done.
response
.
send
(
template
);
}
}
src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.handlers
;
import
emu.grasscutter.GameConstants
;
import
emu.grasscutter.Grasscutter
;
import
emu.grasscutter.server.http.objects.HttpJsonResponse
;
import
emu.grasscutter.server.http.Router
;
import
emu.grasscutter.server.http.objects.WebStaticVersionResponse
;
import
express.Express
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
import
static
emu
.
grasscutter
.
Configuration
.
ACCOUNT
;
/**
* Handles all generic, hard-coded responses.
*/
public
final
class
GenericHandler
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
// hk4e-sdk-os.hoyoverse.com
express
.
get
(
"/hk4e_global/mdk/agreement/api/getAgreementInfos"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}"
));
// hk4e-sdk-os.hoyoverse.com
// this could be either GET or POST based on the observation of different clients
express
.
all
(
"/hk4e_global/combo/granter/api/compareProtocolVersion"
,
new
HttpJsonResponse
(
"{\"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\":\"\"}}}"
));
// api-account-os.hoyoverse.com
express
.
post
(
"/account/risky/api/check"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}"
));
// sdk-os-static.hoyoverse.com
express
.
get
(
"/combo/box/api/config/sdk/combo"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}"
));
// hk4e-sdk-os-static.hoyoverse.com
express
.
get
(
"/hk4e_global/combo/granter/api/getConfig"
,
new
HttpJsonResponse
(
"{\"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}}"
));
// hk4e-sdk-os-static.hoyoverse.com
express
.
get
(
"/hk4e_global/mdk/shield/api/loadConfig"
,
new
HttpJsonResponse
(
"{\"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?
// abtest-api-data-sg.hoyoverse.com
express
.
post
(
"/data_abtest_api/config/experiment/list"
,
new
HttpJsonResponse
(
"{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}"
));
// log-upload-os.mihoyo.com
express
.
all
(
"/log/sdk/upload"
,
new
HttpJsonResponse
(
"{\"code\":0}"
));
express
.
all
(
"/sdk/upload"
,
new
HttpJsonResponse
(
"{\"code\":0}"
));
express
.
post
(
"/sdk/dataUpload"
,
new
HttpJsonResponse
(
"{\"code\":0}"
));
// /perf/config/verify?device_id=xxx&platform=x&name=xxx
express
.
all
(
"/perf/config/verify"
,
new
HttpJsonResponse
(
"{\"code\":0}"
));
// webstatic-sea.hoyoverse.com
express
.
get
(
"/admin/mi18n/plat_oversea/*"
,
new
WebStaticVersionResponse
());
express
.
get
(
"/status/server"
,
GenericHandler:
:
serverStatus
);
}
private
static
void
serverStatus
(
Request
request
,
Response
response
)
{
int
playerCount
=
Grasscutter
.
getGameServer
().
getPlayers
().
size
();
int
maxPlayer
=
ACCOUNT
.
maxPlayer
;
String
version
=
GameConstants
.
VERSION
;
response
.
send
(
"{\"retcode\":0,\"status\":{\"playerCount\":"
+
playerCount
+
",\"maxPlayer\":"
+
maxPlayer
+
",\"version\":\""
+
version
+
"\"}}"
);
}
}
src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java
0 → 100644
View file @
a8293102
package
emu.grasscutter.server.http.handlers
;
import
emu.grasscutter.server.http.Router
;
import
express.Express
;
import
express.http.Request
;
import
express.http.Response
;
import
io.javalin.Javalin
;
/**
* Handles logging requests made to the server.
*/
public
final
class
LogHandler
implements
Router
{
@Override
public
void
applyRoutes
(
Express
express
,
Javalin
handle
)
{
// overseauspider.yuanshen.com
express
.
post
(
"/log"
,
LogHandler:
:
log
);
// log-upload-os.mihoyo.com
express
.
post
(
"/crash/dataUpload"
,
LogHandler:
:
log
);
}
private
static
void
log
(
Request
request
,
Response
response
)
{
// TODO: Figure out how to dump request body and log to file.
response
.
send
(
"{\"code\":0}"
);
}
}
Prev
1
…
9
10
11
12
13
14
15
16
17
…
21
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