Added: #2427 Commandline export with use of imported SWFs (importAssets tag)

This commit is contained in:
Jindra Petřík
2025-03-23 12:23:51 +01:00
parent a992adb5c0
commit d47accdcfa
7 changed files with 266 additions and 7 deletions

View File

@@ -2,6 +2,9 @@
All notable changes to this project will be documented in this file.
## [Unreleased]
### Added
- [#2427] Commandline export with use of imported SWFs (importAssets tag)
### Fixed
- [#2424] DefineEditText handling of letterSpacing, font size on incorrect values
- [#2391] Double not operator in ternar operator expression
@@ -3691,6 +3694,7 @@ Major version of SWF to XML export changed to 2.
[alpha 9]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha8...alpha9
[alpha 8]: https://github.com/jindrapetrik/jpexs-decompiler/compare/alpha7...alpha8
[alpha 7]: https://github.com/jindrapetrik/jpexs-decompiler/releases/tag/alpha7
[#2427]: https://www.free-decompiler.com/flash/issues/2427
[#2424]: https://www.free-decompiler.com/flash/issues/2424
[#2391]: https://www.free-decompiler.com/flash/issues/2391
[#2375]: https://www.free-decompiler.com/flash/issues/2375

View File

@@ -2330,7 +2330,7 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
if (importedSwfs.containsKey(url)) {
iSwf = importedSwfs.get(url);
} else {
iSwf = resolver.resolveUrl(url);
iSwf = resolver.resolveUrl(this.file, url);
importedSwfs.put(url, iSwf);
}
if (iSwf != null) {

View File

@@ -16,6 +16,8 @@
*/
package com.jpexs.decompiler.flash;
import java.io.File;
/**
* URL resolver interface.
*
@@ -26,8 +28,9 @@ public interface UrlResolver {
/**
* Resolves URL to SWF object.
*
* @param basePath Base SWF path
* @param url URL
* @return SWF object or null if not valid
*/
public SWF resolveUrl(String url);
public SWF resolveUrl(String basePath, String url);
}

View File

@@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFCompression;
import com.jpexs.decompiler.flash.SearchMode;
import com.jpexs.decompiler.flash.SwfOpenException;
import com.jpexs.decompiler.flash.UrlResolver;
import com.jpexs.decompiler.flash.ValueTooLargeException;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.ABCInputStream;
@@ -228,6 +229,7 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -494,7 +496,9 @@ public class CommandLineArgumentParser {
}
AbortRetryIgnoreHandler handler = null;
UrlResolver urlResolver = new ConsoleUrlResolver(false, false, false, new HashMap<>());
Map<String, String> format = new HashMap<>();
Map<String, String> changedImports = new LinkedHashMap<>();
double zoom = 1;
String charset = Charset.defaultCharset().name();
boolean cliMode = false;
@@ -598,6 +602,12 @@ public class CommandLineArgumentParser {
}
Configuration._debugMode.set(true);
break;
case "-importassets":
urlResolver = parseImportAssets(args, changedImports);
break;
case "-changeimport":
parseChangeImport(args, changedImports);
break;
default:
break OUTER;
}
@@ -658,7 +668,7 @@ public class CommandLineArgumentParser {
} else if (command.equals("proxy")) {
parseProxy(args);
} else if (command.equals("export")) {
parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom, charset, exportEmbed, resampleWav, transparentBackground);
parseExport(selectionClasses, selection, selectionIds, args, handler, traceLevel, format, zoom, charset, exportEmbed, resampleWav, transparentBackground, urlResolver);
System.exit(0);
} else if (command.equals("compress")) {
parseCompress(args);
@@ -1731,6 +1741,43 @@ public class CommandLineArgumentParser {
System.err.println("Process affinity setting is only available on Windows platform.");
}
}
private static void parseChangeImport(Stack<String> args, Map<String, String> changedImports) {
if (args.size() < 2) {
System.err.println("source and target of import expected");
badArguments("changeimport");
}
changedImports.put(args.pop(), args.pop());
}
private static UrlResolver parseImportAssets(Stack<String> args, Map<String, String> changedImports) {
if (args.isEmpty()) {
System.err.println("importassets options expected");
badArguments("importassets");
}
String impOptionsStr = args.pop();
String[] impOptionsArr = impOptionsStr.split(",", -1);
boolean doResolve = false;
boolean doAsk = false;
boolean localOnly = false;
for (String impOption:impOptionsArr) {
switch (impOption) {
case "yes":
doResolve = true;
break;
case "ask":
doAsk = true;
break;
case "local":
localOnly = true;
break;
default:
System.err.println("incorrect importassets option: " + impOption);
badArguments("importassets");
}
}
return new ConsoleUrlResolver(doResolve, doAsk, localOnly, changedImports);
}
private static void parsePriority(Stack<String> args) {
if (Platform.isWindows()) {
@@ -1930,7 +1977,7 @@ public class CommandLineArgumentParser {
}
private static void parseExport(List<String> selectionClasses, Selection selection, Selection selectionIds, Stack<String> args, AbortRetryIgnoreHandler handler, Level traceLevel, Map<String, String> formats, double zoom, String charset, boolean exportEmbed, boolean resampleWav, boolean transparentBackground) {
private static void parseExport(List<String> selectionClasses, Selection selection, Selection selectionIds, Stack<String> args, AbortRetryIgnoreHandler handler, Level traceLevel, Map<String, String> formats, double zoom, String charset, boolean exportEmbed, boolean resampleWav, boolean transparentBackground, UrlResolver urlResolver) {
if (args.size() < 3) {
badArguments("export");
}
@@ -2021,11 +2068,11 @@ public class CommandLineArgumentParser {
startTimeSwf = System.currentTimeMillis();
System.out.println("Start exporting " + inFile.getName());
}
OpenableSourceInfo sourceInfo = new OpenableSourceInfo(null, inFile.getAbsolutePath(), inFile.getName());
SWF swf;
try {
swf = new SWF(new BufferedInputStream(new StdInAwareFileInputStream(inFile)), sourceInfo.getFile(), sourceInfo.getFileTitle(), null, Configuration.parallelSpeedUp.get(), false, true, charset);
swf = new SWF(new BufferedInputStream(new StdInAwareFileInputStream(inFile)), sourceInfo.getFile(), sourceInfo.getFileTitle(), null, Configuration.parallelSpeedUp.get(), false, true, urlResolver, charset);
} catch (FileNotFoundException | SwfOpenException ex) {
// FileNotFoundException when anti virus software blocks to open the file
logger.log(Level.SEVERE, "Failed to open swf: " + inFile.getName(), ex);

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2025 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.console;
import com.jpexs.decompiler.flash.OpenableSourceInfo;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.UrlResolver;
import com.jpexs.decompiler.flash.configuration.Configuration;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
/**
* Console Url resolver
* @author JPEXS
*/
public class ConsoleUrlResolver implements UrlResolver {
private final boolean doResolve;
private final boolean doAsk;
private final boolean localOnly;
private boolean ignoredInfoPrinted = false;
private Character toAll = null;
private Map<String, String> importSources = new HashMap<>();
/**
*
* @param doResolve Do resolve?
* @param doAsk Do ask?
* @param localOnly Local only?
* @param importSources Import sources
*/
public ConsoleUrlResolver(boolean doResolve, boolean doAsk, boolean localOnly, Map<String, String> importSources) {
this.doResolve = doResolve;
this.doAsk = doAsk;
this.localOnly = localOnly;
this.importSources = importSources;
}
@Override
public SWF resolveUrl(String basePath, String url) {
Character choice = toAll;
String currentUrl = url;
if (importSources.containsKey(url)) {
currentUrl = importSources.get(url);
if (currentUrl.isEmpty()) {
return null;
}
choice = 'y';
} else {
if (choice == null && doResolve) {
choice = 'y';
}
if (!doResolve && !doAsk) {
if (!ignoredInfoPrinted) {
System.out.println("WARNING: SWF tried to import assets from external SWF, the request was ignored. (See --help -importAssets for more options)");
}
ignoredInfoPrinted = true;
return null;
}
if (choice == null && doAsk) {
choice = ask(url);
}
}
while(choice != 'n') {
if (choice == 'c') {
System.out.println("Enter new location instead (empty to cancel):");
Scanner sc = new Scanner(System.in);
String newUrl = sc.nextLine();
importSources.put(url, newUrl);
currentUrl = newUrl;
if (currentUrl.isEmpty()) {
return null;
}
}
if (currentUrl.startsWith("http://") || currentUrl.startsWith("https://")) {
if (localOnly && choice != 'c') {
return null;
}
try {
URL u = new URL(currentUrl);
SWF ret = open(u.openStream(), null, currentUrl); //?
return ret;
} catch (Exception ex) {
//ignore
}
} else {
File swf = new File(new File(basePath).getParentFile(), currentUrl);
if (swf.exists()) {
try {
SWF ret = open(new FileInputStream(swf), swf.getAbsolutePath(), swf.getName());
return ret;
} catch (Exception ex) {
//ignore
}
}
// try .gfx if .swf failed
if (currentUrl.endsWith(".swf")) {
File gfx = new File(new File(basePath).getParentFile(), url.substring(0, url.length() - 4) + ".gfx");
if (gfx.exists()) {
try {
SWF ret = open(new FileInputStream(gfx), gfx.getAbsolutePath(), gfx.getName());
return ret;
} catch (Exception ex) {
//ignore
}
}
}
}
System.err.println("Cannot load imported SWF " + currentUrl);
if (doAsk) {
choice = 'c';
} else {
choice = 'n';
}
}
return null;
}
private SWF open(InputStream is, String file, String fileTitle) throws Exception {
OpenableSourceInfo sourceInfo = new OpenableSourceInfo(is, file, fileTitle);
return new SWF(new BufferedInputStream(is), sourceInfo.getFile(), sourceInfo.getFileTitle(), null, Configuration.parallelSpeedUp.get(), false, true, this, null /*??*/);
}
private String prompt() {
Scanner sc = new Scanner(System.in);
String s;
System.out.print("Select action: (Y)es, (N)o, (C)ustom, (YA) Yes to all, (NA) No to all: ");
s = sc.nextLine();
s = s.toLowerCase(Locale.ENGLISH);
return s;
}
private char ask(String url) {
String s;
if (toAll != null) {
s = "" + toAll;
} else {
System.out.println("The SWF file is trying to import assets from the following location:");
System.out.println(url);
s = prompt();
}
while (true) {
switch (s) {
case "ya":
toAll = 'y';
return 'y';
case "na":
toAll = 'n';
return 'n';
case "y":
return 'y';
case "n":
return 'n';
case "c":
return 'c';
}
System.out.println("Invalid action.");
s = prompt();
}
}
}

View File

@@ -444,3 +444,17 @@ Pre-options:
Ignores SWF background color when exporting frames
and thus make background transparent.
-importAssets <importOptions>
Applies to: -export
Specifies options for importAssets tag - using external SWFs.
<importOptions> format: (combination of yes,ask,local)
yes = use external SWFs when available
ask = ask whether to use external SWFs
yes,ask = use external SWFs, ask for alternatives when not available
yes,local = ignores URLs - SWFs starting with http/s:// prefix
-changeImport <source> <target>
Applies to: -export
Replaces <source> URL in importAssets tag with <target>
for purposes of export. Can be used multiple times.

View File

@@ -1235,7 +1235,7 @@ public class Main {
}
}, Configuration.parallelSpeedUp.get(), false, true, new UrlResolver() {
@Override
public SWF resolveUrl(final String url) {
public SWF resolveUrl(String file, final String url) {
loadedUrls.add(url);
File selFile = null;
int opt = -1;