[FEATURE] GsonManager

This commit is contained in:
Gravit 2019-04-20 05:03:06 +07:00
parent 019d864130
commit b12c26047b
20 changed files with 133 additions and 78 deletions

View file

@ -65,7 +65,7 @@ public void reload() throws Exception {
config.close();
LogHelper.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gson.fromJson(reader, Config.class);
config = Launcher.gsonManager.gson.fromJson(reader, Config.class);
}
config.verify();
Launcher.applyLauncherEnv(config.env);
@ -290,7 +290,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gson.fromJson(reader, ClientProfile.class);
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
@ -403,9 +403,6 @@ public static void main(String... args) throws Throwable {
public final Timer taskPool;
public static Gson gson;
public static GsonBuilder gsonBuilder;
public static Class<? extends LauncherBinary> defaultLauncherEXEBinaryClass = null;
public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException, InvalidKeySpecException {
@ -488,7 +485,7 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
generateConfigIfNotExists(testEnv);
LogHelper.info("Reading LaunchServer config file");
try (BufferedReader reader = IOHelper.newReader(configFile)) {
config = Launcher.gson.fromJson(reader, Config.class);
config = Launcher.gsonManager.gson.fromJson(reader, Config.class);
}
if(!Files.exists(runtimeConfigFile))
{
@ -500,7 +497,7 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
{
LogHelper.info("Reading LaunchServer runtime config file");
try (BufferedReader reader = IOHelper.newReader(runtimeConfigFile)) {
runtime = Launcher.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
runtime = Launcher.gsonManager.gson.fromJson(reader, LaunchServerRuntimeConfig.class);
}
}
runtime.verify();
@ -597,28 +594,8 @@ public LaunchServer(Path dir, boolean testEnv, String[] args) throws IOException
}
public static void initGson() {
if (Launcher.gson != null) return;
Launcher.gsonBuilder = new GsonBuilder();
Launcher.gsonBuilder.registerTypeAdapter(AuthProvider.class, new UniversalJsonAdapter<>(AuthProvider.providers));
Launcher.gsonBuilder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers));
Launcher.gsonBuilder.registerTypeAdapter(AuthHandler.class, new UniversalJsonAdapter<>(AuthHandler.providers));
Launcher.gsonBuilder.registerTypeAdapter(PermissionsHandler.class, new UniversalJsonAdapter<>(PermissionsHandler.providers));
Launcher.gsonBuilder.registerTypeAdapter(HWIDHandler.class, new UniversalJsonAdapter<>(HWIDHandler.providers));
Launcher.gsonBuilder.registerTypeAdapter(Component.class, new UniversalJsonAdapter<>(Component.providers));
Launcher.gsonBuilder.registerTypeAdapter(ProtectHandler.class, new UniversalJsonAdapter<>(ProtectHandler.providers));
Launcher.gson = Launcher.gsonBuilder.create();
//Human readable
LaunchServer.gsonBuilder = new GsonBuilder();
LaunchServer.gsonBuilder.setPrettyPrinting();
LaunchServer.gsonBuilder.registerTypeAdapter(AuthProvider.class, new UniversalJsonAdapter<>(AuthProvider.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(AuthHandler.class, new UniversalJsonAdapter<>(AuthHandler.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(PermissionsHandler.class, new UniversalJsonAdapter<>(PermissionsHandler.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(HWIDHandler.class, new UniversalJsonAdapter<>(HWIDHandler.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(Component.class, new UniversalJsonAdapter<>(Component.providers));
LaunchServer.gsonBuilder.registerTypeAdapter(ProtectHandler.class, new UniversalJsonAdapter<>(ProtectHandler.providers));
LaunchServer.gson = LaunchServer.gsonBuilder.create();
Launcher.gsonManager = new LaunchServerGsonManager();
Launcher.gsonManager.initGson();
}
private LauncherBinary binary() {
@ -654,9 +631,9 @@ public void close() {
LogHelper.info("Save LaunchServer runtime config");
try(Writer writer = IOHelper.newWriter(runtimeConfigFile))
{
if(LaunchServer.gson != null)
if(Launcher.gsonManager.configGson != null)
{
LaunchServer.gson.toJson(runtime, writer);
Launcher.gsonManager.configGson.toJson(runtime, writer);
} else {
LogHelper.error("Error writing LaunchServer runtime config file. Gson is null");
}
@ -745,7 +722,7 @@ private void generateConfigIfNotExists(boolean testEnv) throws IOException {
// Write LaunchServer config
LogHelper.info("Writing LaunchServer config file");
try (BufferedWriter writer = IOHelper.newWriter(configFile)) {
LaunchServer.gson.toJson(newConfig, writer);
Launcher.gsonManager.configGson.toJson(newConfig, writer);
}
}

View file

@ -55,22 +55,22 @@ public class SuccessResponse
}
@Override
protected Entry fetchEntry(String username) throws IOException {
return LaunchServer.gson.fromJson(HTTPRequest.jsonRequest(LaunchServer.gson.toJsonTree(new EntryRequestByUsername(username)), getUrl), Entry.class);
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUsername(username)), getUrl), Entry.class);
}
@Override
protected Entry fetchEntry(UUID uuid) throws IOException {
return LaunchServer.gson.fromJson(HTTPRequest.jsonRequest(LaunchServer.gson.toJsonTree(new EntryRequestByUUID(uuid)), getUrl), Entry.class);
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new EntryRequestByUUID(uuid)), getUrl), Entry.class);
}
@Override
protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException {
return LaunchServer.gson.fromJson(HTTPRequest.jsonRequest(LaunchServer.gson.toJsonTree(new UpdateAuthRequest(uuid, username, accessToken)), updateAuthUrl), SuccessResponse.class).success;
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateAuthRequest(uuid, username, accessToken)), updateAuthUrl), SuccessResponse.class).success;
}
@Override
protected boolean updateServerID(UUID uuid, String serverID) throws IOException {
return LaunchServer.gson.fromJson(HTTPRequest.jsonRequest(LaunchServer.gson.toJsonTree(new UpdateServerIDRequest(uuid, serverID)), updateServerIdUrl), SuccessResponse.class).success;
return Launcher.gsonManager.configGson.fromJson(HTTPRequest.jsonRequest(Launcher.gsonManager.configGson.toJsonTree(new UpdateServerIDRequest(uuid, serverID)), updateServerIdUrl), SuccessResponse.class).success;
}
@Override

View file

@ -60,7 +60,7 @@ public void init() {
Type type = new TypeToken<LinkedList<Entry>>() {
}.getType();
try (Reader reader = IOHelper.newReader(path)) {
list = Launcher.gson.fromJson(reader, type);
list = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}
@ -84,7 +84,7 @@ public void check0(HWID hwid, String username) throws HWIDException {
public void close() throws Exception {
Path path = Paths.get(filename);
try (Writer writer = IOHelper.newWriter(path)) {
LaunchServer.gson.toJson(list, writer);
Launcher.gsonManager.configGson.toJson(list, writer);
}
}

View file

@ -27,7 +27,7 @@ public void reload() {
Type type = new TypeToken<Map<String, ClientPermissions>>() {
}.getType();
try (Reader reader = IOHelper.newReader(path)) {
map = Launcher.gson.fromJson(reader, type);
map = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}
@ -51,13 +51,13 @@ public void init() {
if (!IOHelper.exists(path)) {
map = new HashMap<>();
try (Writer writer = IOHelper.newWriter(path)) {
Launcher.gson.toJson(map, writer);
Launcher.gsonManager.gson.toJson(map, writer);
} catch (IOException e) {
LogHelper.error(e);
}
}
try (Reader reader = IOHelper.newReader(path)) {
map = Launcher.gson.fromJson(reader, type);
map = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}

View file

@ -28,7 +28,7 @@ public void reload() {
Type type = new TypeToken<Map<String, Long>>() {
}.getType();
try (Reader reader = IOHelper.newReader(path)) {
map = Launcher.gson.fromJson(reader, type);
map = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}
@ -52,13 +52,13 @@ public void init() {
if (!IOHelper.exists(path)) {
map = new HashMap<>();
try (Writer writer = IOHelper.newWriter(path)) {
Launcher.gson.toJson(map, writer);
Launcher.gsonManager.gson.toJson(map, writer);
} catch (IOException e) {
LogHelper.error(e);
}
}
try (Reader reader = IOHelper.newReader(path)) {
map = Launcher.gson.fromJson(reader, type);
map = Launcher.gsonManager.gson.fromJson(reader, type);
} catch (IOException e) {
LogHelper.error(e);
}

View file

@ -44,7 +44,7 @@ public void invoke(String... args) throws Exception {
serializable.entryCache = entryCache;
serializable.usernameCache = usernamesCache;
try (Writer writer = IOHelper.newWriter(Paths.get(args[1]))) {
LaunchServer.gson.toJson(serializable, writer);
Launcher.gsonManager.configGson.toJson(serializable, writer);
}
LogHelper.subInfo("Write %d entryCache, %d usernameCache", entryCache.size(), usernamesCache.size());
} else if (args[0].equals("load")) {
@ -52,7 +52,7 @@ public void invoke(String... args) throws Exception {
int size_entry = 0;
int size_username = 0;
try (Reader reader = IOHelper.newReader(Paths.get(args[1]))) {
EntryAndUsername entryAndUsername = LaunchServer.gson.fromJson(reader, EntryAndUsername.class);
EntryAndUsername entryAndUsername = Launcher.gsonManager.configGson.fromJson(reader, EntryAndUsername.class);
size_entry = entryAndUsername.entryCache.size();
size_username = entryAndUsername.usernameCache.size();
authHandler.loadEntryCache(entryAndUsername.entryCache);

View file

@ -36,7 +36,7 @@ public void invoke(String... args) throws Exception {
LogHelper.info("Sessions write to %s", args[1]);
Set<Client> clientSet = server.sessionManager.getSessions();
try (Writer writer = IOHelper.newWriter(Paths.get(args[1]))) {
LaunchServer.gson.toJson(clientSet, writer);
Launcher.gsonManager.configGson.toJson(clientSet, writer);
}
LogHelper.subInfo("Write %d sessions", clientSet.size());
} else if (args[0].equals("load")) {
@ -45,7 +45,7 @@ public void invoke(String... args) throws Exception {
try (Reader reader = IOHelper.newReader(Paths.get(args[1]))) {
Type setType = new TypeToken<HashSet<Client>>() {
}.getType();
Set<Client> clientSet = LaunchServer.gson.fromJson(reader, setType);
Set<Client> clientSet = Launcher.gsonManager.configGson.fromJson(reader, setType);
size = clientSet.size();
server.sessionManager.loadSessions(clientSet);
}

View file

@ -52,13 +52,13 @@ public void invoke(String... args) throws IOException, CommandException {
ClientProfile client;
String profilePath = String.format("ru/gravit/launchserver/defaults/profile%s.cfg", version.name);
try (BufferedReader reader = IOHelper.newReader(IOHelper.getResourceURL(profilePath))) {
client = LaunchServer.gson.fromJson(reader, ClientProfile.class);
client = Launcher.gsonManager.configGson.fromJson(reader, ClientProfile.class);
}
client.setTitle(dirName);
client.setDir(dirName);
try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir,
dirName, "cfg"))) {
LaunchServer.gson.toJson(client, writer);
Launcher.gsonManager.configGson.toJson(client, writer);
}
// Finished

View file

@ -73,7 +73,7 @@ public void invoke(String... args) throws Exception {
if (args.length <= 2) throw new IllegalArgumentException("Must set file");
String fileName = args[2];
try (Reader reader = IOHelper.newReader(Paths.get(fileName))) {
Component component = LaunchServer.gson.fromJson(reader, Component.class);
Component component = Launcher.gsonManager.configGson.fromJson(reader, Component.class);
component.preInit(server);
component.init(server);
component.postInit(server);

View file

@ -0,0 +1,28 @@
package ru.gravit.launchserver.manangers;
import com.google.gson.GsonBuilder;
import ru.gravit.launcher.Launcher;
import ru.gravit.launcher.managers.GsonManager;
import ru.gravit.launchserver.auth.handler.AuthHandler;
import ru.gravit.launchserver.auth.hwid.HWIDHandler;
import ru.gravit.launchserver.auth.permissions.PermissionsHandler;
import ru.gravit.launchserver.auth.protect.ProtectHandler;
import ru.gravit.launchserver.auth.provider.AuthProvider;
import ru.gravit.launchserver.auth.texture.TextureProvider;
import ru.gravit.launchserver.components.Component;
import ru.gravit.utils.UniversalJsonAdapter;
public class LaunchServerGsonManager extends GsonManager {
@Override
public void registerAdapters(GsonBuilder builder) {
super.registerAdapters(builder);
Launcher.gsonManager.gsonBuilder = new GsonBuilder();
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(AuthProvider.class, new UniversalJsonAdapter<>(AuthProvider.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(TextureProvider.class, new UniversalJsonAdapter<>(TextureProvider.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(AuthHandler.class, new UniversalJsonAdapter<>(AuthHandler.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(PermissionsHandler.class, new UniversalJsonAdapter<>(PermissionsHandler.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(HWIDHandler.class, new UniversalJsonAdapter<>(HWIDHandler.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(Component.class, new UniversalJsonAdapter<>(Component.providers));
Launcher.gsonManager.gsonBuilder.registerTypeAdapter(ProtectHandler.class, new UniversalJsonAdapter<>(ProtectHandler.providers));
}
}

View file

@ -9,6 +9,7 @@
import ru.gravit.launcher.gui.RuntimeProvider;
import ru.gravit.launcher.hasher.HashedEntry;
import ru.gravit.launcher.hasher.HashedEntryAdapter;
import ru.gravit.launcher.managers.ClientGsonManager;
import ru.gravit.launcher.request.Request;
import ru.gravit.launcher.request.websockets.StandartClientWebSocketService;
import ru.gravit.utils.helper.CommonHelper;
@ -43,10 +44,8 @@ public static void main(String... args) throws Throwable {
}
public static void initGson() {
if (Launcher.gson != null) return;
Launcher.gsonBuilder = new GsonBuilder();
Launcher.gsonBuilder.registerTypeAdapter(HashedEntry.class, new HashedEntryAdapter());
Launcher.gson = Launcher.gsonBuilder.create();
Launcher.gsonManager = new ClientGsonManager();
Launcher.gsonManager.initGson();
}
// Instance

View file

@ -6,6 +6,7 @@
import ru.gravit.launcher.guard.LauncherGuardManager;
import ru.gravit.launcher.gui.JSRuntimeProvider;
import ru.gravit.launcher.hasher.*;
import ru.gravit.launcher.managers.ClientGsonManager;
import ru.gravit.launcher.profiles.ClientProfile;
import ru.gravit.launcher.profiles.PlayerProfile;
import ru.gravit.launcher.request.Request;
@ -35,7 +36,6 @@
import java.util.*;
public final class ClientLauncher {
private static Gson gson = new Gson();
private static final class ClassPathFileVisitor extends SimpleFileVisitor<Path> {
private final Collection<Path> result;
@ -187,7 +187,7 @@ private static void addClientArgs(Collection<String> args, ClientProfile profile
properties.cloakURL = new String[]{pp.cloak.url};
properties.cloakDigest = new String[]{SecurityHelper.toHex(pp.cloak.digest)};
}
Collections.addAll(args, "--userProperties", ClientLauncher.gson.toJson(properties));
Collections.addAll(args, "--userProperties", Launcher.gsonManager.gson.toJson(properties));
// Add asset index
Collections.addAll(args, "--assetIndex", profile.getAssetIndex());
@ -309,7 +309,7 @@ public static Process launch(
}
try (HOutput output = new HOutput(client.getOutputStream())) {
params.write(output);
output.writeString(Launcher.gson.toJson(profile), 0);
output.writeString(Launcher.gsonManager.gson.toJson(profile), 0);
assetHDir.write(output);
clientHDir.write(output);
}
@ -420,7 +420,7 @@ public static void main(String... args) throws Throwable {
socket.connect(new InetSocketAddress(SOCKET_HOST, SOCKET_PORT));
try (HInput input = new HInput(socket.getInputStream())) {
params = new Params(input);
profile = gson.fromJson(input.readString(0), ClientProfile.class);
profile = Launcher.gsonManager.gson.fromJson(input.readString(0), ClientProfile.class);
assetHDir = new HashedDir(input);
clientHDir = new HashedDir(input);
}
@ -521,10 +521,8 @@ private static LinkedList<Path> resolveClassPathList(Path clientDir, String... c
}
public static void initGson() {
if (Launcher.gson != null) return;
Launcher.gsonBuilder = new GsonBuilder();
Launcher.gsonBuilder.registerTypeAdapter(HashedEntry.class, new HashedEntryAdapter());
Launcher.gson = Launcher.gsonBuilder.create();
Launcher.gsonManager = new ClientGsonManager();
Launcher.gsonManager.initGson();
}
@LauncherAPI

View file

@ -108,7 +108,7 @@ public void read(HInput input) throws IOException, SignatureException {
lastProfiles.clear();
int lastProfilesCount = input.readLength(0);
for (int i = 0; i < lastProfilesCount; i++) {
lastProfiles.add(Launcher.gson.fromJson(input.readString(0), ClientProfile.class));
lastProfiles.add(Launcher.gsonManager.gson.fromJson(input.readString(0), ClientProfile.class));
}
lastHDirs.clear();
int lastHDirsCount = input.readLength(0);
@ -150,7 +150,7 @@ public void write(HOutput output) throws IOException {
}
output.writeLength(lastProfiles.size(), 0);
for (ClientProfile profile : lastProfiles) {
output.writeString(Launcher.gson.toJson(profile), 0);
output.writeString(Launcher.gsonManager.gson.toJson(profile), 0);
}
output.writeLength(lastHDirs.size(), 0);
for (Map.Entry<String, HashedDir> entry : lastHDirs.entrySet()) {

View file

@ -0,0 +1,11 @@
package ru.gravit.launcher.managers;
import com.google.gson.GsonBuilder;
public class ClientGsonManager extends GsonManager {
@Override
public void registerAdapters(GsonBuilder builder) {
super.registerAdapters(builder);
}
}

View file

@ -37,8 +37,6 @@ public class ServerWrapper extends JsonConfigurable<ServerWrapper.Config> {
public ClassLoader loader;
public ClientPermissions permissions;
public static ServerWrapper wrapper;
public static Gson gson;
private static GsonBuilder gsonBuiler;
public static Path modulesDir = Paths.get(System.getProperty("serverwrapper.modulesDir", "modules"));
public static Path configFile = Paths.get(System.getProperty("serverwrapper.configFile", "ServerWrapperConfig.json"));
@ -97,15 +95,11 @@ public boolean loopAuth(int count, int sleeptime) {
}
public static void initGson() {
if (Launcher.gson != null) return;
Launcher.gsonBuilder = new GsonBuilder();
Launcher.gson = Launcher.gsonBuilder.create();
Launcher.gsonManager = new ServerWrapperGsonManager();
Launcher.gsonManager.initGson();
}
public void run(String... args) throws Throwable {
gsonBuiler = new GsonBuilder();
gsonBuiler.setPrettyPrinting();
gson = gsonBuiler.create();
initGson();
if (args.length > 0 && args[0].equals("setup") && !disableSetup) {
LogHelper.debug("Read ServerWrapperConfig.json");

View file

@ -0,0 +1,11 @@
package ru.gravit.launcher.server;
import com.google.gson.GsonBuilder;
import ru.gravit.launcher.managers.GsonManager;
public class ServerWrapperGsonManager extends GsonManager {
@Override
public void registerAdapters(GsonBuilder builder) {
super.registerAdapters(builder);
}
}

View file

@ -2,6 +2,7 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ru.gravit.launcher.managers.GsonManager;
import ru.gravit.launcher.modules.ModulesManager;
import ru.gravit.launcher.profiles.ClientProfile;
import ru.gravit.launcher.serialize.HInput;
@ -63,8 +64,7 @@ public final class Launcher {
public static final int PATCH = 0;
public static final int BUILD = 1;
public static final Version.Type RELEASE = Version.Type.DEV;
public static GsonBuilder gsonBuilder;
public static Gson gson;
public static GsonManager gsonManager;
@LauncherAPI
public static LauncherConfig getConfig() {

View file

@ -0,0 +1,37 @@
package ru.gravit.launcher.managers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import ru.gravit.launcher.hasher.HashedEntry;
import ru.gravit.launcher.hasher.HashedEntryAdapter;
public class GsonManager {
public GsonBuilder gsonBuilder;
public Gson gson;
public GsonBuilder configGsonBuilder;
public Gson configGson;
public void initGson()
{
gsonBuilder = new GsonBuilder();
configGsonBuilder = new GsonBuilder();
configGsonBuilder.setPrettyPrinting();
registerAdapters(gsonBuilder);
registerAdapters(configGsonBuilder);
preConfigGson(configGsonBuilder);
preGson(gsonBuilder);
gson = gsonBuilder.create();
configGson = configGsonBuilder.create();
}
public void registerAdapters(GsonBuilder builder)
{
builder.registerTypeAdapter(HashedEntry.class, new HashedEntryAdapter());
}
public void preConfigGson(GsonBuilder gsonBuilder)
{
//skip
}
public void preGson(GsonBuilder gsonBuilder)
{
//skip
}
}

View file

@ -30,14 +30,14 @@ public JsonConfigurable(Type type, Path configPath) {
@LauncherAPI
public void saveConfig(Path configPath) throws IOException {
try (BufferedWriter writer = IOHelper.newWriter(configPath)) {
Launcher.gson.toJson(getConfig(), type, writer);
Launcher.gsonManager.gson.toJson(getConfig(), type, writer);
}
}
@LauncherAPI
public void loadConfig(Path configPath) throws IOException {
if (generateConfigIfNotExists(configPath)) return;
try (BufferedReader reader = IOHelper.newReader(configPath)) {
setConfig(Launcher.gson.fromJson(reader, type));
setConfig(Launcher.gsonManager.gson.fromJson(reader, type));
}
}
@LauncherAPI

@ -1 +1 @@
Subproject commit 2d7c696aebf750f29b0a185faea25a3262fa905e
Subproject commit f8e6af4366347ba94c45d9d8989256bb45dbe13b