Is using System.IO bad?

In my current project, I’m using BinaryReader and BinaryWriter, part of the System.IO namespace, to read and write to binary files containing user data. The files are stored in the same directory as the game’s executable. I’m wondering if this is considered bad practice, and if so, what a better alternative would be. My project is targeting .NET 6, so I believe BinaryReader and BinaryWriter will work as expected even on MacOS or Linux.

1 Like

As long as it works on all the platforms you want to support, I’d expect it to be fine. So just test that you can do all the operations you want on the platforms you’re interested in and away you go.

For whatever reason, I didn’t use System.IO in my own project. Instead I used a library on nuget called PCLStorage. I think it’s because I was using the portable class library stuff that Microsoft deprecated like 6 months after I started my project (:cry:). This was also for Android support.

It’s been too long and I can’t remember the specifics, so I’ll just throw this info out there for you :slight_smile:

1 Like

I don’t see why using system.io would be bad. I do think it’s a bad idea to store user data in the same folder as the game exe. There could be security issues if the exe is the in the program files folder. Its considered best practice to store user data in the user folder.

1 Like

This can help to find the right folder where you can write / read user data to / from:

Environment.GetFolderPath Method (System) | Microsoft Docs

2 Likes

No problem at all with BinaryReader nor BinaryWriter, however you might want to wrap things like reading and writing files (and in general , all platform dependant functions).

1 Like

Thanks for all the responses! So using BinaryReader and Writer is fine, but it would be best to store user data somewhere else.

I don’t have any Apple products, so I’ll have to have my Apple-using relative test it for me. And I guess for Linux I’d probably use some kind of virtual sandbox to test it in, if that works.

When you say security issues, do you mean the application not having write permissions? Steam stores game files in Program Files I believe, so if I wanted to release my game on Steam eventually (and I do), then that could have been a problem if the game doesn’t have write permissions.

Would it be better to store data in the hidden AppData\Local folder, or in a subdirectory in the user’s documents folder? And what about for Linux or MacOS, do they even have an equivalent to the AppData folder?

Thanks, that’s useful to know.

Would there be much point to wrapping them if I don’t have any platform-dependent code?

Yea, freakin’ Apple! If it works on Linux and Android (which has a free simulator), it’s probably going to work for Apple.

I’d still recommend making an abstraction for it though, just in case haha.

1 Like

System.IO works fine on all my games which I’ve published for Win64bit and MacOs. I’m not sure why somebody told you using System.IO is bad as it is not.

1 Like

Files can be in paths you didn’t expect, or maybe you can’t rely on “being on the same folder as the EXE”. Or maybe you’re doing the game in windows and you’ll be porting a game to a FS that is case sensitive and you have to fix tons of upper/lowercase files.

Personally I had all three problems and I was very glad I had wrapped it. I created a VFS file (all files into a single file with a header), added it to the file loader wrapper without having to alter the game code in any of the games I wanted to port. Later I realized I was loading some small files very often in a project, so I added a file cache for small files reducing loading time (SD can be very slow)

Wrapping file reading has been very useful to me but as usual YMMV.

2 Likes

check out this Environment.SpecialFolder Enum (System) | Microsoft Docs

you should put in the user (mabye roaming) folder its supposed to work on all platfroms and mobile but i havent tried.

System.IO is fine but consider if someone has install permission in windows, then installs a game for All users, but anthor use logs in without admin privileges then they can’t write to the EXE folder. Most people leave their windows in admin move, with pretty much root access all the time so its inherently insecure. Say, if u have a house guest and u give him a guest account he can play your games installed for all users, but not read ur emails or muck up your PC or load a virus by accident. Aslo then his game state gets saved in guest account user data area, like window position or levels unlocked or whatever. Linux and Android also have very limited access by defautl so using the exe folder is not gonna work.

As for Serializing i use data contract serializer. but its old and
most people inssit that Json is superior. its faster and easier to mod…

1 Like

I don’t quite get the difference between the Local, LocalLow, and Roaming folders.

My personal PC has only my account, an administrator account, but it’s protected by my PIN, and I’m the only one who uses my computer anyway. If someone else were using my computer, I’d be more worried about there being a stranger in my home than them maybe having admin access to my PC if they guessed my PIN!

I actually don’t do any official serialization, I just read and write to custom binary files that I parse as necessary.

I thought roaming data is for when a user on a network say logs , into another windows pc as his own account, ( i guess on any network or at least I’d hope) goes to Steam , pulls down his game and sees his own existing game settings still there.

but now it find its for only microsoft networks : ( dont use it)
https://www.thewindowsclub.com/local-localnow-roaming-folders-windows-10#:~:text=What%20is%20LocalLow%20folder%20in,data%20from%20the%20LocalLow%20folder.

if u use serialization it can be binary , have references, and then u just put DataMember and not have to parse anything. It is possible to read just some of a file like a thumbnail as well with advanced de serialization methods, either binary or as text, i love not parsing stuff manually.

i would look for a folder that works on all the platforms at least on one machine thats not admin or rooted or in dev model, and that works on the older systems too. i target 5platform s andthe job of saving settings is delegated to the thin apps that run the core game stuff… tho because i dont believe one code can do all this stuff… even saving windows position i had to use forms to get it to work on windows.

Honestly even if says netcore and xamarin i wouldn’t count all all these to work on all platforms, but the concept is solid.

but in windows, almost noone uses the guest account but trusting a visitor in ur house doesn’t mean u have to trust them on your PC to watch netflix but not got to dirty virusly parts of the internet, when u are away, always have a guest account . but really unless you are developer of software or installing stuff you should NEVER log in as admin just to us existing apps… many companies IT departments have all staff log in w/o install rights so they cant put ransdommare , uturrent , or games, on the corporate system , but for devs well we have to be admins to build our code.

