Added Apply unpacker menu on binary data

Added Harman unpacker for binary data
Added Multilevel binary data unpacking is possible
This commit is contained in:
Jindra Petřík
2023-11-23 19:43:42 +01:00
parent 8ad981ff25
commit 9741e8260a
23 changed files with 783 additions and 210 deletions

View File

@@ -122,6 +122,7 @@ import com.jpexs.decompiler.flash.tags.TagStub;
import com.jpexs.decompiler.flash.tags.VideoFrameTag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer;
import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface;
import com.jpexs.decompiler.flash.tags.base.BoundedTag;
import com.jpexs.decompiler.flash.tags.base.ButtonTag;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
@@ -357,7 +358,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
public DumpInfoSwfNode dumpInfo;
@Internal
public DefineBinaryDataTag binaryData;
public BinaryDataInterface binaryData;
@Internal
private final HashMap<DottedChain, DottedChain> deobfuscated = new HashMap<>();
@@ -1756,7 +1757,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
@Override
public String getShortPathTitle() {
if (binaryData != null) {
return binaryData.getSwf().getShortPathTitle() + "/DefineBinaryData (" + binaryData.getCharacterId() + ")";
return binaryData.getSwf().getShortPathTitle() + "/" + binaryData.getPathIdentifier();
}
if (openableList != null) {
if (openableList.isBundle()) {
@@ -1774,7 +1775,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
@Override
public String getFullPathTitle() {
if (binaryData != null) {
return binaryData.getSwf().getFullPathTitle() + "/DefineBinaryData (" + binaryData.getCharacterId() + ")";
return binaryData.getSwf().getFullPathTitle() + "/" + binaryData.getPathIdentifier();
}
if (openableList != null) {
if (openableList.isBundle()) {

View File

@@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.settings.BinaryDataExportSettings;
import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.Path;
import java.io.BufferedOutputStream;
@@ -44,69 +45,73 @@ import java.util.Set;
public class BinaryDataExporter {
public List<File> exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, ReadOnlyTagList tags, BinaryDataExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<BinaryDataInterface> binaryDatas = new ArrayList<>();
for (Tag t : tags) {
if (t instanceof BinaryDataInterface) {
binaryDatas.add((BinaryDataInterface) t);
}
}
return exportBinaryData(handler, outdir, binaryDatas, settings, evl);
}
public List<File> exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, List<BinaryDataInterface> binaryDatas, BinaryDataExportSettings settings, EventListener evl) throws IOException, InterruptedException {
List<File> ret = new ArrayList<>();
if (Thread.currentThread().isInterrupted()) {
return ret;
}
if (tags.isEmpty()) {
if (binaryDatas.isEmpty()) {
return ret;
}
File foutdir = new File(outdir);
Path.createDirectorySafe(foutdir);
int count = 0;
for (Tag t : tags) {
if (t instanceof DefineBinaryDataTag) {
count++;
}
}
int count = binaryDatas.size();
if (count == 0) {
return ret;
}
int currentIndex = 1;
for (final Tag t : tags) {
if (t instanceof DefineBinaryDataTag) {
DefineBinaryDataTag bdt = (DefineBinaryDataTag) t;
if (evl != null) {
evl.handleExportingEvent("binarydata", currentIndex, count, t.getName());
}
String ext = bdt.innerSwf == null ? ".bin" : ".swf";
final File file = new File(outdir + File.separator + Helper.makeFileName(bdt.getCharacterExportFileName() + ext));
new RetryTask(() -> {
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
fos.write(bdt.binaryData.getRangeData());
}
}, handler).run();
Set<String> classNames = bdt.getClassNames();
if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) {
for (String className : classNames) {
File classFile = new File(outdir + File.separator + Helper.makeFileName(className + ext));
new RetryTask(() -> {
Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}, handler).run();
ret.add(classFile);
}
file.delete();
} else {
ret.add(file);
}
if (Thread.currentThread().isInterrupted()) {
break;
}
if (evl != null) {
evl.handleExportedEvent("binarydata", currentIndex, count, t.getName());
}
currentIndex++;
for (final BinaryDataInterface t : binaryDatas) {
if (evl != null) {
evl.handleExportingEvent("binarydata", currentIndex, count, t.getName());
}
String ext = t.getInnerSwf() == null ? ".bin" : ".swf";
final File file = new File(outdir + File.separator + Helper.makeFileName(t.getCharacterExportFileName() + ext));
new RetryTask(() -> {
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
fos.write(t.getDataBytes().getRangeData());
}
}, handler).run();
DefineBinaryDataTag bdt = (DefineBinaryDataTag) t.getTopLevelBinaryData();
Set<String> classNames = bdt.getClassNames();
if (Configuration.as3ExportNamesUseClassNamesOnly.get() && !classNames.isEmpty()) {
for (String className : classNames) {
File classFile = new File(outdir + File.separator + Helper.makeFileName(t.getClassExportFileName(className) + ext));
new RetryTask(() -> {
Files.copy(file.toPath(), classFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}, handler).run();
ret.add(classFile);
}
file.delete();
} else {
ret.add(file);
}
if (Thread.currentThread().isInterrupted()) {
break;
}
if (evl != null) {
evl.handleExportedEvent("binarydata", currentIndex, count, t.getName());
}
currentIndex++;
}
return ret;

View File

@@ -32,25 +32,7 @@ import javax.crypto.spec.SecretKeySpec;
public class HarmanBinaryDataEncrypt {
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
private static final String[] hexStringCache;
static {
hexStringCache = new String[256];
for (int i = 0; i < hexStringCache.length; i++) {
hexStringCache[i] = String.format("%02x", i);
}
}
public static String byteArrayToHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(hexStringCache[b & 0xff]);
}
return sb.toString();
}
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
public static byte[] encrypt(byte[] data) {
byte[] result;
@@ -153,6 +135,9 @@ public class HarmanBinaryDataEncrypt {
}
public static byte[] decrypt(byte[] data) {
if (data.length < 32) {
return null;
}
long encryptedLen = data.length;
long encryptedLenXorHash = unpack(data, 0);
long decryptedLenXorRandom1 = unpack(data, 4);

View File

@@ -19,9 +19,6 @@ package com.jpexs.decompiler.flash.harman;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
@@ -44,26 +41,7 @@ import javax.crypto.spec.SecretKeySpec;
*/
public class HarmanSwfEncrypt {
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
private static final String[] hexStringCache;
static {
hexStringCache = new String[256];
for (int i = 0; i < hexStringCache.length; i++) {
hexStringCache[i] = String.format("%02x", i);
}
}
public static String byteArrayToHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
sb.append(hexStringCache[b & 0xff]);
}
return sb.toString();
}
private static final String GLOBAL_KEY = "Adobe AIR SDK (c) 2021 HARMAN Internation Industries Incorporated";
private static int sum(byte[] data) {
int s = 0;

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.packers;
import com.jpexs.decompiler.flash.harman.HarmanBinaryDataEncrypt;
import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
import com.jpexs.helpers.Helper;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
*
* @author JPEXS
*/
public class HarmanAirPacker implements Packer {
@Override
public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag) {
if (dataTag.binaryData.getLength() < 32) {
return false;
}
return null;
}
@Override
public Boolean suitableForData(byte[] data) {
if (data.length < 32) {
return false;
}
return null;
}
@Override
public boolean decrypt(InputStream is, OutputStream os) throws IOException {
byte[] encryptedData = Helper.readStream(is);
byte[] decryptedData = HarmanBinaryDataEncrypt.decrypt(encryptedData);
if (decryptedData == null) {
return false;
}
os.write(decryptedData);
return true;
}
@Override
public boolean encrypt(InputStream is, OutputStream os) throws IOException {
byte[] data = Helper.readStream(is);
byte[] encryptedData = HarmanBinaryDataEncrypt.encrypt(data);
if (encryptedData == null) {
return false;
}
os.write(encryptedData);
return true;
}
@Override
public String getName() {
return "Harman AIR SDK";
}
@Override
public String getIdentifier() {
return "harmanair";
}
}

View File

@@ -46,7 +46,12 @@ public class MochiCryptPacker implements Packer {
if (!handleXor(payload)) {
return false;
}
Helper.copyStream(new InflaterInputStream(new ByteArrayInputStream(payload)), os);
try {
Helper.copyStreamEx(new InflaterInputStream(new ByteArrayInputStream(payload)), os);
} catch (IOException ex) {
return false;
}
return true;
}
@@ -100,7 +105,7 @@ public class MochiCryptPacker implements Packer {
public boolean encrypt(InputStream is, OutputStream os) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DeflaterOutputStream def = new DeflaterOutputStream(baos);
Helper.copyStream(is, def);
Helper.copyStreamEx(is, def);
def.finish();
byte[] payload = baos.toByteArray();
@@ -117,4 +122,14 @@ public class MochiCryptPacker implements Packer {
public String getName() {
return "MochiCrypt";
}
@Override
public Boolean suitableForData(byte[] data) {
return null;
}
@Override
public String getIdentifier() {
return "mochicrypt";
}
}

View File

@@ -37,6 +37,16 @@ public interface Packer {
* work
*/
public Boolean suitableForBinaryData(DefineBinaryDataTag dataTag);
/**
* Is this data packed with this packer?
*
* @param data
* @return true = it definitely is encrypted with this, false = it
* definitely is not encrypted with this, null = it is unknown that it will
* work
*/
public Boolean suitableForData(byte[] data);
/**
* Unpack the data
@@ -65,4 +75,6 @@ public interface Packer {
* @return
*/
public String getName();
public String getIdentifier();
}

View File

@@ -22,9 +22,12 @@ import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.configuration.CustomConfigurationKeys;
import com.jpexs.decompiler.flash.configuration.SwfSpecificCustomConfiguration;
import com.jpexs.decompiler.flash.packers.HarmanAirPacker;
import com.jpexs.decompiler.flash.packers.MochiCryptPacker;
import com.jpexs.decompiler.flash.packers.Packer;
import com.jpexs.decompiler.flash.tags.base.BinaryDataInterface;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.PackedBinaryData;
import com.jpexs.decompiler.flash.types.BasicType;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.Reserved;
@@ -43,7 +46,7 @@ import java.nio.charset.Charset;
* @author JPEXS
*/
@SWFVersion(from = 9)
public class DefineBinaryDataTag extends CharacterTag {
public class DefineBinaryDataTag extends CharacterTag implements BinaryDataInterface {
public static final int ID = 87;
@@ -63,11 +66,19 @@ public class DefineBinaryDataTag extends CharacterTag {
@Internal
public Packer usedPacker;
@Internal
private PackedBinaryData sub;
private final Packer[] PACKERS = {
new MochiCryptPacker()
private static final Packer[] PACKERS = {
new MochiCryptPacker(),
new HarmanAirPacker()
};
public static Packer[] getAvailablePackers() {
return PACKERS;
}
/**
* Constructor
*
@@ -84,6 +95,25 @@ public class DefineBinaryDataTag extends CharacterTag {
readData(sis, data, 0, false, false, false);
}
public PackedBinaryData getSub() {
return sub;
}
@Override
public boolean unpack(Packer packer) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
if (!packer.decrypt(new ByteArrayInputStream(binaryData.getRangeData()), baos)) {
return false;
}
} catch (IOException ex) {
return false;
}
sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray()));
usedPacker = packer;
return true;
}
@Override
public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException {
tag = sis.readUI16("tag");
@@ -100,16 +130,16 @@ public class DefineBinaryDataTag extends CharacterTag {
InputStream is = new ByteArrayInputStream(binaryData.getArray(), binaryData.getPos(), binaryData.getLength());
detectPacker();
String packerAdd = "";
BinaryDataInterface binaryData = this;
if (usedPacker != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
usedPacker.decrypt(is, baos);
is = new ByteArrayInputStream(baos.toByteArray());
packerAdd = " - " + usedPacker.getName();
unpack(usedPacker);
is = new ByteArrayInputStream(sub.getDataBytes().getRangeData());
binaryData = sub;
}
SWF bswf = new SWF(is, null, "(SWF Data" + packerAdd + ")", Configuration.parallelSpeedUp.get(), charset);
innerSwf = bswf;
bswf.binaryData = this;
SWF bswf = new SWF(is, null, "(SWF Data)", Configuration.parallelSpeedUp.get(), charset);
binaryData.setInnerSwf(bswf);
bswf.binaryData = binaryData;
} catch (IOException | InterruptedException ex) {
// ignore
}
@@ -138,6 +168,7 @@ public class DefineBinaryDataTag extends CharacterTag {
this.tag = characterId;
}
@Override
public void detectPacker() {
for (Packer packer : PACKERS) {
if (packer.suitableForBinaryData(this) == Boolean.TRUE) {
@@ -147,6 +178,7 @@ public class DefineBinaryDataTag extends CharacterTag {
}
}
@Override
public boolean isSwfData() {
try {
if (binaryData.getLength() > 8) {
@@ -159,9 +191,7 @@ public class DefineBinaryDataTag extends CharacterTag {
//ignored
}
detectPacker();
return usedPacker != null;
return false;
}
@Override
@@ -174,4 +204,77 @@ public class DefineBinaryDataTag extends CharacterTag {
}
return false;
}
@Override
public Packer getUsedPacker() {
return usedPacker;
}
@Override
public void setDataBytes(ByteArrayRange data) {
this.binaryData = data;
}
@Override
public ByteArrayRange getDataBytes() {
return binaryData;
}
@Override
public boolean pack() {
if (sub == null) {
return false;
}
sub.pack();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) {
return false;
}
} catch (IOException ex) {
return false;
}
setDataBytes(new ByteArrayRange(baos.toByteArray()));
return true;
}
@Override
public void setInnerSwf(SWF swf) {
this.innerSwf = swf;
}
@Override
public SWF getInnerSwf() {
return this.innerSwf;
}
@Override
public String getPathIdentifier() {
return "DefineBinaryData (" + getCharacterId() + ")";
}
@Override
public String getStoragesPathIdentifier() {
return "binaryData[" + getCharacterId() + "]";
}
@Override
public BinaryDataInterface getTopLevelBinaryData() {
return this;
}
@Override
public void setModified(boolean value) {
super.setModified(value);
if (!value) {
if (sub != null) {
sub.setModified(false);
}
}
}
@Override
public String getClassExportFileName(String className) {
return className;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.packers.Packer;
import com.jpexs.decompiler.flash.treeitems.TreeItem;
import com.jpexs.helpers.ByteArrayRange;
/**
*
* @author JPEXS
*/
public interface BinaryDataInterface extends Exportable {
public PackedBinaryData getSub();
public boolean isSwfData();
public boolean unpack(Packer packer);
public void detectPacker();
public Packer getUsedPacker();
public void setDataBytes(ByteArrayRange data);
public ByteArrayRange getDataBytes();
public void setModified(boolean value);
public boolean pack();
public SWF getSwf();
public void setInnerSwf(SWF swf);
public SWF getInnerSwf();
public String getPathIdentifier();
public String getStoragesPathIdentifier();
public BinaryDataInterface getTopLevelBinaryData();
public String getCharacterExportFileName();
public String getName();
public String getClassExportFileName(String className);
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2010-2023 JPEXS, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library.
*/
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.packers.Packer;
import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag;
import com.jpexs.decompiler.flash.treeitems.Openable;
import com.jpexs.decompiler.flash.treeitems.TreeItem;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author JPEXS
*/
public class PackedBinaryData implements TreeItem, BinaryDataInterface {
private boolean modified = false;
private final SWF swf;
private final BinaryDataInterface parent;
private ByteArrayRange data;
private PackedBinaryData sub;
private Packer usedPacker;
private SWF innerSwf;
public PackedBinaryData(SWF swf, BinaryDataInterface parent, ByteArrayRange data) {
this.swf = swf;
this.parent = parent;
this.data = data;
}
@Override
public boolean unpack(Packer packer) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
if (!packer.decrypt(new ByteArrayInputStream(data.getRangeData()), baos)) {
return false;
}
} catch (IOException ex) {
return false;
}
sub = new PackedBinaryData(swf, this, new ByteArrayRange(baos.toByteArray()));
usedPacker = packer;
return true;
}
@Override
public PackedBinaryData getSub() {
return sub;
}
public BinaryDataInterface getParent() {
return parent;
}
@Override
public Openable getOpenable() {
return swf;
}
@Override
public SWF getSwf() {
return swf;
}
@Override
public void setModified(boolean value) {
modified = value;
if (value) {
parent.setModified(value);
} else {
if (sub != null) {
sub.setModified(false);
}
}
}
@Override
public boolean isModified() {
return modified;
}
@Override
public boolean isSwfData() {
try {
if (data.getLength() > 8) {
String signature = new String(data.getRangeData(0, 3), Utf8Helper.charset);
if (SWF.swfSignatures.contains(signature)) {
return true;
}
}
} catch (Exception ex) {
//ignored
}
return false;
}
@Override
public Packer getUsedPacker() {
return usedPacker;
}
@Override
public void detectPacker() {
for (Packer packer : DefineBinaryDataTag.getAvailablePackers()) {
if (packer.suitableForData(data.getRangeData()) == Boolean.TRUE) {
usedPacker = packer;
break;
}
}
}
@Override
public ByteArrayRange getDataBytes() {
return data;
}
@Override
public void setDataBytes(ByteArrayRange data) {
this.data = data;
setModified(true);
}
@Override
public boolean pack() {
if (sub == null) {
return false;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
if (!usedPacker.encrypt(new ByteArrayInputStream(sub.getDataBytes().getRangeData()), baos)) {
return false;
}
} catch (IOException ex) {
return false;
}
setDataBytes(new ByteArrayRange(baos.toByteArray()));
return true;
}
@Override
public String toString() {
return "(Data - " + parent.getUsedPacker().getName() + ")";
}
@Override
public void setInnerSwf(SWF swf) {
this.innerSwf = swf;
}
@Override
public SWF getInnerSwf() {
return this.innerSwf;
}
@Override
public String getPathIdentifier() {
return "(Data - " + parent.getUsedPacker().getName() + ")";
}
@Override
public String getStoragesPathIdentifier() {
return "data-" + parent.getUsedPacker().getIdentifier();
}
@Override
public BinaryDataInterface getTopLevelBinaryData() {
PackedBinaryData packed = this;
while (packed.parent instanceof PackedBinaryData) {
packed = (PackedBinaryData) packed.parent;
}
return packed.parent;
}
@Override
public String getExportFileName() {
List<String> parts = new ArrayList<>();
BinaryDataInterface binaryData = this;
while (binaryData instanceof PackedBinaryData) {
parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getIdentifier());
binaryData = ((PackedBinaryData) binaryData).parent;
}
parts.add(0, binaryData.getExportFileName());
return String.join("_", parts);
}
@Override
public String getCharacterExportFileName() {
List<String> parts = new ArrayList<>();
BinaryDataInterface binaryData = this;
while (binaryData instanceof PackedBinaryData) {
parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getIdentifier());
binaryData = ((PackedBinaryData) binaryData).parent;
}
parts.add(0, binaryData.getCharacterExportFileName());
return String.join("_", parts);
}
@Override
public String getName() {
List<String> parts = new ArrayList<>();
BinaryDataInterface binaryData = this;
while (binaryData instanceof PackedBinaryData) {
parts.add(0, ((PackedBinaryData) binaryData).parent.getUsedPacker().getName());
binaryData = ((PackedBinaryData) binaryData).parent;
}
parts.add(0, binaryData.getName());
return String.join(" / ", parts);
}
@Override
public String getClassExportFileName(String className) {
List<String> parts = new ArrayList<>();
BinaryDataInterface binaryData = this;
while (binaryData instanceof PackedBinaryData) {
parts.add(0, binaryData.getStoragesPathIdentifier());
binaryData = ((PackedBinaryData) binaryData).parent;
}
parts.add(0, binaryData.getClassExportFileName(className));
return String.join("_", parts);
}
}

View File

@@ -787,6 +787,15 @@ public class Helper {
return baos.toByteArray();
}
public static void copyStreamEx(InputStream is, OutputStream os) throws IOException {
final int bufSize = 4096;
byte[] buf = new byte[bufSize];
int cnt = 0;
while ((cnt = is.read(buf)) > 0) {
os.write(buf, 0, cnt);
}
}
public static void copyStream(InputStream is, OutputStream os) {
try {
final int bufSize = 4096;