diff --git a/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTab.java b/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTab.java index 58ad2e3a0..93c32e096 100644 --- a/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTab.java +++ b/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTab.java @@ -78,6 +78,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of import org.nschmidt.ldparteditor.helper.composite3d.GuiStatusManager; import org.nschmidt.ldparteditor.helper.composite3d.SelectorSettings; import org.nschmidt.ldparteditor.helper.composite3d.ViewIdleManager; +import org.nschmidt.ldparteditor.helper.compositetext.DataMetacommandExporter; import org.nschmidt.ldparteditor.helper.compositetext.Inliner; import org.nschmidt.ldparteditor.helper.compositetext.Inspector; import org.nschmidt.ldparteditor.helper.compositetext.Issue2ClipboardCopier; @@ -1833,6 +1834,28 @@ public void mouseDown(MouseEvent e) { widgetUtil(mntmCopyPtr[0]).addSelectionListener(e -> tabState.folder[0].copy()); widgetUtil(mntmCutPtr[0]).addSelectionListener(e -> tabState.folder[0].cut()); widgetUtil(mntmPastePtr[0]).addSelectionListener(e -> tabState.folder[0].paste()); + + widgetUtil(mntmImportPngDataPtr[0]).addSelectionListener(e -> { + if (!tabState.getFileNameObj().getVertexManager().isUpdated()){ + return; + } + + final DatFile df = tabState.getFileNameObj(); + final VertexManager vm = df.getVertexManager(); + vm.addSnapshot(); + // TODO Needs implementation! + }); + widgetUtil(mntmExportPngDataPtr[0]).addSelectionListener(e -> { + final DatFile df = tabState.getFileNameObj(); + final StyledText st = getTextComposite(); + int s1 = st.getSelectionRange().x; + int s2 = s1 + st.getSelectionRange().y; + int fromLine = s1 > -1 ? st.getLineAtOffset(s1) : s1 * -1; + int toLine = s2 > -1 ? st.getLineAtOffset(s2) : s2 * -1; + fromLine++; + toLine++; + DataMetacommandExporter.export(fromLine, toLine, df); + }); widgetUtil(mntmDrawSelectionPtr[0]).addSelectionListener(e -> { if (!tabState.getFileNameObj().getVertexManager().isUpdated()){ diff --git a/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTabDesign.java b/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTabDesign.java index 6e6807ce4..fa4591824 100644 --- a/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTabDesign.java +++ b/src/org/nschmidt/ldparteditor/composite/compositetab/CompositeTabDesign.java @@ -62,6 +62,9 @@ class CompositeTabDesign extends CTabItem { final MenuItem[] mntmDeletePtr = new MenuItem[1]; final MenuItem[] mntmPastePtr = new MenuItem[1]; + final MenuItem[] mntmImportPngDataPtr = new MenuItem[1]; + final MenuItem[] mntmExportPngDataPtr = new MenuItem[1]; + final MenuItem[] mntmQuickFixPtr = new MenuItem[1]; final MenuItem[] mntmQuickFixSamePtr = new MenuItem[1]; final NButton[] btnQuickFixPtr = new NButton[1]; @@ -333,6 +336,18 @@ private final void createContents(CTabFolder tabFolder) { mntmDelete.setText(I18n.COPYNPASTE_DELETE); mntmDelete.setImage(ResourceManager.getImage("icon16_delete.png")); //$NON-NLS-1$ mntmDeletePtr[0] = mntmDelete; + + new MenuItem(menu[0], SWT.SEPARATOR); + + MenuItem mntmImportPngData = new MenuItem(menu[0], I18n.rightToLeftStyle()); + mntmImportPngData.setText(I18n.EDITORTEXT_DATA_IMPORT); + mntmImportPngData.setImage(ResourceManager.getImage("icon16_bgpic.png")); //$NON-NLS-1$ + mntmImportPngDataPtr[0] = mntmImportPngData; + + MenuItem mntmExportPngData = new MenuItem(menu[0], I18n.rightToLeftStyle()); + mntmExportPngData.setText(I18n.EDITORTEXT_DATA_EXPORT); + mntmExportPngData.setImage(ResourceManager.getImage("icon16_bgpic.png")); //$NON-NLS-1$ + mntmExportPngDataPtr[0] = mntmExportPngData; } } } diff --git a/src/org/nschmidt/ldparteditor/data/BinaryDataRegistry.java b/src/org/nschmidt/ldparteditor/data/BinaryDataRegistry.java new file mode 100644 index 000000000..8e36d77ec --- /dev/null +++ b/src/org/nschmidt/ldparteditor/data/BinaryDataRegistry.java @@ -0,0 +1,54 @@ +/* MIT - License + +Copyright (c) 2012 - this year, Nils Schmidt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +package org.nschmidt.ldparteditor.data; + +import java.util.HashMap; +import java.util.Map; + +import org.nschmidt.ldparteditor.logger.NLogger; + +class BinaryDataRegistry { + + private final Map fileNameToMetaTagMap = new HashMap<>(); + private final Map fileNameToBinaryMap = new HashMap<>(); + + public boolean hasFile(String filename) { + return fileNameToMetaTagMap.containsKey(filename) + || fileNameToBinaryMap.containsKey(filename); + } + + public byte[] getFileBytes(String filename) { + if (fileNameToBinaryMap.containsKey(filename)) { + return fileNameToBinaryMap.getOrDefault(filename, new byte[0]); + } + + final GDataBinary dataMetaTag = fileNameToMetaTagMap.get(filename); + final byte[] binary = dataMetaTag.loadBinary(); + fileNameToBinaryMap.put(filename, binary); + fileNameToMetaTagMap.remove(filename); + + return binary; + } + + public void addData(GDataBinary dataMetaTag) { + NLogger.debug(BinaryDataRegistry.class, "Register binary !DATA: {0}", dataMetaTag); //$NON-NLS-1$ + // "0 !DATA " has a length of 8 + final String filename = dataMetaTag.toString().substring(8); + NLogger.debug(BinaryDataRegistry.class, "The filename is: {0}", filename); //$NON-NLS-1$ + fileNameToMetaTagMap.put(filename, dataMetaTag); + fileNameToBinaryMap.remove(filename); + } +} diff --git a/src/org/nschmidt/ldparteditor/data/DatFile.java b/src/org/nschmidt/ldparteditor/data/DatFile.java index c11186133..5e9621532 100644 --- a/src/org/nschmidt/ldparteditor/data/DatFile.java +++ b/src/org/nschmidt/ldparteditor/data/DatFile.java @@ -130,6 +130,7 @@ public final class DatFile { private HistoryManager history = new HistoryManager(this); private DuplicateManager duplicate = new DuplicateManager(this); private DatHeaderManager datHeader = new DatHeaderManager(this); + private final BinaryDataRegistry binaryData = new BinaryDataRegistry(); public static DatFile createDatFileForReview(String path) { return new DatFile(path, true); @@ -2050,4 +2051,8 @@ public void setOptimizingCSG(boolean optimizingCSG) { public boolean isFromPartReview() { return fromPartReview; } + + public BinaryDataRegistry getBinaryData() { + return binaryData; + } } diff --git a/src/org/nschmidt/ldparteditor/data/GDataBinary.java b/src/org/nschmidt/ldparteditor/data/GDataBinary.java new file mode 100644 index 000000000..4440381ce --- /dev/null +++ b/src/org/nschmidt/ldparteditor/data/GDataBinary.java @@ -0,0 +1,168 @@ +/* MIT - License + +Copyright (c) 2012 - this year, Nils Schmidt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +package org.nschmidt.ldparteditor.data; + +import java.util.Base64; +import java.util.Map; +import java.util.regex.Pattern; + +import org.nschmidt.ldparteditor.composite.Composite3D; +import org.nschmidt.ldparteditor.helper.math.ThreadsafeHashMap; +import org.nschmidt.ldparteditor.helper.math.ThreadsafeSortedMap; +import org.nschmidt.ldparteditor.logger.NLogger; + +/** + * Holds binary base64 encoded data + */ +public final class GDataBinary extends GData { + + private final DatFile df; + + public GDataBinary(String text, DatFile df, GData1 parent) { + super(parent); + this.df = df; + this.text = text; + this.df.getBinaryData().addData(this); + } + + public byte[] loadBinary() { + final Pattern whitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ + final StringBuilder base64Sb = new StringBuilder(); + + GData gd = this.next; + while (gd != null) { + final String line = whitespace.matcher(gd.toString()).replaceAll(" ").trim(); //$NON-NLS-1$ + if (line.startsWith("0 !: ")) { //$NON-NLS-1$ + final String encodedSubstring = line.substring(5); + base64Sb.append(encodedSubstring); + } else if (line.length() > 0) { + break; + } + + gd = gd.next; + } + + final String encodedString = base64Sb.toString(); + + try { + return Base64.getDecoder().decode(encodedString); + } catch (IllegalArgumentException iae) { + NLogger.debug(GDataBinary.class, iae); + } + + return new byte[0]; + } + + @Override + public void drawGL20(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20RandomColours(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20BFC(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20BFCuncertified(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20BFCbackOnly(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20BFCcolour(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20WhileAddCondlines(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20CoplanarityHeatmap(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20Wireframe(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public void drawGL20BFCtextured(Composite3D c3d) { + // Implementation is not required. + } + + @Override + public int type() { + return 11; + } + + @Override + String getNiceString() { + return text; + } + + @Override + public String inlinedString(BFC bfc, GColour colour) { + return text; + } + + @Override + public String transformAndColourReplace(String colour, Matrix matrix) { + return text; + } + + @Override + public void getBFCorientationMap(Map map) { + // Implementation is not required. + } + + @Override + public void getBFCorientationMapNOCERTIFY(Map map) { + // Implementation is not required. + } + + @Override + public void getBFCorientationMapNOCLIP(Map map) { + // Implementation is not required. + } + + @Override + public void getVertexNormalMap(GDataState state, ThreadsafeSortedMap vertexLinkedToNormalCACHE, ThreadsafeHashMap dataLinkedToNormalCACHE, VM00Base vm) { + // Implementation is not required. + } + + @Override + public void getVertexNormalMapNOCERTIFY(GDataState state, ThreadsafeSortedMap vertexLinkedToNormalCACHE, ThreadsafeHashMap dataLinkedToNormalCACHE, VM00Base vm) { + // Implementation is not required. + } + + @Override + public void getVertexNormalMapNOCLIP(GDataState state, ThreadsafeSortedMap vertexLinkedToNormalCACHE, ThreadsafeHashMap dataLinkedToNormalCACHE, VM00Base vm) { + // Implementation is not required. + } +} diff --git a/src/org/nschmidt/ldparteditor/data/GTexture.java b/src/org/nschmidt/ldparteditor/data/GTexture.java index e0bee2c18..5bf4a66fb 100644 --- a/src/org/nschmidt/ldparteditor/data/GTexture.java +++ b/src/org/nschmidt/ldparteditor/data/GTexture.java @@ -15,6 +15,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package org.nschmidt.ldparteditor.data; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -763,7 +764,10 @@ private int loadPNGTexture(String filename, int textureUnit, DatFile datFile) { } if (in == null) { - if (fileExists) { + if (datFile.getBinaryData().hasFile(filename)) { + final byte[] source = datFile.getBinaryData().getFileBytes(filename); + in = new ByteArrayInputStream(source); + } else if (fileExists) { // Open the PNG file as an FileInputStream filename = fileToOpen.getAbsolutePath(); in = new FileInputStream(filename); diff --git a/src/org/nschmidt/ldparteditor/enumtype/DatKeyword.java b/src/org/nschmidt/ldparteditor/enumtype/DatKeyword.java index f0267f35a..5795ce266 100644 --- a/src/org/nschmidt/ldparteditor/enumtype/DatKeyword.java +++ b/src/org/nschmidt/ldparteditor/enumtype/DatKeyword.java @@ -31,6 +31,7 @@ public enum DatKeyword { keywords.add("!CATEGORY"); //$NON-NLS-1$ keywords.add("!CMDLINE"); //$NON-NLS-1$ keywords.add("!COLOUR"); //$NON-NLS-1$ + keywords.add("!DATA"); //$NON-NLS-1$ keywords.add("!HELP"); //$NON-NLS-1$ keywords.add("!HISTORY"); //$NON-NLS-1$ keywords.add("!KEYWORDS"); //$NON-NLS-1$ diff --git a/src/org/nschmidt/ldparteditor/helper/compositetext/DataMetacommandExporter.java b/src/org/nschmidt/ldparteditor/helper/compositetext/DataMetacommandExporter.java new file mode 100644 index 000000000..0d2850fd4 --- /dev/null +++ b/src/org/nschmidt/ldparteditor/helper/compositetext/DataMetacommandExporter.java @@ -0,0 +1,50 @@ +package org.nschmidt.ldparteditor.helper.compositetext; + +/* MIT - License + +Copyright (c) 2012 - this year, Nils Schmidt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +import org.nschmidt.ldparteditor.data.DatFile; +import org.nschmidt.ldparteditor.data.GData; +import org.nschmidt.ldparteditor.data.GDataBinary; +import org.nschmidt.ldparteditor.helper.math.HashBiMap; + +/** + * Exports !DATA meta-commands to a file (PNG only) + */ +public enum DataMetacommandExporter { + INSTANCE; + + /** + * Exports selected !DATA meta-commands to a file (PNG only) + * + * @param lineStart + * start line number to export + * @param lineEnd + * end line number to export + * @param datFile + */ + public static void export(int lineStart, int lineEnd, DatFile datFile) { + HashBiMap dpl = datFile.getDrawPerLineNoClone(); + lineEnd++; + for (int line = lineStart; line < lineEnd; line++) { + final GData data = dpl.getValue(line); + if (data instanceof GDataBinary gd) { + // TODO Needs implementation! + System.out.println(data); + } + } + } +} diff --git a/src/org/nschmidt/ldparteditor/i18n/I18n.java b/src/org/nschmidt/ldparteditor/i18n/I18n.java index 90fc546a1..776452437 100644 --- a/src/org/nschmidt/ldparteditor/i18n/I18n.java +++ b/src/org/nschmidt/ldparteditor/i18n/I18n.java @@ -758,6 +758,10 @@ private static void adjust() { // Calculate line offset public static final String EDITORTEXT_COMMENT = EDITORTEXT.getString(getProperty()); public static final String EDITORTEXT_COMPILE = EDITORTEXT.getString(getProperty()); public static final String EDITORTEXT_COMPILE_CSG = EDITORTEXT.getString(getProperty()); + public static final String EDITORTEXT_DATA_ERROR = EDITORTEXT.getString(getProperty()); + public static final String EDITORTEXT_DATA_EXPORT = EDITORTEXT.getString(getProperty()); + public static final String EDITORTEXT_DATA_IMPORT = EDITORTEXT.getString(getProperty()); + public static final String EDITORTEXT_DATA_NO_SELECTION = EDITORTEXT.getString(getProperty()); public static final String EDITORTEXT_DRAW_SELECTION = EDITORTEXT.getString(getProperty()); public static final String EDITORTEXT_DRAW_UNTIL_SELECTION = EDITORTEXT.getString(getProperty()); public static final String EDITORTEXT_DUPLICATE = EDITORTEXT.getString(getProperty()); diff --git a/src/org/nschmidt/ldparteditor/i18n/TextEditor.properties b/src/org/nschmidt/ldparteditor/i18n/TextEditor.properties index 3a3a4c451..948640db6 100644 --- a/src/org/nschmidt/ldparteditor/i18n/TextEditor.properties +++ b/src/org/nschmidt/ldparteditor/i18n/TextEditor.properties @@ -3,6 +3,10 @@ COLOUR_2 = Colour [{0}] COMMENT = Toggle Comment COMPILE = Update/Compile linked Subfile Data COMPILE_CSG = Optimize CSG Triangulation...\n(you can skip this) +DATA_ERROR = The file was not a valid PNG image. +DATA_EXPORT = Export !DATA as PNG-Image (secure) +DATA_IMPORT = Import PNG-Image as !DATA +DATA_NO_SELECTION = No single !DATA line was selected. Nothing to export. DRAW_SELECTION = Draw Selection DRAW_UNTIL_SELECTION = Draw Until Selection DUPLICATE = duplicate diff --git a/src/org/nschmidt/ldparteditor/text/DatParser.java b/src/org/nschmidt/ldparteditor/text/DatParser.java index cb794c6d9..b08998312 100644 --- a/src/org/nschmidt/ldparteditor/text/DatParser.java +++ b/src/org/nschmidt/ldparteditor/text/DatParser.java @@ -43,6 +43,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of import org.nschmidt.ldparteditor.data.GData4; import org.nschmidt.ldparteditor.data.GData5; import org.nschmidt.ldparteditor.data.GDataBFC; +import org.nschmidt.ldparteditor.data.GDataBinary; import org.nschmidt.ldparteditor.data.GDataCSG; import org.nschmidt.ldparteditor.data.GDataPNG; import org.nschmidt.ldparteditor.data.Matrix; @@ -268,6 +269,19 @@ private static List parseComment(String line, String[] dataSegmen } else { result.add(new ParsingResult(newLPEmetaTag)); } + } else if (line.startsWith("0 !DATA")) { //$NON-NLS-1$ + if (dataSegments.length <= 2) { + Object[] messageArguments = {dataSegments.length, 3}; + MessageFormat formatter = new MessageFormat(""); //$NON-NLS-1$ + formatter.setLocale(MyLanguage.getLocale()); + formatter.applyPattern(I18n.DATPARSER_WRONG_ARGUMENT_COUNT); + GData0 fallbackComment = new GData0(line, parent); + result.add(new ParsingResult(fallbackComment)); + result.add(new ParsingResult(formatter.format(messageArguments), "[E99] " + I18n.DATPARSER_SYNTAX_ERROR, ResultType.ERROR)); //$NON-NLS-1$ + } else if (!errorCheckOnly) { + GDataBinary newBinaryDataMetaTag = new GDataBinary(line, datFile, parent); + result.add(new ParsingResult(newBinaryDataMetaTag)); + } } else if (line.startsWith("0 !TEXMAP ")) { //$NON-NLS-1$ GData newLPEmetaTag = TexMapParser.parseTEXMAP(dataSegments, line, parent); if (newLPEmetaTag == null) { diff --git a/test/data_metacommand.txt b/test/data_metacommand.txt new file mode 100644 index 000000000..d921df4dc --- /dev/null +++ b/test/data_metacommand.txt @@ -0,0 +1,22 @@ +0 Testcase +0 Name: new.dat +0 Author: Nils Schmidt [BlackBrick89] +0 !LDRAW_ORG Unofficial_Part +0 !LICENSE Licensed under CC BY 2.0 and CC BY 4.0 : see CAreadme.txt + +0 BFC CERTIFY CCW + +0 !DATA sticker.png +0 !: iVBORw0KGgoAAAANSUhEUgAAAFAAAAB4CAIAAADqjOKhAAAAAXNSR0IArs4c6QAAAARnQU1BAACx +0 !: jwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEUSURBVHhe7du9DcIwFABhk5WgQLSsQM0UjMEU +0 !: 1BQsQIsoYAt6NkAYxQV/JQ7WvfuKkFTR6UmOFJzR9bJLkXTlNwyD6QymM5ju5Tl8m67KGUt3XJcz +0 !: J/yY8HZ/6C8BFvNZPoaesMF0BtMZTGcwncF0BtMZTGcwncF0BtMZTGcwnf8t0bmLh85gOoPpDKYz +0 !: mM5gOoPpDKYzmM5gunDBf3tN+/zqNKt367cbOeGUTstxf1nJZHPOx68T/u3XB5/7/zMXLTqD6Qym +0 !: M5jOYDqD6QymM5jOYDqD6QymM5jOYDqD6QymM5jOYLpwwW3t8ajBXTxtTHgwLlp0BtMZTGcwncF0 +0 !: BtMZTNfKZzyDiT3hCFy06IIFp3QH/CBMh66aBy4AAAAASUVORK5CYII= + +0 !TEXMAP START PLANAR 0 0 0 10 0 0 0 10 0 sticker.png +0 !: 4 16 0 0 0 10 0 0 10 10 0 0 10 0 +0 !TEXMAP FALLBACK +4 16 0 0 0 10 0 0 10 10 0 0 10 0 +0 !TEXMAP END