using System; using System.Collections.Generic; using System.Text; using System.Xml; using System.IO; namespace SCJMapper_V2.SC { /// /// should read the default profile - may be replacing the MappingVars once /// Reads the profile as is and creates a CSV Map (internal legacy format) for clients /// default bindings are read as AC1 style items i.e. not containing the device /// class DProfileReader { private static readonly log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod( ).DeclaringType ); public bool ValidContent { get; set; } private Stack m_nodeNameStack = null; // element name stack - keeping track where we are // state for the parser enum EState { idle = 0, inActionMap, } private EState m_state = EState.idle; // an action map and its actions class ProfileAction { public string name { get; set; } // the action name public string devID { get; set; } // the input method K,J,X,P private string m_defBinding = ""; // NOTE: this is AC1 style in the Profile - need to conver later when dumping out public string defBinding { get { return m_defBinding; } set { m_defBinding = value; } } // DONT! need to clean this one, found spaces... private ActivationMode m_defActivationMode = ActivationMode.Default; public ActivationMode defActivationMode { get { return m_defActivationMode; } set { m_defActivationMode = value; } } public string keyName { get { return devID + name; } } // prep for TreView usage - create a key from input+name } class ActionMap : List // carries the action list { public string name { get; set; } // the map name static int ContainsLoop( List list, string value ) { for ( int i = 0; i < list.Count; i++ ) { if ( list[i].keyName == value ) { return i; } } return -1; } public new int IndexOf( ProfileAction pact ) { return ContainsLoop( this, pact.keyName ); } public new bool Contains( ProfileAction pact ) { return ( ContainsLoop( this, pact.keyName ) >= 0 ); } public new void Add( ProfileAction pact ) { // only get the latest .. if ( this.Contains( pact ) ) this.RemoveAt( IndexOf( pact ) ); base.Add( pact ); } }; Dictionary m_aMap = null; // key would be the actionmap name ActionMap m_currentMap = null; // ****************** CLASS ********************** /// /// cTor /// public DProfileReader() { ValidContent = false; // default } /// /// Returns the collected actionmaps as CSV (same format as MappingVars) /// i.e. one line per actionmap where the first element is the actionmap and following are actions;defaultBinding lead by the input Key in uppercase (JKXP) /// public string CSVMap { get { log.Debug( "DProfileReader.CSVMap - Entry" ); string buf = ""; foreach ( ActionMap am in m_aMap.Values ) { buf += am.name + ";"; foreach ( ProfileAction a in am ) { buf += a.keyName + ";" + a.defBinding + ";" + a.defActivationMode.Name + ";" + a.defActivationMode.MultiTap.ToString( ) + ";"; // add default binding + activation mode to the CSV } buf += string.Format( "\n" ); } return buf; } } /// /// Assumes to be in an action element /// retrieves the attributes and collects the various control=binding pairs /// /// An XML reader @ StartElement private void CollectActions( Dictionary attr ) { //first find an ActivationMode if there is - applies to all actions string actModeName = ActivationMode.Default.Name; string multiTap = "0"; // this can be an Activation Mode OR a multitap // if there is an activationMode the multiTap remains 0 // if no ActivationMode is given, multitap is 1 or may be 2... if ( attr.ContainsKey( "ActivationMode" ) ) { actModeName = attr["ActivationMode"]; multiTap = ActivationModes.Instance.MultiTapFor( actModeName ).ToString( ); // given by the already collected items } else { // name remains default - we handle multiTaps only here multiTap = "1"; // default if not changed in the action to may be 2 or so.. if ( attr.ContainsKey( "multiTap" ) ) { multiTap = attr["multiTap"]; } } ActivationMode actMode = new ActivationMode( actModeName, int.Parse( multiTap ) ); // should be a valid ActivationMode for this action // we collect actions for each input ie for K,J,X and M if ( attr.ContainsKey( "joystick" ) ) { ProfileAction ac = new ProfileAction( ); ac.name = attr["name"]; ac.devID = "J"; ac.defBinding = attr["joystick"]; ac.defActivationMode = actMode; if ( ac.defBinding == " " ) { ac.defBinding = Joystick.JoystickCls.BlendedInput; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } else if ( !string.IsNullOrEmpty( ac.defBinding ) ) { ac.defBinding = "js1_" + ac.defBinding; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } } if ( attr.ContainsKey( "keyboard" ) ) { ProfileAction ac = new ProfileAction( ); ac.name = attr["name"]; ac.devID = "K"; ac.defBinding = attr["keyboard"]; ac.defActivationMode = actMode; if ( ac.defBinding == " " ) { ac.defBinding = Keyboard.KeyboardCls.BlendedInput; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } else if ( !string.IsNullOrEmpty( ac.defBinding ) ) { ac.defBinding = "kb1_" + ac.defBinding; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } } if ( attr.ContainsKey( "mouse" ) ) { // 20151220BM: add mouse device (from AC 2.0 defaultProfile usage) ProfileAction ac = new ProfileAction( ); ac.name = attr["name"]; ac.devID = "M"; ac.defBinding = attr["mouse"]; ac.defActivationMode = actMode; if ( ac.defBinding == " " ) { ac.defBinding = Mouse.MouseCls.BlendedInput; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } else if ( !string.IsNullOrEmpty( ac.defBinding ) ) { ac.defBinding = "mo1_" + ac.defBinding; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } } if ( attr.ContainsKey( "xboxpad" ) ) { ProfileAction ac = new ProfileAction( ); ac.name = attr["name"]; ac.devID = "X"; ac.defBinding = attr["xboxpad"]; ac.defActivationMode = actMode; if ( ac.defBinding == " " ) { ac.defBinding = Gamepad.GamepadCls.BlendedInput; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } else if ( !string.IsNullOrEmpty( ac.defBinding ) ) { ac.defBinding = "xi1_" + ac.defBinding; m_currentMap.Add( ac ); // finally add it to the current map if it was bound } } if ( attr.ContainsKey( "ps3pad" ) ) { // ignore } } /// /// Read one 'empty' XML element /// /// /// /// /// An XML reader @ StartElement /// True if reading can continue private bool ReadEmptyElement( XmlReader xr ) { Dictionary attr = new Dictionary( ); string eName = xr.Name; switch ( xr.NodeType ) { case XmlNodeType.Element: //Console.Write( "<{0}", xr.Name ); while ( xr.MoveToNextAttribute( ) ) { attr.Add( xr.Name, xr.Value ); // save the attributes //Console.Write( " {0}='{1}'", xr.Name, xr.Value ); } if ( m_state == EState.inActionMap ) { // processing a valid action map - collect actions if ( eName.ToLower( ) == "action" ) { // this is an action.. - collect it CollectActions( attr ); } }// if inmap //Console.Write( ">\n" ); break; case XmlNodeType.Text: //Console.Write( xr.Value ); break; case XmlNodeType.EndElement: //Console.Write( "\n", xr.Name ); break; } return true; } /// /// Reads an action sub element /// /// An XML reader @ StartElement private void ReadActionSub( XmlReader xr, string actionName, string device ) { /* or or or */ bool done = false; do { xr.Read( ); // get next element Dictionary attr = new Dictionary( ); // add what is not contained in the structure we are about to parse attr.Add( "name", actionName ); // actionName is in the outer element string eName = xr.Name; // this is either the device or inputdata if there are multiple entries // read attributes if any while ( xr.MoveToNextAttribute( ) ) { attr.Add( xr.Name, xr.Value ); // save the attributes //Console.Write( " {0}='{1}'", xr.Name, xr.Value ); } xr.MoveToElement( ); // backup // Have to add the device, otherwise the following does not work.. if ( attr.ContainsKey( "input" ) ) { if ( !string.IsNullOrEmpty( device ) ) { // device is given i.e. enclosing the input statements // 20170512 - some keyboard entries are listed with mouse input ... ?!? // we substitute and add such mouse entries - don't know what the game does later... string ip = attr["input"]; if ( !string.IsNullOrEmpty( ip ) ) { if ( ip.StartsWith( "mouse" ) ) { attr.Add( "mouse", attr["input"] ); // override the device given } else { attr.Add( device, attr["input"] ); // if the device is given, use it } } } else { string ip = attr["input"]; if ( !string.IsNullOrEmpty( ip ) ) { attr.Add( eName, ip ); // else it should be the eName element } } } // the element name is a control if ( xr.NodeType == XmlNodeType.EndElement ) { done = ( xr.Name == m_nodeNameStack.Peek( ) ); // EXIT if the end element matches the entry } else if ( xr.IsEmptyElement ) { // an attribute only element CollectActions( attr ); } else { // one with subelements again m_nodeNameStack.Push( xr.Name ); // recursive .. push element name to terminate later (this is i.e. keyboard) ReadActionSub( xr, actionName, xr.Name ); } } while ( !done ); m_nodeNameStack.Pop( ); // action is finished } /// /// Read one XML element /// /// /// [ Xelement ] /// /// /// /// An XML reader @ StartElement /// True if reading can continue private bool ReadElement( XmlReader xr ) { Dictionary attr = new Dictionary( ); string eName = xr.Name; switch ( xr.NodeType ) { case XmlNodeType.Element: //Console.Write( "<{0}", xr.Name ); while ( xr.MoveToNextAttribute( ) ) { attr.Add( xr.Name, xr.Value ); // save the attributes //Console.Write( " {0}='{1}'", xr.Name, xr.Value ); } // now here we could have an actionmap start if ( m_state == EState.idle ) { if ( m_nodeNameStack.Peek( ).ToLower( ) == "actionmap" ) { // check for a valid one string mapName = attr["name"]; string item = Array.Find( ActionMapsCls.ActionMaps, delegate ( string sstr ) { return sstr == mapName; } ); if ( !string.IsNullOrEmpty( item ) ) { // finally.... it is a valid actionmap m_currentMap = new ActionMap( ); m_currentMap.name = mapName; if ( !m_aMap.ContainsKey( mapName ) ) { //20170325 - fix multiple map names - don't add the second, third etc. (CIG bug..) m_aMap.Add( mapName, m_currentMap ); // add to our inventory }else { log.DebugFormat( "ReadElement: IGNORED duplicate map with name: {0}", mapName ); } m_state = EState.inActionMap; // now we are in and processing the map } } } else if ( m_state == EState.inActionMap ) { // processing a valid action map - collect actions if ( eName.ToLower( ) == "action" ) { // this is an action.. - collect it CollectActions( attr ); ReadActionSub( xr, attr["name"], "" ); // a non empty action element may have a sub element (but no device yet) } } //Console.Write( ">\n" ); break; case XmlNodeType.Text: //Console.Write( xr.Value ); break; case XmlNodeType.EndElement: //Console.Write( "\n", xr.Name ); break; } return true; } /// /// Read the xml part /// /// /// private bool ReadXML( XmlReader xr ) { log.Debug( "DProfileReader.ReadXML - Entry" ); bool retVal = true; try { do { if ( xr.IsStartElement( ) ) { m_nodeNameStack.Push( xr.Name ); if ( xr.IsEmptyElement ) { retVal = retVal && ReadEmptyElement( xr ); m_nodeNameStack.Pop( ); // empty ones end inline } else { retVal = retVal && ReadElement( xr ); } } else if ( xr.NodeType == XmlNodeType.EndElement ) { //Console.Write( "\n", xr.Name ); string exitElement = m_nodeNameStack.Pop( ); if ( m_state == EState.inActionMap ) if ( exitElement.ToLower( ) == "actionmap" ) m_state = EState.idle; // finished } } while ( xr.Read( ) ); if ( m_nodeNameStack.Count == 0 ) return retVal && true; else return false; } catch ( Exception ex ) { // get any exceptions from reading log.Error( "DProfileReader.ReadXML - unexpected", ex ); return false; } } private bool ReadActivationModes( XmlReader xr ) { log.Debug( "DProfileReader.ReadActivationModes - Entry" ); try { xr.ReadToFollowing( "ActivationModes" ); xr.ReadToDescendant( "ActivationMode" ); do { if ( xr.NodeType == XmlNodeType.EndElement ) { xr.Read( ); break; // finished } string name = xr["name"]; string mTap = xr["multiTap"]; if ( !string.IsNullOrEmpty( name ) ) ActivationModes.Instance.Add( new ActivationMode( name, int.Parse( mTap ) ) ); } while ( xr.Read( ) ); return true; } catch ( Exception ex ) { // get any exceptions from reading log.Error( "DProfileReader.ReadXML - unexpected", ex ); return false; } } /// /// Read the defaultProfile.xml - do some sanity check /// /// the XML action defaultProfile Content /// True if an action was decoded public bool fromXML( string xml ) { log.Debug( "DProfileReader.fromXML - Entry" ); if ( ActionMapsCls.ActionMaps.Length == 0 ) ActionMapsCls.LoadSupportedActionMaps( ActionMapList( xml ) ); // make sure we have them loaded ( refactoring to get a singleton or so...) XmlReaderSettings settings = new XmlReaderSettings( ); settings.ConformanceLevel = ConformanceLevel.Fragment; settings.IgnoreWhitespace = true; settings.IgnoreComments = true; XmlReader reader = XmlReader.Create( new StringReader( xml ), settings ); m_nodeNameStack = new Stack( ); m_aMap = new Dictionary( ); // init the activation modes singleton ActivationModes.Instance.Clear( ); ActivationModes.Instance.Add( ActivationMode.Default ); ValidContent = true; // init reader.Read( ); ValidContent &= ReadActivationModes( reader ); m_nodeNameStack.Push( "profile" ); // we are already in the XML now ValidContent &= ReadXML( reader ); return ValidContent; } /// /// Returns the acionmals contained in the profile xml string /// /// A default profile XML string /// A filled SCActionMapList object public SCActionMapList ActionMapList( string xml ) { SCActionMapList aml = new SCActionMapList( ); log.Debug( "DProfileReader.ActionMapList - Entry" ); XmlReaderSettings settings = new XmlReaderSettings( ); settings.ConformanceLevel = ConformanceLevel.Fragment; settings.IgnoreWhitespace = true; settings.IgnoreComments = true; XmlReader reader = XmlReader.Create( new StringReader( xml ), settings ); reader.Read( ); if ( !reader.ReadToFollowing( "actionmap" ) ) return aml; // ERROR empty one.. do { string attr = reader["name"]; if ( !string.IsNullOrEmpty( attr ) ) aml.AddActionMap( attr ); } while ( reader.ReadToFollowing( "actionmap" ) ); return aml; } } }