the other Environment vars might work in android if this doesn’t and u need to write temp files or save user settings… U might google local storage for apps its also a UWP concept but i consider UWP dead and buried for now.

buy by default we have the windows in admin, so one can start adding killer apps right away, the app store stilll sucks and, thats why identity theft is so common is that Windows is like that. i even hate walking around with a android thats in dev mode but at least its not rooted.

1 Like

When writing user files, the best practices is to not write to the application’s folder, and where depends on the system. This our code to handle user data in a cross-platform way in a DesktopGL or WindowsDX project:

public string GetSaveDirectory()
{
    string saveDirectory = string.Empty;
    const string publisherName = "My publisher name";
    const string gameName = "My game";

    string userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

    if (Environment.OSVersion.Platform == PlatformID.MacOSX)
        saveDirectory = System.IO.Path.Combine(userHome, "Library/Application Support/" +     publisherName + "/" + gameName);
    else if (Environment.OSVersion.Platform == PlatformID.Unix)
        // linux people may argue on this one, but there is no consensus there
        saveDirectory = System.IO.Path.Combine(userHome, "." + gameName.ToLowerInvariant());
    else
        saveDirectory = System.IO.Path.Combine(userHome, "AppData\\LocalLow\\" + publisherName + "\\" + gameName);

    if (!System.IO.Directory.Exists(saveDirectory))
        System.IO.Directory.CreateDirectory(saveDirectory);

    return saveDirectory;
}

And as far as UWP is concerned:

saveDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path;

The main reason for not writing to the application folder is because it is a place that is likely to be in read-only in most cases. Which means that some users will have errors saving files. Operating Systems all provide a safe space for the user to write data, which is what the code above retrieves. It basically is this convention:

  • Windows (you can copy/paste this in Explorer): %USERPROFILE%/AppData/LocalLow/My publisher name/My Game
  • macOS: ~/Library/Application Support/My publisher name/My game
  • Linux: ~/.my game
3 Likes

Cool, thanks! Even better that there’s a way to check what the operating system is in code rather than using conditional compilation.

Is there a reason to use LocalLow on Windows rather than Local?

I actually don’t mind parsing things manually. Is that weird?

If we consider only the video game use-case, there’s virtually no difference between Local, and LocalLow. There are basically the same, so hit wherever you prefer.

The difference between those folders has its importance in enterprise networks because those folders are getting synchronized over the network with a different mechanism depending on the folder.

(I love BinaryReader/BinaryWriter, and in most projects I worked on, xml/json serialization have been the roots of all evil in regard to performance, size, and issues. A good middle-ground is a tool like protobuff, but it’s an advanced topic. Just use whatever fits your personal liking and makes you have fun with your code.)

1 Like

Alright, thanks. I’ll probably just use Local, then.

Amen to that! I like BinaryReader/Writer a lot too. I may not always follow “best practices” regarding coding and game development (I’ve never used JSON or anything like that, and know next to nothing about SQL and databases) but if it works, it works, and if I enjoy doing it, all the better.

I dont know how u serialize but in .dotnet you can use DataContract Serialzation. this can handle refernces so if one big data thing has multiple refs it will get an id and not bloat the file. So for my code has lots of classes an collections of instances of those, thats a level file… or a creature, or something, and i just have to tag only the persistent members of classes , like say float Position, wiht DataMember… i never want to type that name of that member anywere else again. not for NotifyPropertyChanged… for XMLserializer field, or anywhere, esle i do more work, if i type one letter wrong its a bug… … serialization has a general solution and as soon as it came out i grabbed it and never let go.

i been using it on a 10 year old codebase… its worked in like 6 versions of .net and now on 4 platfrroms never an issue except if the order of serializatoin fields matters… so don’t have persistent state that depends on other state …

my serializer code is simple … public static T LoadDataFromStream(Stream stream, bool useBinaryFormat)
{
try
{
XmlReaderSettings xset = new XmlReaderSettings();

            xset.ConformanceLevel = ConformanceLevel.Auto;
     
            DataContractSerializer dcs = new DataContractSerializer(typeof(T));
            T data;

            if (useBinaryFormat)
            {
                XmlDictionaryReader xr = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max);
                data = (T)dcs.ReadObject(xr, true); // ReadObject for XmlDictionaryReader
            }
            else
            {
                XmlReader xr = XmlReader.Create(stream, xset);
                data = (T)dcs.ReadObject(xr, true); // ReadObject for XmlReader
            }

            return data;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            return default(T);
        }

        finally
        {
            if (stream != null)
                stream.Dispose();
        }
    }

example of persisent class
[DataContract(Name = “WaveParams”, Namespace = “http://ShadowPlay”)]
public class WaveParams
{
///


/// the simulation grid cells to calculate the unit fluid simulation Eulerian grid for the velocity field. Future to support non square Nu Nv;
///

[DataMember]
public int nBinsX { get; set; }

if the collection is a collection of base with direrived classes in same colletion classes u have to add more tags…

then for collections u mark [KnownType(typeof(PrismaticJoint))]
[KnownType(typeof(LineJoint))]
[KnownType(typeof(SliderJoint))]
[KnownType(typeof(WeldJoint))]
[KnownType(typeof(Joint))]
[CollectionDataContract]
public class JointCollection :

Interesting. Well, for now I’ll be sticking with manual parsing, but I may try out serialization in the future, who knows.

I am using Isolated Storage because I read somewhere its going to work on Xbox. So far i tested it on Windows (OpenGL and UWP) and Linux.