[FEATURE][EXPERIMENTAL] Refactor ProfilesProvider

This commit is contained in:
Gravita 2025-06-11 21:09:38 +07:00
parent 88fa3ca1a0
commit 88a70bf47b
48 changed files with 925 additions and 1298 deletions

View file

@ -106,7 +106,6 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab
public final ConfigManager configManager; public final ConfigManager configManager;
public final FeaturesManager featuresManager; public final FeaturesManager featuresManager;
public final KeyAgreementManager keyAgreementManager; public final KeyAgreementManager keyAgreementManager;
public final UpdatesManager updatesManager;
// HWID ban + anti-brutforce // HWID ban + anti-brutforce
public final CertificateManager certificateManager; public final CertificateManager certificateManager;
// Server // Server
@ -162,7 +161,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
configManager = new ConfigManager(); configManager = new ConfigManager();
featuresManager = new FeaturesManager(this); featuresManager = new FeaturesManager(this);
authManager = new AuthManager(this); authManager = new AuthManager(this);
updatesManager = new UpdatesManager(this);
RestoreResponse.registerProviders(this); RestoreResponse.registerProviders(this);
config.init(ReloadType.FULL); config.init(ReloadType.FULL);
@ -179,7 +177,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
launcherBinary.init(); launcherBinary.init();
launcherEXEBinary.init(); launcherEXEBinary.init();
syncLauncherBinaries();
launcherModuleLoader = new LauncherModuleLoader(this); launcherModuleLoader = new LauncherModuleLoader(this);
if (config.components != null) { if (config.components != null) {
logger.debug("Init components"); logger.debug("Init components");
@ -339,11 +336,6 @@ public void close() throws Exception {
logger.info("LaunchServer stopped"); logger.info("LaunchServer stopped");
} }
@Deprecated
public Set<ClientProfile> getProfiles() {
return config.profileProvider.getProfiles();
}
@Deprecated @Deprecated
public void setProfiles(Set<ClientProfile> profilesList) { public void setProfiles(Set<ClientProfile> profilesList) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -370,21 +362,6 @@ public void run() {
})); }));
CommonHelper.newThread("Command Thread", true, commandHandler).start(); CommonHelper.newThread("Command Thread", true, commandHandler).start();
CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start(); CommonHelper.newThread("Socket Command Thread", true, socketCommandServer).start();
// Sync updates dir
CommonHelper.newThread("Profiles and updates sync", true, () -> {
try {
// Sync profiles dir
syncProfilesDir();
// Sync updates dir
config.updatesProvider.syncInitially();
modulesManager.invokeEvent(new LaunchServerProfilesSyncEvent(this));
} catch (IOException e) {
logger.error("Updates/Profiles not synced", e);
}
}).start();
} }
if (config.netty != null) if (config.netty != null)
rebindNettyServerSocket(); rebindNettyServerSocket();
@ -398,47 +375,6 @@ public void run() {
} }
} }
public void syncLauncherBinaries() throws IOException {
logger.info("Syncing launcher binaries");
// Syncing launcher binary
logger.info("Syncing launcher binary file");
if (!launcherBinary.sync()) logger.warn("Missing launcher binary file");
// Syncing launcher EXE binary
logger.info("Syncing launcher EXE binary file");
if (!launcherEXEBinary.sync())
logger.warn("Missing launcher EXE binary file");
}
public void syncProfilesDir() throws IOException {
logger.info("Syncing profiles dir");
config.profileProvider.sync();
if (config.netty.sendProfileUpdatesEvent) {
sendUpdateProfilesEvent();
}
}
private void sendUpdateProfilesEvent() {
if (nettyServerSocketHandler == null || nettyServerSocketHandler.nettyServer == null || nettyServerSocketHandler.nettyServer.service == null) {
return;
}
nettyServerSocketHandler.nettyServer.service.forEachActiveChannels((ch, handler) -> {
Client client = handler.getClient();
if (client == null || !client.isAuth) {
return;
}
ProfilesRequestEvent event = new ProfilesRequestEvent(config.profileProvider.getProfiles(client));
event.requestUUID = RequestEvent.eventUUID;
handler.service.sendObject(ch, event);
});
}
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
updatesManager.syncUpdatesDir(dirs);
}
public void registerObject(String name, Object object) { public void registerObject(String name, Object object) {
if (object instanceof Reconfigurable) { if (object instanceof Reconfigurable) {
reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object); reconfigurableManager.registerReconfigurable(name, (Reconfigurable) object);

View file

@ -13,7 +13,7 @@
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider; import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.auth.updates.UpdatesProvider;
@ -179,7 +179,7 @@ public static void registerAll() {
OptionalAction.registerProviders(); OptionalAction.registerProviders();
OptionalTrigger.registerProviders(); OptionalTrigger.registerProviders();
MixProvider.registerProviders(); MixProvider.registerProviders();
ProfileProvider.registerProviders(); ProfilesProvider.registerProviders();
UpdatesProvider.registerProviders(); UpdatesProvider.registerProviders();
} }

View file

@ -1,108 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
public class LocalProfileProvider extends ProfileProvider {
public String profilesDir = "profiles";
private transient volatile Map<Path, ClientProfile> profilesMap;
private transient volatile Set<ClientProfile> profilesList; // Cache
@Override
public void sync() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
Map<Path, ClientProfile> newProfiles = new HashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
Set<ClientProfile> newProfilesList = new HashSet<>(newProfiles.values());
profilesMap = newProfiles;
profilesList = newProfilesList;
}
@Override
public Set<ClientProfile> getProfiles() {
return profilesList;
}
@Override
public void addProfile(ClientProfile profile) throws IOException {
Path profilesDirPath = Path.of(profilesDir);
ClientProfile oldProfile;
Path target = null;
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
target = e.getKey();
}
}
if(target == null) {
target = profilesDirPath.resolve(profile.getTitle()+".json");
oldProfile = profilesMap.get(target);
if(oldProfile != null && !oldProfile.getUUID().equals(profile.getUUID())) {
throw new FileAlreadyExistsException(target.toString());
}
}
try (BufferedWriter writer = IOHelper.newWriter(target)) {
Launcher.gsonManager.configGson.toJson(profile, writer);
}
addProfile(target, profile);
}
@Override
public void deleteProfile(ClientProfile profile) throws IOException {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
Files.deleteIfExists(e.getKey());
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
}
private void addProfile(Path path, ClientProfile profile) {
for(var e : profilesMap.entrySet()) {
if(e.getValue().getUUID().equals(profile.getUUID())) {
profilesMap.remove(e.getKey());
profilesList.remove(e.getValue());
break;
}
}
profilesMap.put(path, profile);
profilesList.add(profile);
}
private static final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<Path, ClientProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<Path, ClientProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
// Add SIGNED profile to result list
result.put(file.toAbsolutePath(), profile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -0,0 +1,427 @@
package pro.gravit.launchserver.auth.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.Reconfigurable;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.command.SubCommand;
import pro.gravit.utils.helper.IOHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Stream;
public class LocalProfilesProvider extends ProfilesProvider implements Reconfigurable {
private final transient Logger logger = LogManager.getLogger();
public String profilesDir = "profiles";
public String cacheFile = ".updates-cache";
public String updatesDir = "updates";
public boolean cacheUpdates = true;
private transient volatile Map<String, HashedDir> updatesDirMap;
private transient volatile Map<UUID, LocalProfile> profilesMap;
@Override
public UncompletedProfile create(String name, String description, CompletedProfile reference) {
LocalProfile ref = (LocalProfile) reference;
LocalProfile profile;
if(ref != null) {
ClientProfile newClientProfile = new ClientProfileBuilder(ref.profile)
.setTitle(name)
.setInfo(description)
.setDir(name)
.createClientProfile();
profile = new LocalProfile(newClientProfile, ref.clientDir, ref.assetDir);
Path updatesDirPath = Path.of(updatesDir);
try {
IOHelper.copy(updatesDirPath.resolve(ref.profile.getDir()), updatesDirPath.resolve(profile.profile.getDir()));
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
profile = new LocalProfile(new ClientProfileBuilder()
.setUuid(UUID.randomUUID())
.setTitle(name)
.setInfo(description)
.setDir(name)
.setAssetDir("assets")
.createClientProfile(), null, getUpdatesDir("assets"));
}
profilesMap.put(profile.getUuid(), profile);
try(Writer writer = IOHelper.newWriter(profile.getConfigPath())) {
Launcher.gsonManager.configGson.toJson(profile.profile, writer);
} catch (IOException e) {
throw new RuntimeException(e);
}
return profile;
}
@Override
public void delete(UncompletedProfile profile) {
LocalProfile p = (LocalProfile) profile;
profilesMap.remove(p.getUuid());
try {
Path updatesDirPath = Path.of(updatesDir);
IOHelper.deleteDir(updatesDirPath.resolve(p.profile.getDir()), true);
Files.delete(p.getConfigPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Set<UncompletedProfile> getProfiles(Client client) {
return new HashSet<>(profilesMap.values());
}
@Override
public CompletedProfile pushUpdate(UncompletedProfile profile, String tag, ClientProfile clientProfile, List<ProfileAction> assetActions, List<ProfileAction> clientActions, List<UpdateFlag> flags) throws IOException {
Path updatesDirPath = Path.of(updatesDir);
LocalProfile localProfile = (LocalProfile) profile;
localProfile = new LocalProfile(clientProfile, localProfile.clientDir, localProfile.assetDir);
localProfile.profile = clientProfile;
if(assetActions != null && !assetActions.isEmpty()) {
Path assetDir = updatesDirPath.resolve(clientProfile.getAssetDir());
execute(localProfile.assetDir, assetDir, assetActions);
localProfile.assetDir = new HashedDir(assetDir, null, true, true);
}
if(clientActions != null && !clientActions.isEmpty()) {
Path clientDir = updatesDirPath.resolve(clientProfile.getDir());
execute(localProfile.clientDir, clientDir, clientActions);
localProfile.clientDir = new HashedDir(clientDir, null, true, true);
}
profilesMap.put(localProfile.getUuid(), localProfile);
return localProfile;
}
@Override
public void download(CompletedProfile profile, Map<String, Path> files, boolean assets) throws IOException {
Path sourceDir = Path.of(updatesDir).resolve(assets ? profile.getProfile().getAssetDir() : profile.getProfile().getDir());
for(var e : files.entrySet()) {
var source = sourceDir.resolve(e.getKey());
var target = e.getValue();
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
}
@Override
public HashedDir getUnconnectedDirectory(String name) {
return getUpdatesDir(name);
}
@Override
public CompletedProfile get(UUID uuid, String tag) {
return profilesMap.get(uuid);
}
@Override
public CompletedProfile get(String name, String tag) {
for(var p : profilesMap.values()) {
if(p.getName() != null && p.getName().equals(name)) {
return p;
}
}
return null;
}
private void writeCache(Path file) throws IOException {
try (HOutput output = new HOutput(IOHelper.newOutput(file))) {
output.writeLength(updatesDirMap.size(), 0);
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
public void readProfilesDir() throws IOException {
Path profilesDirPath = Path.of(profilesDir);
Map<UUID, LocalProfile> newProfiles = new HashMap<>();
IOHelper.walk(profilesDirPath, new ProfilesFileVisitor(newProfiles), false);
profilesMap = newProfiles;
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
public HashedDir getUpdatesDir(String updateName) {
if(updateName == null) {
return null;
}
return updatesDirMap.get(updateName);
}
@Override
public void init(LaunchServer server) {
super.init(server);
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
readUpdatesDir();
} catch (IOException e) {
logger.error("Updates not synced", e);
}
try {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
readProfilesDir();
} catch (IOException e) {
logger.error("Profiles not synced", e);
}
}
public static void execute(HashedDir dir, Path updatesDirPath, List<ProfileAction> actions) throws IOException {
for(var action : actions) {
execute(dir, updatesDirPath, action);
}
}
public static void execute(HashedDir dir, Path updatesDirPath, ProfileAction action) throws IOException {
switch (action.type()) {
case UPLOAD -> {
Path target = updatesDirPath.resolve(action.target());
if(action.source() == null) {
IOHelper.createParentDirs(target);
IOHelper.transfer(action.input().get(), target);
} else {
Path source = Path.of(action.source());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
if(action.deleteSource()) {
IOHelper.createParentDirs(target);
IOHelper.move(source, target);
} else {
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
}
}
case COPY -> {
Path source = updatesDirPath.resolve(action.source());
Path target = updatesDirPath.resolve(action.target());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
IOHelper.createParentDirs(target);
IOHelper.copy(source, target);
}
case MOVE -> {
Path source = updatesDirPath.resolve(action.source());
Path target = updatesDirPath.resolve(action.target());
if(source.toAbsolutePath().equals(target.toAbsolutePath())) {
return;
}
IOHelper.createParentDirs(target);
IOHelper.move(source, target);
}
case DELETE -> {
Path target = updatesDirPath.resolve(action.target());
if(Files.isDirectory(target)) {
IOHelper.deleteDir(target, true);
}
}
}
}
@Override
public Map<String, Command> getCommands() {
return Map.of( "sync",
new SubCommand("[]", "sync all") {
@Override
public void invoke(String... args) throws Exception {
try {
if (!IOHelper.isDir(Path.of(updatesDir)))
Files.createDirectory(Path.of(updatesDir));
readUpdatesDir();
} catch (IOException e) {
logger.error("Updates not synced", e);
}
try {
Path profilesDirPath = Path.of(profilesDir);
if (!IOHelper.isDir(profilesDirPath))
Files.createDirectory(profilesDirPath);
readProfilesDir();
} catch (IOException e) {
logger.error("Profiles not synced", e);
}
logger.info("Profiles and updates synced");
}
}
);
}
public class LocalProfile implements CompletedProfile {
private ClientProfile profile;
private HashedDir clientDir;
private HashedDir assetDir;
private Path configPath;
public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir) {
this.profile = profile;
this.clientDir = clientDir;
this.assetDir = assetDir;
this.configPath = Path.of(profilesDir).resolve(profile.getDir());
}
public LocalProfile(ClientProfile profile, HashedDir clientDir, HashedDir assetDir, Path configPath) {
this.profile = profile;
this.clientDir = clientDir;
this.assetDir = assetDir;
this.configPath = configPath;
}
@Override
public String getTag() {
return null;
}
@Override
public ClientProfile getProfile() {
return profile;
}
@Override
public HashedDir getClientDir() {
return clientDir;
}
@Override
public HashedDir getAssetDir() {
return assetDir;
}
@Override
public UUID getUuid() {
return profile.getUUID();
}
@Override
public String getName() {
return profile.getTitle();
}
@Override
public String getDescription() {
return profile.getInfo();
}
@Override
public String getDefaultTag() {
return null;
}
public Path getConfigPath() {
return configPath;
}
}
private final class ProfilesFileVisitor extends SimpleFileVisitor<Path> {
private final Map<UUID, LocalProfile> result;
private final Logger logger = LogManager.getLogger();
private ProfilesFileVisitor(Map<UUID, LocalProfile> result) {
this.result = result;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
logger.info("Syncing '{}' profile", IOHelper.getFileName(file));
// Read profile
ClientProfile profile;
try (BufferedReader reader = IOHelper.newReader(file)) {
profile = Launcher.gsonManager.gson.fromJson(reader, ClientProfile.class);
}
profile.verify();
LocalProfile localProfile = new LocalProfile(profile, getUpdatesDir(profile.getDir()), getUpdatesDir(profile.getAssetDir()), file);
result.put(localProfile.getUuid(), localProfile);
return super.visitFile(file, attrs);
}
}
}

View file

@ -1,76 +0,0 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class ProfileProvider {
public static final ProviderMap<ProfileProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfileProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract void sync() throws IOException;
public abstract Set<ClientProfile> getProfiles();
public abstract void addProfile(ClientProfile profile) throws IOException;
public abstract void deleteProfile(ClientProfile profile) throws IOException;
public void close() {
}
public ClientProfile getProfile(UUID uuid) {
for(var e : getProfiles()) {
if(e.getUUID().equals(uuid)) {
return e;
}
}
return null;
}
public ClientProfile getProfile(String title) {
for(var e : getProfiles()) {
if(e.getTitle().equals(title)) {
return e;
}
}
return null;
}
public List<ClientProfile> getProfiles(Client client) {
List<ClientProfile> profileList;
Set<ClientProfile> serverProfiles = getProfiles();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) {
profileList = new ArrayList<>(4);
for (ClientProfile profile : serverProfiles) {
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
} else {
profileList = List.copyOf(serverProfiles);
}
return profileList;
}
}

View file

@ -0,0 +1,87 @@
package pro.gravit.launchserver.auth.profiles;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.utils.ProviderMap;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
public abstract class ProfilesProvider {
public static final ProviderMap<ProfilesProvider> providers = new ProviderMap<>("ProfileProvider");
private static boolean registredProviders = false;
protected transient LaunchServer server;
public static void registerProviders() {
if (!registredProviders) {
providers.register("local", LocalProfilesProvider.class);
registredProviders = true;
}
}
public void init(LaunchServer server) {
this.server = server;
}
public abstract UncompletedProfile create(String name, String description, CompletedProfile basic);
public abstract void delete(UncompletedProfile profile);
public abstract Set<UncompletedProfile> getProfiles(Client client);
public abstract CompletedProfile pushUpdate(UncompletedProfile profile,
String tag,
ClientProfile clientProfile,
List<ProfileAction> assetActions,
List<ProfileAction> clientActions,
List<UpdateFlag> flags) throws IOException;
public abstract void download(CompletedProfile profile, Map<String, Path> files, boolean assets) throws IOException;
public abstract HashedDir getUnconnectedDirectory(String name);
public abstract CompletedProfile get(UUID uuid, String tag);
public abstract CompletedProfile get(String name, String tag);
public CompletedProfile get(UncompletedProfile profile) {
if(profile == null) {
return null;
}
return get(profile.getUuid(), null);
}
public void close() {
}
public interface UncompletedProfile {
UUID getUuid();
String getName();
String getDescription();
String getDefaultTag();
}
public interface CompletedProfile extends UncompletedProfile {
String getTag();
ClientProfile getProfile();
HashedDir getClientDir();
HashedDir getAssetDir();
}
public record ProfileAction(ProfileActionType type, String source, String target, Supplier<InputStream> input, Consumer<OutputStream> output, boolean deleteSource) {
public enum ProfileActionType {
UPLOAD, COPY, MOVE, DELETE
}
public static ProfileAction upload(Path source, String target, boolean deleteSource) {
return new ProfileAction(ProfileActionType.UPLOAD, source.toString(), target, null, null, deleteSource);
}
public static ProfileAction upload(Supplier<InputStream> input, String target) {
return new ProfileAction(ProfileActionType.UPLOAD, null, target, input, null, false);
}
}
public enum UpdateFlag {
USE_DEFAULT_ASSETS, DELETE_SOURCE_FILES
}
}

View file

@ -2,18 +2,11 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.util.*; public class StdProtectHandler extends ProtectHandler {
public class StdProtectHandler extends ProtectHandler implements ProfilesProtectHandler {
private transient final Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public Map<String, List<String>> profileWhitelist = new HashMap<>();
public List<String> allowUpdates = new ArrayList<>();
@Override @Override
public boolean allowGetAccessToken(AuthResponse.AuthContext context) { public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@ -22,38 +15,6 @@ public boolean allowGetAccessToken(AuthResponse.AuthContext context) {
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
if (profileWhitelist != null && !profileWhitelist.isEmpty()) {
logger.warn("profileWhitelist deprecated. Please use permission 'launchserver.profile.PROFILE_UUID.show' and 'launchserver.profile.PROFILE_UUID.enter'");
}
}
@Override
public boolean canGetProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.show", profile, client);
}
@Override
public boolean canChangeProfile(ClientProfile profile, Client client) {
return (client.isAuth && !profile.isLimited()) || isWhitelisted("launchserver.profile.%s.enter", profile, client);
}
@Override
public boolean canGetUpdates(String updatesDirName, Client client) {
return client.profile != null && (client.profile.getDir().equals(updatesDirName) || client.profile.getAssetDir().equals(updatesDirName) || allowUpdates.contains(updatesDirName));
}
private boolean isWhitelisted(String property, ClientProfile profile, Client client) {
if (client.permissions != null) {
String permByUUID = property.formatted(profile.getUUID());
if (client.permissions.hasPerm(permByUUID)) {
return true;
}
String permByTitle = property.formatted(profile.getTitle().toLowerCase(Locale.ROOT));
if (client.permissions.hasPerm(permByTitle)) {
return true;
}
}
List<String> allowedUsername = profileWhitelist.get(profile.getTitle());
return allowedUsername != null && allowedUsername.contains(client.username);
} }
} }

View file

@ -1,22 +0,0 @@
package pro.gravit.launchserver.auth.protect.interfaces;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.socket.Client;
public interface ProfilesProtectHandler {
default boolean canGetProfiles(Client client) {
return true;
}
default boolean canGetProfile(ClientProfile profile, Client client) {
return true;
}
default boolean canChangeProfile(ClientProfile profile, Client client) {
return client.isAuth;
}
default boolean canGetUpdates(String updatesDirName, Client client) {
return true;
}
}

View file

@ -2,226 +2,81 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.serialize.HInput;
import pro.gravit.launcher.core.serialize.HOutput;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.modules.events.LaunchServerUpdatesSyncEvent;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import java.io.*; import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.util.Arrays;
import java.util.*; import java.util.HashMap;
import java.util.stream.Stream; import java.util.Map;
public class LocalUpdatesProvider extends UpdatesProvider { public class LocalUpdatesProvider extends UpdatesProvider {
private final transient Logger logger = LogManager.getLogger(); private transient final Logger logger = LogManager.getLogger();
public String cacheFile = ".updates-cache";
public String updatesDir = "updates"; public String updatesDir = "updates";
public boolean cacheUpdates = true; public String binaryName = "Launcher";
private volatile transient Map<String, HashedDir> updatesDirMap; public Map<UpdateVariant, String> urls = new HashMap<>(Map.of(
UpdateVariant.JAR, "http://localhost:9274/Launcher.jar",
private void writeCache(Path file) throws IOException { UpdateVariant.EXE, "http://localhost:9274/Launcher.exe"
try (HOutput output = new HOutput(IOHelper.newOutput(file))) { ));
output.writeLength(updatesDirMap.size(), 0); private final transient Map<UpdateVariant, byte[]> hashMap = new HashMap<>();
for (Map.Entry<String, HashedDir> entry : updatesDirMap.entrySet()) {
output.writeString(entry.getKey(), 0);
entry.getValue().write(output);
}
}
logger.debug("Saved {} updates to cache", updatesDirMap.size());
}
private void readCache(Path file) throws IOException {
Map<String, HashedDir> updatesDirMap = new HashMap<>(16);
try (HInput input = new HInput(IOHelper.newInput(file))) {
int size = input.readLength(0);
for (int i = 0; i < size; ++i) {
String name = input.readString(0);
HashedDir dir = new HashedDir(input);
updatesDirMap.put(name, dir);
}
}
logger.debug("Found {} updates from cache", updatesDirMap.size());
this.updatesDirMap = Collections.unmodifiableMap(updatesDirMap);
}
public void readUpdatesFromCache() throws IOException {
readCache(Path.of(cacheFile));
}
public void readUpdatesDir() throws IOException {
var cacheFilePath = Path.of(cacheFile);
if (cacheUpdates) {
if (Files.exists(cacheFilePath)) {
try {
readCache(cacheFilePath);
return;
} catch (Throwable e) {
logger.error("Read updates cache failed", e);
}
}
}
sync(null);
}
@Override @Override
public void init(LaunchServer server) { public void init(LaunchServer server) {
super.init(server); super.init(server);
try { try {
if (!IOHelper.isDir(Path.of(updatesDir))) sync(UpdateVariant.JAR);
Files.createDirectory(Path.of(updatesDir)); sync(UpdateVariant.EXE);
} catch (IOException e) { } catch (IOException e) {
logger.error("Updates not synced", e); logger.error("Error when syncing binaries", e);
} }
} }
@Override @Override
public void syncInitially() throws IOException { public void pushUpdate(Map<UpdateVariant, Path> files) throws IOException {
readUpdatesDir();
}
public void sync(Collection<String> dirs) throws IOException {
logger.info("Syncing updates dir");
Map<String, HashedDir> newUpdatesDirMap = new HashMap<>(16);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(Path.of(updatesDir))) {
for (final Path updateDir : dirStream) {
if (Files.isHidden(updateDir))
continue; // Skip hidden
// Resolve name and verify is dir
String name = IOHelper.getFileName(updateDir);
if (!IOHelper.isDir(updateDir)) {
if (!IOHelper.isFile(updateDir) && Stream.of(".jar", ".exe", ".hash").noneMatch(e -> updateDir.toString().endsWith(e)))
logger.warn("Not update dir: '{}'", name);
continue;
}
// Add from previous map (it's guaranteed to be non-null)
if (dirs != null && !dirs.contains(name)) {
HashedDir hdir = updatesDirMap.get(name);
if (hdir != null) {
newUpdatesDirMap.put(name, hdir);
continue;
}
}
// Sync and sign update dir
logger.info("Syncing '{}' update dir", name);
HashedDir updateHDir = new HashedDir(updateDir, null, true, true);
newUpdatesDirMap.put(name, updateHDir);
}
}
updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap);
if (cacheUpdates) {
try {
writeCache(Path.of(cacheFile));
} catch (Throwable e) {
logger.error("Write updates cache failed", e);
}
}
server.modulesManager.invokeEvent(new LaunchServerUpdatesSyncEvent(server));
}
@Override
public HashedDir getUpdatesDir(String updateName) {
return updatesDirMap.get(updateName);
}
private Path resolveUpdateName(String updateName) {
if(updateName == null) {
return Path.of(updatesDir);
}
return Path.of(updatesDir).resolve(updateName);
}
@Override
public void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) { for(var e : files.entrySet()) {
var target = path.resolve(e.getKey()); IOHelper.copy(e.getValue(), getUpdate(e.getKey()));
var source = e.getValue(); sync(e.getKey());
IOHelper.createParentDirs(target);
if(deleteAfterUpload) {
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
} else {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
@Override
public OutputStream upload(String updateName, String file) throws IOException {
var path = resolveUpdateName(updateName);
var target = path.resolve(file);
IOHelper.createParentDirs(target);
return new FileOutputStream(target.toFile());
}
@Override
public Map<String, Path> download(String updateName, List<String> files) {
var path = resolveUpdateName(updateName);
Map<String, Path> map = new HashMap<>();
for(var e : files) {
map.put(e, path.resolve(e));
}
return map;
}
@Override
public void download(String updateName, Map<String, Path> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files.entrySet()) {
var source = path.resolve(e.getKey());
var target = e.getValue();
IOHelper.copy(source, target);
} }
} }
@Override public void sync(UpdateVariant variant) throws IOException {
public InputStream download(String updateName, String path) throws IOException { var source = getUpdate(variant);
return new FileInputStream(resolveUpdateName(updateName).resolve(path).toFile()); if(!Files.exists(source)) {
logger.warn("Dont exist {} binary", variant);
return;
}
byte[] hash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, source);
hashMap.put(variant, hash);
}
public Path getUpdate(UpdateVariant variant) {
String fileName;
switch (variant) {
case JAR -> {
fileName = binaryName.concat(".jar");
}
case EXE -> {
fileName = binaryName.concat(".exe");
}
default -> {
fileName = binaryName;
}
}
return Path.of(updatesDir).resolve(fileName);
} }
@Override @Override
public void move(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException { public UpdateInfo checkUpdates(UpdateVariant variant, byte[] digest) {
for(var e : files.entrySet()) { byte[] hash = hashMap.get(variant);
var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path()); if (hash == null) {
var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path()); return null; // We dont have this file
IOHelper.move(source, target);
} }
if(Arrays.equals(digest, hash)) {
return null; // Launcher already updated
} }
return new UpdateInfo(urls.get(variant));
@Override
public void copy(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException {
for(var e : files.entrySet()) {
var source = resolveUpdateName(e.getKey().updateName()).resolve(e.getKey().path());
var target = resolveUpdateName(e.getValue().updateName()).resolve(e.getValue().path());
IOHelper.copy(source, target);
}
}
@Override
public void delete(String updateName, List<String> files) throws IOException {
var path = resolveUpdateName(updateName);
for(var e : files) {
var target = path.resolve(e);
Files.delete(target);
}
}
@Override
public void delete(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
IOHelper.deleteDir(path, true);
}
@Override
public void create(String updateName) throws IOException {
var path = resolveUpdateName(updateName);
Files.createDirectories(path);
} }
} }

View file

@ -1,26 +1,15 @@
package pro.gravit.launchserver.auth.updates; package pro.gravit.launchserver.auth.updates;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launcher.core.hasher.HashedEntry;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.utils.ProviderMap; import pro.gravit.utils.ProviderMap;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.net.URL;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
public abstract class UpdatesProvider { public abstract class UpdatesProvider {
public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider"); public static final ProviderMap<UpdatesProvider> providers = new ProviderMap<>("UpdatesProvider");
private static boolean registredProviders = false; private static boolean registredProviders = false;
protected transient LaunchServer server; protected transient LaunchServer server;
@ -36,81 +25,17 @@ public void init(LaunchServer server) {
this.server = server; this.server = server;
} }
public void sync() throws IOException { public abstract void pushUpdate(Map<UpdateVariant, Path> files) throws IOException;
sync(null); public abstract UpdateInfo checkUpdates(UpdateVariant variant, byte[] digest);
}
public abstract void syncInitially() throws IOException;
public abstract void sync(Collection<String> updateNames) throws IOException;
public abstract HashedDir getUpdatesDir(String updateName);
public abstract void upload(String updateName, Map<String, Path> files, boolean deleteAfterUpload) throws IOException;
public abstract OutputStream upload(String updateName, String file) throws IOException;
public void upload(String updateName, Path dir, boolean deleteAfterUpload) throws IOException {
if(!Files.isDirectory(dir)) {
throw new UnsupportedEncodingException(String.format("%s is not a directory", dir));
}
Map<String, Path> map = new HashMap<>();
IOHelper.walk(dir, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
map.put(dir.relativize(file).toString(), file);
return FileVisitResult.CONTINUE;
}
}, true);
upload(updateName, map, deleteAfterUpload);
}
public abstract Map<String, Path> download(String updateName, List<String> files) throws IOException;
public abstract void download(String updateName, Map<String, Path> files) throws IOException;
public abstract InputStream download(String updateName, String path) throws IOException;
public abstract void move(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException;
public void move(String updateName, String newUpdateName) throws IOException {
create(newUpdateName);
var updatesDir = getUpdatesDir(updateName);
Map<UpdateNameAndFile, UpdateNameAndFile> map = new HashMap<>();
updatesDir.walk("/", (path, name, entry) -> {
map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path));
return HashedDir.WalkAction.CONTINUE;
});
move(map);
delete(updateName);
}
public abstract void copy(Map<UpdateNameAndFile, UpdateNameAndFile> files) throws IOException;
public void copy(String updateName, String newUpdateName) throws IOException {
create(newUpdateName);
var updatesDir = getUpdatesDir(updateName);
Map<UpdateNameAndFile, UpdateNameAndFile> map = new HashMap<>();
updatesDir.walk("/", (path, name, entry) -> {
map.put(UpdateNameAndFile.of(updateName, path), UpdateNameAndFile.of(newUpdateName, path));
return HashedDir.WalkAction.CONTINUE;
});
copy(map);
}
public abstract void delete(String updateName, List<String> files) throws IOException;
public abstract void delete(String updateName) throws IOException;
public abstract void create(String updateName) throws IOException;
public void close() { public void close() {
} }
public record UpdateNameAndFile(String updateName, String path) { public enum UpdateVariant {
public static UpdateNameAndFile of(String updateName, String path) { JAR, EXE
return new UpdateNameAndFile(updateName, path);
} }
public record UpdateInfo(String url) {
} }
} }

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import java.io.IOException; import java.io.IOException;
@ -9,14 +10,16 @@
public class EXELauncherBinary extends LauncherBinary { public class EXELauncherBinary extends LauncherBinary {
public EXELauncherBinary(LaunchServer server) { public EXELauncherBinary(LaunchServer server) {
super(server, LauncherBinary.resolve(server, ".exe"), "Launcher-%s.exe"); super(server, "Launcher-%s.exe");
}
@Override
public UpdatesProvider.UpdateVariant getVariant() {
return UpdatesProvider.UpdateVariant.EXE;
} }
@Override @Override
public void build() throws IOException { public void build() throws IOException {
if (IOHelper.isFile(syncBinaryFile)) {
Files.delete(syncBinaryFile);
}
} }
} }

View file

@ -2,6 +2,7 @@
import pro.gravit.launcher.base.Launcher; import pro.gravit.launcher.base.Launcher;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.*; import pro.gravit.launchserver.binary.tasks.*;
import java.io.IOException; import java.io.IOException;
@ -23,7 +24,7 @@ public final class JARLauncherBinary extends LauncherBinary {
public final Map<String, Path> files; public final Map<String, Path> files;
public JARLauncherBinary(LaunchServer server) throws IOException { public JARLauncherBinary(LaunchServer server) throws IOException {
super(server, resolve(server, ".jar"), "Launcher-%s.jar"); super(server, "Launcher-%s.jar");
count = new AtomicLong(0); count = new AtomicLong(0);
runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR);
buildDir = server.dir.resolve("build"); buildDir = server.dir.resolve("build");
@ -36,6 +37,11 @@ public JARLauncherBinary(LaunchServer server) throws IOException {
} }
} }
@Override
public UpdatesProvider.UpdateVariant getVariant() {
return UpdatesProvider.UpdateVariant.JAR;
}
@Override @Override
public void init() { public void init() {
tasks.add(new PrepareBuildTask(server)); tasks.add(new PrepareBuildTask(server));

View file

@ -1,6 +1,7 @@
package pro.gravit.launchserver.binary; package pro.gravit.launchserver.binary;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.binary.tasks.LauncherBuildTask; import pro.gravit.launchserver.binary.tasks.LauncherBuildTask;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper; import pro.gravit.utils.helper.SecurityHelper;
@ -12,13 +13,10 @@
public abstract class LauncherBinary extends BinaryPipeline { public abstract class LauncherBinary extends BinaryPipeline {
public final LaunchServer server; public final LaunchServer server;
public final Path syncBinaryFile;
private volatile byte[] digest;
protected LauncherBinary(LaunchServer server, Path binaryFile, String nameFormat) { protected LauncherBinary(LaunchServer server, String nameFormat) {
super(server.tmpDir.resolve("build"), nameFormat); super(server.tmpDir.resolve("build"), nameFormat);
this.server = server; this.server = server;
syncBinaryFile = binaryFile;
} }
public static Path resolve(LaunchServer server, String ext) { public static Path resolve(LaunchServer server, String ext) {
@ -40,30 +38,17 @@ public void build() throws IOException {
logger.info("Task {} processed from {} millis", task.getName(), time_task); logger.info("Task {} processed from {} millis", task.getName(), time_task);
} }
long time_end = System.currentTimeMillis(); long time_end = System.currentTimeMillis();
server.config.updatesProvider.upload(null, Map.of(syncBinaryFile.toString(), thisPath), true); if(thisPath != null) {
server.config.updatesProvider.pushUpdate(Map.of(getVariant(), thisPath));
} else {
logger.warn("Missing {} binary file", getVariant());
}
IOHelper.deleteDir(buildDir, false); IOHelper.deleteDir(buildDir, false);
logger.info("Build successful from {} millis", time_end - time_start); logger.info("Build successful from {} millis", time_end - time_start);
} }
public final boolean exists() { public abstract UpdatesProvider.UpdateVariant getVariant();
return syncBinaryFile != null && IOHelper.isFile(syncBinaryFile);
}
public final byte[] getDigest() {
return digest;
}
public void init() { public void init() {
} }
public final boolean sync() {
try {
var target = syncBinaryFile.toString();
var path = server.config.updatesProvider.download(null, List.of(target)).get(target);
digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512, IOHelper.read(path));
return true;
} catch (Throwable e) {
return false;
}
}
} }

View file

@ -44,7 +44,7 @@ protected boolean showApplyDialog(String text) throws IOException {
return response.equals("y"); return response.equals("y");
} }
protected Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception { protected static Downloader downloadWithProgressBar(String taskName, List<Downloader.SizedFile> list, String baseUrl, Path targetDir) throws Exception {
long total = 0; long total = 0;
for (Downloader.SizedFile file : list) { for (Downloader.SizedFile file : list) {
if(file.size < 0) { if(file.size < 0) {

View file

@ -21,6 +21,5 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
server.buildLauncherBinaries(); server.buildLauncherBinaries();
server.syncLauncherBinaries();
} }
} }

View file

@ -33,10 +33,8 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand
// Register sync commands // Register sync commands
BaseCommandCategory updates = new BaseCommandCategory(); BaseCommandCategory updates = new BaseCommandCategory();
updates.registerCommand("indexAsset", new IndexAssetCommand(server)); //updates.registerCommand("indexAsset", new IndexAssetCommand(server));
updates.registerCommand("unindexAsset", new UnindexAssetCommand(server)); //updates.registerCommand("unindexAsset", new UnindexAssetCommand(server));
updates.registerCommand("downloadAsset", new DownloadAssetCommand(server));
updates.registerCommand("downloadClient", new DownloadClientCommand(server));
updates.registerCommand("sync", new SyncCommand(server)); updates.registerCommand("sync", new SyncCommand(server));
updates.registerCommand("profile", new ProfilesCommand(server)); updates.registerCommand("profile", new ProfilesCommand(server));
Category updatesCategory = new Category(updates, "updates", "Update and Sync Management"); Category updatesCategory = new Category(updates, "updates", "Update and Sync Management");

View file

@ -1,137 +0,0 @@
package pro.gravit.launchserver.command.hash;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class DownloadAssetCommand extends Command {
private static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
private transient final Logger logger = LogManager.getLogger();
public DownloadAssetCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mojang/mirror)";
}
@Override
public String getUsageDescription() {
return "Download asset dir";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args.length > 1 ? args[1] : "assets");
String type = args.length > 2 ? args[2] : "mojang";
Path assetDir = server.createTempDirectory("assets");
var updatesDir = server.config.updatesProvider.getUpdatesDir(dirName);
if(updatesDir == null) {
updatesDir = new HashedDir();
server.config.updatesProvider.create(dirName);
}
// Create asset dir
if (Files.notExists(assetDir)) {
logger.info("Creating asset dir: '{}'", dirName);
Files.createDirectory(assetDir);
}
if (type.equals("mojang")) {
HttpRequester requester = new HttpRequester();
logger.info("Fetch versions from {}", MINECRAFT_VERSIONS_URL);
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null;
for (var e : versions.versions) {
if (e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
if (profileUrl == null) {
logger.error("Version {} not found", versionName);
return;
}
logger.info("Fetch profile {} from {}", versionName, profileUrl);
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
Path indexPath = assetDir.resolve("indexes").resolve(assetIndex + ".json");
logger.info("Fetch asset index {} from {}", assetIndex, assetsIndexUrl);
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
JsonObject objects = assets.get("objects").getAsJsonObject();
try (Writer writer = IOHelper.newWriter(indexPath)) {
logger.info("Save {}", indexPath);
Launcher.gsonManager.configGson.toJson(assets, writer);
}
if (!assetIndex.equals(versionName)) {
Path targetPath = assetDir.resolve("indexes").resolve(versionName + ".json");
logger.info("Copy {} into {}", indexPath, targetPath);
Files.copy(indexPath, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : objects.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
if (updatesDir.tryFindRecursive(path).isFound()) {
continue;
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
logger.info("Download {} files", toDownload.size());
Downloader downloader = downloadWithProgressBar(dirName, toDownload, RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
} else {
// Download required asset
logger.info("Downloading asset, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getAssetsURL(version.name), assetDir);
server.mirrorManager.downloadZip(assetDir, "assets/%s.zip", versionName);
}
server.config.updatesProvider.upload(dirName, assetDir, true);
// Finished
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Asset successfully downloaded: '{}'", dirName);
}
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
}

View file

@ -1,108 +0,0 @@
package pro.gravit.launchserver.command.hash;
import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import pro.gravit.utils.command.CommandException;
import pro.gravit.utils.helper.IOHelper;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.UUID;
public final class DownloadClientCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public DownloadClientCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mirror/generate)";
}
@Override
public String getUsageDescription() {
return "Download client dir";
}
@Override
public void invoke(String... args) throws IOException, CommandException {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = IOHelper.verifyFileName(args[1] != null ? args[1] : args[0]);
Path clientDir = server.createTempDirectory("client");
server.config.updatesProvider.create(dirName);
boolean isMirrorClientDownload = false;
if (args.length > 2) {
isMirrorClientDownload = args[2].equals("mirror");
}
// Download required client
logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName);
// Create profile file
logger.info("Creaing profile file: '{}'", dirName);
ClientProfile clientProfile = null;
if (isMirrorClientDownload) {
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorClientDownload = false;
}
}
if (!isMirrorClientDownload) {
try {
String internalVersion = versionName;
if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
}
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) {
isMirrorClientDownload = true;
}
}
server.config.profileProvider.addProfile(clientProfile);
server.config.updatesProvider.upload(dirName, clientDir, true);
// Finished
server.syncProfilesDir();
server.syncUpdatesDir(Collections.singleton(dirName));
logger.info("Client successfully downloaded: '{}'", dirName);
}
}

View file

@ -52,7 +52,7 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 3); /*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]); String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]); String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]); String outputAssetDirName = IOHelper.verifyFileName(args[2]);
@ -78,7 +78,7 @@ public void invoke(String... args) throws Exception {
// Finished // Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully indexed: '{}'", inputAssetDirName); logger.info("Asset successfully indexed: '{}'", inputAssetDirName);*/
} }
public static class IndexObject { public static class IndexObject {

View file

@ -37,7 +37,7 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 3); /*verifyArgs(args, 3);
String inputAssetDirName = IOHelper.verifyFileName(args[0]); String inputAssetDirName = IOHelper.verifyFileName(args[0]);
String indexFileName = IOHelper.verifyFileName(args[1]); String indexFileName = IOHelper.verifyFileName(args[1]);
String outputAssetDirName = IOHelper.verifyFileName(args[2]); String outputAssetDirName = IOHelper.verifyFileName(args[2]);
@ -73,6 +73,6 @@ public void invoke(String... args) throws Exception {
// Finished // Finished
server.syncUpdatesDir(Collections.singleton(outputAssetDirName)); server.syncUpdatesDir(Collections.singleton(outputAssetDirName));
logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString()); logger.info("Asset successfully unindexed: '{}'", outputAssetDir.toAbsolutePath().toString());*/
} }
} }

View file

@ -5,6 +5,7 @@
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder; import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
@ -34,26 +35,14 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 2); verifyArgs(args, 2);
ClientProfile profile; ProfilesProvider.CompletedProfile profile;
try { try {
UUID uuid = UUID.fromString(args[0]); UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid); profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]); profile = server.config.profilesProvider.get(args[0], null);
} }
var builder = new ClientProfileBuilder(profile); server.config.profilesProvider.create(args[1], "Description", profile);
builder.setTitle(args[1]);
builder.setUuid(UUID.randomUUID());
if(profile.getServers().size() == 1) {
profile.getServers().getFirst().name = args[1];
}
logger.info("Copy {} to {}", profile.getDir(), args[1]);
server.config.updatesProvider.copy(profile.getDir(), args[1]);
builder.setDir(args[1]);
profile = builder.createClientProfile();
server.config.profileProvider.addProfile(profile);
logger.info("Profile {} cloned from {}", args[1], args[0]); logger.info("Profile {} cloned from {}", args[1], args[0]);
server.syncProfilesDir();
server.syncUpdatesDir(List.of(args[1]));
} }
} }

View file

@ -0,0 +1,140 @@
package pro.gravit.launchserver.command.profiles;
import com.google.gson.JsonElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.base.Launcher;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launcher.base.profiles.ClientProfileVersions;
import pro.gravit.launchserver.HttpRequester;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.AssetsDirHelper;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class CreateProfileCommand extends Command {
private static final Logger logger = LogManager.getLogger();
public CreateProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[version] [dir] (mirror/generate)";
}
@Override
public String getUsageDescription() {
return "Download client";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 2);
//Version version = Version.byName(args[0]);
String versionName = args[0];
String dirName = args[1];
Path clientDir = server.createTempDirectory("client");
boolean isMirrorProfileDownload = false;
if (args.length > 2) {
isMirrorProfileDownload = args[2].equals("mirror");
}
// Download required client
logger.info("Downloading client, it may take some time");
//HttpDownloader.downloadZip(server.mirrorManager.getDefaultMirror().getClientsURL(version.name), clientDir);
server.mirrorManager.downloadZip(clientDir, "clients/%s.zip", versionName);
// Create profile file
logger.info("Creaing profile file: '{}'", dirName);
ClientProfile clientProfile = null;
try {
JsonElement clientJson = server.mirrorManager.jsonRequest(null, "GET", "clients/%s.json", versionName);
clientProfile = Launcher.gsonManager.configGson.fromJson(clientJson, ClientProfile.class);
var builder = new ClientProfileBuilder(clientProfile);
builder.setTitle(dirName);
builder.setDir(dirName);
builder.setUuid(UUID.randomUUID());
clientProfile = builder.createClientProfile();
if (clientProfile.getServers() != null) {
ClientProfile.ServerProfile serverProfile = clientProfile.getDefaultServerProfile();
if (serverProfile != null) {
serverProfile.name = dirName;
}
}
} catch (Exception e) {
logger.error("Filed download clientProfile from mirror: '{}' Generation through MakeProfileHelper", versionName);
isMirrorProfileDownload = false;
}
if (!isMirrorProfileDownload) {
try {
String internalVersion = versionName;
if (internalVersion.contains("-")) {
internalVersion = internalVersion.substring(0, versionName.indexOf('-'));
}
ClientProfile.Version version = ClientProfile.Version.of(internalVersion);
if (version.compareTo(ClientProfileVersions.MINECRAFT_1_7_10) <= 0) {
logger.warn("Minecraft 1.7.9 and below not supported. Use at your own risk");
}
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(clientDir, version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.debug("Detected option {}", option.getClass().getSimpleName());
}
clientProfile = MakeProfileHelper.makeProfile(version, dirName, options);
} catch (Throwable e) {
isMirrorProfileDownload = true;
}
}
pushClientAndDownloadAssets(server, clientProfile, clientDir);
// Finished
logger.info("Client successfully downloaded: '{}'", dirName);
}
public static ProfilesProvider.CompletedProfile pushClientAndDownloadAssets(LaunchServer server, ClientProfile clientProfile, Path clientDir) throws Exception {
var uncompleted = server.config.profilesProvider.create(clientProfile.getTitle(), "Description", null);
var completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
{
String assetIndexPath = String.format("indexes/%s.json", completed.getProfile().getAssetIndex());
if (!completed.getAssetDir().tryFindRecursive(assetIndexPath).isFound()) {
Path assetDir = server.createTempDirectory("assets");
HttpRequester requester = new HttpRequester();
var assetInfo = AssetsDirHelper.getAssetInfo(requester, completed.getProfile().getAssetIndex());
var toDownload = AssetsDirHelper.makeToDownloadFiles(assetInfo, completed.getAssetDir());
logger.info("Download assets {}", completed.getProfile().getAssetIndex());
Downloader downloader = downloadWithProgressBar(completed.getProfile().getAssetIndex(),
toDownload, AssetsDirHelper.RESOURCES_DOWNLOAD_URL, assetDir);
downloader.getFuture().get();
byte[] assetIndexBytes = Launcher.gsonManager.configGson.toJson(assetInfo.assets()).getBytes(StandardCharsets.UTF_8);
completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, List.of(
ProfilesProvider.ProfileAction.upload(() -> {
return new ByteArrayInputStream(assetIndexBytes);
}, assetIndexPath),
ProfilesProvider.ProfileAction.upload(assetDir, "", true)
), List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
} else {
completed = server.config.profilesProvider.pushUpdate(uncompleted, null, clientProfile, null, List.of(
ProfilesProvider.ProfileAction.upload(clientDir, "", true)
), List.of(ProfilesProvider.UpdateFlag.USE_DEFAULT_ASSETS));
}
}
return completed;
}
}

