list) throws IOException {
- saveTo(file, list, 0);
+ saveTo(file, list, 0, false);
+ }
+
+ /**
+ * Save the Entity
s in the list to the given file.
+ *
+ * The Entity
s' pilots, damage, ammo loads, ammo usage, and
+ * other campaign-related information are retained, but data specific to a
+ * particular game is ignored. This method is a simpler version of the
+ * overloaded method {@code saveTo}, with a default generic battle value of 0
+ * (this causes GBV to be ignored).
+ *
+ * @param file
+ * - The current contents of the file will be discarded and all
+ * Entity
s in the list will be written to the file.
+ * @param list
+ * - An ArrayList
containing Entity
s to be
+ * stored in a file.
+ * @param embedUnits
+ * - Set to true
to embed the unit file of custom units (blk/mtf data) into the file.
+ * This allows the resulting file to be loaded by someone who doesn't have those custom units available.
+ * @throws IOException
+ * - Is thrown on any error.
+ */
+ public static void saveTo(File file, ArrayList list, boolean embedUnits) throws IOException {
+ saveTo(file, list, 0, embedUnits);
}
/**
@@ -537,6 +568,7 @@ public static void saveTo(File file, ArrayList list) throws IOException
* The Entity
s' pilots, damage, ammo loads, ammo usage, and
* other campaign-related information are retained, but data specific to a
* particular game is ignored.
+ * Unit embedding is off, see {@link #saveTo(File, ArrayList, int, boolean) the overloaded version of this function}
*
* @param file
* - The current contents of the file will be discarded and all
@@ -551,6 +583,32 @@ public static void saveTo(File file, ArrayList list) throws IOException
* - Is thrown on any error.
*/
public static void saveTo(File file, ArrayList list, int genericBattleValue) throws IOException {
+ saveTo(file, list, genericBattleValue, false);
+ }
+
+ /**
+ * Save the Entity
s in the list to the given file.
+ *
+ * The Entity
s' pilots, damage, ammo loads, ammo usage, and
+ * other campaign-related information are retained, but data specific to a
+ * particular game is ignored.
+ *
+ * @param file
+ * - The current contents of the file will be discarded and all
+ * Entity
s in the list will be written to the file.
+ * @param list
+ * - A ArrayList
containing Entity
s to be
+ * stored in a file.
+ * @param genericBattleValue
+ * - An Integer
representing the generic battle value. If it
+ * is greater than 0, it will be written into the XML.
+ * @param embedUnits
+ * - Set to true
to embed the unit file of custom units (blk/mtf data) into the file.
+ * This allows the resulting file to be loaded by someone who doesn't have those custom units available.
+ * @throws IOException
+ * - Is thrown on any error.
+ */
+ public static void saveTo(File file, ArrayList list, int genericBattleValue, boolean embedUnits) throws IOException {
// Open up the file. Produce UTF-8 output.
Writer output = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file), StandardCharsets.UTF_8));
@@ -563,7 +621,7 @@ public static void saveTo(File file, ArrayList list, int genericBattleVa
output.write("" + genericBattleValue + "\n\n");
}
- writeEntityList(output, list);
+ writeEntityList(output, list, embedUnits);
// Finish writing.
output.write("" + MULParser.ELE_UNIT + ">\n");
@@ -731,6 +789,10 @@ private static void writeKills(Writer output, Hashtable kills) t
}
public static void writeEntityList(Writer output, ArrayList list) throws IOException {
+ writeEntityList(output, list, false);
+ }
+
+ public static void writeEntityList(Writer output, ArrayList list, boolean embedUnits) throws IOException {
// Walk through the list of entities.
Iterator items = list.iterator();
while (items.hasNext()) {
@@ -1176,6 +1238,42 @@ public static void writeEntityList(Writer output, ArrayList list) throws
output.write("\"/>\n");
}
+ // Write out the mtf/blk file for the unit
+ String data = null;
+ if (embedUnits && !entity.isCanon()) {
+ if (entity instanceof Mek mek) {
+ data = mek.getMtf();
+ } else {
+ try {
+ data = String.join("\n", BLKFile.getBlock(entity).getAllDataAsString());
+ } catch (EntitySavingException e) {
+ logger.error("Error writing unit: {}", entity);
+ logger.error(e);
+ }
+ }
+ }
+
+ if (data != null) {
+ String fileName = (entity.getChassis() + ' ' + entity.getModel()).trim();
+ fileName = fileName.replaceAll("[/\\\\<>:\"|?*]", "_");
+ fileName = fileName + ((entity instanceof Mek) ? ".mtf" : ".blk");
+
+
+ output.write(indentStr(indentLvl + 1) + '<' + MULParser.ELE_CONSTRUCTION_DATA + ' ' + MULParser.ATTR_FILENAME
+ + "=\"" + fileName + "\">\n");
+
+ output.write(indentStr(indentLvl + 2));
+
+ var dataStream = new ByteArrayOutputStream();
+ var ps = new PrintStream(new GZIPOutputStream(Base64.getEncoder().wrap(dataStream), true));
+ ps.print(data);
+ ps.close();
+
+ output.write(dataStream.toString());
+ output.write('\n' + indentStr(indentLvl + 1) + "" + MULParser.ELE_CONSTRUCTION_DATA + ">\n");
+ }
+
+
// Finish writing this entity to the file.
output.write(indentStr(indentLvl) + "" + MULParser.ELE_ENTITY + ">\n\n");
diff --git a/megamek/src/megamek/common/MULParser.java b/megamek/src/megamek/common/MULParser.java
index 1731944b714..1edc768b4a3 100644
--- a/megamek/src/megamek/common/MULParser.java
+++ b/megamek/src/megamek/common/MULParser.java
@@ -13,16 +13,9 @@
*/
package megamek.common;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Objects;
-import java.util.StringTokenizer;
-import java.util.Vector;
+import java.io.*;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
import javax.xml.parsers.DocumentBuilder;
@@ -108,6 +101,7 @@ public class MULParser {
public static final String ELE_BA_APM = "antiPersonnelMount";
public static final String ELE_LOADED = "loaded";
public static final String ELE_SHIP = "ship";
+ public static final String ELE_CONSTRUCTION_DATA = "construction_data";
/**
* The names of attributes generally associated with Entity tags
@@ -238,6 +232,7 @@ public class MULParser {
public static final String ATTR_GUNNERYAEROB = "gunneryAeroB";
public static final String ATTR_PILOTINGAERO = "pilotingAero";
public static final String ATTR_CREWTYPE = "crewType";
+ public static final String ATTR_FILENAME = "filename";
/**
* Special values recognized by this parser.
@@ -540,8 +535,30 @@ private void parseEntity(final Element entityNode, final @Nullable GameOptions o
String chassis = entityNode.getAttribute(ATTR_CHASSIS);
String model = entityNode.getAttribute(ATTR_MODEL);
- // Create a new entity
- Entity entity = getEntity(chassis, model);
+ Entity entity = null;
+
+ // Attempt to load the entity from the data embedded into the MUL file
+ try {
+ var cdnl = entityNode.getElementsByTagName(ELE_CONSTRUCTION_DATA);
+ if (cdnl.getLength() == 1) {
+ var cd = (Element) cdnl.item(0);
+ logger.info("Trying to load unit {} {} from embedded data instead of cache.", chassis, model);
+
+ try (
+ var rawStream = new ByteArrayInputStream(Base64.getDecoder().decode(cd.getTextContent().trim()));
+ var gzs = new GZIPInputStream(rawStream);
+ ) {
+ entity = new MekFileParser(gzs, cd.getAttribute(ATTR_FILENAME)).getEntity();
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e, "Failed to load unit from embedded data.");
+ }
+
+ // Look for the entity in the unit cache if it couldn't be loaded.
+ if (entity == null) {
+ entity = getEntity(chassis, model);
+ }
// Make sure we've got an Entity
if (entity == null) {