Files
2026-08-27 17:15:05 -07:00

1015 lines
34 KiB
C++

#include "ImXui/XurFormat.h"
#include "ImXui/XurWriter.h"
#include "ImXui/XurXml.h"
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <fstream>
#include <sstream>
#include "zlib.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
namespace {
const char* TOOL_VERSION = "0.1.0";
enum class FileFormat { Unknown, XUI, XUR };
struct XurHeaderInfo {
bool valid;
uint32_t version;
uint32_t flags;
uint32_t fileSize;
uint16_t sectionsCount;
XurHeaderInfo() : valid(false), version(0), flags(0), fileSize(0), sectionsCount(0) {}
};
std::string lower(const std::string& s) {
std::string r = s;
for (size_t i = 0; i < r.size(); i++)
if (r[i] >= 'A' && r[i] <= 'Z') r[i] = (char)(r[i] - 'A' + 'a');
return r;
}
std::string ws2s(const std::wstring& ws) {
return std::string(ws.begin(), ws.end());
}
bool hasExtension(const std::string& path, const char* ext) {
if (path.size() < strlen(ext)) return false;
return lower(path.substr(path.size() - strlen(ext))) == ext;
}
FileFormat detectFormat(const std::string& path) {
std::vector<uint8_t> head(16, 0);
{
std::ifstream ifs(path, std::ios::binary);
if (ifs) ifs.read((char*)head.data(), 16);
}
if (head.size() >= 4 && memcmp(head.data(), "XUIB", 4) == 0)
return FileFormat::XUR;
if (head.size() >= 1 && (head[0] == '<' || head[0] == 0xEF || head[0] == 0xFF || head[0] == 0xFE))
return FileFormat::XUI;
if (hasExtension(path, ".xur")) return FileFormat::XUR;
if (hasExtension(path, ".xui")) return FileFormat::XUI;
return FileFormat::Unknown;
}
XurHeaderInfo readXurHeader(const std::string& path) {
XurHeaderInfo info;
std::ifstream ifs(path, std::ios::binary);
if (!ifs) return info;
uint8_t bytes[20];
ifs.read((char*)bytes, 20);
if (ifs.gcount() < 20) return info;
if (memcmp(bytes, "XUIB", 4) != 0) return info;
auto be32 = [&](size_t off) -> uint32_t {
return ((uint32_t)bytes[off] << 24) | ((uint32_t)bytes[off + 1] << 16) |
((uint32_t)bytes[off + 2] << 8) | (uint32_t)bytes[off + 3];
};
auto be16 = [&](size_t off) -> uint16_t {
return (uint16_t)(((uint16_t)bytes[off] << 8) | (uint16_t)bytes[off + 1]);
};
info.valid = true;
info.version = be32(4);
info.flags = be32(8);
info.fileSize = be32(14);
info.sectionsCount = be16(18);
return info;
}
const char* formatName(FileFormat f) {
switch (f) {
case FileFormat::XUI: return "XUI v12";
case FileFormat::XUR: return "XUR";
default: return "unknown";
}
}
void usage() {
std::cout <<
"XUITool " << TOOL_VERSION << " - ImXui alternative to XUIHelper / XuiTool\n"
"Reads, writes and converts XUI and XUR files (Xbox 360 UI format).\n"
"\n"
"Usage:\n"
" xuitool about\n"
" Print version and supported formats.\n"
" xuitool info <file>\n"
" Detect format and print a summary of a .xui / .xur file.\n"
" xuitool validate <file>\n"
" Parse a file and report validity. Exit code 0 = valid.\n"
" xuitool conv -s <src> -o <dst> [-f xurv5|xuiv12]\n"
" Convert a single file. Input format is auto-detected; output\n"
" format comes from -f or is inferred from the output extension.\n"
" xuitool massconv -s <dir> -o <dir> [-f xurv5|xuiv12] [-r]\n"
" Convert every .xui / .xur file in a directory (recursive with -r).\n"
" xuitool swf2xui <file.swf> [output.xui]\n"
" Convert a Flash SWF to XUI XML (shapes, sprites, buttons).\n";
}
bool loadObject(const std::string& path, FileFormat fmt, ImXui::XurFormat::XurObject& out) {
if (fmt == FileFormat::XUR) {
auto doc = ImXui::XurFormat::XurParser::parseFromFile(path);
if (!doc || !ImXui::XurFormat::XurParser::validate(doc.get())) return false;
out = doc->rootObject;
return true;
}
out = ImXui::XurFormat::xuiFileToObject(path);
return !out.className.empty();
}
bool writeObject(const std::string& dst, const ImXui::XurFormat::XurObject& obj, bool toXur) {
if (toXur)
return ImXui::XurFormat::XurWriter::writeToFile(dst, obj);
return ImXui::XurFormat::xurObjectToXuiXml(obj, dst);
}
void dumpObjectTree(const ImXui::XurFormat::XurObject& obj, int depth) {
std::string indent((size_t)depth * 2, ' ');
std::cout << indent << obj.className << "\n";
for (auto& p : obj.properties) {
if (p.name == "Id" && p.type == ImXui::XurFormat::XurPropertyType_String)
std::cout << indent << " id=" << ws2s(p.value.stringVal) << "\n";
else if (p.name == "Visual" && p.type == ImXui::XurFormat::XurPropertyType_String)
std::cout << indent << " visual=" << ws2s(p.value.stringVal) << "\n";
else if (p.name == "Text" && p.type == ImXui::XurFormat::XurPropertyType_String)
std::cout << indent << " text=\"" << ws2s(p.value.stringVal) << "\"\n";
}
for (auto& c : obj.children)
dumpObjectTree(c, depth + 1);
}
int cmdAbout() {
std::cout <<
"XUITool " << TOOL_VERSION << "\n"
"An ImXui-based alternative to XUIHelper / XuiTool for editing the Xbox 360 UI format.\n"
"\n"
"Supported formats:\n"
" XUI v12 - XML scene files (version=\"000c\"), read/write\n"
" XUR v5 - compiled binary scenes, read/write\n"
" XUR v8+ - detected and reported, but not read/write capable in this build\n"
"\n"
"Library: ImXui (XurFormat, XurParser, XurWriter, XurXml)\n";
return 0;
}
int cmdInfo(const std::string& path) {
FileFormat fmt = detectFormat(path);
std::cout << "File: " << path << "\n";
std::cout << "Type: " << formatName(fmt) << "\n";
if (fmt == FileFormat::XUR) {
XurHeaderInfo hdr = readXurHeader(path);
if (!hdr.valid) {
std::cerr << "Error: not a XUR file\n";
return 1;
}
std::cout << "XUR version: " << hdr.version << "\n";
std::cout << "Flags: " << hdr.flags << "\n";
std::cout << "File size: " << hdr.fileSize << " bytes\n";
std::cout << "Sections: " << hdr.sectionsCount << "\n";
if (hdr.version != 5) {
std::cout << "Note: this build can only read/write XUR v5.\n";
return 1;
}
}
ImXui::XurFormat::XurObject obj;
if (!loadObject(path, fmt, obj)) {
std::cerr << "Error: failed to parse file\n";
return 1;
}
std::cout << "Root object: " << obj.className
<< " (" << obj.properties.size() << " props, "
<< obj.children.size() << " children)\n";
std::cout << "Object tree:\n";
dumpObjectTree(obj, 0);
return 0;
}
int cmdValidate(const std::string& path) {
FileFormat fmt = detectFormat(path);
if (fmt == FileFormat::XUR) {
XurHeaderInfo hdr = readXurHeader(path);
if (!hdr.valid) {
std::cerr << "Error: not a XUR file\n";
return 1;
}
if (hdr.version != 5) {
std::cerr << "Error: XUR v" << hdr.version << " is not supported (only v5)\n";
return 1;
}
auto doc = ImXui::XurFormat::XurParser::parseFromFile(path);
if (!doc || !ImXui::XurFormat::XurParser::validate(doc.get())) {
std::cerr << "Error: invalid XUR v5\n";
return 1;
}
std::cout << "OK: valid XUR v5\n";
return 0;
}
if (fmt == FileFormat::XUI) {
ImXui::XurFormat::XurObject obj = ImXui::XurFormat::xuiFileToObject(path);
if (obj.className.empty()) {
std::cerr << "Error: invalid XUI v12\n";
return 1;
}
std::cout << "OK: valid XUI v12\n";
return 0;
}
std::cerr << "Error: unrecognized file format\n";
return 1;
}
bool convertFile(const std::string& src, const std::string& dst,
FileFormat srcFmt, const std::string& outFormat) {
ImXui::XurFormat::XurObject obj;
if (!loadObject(src, srcFmt, obj)) {
std::cerr << "Error: failed to read '" << src << "'\n";
return false;
}
bool toXur;
if (outFormat == "xurv5") toXur = true;
else if (outFormat == "xuiv12") toXur = false;
else toXur = hasExtension(dst, ".xur");
if (!writeObject(dst, obj, toXur)) {
std::cerr << "Error: failed to write '" << dst << "'\n";
return false;
}
std::cout << "Converted " << src << " -> " << dst << "\n";
return true;
}
int cmdConv(int argc, char* argv[]) {
std::string src, dst, outFormat;
for (int i = 2; i < argc; i++) {
std::string a = argv[i];
if (a == "-s" && i + 1 < argc) src = argv[++i];
else if (a == "-o" && i + 1 < argc) dst = argv[++i];
else if (a == "-f" && i + 1 < argc) outFormat = lower(argv[++i]);
else {
std::cerr << "Unknown argument: " << a << "\n";
usage();
return 1;
}
}
if (src.empty() || dst.empty()) {
std::cerr << "conv requires -s <src> and -o <dst>\n";
usage();
return 1;
}
if (!outFormat.empty() && outFormat != "xurv5" && outFormat != "xuiv12") {
std::cerr << "Unknown output format '" << outFormat
<< "' (must be xurv5 or xuiv12)\n";
return 1;
}
FileFormat srcFmt = detectFormat(src);
if (srcFmt == FileFormat::Unknown) {
std::cerr << "Error: cannot detect format of '" << src << "'\n";
return 1;
}
if (srcFmt == FileFormat::XUR) {
XurHeaderInfo hdr = readXurHeader(src);
if (hdr.valid && hdr.version != 5) {
std::cerr << "Error: XUR v" << hdr.version << " is not supported (only v5)\n";
return 1;
}
}
return convertFile(src, dst, srcFmt, outFormat) ? 0 : 1;
}
#ifdef _WIN32
std::vector<std::string> listFiles(const std::string& dir, bool recursive) {
std::vector<std::string> files;
std::string pattern = dir + "\\*";
WIN32_FIND_DATAA fd;
HANDLE h = FindFirstFileA(pattern.c_str(), &fd);
if (h == INVALID_HANDLE_VALUE) return files;
do {
std::string name = fd.cFileName;
if (name == "." || name == "..") continue;
std::string full = dir + "\\" + name;
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (recursive) {
std::vector<std::string> sub = listFiles(full, recursive);
files.insert(files.end(), sub.begin(), sub.end());
}
} else if (hasExtension(name, ".xui") || hasExtension(name, ".xur")) {
files.push_back(full);
}
} while (FindNextFileA(h, &fd));
FindClose(h);
return files;
}
#else
#include <dirent.h>
#include <sys/stat.h>
static std::vector<std::string> listFiles(const std::string& dir, bool recursive) {
std::vector<std::string> files;
DIR* d = opendir(dir.c_str());
if (!d) return files;
struct dirent* ent;
while ((ent = readdir(d)) != nullptr) {
std::string name = ent->d_name;
if (name == "." || name == "..") continue;
std::string full = dir + "/" + name;
struct stat st;
if (stat(full.c_str(), &st) != 0) continue;
if (S_ISDIR(st.st_mode)) {
if (recursive) {
std::vector<std::string> sub = listFiles(full, recursive);
files.insert(files.end(), sub.begin(), sub.end());
}
} else if (hasExtension(name, ".xui") || hasExtension(name, ".xur")) {
files.push_back(full);
}
}
closedir(d);
return files;
}
#endif
int cmdMassConv(int argc, char* argv[]) {
std::string srcDir, dstDir, outFormat;
bool recursive = false;
for (int i = 2; i < argc; i++) {
std::string a = argv[i];
if (a == "-s" && i + 1 < argc) srcDir = argv[++i];
else if (a == "-o" && i + 1 < argc) dstDir = argv[++i];
else if (a == "-f" && i + 1 < argc) outFormat = lower(argv[++i]);
else if (a == "-r") recursive = true;
else {
std::cerr << "Unknown argument: " << a << "\n";
usage();
return 1;
}
}
if (srcDir.empty() || dstDir.empty()) {
std::cerr << "massconv requires -s <dir> and -o <dir>\n";
usage();
return 1;
}
if (!outFormat.empty() && outFormat != "xurv5" && outFormat != "xuiv12") {
std::cerr << "Unknown output format '" << outFormat
<< "' (must be xurv5 or xuiv12)\n";
return 1;
}
if (outFormat.empty()) outFormat = "xuiv12";
#ifdef _WIN32
CreateDirectoryA(dstDir.c_str(), nullptr);
#else
mkdir(dstDir.c_str(), 0755);
#endif
std::vector<std::string> files = listFiles(srcDir, recursive);
int ok = 0, failed = 0, skipped = 0;
for (auto& src : files) {
FileFormat srcFmt = detectFormat(src);
if (srcFmt == FileFormat::Unknown) { skipped++; continue; }
size_t sep = src.find_last_of("\\/");
std::string base = (sep == std::string::npos) ? src : src.substr(sep + 1);
size_t dot = base.find_last_of('.');
if (dot != std::string::npos) base = base.substr(0, dot);
std::string dst = dstDir + "\\" + base + (outFormat == "xurv5" ? ".xur" : ".xui");
bool srcIsXur = (srcFmt == FileFormat::XUR);
bool dstIsXur = (outFormat == "xurv5");
if (srcIsXur == dstIsXur) {
std::cout << "Skipping (already " << (srcIsXur ? "xurv5" : "xuiv12") << "): " << src << "\n";
skipped++;
continue;
}
if (srcFmt == FileFormat::XUR) {
XurHeaderInfo hdr = readXurHeader(src);
if (hdr.valid && hdr.version != 5) {
std::cout << "Skipping (unsupported XUR v" << hdr.version << "): " << src << "\n";
skipped++;
continue;
}
}
if (convertFile(src, dst, srcFmt, outFormat)) ok++;
else failed++;
}
std::cout << "massconv: " << ok << " converted, " << failed << " failed, "
<< skipped << " skipped\n";
return failed == 0 ? 0 : 1;
}
static bool decompressSWF(const uint8_t* compressed, size_t csize, std::vector<uint8_t>& out, size_t uncompressedSize) {
if (csize < 2) return false;
out.reserve(uncompressedSize > 0 ? uncompressedSize : 65536);
z_stream zs;
memset(&zs, 0, sizeof(zs));
// CWS is a zlib stream; skip the 2-byte header and inflate the raw deflate
if (inflateInit2(&zs, -MAX_WBITS) != Z_OK) return false;
zs.next_in = (Bytef*)(compressed + 2);
zs.avail_in = (uInt)(csize - 2);
std::vector<uint8_t> buf(65536);
int ret = Z_OK;
while (ret != Z_STREAM_END) {
zs.next_out = buf.data();
zs.avail_out = (uInt)buf.size();
ret = inflate(&zs, Z_NO_FLUSH);
if (ret != Z_OK && ret != Z_STREAM_END) {
inflateEnd(&zs);
return false;
}
out.insert(out.end(), buf.begin(), buf.end() - zs.avail_out);
}
inflateEnd(&zs);
return true;
}
#pragma pack(push, 1)
struct SwfHeader {
uint8_t signature[3];
uint8_t version;
uint32_t fileLength;
};
#pragma pack(pop)
struct SwfRect {
int32_t xMin, xMax, yMin, yMax;
};
struct SwfTag {
uint16_t type;
uint32_t length;
std::vector<uint8_t> data;
};
static uint8_t readU8(const uint8_t*& p) { return *p++; }
static uint16_t readU16(const uint8_t*& p) {
uint16_t v;
memcpy(&v, p, 2);
p += 2;
return v;
}
static uint32_t readU32(const uint8_t*& p) {
uint32_t v;
memcpy(&v, p, 4);
p += 4;
return v;
}
struct SwfMatrix {
float scaleX, scaleY;
float rotateSkew0, rotateSkew1;
float translateX, translateY;
};
static SwfRect readRect(const uint8_t*& p) {
SwfRect r = {};
uint8_t nBits = (*p) >> 3;
uint32_t bitBuf = 0;
int bitsRead = 0;
auto readBits = [&](int n) -> uint32_t {
uint32_t val = 0;
for (int i = 0; i < n; i++) {
if (bitsRead % 8 == 0) {
bitBuf = *p++;
}
val = (val << 1) | ((bitBuf >> (7 - (bitsRead % 8))) & 1);
bitsRead++;
}
return val;
};
bitBuf = *p++;
bitsRead = 5;
int32_t vals[4];
for (int i = 0; i < 4; i++) {
uint32_t v = readBits(nBits);
vals[i] = (int32_t)v;
}
r.xMin = vals[0] / 20;
r.xMax = vals[1] / 20;
r.yMin = vals[2] / 20;
r.yMax = vals[3] / 20;
if (bitsRead % 8 != 0) {
int skip = 8 - (bitsRead % 8);
readBits(skip);
}
return r;
}
static SwfMatrix readMatrix(const uint8_t*& p) {
SwfMatrix m = {1, 0, 0, 1, 0, 0};
uint32_t bitBuf = 0;
int bitsRead = 0;
auto readBits = [&](int n) -> uint32_t {
uint32_t val = 0;
for (int i = 0; i < n; i++) {
if (bitsRead % 8 == 0) {
bitBuf = *p++;
}
val = (val << 1) | ((bitBuf >> (7 - (bitsRead % 8))) & 1);
bitsRead++;
}
return val;
};
auto readSBits = [&](int n) -> int32_t {
uint32_t v = readBits(n);
if (v & (1u << (n - 1))) {
v |= ~((1u << n) - 1);
}
return (int32_t)v;
};
uint8_t hasScale = readBits(1);
if (hasScale) {
int nScaleBits = readBits(5);
m.scaleX = (float)readSBits(nScaleBits) / 65536.0f;
m.scaleY = (float)readSBits(nScaleBits) / 65536.0f;
}
uint8_t hasRotate = readBits(1);
if (hasRotate) {
int nRotateBits = readBits(5);
m.rotateSkew0 = (float)readSBits(nRotateBits) / 65536.0f;
m.rotateSkew1 = (float)readSBits(nRotateBits) / 65536.0f;
}
int nTranslateBits = readBits(5);
if (nTranslateBits > 0) {
m.translateX = (float)readSBits(nTranslateBits) / 20.0f;
m.translateY = (float)readSBits(nTranslateBits) / 20.0f;
}
if (bitsRead % 8 != 0) {
int skip = 8 - (bitsRead % 8);
readBits(skip);
}
return m;
}
class SwfParser {
public:
std::vector<uint8_t> data;
SwfHeader header;
SwfRect frameSize;
float frameRate;
uint16_t frameCount;
std::vector<SwfTag> tags;
bool parse(const std::string& path) {
std::ifstream ifs(path, std::ios::binary | std::ios::ate);
if (!ifs) return false;
std::streamsize sz = ifs.tellg();
ifs.seekg(0);
data.resize((size_t)sz);
if (!ifs.read((char*)data.data(), sz)) return false;
const uint8_t* p = data.data();
memcpy(&header, p, sizeof(SwfHeader));
p += sizeof(SwfHeader);
if (header.signature[0] != 'F' && header.signature[0] != 'C') return false;
if (header.signature[1] != 'W' || header.signature[2] != 'S') return false;
bool compressed = (header.signature[0] == 'C');
if (compressed) {
size_t uncompLen = header.fileLength;
const uint8_t* compStart = p;
size_t compSize = data.size() - (size_t)(p - data.data());
std::vector<uint8_t> decomp;
if (!decompressSWF(compStart, compSize, decomp, uncompLen)) {
std::cerr << " Decompression failed\n";
return false;
}
size_t hdrBytes = (size_t)(p - data.data());
std::vector<uint8_t> newData(hdrBytes + decomp.size());
memcpy(newData.data(), data.data(), hdrBytes);
if (!decomp.empty()) memcpy(newData.data() + hdrBytes, decomp.data(), decomp.size());
newData.swap(data);
p = data.data() + sizeof(SwfHeader);
header.signature[0] = 'F';
}
frameSize = readRect(p);
frameRate = (float)readU16(p) / 256.0f;
frameCount = readU16(p);
while ((size_t)(p - data.data()) < data.size()) {
uint16_t tagTypeAndLen = readU16(p);
uint16_t tagType = tagTypeAndLen >> 6;
uint32_t tagLen = tagTypeAndLen & 0x3F;
if (tagLen == 0x3F) {
tagLen = readU32(p);
}
if (tagType == 0) break;
SwfTag tag;
tag.type = tagType;
tag.length = tagLen;
if (tagLen > 0 && (size_t)(p - data.data() + tagLen) <= data.size()) {
tag.data.assign(p, p + tagLen);
p += tagLen;
}
tags.push_back(tag);
}
return true;
}
};
static std::string escapeXml(const std::string& s) {
std::string r;
for (char c : s) {
switch (c) {
case '&': r += "&amp;"; break;
case '<': r += "&lt;"; break;
case '>': r += "&gt;"; break;
case '\"': r += "&quot;"; break;
case '\'': r += "&apos;"; break;
default: r += c;
}
}
return r;
}
struct XuiElement {
std::string className;
std::map<std::string, std::string> props;
std::vector<XuiElement> children;
};
static void writeXuiElement(std::ostream& os, const XuiElement& el, int depth) {
std::string indent(depth * 2, ' ');
os << indent << "<" << el.className << ">\n";
if (!el.props.empty()) {
os << indent << " <Properties>\n";
for (auto& prop : el.props) {
os << indent << " <" << prop.first << ">" << escapeXml(prop.second) << "</" << prop.first << ">\n";
}
os << indent << " </Properties>\n";
}
for (auto& child : el.children) {
writeXuiElement(os, child, depth + 1);
}
os << indent << "</" << el.className << ">\n";
}
static void addProp(XuiElement& el, const std::string& key, const std::string& val) {
el.props[key] = val;
}
static std::string floatStr(float v) {
std::ostringstream ss;
ss << v;
return ss.str();
}
int cmdSwf2Xui(int argc, char* argv[]) {
if (argc < 2) {
std::cerr << "Usage: xuitool swf2xui <input.swf> [output.xui]\n";
std::cerr << " Converts basic Flash SWF files to XUI XML format.\n";
std::cerr << " Handles shapes, sprites, place/remove objects, and buttons.\n";
return 1;
}
std::string inPath = argv[1];
std::string outPath;
if (argc >= 3) {
outPath = argv[2];
} else {
if (inPath.size() >= 4 && inPath.substr(inPath.size() - 4) == ".swf") {
outPath = inPath.substr(0, inPath.size() - 4) + ".xui";
} else {
outPath = inPath + ".xui";
}
}
SwfParser parser;
if (!parser.parse(inPath)) {
std::cerr << "Error: failed to parse SWF file '" << inPath << "'\n";
std::cerr << "Note: Corrupt or unsupported SWF format\n";
return 1;
}
std::cout << "SWF: " << inPath << "\n";
std::cout << " Version: " << (int)parser.header.version << "\n";
std::cout << " FrameSize: " << parser.frameSize.xMax << "x" << parser.frameSize.yMax << "\n";
std::cout << " FrameRate: " << parser.frameRate << "\n";
std::cout << " FrameCount: " << parser.frameCount << "\n";
std::cout << " Tags: " << parser.tags.size() << "\n";
std::cout << " Tag types: ";
for (auto& tag : parser.tags) std::cout << tag.type << "(" << tag.length << ") ";
std::cout << "\n";
XuiElement root;
root.className = "XuiCanvas";
addProp(root, "Width", floatStr((float)parser.frameSize.xMax));
addProp(root, "Height", floatStr((float)parser.frameSize.yMax));
std::map<uint16_t, std::pair<uint16_t, std::vector<uint8_t>>> characters;
for (auto& tag : parser.tags) {
if (tag.type >= 1 && tag.type <= 88) {
switch (tag.type) {
case 76:
{
const uint8_t* sp = tag.data.data();
const uint8_t* send = tag.data.data() + tag.data.size();
uint16_t numSymbols = readU16(sp);
std::cout << " SymbolClass: " << numSymbols << " symbols\n";
for (uint16_t si = 0; si < numSymbols && sp < send; si++) {
uint16_t tagId = readU16(sp);
std::string name;
while (sp < send && *sp) { name += (char)*sp++; }
if (sp < send) sp++;
std::cout << " Symbol " << tagId << " -> " << name << "\n";
}
break;
}
case 2:
case 22:
case 32:
case 83:
case 11:
case 33:
case 37:
case 39:
case 34:
case 35:
{
const uint8_t* p = tag.data.data();
uint16_t charId = readU16(p);
characters[charId] = std::make_pair(tag.type, tag.data);
std::cout << " CharDef id=" << charId << " type=" << tag.type << " len=" << tag.length << "\n";
break;
}
}
}
}
std::map<int32_t, XuiElement> displayElements;
for (auto& tag : parser.tags) {
switch (tag.type) {
case 26:
{
const uint8_t* tp = tag.data.data();
const uint8_t* tend = tag.data.data() + tag.data.size();
uint8_t flags = readU8(tp);
uint8_t charIdByte = readU8(tp);
uint16_t charId = charIdByte;
SwfMatrix mat = {1, 0, 0, 1, 0, 0};
if (tp < tend) mat = readMatrix(tp);
static int po26Count = 0;
po26Count++;
auto it = characters.find(charId);
if (it != characters.end()) {
XuiElement el;
if (it->second.first == 39) {
el.className = "XuiScene";
} else if (it->second.first >= 11 && it->second.first <= 37) {
el.className = "XuiLabel";
} else {
el.className = "XuiImage";
}
addProp(el, "Id", "swf_char_" + std::to_string(charId) + "_p" + std::to_string(po26Count));
addProp(el, "X", floatStr(mat.translateX));
addProp(el, "Y", floatStr(mat.translateY));
addProp(el, "Width", "100.000000");
addProp(el, "Height", "100.000000");
addProp(el, "Show", "true");
std::cout << " PlaceObject26 flags=0x" << std::hex << (int)flags << std::dec << " charId=" << charId << " pos=(" << mat.translateX << "," << mat.translateY << ")\n";
displayElements[po26Count] = el;
} else {
std::cout << " PlaceObject26 flags=0x" << std::hex << (int)flags << std::dec << " charId=" << charId << " NOT FOUND\n";
}
break;
}
case 28:
{
const uint8_t* tp = tag.data.data();
uint16_t charId = readU16(tp);
int32_t depth = readU16(tp);
std::cout << " RemoveObject28 charId=" << charId << " depth=" << depth << "\n";
displayElements.erase(depth);
break;
}
case 33:
{
const uint8_t* tp = tag.data.data();
int32_t depth = readU16(tp);
std::cout << " RemoveObject33 depth=" << depth << "\n";
displayElements.erase(depth);
break;
}
case 43:
{
const uint8_t* tp = tag.data.data();
const uint8_t* tend = tag.data.data() + tag.data.size();
uint8_t flags = readU8(tp);
int32_t depth = readU16(tp);
uint16_t charId = 0;
SwfMatrix mat = {1, 0, 0, 1, 0, 0};
if (flags & 0x02) { charId = readU16(tp); }
if (flags & 0x04) {
if (tp < tend) mat = readMatrix(tp);
}
if (flags & 0x08) { }
if (flags & 0x10) { readU16(tp); }
if (flags & 0x20) {
while (tp < tend && *tp) tp++;
if (tp < tend) tp++;
}
if (flags & 0x40) { readU16(tp); }
if (flags & 0x80) { }
std::cout << " PlaceObject43 depth=" << depth << " charId=" << charId << " flags=0x" << std::hex << (int)flags << std::dec << " pos=(" << mat.translateX << "," << mat.translateY << ") found=" << characters.count(charId) << "\n";
if (flags & 0x01) {
displayElements.erase(depth);
} else {
auto it = characters.find(charId);
if (it != characters.end()) {
XuiElement el;
if (it->second.first == 39) {
el.className = "XuiScene";
} else if (it->second.first >= 34 && it->second.first <= 35) {
el.className = "XuiButton";
} else if (it->second.first >= 11 && it->second.first <= 37) {
el.className = "XuiLabel";
} else {
el.className = "XuiImage";
}
addProp(el, "Id", "swf_char_" + std::to_string(charId) + "_d" + std::to_string(depth));
addProp(el, "X", floatStr(mat.translateX));
addProp(el, "Y", floatStr(mat.translateY));
addProp(el, "Width", floatStr((float)parser.frameSize.xMax / 10.0f));
addProp(el, "Height", floatStr((float)parser.frameSize.yMax / 10.0f));
displayElements[depth] = el;
} else {
XuiElement el;
el.className = "XuiControl";
addProp(el, "Id", "swf_unknown_d" + std::to_string(depth));
addProp(el, "X", floatStr(mat.translateX));
addProp(el, "Y", floatStr(mat.translateY));
addProp(el, "Width", "50.000000");
addProp(el, "Height", "50.000000");
displayElements[depth] = el;
}
}
break;
}
case 70:
{
const uint8_t* tp = tag.data.data();
const uint8_t* tend = tag.data.data() + tag.data.size();
uint8_t flags = readU8(tp);
uint8_t flags2 = readU8(tp);
int32_t depth = readU16(tp);
std::string className;
while (tp < tend && *tp) { className += (char)*tp; }
if (tp < tend) tp++;
std::string instanceName;
{
const uint8_t* s = tend - 1;
while (s > tp && *s == 0) s--;
const uint8_t* strEnd = s + 1;
while (s > tp && *s != 0 && *s >= 0x20 && *s < 0x7F) s--;
s++;
if (strEnd > s) {
for (const uint8_t* p2 = s; p2 < strEnd; p2++) instanceName += (char)*p2;
while (!instanceName.empty() &&
!((instanceName[0] >= 'A' && instanceName[0] <= 'Z') ||
(instanceName[0] >= 'a' && instanceName[0] <= 'z') ||
(instanceName[0] >= '0' && instanceName[0] <= '9') ||
instanceName[0] == '_')) {
instanceName.erase(0, 1);
}
}
}
std::cout << " PlaceObject70 depth=" << depth << " class=\"" << className << "\"";
if (!instanceName.empty()) std::cout << " instance=\"" << instanceName << "\"";
std::cout << " tagLen=" << tag.length << "\n";
if (flags & 0x01) {
displayElements.erase(depth);
} else {
XuiElement el;
if (className.find("Button") != std::string::npos ||
className.find("button") != std::string::npos) {
el.className = "XuiButton";
} else if (className.find("Label") != std::string::npos ||
className.find("Text") != std::string::npos ||
className.find("text") != std::string::npos) {
el.className = "XuiLabel";
} else if (className.find("Image") != std::string::npos ||
className.find("Graphic") != std::string::npos) {
el.className = "XuiImage";
} else if (className.find("Scene") != std::string::npos ||
className.find("Menu") != std::string::npos ||
className.find("Panel") != std::string::npos ||
className.find("Dialog") != std::string::npos ||
className.find("Canvas") != std::string::npos) {
el.className = "XuiScene";
} else {
el.className = "XuiControl";
}
std::string id = !instanceName.empty() ? instanceName : className;
addProp(el, "Id", id);
addProp(el, "ClassName", className);
addProp(el, "X", "0.000000");
addProp(el, "Y", "0.000000");
addProp(el, "Width", floatStr((float)parser.frameSize.xMax / 10.0f));
addProp(el, "Height", floatStr((float)parser.frameSize.yMax / 10.0f));
displayElements[depth] = el;
}
break;
}
}
}
for (auto& de : displayElements) {
root.children.push_back(de.second);
}
std::ofstream ofs(outPath);
if (!ofs) {
std::cerr << "Error: cannot write to '" << outPath << "'\n";
return 1;
}
writeXuiElement(ofs, root, 0);
std::cout << "Wrote " << outPath << "\n";
return 0;
}
}
int main(int argc, char* argv[]) {
if (argc < 2) {
usage();
return 1;
}
std::string cmd = lower(argv[1]);
if (cmd == "about") return cmdAbout();
if (cmd == "info") {
if (argc < 3) { std::cerr << "Usage: xuitool info <file>\n"; return 1; }
return cmdInfo(argv[2]);
}
if (cmd == "validate") {
if (argc < 3) { std::cerr << "Usage: xuitool validate <file>\n"; return 1; }
return cmdValidate(argv[2]);
}
if (cmd == "conv") return cmdConv(argc, argv);
if (cmd == "massconv") return cmdMassConv(argc, argv);
if (cmd == "swf2xui") return cmdSwf2Xui(argc - 1, argv + 1);
std::cerr << "Unknown command: " << argv[1] << "\n";
usage();
return 1;
}