From 2b84014a01e46bab329eb1dad5d7da260f6e6829 Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Mon, 30 Mar 2026 23:54:38 +0200 Subject: [PATCH] Fix linked sound export in Animate and SWFLite paths --- scripts/Tools.hx | 10 +- src/swf/exporters/AnimateLibraryExporter.hx | 447 +++++++++++++++--- src/swf/exporters/SWFLiteExporter.hx | 310 +++++++++++- src/swf/exporters/animate/AnimateLibrary.hx | 101 +++- .../exporters/animate/AnimateSoundSymbol.hx | 15 + src/swf/exporters/swflite/SWFLiteLoader.hx | 5 +- 6 files changed, 789 insertions(+), 99 deletions(-) create mode 100644 src/swf/exporters/animate/AnimateSoundSymbol.hx diff --git a/scripts/Tools.hx b/scripts/Tools.hx index 9a3d0fb..45268fd 100644 --- a/scripts/Tools.hx +++ b/scripts/Tools.hx @@ -760,8 +760,11 @@ class Tools var bytes:ByteArray = File.getBytes(sourcePath); bytes = readSWC(bytes); + var sourceBytes = new ByteArray(); + sourceBytes.writeBytes(bytes, 0, bytes.length); + sourceBytes.position = 0; var swf = new SWF(bytes); - var exporter = new AnimateLibraryExporter(swf.data, targetPath); + var exporter = new AnimateLibraryExporter(swf.data, targetPath, sourceBytes); if (generate) { @@ -1050,8 +1053,11 @@ class Tools var bytes:ByteArray = File.getBytes(library.sourcePath); bytes = readSWC(bytes); + var sourceBytes = new ByteArray(); + sourceBytes.writeBytes(bytes, 0, bytes.length); + sourceBytes.position = 0; var swf = new SWF(bytes); - var exporter = new AnimateLibraryExporter(swf.data, cacheFile); + var exporter = new AnimateLibraryExporter(swf.data, cacheFile, sourceBytes); if (library.generate != false) { diff --git a/src/swf/exporters/AnimateLibraryExporter.hx b/src/swf/exporters/AnimateLibraryExporter.hx index f36ebe7..d358644 100644 --- a/src/swf/exporters/AnimateLibraryExporter.hx +++ b/src/swf/exporters/AnimateLibraryExporter.hx @@ -6,6 +6,7 @@ import swf.data.consts.BitmapFormat; import swf.data.consts.BlendMode; import swf.data.filters.IFilter; import swf.data.SWFButtonRecord; +import swf.data.SWFRawTag; import swf.data.SWFSymbol; import swf.exporters.ShapeBitmapExporter; import swf.exporters.ShapeBitmapExporter.BitmapFill; @@ -26,11 +27,14 @@ import swf.tags.TagDefineShape; import swf.tags.TagDefineSound; import swf.tags.TagDefineSprite; import swf.tags.TagDefineText; +import swf.tags.TagExportAssets; +import swf.tags.TagNameCharacter; import swf.tags.TagPlaceObject; import swf.tags.TagSymbolClass; import swf.timeline.Frame; import swf.utils.SymbolUtils; import swf.SWFRoot; +import swf.SWFData; import swf.SWFTimelineContainer; import haxe.Template; import hxp.Haxelib; @@ -50,6 +54,7 @@ import openfl.text.TextFormatAlign; import openfl.utils.AssetManifest; import openfl.utils.AssetType; import openfl.utils.ByteArray; +import openfl.utils.Endian; import haxe.crypto.Crc32; import haxe.io.Bytes; import haxe.io.BytesOutput; @@ -71,7 +76,7 @@ class AnimateLibraryExporter private var symbolsByTagID:Map; private var targetPath:String; - public function new(swfData:SWFRoot, targetPath:String) + public function new(swfData:SWFRoot, targetPath:String, sourceBytes:ByteArray = null) { this.swfData = swfData; this.targetPath = targetPath; @@ -79,16 +84,35 @@ class AnimateLibraryExporter symbols = []; symbolsByTagID = new Map(); - for (tag in swfData.tags) + var registerSymbol = function(symbol:SWFSymbol) { - if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagSymbolClass)) + if (symbol == null || symbol.name == null || symbol.name == "") + { + return; + } + + var replaced = false; + for (i in 0...symbols.length) { - for (symbol in cast(tag, TagSymbolClass).symbols) + if (symbols[i].tagId == symbol.tagId) { - symbols.push(symbol); - symbolsByTagID.set(symbol.tagId, symbol); + symbols[i] = symbol; + replaced = true; + break; } } + + if (!replaced) + { + symbols.push(symbol); + } + + symbolsByTagID.set(symbol.tagId, symbol); + } + + for (symbol in collectLinkageSymbols(sourceBytes)) + { + registerSymbol(symbol); } manifestData = new AssetManifest(); @@ -277,6 +301,230 @@ class AnimateLibraryExporter return symbol; } + private function extractNameCharacterSymbol(tag:TagNameCharacter):SWFSymbol + { + if (tag == null || tag.binaryData == null || tag.binaryData.length == 0) + { + return null; + } + + tag.binaryData.position = 0; + var name = tag.binaryData.readUTFBytes(tag.binaryData.length - 1); + tag.binaryData.position = 0; + return SWFSymbol.create(tag.characterId, name); + } + + private function collectLinkageSymbols(sourceBytes:ByteArray):Array + { + var result:Array = []; + + for (tag in swfData.tags) + { + appendSymbols(result, extractTagSymbols(tag)); + } + + for (tagRaw in swfData.tagsRaw) + { + appendSymbols(result, extractRawTagSymbols(tagRaw)); + } + + if (sourceBytes != null) + { + appendSymbols(result, extractLinkageSymbols(sourceBytes)); + } + + return result; + } + + private function extractTagSymbols(tag:Dynamic):Array + { + if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagSymbolClass)) + { + return cast(tag, TagSymbolClass).symbols.copy(); + } + else if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagExportAssets)) + { + return cast(tag, TagExportAssets).symbols.copy(); + } + else if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagNameCharacter)) + { + var symbol = extractNameCharacterSymbol(cast tag); + return symbol != null ? [symbol] : []; + } + + return []; + } + + private function extractRawTagSymbols(tagRaw:SWFRawTag):Array + { + switch (tagRaw.header.type) + { + case TagSymbolClass.TYPE, TagExportAssets.TYPE: + return extractRawAssetSymbols(tagRaw); + + case TagNameCharacter.TYPE: + var symbol = extractRawNameCharacterSymbol(tagRaw); + return symbol != null ? [symbol] : []; + + default: + return []; + } + } + + private function appendSymbols(target:Array, values:Array):Void + { + if (values == null || values.length == 0) + { + return; + } + + for (value in values) + { + if (value != null) + { + target.push(value); + } + } + } + + private function extractRawAssetSymbols(tagRaw:SWFRawTag):Array + { + var data = cloneRawTagData(tagRaw); + if (data == null) + { + return []; + } + + data.readTagHeader(); + var count = data.readUI16(); + var result:Array = []; + for (i in 0...count) + { + result.push(new SWFSymbol(data)); + } + return result; + } + + private function extractRawNameCharacterSymbol(tagRaw:SWFRawTag):SWFSymbol + { + var data = cloneRawTagData(tagRaw); + if (data == null) + { + return null; + } + + data.readTagHeader(); + var characterId = data.readUI16(); + var name = data.readSTRING(); + return name != null && name != "" ? SWFSymbol.create(characterId, name) : null; + } + + private function cloneRawTagData(tagRaw:SWFRawTag):SWFData + { + if (tagRaw == null || tagRaw.bytes == null) + { + return null; + } + + var data = new SWFData(); + data.writeBytes(tagRaw.bytes); + data.position = 0; + return data; + } + + private function extractLinkageSymbols(sourceBytes:ByteArray):Array + { + var data = getUncompressedSWFData(sourceBytes); + var result:Array = []; + if (data == null) + { + return result; + } + + data.position = 8; + data.readRECT(); + data.readFIXED8(); + data.readUI16(); + + while (data.position + 1 < data.length) + { + var header = data.readTagHeader(); + if (header.type == 0) + { + break; + } + var tagEnd = data.position + header.contentLength; + + switch (header.type) + { + case TagSymbolClass.TYPE, TagExportAssets.TYPE: + var count = data.readUI16(); + for (i in 0...count) + { + result.push(new SWFSymbol(data)); + } + + case TagNameCharacter.TYPE: + var characterId = data.readUI16(); + var name = data.readSTRING(); + if (name != null && name != "") + { + result.push(SWFSymbol.create(characterId, name)); + } + + default: + } + + data.position = tagEnd; + } + + return result; + } + + private function getUncompressedSWFData(sourceBytes:ByteArray):SWFData + { + if (sourceBytes == null || sourceBytes.length < 8) + { + return null; + } + + var input = new ByteArray(); + input.writeBytes(sourceBytes, 0, sourceBytes.length); + input.position = 0; + + var signature = input.readUTFBytes(3); + var version = input.readUnsignedByte(); + var fileLength = input.readUnsignedInt(); + + var body = new ByteArray(); + body.writeBytes(input, 8, input.length - 8); + body.position = 0; + + switch (signature) + { + case "FWS": + // already uncompressed + + case "CWS": + body.uncompress(); + + case "ZWS": + Log.warn("LZMA-compressed SWF linkages are not currently supported for audio export"); + return null; + + default: + return null; + } + + var data = new SWFData(); + data.writeUTFBytes("FWS"); + data.writeByte(version); + data.writeUI32(fileLength); + data.writeBytes(body, 0, body.length); + data.position = 0; + return data; + } + private function addBitmap(tag:IDefinitionTag):Dynamic { var alphaByteArray = null; @@ -1056,74 +1304,140 @@ class AnimateLibraryExporter return symbol; } - private function addSound(tag:IDefinitionTag):Void + private function addSound(tag:IDefinitionTag):Dynamic { if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagDefineSound)) { var defineSound:TagDefineSound = cast tag; - - var byteArray = defineSound.soundData; - var type:SoundType = switch (defineSound.soundFormat) + var soundType = getSoundType(defineSound.soundFormat); + + var export = exportSound(defineSound); + if (export == null) { - case 0: SoundType.UNCOMPRESSED_NATIVE_ENDIAN; - case 1: SoundType.ADPCM; - case 2: SoundType.MP3; - case 3: SoundType.UNCOMPRESSED_LITTLE_ENDIAN; - case 4: SoundType.NELLYMOSER_16_KHZ; - case 5: SoundType.NELLYMOSER_8_KHZ; - case 6: SoundType.NELLYMOSER; - case 7: SoundType.SPEEX; - case _: throw("invalid sound type!"); + var soundTypeName = soundType != null ? Std.string(soundType) : "UNKNOWN"; + Log.warn('Skipping unsupported DefineSound id=${defineSound.characterId} format=${defineSound.soundFormat} type=${soundTypeName}'); + return null; } - // TODO - - // var entry:Entry = { - // fileName: symbol.path, - // fileSize: byteArray.length, - // fileTime: Date.now(), - // compressed: false, - // dataSize: 0, - // data: byteArray, - // crc32: Crc32.make(byteArray) - // }; - // outputList.add(entry); - - // createdDirectory = false; - // for (id in exporter.sounds.keys()) - // { - // if (!createdDirectory) - // { - // System.mkdir(Path.combine(targetPath, "sounds")); - // createdDirectory = true; - // } - - // var symbolClassName = exporter.soundSymbolClassNames.get(id); - // var typeId = exporter.soundTypes.get(id); - - // Log.info("", " - \x1b[1mExporting sound:\x1b[0m [id=" + id + ", type=" + typeId + ", symbolClassName=" + symbolClassName + "]"); - - // var type; - // switch (typeId) - // { - // case SoundType.MP3: - // type = "mp3"; - // case SoundType.ADPCM: - // type = "adpcm"; - // case _: - // throw "unsupported sound type " + id + ", type " + typeId + ", symbol class name " + symbolClassName; - // }; - // var path = "sounds/" + symbolClassName + "." + type; - // var assetData = exporter.sounds.get(id); - - // File.saveBytes(Path.combine(targetPath, path), assetData); - - // // NOTICE: everything must be .mp3 in its final form, even though we write out various formats to disk - // var soundAsset = new Asset("", "sounds/" + symbolClassName + ".mp3", AssetType.SOUND); - // project.assets.push(soundAsset); + var symbol:Dynamic = {}; + symbol.type = SWFSymbolType.SOUND; + symbol.id = defineSound.characterId; + symbol.path = "sounds/" + symbol.id + "." + export.extension; + + var entry:Entry = { + fileName: symbol.path, + fileSize: export.bytes.length, + fileTime: Date.now(), + compressed: false, + dataSize: 0, + data: export.bytes, + crc32: Crc32.make(export.bytes) + }; + outputList.add(entry); + + manifestData.assets.push({ + path: symbol.path, + type: AssetType.SOUND + }); + + libraryData.symbols.set(symbol.id, symbol); + return symbol; } - return; + return null; + } + + private function exportSound(defineSound:TagDefineSound):{bytes:ByteArray, extension:String} + { + // Not all SWF DefineSound compression modes are implemented yet. + // For now, only formats that map directly to standard container formats + // are exported. + switch (getSoundType(defineSound.soundFormat)) + { + case MP3: + var mp3 = new ByteArray(); + if (defineSound.soundData.length > 2) + { + mp3.writeBytes(defineSound.soundData, 2, defineSound.soundData.length - 2); + } + mp3.position = 0; + return {bytes: mp3, extension: "mp3"}; + + case UNCOMPRESSED_NATIVE_ENDIAN, UNCOMPRESSED_LITTLE_ENDIAN: + return {bytes: buildWAVFromPCM(defineSound), extension: "wav"}; + + case ADPCM, NELLYMOSER_16_KHZ, NELLYMOSER_8_KHZ, NELLYMOSER, SPEEX, null: + return null; + } + } + + private function getSoundType(soundFormat:Int):Null + { + return switch (soundFormat) + { + case 0: SoundType.UNCOMPRESSED_NATIVE_ENDIAN; + case 1: SoundType.ADPCM; + case 2: SoundType.MP3; + case 3: SoundType.UNCOMPRESSED_LITTLE_ENDIAN; + case 4: SoundType.NELLYMOSER_16_KHZ; + case 5: SoundType.NELLYMOSER_8_KHZ; + case 6: SoundType.NELLYMOSER; + case 11: SoundType.SPEEX; + case _: null; + } + } + + private function buildWAVFromPCM(defineSound:TagDefineSound):ByteArray + { + var pcmData = new ByteArray(); + pcmData.writeBytes(defineSound.soundData, 0, defineSound.soundData.length); + pcmData.position = 0; + + var channels = getSoundChannels(defineSound.soundType); + var sampleRate = getSoundSampleRate(defineSound.soundRate); + var bitsPerSample = getSoundBitsPerSample(defineSound.soundSize); + var blockAlign = channels * Std.int(bitsPerSample / 8); + var byteRate = sampleRate * blockAlign; + var wav = new ByteArray(); + wav.endian = Endian.LITTLE_ENDIAN; + wav.writeUTFBytes("RIFF"); + wav.writeInt(36 + pcmData.length); + wav.writeUTFBytes("WAVE"); + wav.writeUTFBytes("fmt "); + wav.writeInt(16); + wav.writeShort(1); + wav.writeShort(channels); + wav.writeInt(sampleRate); + wav.writeInt(byteRate); + wav.writeShort(blockAlign); + wav.writeShort(bitsPerSample); + wav.writeUTFBytes("data"); + wav.writeInt(pcmData.length); + wav.writeBytes(pcmData, 0, pcmData.length); + wav.position = 0; + return wav; + } + + private function getSoundBitsPerSample(soundSize:Int):Int + { + return soundSize == swf.data.consts.SoundSize.BIT_16 ? 16 : 8; + } + + private function getSoundChannels(soundType:Int):Int + { + return soundType == swf.data.consts.SoundType.STEREO ? 2 : 1; + } + + private function getSoundSampleRate(soundRate:Int):Int + { + return switch (soundRate) + { + case swf.data.consts.SoundRate.KHZ_5: 5512; + case swf.data.consts.SoundRate.KHZ_11: 11025; + case swf.data.consts.SoundRate.KHZ_22: 22050; + case swf.data.consts.SoundRate.KHZ_44: 44100; + case _: 44100; + } } public function generateClasses(targetPath:String, output:Array, prefix:String = ""):Array @@ -1362,7 +1676,7 @@ class AnimateLibraryExporter } else if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagDefineSound)) { - addSound(tag); + return addSound(tag); } return null; @@ -1546,6 +1860,7 @@ private #if (haxe_ver >= 4.0) enum #end abstract SWFSymbolType(Int) from Int to public var SHAPE = 4; public var SPRITE = 5; public var STATIC_TEXT = 6; + public var SOUND = 7; } #if (haxe_ver < 4.0) @:enum #end diff --git a/src/swf/exporters/SWFLiteExporter.hx b/src/swf/exporters/SWFLiteExporter.hx index 224c70d..90fd48e 100644 --- a/src/swf/exporters/SWFLiteExporter.hx +++ b/src/swf/exporters/SWFLiteExporter.hx @@ -8,15 +8,12 @@ import format.png.Writer; import swf.data.consts.BitmapFormat; import swf.data.consts.BlendMode; import swf.data.SWFButtonRecord; +import swf.data.SWFRawTag; import swf.exporters.core.FilterType; import swf.exporters.core.ShapeCommand; import swf.exporters.ShapeBitmapExporter.BitmapFill; import swf.runtime.Bitmap; -// #if hxp -// import hxp.Log; -// #else -// import lime.tools.helpers.LogHelper in Log; -// #end +import hxp.Log; import lime.graphics.Image; import swf.exporters.swflite.BitmapSymbol; import swf.exporters.swflite.ButtonSymbol; @@ -45,8 +42,11 @@ import swf.tags.TagDefineScalingGrid; import swf.tags.TagDefineShape; import swf.tags.TagDefineSprite; import swf.tags.TagDefineText; +import swf.tags.TagExportAssets; +import swf.tags.TagNameCharacter; import swf.tags.TagPlaceObject; import swf.tags.TagSymbolClass; +import swf.SWFData; import swf.SWFRoot; import swf.SWFTimelineContainer; import haxe.io.Bytes; @@ -74,7 +74,7 @@ class SWFLiteExporter private var data:SWFRoot; private var symbolsByTagID:Map; - public function new(data:SWFRoot) + public function new(data:SWFRoot, sourceBytes:ByteArray = null) { this.data = data; @@ -92,19 +92,267 @@ class SWFLiteExporter addSprite(data, true); - for (tag in data.tags) + var symbols:Array = []; + var registerSymbol = function(symbol:swf.data.SWFSymbol) { - if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagSymbolClass)) + if (symbol == null || symbol.name == null || symbol.name == "") { - for (symbol in cast(tag, TagSymbolClass).symbols) + return; + } + + var replaced = false; + for (i in 0...symbols.length) + { + if (symbols[i].tagId == symbol.tagId) { - processSymbol(symbol); - symbolsByTagID.set(symbol.tagId, symbol); + symbols[i] = symbol; + replaced = true; + break; } } + + if (!replaced) + { + symbols.push(symbol); + } + + symbolsByTagID.set(symbol.tagId, symbol); + } + + for (symbol in collectLinkageSymbols(sourceBytes)) + { + registerSymbol(symbol); + } + + for (symbol in symbols) + { + processSymbol(symbol); } } + private function extractNameCharacterSymbol(tag:TagNameCharacter):swf.data.SWFSymbol + { + if (tag == null || tag.binaryData == null || tag.binaryData.length == 0) + { + return null; + } + + tag.binaryData.position = 0; + var name = tag.binaryData.readUTFBytes(tag.binaryData.length - 1); + tag.binaryData.position = 0; + return swf.data.SWFSymbol.create(tag.characterId, name); + } + + private function collectLinkageSymbols(sourceBytes:ByteArray):Array + { + var result:Array = []; + + for (tag in data.tags) + { + appendSymbols(result, extractTagSymbols(tag)); + } + + for (tagRaw in data.tagsRaw) + { + appendSymbols(result, extractRawTagSymbols(tagRaw)); + } + + if (sourceBytes != null) + { + appendSymbols(result, extractLinkageSymbols(sourceBytes)); + } + + return result; + } + + private function extractTagSymbols(tag:Dynamic):Array + { + if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagSymbolClass)) + { + return cast(tag, TagSymbolClass).symbols.copy(); + } + else if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagExportAssets)) + { + return cast(tag, TagExportAssets).symbols.copy(); + } + else if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagNameCharacter)) + { + var symbol = extractNameCharacterSymbol(cast tag); + return symbol != null ? [symbol] : []; + } + + return []; + } + + private function extractRawTagSymbols(tagRaw:SWFRawTag):Array + { + switch (tagRaw.header.type) + { + case TagSymbolClass.TYPE, TagExportAssets.TYPE: + return extractRawAssetSymbols(tagRaw); + + case TagNameCharacter.TYPE: + var symbol = extractRawNameCharacterSymbol(tagRaw); + return symbol != null ? [symbol] : []; + + default: + return []; + } + } + + private function appendSymbols(target:Array, values:Array):Void + { + if (values == null || values.length == 0) + { + return; + } + + for (value in values) + { + if (value != null) + { + target.push(value); + } + } + } + + private function extractRawAssetSymbols(tagRaw:SWFRawTag):Array + { + var data = cloneRawTagData(tagRaw); + if (data == null) + { + return []; + } + + data.readTagHeader(); + var count = data.readUI16(); + var result:Array = []; + for (i in 0...count) + { + result.push(new swf.data.SWFSymbol(data)); + } + return result; + } + + private function extractRawNameCharacterSymbol(tagRaw:SWFRawTag):swf.data.SWFSymbol + { + var data = cloneRawTagData(tagRaw); + if (data == null) + { + return null; + } + + data.readTagHeader(); + var characterId = data.readUI16(); + var name = data.readSTRING(); + return name != null && name != "" ? swf.data.SWFSymbol.create(characterId, name) : null; + } + + private function cloneRawTagData(tagRaw:SWFRawTag):SWFData + { + if (tagRaw == null || tagRaw.bytes == null) + { + return null; + } + + var data = new SWFData(); + data.writeBytes(tagRaw.bytes); + data.position = 0; + return data; + } + + private function extractLinkageSymbols(sourceBytes:ByteArray):Array + { + var data = getUncompressedSWFData(sourceBytes); + var result:Array = []; + if (data == null) + { + return result; + } + + data.position = 8; + data.readRECT(); + data.readFIXED8(); + data.readUI16(); + + while (data.position + 1 < data.length) + { + var header = data.readTagHeader(); + if (header.type == 0) + { + break; + } + var tagEnd = data.position + header.contentLength; + + switch (header.type) + { + case TagSymbolClass.TYPE, TagExportAssets.TYPE: + var count = data.readUI16(); + for (i in 0...count) + { + result.push(new swf.data.SWFSymbol(data)); + } + + case TagNameCharacter.TYPE: + var characterId = data.readUI16(); + var name = data.readSTRING(); + if (name != null && name != "") + { + result.push(swf.data.SWFSymbol.create(characterId, name)); + } + + default: + } + + data.position = tagEnd; + } + + return result; + } + + private function getUncompressedSWFData(sourceBytes:ByteArray):SWFData + { + if (sourceBytes == null || sourceBytes.length < 8) + { + return null; + } + + var input = new ByteArray(); + input.writeBytes(sourceBytes, 0, sourceBytes.length); + input.position = 0; + + var signature = input.readUTFBytes(3); + var version = input.readUnsignedByte(); + var fileLength = input.readUnsignedInt(); + + var body = new ByteArray(); + body.writeBytes(input, 8, input.length - 8); + body.position = 0; + + switch (signature) + { + case "FWS": + // already uncompressed + + case "CWS": + body.uncompress(); + + case "ZWS": + Log.warn("LZMA-compressed SWF linkages are not currently supported for SWFLite export"); + return null; + default: + return null; + } + + var data = new SWFData(); + data.writeUTFBytes("FWS"); + data.writeByte(version); + data.writeUI32(fileLength); + data.writeBytes(body, 0, body.length); + data.position = 0; + return data; + } + private function addButton(tag:IDefinitionTag):SWFSymbol { var symbol = new ButtonSymbol(); @@ -820,30 +1068,40 @@ class SWFLiteExporter return symbol; } - private function addSound(tag:IDefinitionTag):Void + private function addSound(tag:IDefinitionTag):Bool { if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (tag, TagDefineSound)) { var defineSound:TagDefineSound = cast tag; - var byteArray = defineSound.soundData; - var type:SoundType = switch (defineSound.soundFormat) + var type = getSoundType(defineSound.soundFormat); + if (type == null) { - case 0: SoundType.UNCOMPRESSED_NATIVE_ENDIAN; - case 1: SoundType.ADPCM; - case 2: SoundType.MP3; - case 3: SoundType.UNCOMPRESSED_LITTLE_ENDIAN; - case 4: SoundType.NELLYMOSER_16_KHZ; - case 5: SoundType.NELLYMOSER_8_KHZ; - case 6: SoundType.NELLYMOSER; - case 7: SoundType.SPEEX; - case _: throw("invalid sound type!"); + Log.warn('Skipping unsupported DefineSound id=${defineSound.characterId} format=${defineSound.soundFormat} type=UNKNOWN'); + return false; } sounds.set(tag.characterId, byteArray); soundTypes.set(tag.characterId, type); + return true; } - return; + return false; + } + + private function getSoundType(soundFormat:Int):Null + { + return switch (soundFormat) + { + case 0: SoundType.UNCOMPRESSED_NATIVE_ENDIAN; + case 1: SoundType.ADPCM; + case 2: SoundType.MP3; + case 3: SoundType.UNCOMPRESSED_LITTLE_ENDIAN; + case 4: SoundType.NELLYMOSER_16_KHZ; + case 5: SoundType.NELLYMOSER_8_KHZ; + case 6: SoundType.NELLYMOSER; + case 11: SoundType.SPEEX; + case _: null; + } } private function processSymbol(symbol:swf.data.SWFSymbol):Void @@ -862,6 +1120,10 @@ class SWFLiteExporter data2.className = symbol.name; data2.baseClassName = FrameScriptParser.getBaseClassName(data, symbol.name); } + else if (sounds.exists(symbol.tagId) && symbol.name != null) + { + soundSymbolClassNames.set(symbol.tagId, symbol.name); + } } private function processTag(tag:IDefinitionTag):Dynamic diff --git a/src/swf/exporters/animate/AnimateLibrary.hx b/src/swf/exporters/animate/AnimateLibrary.hx index 366cb0a..a712b2a 100644 --- a/src/swf/exporters/animate/AnimateLibrary.hx +++ b/src/swf/exporters/animate/AnimateLibrary.hx @@ -64,6 +64,7 @@ import openfl.filters.GlowFilter; private var preloading:Bool; private var root:AnimateSpriteSymbol; private var rootPath:String; + private var soundClassNames:Map; private var symbols:Map; private var symbolsByClassName:Map; private var uuid:String; @@ -79,6 +80,7 @@ import openfl.filters.GlowFilter; alphaCheck = new Map(); bitmapClassNames = new Map(); + soundClassNames = new Map(); #if (ios || tvos) rootPath = "assets/"; @@ -129,7 +131,22 @@ import openfl.filters.GlowFilter; if (type == null || type == (cast AssetType.IMAGE) || type == (cast AssetType.MOVIE_CLIP)) { - return (symbolsByClassName != null && symbolsByClassName.exists(id)); + if (symbolsByClassName != null && symbolsByClassName.exists(id)) + { + return true; + } + + if (type == null) + { + return soundClassNames != null && soundClassNames.exists(id); + } + + return false; + } + + if (type == (cast AssetType.SOUND) || type == (cast AssetType.MUSIC)) + { + return soundClassNames != null && soundClassNames.exists(id); } } @@ -177,6 +194,18 @@ import openfl.filters.GlowFilter; } #end + #if lime + public override function getAudioBuffer(id:String):lime.media.AudioBuffer + { + if (soundClassNames != null && soundClassNames.exists(id)) + { + id = soundClassNames.get(id); + } + + return super.getAudioBuffer(id); + } + #end + #if lime public override function getMovieClip(id:String):MovieClip { @@ -221,11 +250,39 @@ import openfl.filters.GlowFilter; var requestedType = type != null ? cast(type, AssetType) : null; var items = []; - if (symbolsByClassName != null) + if (requestedType == null || requestedType == MOVIE_CLIP) { items.push(""); + } + if (symbolsByClassName != null) + { for (id in symbolsByClassName.keys()) + { + var symbol = symbolsByClassName.get(id); + if (symbol == null) + { + continue; + } + + if (requestedType == null) + { + items.push(id); + } + else if (requestedType == IMAGE && #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (symbol, AnimateBitmapSymbol)) + { + items.push(id); + } + else if (requestedType == MOVIE_CLIP && #if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (symbol, AnimateSpriteSymbol)) + { + items.push(id); + } + } + } + + if (requestedType == null || requestedType == SOUND || requestedType == MUSIC) + { + for (id in soundClassNames.keys()) { items.push(id); } @@ -264,6 +321,7 @@ import openfl.filters.GlowFilter; symbols = new Map(); symbolsByClassName = new Map(); bitmapSymbols = new Array(); + soundClassNames = new Map(); for (i in 0...symbolData.length) { @@ -290,6 +348,8 @@ import openfl.filters.GlowFilter; spriteSymbol = __parseSprite(data); if (i == rootIndex) root = spriteSymbol; symbol = spriteSymbol; + case SOUND: + symbol = __parseSound(data); case STATIC_TEXT: symbol = __parseStaticText(data); default: @@ -299,10 +359,17 @@ import openfl.filters.GlowFilter; symbols.set(symbol.id, symbol); if (symbol.className != null) { - symbolsByClassName.set(symbol.className, symbol); - #if (openfl > "9.1.0") - Assets.registerBinding(symbol.className, this); - #end + if (#if (haxe_ver >= 4.2) Std.isOfType #else Std.is #end (symbol, AnimateSoundSymbol)) + { + soundClassNames.set(symbol.className, cast(symbol, AnimateSoundSymbol).path); + } + else + { + symbolsByClassName.set(symbol.className, symbol); + #if (openfl > "9.1.0") + Assets.registerBinding(symbol.className, this); + #end + } } } @@ -444,6 +511,18 @@ import openfl.filters.GlowFilter; } #end + #if lime + public override function loadAudioBuffer(id:String):Future + { + if (soundClassNames != null && soundClassNames.exists(id)) + { + id = soundClassNames.get(id); + } + + return super.loadAudioBuffer(id); + } + #end + #if lime public override function unload():Void { @@ -732,6 +811,15 @@ import openfl.filters.GlowFilter; return symbol; } + private function __parseSound(data:Dynamic):AnimateSoundSymbol + { + var symbol = new AnimateSoundSymbol(); + symbol.id = data.id; + symbol.className = data.className; + symbol.path = data.path; + return symbol; + } + private function __parseSprite(data:Dynamic):AnimateSpriteSymbol { if (data == null) return null; @@ -830,4 +918,5 @@ import openfl.filters.GlowFilter; public var SHAPE = 4; public var SPRITE = 5; public var STATIC_TEXT = 6; + public var SOUND = 7; } diff --git a/src/swf/exporters/animate/AnimateSoundSymbol.hx b/src/swf/exporters/animate/AnimateSoundSymbol.hx new file mode 100644 index 0000000..7f5bc21 --- /dev/null +++ b/src/swf/exporters/animate/AnimateSoundSymbol.hx @@ -0,0 +1,15 @@ +package swf.exporters.animate; + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +class AnimateSoundSymbol extends AnimateSymbol +{ + public var path:String; + + public function new() + { + super(); + } +} diff --git a/src/swf/exporters/swflite/SWFLiteLoader.hx b/src/swf/exporters/swflite/SWFLiteLoader.hx index 8f45181..3c0bc4e 100644 --- a/src/swf/exporters/swflite/SWFLiteLoader.hx +++ b/src/swf/exporters/swflite/SWFLiteLoader.hx @@ -66,7 +66,10 @@ class SWFLiteLoader implements IDisplayObjectLoader #else // TODO: No intermediate format var swf = new SWF(buffer); - var exporter = new SWFLiteExporter(swf.data); + var sourceBytes = new ByteArray(); + sourceBytes.writeBytes(buffer, 0, buffer.length); + sourceBytes.position = 0; + var exporter = new SWFLiteExporter(swf.data, sourceBytes); var swfLite = exporter.swfLite; var library = new SWFLiteLibrary("test"); swfLite.library = library;