fix(server): login and map stuff
This commit is contained in:
1 parent
e05122031d
commit
3839761c34
23 files changed
+234
-136
No files matched your search
+1
-1
@@ -1,2 +1,2 @@
|
||||
java -jar LunaGC-7.0.0.jar
|
||||
java -jar LunaGC-7.1.0.jar
|
||||
pause
|
||||
+1
-1
@@ -65,7 +65,7 @@ sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
group = 'io.grasscutter'
|
||||
version = '7.0.0'
|
||||
version = '7.1.0'
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
|
||||
@@ -7,8 +7,8 @@ import emu.grasscutter.utils.objects.SparseSet;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class GameConstants {
|
||||
public static String VERSION = "7.0.0";
|
||||
public static int[] VERSION_PARTS = {7, 0, 0};
|
||||
public static String VERSION = "7.1.0";
|
||||
public static int[] VERSION_PARTS = {7, 1, 0};
|
||||
public static boolean DEBUG = false;
|
||||
|
||||
public static final int ENTITY_ID_BIT_SHIFT = 21;
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class Grasscutter {
|
||||
// Initialize server.
|
||||
logger.info(translate("messages.status.starting"));
|
||||
logger.info(translate("messages.status.game_version", GameConstants.VERSION));
|
||||
logger.info(translate("messages.status.version", "7.0.0", "capy"));
|
||||
logger.info(translate("messages.status.version", GameConstants.VERSION, "capy"));
|
||||
|
||||
// Initialize database.
|
||||
DatabaseManager.initialize();
|
||||
|
||||
@@ -242,7 +242,7 @@ public final class SetPropCommand implements CommandHandler {
|
||||
|
||||
boolean forbidSimpleUnlock = pointData.isForbidSimpleUnlock();
|
||||
boolean sceneBuildingPointLocked =
|
||||
pointData.getType().equals("SceneBuildingPoint") && !pointData.isUnlocked();
|
||||
"SceneBuildingPoint".equals(pointData.getType()) && !pointData.isUnlocked();
|
||||
|
||||
if (forbidSimpleUnlock || sceneBuildingPointLocked) scenePointsBackup.remove(p);
|
||||
}
|
||||
|
||||
@@ -362,7 +362,6 @@ public class ConfigContainer {
|
||||
/* Protobuf field number the Lua payload is written to, flat at the top level.
|
||||
* 0 uses the built-in default (6, which is `payload` on 7.0's message). */
|
||||
public int payloadField = 0;
|
||||
public String[] sweep = {};
|
||||
}
|
||||
|
||||
public static class BirthdayMailOptions {
|
||||
@@ -438,7 +437,7 @@ public class ConfigContainer {
|
||||
|
||||
public static class JoinOptions {
|
||||
public int[] welcomeEmotes = {2007, 1002, 4010};
|
||||
public String welcomeMessage = "Welcome to LunaGC 7.0.0";
|
||||
public String welcomeMessage = "Welcome to LunaGC " + emu.grasscutter.GameConstants.VERSION;
|
||||
public JoinOptions.Mail welcomeMail = new JoinOptions.Mail();
|
||||
|
||||
public static class Mail {
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
package emu.grasscutter.data.excels;
|
||||
|
||||
import emu.grasscutter.data.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ResourceType(name = "OpenStateConfigData.json", loadPriority = ResourceType.LoadPriority.HIGHEST)
|
||||
public class OpenStateData extends GameResource {
|
||||
@Getter(onMethod_ = @Override)
|
||||
private int id;
|
||||
@Getter
|
||||
private boolean defaultState;
|
||||
@Getter
|
||||
private boolean allowClientOpen;
|
||||
@Getter
|
||||
private int systemOpenUiId;
|
||||
@Getter
|
||||
private List<OpenStateCond> cond;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Add this open state to the global list.
|
||||
GameData.getOpenStateList().add(this);
|
||||
|
||||
// Remove any empty conditions
|
||||
if (this.cond != null) {
|
||||
this.cond.removeIf(c -> c.getCondType() == null);
|
||||
} else {
|
||||
this.cond = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public enum OpenStateCondType {
|
||||
OPEN_STATE_COND_PLAYER_LEVEL,
|
||||
OPEN_STATE_COND_QUEST,
|
||||
OPEN_STATE_OFFERING_LEVEL,
|
||||
OPEN_STATE_CITY_REPUTATION_LEVEL,
|
||||
OPEN_STATE_COND_PARENT_QUEST
|
||||
}
|
||||
|
||||
public static class OpenStateCond {
|
||||
@Getter
|
||||
private OpenStateCondType condType;
|
||||
@Getter
|
||||
private int param;
|
||||
@Getter
|
||||
private int param2;
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.data.excels;
|
||||
|
||||
import emu.grasscutter.data.*;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ResourceType(name = "OpenStateConfigData.json", loadPriority = ResourceType.LoadPriority.HIGHEST)
|
||||
public class OpenStateData extends GameResource {
|
||||
@Getter(onMethod_ = @Override)
|
||||
private int id;
|
||||
@Getter
|
||||
private boolean defaultState;
|
||||
@Getter
|
||||
private boolean allowClientOpen;
|
||||
@Getter
|
||||
private int systemOpenUiId;
|
||||
@Getter
|
||||
private List<OpenStateCond> cond;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
// Add this open state to the global list.
|
||||
GameData.getOpenStateList().add(this);
|
||||
|
||||
// Remove any empty conditions
|
||||
if (this.cond != null) {
|
||||
this.cond.removeIf(c -> c.getCondType() == null);
|
||||
} else {
|
||||
this.cond = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public enum OpenStateCondType {
|
||||
OPEN_STATE_COND_PLAYER_LEVEL,
|
||||
OPEN_STATE_COND_QUEST,
|
||||
OPEN_STATE_OFFERING_LEVEL,
|
||||
OPEN_STATE_CITY_REPUTATION_LEVEL,
|
||||
OPEN_STATE_COND_PARENT_QUEST
|
||||
}
|
||||
|
||||
public static class OpenStateCond {
|
||||
@Getter
|
||||
private OpenStateCondType condType;
|
||||
@Getter
|
||||
private int param;
|
||||
@Getter
|
||||
private int param2;
|
||||
}
|
||||
}
|
||||
␍
|
||||
@@ -24,6 +24,10 @@ public class ActivityData extends GameResource {
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
if (watcherId == null) {
|
||||
this.watcherDataList = List.of();
|
||||
return;
|
||||
}
|
||||
this.watcherDataList =
|
||||
watcherId.stream()
|
||||
.map(item -> GameData.getActivityWatcherDataMap().get(item.intValue()))
|
||||
|
||||
@@ -20,8 +20,14 @@ public class TowerScheduleData extends GameResource {
|
||||
@Override
|
||||
public void onLoad() {
|
||||
super.onLoad();
|
||||
if (this.schedules == null) {
|
||||
this.schedules = List.of();
|
||||
return;
|
||||
}
|
||||
this.schedules =
|
||||
this.schedules.stream().filter(item -> item.getFloorList().size() > 0).toList();
|
||||
this.schedules.stream()
|
||||
.filter(item -> item.getFloorList() != null && !item.getFloorList().isEmpty())
|
||||
.toList();
|
||||
}
|
||||
|
||||
public int getScheduleId() {
|
||||
|
||||
@@ -235,6 +235,8 @@ public class ChatSystem implements ChatSystemHandler {
|
||||
private void sendServerWelcomeMessages(Player player) {
|
||||
var joinOptions = GAME_INFO.joinOptions;
|
||||
|
||||
player.sendPacket(new PacketGetPlayerFriendListRsp(player));
|
||||
|
||||
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
|
||||
this.sendPrivateMessageFromServer(
|
||||
player.getUid(),
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package emu.grasscutter.server.game;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.protobuf.Descriptors.Descriptor;
|
||||
import com.google.protobuf.UnknownFieldSet;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.utils.FileUtils;
|
||||
import emu.grasscutter.utils.JsonUtils;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class FanOut {
|
||||
public static final class Target {
|
||||
public int cmdId;
|
||||
public String symbol;
|
||||
public Map<String, Integer> fields = new HashMap<>();
|
||||
}
|
||||
|
||||
private static Map<String, List<Target>> table;
|
||||
private static final Map<String, Map<Integer, String>> numberNames = new ConcurrentHashMap<>();
|
||||
private static final Set<String> announced = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private static synchronized Map<String, List<Target>> table() {
|
||||
if (table != null) return table;
|
||||
table = Map.of();
|
||||
var path = FileUtils.getDataUserPath("proto-fanout.json");
|
||||
if (!Files.exists(path)) return table;
|
||||
try {
|
||||
Map<String, List<Target>> loaded =
|
||||
JsonUtils.decode(
|
||||
Files.readString(path), new TypeToken<Map<String, List<Target>>>() {}.getType());
|
||||
if (loaded != null) table = loaded;
|
||||
Grasscutter.getLogger().info("[fan-out] {} packets resolved by family", table.size());
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().warn("[fan-out] could not read {}: {}", path, e.getMessage());
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
public static boolean send(GameSession session, BasePacket packet) {
|
||||
var simple = packet.getClass().getSimpleName();
|
||||
if (!simple.startsWith("Packet")) return false;
|
||||
var name = simple.substring("Packet".length());
|
||||
var targets = table().get(name);
|
||||
if (targets == null || targets.isEmpty() || packet.getData() == null) return false;
|
||||
|
||||
var names = numberNames.computeIfAbsent(name, FanOut::numberNames);
|
||||
if (names.isEmpty()) return false;
|
||||
|
||||
UnknownFieldSet source;
|
||||
try {
|
||||
source = UnknownFieldSet.parseFrom(packet.getData());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (announced.add(name)) {
|
||||
Grasscutter.getLogger()
|
||||
.info("[fan-out] {} goes to {} candidate(s)", name, targets.size());
|
||||
}
|
||||
|
||||
for (var target : targets) {
|
||||
var out = UnknownFieldSet.newBuilder();
|
||||
for (var entry : source.asMap().entrySet()) {
|
||||
var field = names.get(entry.getKey());
|
||||
var number = field == null ? null : target.fields.get(field);
|
||||
if (number != null) out.mergeField(number, entry.getValue());
|
||||
}
|
||||
var copy =
|
||||
packet.shouldBuildHeader()
|
||||
? new BasePacket(target.cmdId, true)
|
||||
: new BasePacket(target.cmdId);
|
||||
if (!packet.shouldBuildHeader() && packet.getHeader() != null) {
|
||||
copy.setHeader(packet.getHeader());
|
||||
}
|
||||
copy.shouldEncrypt = packet.shouldEncrypt;
|
||||
copy.setUseDispatchKey(packet.useDispatchKey());
|
||||
copy.setData(out.build().toByteArray());
|
||||
session.send(copy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Map<Integer, String> numberNames(String name) {
|
||||
var out = new HashMap<Integer, String>();
|
||||
try {
|
||||
var cls = Class.forName("emu.grasscutter.net.proto." + name + "OuterClass$" + name);
|
||||
var descriptor = (Descriptor) cls.getMethod("getDescriptor").invoke(null);
|
||||
for (var field : descriptor.getFields()) out.put(field.getNumber(), field.getName());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,7 @@ public final class GameServerPacketHandler {
|
||||
public void handle(GameSession session, int opcode, byte[] header, byte[] payload) {
|
||||
PacketHandler handler = this.handlers.get(opcode);
|
||||
|
||||
|
||||
if (handler == null && unannounced.add(opcode)) {
|
||||
String fields;
|
||||
try {
|
||||
|
||||
@@ -121,11 +121,15 @@ public class GameSession implements GameSessionManager.KcpChannel {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getOpcode() <= 0 && FanOut.send(this, packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getOpcode() <= 0) {
|
||||
if (missingCmdIdReported.add(packet.getClass().getSimpleName())) {
|
||||
Grasscutter.getLogger()
|
||||
.warn(
|
||||
"{} has no 7.0 CmdId, so it is not being sent.",
|
||||
"{} has no CmdId in this version, so it is not being sent.",
|
||||
packet.getClass().getSimpleName());
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,7 @@ import emu.grasscutter.*;
|
||||
import emu.grasscutter.database.DatabaseHelper;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.packet.*;
|
||||
import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq;
|
||||
import emu.grasscutter.server.event.game.PlayerCreationEvent;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.server.game.GameSession.SessionState;
|
||||
@@ -19,17 +20,13 @@ import javax.crypto.Cipher;
|
||||
@Opcodes(PacketOpcodes.GetPlayerTokenReq)
|
||||
public class HandlerGetPlayerTokenReq extends PacketHandler {
|
||||
|
||||
private static final int F_ACCOUNT_UID = 2; // 6.7: 2
|
||||
private static final int F_ACCOUNT_TOKEN = 6; // 6.7: 3
|
||||
private static final int F_KEY_ID = 588; // 6.7: 41
|
||||
private static final int F_CLIENT_RAND_KEY = 932; // 6.7: 1475
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
var accountId = ProtoRead.string(payload, F_ACCOUNT_UID);
|
||||
var accountToken = ProtoRead.string(payload, F_ACCOUNT_TOKEN);
|
||||
var clientRandKey = ProtoRead.string(payload, F_CLIENT_RAND_KEY);
|
||||
var keyId = (int) ProtoRead.varint(payload, F_KEY_ID);
|
||||
var req = GetPlayerTokenReq.parseFrom(payload);
|
||||
var accountId = req.getAccountUid();
|
||||
var accountToken = req.getAccountToken();
|
||||
var clientRandKey = req.getClientRandKey();
|
||||
var keyId = req.getKeyId();
|
||||
|
||||
var account = DispatchUtils.authenticate(accountId, accountToken);
|
||||
|
||||
|
||||
@@ -2,22 +2,19 @@ package emu.grasscutter.server.packet.recv;
|
||||
|
||||
import emu.grasscutter.net.packet.*;
|
||||
import emu.grasscutter.net.proto.PacketHeadOuterClass.PacketHead;
|
||||
import emu.grasscutter.net.proto.PingReqOuterClass.PingReq;
|
||||
import emu.grasscutter.server.game.GameSession;
|
||||
import emu.grasscutter.utils.ProtoRead;
|
||||
import emu.grasscutter.server.packet.send.PacketPingRsp;
|
||||
|
||||
@Opcodes(PacketOpcodes.PingReq)
|
||||
public class HandlerPingReq extends PacketHandler {
|
||||
|
||||
// read by field number so a renumber cannot silently zero the ping
|
||||
private static final int F_CLIENT_TIME = 1;
|
||||
private static final int F_SEQ = 15;
|
||||
|
||||
@Override
|
||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||
PacketHead head = PacketHead.parseFrom(header);
|
||||
var clientTime = (int) ProtoRead.varint(payload, F_CLIENT_TIME);
|
||||
var seq = (int) ProtoRead.varint(payload, F_SEQ);
|
||||
var req = PingReq.parseFrom(payload);
|
||||
var clientTime = req.getClientTime();
|
||||
var seq = req.getSeq();
|
||||
|
||||
session.updateLastPingTime(clientTime);
|
||||
|
||||
|
||||
@@ -48,11 +48,8 @@ public class HandlerSceneInitFinishReq extends PacketHandler {
|
||||
|
||||
// The wind seed CmdId is unconfirmed for 6.7; a wrong one can hang the client here, so
|
||||
// cmdId=0 suppresses the packet entirely rather than falling back to the UID watermark.
|
||||
var sweep = Configuration.GAME_OPTIONS.watermark.sweep;
|
||||
if (PacketWindSeedClientNotify.disabled()) {
|
||||
Grasscutter.getLogger().info("[watermark] suppressed (cmdId=0)");
|
||||
} else if (sweep != null && sweep.length > 0) {
|
||||
sendSweep(session, sweep);
|
||||
} else {
|
||||
var packet = buildWatermarkPacket();
|
||||
Grasscutter.getLogger()
|
||||
@@ -69,50 +66,6 @@ public class HandlerSceneInitFinishReq extends PacketHandler {
|
||||
player.getScene().playerSceneInitialized(player);
|
||||
}
|
||||
|
||||
private static void sendSweep(GameSession session, String[] sweep) {
|
||||
var options = Configuration.GAME_OPTIONS.watermark;
|
||||
if (!options.enabled || options.text == null || options.text.isBlank()) {
|
||||
Grasscutter.getLogger().warn("[watermark] sweep configured but the watermark is disabled or has no text.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var entry : sweep) {
|
||||
var parts = entry.split(":");
|
||||
int cmdId, field;
|
||||
try {
|
||||
cmdId = Integer.parseInt(parts[0].trim());
|
||||
field = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 1;
|
||||
} catch (Exception e) {
|
||||
Grasscutter.getLogger().warn("[watermark] ignoring malformed sweep entry '{}'", entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
// These two are PlayerLoginRsp and GetPlayerTokenRsp. A Lua payload under either breaks
|
||||
// the login handshake instead of the watermark, which is a far worse failure to debug.
|
||||
if (cmdId == 8191 || cmdId == 9250) {
|
||||
Grasscutter.getLogger().warn("[watermark] refusing to sweep {}, it is a login packet", cmdId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Each candidate writes its own CmdId as the watermark text, so whichever one the
|
||||
// client actually accepts identifies itself on screen - one login instead of a bisect.
|
||||
var text = String.valueOf(cmdId);
|
||||
int from = RichTextUtils.parseColor(options.color);
|
||||
int to = RichTextUtils.parseColor(options.gradientTo);
|
||||
var payload =
|
||||
from < 0
|
||||
? text
|
||||
: (to < 0
|
||||
? RichTextUtils.colorize(text, from)
|
||||
: RichTextUtils.gradient(text, from, to));
|
||||
if (!WatermarkUtils.fits(payload)) payload = text;
|
||||
|
||||
Grasscutter.getLogger().info("[watermark] sweep -> cmdId={} payloadField={}", cmdId, field);
|
||||
session.send(
|
||||
new PacketWindSeedClientNotify(WatermarkUtils.buildLuac(payload), cmdId, field));
|
||||
}
|
||||
}
|
||||
|
||||
private static BasePacket buildWatermarkPacket() {
|
||||
var options = Configuration.GAME_OPTIONS.watermark;
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@ public class PacketEnterSceneReadyRsp extends BasePacket {
|
||||
.setRetcode(maskedRetcode)
|
||||
.build();
|
||||
|
||||
this.setData(p.toByteArray());
|
||||
this.setData(p);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.net.packet.*;
|
||||
import emu.grasscutter.net.proto.GetScenePointRspOuterClass.GetScenePointRsp;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class PacketGetScenePointRsp extends BasePacket {
|
||||
|
||||
@@ -23,10 +25,18 @@ public class PacketGetScenePointRsp extends BasePacket {
|
||||
p.addAllUnhidePointList(unlockedPoints);
|
||||
}
|
||||
|
||||
for (int i = 1; i < 9; i++) {
|
||||
p.addUnlockAreaList(i);
|
||||
}
|
||||
p.addAllUnlockAreaList(areas(player, sceneId));
|
||||
|
||||
this.setData(p);
|
||||
}
|
||||
|
||||
private static Collection<Integer> areas(Player player, int sceneId) {
|
||||
var unlocked = player.getUnlockedSceneAreas(sceneId);
|
||||
if (!unlocked.isEmpty()) return unlocked;
|
||||
var all = new TreeSet<Integer>();
|
||||
for (var area : GameData.getWorldAreaDataMap().values()) {
|
||||
if (area.getSceneId() == sceneId && area.getChildArea() == 0) all.add(area.getParentArea());
|
||||
}
|
||||
return all;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
// removed due to proto not existing
|
||||
// removed due to proto not existing
|
||||
␍
|
||||
@@ -18,6 +18,6 @@ public class PacketSceneInitFinishRsp extends BasePacket {
|
||||
.setRetcode(maskedRetcode)
|
||||
.build();
|
||||
|
||||
this.setData(p.toByteArray());
|
||||
this.setData(p);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,10 @@ public class PacketWindSeedClientNotify extends BasePacket {
|
||||
}
|
||||
|
||||
/** `payload` on 7.0's _PlayerNormalLuaShellNotify. 6.7 had it at 11, 6.6 at 8. */
|
||||
private static final int DEFAULT_PAYLOAD_FIELD = 6;
|
||||
private static final int DEFAULT_PAYLOAD_FIELD =
|
||||
emu.grasscutter.net.proto.WindSeedType1NotifyOuterClass.WindSeedType1Notify.getDescriptor()
|
||||
.findFieldByName("_payload")
|
||||
.getNumber();
|
||||
|
||||
public static boolean disabled() {
|
||||
return Configuration.GAME_OPTIONS.watermark.cmdId < 0;
|
||||
@@ -64,10 +67,4 @@ public class PacketWindSeedClientNotify extends BasePacket {
|
||||
super(cmdId());
|
||||
this.setData(encode(data));
|
||||
}
|
||||
|
||||
/** One candidate of a sweep: an explicit CmdId and payload field rather than the configured pair. */
|
||||
public PacketWindSeedClientNotify(byte[] data, int cmdId, int payloadField) {
|
||||
super(cmdId);
|
||||
this.setData(encode(data, payloadField));
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,31 @@ public interface JsonAdapters {
|
||||
}
|
||||
}
|
||||
|
||||
class UnsignedLongAdapter extends TypeAdapter<Long> {
|
||||
@Override
|
||||
public Long read(JsonReader reader) throws IOException {
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull();
|
||||
return null;
|
||||
}
|
||||
val value = reader.nextString();
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException signed) {
|
||||
try {
|
||||
return Long.parseUnsignedLong(value);
|
||||
} catch (NumberFormatException unsigned) {
|
||||
throw new IOException("Invalid long - " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter writer, Long value) throws IOException {
|
||||
writer.value(value);
|
||||
}
|
||||
}
|
||||
|
||||
class IntListAdapter extends TypeAdapter<IntList> {
|
||||
@Override
|
||||
public IntList read(JsonReader reader) throws IOException {
|
||||
|
||||
@@ -19,6 +19,8 @@ public final class JsonUtils {
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter())
|
||||
.registerTypeAdapter(IntList.class, new IntListAdapter())
|
||||
.registerTypeAdapter(long.class, new UnsignedLongAdapter())
|
||||
.registerTypeAdapter(Long.class, new UnsignedLongAdapter())
|
||||
.registerTypeAdapter(Position.class, new PositionAdapter())
|
||||
.registerTypeAdapter(GridPosition.class, new GridPositionAdapter())
|
||||
.registerTypeAdapter(byte[].class, new ByteArrayAdapter())
|
||||
|
||||
Reference in new issue
Block a user