Minecraft Modding: Tips For Files And Resources

Background


Java programs, including Minecraft mods and Minecraft itself are often distributed in JAR format which is essentially a compressed folder containing all the class files and assets necessary to run the program.  During execution, the files in the JAR are essentially read-only as standard Java file IO will not be able to access the files (they will not present as a filing path that can be written to).

This is both good and bad.  The good part is that the relative paths between files is known and unchanging.  The bad part is that if any of the assets are meant to be changeable they need to be copied somewhere else and then manipulated there.

Here are some tips for reading and writing files, relevant to modding.

Getting The modid From A ResourceLocation


For your own mod, you should already know your modid, but there may be cases when you want to know the modid of a resource location from another mod. In that case you can use the ResourceLocation#getResourceDomain() method.

Reading An Asset File Using Standard Java Methods


Since JAR is common to Java programs, the ClassLoader class has a getResourceAsStream() method to get things from the relative path within the JAR.

Using that method, an example of reading a text file and converting the values to an int array is:

protected void readArrays(String parName)
{
    try 
    {
        System.out.println("Reading file = "+parName+".txt");
        readIn = new BufferedReader(new InputStreamReader(getClass().getClassLoader()
            .getResourceAsStream("assets/magicbeans/structures/"+parName+".txt"), "UTF-8"));
        dimX = Integer.valueOf(readIn.readLine());
        dimY = Integer.valueOf(readIn.readLine());
        dimZ = Integer.valueOf(readIn.readLine());
        blockNameArray = new String[dimX][dimY][dimZ];
        blockMetaArray = new int[dimX][dimY][dimZ];
        System.out.println("Dimensions of structure = "+dimX+", "+dimY+", "+dimZ);
        for (int indY = 0; indY < dimY; indY++) // Y first to organize in vertical layers
        {
            for (int indX = 0; indX < dimX; indX++)
            {
                for (int indZ = 0; indZ < dimZ; indZ++)
                {
                    blockNameArray[indX][indY][indZ] = readIn.readLine();
                    blockMetaArray[indX][indY][indZ] = Integer.valueOf(readIn.readLine());
                }
            }
        }
    } 
    catch (FileNotFoundException e) 
    {
        // replace this with better exception handling
  e.printStackTrace();
    } 
    catch (IOException e) 
    {
        // replace this with better exception handling
  e.printStackTrace();
    }
    // remember to close the stream to avoid memory leaks     
try {   readIn.close();
    } 
    catch (IOException e)
    {
       // replace this with better exception handling
  e.printStackTrace();
    }
}

Reading An Asset File Using Minecraft Methods


There is nothing wrong with (in fact there is some advantage see note below) sticking with Java methods to access file assets, but Minecraft itself has some resource management methods that can be used instead.  The Minecraft class creates a resource manager instance called mcResourceManager which implements IReloadableResourceManager that extends IResourceManager.

The mcResourceManager instance has a method called getResource() that returns an IResource which is basically just an extension of InputStream.  So you can use that to get the input stream and replace that part above.

Note: the IResource class is @SideOnly(Side.CLIENT) so only available on the client.  For traditional assets, such as textures and sounds, that makes sense since those are only used on the client side.  However, it is quite possible you may want to access files within the JAR from the server side; for example you may have a file containing a schematic for a structure you want to build.  In that case I recommend sticking with the standard Java file methods in the previous session.

Using The IResourcePack Interface


This information is based on this tutorial in Japanese.

IResourcePack interface is used to create a dynamic class that functions like a native resource pack. In this usage soon, because you can see the resources that are outside (jar file of output by MOD) build path, it is also possible for the user of the MOD to change the resources without using an actual resource pack.

Warning: IResourcePack is a client-side only interface, so make sure you have all the code in or accessed from your client proxy or other client-side only methods.

In the init handling method of your mod's main class or proxy, you want to have the following code:
        // DefaultResourcePacks because of the private, reflection is necessary.
        List < IResourcePack > DefaultResourcePacks = ObifuscationReflectionHelper.GetPrivateValue(Minecraft.class, Minecraft.getMinecraft(), "defaultResourcePacks" , "Field_110449_ao" ) ;
      . DefaultResourcePacks add ( new SampleResourcePack ( ) ) ;

Then make your custom IResourcePack class:

public  class SampleResourcePack implements IResourcePack { 
    public SampleResourcePack ( ) { 
        // constructor good basically empty 
    }

    Override
    public  InputStream getInputStream ( resourceLocation Par1ResourceLocation )  throws  IOException  { 
        // to the referenced resource, it will return the InputStream. 
        Return  new  FileInputStream ( ...
     }

    Override
    public  boolean ResourceExists ( resourceLocation resource )  { 
        // referenced one of the specified resource exists. 
        Return  true ; 
    }

    Override
    SuppressWarnings ( "Rawtypes" ) 
    public  Set GetResourceDomains ( )  { 
        // IResourcePack is specify the domain of resources to process. Multiple allowed. 
        Return . ImmutableSet of ( "Samplemod" ) ; 
    }

    Override
    public IMetadataSection GetPackMetadata ( IMetadataSerializer Par1MetadataSerializer, String Par2Str ) { 
        // good in null if not have a metadata 
        Return  null ; 
    }

    Override
    public  BufferedImage GetPackImage ( )  { 
        // pack image is also good at null if you have not prepared 
        Return  null ; 
    }

    Override
    public  String GetPackName ( )  { 
        // the name of the pack. As long as it does not provide the metadata or pack image, not be visible from the playing user 
        to crash when // returns empty or null 
        Return  "SampleResourcePack" ; 
    }

}

Register the resource pack in the init handling method of your client proxy:

        List<IResourcePack> DefaultResourcePacks = ObifuscationReflectionHelper.GetPrivateValue(Minecraft.class ., Minecraft.GetMinecraft(), "defaultResourcePacks", "defaultResourcePacks" ) ; 
        DefaultResourcePacks.add(new SampleResourcePack()) ;

To actually recognized as a resource pack a class implementing IResourcePack it needs to be added to the Minecraft.defaultResourcePacks field. Since that field is private, it is accessed by using Java "reflection".

Accessing The Resources

    Override
     public  InputStream getInputStream ( resourceLocation Par1ResourceLocation )  throws  IOException  { 
        Return  new  FileInputStream ( ...
     }

    Override
    public  boolean ResourceExists ( resourceLocation resource )  { 
        Return  true ; 
    }

Basically, resources are acquired in the InputStream. getInputStream (ResourceLocation) is mandatory methods in order to obtain the InputStream to referenced resource (ResourceLocation passed to the first argument). In this method, the use of a FileInputStream will Naninari, possible to get a file that is external to the build path as a resource.

resourceExists (ResourceLocation), the essential methods that reference resource is to specify whether (or SampleResourcePack returns resources) exist in SampleResourcePack. When referring to a resource that was prepared in build path, it is sufficient to return the false.

Although are simplified in the example, check the ResourceLocation passed as an argument to be made to return the correct value. In particular getInputStream, I want to be that there is no will, such as returning a FileInputStream to a file that does not exist by mistake.


After all this, you can use it like a regular ResourceLocation.

2 comments: