Update #30 - Fixed various client bugs

This commit is contained in:
lax1dude 2024-05-18 23:17:29 -07:00
parent 32fda35ace
commit c6ac781036
50 changed files with 798 additions and 446 deletions

View File

@ -153,6 +153,37 @@ The default eaglercraftXOpts values is this:
- `enableSignatureBadge:` show a badge on the title screen indicating if digital signature is valid - `enableSignatureBadge:` show a badge on the title screen indicating if digital signature is valid
- `checkRelaysForUpdates:` proprietary feature used in offline downloads - `checkRelaysForUpdates:` proprietary feature used in offline downloads
- `allowVoiceClient:` can be used to disable the voice chat feature - `allowVoiceClient:` can be used to disable the voice chat feature
- `allowFNAWSkins:` can be used to disable the high poly FNAW skins
- `localStorageNamespace:` can be used to change the prefix of the local storage keys (Default: `"_eaglercraftX"`)
- `hooks:` can be used to define JavaScript callbacks for certain events
* `localStorageSaved:` JavaScript callback to save local storage keys
* `localStorageLoaded:` JavaScript callback to load local storage keys
### Using Hooks
You may want to implement some custom logic for loading/saving certain local storage keys. The eaglercraftXOpts hooks section can be used to override the client's local storage load and save functions. Currently, local storage keys are used to save game settings, the user's profile, custom servers, and shared world relays. Worlds and resource packs do not use local storage keys because modern browsers limit local storage keys to only 5 megabytes per domain which is too small for saving entire worlds and resource packs. Worlds and resource packs are saved using [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).
window.eaglercraftXOpts = {
...
...
...
hooks: {
localStorageSaved: function(key, data) {
// 'key' is local storage key name as a string
// 'data' is base64-encoded byte array as a string
// function returns nothing
},
localStorageLoaded: function(key) {
// 'key' is local storage key name as a string
// function returns a base64-encoded byte array as a string
// function returns null if the key does not exist
}
}
}
Be aware that the client will still save the key to the browser's local storage anyway even if you define a custom save handler, and will just attempt to load the key from the browser's local storage normally if you return null, these are meant to be used like event handlers for creating backups of keys instead of completely replacing the local storage save and load functions.
On a normal client you will only ever need to handle local storage keys called `_eaglercraftX.p` (profile), `_eaglercraftX.g` (game settings), `_eaglercraftX.s` (server list), `_eaglercraftX.r` (shared world relays), feel free to just ignore any other keys. It is guaranteed that the data the client stores will always be valid base64, so it is best practice to decode it to raw binary first if possible to reduce it's size before saving it to something like a MySQL database in your backend if you are trying to implement some kind of profile syncing system for your website. The keys already have GZIP compression applied to them by default so don't bother trying to compress them yourself a second time because it won't reduce their size.
## Developing a Client ## Developing a Client

View File

@ -1 +1 @@
u29 u30

View File

@ -20,7 +20,7 @@
> DELETE 1 @ 1 : 4 > DELETE 1 @ 1 : 4
> CHANGE 1 : 55 @ 1 : 4 > CHANGE 1 : 56 @ 1 : 4
~ ~
~ import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput; ~ import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput;
@ -31,6 +31,7 @@
~ ~
~ import net.lax1dude.eaglercraft.v1_8.Display; ~ import net.lax1dude.eaglercraft.v1_8.Display;
~ import net.lax1dude.eaglercraft.v1_8.EagRuntime; ~ import net.lax1dude.eaglercraft.v1_8.EagRuntime;
~ import net.lax1dude.eaglercraft.v1_8.EagUtils;
~ import net.lax1dude.eaglercraft.v1_8.EaglerXBungeeVersion; ~ import net.lax1dude.eaglercraft.v1_8.EaglerXBungeeVersion;
~ import net.lax1dude.eaglercraft.v1_8.HString; ~ import net.lax1dude.eaglercraft.v1_8.HString;
~ import net.lax1dude.eaglercraft.v1_8.IOUtils; ~ import net.lax1dude.eaglercraft.v1_8.IOUtils;
@ -430,7 +431,21 @@
~ Util.func_181617_a((FutureTask) this.scheduledTasks.remove(0), logger); ~ Util.func_181617_a((FutureTask) this.scheduledTasks.remove(0), logger);
> DELETE 18 @ 18 : 26 > CHANGE 7 : 18 @ 7 : 8
~ if (this.timer.elapsedTicks > 1) {
~ long watchdog = System.currentTimeMillis();
~ for (int j = 0; j < this.timer.elapsedTicks; ++j) {
~ this.runTick();
~ long millis = System.currentTimeMillis();
~ if (millis - watchdog > 50l) {
~ watchdog = millis;
~ EagUtils.sleep(0l);
~ }
~ }
~ } else if (this.timer.elapsedTicks == 1) {
> DELETE 10 @ 10 : 18
> CHANGE 1 : 4 @ 1 : 5 > CHANGE 1 : 4 @ 1 : 5
@ -503,9 +518,13 @@
+ Mouse.tickCursorShape(); + Mouse.tickCursorShape();
> INSERT 5 : 6 @ 5 > INSERT 5 : 10 @ 5
+ Display.setVSync(this.gameSettings.enableVsync); + if (Display.isVSyncSupported()) {
+ Display.setVSync(this.gameSettings.enableVsync);
+ } else {
+ this.gameSettings.enableVsync = false;
+ }
> DELETE 34 @ 34 : 52 > DELETE 34 @ 34 : 52

View File

@ -30,7 +30,9 @@
> DELETE 7 @ 7 : 11 > DELETE 7 @ 7 : 11
> DELETE 3 @ 3 : 18 > CHANGE 3 : 4 @ 3 : 18
~ private static final Logger tipLogger = LogManager.getLogger("EaglercraftX");
> CHANGE 3 : 4 @ 3 : 4 > CHANGE 3 : 4 @ 3 : 4
@ -44,7 +46,14 @@
~ for (Entry entry : (Set<Entry>) map.entrySet()) { ~ for (Entry entry : (Set<Entry>) map.entrySet()) {
> INSERT 14 : 24 @ 14 > INSERT 12 : 16 @ 12
+ if (this.sndRegistry.getObject(new ResourceLocation("minecraft:sounds/music/game/calm1.ogg")) == null) {
+ tipLogger.info(
+ "Download this resource pack if you want music: https://bafybeiayojww5jfyzvlmtuk7l5ufkt7nlfto7mhwmzf2vs4bvsjd5ouiuq.ipfs.nftstorage.link/?filename=Music_For_Eaglercraft.zip");
+ }
> INSERT 2 : 12 @ 2
+ public static class SoundMap { + public static class SoundMap {
+ +

View File

@ -7,11 +7,12 @@
> DELETE 2 @ 2 : 3 > DELETE 2 @ 2 : 3
> INSERT 1 : 9 @ 1 > INSERT 1 : 10 @ 1
+ +
+ import com.google.common.collect.Maps; + import com.google.common.collect.Maps;
+ +
+ import net.lax1dude.eaglercraft.v1_8.EagRuntime;
+ import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; + import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
+ import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper; + import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper;
+ import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer; + import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer;
@ -32,12 +33,16 @@
+ private RenderPlayer eaglerRenderer; + private RenderPlayer eaglerRenderer;
> CHANGE 82 : 88 @ 82 : 83 > CHANGE 82 : 92 @ 82 : 83
~ this.skinMap.put("slim", new RenderPlayer(this, true, false)); ~ this.skinMap.put("slim", new RenderPlayer(this, true, false));
~ this.skinMap.put("zombie", new RenderPlayer(this, false, true)); ~ this.skinMap.put("zombie", new RenderPlayer(this, false, true));
~ this.eaglerRenderer = new RenderHighPoly(this, this.playerRenderer.getMainModel(), ~ if (EagRuntime.getConfiguration().isAllowFNAWSkins()) {
~ this.playerRenderer.shadowSize); ~ this.eaglerRenderer = new RenderHighPoly(this, this.playerRenderer.getMainModel(),
~ this.playerRenderer.shadowSize);
~ } else {
~ this.eaglerRenderer = this.playerRenderer;
~ }
~ this.skinMap.put("eagler", ~ this.skinMap.put("eagler",
~ Minecraft.getMinecraft().gameSettings.enableFNAWSkins ? this.eaglerRenderer : this.playerRenderer); ~ Minecraft.getMinecraft().gameSettings.enableFNAWSkins ? this.eaglerRenderer : this.playerRenderer);

View File

@ -225,7 +225,7 @@
~ worldIn.theItemInWorldManager.initializeGameType(parWorld.getWorldInfo().getGameType()); ~ worldIn.theItemInWorldManager.initializeGameType(parWorld.getWorldInfo().getGameType());
~ } else { ~ } else {
~ parEntityPlayerMP2.theItemInWorldManager.setGameType(lanGamemode); ~ worldIn.theItemInWorldManager.setGameType(lanGamemode);
~ } ~ }
> CHANGE 7 : 8 @ 7 : 8 > CHANGE 7 : 8 @ 7 : 8

View File

@ -64,8 +64,12 @@ public class PlatformApplication {
String str = glfwGetClipboardString(win); String str = glfwGetClipboardString(win);
return str == null ? "" : str; return str == null ? "" : str;
} }
public static void setLocalStorage(String name, byte[] data) { public static void setLocalStorage(String name, byte[] data) {
setLocalStorage(name, data, true);
}
public static void setLocalStorage(String name, byte[] data, boolean hooks) {
if(data == null) { if(data == null) {
(new File("_eagstorage."+name+".dat")).delete(); (new File("_eagstorage."+name+".dat")).delete();
}else { }else {
@ -76,8 +80,12 @@ public class PlatformApplication {
} }
} }
} }
public static byte[] getLocalStorage(String data) { public static byte[] getLocalStorage(String data) {
return getLocalStorage(data, true);
}
public static byte[] getLocalStorage(String data, boolean hooks) {
File f = new File("_eagstorage."+data+".dat"); File f = new File("_eagstorage."+data+".dat");
if(!f.isFile()) { if(!f.isFile()) {
return null; return null;

View File

@ -230,6 +230,10 @@ public class PlatformInput {
glfwSwapBuffers(win); glfwSwapBuffers(win);
} }
public static boolean isVSyncSupported() {
return true;
}
public static boolean wasResized() { public static boolean wasResized() {
boolean b = windowResized; boolean b = windowResized;
windowResized = false; windowResized = false;

View File

@ -8,6 +8,7 @@ import org.json.JSONObject;
import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom; import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion; import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter; import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter;
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapterHooks;
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry; import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry;
/** /**
@ -31,6 +32,8 @@ public class DesktopClientConfigAdapter implements IClientConfigAdapter {
public final List<DefaultServer> defaultServers = new ArrayList(); public final List<DefaultServer> defaultServers = new ArrayList();
private final DesktopClientConfigAdapterHooks hooks = new DesktopClientConfigAdapterHooks();
@Override @Override
public String getDefaultLocale() { public String getDefaultLocale() {
return "en_US"; return "en_US";
@ -129,4 +132,32 @@ public class DesktopClientConfigAdapter implements IClientConfigAdapter {
return false; return false;
} }
@Override
public boolean isAllowFNAWSkins() {
return true;
}
@Override
public String getLocalStorageNamespace() {
return EaglercraftVersion.localStorageNamespace;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
}
private static class DesktopClientConfigAdapterHooks implements IClientConfigAdapterHooks {
@Override
public void callLocalStorageSavedHook(String key, String base64) {
}
@Override
public String callLocalStorageLoadHook(String key) {
return null;
}
}
} }

View File

@ -49,6 +49,10 @@ public class Display {
PlatformInput.setVSync(enable); PlatformInput.setVSync(enable);
} }
public static boolean isVSyncSupported() {
return PlatformInput.isVSyncSupported();
}
public static void update() { public static void update() {
PlatformInput.update(); PlatformInput.update();
} }

View File

@ -10,7 +10,7 @@ public class EaglercraftVersion {
/// Customize these to fit your fork: /// Customize these to fit your fork:
public static final String projectForkName = "EaglercraftX"; public static final String projectForkName = "EaglercraftX";
public static final String projectForkVersion = "u29"; public static final String projectForkVersion = "u30";
public static final String projectForkVendor = "lax1dude"; public static final String projectForkVendor = "lax1dude";
public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8";
@ -20,7 +20,7 @@ public class EaglercraftVersion {
public static final String projectOriginName = "EaglercraftX"; public static final String projectOriginName = "EaglercraftX";
public static final String projectOriginAuthor = "lax1dude"; public static final String projectOriginAuthor = "lax1dude";
public static final String projectOriginRevision = "1.8"; public static final String projectOriginRevision = "1.8";
public static final String projectOriginVersion = "u29"; public static final String projectOriginVersion = "u30";
public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace
@ -31,7 +31,7 @@ public class EaglercraftVersion {
public static final boolean enableUpdateService = true; public static final boolean enableUpdateService = true;
public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client"; public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client";
public static final int updateBundlePackageVersionInt = 29; public static final int updateBundlePackageVersionInt = 30;
public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName; public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName;
@ -60,4 +60,6 @@ public class EaglercraftVersion {
public static final boolean forceDemoMode = false; public static final boolean forceDemoMode = false;
public static final String localStorageNamespace = "_eaglercraftX";
} }

View File

@ -69,4 +69,11 @@ public interface IClientConfigAdapter {
boolean isEnableSignatureBadge(); boolean isEnableSignatureBadge();
boolean isAllowVoiceClient(); boolean isAllowVoiceClient();
boolean isAllowFNAWSkins();
String getLocalStorageNamespace();
IClientConfigAdapterHooks getHooks();
} }

View File

@ -1,8 +1,4 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm.opts; package net.lax1dude.eaglercraft.v1_8.internal;
import org.teavm.jso.JSIndexer;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
/** /**
* Copyright (c) 2024 lax1dude. All Rights Reserved. * Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -19,12 +15,10 @@ import org.teavm.jso.JSProperty;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public interface JSEaglercraftXOptsRelaysArray extends JSObject { public interface IClientConfigAdapterHooks {
@JSIndexer void callLocalStorageSavedHook(String key, String base64);
JSEaglercraftXOptsRelay get(int idx);
@JSProperty String callLocalStorageLoadHook(String key);
int getLength();
} }

View File

@ -13,6 +13,21 @@ import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
/**
* Copyright (c) 2023 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class BlockVertexIDs implements IResourceManagerReloadListener { public class BlockVertexIDs implements IResourceManagerReloadListener {
private static final Logger logger = LogManager.getLogger("BlockVertexIDsCSV"); private static final Logger logger = LogManager.getLogger("BlockVertexIDsCSV");

View File

@ -61,7 +61,7 @@ public class EaglerSkinTexture implements ITextureObject {
} }
System.arraycopy(pixels, 0, this.pixels, 0, pixels.length); System.arraycopy(pixels, 0, this.pixels, 0, pixels.length);
if(textureId != -1) { if(textureId != -1) {
TextureUtil.uploadTextureImageAllocate(textureId, new ImageData(width, height, pixels, true), false, false); TextureUtil.uploadTextureImageSub(textureId, new ImageData(width, height, pixels, true), 0, 0, false, false);
} }
} }

View File

@ -73,14 +73,20 @@ public class GuiScreenEditProfile extends GuiScreen {
} }
private void updateOptions() { private void updateOptions() {
DefaultSkins[] arr = DefaultSkins.defaultSkinsMap;
if(!EagRuntime.getConfiguration().isAllowFNAWSkins()) {
DefaultSkins[] arrNoFNAW = new DefaultSkins[arr.length - 5];
System.arraycopy(arr, 0, arrNoFNAW, 0, arrNoFNAW.length);
arr = arrNoFNAW;
}
int numCustom = EaglerProfile.customSkins.size(); int numCustom = EaglerProfile.customSkins.size();
String[] n = new String[numCustom + DefaultSkins.defaultSkinsMap.length]; String[] n = new String[numCustom + arr.length];
for(int i = 0; i < numCustom; ++i) { for(int i = 0; i < numCustom; ++i) {
n[i] = EaglerProfile.customSkins.get(i).name; n[i] = EaglerProfile.customSkins.get(i).name;
} }
int numDefault = DefaultSkins.defaultSkinsMap.length; int numDefault = arr.length;
for(int j = 0; j < numDefault; ++j) { for(int j = 0; j < numDefault; ++j) {
n[numCustom + j] = DefaultSkins.defaultSkinsMap[j].name; n[numCustom + j] = arr[j].name;
} }
dropDownOptions = n; dropDownOptions = n;
} }
@ -106,6 +112,10 @@ public class GuiScreenEditProfile extends GuiScreen {
GlStateManager.translate(skinX + 2, skinY - 9, 0.0f); GlStateManager.translate(skinX + 2, skinY - 9, 0.0f);
GlStateManager.scale(0.75f, 0.75f, 0.75f); GlStateManager.scale(0.75f, 0.75f, 0.75f);
if(selectedSlot > dropDownOptions.length - 1) {
selectedSlot = 0;
}
int numberOfCustomSkins = EaglerProfile.customSkins.size(); int numberOfCustomSkins = EaglerProfile.customSkins.size();
int skid = selectedSlot - numberOfCustomSkins; int skid = selectedSlot - numberOfCustomSkins;
SkinModel selectedSkinModel = skid < 0 ? EaglerProfile.customSkins.get(selectedSlot).model : DefaultSkins.getSkinFromId(skid).model; SkinModel selectedSkinModel = skid < 0 ? EaglerProfile.customSkins.get(selectedSlot).model : DefaultSkins.getSkinFromId(skid).model;

View File

@ -56,11 +56,15 @@ public class GuiScreenImportExportProfile extends GuiScreen {
FileChooserResult result = EagRuntime.getFileChooserResult(); FileChooserResult result = EagRuntime.getFileChooserResult();
if(result != null) { if(result != null) {
mc.loadingScreen.eaglerShow(I18n.format("settingsBackup.importing.1"), "settingsBackup.importing.2"); mc.loadingScreen.eaglerShow(I18n.format("settingsBackup.importing.1"), "settingsBackup.importing.2");
ProfileImporter importer = new ProfileImporter(result.fileData);
try { try {
ProfileImporter importer = new ProfileImporter(result.fileData);
importer.readHeader(); importer.readHeader();
mc.displayGuiScreen(new GuiScreenImportProfile(importer, back)); mc.displayGuiScreen(new GuiScreenImportProfile(importer, back));
}catch(IOException ex) { }catch(IOException ex) {
try {
importer.close();
} catch (IOException e) {
}
EagRuntime.debugPrintStackTrace(ex); EagRuntime.debugPrintStackTrace(ex);
mc.displayGuiScreen(new GuiScreenGenericErrorMessage("settingsBackup.importing.failed.1", "settingsBackup.importing.failed.2", back)); mc.displayGuiScreen(new GuiScreenGenericErrorMessage("settingsBackup.importing.failed.1", "settingsBackup.importing.failed.2", back));
} }

View File

@ -62,6 +62,14 @@ public class GuiScreenImportProfile extends GuiScreen {
this.buttonList.add(new GuiButton(1, this.width / 2 - 100, this.height / 4 + 140, I18n.format("gui.cancel"))); this.buttonList.add(new GuiButton(1, this.width / 2 - 100, this.height / 4 + 140, I18n.format("gui.cancel")));
} }
@Override
public void onGuiClosed() {
try {
importer.close();
} catch (IOException e) {
}
}
protected void actionPerformed(GuiButton par1GuiButton) { protected void actionPerformed(GuiButton par1GuiButton) {
if(par1GuiButton.id == 0) { if(par1GuiButton.id == 0) {
if(!doImportProfile && !doImportSettings && !doImportServers && !doImportResourcePacks) { if(!doImportProfile && !doImportSettings && !doImportServers && !doImportResourcePacks) {

View File

@ -69,98 +69,97 @@ public class ProfileExporter {
osb.write(new byte[]{(byte)255,(byte)255,(byte)255,(byte)255}); // this will be replaced with the file count osb.write(new byte[]{(byte)255,(byte)255,(byte)255,(byte)255}); // this will be replaced with the file count
osb.write('G'); osb.write('G');
OutputStream os = EaglerZLIB.newGZIPOutputStream(osb);
os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
os.write(new byte[]{(byte)9,(byte)102,(byte)105,(byte)108,(byte)101,(byte)45,(byte)116,(byte)121,
(byte)112,(byte)101}); // 9 + file-type
os.write(new byte[]{(byte)0,(byte)0,(byte)0,(byte)14,(byte)101,(byte)112,(byte)107,(byte)47,(byte)112,(byte)114,(byte)111,
(byte)102,(byte)105,(byte)108,(byte)101,(byte)49,(byte)56,(byte)56}); // 14 + epk/profile188
os.write('>');
os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
os.write(new byte[]{(byte)12,(byte)102,(byte)105,(byte)108,(byte)101,(byte)45,(byte)101,(byte)120,
(byte)112,(byte)111,(byte)114,(byte)116,(byte)115,(byte)0,(byte)0,(byte)0,(byte)1}); // 12 + file-exports + 1
os.write((doExportProfile ? 1 : 0) | (doExportSettings ? 2 : 0) | (doExportServers ? 4 : 0) | (doExportResourcePacks ? 8 : 0));
os.write('>');
int fileCount = 2; int fileCount = 2;
try(OutputStream os = EaglerZLIB.newGZIPOutputStream(osb)) {
if(doExportProfile) { os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
byte[] profileData = EaglerProfile.write(); os.write(new byte[]{(byte)9,(byte)102,(byte)105,(byte)108,(byte)101,(byte)45,(byte)116,(byte)121,
if(profileData == null) { (byte)112,(byte)101}); // 9 + file-type
throw new IOException("Could not write profile data!"); os.write(new byte[]{(byte)0,(byte)0,(byte)0,(byte)14,(byte)101,(byte)112,(byte)107,(byte)47,(byte)112,(byte)114,(byte)111,
} (byte)102,(byte)105,(byte)108,(byte)101,(byte)49,(byte)56,(byte)56}); // 14 + epk/profile188
exportFileToEPK("_eaglercraftX.p", profileData, os); os.write('>');
++fileCount;
} os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
os.write(new byte[]{(byte)12,(byte)102,(byte)105,(byte)108,(byte)101,(byte)45,(byte)101,(byte)120,
if(doExportSettings) { (byte)112,(byte)111,(byte)114,(byte)116,(byte)115,(byte)0,(byte)0,(byte)0,(byte)1}); // 12 + file-exports + 1
logger.info("Exporting game settings..."); os.write((doExportProfile ? 1 : 0) | (doExportSettings ? 2 : 0) | (doExportServers ? 4 : 0) | (doExportResourcePacks ? 8 : 0));
byte[] gameSettings = Minecraft.getMinecraft().gameSettings.writeOptions(); os.write('>');
if(gameSettings == null) {
throw new IOException("Could not write game settings!");
} if(doExportProfile) {
exportFileToEPK("_eaglercraftX.g", gameSettings, os); byte[] profileData = EaglerProfile.write();
++fileCount; if(profileData == null) {
logger.info("Exporting relay settings..."); throw new IOException("Could not write profile data!");
byte[] relays = RelayManager.relayManager.write(); }
if(relays == null) { exportFileToEPK("_eaglercraftX.p", profileData, os);
throw new IOException("Could not write relay settings!");
}
exportFileToEPK("_eaglercraftX.r", relays, os);
++fileCount;
}
if(doExportServers) {
logger.info("Exporting server list...");
byte[] servers = ServerList.getServerList().writeServerList();
if(servers == null) {
throw new IOException("Could not write server list!");
}
exportFileToEPK("_eaglercraftX.s", servers, os);
++fileCount;
}
logger.info("Exporting certificates...");
UpdateCertificate cert = UpdateService.getClientCertificate();
if(cert != null) {
exportFileToEPK("certs/main.cert", cert.rawCertData, os);
++fileCount;
}
Collection<UpdateCertificate> updatesExport = UpdateService.getAvailableUpdates();
int cc = 0;
for(UpdateCertificate cert2 : updatesExport) {
exportFileToEPK("certs/c" + (cc++) + ".cert", cert2.rawCertData, os);
++fileCount;
}
if(doExportResourcePacks) {
logger.info("Exporting resource packs...");
byte[] packManifest = (new VFile2(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json")).getAllBytes();
if(packManifest != null) {
exportFileToEPK(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json", packManifest, os);
++fileCount; ++fileCount;
VFile2 baseDir = new VFile2(EaglerFolderResourcePack.RESOURCE_PACKS); }
List<VFile2> files = baseDir.listFiles(true);
logger.info("({} files to export)", files.size()); if(doExportSettings) {
for(int i = 0, l = files.size(); i < l; ++i) { logger.info("Exporting game settings...");
VFile2 f = files.get(i); byte[] gameSettings = Minecraft.getMinecraft().gameSettings.writeOptions();
if(f.getPath().equals(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json")) { if(gameSettings == null) {
continue; throw new IOException("Could not write game settings!");
} }
exportFileToEPK(f.getPath(), f.getAllBytes(), os); exportFileToEPK("_eaglercraftX.g", gameSettings, os);
++fileCount;
logger.info("Exporting relay settings...");
byte[] relays = RelayManager.relayManager.write();
if(relays == null) {
throw new IOException("Could not write relay settings!");
}
exportFileToEPK("_eaglercraftX.r", relays, os);
++fileCount;
}
if(doExportServers) {
logger.info("Exporting server list...");
byte[] servers = ServerList.getServerList().writeServerList();
if(servers == null) {
throw new IOException("Could not write server list!");
}
exportFileToEPK("_eaglercraftX.s", servers, os);
++fileCount;
}
logger.info("Exporting certificates...");
UpdateCertificate cert = UpdateService.getClientCertificate();
if(cert != null) {
exportFileToEPK("certs/main.cert", cert.rawCertData, os);
++fileCount;
}
Collection<UpdateCertificate> updatesExport = UpdateService.getAvailableUpdates();
int cc = 0;
for(UpdateCertificate cert2 : updatesExport) {
exportFileToEPK("certs/c" + (cc++) + ".cert", cert2.rawCertData, os);
++fileCount;
}
if(doExportResourcePacks) {
logger.info("Exporting resource packs...");
byte[] packManifest = (new VFile2(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json")).getAllBytes();
if(packManifest != null) {
exportFileToEPK(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json", packManifest, os);
++fileCount; ++fileCount;
if(i > 0 && i % 100 == 0) { VFile2 baseDir = new VFile2(EaglerFolderResourcePack.RESOURCE_PACKS);
logger.info("Exported {} files", i); List<VFile2> files = baseDir.listFiles(true);
logger.info("({} files to export)", files.size());
for(int i = 0, l = files.size(); i < l; ++i) {
VFile2 f = files.get(i);
if(f.getPath().equals(EaglerFolderResourcePack.RESOURCE_PACKS + "/manifest.json")) {
continue;
}
exportFileToEPK(f.getPath(), f.getAllBytes(), os);
++fileCount;
if(i > 0 && i % 100 == 0) {
logger.info("Exported {} files", i);
}
} }
} }
} }
os.write(new byte[]{(byte)69,(byte)78,(byte)68,(byte)36}); // END$
} }
os.write(new byte[]{(byte)69,(byte)78,(byte)68,(byte)36}); // END$
os.close();
osb.write(new byte[]{(byte)58,(byte)58,(byte)58,(byte)89,(byte)69,(byte)69,(byte)58,(byte)62}); // :::YEE:> osb.write(new byte[]{(byte)58,(byte)58,(byte)58,(byte)89,(byte)69,(byte)69,(byte)58,(byte)62}); // :::YEE:>
byte[] ret = osb.toByteArray(); byte[] ret = osb.toByteArray();

View File

@ -1,5 +1,6 @@
package net.lax1dude.eaglercraft.v1_8.profile; package net.lax1dude.eaglercraft.v1_8.profile;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import net.lax1dude.eaglercraft.v1_8.EagRuntime; import net.lax1dude.eaglercraft.v1_8.EagRuntime;
@ -29,7 +30,7 @@ import net.minecraft.client.multiplayer.ServerList;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public class ProfileImporter { public class ProfileImporter implements Closeable {
private static final Logger logger = LogManager.getLogger("ProfileImporter"); private static final Logger logger = LogManager.getLogger("ProfileImporter");
@ -148,4 +149,9 @@ public class ProfileImporter {
} }
logger.info("Import complete!"); logger.info("Import complete!");
} }
@Override
public void close() throws IOException {
epkDecompiler.close();
}
} }

View File

@ -468,4 +468,13 @@ public class RenderHighPoly extends RenderPlayer {
GlStateManager.popMatrix(); GlStateManager.popMatrix();
} }
} }
public void renderLivingAt(AbstractClientPlayer abstractclientplayer, double d0, double d1, double d2) {
if (abstractclientplayer.isEntityAlive() && abstractclientplayer.isPlayerSleeping()) {
super.renderLivingAt(abstractclientplayer, d0 - (double) abstractclientplayer.renderOffsetX,
d1 - (double) abstractclientplayer.renderOffsetY, d2 - (double) abstractclientplayer.renderOffsetZ);
} else {
super.renderLivingAt(abstractclientplayer, d0, d1, d2);
}
}
} }

View File

@ -46,7 +46,7 @@ public enum SkinModel {
private SkinModel(int id, HighPolySkin highPoly) { private SkinModel(int id, HighPolySkin highPoly) {
this.id = id; this.id = id;
this.width = 256; this.width = 256;
this.height = 128; this.height = 256;
this.profileSkinType = "eagler"; this.profileSkinType = "eagler";
this.sanitize = true; this.sanitize = true;
this.highPoly = highPoly; this.highPoly = highPoly;

View File

@ -156,7 +156,6 @@ public class GuiShareToLan extends GuiScreen {
this.mc.ingameGUI.getChatGUI().printChatMessage(new ChatComponentText(I18n.format("lanServer.opened") this.mc.ingameGUI.getChatGUI().printChatMessage(new ChatComponentText(I18n.format("lanServer.opened")
.replace("$relay$", LANServerController.getCurrentURI()).replace("$code$", code))); .replace("$relay$", LANServerController.getCurrentURI()).replace("$code$", code)));
} else { } else {
SingleplayerServerController.configureLAN(mc.theWorld.getWorldInfo().getGameType(), false);
this.mc.displayGuiScreen(new GuiScreenNoRelays(this, "noRelay.titleFail")); this.mc.displayGuiScreen(new GuiScreenNoRelays(this, "noRelay.titleFail"));
} }
} }

View File

@ -340,9 +340,11 @@ public class LANClientNetworkManager extends EaglercraftNetworkManager {
} }
EaglerInputStream bi = new EaglerInputStream(fullData); EaglerInputStream bi = new EaglerInputStream(fullData);
int i = (bi.read() << 24) | (bi.read() << 16) | (bi.read() << 8) | bi.read(); int i = (bi.read() << 24) | (bi.read() << 16) | (bi.read() << 8) | bi.read();
InputStream inflaterInputStream = EaglerZLIB.newInflaterInputStream(bi);
fullData = new byte[i]; fullData = new byte[i];
int r = IOUtils.readFully(inflaterInputStream, fullData); int r;
try(InputStream inflaterInputStream = EaglerZLIB.newInflaterInputStream(bi)) {
r = IOUtils.readFully(inflaterInputStream, fullData);
}
if (i != r) { if (i != r) {
logger.warn("Decompressed packet expected size {} differs from actual size {}!", i, r); logger.warn("Decompressed packet expected size {} differs from actual size {}!", i, r);
} }

View File

@ -479,7 +479,7 @@ public class EaglerIntegratedServerWorker {
while(true) { while(true) {
mainLoop(); mainLoop();
EagUtils.sleep(1l); EagUtils.sleep(0l);
} }
}catch(Throwable tt) { }catch(Throwable tt) {
if(tt instanceof ReportedException) { if(tt instanceof ReportedException) {

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.sp.server.export; package net.lax1dude.eaglercraft.v1_8.sp.server.export;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
@ -24,7 +25,7 @@ import net.lax1dude.eaglercraft.v1_8.IOUtils;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public class EPKDecompiler { public class EPKDecompiler implements Closeable {
public static class FileEntry { public static class FileEntry {
public final String type; public final String type;
@ -178,4 +179,9 @@ public class EPKDecompiler {
return new String(charIn); return new String(charIn);
} }
@Override
public void close() throws IOException {
zis.close();
}
} }

View File

@ -40,44 +40,45 @@ public class WorldConverterEPK {
folderName += "_"; folderName += "_";
worldDir = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory(); worldDir = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory();
} }
EPKDecompiler dc = new EPKDecompiler(archiveContents); try(EPKDecompiler dc = new EPKDecompiler(archiveContents)) {
EPKDecompiler.FileEntry f = null; EPKDecompiler.FileEntry f = null;
int lastProgUpdate = 0; int lastProgUpdate = 0;
int prog = 0; int prog = 0;
String hasReadType = null; String hasReadType = null;
boolean has152Format = false; boolean has152Format = false;
int cnt = 0; int cnt = 0;
while((f = dc.readFile()) != null) { while((f = dc.readFile()) != null) {
byte[] b = f.data; byte[] b = f.data;
if(hasReadType == null) { if(hasReadType == null) {
if (f.type.equals("HEAD") && f.name.equals("file-type") if (f.type.equals("HEAD") && f.name.equals("file-type")
&& ((hasReadType = EPKDecompiler.readASCII(f.data)).equals("epk/world188") && ((hasReadType = EPKDecompiler.readASCII(f.data)).equals("epk/world188")
|| (has152Format = hasReadType.equals("epk/world152")))) { || (has152Format = hasReadType.equals("epk/world152")))) {
if(has152Format) { if(has152Format) {
logger.warn("World type detected as 1.5.2, it will be converted to 1.8.8 format"); logger.warn("World type detected as 1.5.2, it will be converted to 1.8.8 format");
}
continue;
}else {
throw new IOException("file does not contain a singleplayer 1.5.2 or 1.8.8 world!");
} }
continue;
}else {
throw new IOException("file does not contain a singleplayer 1.5.2 or 1.8.8 world!");
} }
} if(f.type.equals("FILE")) {
if(f.type.equals("FILE")) { if(f.name.equals("level.dat") || f.name.equals("level.dat_old")) {
if(f.name.equals("level.dat") || f.name.equals("level.dat_old")) { NBTTagCompound worldDatNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b));
NBTTagCompound worldDatNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b)); worldDatNBT.getCompoundTag("Data").setString("LevelName", newName);
worldDatNBT.getCompoundTag("Data").setString("LevelName", newName); worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis());
worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis()); EaglerOutputStream tmp = new EaglerOutputStream();
EaglerOutputStream tmp = new EaglerOutputStream(); CompressedStreamTools.writeCompressed(worldDatNBT, tmp);
CompressedStreamTools.writeCompressed(worldDatNBT, tmp); b = tmp.toByteArray();
b = tmp.toByteArray(); }
} VFile2 ff = new VFile2(worldDir, f.name);
VFile2 ff = new VFile2(worldDir, f.name); ff.setAllBytes(b);
ff.setAllBytes(b); prog += b.length;
prog += b.length; ++cnt;
++cnt; if(prog - lastProgUpdate > 25000) {
if(prog - lastProgUpdate > 25000) { lastProgUpdate = prog;
lastProgUpdate = prog; logger.info("Extracted {} files, {} bytes from EPK...", cnt, prog);
logger.info("Extracted {} files, {} bytes from EPK...", cnt, prog); EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.1", prog);
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.1", prog); }
} }
} }
} }

View File

@ -50,111 +50,113 @@ public class WorldConverterMCA {
folderName += "_"; folderName += "_";
worldDir = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory(); worldDir = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory();
} }
ZipInputStream zis = new ZipInputStream(new EaglerInputStream(archiveContents));
ZipEntry folderNameFile = null;
List<char[]> fileNames = new ArrayList<>(); List<char[]> fileNames = new ArrayList<>();
while((folderNameFile = zis.getNextEntry()) != null) { try(ZipInputStream zis = new ZipInputStream(new EaglerInputStream(archiveContents))) {
if (folderNameFile.getName().contains("__MACOSX/")) continue; ZipEntry folderNameFile = null;
if (folderNameFile.isDirectory()) continue; while((folderNameFile = zis.getNextEntry()) != null) {
String lowerName = folderNameFile.getName().toLowerCase(); if (folderNameFile.getName().contains("__MACOSX/")) continue;
if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr"))) continue; if (folderNameFile.isDirectory()) continue;
fileNames.add(folderNameFile.getName().toCharArray()); String lowerName = folderNameFile.getName().toLowerCase();
if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr"))) continue;
fileNames.add(folderNameFile.getName().toCharArray());
}
} }
final int[] i = new int[] { 0 }; final int[] i = new int[] { 0 };
while(fileNames.get(0).length > i[0] && fileNames.stream().allMatch(w -> w[i[0]] == fileNames.get(0)[i[0]])) i[0]++; while(fileNames.get(0).length > i[0] && fileNames.stream().allMatch(w -> w[i[0]] == fileNames.get(0)[i[0]])) i[0]++;
int folderPrefixOffset = i[0]; int folderPrefixOffset = i[0];
zis = new ZipInputStream(new EaglerInputStream(archiveContents)); try(ZipInputStream zis = new ZipInputStream(new EaglerInputStream(archiveContents))) {
ZipEntry f = null; ZipEntry f = null;
int lastProgUpdate = 0; int lastProgUpdate = 0;
int prog = 0; int prog = 0;
byte[] bb = new byte[16384]; byte[] bb = new byte[16384];
while ((f = zis.getNextEntry()) != null) { while ((f = zis.getNextEntry()) != null) {
if (f.getName().contains("__MACOSX/")) continue; if (f.getName().contains("__MACOSX/")) continue;
if (f.isDirectory()) continue; if (f.isDirectory()) continue;
String lowerName = f.getName().toLowerCase(); String lowerName = f.getName().toLowerCase();
if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr") || lowerName.endsWith(".bmp"))) continue; if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr") || lowerName.endsWith(".bmp"))) continue;
EaglerOutputStream baos = new EaglerOutputStream(); EaglerOutputStream baos = new EaglerOutputStream();
int len; int len;
while ((len = zis.read(bb)) != -1) { while ((len = zis.read(bb)) != -1) {
baos.write(bb, 0, len); baos.write(bb, 0, len);
} }
baos.close(); baos.close();
byte[] b = baos.toByteArray(); byte[] b = baos.toByteArray();
String fileName = f.getName().substring(folderPrefixOffset); String fileName = f.getName().substring(folderPrefixOffset);
if (fileName.equals("level.dat") || fileName.equals("level.dat_old")) { if (fileName.equals("level.dat") || fileName.equals("level.dat_old")) {
NBTTagCompound worldDatNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b)); NBTTagCompound worldDatNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b));
NBTTagCompound gameRulesNBT = worldDatNBT.getCompoundTag("Data").getCompoundTag("GameRules"); NBTTagCompound gameRulesNBT = worldDatNBT.getCompoundTag("Data").getCompoundTag("GameRules");
gameRulesNBT.setString("loadSpawnChunks", (gameRules & 2) != 0 ? "true" : "false"); gameRulesNBT.setString("loadSpawnChunks", (gameRules & 2) != 0 ? "true" : "false");
String s = (gameRules & 1) != 0 ? "true" : "false"; String s = (gameRules & 1) != 0 ? "true" : "false";
gameRulesNBT.setString("bedSpawnPoint", s); gameRulesNBT.setString("bedSpawnPoint", s);
gameRulesNBT.setString("clickToRide", "false"); gameRulesNBT.setString("clickToRide", "false");
gameRulesNBT.setString("clickToSit", s); gameRulesNBT.setString("clickToSit", s);
gameRulesNBT.setString("colorCodes", s); gameRulesNBT.setString("colorCodes", s);
gameRulesNBT.setString("doSignEditing", s); gameRulesNBT.setString("doSignEditing", s);
worldDatNBT.getCompoundTag("Data").setTag("GameRules", gameRulesNBT); worldDatNBT.getCompoundTag("Data").setTag("GameRules", gameRulesNBT);
worldDatNBT.getCompoundTag("Data").setString("LevelName", newName); worldDatNBT.getCompoundTag("Data").setString("LevelName", newName);
worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis()); worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis());
EaglerOutputStream bo = new EaglerOutputStream(); EaglerOutputStream bo = new EaglerOutputStream();
CompressedStreamTools.writeCompressed(worldDatNBT, bo); CompressedStreamTools.writeCompressed(worldDatNBT, bo);
b = bo.toByteArray(); b = bo.toByteArray();
VFile2 ff = new VFile2(worldDir, fileName); VFile2 ff = new VFile2(worldDir, fileName);
ff.setAllBytes(b); ff.setAllBytes(b);
prog += b.length; prog += b.length;
} else if ((fileName.endsWith(".mcr") || fileName.endsWith(".mca")) && (fileName.startsWith("region/") || fileName.startsWith("DIM1/region/") || fileName.startsWith("DIM-1/region/"))) { } else if ((fileName.endsWith(".mcr") || fileName.endsWith(".mca")) && (fileName.startsWith("region/") || fileName.startsWith("DIM1/region/") || fileName.startsWith("DIM-1/region/"))) {
VFile2 chunkFolder = new VFile2(worldDir, fileName.startsWith("DIM1") ? "level1" : (fileName.startsWith("DIM-1") ? "level-1" : "level0")); VFile2 chunkFolder = new VFile2(worldDir, fileName.startsWith("DIM1") ? "level1" : (fileName.startsWith("DIM-1") ? "level-1" : "level0"));
RegionFile mca = new RegionFile(new RandomAccessMemoryFile(b, b.length)); RegionFile mca = new RegionFile(new RandomAccessMemoryFile(b, b.length));
int loadChunksCount = 0; int loadChunksCount = 0;
for(int j = 0; j < 32; ++j) { for(int j = 0; j < 32; ++j) {
for(int k = 0; k < 32; ++k) { for(int k = 0; k < 32; ++k) {
if(mca.isChunkSaved(j, k)) { if(mca.isChunkSaved(j, k)) {
NBTTagCompound chunkNBT; NBTTagCompound chunkNBT;
NBTTagCompound chunkLevel; NBTTagCompound chunkLevel;
try { try {
chunkNBT = CompressedStreamTools.read(mca.getChunkDataInputStream(j, k)); chunkNBT = CompressedStreamTools.read(mca.getChunkDataInputStream(j, k));
if(!chunkNBT.hasKey("Level", 10)) { if(!chunkNBT.hasKey("Level", 10)) {
throw new IOException("Chunk is missing level data!"); throw new IOException("Chunk is missing level data!");
}
chunkLevel = chunkNBT.getCompoundTag("Level");
}catch(Throwable t) {
logger.error("{}: Could not read chunk: {}, {}", fileName, j, k);
logger.error(t);
continue;
} }
chunkLevel = chunkNBT.getCompoundTag("Level"); int chunkX = chunkLevel.getInteger("xPos");
}catch(Throwable t) { int chunkZ = chunkLevel.getInteger("zPos");
logger.error("{}: Could not read chunk: {}, {}", fileName, j, k); VFile2 chunkOut = new VFile2(chunkFolder, EaglerChunkLoader.getChunkPath(chunkX, chunkZ) + ".dat");
logger.error(t); if(chunkOut.exists()) {
continue; logger.error("{}: Chunk already exists: {}", fileName, chunkOut.getPath());
continue;
}
EaglerOutputStream bao = new EaglerOutputStream();
CompressedStreamTools.writeCompressed(chunkNBT, bao);
b = bao.toByteArray();
chunkOut.setAllBytes(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.2", prog);
}
++loadChunksCount;
} }
int chunkX = chunkLevel.getInteger("xPos");
int chunkZ = chunkLevel.getInteger("zPos");
VFile2 chunkOut = new VFile2(chunkFolder, EaglerChunkLoader.getChunkPath(chunkX, chunkZ) + ".dat");
if(chunkOut.exists()) {
logger.error("{}: Chunk already exists: {}", fileName, chunkOut.getPath());
continue;
}
EaglerOutputStream bao = new EaglerOutputStream();
CompressedStreamTools.writeCompressed(chunkNBT, bao);
b = bao.toByteArray();
chunkOut.setAllBytes(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.2", prog);
}
++loadChunksCount;
} }
} }
logger.info("{}: Imported {} chunks successfully ({} bytes)", fileName, loadChunksCount, prog);
} else if (fileName.startsWith("playerdata/") || fileName.startsWith("stats/")) {
//TODO: LAN player inventories
} else if (fileName.startsWith("data/") || fileName.startsWith("players/") || fileName.startsWith("eagler/skulls/")) {
VFile2 ff = new VFile2(worldDir, fileName);
ff.setAllBytes(b);
prog += b.length;
} else if (!fileName.equals("level.dat_mcr") && !fileName.equals("session.lock")) {
logger.info("Skipping file: {}", fileName);
}
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.2", prog);
} }
logger.info("{}: Imported {} chunks successfully ({} bytes)", fileName, loadChunksCount, prog);
} else if (fileName.startsWith("playerdata/") || fileName.startsWith("stats/")) {
//TODO: LAN player inventories
} else if (fileName.startsWith("data/") || fileName.startsWith("players/") || fileName.startsWith("eagler/skulls/")) {
VFile2 ff = new VFile2(worldDir, fileName);
ff.setAllBytes(b);
prog += b.length;
} else if (!fileName.equals("level.dat_mcr") && !fileName.equals("session.lock")) {
logger.info("Skipping file: {}", fileName);
}
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.importing.2", prog);
} }
} }
logger.info("MCA was successfully extracted into directory \"{}\"", worldDir.getPath()); logger.info("MCA was successfully extracted into directory \"{}\"", worldDir.getPath());
@ -172,129 +174,130 @@ public class WorldConverterMCA {
public static byte[] exportWorld(String folderName) throws IOException { public static byte[] exportWorld(String folderName) throws IOException {
EaglerOutputStream bao = new EaglerOutputStream(); EaglerOutputStream bao = new EaglerOutputStream();
ZipOutputStream zos = new ZipOutputStream(bao); VFile2 worldFolder;
zos.setComment("contains backup of world '" + folderName + "'"); try(ZipOutputStream zos = new ZipOutputStream(bao)) {
VFile2 worldFolder = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory(); zos.setComment("contains backup of world '" + folderName + "'");
logger.info("Exporting world directory \"{}\" as MCA", worldFolder.getPath()); worldFolder = EaglerIntegratedServerWorker.saveFormat.getSaveLoader(folderName, false).getWorldDirectory();
VFile2 vf = new VFile2(worldFolder, "level.dat"); logger.info("Exporting world directory \"{}\" as MCA", worldFolder.getPath());
byte[] b; VFile2 vf = new VFile2(worldFolder, "level.dat");
int lastProgUpdate = 0; byte[] b;
int prog = 0; int lastProgUpdate = 0;
boolean safe = false; int prog = 0;
if(vf.exists()) { boolean safe = false;
zos.putNextEntry(new ZipEntry(folderName + "/level.dat")); if(vf.exists()) {
b = vf.getAllBytes(); zos.putNextEntry(new ZipEntry(folderName + "/level.dat"));
zos.write(b); b = vf.getAllBytes();
prog += b.length; zos.write(b);
safe = true; prog += b.length;
} safe = true;
vf = new VFile2(worldFolder, "level.dat_old"); }
if(vf.exists()) { vf = new VFile2(worldFolder, "level.dat_old");
zos.putNextEntry(new ZipEntry(folderName + "/level.dat_old")); if(vf.exists()) {
b = vf.getAllBytes(); zos.putNextEntry(new ZipEntry(folderName + "/level.dat_old"));
zos.write(b); b = vf.getAllBytes();
prog += b.length; zos.write(b);
safe = true; prog += b.length;
} safe = true;
if (prog - lastProgUpdate > 25000) { }
lastProgUpdate = prog; if (prog - lastProgUpdate > 25000) {
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog); lastProgUpdate = prog;
} EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
String[] srcFolderNames = new String[] { "level0", "level-1", "level1" }; }
String[] dstFolderNames = new String[] { "/region/", "/DIM-1/region/", "/DIM1/region/" }; String[] srcFolderNames = new String[] { "level0", "level-1", "level1" };
List<VFile2> fileList; String[] dstFolderNames = new String[] { "/region/", "/DIM-1/region/", "/DIM1/region/" };
for(int i = 0; i < 3; ++i) { List<VFile2> fileList;
vf = new VFile2(worldFolder, srcFolderNames[i]); for(int i = 0; i < 3; ++i) {
fileList = vf.listFiles(true); vf = new VFile2(worldFolder, srcFolderNames[i]);
String regionFolder = folderName + dstFolderNames[i]; fileList = vf.listFiles(true);
logger.info("Converting chunks in \"{}\" as MCA to \"{}\"...", vf.getPath(), regionFolder); String regionFolder = folderName + dstFolderNames[i];
Map<String,RegionFile> regionFiles = new HashMap(); logger.info("Converting chunks in \"{}\" as MCA to \"{}\"...", vf.getPath(), regionFolder);
for(int k = 0, l = fileList.size(); k < l; ++k) { Map<String,RegionFile> regionFiles = new HashMap();
VFile2 chunkFile = fileList.get(k); for(int k = 0, l = fileList.size(); k < l; ++k) {
NBTTagCompound chunkNBT; VFile2 chunkFile = fileList.get(k);
NBTTagCompound chunkLevel; NBTTagCompound chunkNBT;
try { NBTTagCompound chunkLevel;
b = chunkFile.getAllBytes(); try {
chunkNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b)); b = chunkFile.getAllBytes();
if(!chunkNBT.hasKey("Level", 10)) { chunkNBT = CompressedStreamTools.readCompressed(new EaglerInputStream(b));
throw new IOException("Chunk is missing level data!"); if(!chunkNBT.hasKey("Level", 10)) {
throw new IOException("Chunk is missing level data!");
}
chunkLevel = chunkNBT.getCompoundTag("Level");
}catch(IOException t) {
logger.error("Could not read chunk: {}", chunkFile.getPath());
logger.error(t);
continue;
} }
chunkLevel = chunkNBT.getCompoundTag("Level"); int chunkX = chunkLevel.getInteger("xPos");
}catch(IOException t) { int chunkZ = chunkLevel.getInteger("zPos");
logger.error("Could not read chunk: {}", chunkFile.getPath()); String regionFileName = "r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mca";
logger.error(t); RegionFile rf = regionFiles.get(regionFileName);
if(rf == null) {
rf = new RegionFile(new RandomAccessMemoryFile(new byte[65536], 0));
regionFiles.put(regionFileName, rf);
}
try(DataOutputStream dos = rf.getChunkDataOutputStream(chunkX & 31, chunkZ & 31)) {
CompressedStreamTools.write(chunkNBT, dos);
}catch(IOException t) {
logger.error("Could not write chunk to {}: {}", regionFileName, chunkFile.getPath());
logger.error(t);
continue;
}
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
}
if(regionFiles.isEmpty()) {
logger.info("No region files were generated");
continue; continue;
} }
int chunkX = chunkLevel.getInteger("xPos"); for(Entry<String,RegionFile> etr : regionFiles.entrySet()) {
int chunkZ = chunkLevel.getInteger("zPos"); String regionPath = regionFolder + etr.getKey();
String regionFileName = "r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mca"; logger.info("Writing region file: {}", regionPath);
RegionFile rf = regionFiles.get(regionFileName); zos.putNextEntry(new ZipEntry(regionPath));
if(rf == null) { zos.write(etr.getValue().getFile().getByteArray());
rf = new RegionFile(new RandomAccessMemoryFile(new byte[65536], 0));
regionFiles.put(regionFileName, rf);
}
try(DataOutputStream dos = rf.getChunkDataOutputStream(chunkX & 31, chunkZ & 31)) {
CompressedStreamTools.write(chunkNBT, dos);
}catch(IOException t) {
logger.error("Could not write chunk to {}: {}", regionFileName, chunkFile.getPath());
logger.error(t);
continue;
} }
}
logger.info("Copying extra world data...");
fileList = (new VFile2(worldFolder, "data")).listFiles(false);
for(int k = 0, l = fileList.size(); k < l; ++k) {
VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(folderName + "/data/" + dataFile.getName()));
b = dataFile.getAllBytes();
zos.write(b);
prog += b.length; prog += b.length;
if (prog - lastProgUpdate > 25000) { if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog; lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog); EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
} }
} }
if(regionFiles.isEmpty()) { fileList = (new VFile2(worldFolder, "players")).listFiles(false);
logger.info("No region files were generated"); for(int k = 0, l = fileList.size(); k < l; ++k) {
continue; VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(folderName + "/players/" + dataFile.getName()));
b = dataFile.getAllBytes();
zos.write(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
} }
for(Entry<String,RegionFile> etr : regionFiles.entrySet()) { fileList = (new VFile2(worldFolder, "eagler/skulls")).listFiles(false);
String regionPath = regionFolder + etr.getKey(); for(int k = 0, l = fileList.size(); k < l; ++k) {
logger.info("Writing region file: {}", regionPath); VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(regionPath)); zos.putNextEntry(new ZipEntry(folderName + "/eagler/skulls/" + dataFile.getName()));
zos.write(etr.getValue().getFile().getByteArray()); b = dataFile.getAllBytes();
zos.write(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
} }
} }
logger.info("Copying extra world data...");
fileList = (new VFile2(worldFolder, "data")).listFiles(false);
for(int k = 0, l = fileList.size(); k < l; ++k) {
VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(folderName + "/data/" + dataFile.getName()));
b = dataFile.getAllBytes();
zos.write(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
}
fileList = (new VFile2(worldFolder, "players")).listFiles(false);
for(int k = 0, l = fileList.size(); k < l; ++k) {
VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(folderName + "/players/" + dataFile.getName()));
b = dataFile.getAllBytes();
zos.write(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
}
fileList = (new VFile2(worldFolder, "eagler/skulls")).listFiles(false);
for(int k = 0, l = fileList.size(); k < l; ++k) {
VFile2 dataFile = fileList.get(k);
zos.putNextEntry(new ZipEntry(folderName + "/eagler/skulls/" + dataFile.getName()));
b = dataFile.getAllBytes();
zos.write(b);
prog += b.length;
if (prog - lastProgUpdate > 25000) {
lastProgUpdate = prog;
EaglerIntegratedServerWorker.sendProgress("singleplayer.busy.exporting.2", prog);
}
}
zos.close();
logger.info("World directory \"{}\" was successfully exported as MCA", worldFolder.getPath()); logger.info("World directory \"{}\" was successfully exported as MCA", worldFolder.getPath());
return bao.toByteArray(); return bao.toByteArray();
} }

View File

@ -243,9 +243,9 @@ public class IntegratedServerPlayerNetworkManager {
temporaryOutputStream.write((len >> 16) & 0xFF); temporaryOutputStream.write((len >> 16) & 0xFF);
temporaryOutputStream.write((len >> 8) & 0xFF); temporaryOutputStream.write((len >> 8) & 0xFF);
temporaryOutputStream.write(len & 0xFF); temporaryOutputStream.write(len & 0xFF);
OutputStream os = EaglerZLIB.newDeflaterOutputStream(temporaryOutputStream); try(OutputStream os = EaglerZLIB.newDeflaterOutputStream(temporaryOutputStream)) {
temporaryBuffer.readBytes(os, len); temporaryBuffer.readBytes(os, len);
os.close(); }
compressedData = temporaryOutputStream.toByteArray(); compressedData = temporaryOutputStream.toByteArray();
}catch(IOException ex) { }catch(IOException ex) {
logger.error("Failed to compress packet {}!", pkt.getClass().getSimpleName()); logger.error("Failed to compress packet {}!", pkt.getClass().getSimpleName());

View File

@ -64,7 +64,7 @@ public class RelayUpdateChecker {
for(net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry etr : EagRuntime.getConfiguration().getRelays()) { for(net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry etr : EagRuntime.getConfiguration().getRelays()) {
relaysList.add(new RelayEntry(etr.address)); relaysList.add(new RelayEntry(etr.address));
} }
byte[] b = PlatformApplication.getLocalStorage("lastRelayUpdate"); byte[] b = PlatformApplication.getLocalStorage("lastRelayUpdate", false);
if(b != null) { if(b != null) {
try { try {
lastUpdateCheck = (new DataInputStream(new EaglerInputStream(b))).readLong(); lastUpdateCheck = (new DataInputStream(new EaglerInputStream(b))).readLong();
@ -79,7 +79,7 @@ public class RelayUpdateChecker {
try { try {
EaglerOutputStream bao = new EaglerOutputStream(8); EaglerOutputStream bao = new EaglerOutputStream(8);
(new DataOutputStream(bao)).writeLong(lastUpdateCheck); (new DataOutputStream(bao)).writeLong(lastUpdateCheck);
PlatformApplication.setLocalStorage("lastRelayUpdate", bao.toByteArray()); PlatformApplication.setLocalStorage("lastRelayUpdate", bao.toByteArray(), false);
} catch (IOException e) { } catch (IOException e) {
} }
for (int i = 0, l = relaysList.size(); i < l; ++i) { for (int i = 0, l = relaysList.size(); i < l; ++i) {

View File

@ -148,7 +148,16 @@ public class UpdateCertificate {
throw new CertificateInvalidException("SHA256 checksum of signature payload is invalid!"); throw new CertificateInvalidException("SHA256 checksum of signature payload is invalid!");
} }
return new UpdateCertificate(certData, EaglerZLIB.newGZIPInputStream(new EaglerInputStream(signaturePayload)), vers); UpdateCertificate cert;
try(InputStream gis = EaglerZLIB.newGZIPInputStream(new EaglerInputStream(signaturePayload))) {
cert = new UpdateCertificate(certData, gis, vers);
}
if(System.currentTimeMillis() < cert.sigTimestamp) {
throw new CertificateInvalidException("Update certificate timestamp is from the future!?");
}
return cert;
} }
private UpdateCertificate(byte[] certData, InputStream is, int sigVers) throws IOException { private UpdateCertificate(byte[] certData, InputStream is, int sigVers) throws IOException {

View File

@ -94,7 +94,7 @@ public class UpdateService {
} }
} }
} }
byte[] latestUpdate = PlatformApplication.getLocalStorage(EaglercraftVersion.updateLatestLocalStorageKey); byte[] latestUpdate = PlatformApplication.getLocalStorage(EaglercraftVersion.updateLatestLocalStorageKey, false);
if(latestUpdate != null) { if(latestUpdate != null) {
addCertificateToSet(latestUpdate, false); addCertificateToSet(latestUpdate, false);
} }
@ -150,7 +150,7 @@ public class UpdateService {
latestUpdateFound = cert; latestUpdateFound = cert;
if (saveLatest) { if (saveLatest) {
PlatformApplication.setLocalStorage(EaglercraftVersion.updateLatestLocalStorageKey, PlatformApplication.setLocalStorage(EaglercraftVersion.updateLatestLocalStorageKey,
certificateData); certificateData, false);
} }
} }
}else if(EagRuntime.getConfiguration().isLogInvalidCerts()) { }else if(EagRuntime.getConfiguration().isLogInvalidCerts()) {

Binary file not shown.

View File

@ -95,34 +95,66 @@ public class PlatformApplication {
private static native void setClipboard0(String str); private static native void setClipboard0(String str);
public static void setLocalStorage(String name, byte[] data) { public static void setLocalStorage(String name, byte[] data) {
setLocalStorage(name, data, true);
}
public static void setLocalStorage(String name, byte[] data, boolean hooks) {
IClientConfigAdapter adapter = PlatformRuntime.getClientConfigAdapter();
String eagName = adapter.getLocalStorageNamespace() + "." + name;
String b64 = Base64.encodeBase64String(data);
try { try {
Storage s = Window.current().getLocalStorage(); Storage s = Window.current().getLocalStorage();
if(s != null) { if(s != null) {
if(data != null) { if(data != null) {
s.setItem("_eaglercraftX." + name, Base64.encodeBase64String(data)); s.setItem(eagName, b64);
}else { }else {
s.removeItem("_eaglercraftX." + name); s.removeItem(eagName);
} }
} }
}catch(Throwable t) { }catch(Throwable t) {
} }
if(hooks) {
adapter.getHooks().callLocalStorageSavedHook(name, b64);
}
} }
public static byte[] getLocalStorage(String name) { public static byte[] getLocalStorage(String name) {
try { return getLocalStorage(name, true);
Storage s = Window.current().getLocalStorage(); }
if(s != null) {
String str = s.getItem("_eaglercraftX." + name); public static byte[] getLocalStorage(String name, boolean hooks) {
if(str != null) { IClientConfigAdapter adapter = PlatformRuntime.getClientConfigAdapter();
return Base64.decodeBase64(str); String eagName = adapter.getLocalStorageNamespace() + "." + name;
byte[] hooked = null;
if(hooks) {
String hookedStr = adapter.getHooks().callLocalStorageLoadHook(eagName);
if(hookedStr != null) {
try {
hooked = Base64.decodeBase64(hookedStr);
}catch(Throwable t) {
PlatformRuntime.logger.error("Invalid Base64 recieved from local storage hook!");
hooked = null;
}
}
}
if(hooked == null) {
try {
Storage s = Window.current().getLocalStorage();
if(s != null) {
String str = s.getItem(eagName);
if(str != null) {
return Base64.decodeBase64(str);
}else {
return null;
}
}else { }else {
return null; return null;
} }
}else { }catch(Throwable t) {
return null; return null;
} }
}catch(Throwable t) { }else {
return null; return hooked;
} }
} }

View File

@ -30,16 +30,18 @@ import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.minecraft.util.MathHelper; import net.minecraft.util.MathHelper;
/** /**
* Copyright (c) 2022-2023 LAX1DUDE. All Rights Reserved. * Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
* *
* WITH THE EXCEPTION OF PATCH FILES, MINIFIED JAVASCRIPT, AND ALL FILES * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* NORMALLY FOUND IN AN UNMODIFIED MINECRAFT RESOURCE PACK, YOU ARE NOT ALLOWED * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* TO SHARE, DISTRIBUTE, OR REPURPOSE ANY FILE USED BY OR PRODUCED BY THE * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* SOFTWARE IN THIS REPOSITORY WITHOUT PRIOR PERMISSION FROM THE PROJECT AUTHOR. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT FOR COMMERCIAL OR MALICIOUS USE * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* (please read the 'LICENSE' file this repo's root directory for more info) * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public class PlatformAudio { public class PlatformAudio {

View File

@ -102,8 +102,9 @@ public class PlatformInput {
public static boolean keyboardLockSupported = false; public static boolean keyboardLockSupported = false;
public static boolean lockKeys = false; public static boolean lockKeys = false;
private static boolean vsync = true; private static boolean vsync = true;
private static boolean vsyncSupport = false;
@JSBody(params = { }, script = "window.onbeforeunload = () => {return false;};") @JSBody(params = { }, script = "window.onbeforeunload = () => {return false;};")
private static native void onBeforeCloseRegister(); private static native void onBeforeCloseRegister();
@ -252,7 +253,18 @@ public class PlatformInput {
mouseDY = 0.0D; mouseDY = 0.0D;
} }
}); });
onBeforeCloseRegister();
try {
onBeforeCloseRegister();
}catch(Throwable t) {
}
try {
asyncRequestAnimationFrame();
vsyncSupport = true;
}catch(Throwable t) {
PlatformRuntime.logger.error("VSync is not supported on this browser!");
}
fullscreenQuery = fullscreenMediaQuery(); fullscreenQuery = fullscreenMediaQuery();
if (keyboardLockSupported = checkKeyboardLockSupported()) { if (keyboardLockSupported = checkKeyboardLockSupported()) {
@ -300,6 +312,9 @@ public class PlatformInput {
vsync = enable; vsync = enable;
} }
@JSBody(params = { "doc" }, script = "return (doc.visibilityState === \"visible\");")
private static native boolean getVisibilityState(JSObject doc);
public static void update() { public static void update() {
double r = win.getDevicePixelRatio(); double r = win.getDevicePixelRatio();
int w = PlatformRuntime.parent.getClientWidth(); int w = PlatformRuntime.parent.getClientWidth();
@ -320,10 +335,14 @@ public class PlatformInput {
PlatformRuntime.lastFrame = t; PlatformRuntime.lastFrame = t;
} }
} }
if(vsync) { if(getVisibilityState(win.getDocument())) {
asyncRequestAnimationFrame(); if(vsyncSupport && vsync) {
asyncRequestAnimationFrame();
}else {
EagUtils.sleep(0l);
}
}else { }else {
EagUtils.sleep(0l); EagUtils.sleep(50l);
} }
} }
@ -348,6 +367,10 @@ public class PlatformInput {
}, 50); }, 50);
} }
public static boolean isVSyncSupported() {
return vsyncSupport;
}
static void initFramebuffer(WebGL2RenderingContext ctx, WebGLFramebuffer fbo, int sw, int sh) { static void initFramebuffer(WebGL2RenderingContext ctx, WebGLFramebuffer fbo, int sw, int sh) {
context = ctx; context = ctx;
mainFramebuffer = fbo; mainFramebuffer = fbo;
@ -599,7 +622,7 @@ public class PlatformInput {
keyEvents.clear(); keyEvents.clear();
} }
@JSBody(params = {}, script = "return window.matchMedia('(display-mode: fullscreen)');") @JSBody(params = {}, script = "return window.matchMedia(\"(display-mode: fullscreen)\");")
private static native JSObject fullscreenMediaQuery(); private static native JSObject fullscreenMediaQuery();
@JSBody(params = { "mediaQuery" }, script = "return mediaQuery.matches;") @JSBody(params = { "mediaQuery" }, script = "return mediaQuery.matches;")

View File

@ -9,6 +9,7 @@ import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor; import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.browser.Window; import org.teavm.jso.browser.Window;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.jso.core.JSError; import org.teavm.jso.core.JSError;
import org.teavm.jso.dom.css.CSSStyleDeclaration; import org.teavm.jso.dom.css.CSSStyleDeclaration;
import org.teavm.jso.dom.html.HTMLCanvasElement; import org.teavm.jso.dom.html.HTMLCanvasElement;
@ -21,7 +22,6 @@ import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication; import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsAssetsURI; import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsAssetsURI;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsAssetsURIsArray;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot; import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot;
import net.lax1dude.eaglercraft.v1_8.log4j.ILogRedirector; import net.lax1dude.eaglercraft.v1_8.log4j.ILogRedirector;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
@ -87,7 +87,7 @@ public class ClientMain {
if(epkSingleURL != null) { if(epkSingleURL != null) {
configEPKFiles = new EPKFileEntry[] { new EPKFileEntry(epkSingleURL, "") }; configEPKFiles = new EPKFileEntry[] { new EPKFileEntry(epkSingleURL, "") };
}else { }else {
JSEaglercraftXOptsAssetsURIsArray epkURLs = eaglercraftOpts.getAssetsURIArray(); JSArrayReader<JSEaglercraftXOptsAssetsURI> epkURLs = eaglercraftOpts.getAssetsURIArray();
int len = epkURLs.getLength(); int len = epkURLs.getLength();
if(len == 0) { if(len == 0) {
throw new JSONException("assetsURI array cannot be empty!"); throw new JSONException("assetsURI array cannot be empty!");

View File

@ -13,6 +13,7 @@ import org.teavm.jso.dom.html.HTMLDocument;
import org.teavm.jso.dom.html.HTMLElement; import org.teavm.jso.dom.html.HTMLElement;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication; import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
/** /**
@ -63,13 +64,13 @@ public class DebugConsoleWindow {
destroyWindow(); destroyWindow();
} }
}); });
if("true".equals(parent.getLocalStorage().getItem("_eaglercraftX.showDebugConsole"))) { if("true".equals(parent.getLocalStorage().getItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole"))) {
showDebugConsole0(); showDebugConsole0();
} }
} }
public static void showDebugConsole() { public static void showDebugConsole() {
parent.getLocalStorage().setItem("_eaglercraftX.showDebugConsole", "true"); parent.getLocalStorage().setItem(PlatformRuntime.getClientConfigAdapter().getLocalStorageNamespace() + ".showDebugConsole", "true");
showDebugConsole0(); showDebugConsole0();
} }

View File

@ -10,13 +10,14 @@ import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSArrayReader;
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter; import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter;
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapterHooks;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsHooks;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRelay; import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRelay;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRelaysArray;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot; import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsRoot;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsServer; import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsServer;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsServersArray;
import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry; import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry;
/** /**
@ -56,6 +57,9 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
private boolean checkRelaysForUpdates = false; private boolean checkRelaysForUpdates = false;
private boolean enableSignatureBadge = false; private boolean enableSignatureBadge = false;
private boolean allowVoiceClient = true; private boolean allowVoiceClient = true;
private boolean allowFNAWSkins = true;
private String localStorageNamespace = "_eaglercraftX";
private final TeaVMClientConfigAdapterHooks hooks = new TeaVMClientConfigAdapterHooks();
public void loadNative(JSObject jsObject) { public void loadNative(JSObject jsObject) {
integratedServerOpts = new JSONObject(); integratedServerOpts = new JSONObject();
@ -75,6 +79,12 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftXOpts.getLogInvalidCerts(false); logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftXOpts.getLogInvalidCerts(false);
enableSignatureBadge = eaglercraftXOpts.getEnableSignatureBadge(false); enableSignatureBadge = eaglercraftXOpts.getEnableSignatureBadge(false);
allowVoiceClient = eaglercraftXOpts.getAllowVoiceClient(true); allowVoiceClient = eaglercraftXOpts.getAllowVoiceClient(true);
allowFNAWSkins = eaglercraftXOpts.getAllowFNAWSkins(true);
localStorageNamespace = eaglercraftXOpts.getLocalStorageNamespace(EaglercraftVersion.localStorageNamespace);
JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks();
if(hooksObj != null) {
hooks.loadHooks(hooksObj);
}
integratedServerOpts.put("worldsDB", worldsDB); integratedServerOpts.put("worldsDB", worldsDB);
integratedServerOpts.put("demoMode", demoMode); integratedServerOpts.put("demoMode", demoMode);
@ -82,8 +92,9 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
integratedServerOpts.put("allowUpdateSvc", isAllowUpdateSvc); integratedServerOpts.put("allowUpdateSvc", isAllowUpdateSvc);
integratedServerOpts.put("allowUpdateDL", isAllowUpdateDL); integratedServerOpts.put("allowUpdateDL", isAllowUpdateDL);
integratedServerOpts.put("allowVoiceClient", allowVoiceClient); integratedServerOpts.put("allowVoiceClient", allowVoiceClient);
integratedServerOpts.put("allowFNAWSkins", allowFNAWSkins);
JSEaglercraftXOptsServersArray serversArray = eaglercraftXOpts.getServers(); JSArrayReader<JSEaglercraftXOptsServer> serversArray = eaglercraftXOpts.getServers();
if(serversArray != null) { if(serversArray != null) {
for(int i = 0, l = serversArray.getLength(); i < l; ++i) { for(int i = 0, l = serversArray.getLength(); i < l; ++i) {
JSEaglercraftXOptsServer serverEntry = serversArray.get(i); JSEaglercraftXOptsServer serverEntry = serversArray.get(i);
@ -95,7 +106,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
} }
} }
JSEaglercraftXOptsRelaysArray relaysArray = eaglercraftXOpts.getRelays(); JSArrayReader<JSEaglercraftXOptsRelay> relaysArray = eaglercraftXOpts.getRelays();
if(relaysArray != null) { if(relaysArray != null) {
boolean gotAPrimary = false; boolean gotAPrimary = false;
for(int i = 0, l = relaysArray.getLength(); i < l; ++i) { for(int i = 0, l = relaysArray.getLength(); i < l; ++i) {
@ -162,6 +173,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftOpts.optBoolean("logInvalidCerts", false); logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftOpts.optBoolean("logInvalidCerts", false);
enableSignatureBadge = eaglercraftOpts.optBoolean("enableSignatureBadge", false); enableSignatureBadge = eaglercraftOpts.optBoolean("enableSignatureBadge", false);
allowVoiceClient = eaglercraftOpts.optBoolean("allowVoiceClient", true); allowVoiceClient = eaglercraftOpts.optBoolean("allowVoiceClient", true);
allowFNAWSkins = eaglercraftOpts.optBoolean("allowFNAWSkins", true);
localStorageNamespace = eaglercraftOpts.optString("localStorageNamespace", EaglercraftVersion.localStorageNamespace);
JSONArray serversArray = eaglercraftOpts.optJSONArray("servers"); JSONArray serversArray = eaglercraftOpts.optJSONArray("servers");
if(serversArray != null) { if(serversArray != null) {
for(int i = 0, l = serversArray.length(); i < l; ++i) { for(int i = 0, l = serversArray.length(); i < l; ++i) {
@ -309,6 +322,21 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
return allowVoiceClient; return allowVoiceClient;
} }
@Override
public boolean isAllowFNAWSkins() {
return allowFNAWSkins;
}
@Override
public String getLocalStorageNamespace() {
return localStorageNamespace;
}
@Override
public IClientConfigAdapterHooks getHooks() {
return hooks;
}
@Override @Override
public String toString() { public String toString() {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
@ -327,6 +355,8 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter {
jsonObject.put("checkRelaysForUpdates", checkRelaysForUpdates); jsonObject.put("checkRelaysForUpdates", checkRelaysForUpdates);
jsonObject.put("enableSignatureBadge", enableSignatureBadge); jsonObject.put("enableSignatureBadge", enableSignatureBadge);
jsonObject.put("allowVoiceClient", allowVoiceClient); jsonObject.put("allowVoiceClient", allowVoiceClient);
jsonObject.put("allowFNAWSkins", allowFNAWSkins);
jsonObject.put("localStorageNamespace", localStorageNamespace);
JSONArray serversArr = new JSONArray(); JSONArray serversArr = new JSONArray();
for(int i = 0, l = defaultServers.size(); i < l; ++i) { for(int i = 0, l = defaultServers.size(); i < l; ++i) {
DefaultServer srv = defaultServers.get(i); DefaultServer srv = defaultServers.get(i);

View File

@ -0,0 +1,100 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm;
import java.util.function.Supplier;
import org.teavm.interop.Async;
import org.teavm.interop.AsyncCallback;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.browser.Window;
import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapterHooks;
import net.lax1dude.eaglercraft.v1_8.internal.teavm.opts.JSEaglercraftXOptsHooks;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class TeaVMClientConfigAdapterHooks implements IClientConfigAdapterHooks {
private static final Logger logger = LogManager.getLogger("TeaVMClientConfigAdapterHooks");
private LocalStorageSaveHook saveHook = null;
private LocalStorageLoadHook loadHook = null;
@JSFunctor
private static interface LocalStorageSaveHook extends JSObject {
void call(String key, String base64);
}
@Override
public void callLocalStorageSavedHook(String key, String base64) {
if(saveHook != null) {
callHookSafe("localStorageSaved", () -> {
saveHook.call(key, base64);
});
}
}
@JSFunctor
private static interface LocalStorageLoadHook extends JSObject {
String call(String key);
}
@Override
public String callLocalStorageLoadHook(String key) {
if(loadHook != null) {
return (String)callHookSafeWithReturn("localStorageLoaded", () -> {
return loadHook.call(key);
});
}else {
return null;
}
}
private static void callHookSafe(String identifer, Runnable hooker) {
Window.setTimeout(() -> {
try {
hooker.run();
}catch(Throwable t) {
logger.error("Caught exception while invoking eaglercraftXOpts \"{}\" hook!", identifer);
logger.error(t);
}
}, 0);
}
@Async
private static native Object callHookSafeWithReturn(String identifer, Supplier<Object> hooker);
private static void callHookSafeWithReturn(String identifer, Supplier<Object> hooker, final AsyncCallback<Object> cb) {
Window.setTimeout(() -> {
Object res = null;
try {
res = hooker.get();
}catch(Throwable t) {
logger.error("Caught exception while invoking eaglercraftXOpts \"{}\" hook!", identifer);
logger.error(t);
}finally {
cb.complete(res);
}
}, 0);
}
public void loadHooks(JSEaglercraftXOptsHooks hooks) {
saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook();
loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook();
}
}

View File

@ -1,8 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm.opts; package net.lax1dude.eaglercraft.v1_8.internal.teavm.opts;
import org.teavm.jso.JSIndexer; import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
/** /**
* Copyright (c) 2024 lax1dude. All Rights Reserved. * Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -19,12 +18,12 @@ import org.teavm.jso.JSProperty;
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
* *
*/ */
public interface JSEaglercraftXOptsAssetsURIsArray extends JSObject { public abstract class JSEaglercraftXOptsHooks implements JSObject {
@JSIndexer @JSBody(script = "return (typeof this.localStorageSaved === \"function\") ? this.localStorageSaved : null;")
JSEaglercraftXOptsAssetsURI get(int idx); public native JSObject getLocalStorageSavedHook();
@JSProperty @JSBody(script = "return (typeof this.localStorageLoaded === \"function\") ? this.localStorageLoaded : null;")
int getLength(); public native JSObject getLocalStorageLoadedHook();
} }

View File

@ -2,6 +2,7 @@ package net.lax1dude.eaglercraft.v1_8.internal.teavm.opts;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSArrayReader;
/** /**
* Copyright (c) 2024 lax1dude. All Rights Reserved. * Copyright (c) 2024 lax1dude. All Rights Reserved.
@ -27,7 +28,7 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject {
public native String getAssetsURI(); public native String getAssetsURI();
@JSBody(script = "return (typeof this.assetsURI === \"object\") ? this.assetsURI : null;") @JSBody(script = "return (typeof this.assetsURI === \"object\") ? this.assetsURI : null;")
public native JSEaglercraftXOptsAssetsURIsArray getAssetsURIArray(); public native JSArrayReader<JSEaglercraftXOptsAssetsURI> getAssetsURIArray();
@JSBody(params = { "def" }, script = "return (typeof this.lang === \"string\") ? this.lang : def;") @JSBody(params = { "def" }, script = "return (typeof this.lang === \"string\") ? this.lang : def;")
public native String getLang(String defaultValue); public native String getLang(String defaultValue);
@ -48,10 +49,10 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject {
public native boolean getDemoMode(boolean defaultValue); public native boolean getDemoMode(boolean defaultValue);
@JSBody(script = "return (typeof this.servers === \"object\") ? this.servers : null;") @JSBody(script = "return (typeof this.servers === \"object\") ? this.servers : null;")
public native JSEaglercraftXOptsServersArray getServers(); public native JSArrayReader<JSEaglercraftXOptsServer> getServers();
@JSBody(script = "return (typeof this.relays === \"object\") ? this.relays : null;") @JSBody(script = "return (typeof this.relays === \"object\") ? this.relays : null;")
public native JSEaglercraftXOptsRelaysArray getRelays(); public native JSArrayReader<JSEaglercraftXOptsRelay> getRelays();
@JSBody(params = { "def" }, script = "return (typeof this.checkShaderGLErrors === \"boolean\") ? this.checkShaderGLErrors : def;") @JSBody(params = { "def" }, script = "return (typeof this.checkShaderGLErrors === \"boolean\") ? this.checkShaderGLErrors : def;")
public native boolean getCheckShaderGLErrors(boolean defaultValue); public native boolean getCheckShaderGLErrors(boolean defaultValue);
@ -83,4 +84,13 @@ public abstract class JSEaglercraftXOptsRoot implements JSObject {
@JSBody(params = { "def" }, script = "return (typeof this.allowVoiceClient === \"boolean\") ? this.allowVoiceClient : def;") @JSBody(params = { "def" }, script = "return (typeof this.allowVoiceClient === \"boolean\") ? this.allowVoiceClient : def;")
public native boolean getAllowVoiceClient(boolean defaultValue); public native boolean getAllowVoiceClient(boolean defaultValue);
@JSBody(params = { "def" }, script = "return (typeof this.allowFNAWSkins === \"boolean\") ? this.allowFNAWSkins : def;")
public native boolean getAllowFNAWSkins(boolean defaultValue);
@JSBody(script = "return (typeof this.hooks === \"object\") ? this.hooks : null;")
public native JSEaglercraftXOptsHooks getHooks();
@JSBody(params = { "def" }, script = "return (typeof this.localStorageNamespace === \"string\") ? this.localStorageNamespace : def;")
public native String getLocalStorageNamespace(String defaultValue);
} }

View File

@ -1,30 +0,0 @@
package net.lax1dude.eaglercraft.v1_8.internal.teavm.opts;
import org.teavm.jso.JSIndexer;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
/**
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public interface JSEaglercraftXOptsServersArray extends JSObject {
@JSIndexer
JSEaglercraftXOptsServer get(int idx);
@JSProperty
int getLength();
}