plaxtony
Version:
Static code analysis of SC2 Galaxy Script
576 lines • 18.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.XMLReader = exports.TriggerStore = exports.Library = exports.ElementContainer = exports.Label = exports.Category = exports.FunctionCall = exports.Param = exports.PresetValue = exports.Preset = exports.FunctionDef = exports.ParamDef = exports.Element = exports.Tag = exports.ParameterType = exports.ElementReference = exports.ElementFlag = void 0;
const xml = require("xml2js");
var ElementFlag;
(function (ElementFlag) {
ElementFlag[ElementFlag["Native"] = 2] = "Native";
ElementFlag[ElementFlag["FuncAction"] = 4] = "FuncAction";
ElementFlag[ElementFlag["FuncCall"] = 8] = "FuncCall";
ElementFlag[ElementFlag["Event"] = 16] = "Event";
ElementFlag[ElementFlag["Template"] = 32] = "Template";
ElementFlag[ElementFlag["PresetGenConstVar"] = 64] = "PresetGenConstVar";
ElementFlag[ElementFlag["PresetCustom"] = 128] = "PresetCustom";
ElementFlag[ElementFlag["CustomScript"] = 256] = "CustomScript";
ElementFlag[ElementFlag["Operator"] = 512] = "Operator";
ElementFlag[ElementFlag["CustomAI"] = 1024] = "CustomAI";
ElementFlag[ElementFlag["SubFunctions"] = 2048] = "SubFunctions";
ElementFlag[ElementFlag["AllowBreak"] = 4096] = "AllowBreak";
ElementFlag[ElementFlag["Hidden"] = 8192] = "Hidden";
ElementFlag[ElementFlag["NoScriptPrefix"] = 16384] = "NoScriptPrefix";
ElementFlag[ElementFlag["Deprecated"] = 32768] = "Deprecated";
ElementFlag[ElementFlag["Internal"] = 65536] = "Internal";
ElementFlag[ElementFlag["Restricted"] = 131072] = "Restricted";
})(ElementFlag = exports.ElementFlag || (exports.ElementFlag = {}));
class ElementReference {
constructor(container) {
this.store = container;
}
link() {
return this.type.name + '/' + this.id;
}
globalLink() {
let link = this.link();
if (this.library) {
link = this.library + '/' + link;
}
return link;
}
resolve() {
if (this.library) {
const lib = this.store.getLibraries().get(this.library);
return lib.findElementById(this.link(), null);
}
return this.store.findElementById(this.link(), null);
// return this.container.findElementById(this.id, this.type) as T;
}
}
exports.ElementReference = ElementReference;
class ParameterType {
get galaxyType() {
switch (this.type) {
case 'anygamelink':
case 'gamelink':
case 'filepath':
case 'actormsg':
case 'aidef':
case 'modelanim':
case 'attributegame':
case 'attributeplayer':
case 'attributevalue':
case 'catalogentry':
case 'catalogfieldname':
case 'catalogfieldpath':
case 'reference':
case 'catalogscope':
case 'charge':
case 'convcharacter':
case 'convline':
case 'convstateindex':
case 'conversationtag':
case 'cooldown':
case 'fontstyle':
case 'gameoption':
case 'gameoptionvalue':
case 'modelcamera':
case 'timeofday':
case 'layoutframe':
case 'layoutframerel':
case 'userfield':
case 'userinstance':
case 'water':
{
return 'string';
}
case 'aidefwave':
case 'cinematic':
case 'conversation':
case 'reply':
case 'datatable':
case 'dialog':
case 'control':
case 'difficulty':
case 'objective':
case 'path':
case 'ping':
case 'planet':
case 'playercolor':
case 'portrait':
case 'transmission':
{
return 'int';
}
case 'animlengthquery':
{
return 'generichandle';
}
case 'targetfilter':
{
return 'unitfilter';
}
case 'preset':
return this.typeElement.resolve().baseType;
default:
return this.type;
}
}
}
exports.ParameterType = ParameterType;
class Tag {
link() {
return (this.libId ? this.libId + '/' : '') + (this.constructor.name + '/') + this.id;
}
toString() {
const parts = [];
if (this.libId) {
parts.push('lib' + this.libId);
}
const prefix = this.constructor.prefix;
if (prefix) {
parts.push(prefix);
}
parts.push(this.name ? this.name : this.id);
return parts.join('_');
}
textKey(kind) {
const parts = [];
parts.push(this.constructor.name);
parts.push(kind);
if (this.libId) {
parts.push(['lib', this.libId, this.id].join('_'));
}
else {
parts.push(this.id);
}
return parts.join('/');
}
}
exports.Tag = Tag;
class Element extends Tag {
constructor() {
super(...arguments);
this.items = [];
}
toString() {
if (this.flags & 2 /* Native */) {
return this.name ? this.name : this.id;
}
return super.toString();
}
}
exports.Element = Element;
class ParamDef extends Element {
get galaxyType() {
const rtype = this.type.galaxyType;
if (this.isReference) {
switch (rtype) {
case 'unit':
{
return 'unitref';
break;
}
}
}
return rtype;
}
}
exports.ParamDef = ParamDef;
class FunctionDef extends Element {
constructor() {
super(...arguments);
this.parameters = [];
this.eventResponses = [];
}
getParameters() {
return this.parameters.map((paramRef) => {
return paramRef.resolve();
});
}
}
exports.FunctionDef = FunctionDef;
FunctionDef.prefix = 'gf';
class Preset extends Element {
constructor() {
super(...arguments);
this.values = [];
}
}
exports.Preset = Preset;
class PresetValue extends Element {
}
exports.PresetValue = PresetValue;
class Param extends Element {
}
exports.Param = Param;
class FunctionCall extends Element {
}
exports.FunctionCall = FunctionCall;
class Category extends Element {
}
exports.Category = Category;
;
class Label extends Element {
}
exports.Label = Label;
;
const ElementClasses = {
ParamDef,
FunctionDef,
Preset,
PresetValue,
Param,
FunctionCall,
Category,
Label,
};
// export class TriggerExplorer {
// protected containers: ElementContainer;
// }
class ElementContainer {
constructor() {
this.items = [];
this.elements = new Map();
this.nameMap = new Map();
}
addElement(identifier, el) {
el.id = identifier;
if (el instanceof FunctionDef) {
this.nameMap.set(el.name, el);
}
this.elements.set(el.constructor.name + '/' + el.id, el);
}
findElementByName(name) {
return this.nameMap.get(name);
}
findPresetValueByStr(value) {
for (const el of this.elements.values()) {
if (!(el instanceof PresetValue))
continue;
if (el.value && el.value == value) {
return el;
}
}
return null;
}
findPresetByValue(value) {
for (const el of this.elements.values()) {
if (!(el instanceof Preset))
continue;
const belongsTo = el.values.find((localVal) => {
return value.id === localVal.id;
});
if (belongsTo) {
return el;
}
}
return null;
}
findElementById(id, type) {
if (type && type.name !== 'Element') {
id = type.name + '/' + id;
}
return this.elements.get(id);
}
getElements() {
return this.elements;
}
}
exports.ElementContainer = ElementContainer;
class Library extends ElementContainer {
constructor(id) {
super();
this.id = id;
}
addElement(identifier, el) {
el.libId = this.id;
super.addElement(identifier, el);
}
getId() {
return this.id;
}
}
exports.Library = Library;
class TriggerStore extends ElementContainer {
constructor() {
super(...arguments);
this.libraries = new Map();
this.unresolvedReferences = new Map();
}
addElementReference(ref) {
const link = ref.globalLink();
if (this.unresolvedReferences.has(link)) {
const refList = this.unresolvedReferences.get(link);
refList.push(ref);
}
else {
this.unresolvedReferences.set(link, [ref]);
}
}
addLibrary(library) {
this.libraries.set(library.getId(), library);
}
// public findElementById<T extends Element>(elementId: string, libraryId?: string): T | undefined {
// if (libraryId) {
// return this.libraries.get(libraryId).findElementById(elementId) as T;
// }
// return super.findElementById(elementId) as T;
// }
getLibraries() {
return this.libraries;
}
}
exports.TriggerStore = TriggerStore;
class XMLReader {
constructor(container) {
this.store = container;
}
parseReference(data, type) {
const ref = new ElementReference(this.store);
ref.id = data.$.Id;
if (data.$.Library) {
ref.library = data.$.Library;
}
ref.type = ElementClasses[type.name];
this.store.addElementReference(ref);
return ref;
}
parseParam(item) {
const element = new Param();
if (item.FunctionCall) {
element.functionCall = this.parseReference(item.FunctionCall[0], FunctionCall);
}
if (item.Preset) {
element.preset = this.parseReference(item.Preset[0], PresetValue);
}
if (item.Value) {
element.value = item.Value[0];
}
if (item.ValueType) {
element.valueType = item.ValueType[0].$.Type;
if (item.ValueGameType) {
// item.ValueGameType[0].$.Type;
}
}
if (item.ValueElement) {
element.valueElement = this.parseReference(item.ValueElement[0], Preset);
}
return element;
}
parseParamDef(item) {
const paramDef = new ParamDef();
paramDef.type = this.parseParameterType(item.ParameterType[0]);
if (item.Default) {
paramDef.default = this.parseReference(item.Default[0], Param);
}
if (item.ParamFlagReference) {
paramDef.isReference = true;
}
return paramDef;
}
parseFunctionCall(item) {
const element = new FunctionCall();
if (item.FunctionDef) {
// TODO: check one of the void story libraries - it doesn't have FunctionDef
element.functionDef = this.parseReference(item.FunctionDef[0], FunctionDef);
}
return element;
}
parseParameterType(item) {
const element = new ParameterType();
element.type = item.Type[0].$.Value;
if (element.type === 'gamelink' && item.GameType) {
element.gameType = item.GameType[0].$.Value;
}
if (element.type === 'preset') {
element.typeElement = this.parseReference(item.TypeElement[0], Preset);
}
return element;
}
parseCategory(item) {
const element = new Category();
return element;
}
parseLabel(item) {
const element = new Label();
if (item.Icon) {
element.icon = item.Icon[0];
}
if (item.Color) {
element.color = item.Color[0];
}
return element;
}
parseElement(item) {
let el;
switch (item.$.Type) {
case 'FunctionDef':
{
const func = el = new FunctionDef();
func.flags |= item.FlagNative ? 2 /* Native */ : 0;
func.flags |= item.FlagAction ? 4 /* FuncAction */ : 0;
func.flags |= item.FlagCall ? 8 /* FuncCall */ : 0;
func.flags |= item.FlagEvent ? 16 /* Event */ : 0;
func.flags |= item.Template ? 32 /* Template */ : 0;
func.flags |= item.FlagCustomScript ? 256 /* CustomScript */ : 0;
func.flags |= item.FlagOperator ? 512 /* Operator */ : 0;
func.flags |= item.FlagCustomAI ? 1024 /* CustomAI */ : 0;
func.flags |= item.FlagSubFunctions ? 2048 /* SubFunctions */ : 0;
func.flags |= item.FlagAllowBreak ? 4096 /* AllowBreak */ : 0;
func.flags |= item.FlagHidden ? 8192 /* Hidden */ : 0;
func.flags |= item.FlagNoScriptPrefix ? 16384 /* NoScriptPrefix */ : 0;
func.flags |= item.Deprecated ? 32768 /* Deprecated */ : 0;
func.flags |= item.Internal ? 65536 /* Internal */ : 0;
func.flags |= item.FlagRestricted ? 131072 /* Restricted */ : 0;
if (item.Parameter) {
for (const param of item.Parameter) {
if (param.$.Type === 'Comment')
continue;
func.parameters.push(this.parseReference(param, ParamDef));
}
}
if (item.ReturnType) {
func.returnType = this.parseParameterType(item.ReturnType[0]);
}
if (item.ScriptCode) {
func.scriptCode = item.ScriptCode[0];
const whitespace = func.scriptCode.match(/^\r?\n(\s+)/);
if (whitespace) {
func.scriptCode = func.scriptCode.trim().replace(/\r?\n/g, '\n').replace(new RegExp('^' + whitespace[1], 'gm'), '');
}
}
if (item.EventResponse) {
for (const er of item.EventResponse) {
func.eventResponses.push(this.parseReference(er, FunctionDef));
}
}
break;
}
case 'ParamDef':
{
el = this.parseParamDef(item);
break;
}
case 'Param':
el = this.parseParam(item);
break;
case 'FunctionCall':
el = this.parseFunctionCall(item);
break;
case 'Preset':
{
const preset = el = new Preset();
preset.flags |= item.PresetGenConstVar ? 64 /* PresetGenConstVar */ : 0;
preset.flags |= item.PresetCustom ? 128 /* PresetCustom */ : 0;
preset.baseType = item.BaseType[0].$.Value;
if (item.Item) {
for (const val of item.Item) {
if (val.$.Type !== 'PresetValue')
continue;
preset.values.push(this.parseReference(val, PresetValue));
}
}
// item.$.PresetCustom
// item.$.PresetGenConstVar
// item.$.PresetAsBits
break;
}
case 'PresetValue':
{
const presetValue = el = new PresetValue();
if (item.Value) {
presetValue.value = item.Value[0];
}
break;
}
case 'Category':
el = this.parseCategory(item);
break;
case 'Label':
el = this.parseLabel(item);
break;
default:
{
return null;
}
}
if (item.Item) {
el.items = this.parseItems(item);
}
if (item.Label) {
el.label = this.parseReference(item.Label[0], Label);
}
if (item.Identifier) {
el.name = item.Identifier[0];
}
return el;
}
parseItems(data) {
const elItems = [];
for (const itEntry of data.Item) {
const cls = ElementClasses[itEntry.$.Type];
if (!cls)
continue;
elItems.push(this.parseReference(itEntry, cls));
}
return elItems;
}
parseTree(data, container) {
if (data.Root && data.Root[0].Item) {
container.items = this.parseItems(data.Root[0]);
}
if (!data.Element)
return;
for (const item of data.Element) {
const el = this.parseElement(item);
if (el) {
container.addElement(item.$.Id, el);
}
}
}
parseLibrary(id, data) {
const lib = new Library(id);
this.parseTree(data, lib);
return lib;
}
loadXML(content) {
return new Promise((resolve, reject) => {
xml.parseString(content, (err, result) => {
if (err) {
reject(err);
}
else {
try {
resolve(result.TriggerData);
}
catch (err) {
reject(err);
}
}
});
});
}
async loadLibrary(content) {
const data = await this.loadXML(content);
const lib = this.parseLibrary(data.Standard[0].$.Id, data);
this.store.addLibrary(lib);
return lib;
}
async load(content, onlyLibraries = false) {
const data = await this.loadXML(content);
if (data.Library) {
for (const item of data.Library) {
const lib = this.parseLibrary(item.$.Id, item);
this.store.addLibrary(lib);
}
}
if (!onlyLibraries) {
this.parseTree(data, this.store);
}
return this.store;
}
}
exports.XMLReader = XMLReader;
// export function forEachElement(element: Element) {
// function visitElement()
// }
//# sourceMappingURL=trigger.js.map