View file

@ -4,6 +4,7 @@
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import java.util.UUID; import java.util.UUID;
@ -27,24 +28,18 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) throws Exception { public void invoke(String... args) throws Exception {
verifyArgs(args, 1); verifyArgs(args, 1);
ClientProfile profile; ProfilesProvider.CompletedProfile profile;
try { try {
UUID uuid = UUID.fromString(args[0]); UUID uuid = UUID.fromString(args[0]);
profile = server.config.profileProvider.getProfile(uuid); profile = server.config.profilesProvider.get(uuid, null);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(args[0]); profile = server.config.profilesProvider.get(args[0], null);
} }
if(profile == null) { if(profile == null) {
logger.error("Profile {} not found", args[0]); logger.error("Profile {} not found", args[0]);
return; return;
} }
logger.warn("THIS ACTION DELETE PROFILE AND ALL FILES IN {}", profile.getDir()); logger.info("Delete {}", args[0]);
if(!showApplyDialog("Continue?")) { server.config.profilesProvider.delete(profile);
return;
}
logger.info("Delete {} ({})", profile.getTitle(), profile.getUUID());
server.config.profileProvider.deleteProfile(profile);
logger.info("Delete {}", profile.getDir());
server.config.updatesProvider.delete(profile.getDir());
} }
} }

View file

@ -23,8 +23,8 @@ public String getUsageDescription() {
@Override @Override
public void invoke(String... args) { public void invoke(String... args) {
for(var profile : server.getProfiles()) { for(var profile : server.config.profilesProvider.getProfiles(null)) {
logger.info("{} ({}) {}", profile.getTitle(), profile.getVersion().toString(), profile.isLimited() ? "limited" : ""); logger.info("{} ({})", profile.getName(), profile.getUuid());
} }
} }
} }

View file

@ -1,42 +0,0 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import pro.gravit.launchserver.helper.MakeProfileHelper;
import java.nio.file.Path;
public class MakeProfileCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public MakeProfileCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[name] [minecraft version] [dir]";
}
@Override
public String getUsageDescription() {
return "make profile for any minecraft versions";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 3);
ClientProfile.Version version = parseClientVersion(args[1]);
MakeProfileHelper.MakeProfileOption[] options = MakeProfileHelper.getMakeProfileOptionsFromDir(Path.of(args[2]), version);
for (MakeProfileHelper.MakeProfileOption option : options) {
logger.info("Detected option {}", option);
}
ClientProfile profile = MakeProfileHelper.makeProfile(version, args[0], options);
server.config.profileProvider.addProfile(profile);
logger.info("Profile {} created", args[0]);
server.syncProfilesDir();
}
}

View file

@ -6,8 +6,7 @@
public class ProfilesCommand extends Command { public class ProfilesCommand extends Command {
public ProfilesCommand(LaunchServer server) { public ProfilesCommand(LaunchServer server) {
super(server); super(server);
this.childCommands.put("make", new MakeProfileCommand(server)); this.childCommands.put("create", new CreateProfileCommand(server));
this.childCommands.put("save", new SaveProfilesCommand(server));
this.childCommands.put("clone", new CloneProfileCommand(server)); this.childCommands.put("clone", new CloneProfileCommand(server));
this.childCommands.put("list", new ListProfilesCommand(server)); this.childCommands.put("list", new ListProfilesCommand(server));
this.childCommands.put("delete", new DeleteProfileCommand(server)); this.childCommands.put("delete", new DeleteProfileCommand(server));

View file

@ -1,46 +0,0 @@
package pro.gravit.launchserver.command.profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.util.UUID;
public class SaveProfilesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SaveProfilesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[profile names...]";
}
@Override
public String getUsageDescription() {
return "load and save profile";
}
@Override
public void invoke(String... args) throws Exception {
verifyArgs(args, 1);
if (args.length > 0) {
for (String profileName : args) {
ClientProfile profile;
try {
UUID uuid = UUID.fromString(profileName);
profile = server.config.profileProvider.getProfile(uuid);
} catch (IllegalArgumentException ex) {
profile = server.config.profileProvider.getProfile(profileName);
}
server.config.profileProvider.addProfile(profile);
}
server.syncProfilesDir();
}
}
}

View file

@ -149,8 +149,8 @@ public void invoke(String... args) {
case PROD -> printCheckResult("env", "", true); case PROD -> printCheckResult("env", "", true);
} }
//Profiles //Profiles TODO: Implement
for (ClientProfile profile : server.getProfiles()) { /*for (ClientProfile profile : server.config.profilesProvider.getProfiles(null)) {
boolean bad = false; boolean bad = false;
String profileModuleName = "profiles.%s".formatted(profile.getTitle()); String profileModuleName = "profiles.%s".formatted(profile.getTitle());
for (String exc : profile.getUpdateExclusions()) { for (String exc : profile.getUpdateExclusions()) {
@ -177,7 +177,7 @@ public void invoke(String... args) {
} }
if (!bad) if (!bad)
printCheckResult(profileModuleName, "", true); printCheckResult(profileModuleName, "", true);
} }*/
//Linux permissions check //Linux permissions check
if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) { if (JVMHelper.OS_TYPE == JVMHelper.OS.LINUX) {

View file

@ -6,6 +6,7 @@
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.command.Command; import pro.gravit.launchserver.command.Command;
import pro.gravit.utils.command.SubCommand; import pro.gravit.utils.command.SubCommand;
@ -28,9 +29,9 @@ public void invoke(String... args) throws Exception {
public void invoke(String... args) { public void invoke(String... args) {
AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair(); AuthProviderPair pair = args.length > 1 ? server.config.getAuthProviderPair(args[1]) : server.config.getAuthProviderPair();
boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]); boolean publicOnly = args.length <= 2 || Boolean.parseBoolean(args[2]);
ClientProfile profile = null; ProfilesProvider.UncompletedProfile profile = null;
for (ClientProfile p : server.getProfiles()) { for (var p : server.config.profilesProvider.getProfiles(null)) {
if (p.getTitle().equals(args[0]) || p.getUUID().toString().equals(args[0])) { if (p.getName().equals(args[0]) || p.getUuid().toString().equals(args[0])) {
profile = p; profile = p;
break; break;
} }
@ -42,7 +43,7 @@ public void invoke(String... args) {
logger.error("AuthId {} not found", args[1]); logger.error("AuthId {} not found", args[1]);
return; return;
} }
String token = server.authManager.newCheckServerToken(profile != null ? profile.getUUID().toString() : args[0], pair.name, publicOnly); String token = server.authManager.newCheckServerToken(profile != null ? profile.getUuid().toString() : args[0], pair.name, publicOnly);
logger.info("Server token {} authId {}: {}", args[0], pair.name, token); logger.info("Server token {} authId {}: {}", args[0], pair.name, token);
} }
}); });

View file

@ -1,32 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncBinariesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncBinariesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync launcher binaries";
}
@Override
public void invoke(String... args) throws IOException {
server.syncLauncherBinaries();
logger.info("Binaries successfully resynced");
}
}

View file

@ -6,12 +6,7 @@
public class SyncCommand extends Command { public class SyncCommand extends Command {
public SyncCommand(LaunchServer server) { public SyncCommand(LaunchServer server) {
super(server); super(server);
this.childCommands.put("profiles", new SyncProfilesCommand(server));
this.childCommands.put("binaries", new SyncBinariesCommand(server));
this.childCommands.put("updates", new SyncUpdatesCommand(server));
this.childCommands.put("up", new SyncUPCommand(server));
this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server)); this.childCommands.put("launchermodules", new SyncLauncherModulesCommand(server));
this.childCommands.put("updatescache", new SyncUpdatesCacheCommand(server));
} }
@Override @Override

View file

@ -1,32 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncProfilesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncProfilesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync profiles dir";
}
@Override
public void invoke(String... args) throws IOException {
server.syncProfilesDir();
logger.info("Profiles successfully resynced");
}
}

View file

@ -1,35 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
public final class SyncUPCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncUPCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "Resync profiles & updates dirs";
}
@Override
public void invoke(String... args) throws IOException {
server.syncProfilesDir();
logger.info("Profiles successfully resynced");
server.syncUpdatesDir(null);
logger.info("Updates dir successfully resynced");
}
}

View file

@ -1,25 +0,0 @@
package pro.gravit.launchserver.command.sync;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
public class SyncUpdatesCacheCommand extends Command {
public SyncUpdatesCacheCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return "sync updates cache";
}
@Override
public void invoke(String... args) throws Exception {
server.updatesManager.readUpdatesFromCache();
}
}

View file

@ -1,42 +0,0 @@
package pro.gravit.launchserver.command.sync;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.command.Command;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public final class SyncUpdatesCommand extends Command {
private transient final Logger logger = LogManager.getLogger();
public SyncUpdatesCommand(LaunchServer server) {
super(server);
}
@Override
public String getArgsDescription() {
return "[subdirs...]";
}
@Override
public String getUsageDescription() {
return "Resync updates dir";
}
@Override
public void invoke(String... args) throws IOException {
Set<String> dirs = null;
if (args.length > 0) { // Hash all updates dirs
dirs = new HashSet<>(args.length);
Collections.addAll(dirs, args);
}
// Hash updates dir
server.syncUpdatesDir(dirs);
logger.info("Updates dir successfully resynced");
}
}

View file

@ -8,8 +8,8 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider; import pro.gravit.launchserver.auth.core.RejectAuthCoreProvider;
import pro.gravit.launchserver.auth.profiles.LocalProfileProvider; import pro.gravit.launchserver.auth.profiles.LocalProfilesProvider;
import pro.gravit.launchserver.auth.profiles.ProfileProvider; import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.protect.StdProtectHandler; import pro.gravit.launchserver.auth.protect.StdProtectHandler;
import pro.gravit.launchserver.auth.texture.RequestTextureProvider; import pro.gravit.launchserver.auth.texture.RequestTextureProvider;
@ -39,7 +39,7 @@ public final class LaunchServerConfig {
// Handlers & Providers // Handlers & Providers
public ProtectHandler protectHandler; public ProtectHandler protectHandler;
public Map<String, Component> components; public Map<String, Component> components;
public ProfileProvider profileProvider = new LocalProfileProvider(); public ProfilesProvider profilesProvider = new LocalProfilesProvider();
public UpdatesProvider updatesProvider = new LocalUpdatesProvider(); public UpdatesProvider updatesProvider = new LocalUpdatesProvider();
public NettyConfig netty; public NettyConfig netty;
public LauncherConf launcher; public LauncherConf launcher;
@ -85,7 +85,8 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
newConfig.components.put("authLimiter", authLimiterComponent); newConfig.components.put("authLimiter", authLimiterComponent);
ProGuardComponent proGuardComponent = new ProGuardComponent(); ProGuardComponent proGuardComponent = new ProGuardComponent();
newConfig.components.put("proguard", proGuardComponent); newConfig.components.put("proguard", proGuardComponent);
newConfig.profileProvider = new LocalProfileProvider(); newConfig.profilesProvider = new LocalProfilesProvider();
newConfig.updatesProvider = new LocalUpdatesProvider();
return newConfig; return newConfig;
} }
@ -167,9 +168,9 @@ public void init(LaunchServer.ReloadType type) {
server.registerObject("protectHandler", protectHandler); server.registerObject("protectHandler", protectHandler);
protectHandler.init(server); protectHandler.init(server);
} }
if(profileProvider != null) { if(profilesProvider != null) {
server.registerObject("profileProvider", profileProvider); server.registerObject("profileProvider", profilesProvider);
profileProvider.init(server); profilesProvider.init(server);
} }
if(updatesProvider != null) { if(updatesProvider != null) {
server.registerObject("updatesProvider", updatesProvider); server.registerObject("updatesProvider", updatesProvider);
@ -215,9 +216,9 @@ public void close(LaunchServer.ReloadType type) {
server.unregisterObject("protectHandler", protectHandler); server.unregisterObject("protectHandler", protectHandler);
protectHandler.close(); protectHandler.close();
} }
if(profileProvider != null) { if(profilesProvider != null) {
server.unregisterObject("profileProvider", profileProvider); server.unregisterObject("profilesProvider", profilesProvider);
profileProvider.close(); profilesProvider.close();
} }
if(updatesProvider != null) { if(updatesProvider != null) {
server.unregisterObject("updatesProvider", updatesProvider); server.unregisterObject("updatesProvider", updatesProvider);

View file

@ -0,0 +1,67 @@
package pro.gravit.launchserver.helper;
import com.google.gson.JsonObject;
import pro.gravit.launcher.base.Downloader;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.HttpRequester;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class AssetsDirHelper {
public static final String MINECRAFT_VERSIONS_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
public static final String RESOURCES_DOWNLOAD_URL = "https://resources.download.minecraft.net/";
public static List<Downloader.SizedFile> makeToDownloadFiles(AssetInfo assetInfo, HashedDir updatesDir) {
List<Downloader.SizedFile> toDownload = new ArrayList<>(128);
for (var e : assetInfo.assets.entrySet()) {
var value = e.getValue().getAsJsonObject();
var hash = value.get("hash").getAsString();
hash = hash.substring(0, 2) + "/" + hash;
var size = value.get("size").getAsLong();
var path = "objects/" + hash;
if (updatesDir.tryFindRecursive(path).isFound()) {
continue;
}
toDownload.add(new Downloader.SizedFile(hash, path, size));
}
return toDownload;
}
public static AssetInfo getAssetInfo(HttpRequester requester, String versionName) throws IOException {
var versions = requester.send(requester.get(MINECRAFT_VERSIONS_URL, null), MinecraftVersions.class).getOrThrow();
String profileUrl = null;
for (var e : versions.versions) {
if (e.id.equals(versionName)) {
profileUrl = e.url;
break;
}
}
var profileInfo = requester.send(requester.get(profileUrl, null), MiniVersion.class).getOrThrow();
String assetsIndexUrl = profileInfo.assetIndex.url;
String assetIndex = profileInfo.assetIndex.id;
JsonObject assets = requester.send(requester.get(assetsIndexUrl, null), JsonObject.class).getOrThrow();
return new AssetInfo(assetIndex, assets);
}
public record AssetInfo(String assetIndex, JsonObject assets) {
}
public record MiniVersionInfo(String id, String url) {
}
public record MinecraftVersions(List<MiniVersionInfo> versions) {
}
public record MinecraftAssetIndexInfo(String id, String url) {
}
public record MiniVersion(MinecraftAssetIndexInfo assetIndex) {
}
}

View file

@ -191,7 +191,7 @@ public PlayerProfile getPlayerProfile(Client client) {
playerProfile = getPlayerProfile(client.auth, user); playerProfile = getPlayerProfile(client.auth, user);
if (playerProfile != null) return playerProfile; if (playerProfile != null) return playerProfile;
if (client.auth.textureProvider != null) { if (client.auth.textureProvider != null) {
return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getTitle(), client.auth.textureProvider, new HashMap<>()); return getPlayerProfile(client.uuid, client.username, client.profile == null ? null : client.profile.getName(), client.auth.textureProvider, new HashMap<>());
} }
// Return combined profile // Return combined profile
return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>()); return new PlayerProfile(client.uuid, client.username, new HashMap<>(), new HashMap<>());

View file

@ -14,7 +14,7 @@
import pro.gravit.launchserver.auth.core.AuthCoreProvider; import pro.gravit.launchserver.auth.core.AuthCoreProvider;
import pro.gravit.launchserver.auth.mix.MixProvider; import pro.gravit.launchserver.auth.mix.MixProvider;
import pro.gravit.launchserver.auth.password.PasswordVerifier; import pro.gravit.launchserver.auth.password.PasswordVerifier;
import pro.gravit.launchserver.auth.profiles.ProfileProvider; import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.ProtectHandler;
import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.auth.texture.TextureProvider;
import pro.gravit.launchserver.auth.updates.UpdatesProvider; import pro.gravit.launchserver.auth.updates.UpdatesProvider;
@ -48,7 +48,7 @@ public void registerAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers)); builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers));
builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers)); builder.registerTypeAdapter(OptionalTrigger.class, new UniversalJsonAdapter<>(OptionalTrigger.providers));
builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers)); builder.registerTypeAdapter(MixProvider.class, new UniversalJsonAdapter<>(MixProvider.providers));
builder.registerTypeAdapter(ProfileProvider.class, new UniversalJsonAdapter<>(ProfileProvider.providers)); builder.registerTypeAdapter(ProfilesProvider.class, new UniversalJsonAdapter<>(ProfilesProvider.providers));
builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers)); builder.registerTypeAdapter(UpdatesProvider.class, new UniversalJsonAdapter<>(UpdatesProvider.providers));
modulesManager.invokeEvent(new PreGsonPhase(builder)); modulesManager.invokeEvent(new PreGsonPhase(builder));
//ClientWebSocketService.appendTypeAdapters(builder); //ClientWebSocketService.appendTypeAdapters(builder);

View file

@ -1,45 +0,0 @@
package pro.gravit.launchserver.manangers;
import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.LaunchServer;
import java.io.IOException;
import java.util.*;
public class UpdatesManager {
private final LaunchServer server;
public UpdatesManager(LaunchServer server) {
this.server = server;
}
@Deprecated
public void readUpdatesFromCache() {
}
@Deprecated
public void readUpdatesDir() {
}
@Deprecated
public void syncUpdatesDir(Collection<String> dirs) throws IOException {
server.config.updatesProvider.sync(dirs);
}
@Deprecated
public HashSet<String> getUpdatesList() {
return new HashSet<>();
}
@Deprecated
public HashedDir getUpdate(String name) {
return server.config.updatesProvider.getUpdatesDir(name);
}
@Deprecated
public void addUpdate(String name, HashedDir dir) {
throw new UnsupportedOperationException();
}
}

View file

