0

I was using a simple serializer to load an xml file to my game (I'm implementing mod support). The way I had it first (it's not my own serializer) was like this:

public static EnemyList Load(string path)
{
    TextAsset _xml = Resources.Load<TextAsset>(path);

    XmlSerializer serializer = new XmlSerializer(typeof(EnemyList));

    StringReader reader = new StringReader(_xml.text);

    EnemyList enemies = serializer.Deserialize(reader) as EnemyList;

    reader.Close();

    return enemies;
}

The problem with that is that "Resources.Load" is being used. Since I want the players to write/install mods the Resources folder can't be used here (because as far as I know they can't access the Resources folder). Therefore I created a "Mods" folder in the build folder and in that Mods folder other peoples folders (for example if I would make a mod I would have a folder like "MyMod" and that folder would have other folders like "entities" with an "entities.xml" file) are located. To get all the files from a folder I used DirectoryInfo. And here is my problem: I'm trying to change the serializer to work with DirectoryInfo. This is the code so far:

public static EnemyList LoadXml(string path)
{
    DirectoryInfo direcInfo = new DirectoryInfo(path);
    FileInfo[] fileInfo = direcInfo.GetFiles();

    XmlSerializer serializer = new XmlSerializer(typeof(EnemyList));

    StringReader reader = new StringReader(fileInfo[0].ToString());

    EnemyList enemies = serializer.Deserialize(reader) as EnemyList;

    reader.Close();

    return enemies;
}

but when I start the game I'm getting the Error: XmlException: Data at the root level is invalid. Line 1, position 1. I've also tried things like File.ReadAllText(path) but I'm getting an "unauthorizedaccessexception". When I googled that problem I found out that I need to specify a file in the path and not the directory (not just Mods/entities but Mods/entities/entities.xml) but I don't want to just read one single file. I want to ready every xml file that is in there. And even if I change it to entities.xml I'm still getting an Error IOException: Error 267 (couldn't find any answers to fix that)

I hope someone can help me with that. I've already googled but the people on the forum did completely different things, I couldn't apply that to my case.

Thank you in advance!

In case the xml is needed:

enter image description here

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Are you trying to parse a string? You first need to create a stream. Use StringReader reader = new StringReader(string) and then serialize the StringReader (which is a stream) – jdweng Sep 03 '20 at 11:39
  • How would I use that streamer then? I've looked up how to stream a StringReader and in another post in Stackoverflow I've found something [link](https://stackoverflow.com/questions/1879395/how-do-i-generate-a-stream-from-a-string) And when I called that method I used reader.ToString() but I'm still getting an IOExceptin Error 267 – Perion Termia Sep 03 '20 at 11:54
  • XmlReader xReader = XmlReader.Create(reader); – jdweng Sep 03 '20 at 12:07
  • Please post your code snippets here as **text** not links and format is using the `{ }` button! – derHugo Sep 03 '20 at 12:17
  • Could you show us what exactly you are passing into `path`? It's probably not the correct folder path. Please show us the result of `Debug.Log(path);` .. I'm pretty sure `Mods/entities` would not work since this folder lies outside of your `Assets`. Try to put them into `Assets/StreamingAssets` and rather use `new DirectoryInfo(Path.Combine(Application.streamingAssetsPath, "Mods", "entities"));` – derHugo Sep 03 '20 at 12:21
  • The path iself shouldn't be a problem. This is the path: `public const string enemyXMLPath = "Nations of Cubion/Mods/entities";` When I printed the files in the path doing this: `enemies.info = new DirectoryInfo(enemyXMLPath); FileInfo[] fileInfo = enemies.info.GetFiles(); foreach (FileInfo file in fileInfo) { print("FILE INFOS: " + file); }` It printed the xml file in the folder. The reason for me not using a folder that is in the Assets folder is because players can't access that folder. – Perion Termia Sep 03 '20 at 12:41
  • And sorry for not posting the codes in code format but as hastebin links, usually on the discords that I am, the people like it more if codes are posted with hastebin/pastebin etc. – Perion Termia Sep 03 '20 at 12:44
  • If you want a folder Players can access then use the [`Application.persistentDataPath`](https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html) instead ... usually apps are kind of sandboxed to their own folder unless you explicitly give them the permission to read other files – derHugo Sep 03 '20 at 13:19

1 Answers1

0

First you need to get the path to the directory you want to search in and then loop through each XML File in that Directory. With SearchOption.AllDirectory you also search in all sub directories that are in the current directory.

Loop through your Directory:

string folderWithModFiles = "YourPath";
List<Enemy> enemylist = new List<Enemy>();

foreach (string xmlFile in Directory.EnumerateFiles(folderWithModFiles, "*.xml", SearchOption.AllDirectories)) {
    try {
        enemylist = SerializeXML(xmlFile);
    }
    catch(Exception e) { 
       Debug.LogException(e); 
    }
}

The try catch is to ensure that no faulty XML files are read.

De-serializing XML File:

private List<Enemy> SerializeXMLS(string filePath) {
        using (FileStream fileStream = System.IO.File.Open(filePath, FileMode.Open)) {
            var serializer = new XmlSerializer(typeof(EnemyList));
            var enemyList = serializer.Deserialize(fileStream) as EnemyList;

            // Read the data you need from the XML File
            // If the user didn't specify certain things he need to specify,
            // you can throw an ArgumentExpection and the file will be skipped
            return enemyList.ListOfEnemies;
        }
    }
}

