/** * Reads the next script element from the stream. * This could be an entire command or a single parameter. */ ScriptElement NextScriptElement(Stream s) { BinaryReader br = new BinaryReader(s); ScriptElement elem = null; // read the next data element. DataElement cmd = NextDataElement(s); // if no data element could be found, ... if (cmd == null) { // ...return nothing. return(null); } // if the element is... switch (cmd.type) { // ...a function call, then... case DataElement.TYPE_FUNC: { // ...determine how many parameters the function call takes. int paramCount = br.ReadInt32(); ScriptElement[] parameters = new ScriptElement[paramCount]; // for each parameter... for (var j = 0; j < paramCount; j++) { // ...read it's value DataElement param = NextDataElement(s); // if the parameter is a temporary variable reference, then... if (param is VarTempRefDataElement) { // ...replace it by the appropriate substitution expression. parameters[j] = substituteExpressions[((VarTempRefDataElement)param).id]; // after that, continue with the next parameter. continue; } // if the function is called... // ..."StrOut" and this parameter is a string, ... if ((((FuncDataElement)cmd).name.Equals("StrOut") || ((FuncDataElement)cmd).name.Equals("StrOutNW")) && param is StringDataElement) { // (...then this is a line of text to be displayed.) // (we want to store those in a separate file to make it easier to translate them.) // get the text string line = ((StringDataElement)param).data; // generate a new line number string id = "L" + (++lineCount); // add those to the list of known strings... stringTable.Add(id, new ScriptLine(fontID, speaker, line)); // ...and replace the string parameter with reference to the line number parameters[j] = new DataScriptElement(new ExternalStringDataElement(id, stringTable)); } // ..."StrOutNWC", ... else if (((FuncDataElement)cmd).name.Equals("StrOutNWC")) { // (...then this parameter represents a name.) // if the parameter is... // ...a string, then... if (param is StringDataElement) { // (...we want to store it separately to make it easier to translate.) string name = ((StringDataElement)param).data; // set the current speaker to this name. speaker = name; string id = null; // for each entry on our list of known strings... foreach (var entry in stringTable) { // ...check if it is a name. if (entry.Key[0] == 'N' && entry.Value.Text.Equals(name)) { // if it is, remember it's ID... id = entry.Key; // ...and stop searching. break; } } // if you didn't find a matching ID, ... if (id == null) { // generate a new one... id = "N" + (++nameCount); // ...and add it - together with the name - to the list of known strings. stringTable.Add(id, new ScriptLine(-1, null, name)); } // finally, replace the parameter by a reference to the name ID. parameters[j] = new DataScriptElement(new ExternalStringDataElement(id, stringTable)); } // ...a variable string reference, then... else if (param is VarStringRefDataElement) { // ...set the current speaker to "me"... speaker = "me"; // ...and keep the parameter as it is. parameters[j] = new DataScriptElement(param); } // ...something else, ... else { // ...then we just keep it as it is. parameters[j] = new DataScriptElement(param); } } // ...none of the above, ... else { // ...then we just keep the parameter as it is. parameters[j] = new DataScriptElement(param); } } // if the function is called "PF" (PrintFlush?), then... if (((FuncDataElement)cmd).name.Equals("PF")) { // ...reset the current speaker. speaker = ""; } // if the function is called "FontSet" and the second parameter is an integer, then... if (((FuncDataElement)cmd).name.Equals("FontSet") && parameters.Length >= 2 && parameters[1] is DataScriptElement dataElem && dataElem.elem is IntDataElement intElem) { // ...keep track of the current font fontID = intElem.data; } // save the current position in the code stream. long pos = s.Position; // read the next data element DataElement next = NextDataElement(s); // check if the next data element is a opening control flow element. // if it is, then... if (next is ControlDataElement && ((ControlDataElement)next).name.Equals("{")) { // ...create an empty list of script elements. List <ScriptElement> body = new List <ScriptElement>(); ScriptElement cur; // (read script elements and add them to the list of commands until you encounter a closing flow control element.) // repeat: while (true) { // read the next script element. cur = NextScriptElement(s); // if it is a closing flow control element, ... if (cur is JumpLabelScriptElement && ((JumpLabelScriptElement)cur).name.Equals("}")) { // ...stop repeating immediately break; } // otherwise, add it to the list of commands. body.Add(cur); } // if the function is a branching operation, then... if (((FuncDataElement)cmd).name.Equals("if")) { // ...save the current position in the code stream. pos = s.Position; // read the next data element next = NextDataElement(s); // check if the next data element is a else clause. // if it is, then... if (next is ControlDataElement && ((ControlDataElement)next).name.Equals("else")) { // ...discard the opening flow control element. NextDataElement(s); // ...create an empty list of script elements. List <ScriptElement> falsebody = new List <ScriptElement>(); // (read script elements and add them to the list of commands until you encounter a closing flow control element.) // repeat: while (true) { // read the next script element. cur = NextScriptElement(s); // if it is a closing flow control element, ... if (cur is JumpLabelScriptElement && ((JumpLabelScriptElement)cur).name.Equals("}")) { // ...stop repeating immediately break; } // otherwise, add it to the list of commands. falsebody.Add(cur); } // use the first (and only) parameter of the function call as branching condition, wrap it - together with the code blocks - in a script element and save it as the result. elem = new BranchScriptElement(parameters[0], body, falsebody); } // it it's not, then... else { // ...jump back to the previously saved code position. s.Seek(pos, SeekOrigin.Begin); // use the first (and only) parameter of the function call as branching condition, wrap it - together with the code block - in a script element and save it as the result. elem = new BranchScriptElement(parameters[0], body, null); } } // if it it not, then else { elem = new SwitchFunctionScriptElement(((FuncDataElement)cmd).name, parameters, body); } } // if not, ... else { // ...jump back to the previously saved code position. s.Seek(pos, SeekOrigin.Begin); // wrap the method name and and list of parameters in a script element and save it as the result. elem = new FuncCallScriptElement(((FuncDataElement)cmd).name, parameters); } break; } // ...a flow control element, then... case DataElement.TYPE_CTRL: { // ...create a new jump label with the same name and save it as the result. elem = new JumpLabelScriptElement(((ControlDataElement)cmd).name); break; } // ...a variable int or string reference, then... case DataElement.TYPE_VINT: case DataElement.TYPE_VSTR: { // (...we expect an assignment operation to follow.) // read assigned expression. ScriptElement expression = NextAssignment(s); // wrap the variable int/string reference and the expression in a new script element and save it as the result. elem = new AssignmentScriptElement(cmd, expression); break; } // ...a temporary variable id, then... case DataElement.TYPE_VTMP: { // (...we expect an assignment operation to follow.) // (after that we expect a command which uses this temporary variable.) // read assigned expression. ScriptElement expression = NextAssignment(s); // set the current temp variable expression to the one we just read... substituteExpressions[((VarTempRefDataElement)cmd).id] = expression; // ...and return nothing this time. break; } // ...an integer constant, then... case DataElement.TYPE_CINT: { // (...we have reached a very confusing part of this binary script format.) // (we expect an assignment to follow, basically "0 = <something>".) // (this sets the current flag pointer (represented by the 0) to <something>.) // (the flag pointed to by the pointer can be a accessed by using flag_$, globalflag_$, str_$ or globalstr_$.) // read the next script element (the expected assignment). FuncCallScriptElement assignment = NextScriptElement(s) as FuncCallScriptElement; ScriptElement expression = null; // if the assignment has... // ...more than 1 element, ... if (assignment.parameters.Length > 1) { // ...then create an expression from those. expression = new ExpressionScriptElement(assignment.parameters); } // ...exactly 1 element, ... else if (assignment.parameters.Length == 1) { // ...then it already is a valid expression. expression = assignment.parameters[0]; } // ...no elements, ... else { // ...then read the next script element and use it as expression. expression = NextScriptElement(s); } // wrap the integer and the expression in a new script element and save it as the result. elem = new AssignmentScriptElement(cmd, expression); break; } // ...something else, ... default: { // the script is probably damaged, so we throw an error. throw new Exception("Script format invalid"); } } // return the saved result return(elem); }
ScriptElement NextAssignment(Stream s) { // read the next script element (the expected assignment). FuncCallScriptElement assignment = NextScriptElement(s) as FuncCallScriptElement; ScriptElement[] parameters = new ScriptElement[assignment.parameters.Length]; // go through all parameters of the assignment. for (int i = 0; i < assignment.parameters.Length; i++) { ScriptElement param = assignment.parameters[i]; // if the parameter is... // ...a string constant, then... if (param is DataScriptElement && ((DataScriptElement)param).elem is StringDataElement) { // ...retrieve it's value. string str = ((StringDataElement)((DataScriptElement)param).elem).data; // if it contains only whitespace, if it is a number, or if it is part of a file name, then... if (str.Trim().Length == 0 || str.All(char.IsDigit) || str.Contains("\\") || str.ToLower().Contains(".yk") || str.ToLower().Contains(".ogg")) { // ...just use it in the expression. parameters[i] = param; } // otherwise... else { string id = null; // ...generate a new one... id = "S" + (++stringCount); // ...and add it to the list of known strings. stringTable.Add(id, new ScriptLine(-1, null, str)); // then use an external string reference as the new expression. parameters[i] = new DataScriptElement(new ExternalStringDataElement(id, stringTable)); } } // ...a temporary variable reference, then... else if (param is DataScriptElement && ((DataScriptElement)param).elem is VarTempRefDataElement) { // ...replace it by the current substitution expression. parameters[i] = substituteExpressions[((VarTempRefDataElement)((DataScriptElement)param).elem).id]; } // ...something else, then... else { // ...just use it in the expression. parameters[i] = param; } } ScriptElement expression = null; // if the assignment has... // ...more than 1 elements, then... if (parameters.Length > 1) { // ...create an expression from those. expression = new ExpressionScriptElement(parameters); } // ...exactly 1 element, then... else if (assignment.parameters.Length == 1) { expression = parameters[0]; } // ...no elements, ... else { // ...then read the next script element and use it as expression. expression = NextScriptElement(s); } return(expression); }