@ -5,6 +5,7 @@
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.core.interfaces.UserHardware; import pro.gravit.launchserver.auth.core.interfaces.UserHardware;
import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
import java.util.HashMap; import java.util.HashMap;
@ -19,7 +20,7 @@ public class Client {
public String auth_id; public String auth_id;
public long timestamp; public long timestamp;
public AuthResponse.ConnectTypes type; public AuthResponse.ConnectTypes type;
public ClientProfile profile; public ProfilesProvider.CompletedProfile profile;
public boolean isAuth; public boolean isAuth;
public boolean checkSign; public boolean checkSign;
public ClientPermissions permissions; public ClientPermissions permissions;

View file

@ -3,8 +3,9 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.ProfilesRequestEvent; import pro.gravit.launcher.base.events.request.ProfilesRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile; import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launcher.base.profiles.ClientProfileBuilder;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler; import pro.gravit.launchserver.auth.profiles.ProfilesProvider;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -13,21 +14,22 @@
import java.util.Set; import java.util.Set;
public class ProfilesResponse extends SimpleResponse { public class ProfilesResponse extends SimpleResponse {
@Deprecated
public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) { public static List<ClientProfile> getListVisibleProfiles(LaunchServer server, Client client) {
List<ClientProfile> profileList; Set<ProfilesProvider.UncompletedProfile> serverProfiles = server.config.profilesProvider.getProfiles(client);
Set<ClientProfile> serverProfiles = server.getProfiles(); List<ClientProfile> profiles = new ArrayList<>();
if (server.config.protectHandler instanceof ProfilesProtectHandler protectHandler) { for(var uncompleted : serverProfiles) {
profileList = new ArrayList<>(4); if(uncompleted instanceof ProfilesProvider.CompletedProfile completed) {
for (ClientProfile profile : serverProfiles) { profiles.add(completed.getProfile());
if (protectHandler.canGetProfile(profile, client)) {
profileList.add(profile);
}
}
} else { } else {
profileList = List.copyOf(serverProfiles); profiles.add(new ClientProfileBuilder()
.setUuid(uncompleted.getUuid())
.setTitle(uncompleted.getName())
.setInfo(uncompleted.getDescription())
.createClientProfile());
} }
return profileList; }
return profiles;
} }
@Override @Override
@ -37,10 +39,6 @@ public String getType() {
@Override @Override
public void execute(ChannelHandlerContext ctx, Client client) { public void execute(ChannelHandlerContext ctx, Client client) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetProfiles(client)) { sendResult(new ProfilesRequestEvent(getListVisibleProfiles(server, client)));
sendError("Access denied");
return;
}
sendResult(new ProfilesRequestEvent(server.config.profileProvider.getProfiles(client)));
} }
} }

View file

@ -2,16 +2,15 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.SetProfileRequestEvent; import pro.gravit.launcher.base.events.request.SetProfileRequestEvent;
import pro.gravit.launcher.base.profiles.ClientProfile;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.utils.HookException; import pro.gravit.utils.HookException;
import java.util.Collection; import java.util.UUID;
public class SetProfileResponse extends SimpleResponse { public class SetProfileResponse extends SimpleResponse {
public String client; public UUID uuid;
public String tag;
@Override @Override
public String getType() { public String getType() {
@ -25,20 +24,13 @@ public void execute(ChannelHandlerContext ctx, Client client) {
} catch (HookException e) { } catch (HookException e) {
sendError(e.getMessage()); sendError(e.getMessage());
} }
Collection<ClientProfile> profiles = server.getProfiles(); var profile = server.config.profilesProvider.get(uuid, tag);
for (ClientProfile p : profiles) { if(profile == null) {
if (p.getTitle().equals(this.client)) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler &&
!profilesProtectHandler.canChangeProfile(p, client)) {
sendError("Access denied");
return;
}
client.profile = p;
sendResult(new SetProfileRequestEvent(p));
return;
}
}
sendError("Profile not found"); sendError("Profile not found");
return;
}
client.profile = profile;
sendResult(new SetProfileRequestEvent(profile.getProfile(), profile.getTag()));
} }
@Override @Override

View file

@ -9,6 +9,7 @@
import pro.gravit.launcher.base.events.request.LauncherRequestEvent; import pro.gravit.launcher.base.events.request.LauncherRequestEvent;
import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.auth.AuthProviderPair; import pro.gravit.launchserver.auth.AuthProviderPair;
import pro.gravit.launchserver.auth.updates.UpdatesProvider;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
import pro.gravit.launchserver.socket.response.auth.AuthResponse; import pro.gravit.launchserver.socket.response.auth.AuthResponse;
@ -43,28 +44,21 @@ public void execute(ChannelHandlerContext ctx, Client client) {
bytes = Base64.getDecoder().decode(hash); bytes = Base64.getDecoder().decode(hash);
else else
bytes = digest; bytes = digest;
if (launcher_type == 1) // JAR UpdatesProvider.UpdateVariant variant = UpdatesProvider.UpdateVariant.JAR;
{ if(launcher_type == 2) {
byte[] hash = server.launcherBinary.getDigest(); variant = UpdatesProvider.UpdateVariant.EXE;
if (hash == null)
service.sendObjectAndClose(ctx, new LauncherRequestEvent(true, server.config.netty.launcherURL));
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) {
client.checkSign = true;
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherURL, null, 0));
} }
} else if (launcher_type == 2) //EXE byte[] hashToCheck = bytes;
{ if(!checkSecure(secureHash, secureSalt)) {
byte[] hash = server.launcherEXEBinary.getDigest(); hashToCheck = null; // Always need update
if (hash == null) sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL)); }
if (Arrays.equals(bytes, hash) && checkSecure(secureHash, secureSalt)) { UpdatesProvider.UpdateInfo info = server.config.updatesProvider.checkUpdates(variant, hashToCheck);
client.checkSign = true; if (info != null) {
sendResult(new LauncherRequestEvent(false, server.config.netty.launcherEXEURL, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000)); sendResult(new LauncherRequestEvent(true, info.url()));
} else { } else {
sendResultAndClose(new LauncherRequestEvent(true, server.config.netty.launcherEXEURL, null, 0)); client.checkSign = true;
sendResult(new LauncherRequestEvent(false, null, createLauncherExtendedToken(), server.config.netty.security.launcherTokenExpire*1000));
} }
} else sendError("Request launcher type error");
} }
public String createLauncherExtendedToken() { public String createLauncherExtendedToken() {

View file

@ -3,7 +3,6 @@
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import pro.gravit.launcher.base.events.request.UpdateRequestEvent; import pro.gravit.launcher.base.events.request.UpdateRequestEvent;
import pro.gravit.launcher.core.hasher.HashedDir; import pro.gravit.launcher.core.hasher.HashedDir;
import pro.gravit.launchserver.auth.protect.interfaces.ProfilesProtectHandler;
import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.socket.Client; import pro.gravit.launchserver.socket.Client;
import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.SimpleResponse;
@ -19,15 +18,22 @@ public String getType() {
@Override @Override
public void execute(ChannelHandlerContext ctx, Client client) { public void execute(ChannelHandlerContext ctx, Client client) {
if (server.config.protectHandler instanceof ProfilesProtectHandler profilesProtectHandler && !profilesProtectHandler.canGetUpdates(dirName, client)) {
sendError("Access denied");
return;
}
if (dirName == null) { if (dirName == null) {
sendError("Invalid request"); sendError("Invalid request");
return; return;
} }
HashedDir dir = server.updatesManager.getUpdate(dirName); if(client.profile == null) {
sendError("Profile not setted");
return;
}
HashedDir dir = null;
if(dirName.equals(client.profile.getProfile().getDir())) {
dir = client.profile.getClientDir();
} else if(dirName.equals(client.profile.getProfile().getAssetDir())) {
dir = client.profile.getAssetDir();
} else {
dir = server.config.profilesProvider.getUnconnectedDirectory(dirName);
}
if (dir == null) { if (dir == null) {
sendError("Directory %s not found".formatted(dirName)); sendError("Directory %s not found".formatted(dirName));
return; return;

View file

@ -12,9 +12,17 @@ public class SetProfileRequestEvent extends RequestEvent {
private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541"); private static final UUID uuid = UUID.fromString("08c0de9e-4364-4152-9066-8354a3a48541");
@LauncherNetworkAPI @LauncherNetworkAPI
public final ClientProfile newProfile; public final ClientProfile newProfile;
@LauncherNetworkAPI
public final String tag;
public SetProfileRequestEvent(ClientProfile newProfile) { public SetProfileRequestEvent(ClientProfile newProfile) {
this.newProfile = newProfile; this.newProfile = newProfile;
this.tag = null;
}
public SetProfileRequestEvent(ClientProfile newProfile, String tag) {
this.newProfile = newProfile;
this.tag = tag;
} }
@Override @Override

View file

@ -6,12 +6,21 @@
import pro.gravit.launcher.base.request.Request; import pro.gravit.launcher.base.request.Request;
import pro.gravit.launcher.base.request.websockets.WebSocketRequest; import pro.gravit.launcher.base.request.websockets.WebSocketRequest;
import java.util.UUID;
public class SetProfileRequest extends Request<SetProfileRequestEvent> implements WebSocketRequest { public class SetProfileRequest extends Request<SetProfileRequestEvent> implements WebSocketRequest {
@LauncherNetworkAPI @LauncherNetworkAPI
public final String client; public final UUID uuid;
public final String tag;
public SetProfileRequest(UUID uuid, String tag) {
this.uuid = uuid;
this.tag = tag;
}
public SetProfileRequest(ClientProfile profile) { public SetProfileRequest(ClientProfile profile) {
this.client = profile.getTitle(); this.uuid = profile.getUUID();
this.tag = null;
} }
@Override @Override

@ -1 +1 @@
Subproject commit 38788df61f8efc5453fe9c05017b629648b5fe6f Subproject commit 6f699fae50f98cec19279092d9d5fac1ac451914