Example of the EnemyList XML Parse File:

[XmlRoot("Entities")]
[CreateAssetMenu(menuName = "Nations of Cubion/Item/List of Enemies")]
public class EnemyList {
    [XmlArray]
    [XmlArrayItem("Entity")]
    public List<Entity> ListOfEnemies {
        get; set;
    }
}

public class Entity{
    [XmlAttribute("name")]
    public string name{
        get; set;
    }

    [XmlElement("mesh")]
    public string mesh {
        get; set;
    }

    [XmlElement("material")]
    public string material {
        get; set;
    }

    [XmlElement("enemyType")]
    public string enemyType {
        get; set;
    }

   [XmlElement("elementType")]
    public string elementType {
        get; set;
    }

    [XmlElement("maxHealth")]
    public string maxHealth {
        get; set;
    }
}

IndieGameDev
  • 2,905
  • 3
  • 16
  • 29
  • I'm suspecting though that the issue already lies in the folder path itself OP is using ... also maybe suggesting an empty `catch { }` isn't the best thing to do ... at least do `catch(Exception e) { Debug.LogException(e); }` – derHugo Sep 03 '20 at 12:27
  • We'll see if it doesn't help OP there's always the delete button :O – IndieGameDev Sep 03 '20 at 12:30
  • Alright so I've tried your solution (I posted the code in the EnemyLoader script, that derives from Monobehaviour). There is one small problem I think: I had no errors but nothing happened. I think the problem is because in my EnemyList script (which is a ScriptableObject) I have a List of Enemies (List) so if I return enemyList.listOfEnemies it won't work. I'm getting the "cannot implicitly convert System.Collections.Generic.List into "EnemyList" error (and changing the function type from EnemyList to Enemy resultet in the same Error but this time cannot convert to "Enemy" ) – Perion Termia Sep 03 '20 at 13:10
  • But I guess I need to acces the listOfEnemies variable since the xml is accessing that List as an Element and also the Root "Entities" which is the EnemyList script. – Perion Termia Sep 03 '20 at 13:13
  • The code I've posted was just and example on how to serialize because I don't know how your XML structure looks like (the EnemyList.cs File would be nice to have). – IndieGameDev Sep 03 '20 at 13:17
  • Yeah sorry, shoulv'e posted it. I'll post it in hastebin since it looks more structured than posting it in the comments as code: https://hastebin.com/ativiqiteg.cs and if you need it here is the EnemyLoader.cs: https://hastebin.com/yoqobemahe.cs – Perion Termia Sep 03 '20 at 13:21
  • I added an Example List don't know if it'll help. – IndieGameDev Sep 03 '20 at 13:41
  • Yeah thank you that one acutally worked :D. just a quick question: if I need to load another file type (for example a prefab file) do I also need to do something similar? Because the way I did it first was to just Load from the Resources folder. `Object prefab = Resources.Load(enemies.listOfEnemies[i].property.mesh);` To Load it from another directory like "Mods/prefabs/" would I need something similar to what I used for the xml? In the SerializeXMLS function we used `new XmlSerializer` and I don't know how I would apply that to an "Object" – Perion Termia Sep 03 '20 at 14:42
  • Loading more than primitive data types in xml is pretty hard (string, bool, int, float etc.). But shouldn't enemies[i].mesh work? – IndieGameDev Sep 03 '20 at 15:35
  • hey, just wanted to let you know, I've managed to do that with the enemies. I'm doing with Assetbundles now. Works great :) – Perion Termia Sep 07 '20 at 12:32