From d8e569945b361cf2e431c850df06fb78df9514bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 17 Feb 2026 19:24:53 +0100 Subject: [PATCH] Fixed: StackOverflow on cyclic chyracters --- CHANGELOG.md | 1 + .../src/com/jpexs/decompiler/flash/SWF.java | 51 +++++++++++-------- .../decompiler/flash/tags/base/ButtonTag.java | 3 ++ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9414a674a..4ce15bdc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - Improper standalone layer termination, not cleaning temp files - [#2636] ActionScript - Incorrect always-break detection causing insertion of while(true) - ActionScript - newline after do..while +- StackOverflow on cyclic buttons ## [25.0.0] - 2026-02-10 ### Added diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index 867a9d042..3127d47cc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -697,7 +697,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * Lock for characters synchronization */ private final Object charactersLock = new Object(); - + /** * SHA 256 hash of original data */ @@ -705,7 +705,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { public String getHashSha256() { return hashSha256; - } + } public UninitializedClassFieldsDetector getUninitializedClassFieldsDetector() { return uninitializedClassFieldsDetector; @@ -1829,16 +1829,16 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { * @param includeImported Include imported characters * @throws IOException On I/O error */ - public void saveTo(OutputStream os, boolean gfx, boolean includeImported) throws IOException { + public void saveTo(OutputStream os, boolean gfx, boolean includeImported) throws IOException { checkCharset(); byte[] newUncompressedData = saveToByteArray(gfx, includeImported); - + try { hashSha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(newUncompressedData)); } catch (NoSuchAlgorithmException ex) { //ignore } - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); compress(new ByteArrayInputStream(newUncompressedData), baos, compression, lzmaProperties); byte[] newCompressedData = baos.toByteArray(); @@ -1846,7 +1846,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { encrypt(new ByteArrayInputStream(newCompressedData), os); } else { os.write(newCompressedData); - } + } } /** @@ -2316,7 +2316,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { public SWF(InputStream is, String file, String fileTitle, ProgressListener listener, boolean parallelRead, boolean checkOnly, boolean lazy, UrlResolver resolver, String charset, boolean allowRenameIdentifiers) throws IOException, InterruptedException { this.file = file; this.fileTitle = fileTitle; - this.charset = charset; + this.charset = charset; ByteArrayOutputStream baos = new ByteArrayOutputStream(); SWFHeader header = decompress(is, baos, true); gfx = header.gfx; @@ -2325,7 +2325,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { lzmaProperties = header.lzmaProperties; uncompressedData = baos.toByteArray(); originalUncompressedData = uncompressedData; - + try { hashSha256 = Helper.byteArrayToHex(MessageDigest.getInstance("SHA-256").digest(uncompressedData)); } catch (NoSuchAlgorithmException ex) { @@ -6247,6 +6247,25 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { return getRect(); } + private void getCyclicInTags(ReadOnlyTagList tags, Map> characterToNeeded) { + for (Tag t : tags) { + if (t instanceof CharacterTag) { + CharacterTag cht = (CharacterTag) t; + if (cht.getCharacterId() != -1) { + Set needed = new HashSet<>(); + Set neededClasses = new HashSet<>(); + cht.getNeededCharacters(needed, neededClasses, this); + //TODO: check cyclic classes + characterToNeeded.put(cht.getCharacterId(), needed); + } + } + if (t instanceof DefineSpriteTag) { + DefineSpriteTag sprite = (DefineSpriteTag) t; + getCyclicInTags(sprite.getTags(), characterToNeeded); + } + } + } + /** * Gets cyclic character ids. * @@ -6258,19 +6277,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { } Set ct = new HashSet<>(); Map> characterToNeeded = new HashMap<>(); - - for (Tag t : getTags()) { - if (t instanceof CharacterTag) { - CharacterTag cht = (CharacterTag) t; - if (cht.getCharacterId() != -1) { - Set needed = new HashSet<>(); - Set neededClasses = new HashSet<>(); - cht.getNeededCharacters(needed, neededClasses, this); - //TODO: check cyclic classes - characterToNeeded.put(cht.getCharacterId(), needed); - } - } - } + getCyclicInTags(readOnlyTags, characterToNeeded); for (int chid : characterToNeeded.keySet()) { for (int n : characterToNeeded.get(chid)) { @@ -6469,5 +6476,5 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { @Override public RECT getRectWithFilters() { return getRect(); - } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index 39a852fcf..d72ba9c2e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -413,6 +413,9 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { CharacterTag ch = null; if (chId != -1) { ch = swf.getCharacter(chId); + if (swf.getCyclicCharacters().contains(chId)) { + continue; + } } if (ch instanceof DrawableTag) { Dimension filterDimension = ((DrawableTag) ch).getFilterDimensions();