How to modify ContentManager.Load<Song>()?

Hi, folk. I’m working on ContentManager to make it able load content form zip file. Everything went nice except the method Load() since I got an error that the file cannot be found. I have realize that when I import song to project using MonoGame Content Pipeline Tool, there are .xnb and the actual music file in content folder. I assume that there are another method try to load the music file using URI from .xnb file which works separate from load logic in ContentManager. Any idea to work this out guys?

Here is my code.
`public class ContentManager : Microsoft.Xna.Framework.Content.ContentManager
{
private bool usingZip = false;
private ZipFile zipFile;

    //not using zip
    public ContentManager(GameServiceContainer gsc, string RootDirectory) : base(gsc)
    {
        if (Directory.Exists(RootDirectory))
        {
            this.RootDirectory = RootDirectory;
        }
        else
            Console.WriteLine("Error - ContentManager : Content folder cannot be found.");
    }

    //using zip
    public ContentManager(GameServiceContainer gsc, string RootDirectory, string ContentPassword) : base(gsc)
    {
        usingZip = true;
        this.RootDirectory = RootDirectory;
        //check if content.zip is exist
        if(File.Exists(RootDirectory + ".zip"))
        {
            zipFile = new ZipFile(this.RootDirectory + ".zip");
            zipFile.Password = ContentPassword;
        }
        else
        {
            Console.WriteLine("Error - ContentManager : Content.zip cannot be found.");
            usingZip = false;
        }
    }

    protected override Stream OpenStream(string assetName)
    {
        if(usingZip)
        {
            int entry = zipFile.FindEntry(RootDirectory + "/" + assetName, true);
            if (entry != -1)
                return zipFile.GetInputStream(entry);
            else
            {
                entry = zipFile.FindEntry(RootDirectory + "/" + assetName + ".xnb", true);
                if (entry != -1)
                    return zipFile.GetInputStream(entry);
            }
        }
        else
        {
            if (File.Exists(Path.Combine(RootDirectory, assetName)))
                return new FileStream(Path.Combine(RootDirectory, assetName), FileMode.Open, FileAccess.Read, FileShare.None);
        }

        return base.OpenStream(assetName);
    }
}`

Maybe a stupid question, but are you sure you’re passing in the correct filepath without extensions?

Also are you sure the exception is originating in ContentManager and not in your ZipFile.FindEntry/GetInputStream methods? There’s lots of places for the filepath to get mixed up.

Anyway, a stack trace would be useful here. As near as I can tell, if the exact wording of the exception error you’re getting is :“file cannot be found” then I think that indicates the issue is in your ZipFile class. Unless I’m missing something, the source code for Load<T> and ReadAsset<T> shows that when a file not found error happens, it’s caught and rethrown as a more specific ContentLoadException with the more specific “The content file was not found”. And virtually any other error that might occur would have thrown a much more specific error message in the case an XNB file is found but in a bad format, etc.

I think this can also be inferred because if the ZipFile class had successfully returned a valid Stream, that indicates a valid filepath because you generally can’t get a valid Stream object if your filepaths are bad, and using a valid stream file in the rest of the ContentManager’s methods would result in very different error messages if something went wrong. So likely the error is originating between Load<T> and your overriden OpenStream, and the only code I can see that would generate a generic file not found error in that situation is likely happening in the overridden method there. That can be inferred due to the fact that the normal base OpenStream method catches file not found exceptions and rethrows them, and if that isn’t happening it’s almost certaintly because the base isn’t being called… IE, the exception is being thrown in your if/else block and bubbles back up to ReadAsset<T>, and that method is designed to catch ContentLoadExceptions, not file not found exceptions… so that exception continues to bubble up and goes unhandled.

Anyway, that’s just my theory, and it depends on the type of your exception being FileNotFound, which I’m only inferring.

1 Like

Thanks for the answer. I have tried some experiment and the result is : if I left the music file in folder Content (original folder created by MonoGame Content Pipeline Tool), the game would run without errors. So it could be that the game trying to load the music from original path. I’m still looking where the code is so I can modify it.

*To test the code, I deleted the folder Content that created by MonoGame Pipeline Tool to see if the game can load only by the zip.

Apologize if it not clear. Here’s an actual error I got :

An unhandled exception of type ‘SharpDX.SharpDXException’ occurred in SharpDX.dll

Additional information: HRESULT: [0x80070003], Module: [Unknown], ApiCode: [Unknown/Unknown], Message: The system cannot find the path specified.

Maybe this could help?

Also, what version are you using?

Apparently there were some issues with this error while loading song files which were fixed in newer versions, but the error happened in 3.6.

I’m using MonoGame 3.7.1.

I think I’m figure out that MonoGame using SharpDX to load and play the music. So it impossible to modify the framework to do that unless I modify the SharpDX directly.

I believe that this directly means that the path to the file is incorrect (and that is still primarily a os system file error) which would imply your code not sharp dx, probably in here…

  protected override Stream OpenStream(string assetName){ ...

is bugged.

I think also you have to be careful with odd characters in filenames or paths.

.
.
.

Note 2 points

1) doing this is bad practice.

RootDirectory + “.zip”
RootDirectory + “/” + assetName + “.xnb”

You should use the Path class Combine method every time to pre-combine paths and path file names.
i see you are and are not doing it in places.

I say that from experience of countless headaches when not following that simple rule.

string fullPath = Path.Combine( stringA , “SubFolder” ect…
or
string[] paths = {@“d:\archives”, “2001”, “media”, “somefile.watever”};
string fullFilePath = Path.Combine(paths);

2) You should check for file exists on a single completed full file path (just prior to using it every time) as well and throw a failing exception and printing out the fullPath in the exception.

if(File.Exists(fullFilePath) == false)
throw new Exception(" the path to the file: /n"+ fullFilePath + “/n does not exist”);

or
By using a debug and or trace Assert method so you can continue on if you have a fallback after the messege box pops up for a failure notification. I think that is found in the system.Diagnostics namespace.

1 Like

You can’t put the song inside a .zip file. In some platforms the Media player doesn’t take a stream and require a URI to stream directly from the file. You have to exclude any media files (wma,wmv, ogg,etc) from the zip.

Thank you for the comments and advises. I really appreciate.