Flash SWF Editing

From Nexus Mods Wiki
Revision as of 01:01, 16 November 2018 by Dubiousintent (talk | contribs) (Added 'Category:Mod_Creation')
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


Overview

Adobe Flash SWF files (extensions of .swf and .swc) contain code in the form of ActionScripts which can control the User Interface (UI), and are called Native Functions and sometimes are called in code as native simulated function. They can also contain graphic sprites and icons used in those interfaces or elsewhere.

In the case of XCOM:Enemy Unknown 2012 these SWF files are embedded in various UPK files, but primarily in files command1.upk and GlobalPersistentCookerData.upk. From the game developer and modders perspective, a huge advantage to using SWF files is that they do not have to be of known or fixed lengths.

This article describe the currently known information about editing SWF files, ActionScripts, and sprites/icons.

Programs and Tools

In order to examine or alter SWF files, they must first be extracted from the UPK files they reside in. For that decompilers are useful. There is, however, and proven manual method described below in the Separate Content section.

SWF Decompilers

  • The Open Source JPEXS Decompiler has successfully been used on SWF files extracted from XCOM upk files. It can save the modified ActionScript back into the extracted SWF file. Hex replacement within the upk file is still required at the current time.
  • The blog [Free SWF Decompilers List F.L.A.S.W.F.] has a List of Free SWF Decompilers.
  • Show My Code is a free Online decode/decompiler that can:
    • Decompile flash ActionScript from the swf flash file. Use this online flash swf decompiler to get the source code of the swf file.
    • Decompile java CLASS files and save source code in text. Reconstruct the original source code from the compiled binary.
    • Decompile .NET application and get it source code. Now you can get close to original source code of C# (c sharp), Visual Basic .NET (VB.NET.), Visual C++ .NET (VC++.NET) and J# (jay-sharp).
    • Decode and get source of php files encoded with Zend Guard. This process also called dezending and the program called dezender (de-zender). So, dezend php files and get the source code!
    • QR-code Reader. Decode a QR Code and get text! It's a two-dimensional bar code. The "QR" is derived from "Quick Response". Most current mobile phones can read this code with their camera. UTF-8 is supported.

Video

To date, no one has figured out how to alter or replace the games texture of AVI files. However, the following tools may be of assistance, even if simply for recording things for analysis or replay.

  • CamStudio (FREE) is able to record all screen and audio activity on your computer and create industry-standard AVI video files and using its built-in SWF Producer can turn those AVIs into lean, mean, bandwidth-friendly Streaming Flash videos (SWFs).
  • Anyone attempting to work with video is advised to check out the Video_capture_and_edit_tools page on the Nexus Wiki.

Details


Icons are located in file Command1.upk, in the portion identified as gfxXComIcons\XComIcons.swf. Flag and Perk icons have been found in file GlobalPersistentCookerData.upk. All the SWF files in Command1.upk have been extracted and posted in the Mod Long War Miscellaneous Files section.

It's possible to use the generic resizing capability within the tool [UPKmodder] on an actionscript object to add additional actionscript / sprites without having to remove code from other portions. I think this is definitely a case where the "expand-in-place" approach is superior as some of the flash objects (as in UPK-level objects) are quite large.

For example, the SquadSelect SWF file (when extracted) is 135k, while the StrategComponents SWF file is 153k. These SWF files comprise the bulk of the UPK-level object containing them, although not quite all of it.

Using "resize-in-place" it's possible to rewrite a small portion of the SWF file (like a single flash-level object) and resize it accordingly, which allows for much easier editing of the embedded SWF files. There are actually decompile/recompile tools for SWF files, which makes creating new code here easier than in the UPK itself (since we don't have a re-compile tool that will use existing object info from an existing UPK).

Not only does this allow for easier modifying of the actionscript components, but also for adding additional sprite data. This method could even be used to add additional image / icon data, or to modify compressed imagery (where the replacement won't be the same size as the original).

See #Resize-in-Place in the #Examples section for an illustration of this resizing technique, as well as discussion of the specific accompanying changes that must be made to the flash-level object block header.

Separate Content


Edit Process

Here is the manual process for editing SWF files, from the XCOM Modding thread UI Editing:

  1. Get the name of the ActionScript you want to edit. In UE Explorer, it's the text string called from an "ActionScriptVoid" or other function starting with "ActionScript," like ActionScriptFloat. These appear to be called from unreal functions beginning with "AS_" in various classes.
  2. Figure out which UPK the ActionScript appears in. I finally started using grep to text search the entire uncompressed set of UPKs. You'll have to hunt around, as you might find function calls rather than the function itself. For interface editing, Command1.UPK appears to have lots of ActionScript sections, although XComGame.UPK has one or two, as well.
  3. Now you have to excise the .SWF containing the Actionscript file from within the .UPK. "UPKs" can hold multiple SWF files.
    • Manual Method:
    1. SWF files begin with the three letters "GFX" (all uppercase) within the UPK. In your hex editor, do an ANSI search for the ActionScript function name you want to edit. Then, search the ANSI *UP* from there for the letters "GFX". Look at the context in which "GFX" appears -- if it looks like it's part of a function name, and not a standalone code, then search upward again.
    2. Delete every single byte in the file BEFORE the letters "GFX", all the way to the beginning.
    Note this may leave other SWF files than one in which you are interested below it. Just repeat the process in the other direction, but be sure to search for "GFX" BELOW the ActionScript function name you want to edit.
    • Programmable Method:
    1. Search for GFX (all caps).
    2. Skip the next byte -- it's the version number.
    3. The next four bytes are the SWF file size.
    4. Add the file size to the hex location of the "G" in GFX.
    5. Resultant position is the end of the embedded SWF file.
    6. As a double check, the four bytes immediately following the embedded SWF file should be FF FF FF FF.
    7. Extract the identified block of hex code (from the "GFX" to the calculated end of the embedded SWF file) to a 'temporary workspace'.
  4. Change the letters "GFX" to "FWS". These should be the first three bytes of your new file. This allows decompilers to treat it as a distinct file (Thanks to Dumbo111 for this trick)
  5. Save the result as an .SWF file -- do NOT save as a UPK, as you've already ruined this file as a UPK, and you want to keep your original file intact. There may be additional ActionScripts in separate SWFs below the SWF you want to edit, but you won't be able to get at it without excising each one separately.
  6. Open your new .SWF file in an SWF / ActionScript decompiler, such as SoThink's decompiler, which has a 30-day trial limit, or JPEXS decompiler (free, link in the 'Modding Tools' wiki article). But I was able to translate the commands to hex (by looking at the "raw data" option and matching the assembler commands to the ActionScript) and make changes to in Notepad++ to the hex of the original UPK. (In other words, extract the SWF section for observation purposes, but make your edits to the original UPK.)

Hex Code

A fairly comprehensive set of hex codes can be found here: SWF Actions

Scripts in Actionscript are considered a specialized type of ACTION, and are always wrapped in an action tag. Scripts are not procedural (like in UE hex code), but is stack-based, which may require some adjustment.

Null Ops

Actionscript doesn't directly have a null-op (like the 0B in UE hex), but you can create null ops of various sizes. Simply PUSH a string onto the stack, then POP it back off. The string can be variable length so a variable length null-op can be created. The PUSH, POP, and the string terminator require 6 bytes, so this is the smallest null-op that can be created this way.

Quickly generating hex code

The JPEXS decompiler supports a limited amount of editing. Currently it is limited to editing the stack-level commands (push, pop, etc) as opposed to the higher-level C/java style code (there is beta version of that editing capability, but it isn't working yet).

The following sequence that lets me edit the scripts fairly quickly:

  1. Open Actionscript, figure out what code I want to change.
  2. From the JPEXS hex-view, identify the leading hex blocks for the original function and the NEXT function.
  3. Using HxD (or any hex editor), copy out the hex of the function from the original SWF.
  4. Back in JPEXS, edit the function in the "stack view".
  5. "Save as" the changes to a new file (JPEXS can't save much of the data in the original SWF, but it can correctly save all of the scripts).
  6. From JPEXS hex-view, identify the leading hex blocks for the edited function and the NEXT function.
  7. Open the newly saved SWF file in HxD (or other hex editor) and find the block of changed hex.
  8. Copy out the modified hex from the modified copy of the SWF.
  9. Test the new hex by search and replace in a copy of the original SWF, and try to decompile in JPEXS.

This is MUCH MUCH faster than trying to manually edit the hex codes. Due to the stack-based structure, MANY of the commands are 1 byte. Using the above method I was able to rewrite a 1014 byte SWF function in about an hour.

Getting UI Inputs to UnrealScript

[Thanks to tracktwo in the "R&D XCOM UI/Flash modding" thread in the Nexus XCOM "Mod Talk" forum for the following:]

Getting mouse input back into UnrealScript from Flash turned out to be not so big of a deal once I figured out how to organize things in Firaxis' system. When using the built in screen/panel system, mouse input is managed by the interface manager, UIFxsMovie. To communicate between your Flash movie and your UI_FxsScreen or UI_FxsPanel sub-class, you just need the following:

  1. The s_package, s_screenId, and s_name fields in your UI_FxsScreen or UI_FxsPanel subclass need to be set correctly: s_package to the package and object containing your Flash, s_screenId to a unique Id to identify your screen, and s_name to name the "main" movieclip (MC) in your movie.
  2. On the Flash side, your main MC instance needs to be exported for ActionScript in the first frame.
  3. Register and display your screen through the interface manager: The convention is to have some "manager" class spawn and initialize your UI class by calling Init() and passing the XComPlayerController as the manager and a interface manager instance (e.g. PRES().GetHUD() for the strategy hud interface manager) as the 2nd. Your Init() function should call LoadScreen() on the manager you passed in, passing self. This will set your movie up as a child screen of the interface manager.
  4. After completing any setup on the Flash side, call the externalInterface function "FlashRaiseInit", passing along the full path to your MC (i.e. flash.external.ExternalInterface.call("FlashRaiseInit", string(this)). This is required to tell both the interface manager and your UI object that your Flash code has completed any initialization. This call will be handled by the UIFxsMovie instance manager, which will call the OnInit function in your subclass. It looks up the correct UI object to invoke by looking at the first argument to "FlashRaiseInit". This means that the full path as returned by string(this) in Flash needs to match the s_screenID and s_name fields in your UI subclass, otherwise it won't find the right object to call. Failing to call "FlashRaiseInit" will prevent the interface manager from forwarding any mouse input to your UI as it thinks it is not yet initialized and ready for input processing.
  5. Mouse events are raised by the external interface call "FlashRaiseMouseEvent". This call is handled by the interface manager and dispatches to the OnMouseEvent function in your UI class. The Flash external call takes three additional arguments. The first is the main MC of the clip again (as for "FlashRaiseInit", this is how the interface manager finds the correct UI instance to call). The second is an integer which represents the input event type. The third is an arbitrary string "args". The convention used by most vanilla UIs is to pass the full path to the movieclip that generated the event here. The interface manager will split this string on "." and pass the array of strings that make up the argument to your OnMouseEvent function. In UnrealScript, OnMouseEvent takes 2 arguments: the integer (int) command and the "args" array of strings.
  6. All the Flash external interface calls must be generated through a movieclip exposed to ActionScript and exported in the first frame of your movie, or scaleform won't process them.

Keyboard/Gamepad input is fairly easy to manage, as expected. Just provide the OnUnrealCommand(int Cmd, int Arg) function in your UI class and you'll get keyboard and gamepad events whenever your UI element is focused. Mouse events also come in through here, but in most cases you're probably better off handling this at the Flash level so it is easier to figure out what was clicked on.

  • The list of input constants is in the class UI_FxsInput. For example, many UI screens will respond to all of the options:
    • the <Escape> key
    • the gamepad < B/Circle > button
    • the right mouse button
to close the UI. These correspond to entries for commands 301, 405, and 510 respectively.

Also a neat trick: by using the standard screen/panel system, the UI elements all coexist in the same movie instance as sub-clips. This means that if your UI is layered on top of another UI (as the campaign manager is currently layered on top of the situation room) you can actually access all of the flash elements of the other UI from ActionScript. For example, the main text box in the situation room can be accessed through _level0.theInterfaceMgr.gfxSituationRoomHUD.theScreen.continentBody. "theInterfaceMgr" is the main interface movie (UIInterfaceMgr). You can derive the rest of the path by looking at the UI class in question (gfxSituationRoomHUD is the s_screenId field in UISituationRoom and "theScreen" is the s_name field) and the actual Flash itself ("continentBody" is a child element of "theScreen" when you look at the Flash file in JPEXS). So one possible way to add new Flash to an existing UI is to layer an invisible flash screen on top of it and tinker with the screen "below" through ActionScript.

Code Breakdown


Compiled Code


Examples

Resize-in-Place

Altering the Situation Room: Written for use with UPK-Modder, and it will be necessary to remove the line breaks in the "[BEFORE_HEX]" and "[AFTER_HEX]" sections. Comments are preceded by double slashes (//) and can be ignored by UPK-Modder.

MODFILEVERSION=4
UPKFILE=UICollection_Strategy_SF.upk
GUID=7B 52 25 96 F3 8E 7C 48 8F 11 3B 40 43 3F BD 3F
FUNCTION=SituationRoom@gfxSituationRoom
RESIZE=18
//
[BEFORE_HEX]
FF 09 A4 00 00 00 39 00 14 00 FF 0A 03 00 00 00
5F 31 00 89 06 06 01 00 38 00 16 92 39 80 40 00
40 00 40 00 40 00 3F 03 02 00 00 00 07 00 40 00
FF 0A 03 00 00 00 5F 32 00 8B 06 16 03 00 38 00
16 EE 39 80 05 00 40 00 40 00 40 00 40 00 3F 03
02 00 00 00 07 00 40 00 FF 0A 03 00 00 00 5F 33
00 8B 06 16 05 00 38 00 16 C0 30 C0 0A 00 40 00
40 00 40 00 40 00 3F 03 02 00 00 00 07 00 40 00
FF 0A 03 00 00 00 5F 34 00 8B 06 16 07 00 38 00
18 8F 5C 2E 0F 00 40 00 40 00 40 00 40 00 3F 03
02 00 00 00 07 00 40 00 00 00
[/BEFORE_HEX]
//
[AFTER_HEX]
FF 09 BC 00 00 00 39 00 06 00 FF 0A 03 00 00 00
5F 31 00 89 06 06 01 00 38 00 16 92 39 80 40 00
3F 03 02 00 00 00 07 00 FF 0A 03 00 00 00 5F 32
00 8B 06 16 03 00 38 00 16 EE 39 80 01 00 40 00
3F 03 02 00 00 00 07 00 FF 0A 03 00 00 00 5F 33
00 8B 06 16 05 00 38 00 16 C0 30 C0 02 00 40 00
3F 03 02 00 00 00 07 00 FF 0A 03 00 00 00 5F 34
00 8B 06 16 07 00 38 00 18 8F 5C 30 03 00 40 00
3F 03 02 00 00 00 07 00 FF 0A 03 00 00 00 5F 35
00 8B 06 16 09 00 38 00 16 C0 02 40 04 00 40 00
3F 03 02 00 00 00 07 00 FF 0A 03 00 00 00 5F 36
00 8B 06 16 0B 00 38 00 18 8F 40 90 05 00 40 00
00 00
[/AFTER_HEX]

If you are familiar with actionscript hex, you'll recognize that the FF 09 at the beginning marks the beginning of a flash-level object block, and the next 4 bytes are the size of the object, which is changing from A4 to BC. This matches with the RESIZE operation amount of 18, as 0xA4 + 0x18 == 0xBC.

The object containing an SWF object has a fairly long and variable-sized "header" preceding the actual embedded SWF file (which has the "FWS" leading string replaced with "GFX").

The objectlist entry for "SituationRoom" looks like so:

FC FF FF FF             // type -- Core:SwfMovie@GFxUI
00 00 00 00             // parent - none
2A 00 00 00             // owner (gfxSituationRoom)
3D 02 00 00 00 00 00 00 // namelist reference ("SituationRoom")
00 00 00 00 00 00 00 00 // unknown
04 00 0F 00             // flags
2B 69 02 00             // object size
53 11 69 00             // object position
01 00 00 00             // unknown
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00

Of particular note are the object's file size and file position, which are 0x2692B and 0x691153.

The embedded SWF file begins with:

47 46 58 08 30 67 02 00

47 46 58 in ASCII is "GFX", the 08 is the version, and the next 4 bytes are (in little endian) the SWF filesize, 0x26730. This is 0x1FB = 507 bytes smaller than the full object. This extra 0x1FB bytes is entirely contained within a "header" preceding the SWF embedded file within the object.

The entire hex header is variable size, as it contains string elements:

FF FF FF FF 46 00 00 00 00 00 00 00 2D 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 01 C6 01 00
00 00 00 00 00 69 01 00 00 00 00 00 00 04 00 00
00 00 00 00 00 00 04 00 00 94 02 00 00 00 00 00
00 47 00 00 00 00 00 00 00 08 00 00 00 00 00 00
00 D7 00 00 00 00 00 00 00 D8 00 00 00 00 00 00
00 90 02 00 00 00 00 00 00 7E 02 00 00 00 00 00
00 08 00 00 00 00 00 00 00 04 00 00 00 54 47 41
00 0B 02 00 00 00 00 00 00 10 00 00 00 00 00 00
00 8A 00 00 00 00 00 00 00 04 00 00 00 19 00 00
00 67 66 78 63 6F 6D 70 6F 6E 65 6E 74 73 2E 63
6F 6D 70 6F 6E 65 6E 74 73 00 1D 00 00 00 67 66
78 47 61 6D 65 70 61 64 49 63 6F 6E 73 2E 47 61
6D 65 70 61 64 49 63 6F 6E 73 00 29 00 00 00 67
66 78 53 74 72 61 74 65 67 79 43 6F 6D 70 6F 6E
65 6E 74 73 2E 53 74 72 61 74 65 67 79 43 6F 6D
70 6F 6E 65 6E 74 73 00 17 00 00 00 67 66 78 58
43 6F 6D 49 63 6F 6E 73 2E 58 43 6F 6D 49 63 6F
6E 73 00 0C 02 00 00 00 00 00 00 10 00 00 00 00
00 00 00 B4 00 00 00 00 00 00 00 2C 00 00 00 9A
01 00 00 9B 01 00 00 9C 01 00 00 9D 01 00 00 9E
01 00 00 9F 01 00 00 A1 01 00 00 A0 01 00 00 A2
01 00 00 A3 01 00 00 A4 01 00 00 A5 01 00 00 A6
01 00 00 A7 01 00 00 A8 01 00 00 A9 01 00 00 AA
01 00 00 AB 01 00 00 AC 01 00 00 AD 01 00 00 AE
01 00 00 AF 01 00 00 B0 01 00 00 B1 01 00 00 B2
01 00 00 B3 01 00 00 B4 01 00 00 B5 01 00 00 B6
01 00 00 B7 01 00 00 B8 01 00 00 B9 01 00 00 BA
01 00 00 BB 01 00 00 BC 01 00 00 BD 01 00 00 BE
01 00 00 BF 01 00 00 C0 01 00 00 C1 01 00 00 45
00 00 00 4B 00 00 00 70 00 00 00 75 00 00 00 B0
01 00 00 00 00 00 00 30 67 02 00
//
// The above hex displays as the following printable characters:
ÿÿÿÿF.......-...
.............Æ..
.....i..........
.........”......
.G..............
.×.......Ø......
.........~......
.............TGA
................
.Š..............
.gfxcomponents.c
omponents.....gf
xGamepadIcons.Ga
mepadIcons.)...g
fxStrategyCompon
ents.StrategyCom
ponents.....gfxX
ComIcons.XComIco
ns..............
...´.......,...š
...›...œ.......ž
...Ÿ...¡... ...¢
...£...¤...¥...¦
...§...¨...©...ª
...«...¬.......®
...¯...°...±...²
...³...´...µ...¶
...·...¸...¹...º
...»...¼...½...¾
...¿...À...Á...E
...K...p...u...°
.......0g..

I'm sure there are several interesting portions here, but what I am interested in is the last 4-byte word of this "header" : 30 67 02 00, which is the size of the SWF object within the UPK-level object.

Hence there SWF object size is actually declared twice (and immediately adjacent):

30 67 02 00  // end of upk-object header -- file size of SWF movie
47 46 58     // beginning of SWF file, ASCII "GFX"
08           // version number
30 67 02 00  // size of SWF movie object

The positioning of the file size is consistent with the position of an unrealscript filesize -- again it's the final 4-byte word of the header. The second instance of the size is for the flash interpreter in order to conform with Flash file specification.

Slightly later in the Flash file is some embedded XML:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 
<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/"> 
<xmp:CreatorTool>Adobe Flash Professional CS5</xmp:CreatorTool> 
<xmp:CreateDate>2011-11-15T11:20:44-05:00</xmp:CreateDate>

Here we can see that Firaxis is using Adobe Flash Professional CS5 to create the Flash files, which are later cooked into the UPK.

At any rate, in order to resize an actionscript object both of these values must be adjusted. Since the current RESIZE operation in UPKmodder only allows a single hex change, if this header is separate from the change (quite likely), it would require a second-pass modification.


References

Referred to by this article:



That refer to this article: