Assaf Raman avatar Assaf Raman committed 96dfaee

Added EmbeddedZipArchiveFactory to OgreZip.
Also added a tool that helps to embed zip files in programs (converts zip files to cpp files).
Read more about this commit on this thread: http://www.ogre3d.org/forums/viewtopic.php?f=4&t=62181

Comments (0)

Files changed (5)

OgreMain/include/OgreRoot.h

         OverlayManager* mOverlayManager;
         FontManager* mFontManager;
         ArchiveFactory *mZipArchiveFactory;
+        ArchiveFactory *mEmbeddedZipArchiveFactory;
         ArchiveFactory *mFileSystemArchiveFactory;
 		ResourceGroupManager* mResourceGroupManager;
 		ResourceBackgroundQueue* mResourceBackgroundQueue;

OgreMain/include/OgreZip.h

 // Forward declaration for zziplib to avoid header file dependency.
 typedef struct zzip_dir		ZZIP_DIR;
 typedef struct zzip_file	ZZIP_FILE;
+typedef union _zzip_plugin_io zzip_plugin_io_handlers;
 
 namespace Ogre {
 
         void checkZzipError(int zzipError, const String& operation) const;
         /// File list (since zziplib seems to only allow scanning of dir tree once)
         FileInfoList mFileList;
+        /// A pointer to file io alternative implementation 
+        zzip_plugin_io_handlers* mPluginIo;
 
 		OGRE_AUTO_MUTEX
     public:
-        ZipArchive(const String& name, const String& archType );
+        ZipArchive(const String& name, const String& archType, zzip_plugin_io_handlers* pluginIo = NULL);
         ~ZipArchive();
         /// @copydoc Archive::isCaseSensitive
         bool isCaseSensitive(void) const { return false; }
         void destroyInstance( Archive* arch) { OGRE_DELETE arch; }
     };
 
+    /** Specialisation of ZipArchiveFactory for embedded Zip files. */
+    class _OgreExport EmbeddedZipArchiveFactory : public ZipArchiveFactory
+    {
+    protected:
+        /// A static pointer to file io alternative implementation for the embedded files
+        static zzip_plugin_io_handlers* mPluginIo; 
+    public:
+        EmbeddedZipArchiveFactory();
+        virtual ~EmbeddedZipArchiveFactory();
+        /// @copydoc FactoryObj::getType
+        const String& getType(void) const;
+        /// @copydoc FactoryObj::createInstance
+        Archive *createInstance( const String& name ) 
+        {
+            ZipArchive * resZipArchive = OGRE_NEW ZipArchive(name, "EmbeddedZip", mPluginIo);
+            return resZipArchive;
+        }
+        
+        /** a function type to decrypt embedded zip file
+        @param pos pos in file
+        @param buf current buffer to decrypt
+        @param len - length of buffer
+        @returns success
+        */  
+        typedef bool (*DecryptEmbeddedZipFileFunc)(size_t pos, void* buf, size_t len);
+
+        /// Add an embedded file to the embedded file list
+        static void addEmbbeddedFile(const String& name, const uint8 * fileData, 
+                        size_t fileSize, DecryptEmbeddedZipFileFunc decryptFunc);
+
+        /// Remove an embedded file to the embedded file list
+        static void removeEmbbeddedFile(const String& name);
+
+    };
+
     /** Specialisation of DataStream to handle streaming data from zip archives. */
     class _OgrePrivate ZipDataStream : public DataStream
     {

OgreMain/src/OgreRoot.cpp

 #if OGRE_NO_ZIP_ARCHIVE == 0
         mZipArchiveFactory = OGRE_NEW ZipArchiveFactory();
         ArchiveManager::getSingleton().addArchiveFactory( mZipArchiveFactory );
+        mEmbeddedZipArchiveFactory = OGRE_NEW EmbeddedZipArchiveFactory();
+        ArchiveManager::getSingleton().addArchiveFactory( mEmbeddedZipArchiveFactory );
 #endif
 #if OGRE_NO_DDS_CODEC == 0
 		// Register image codecs
         OGRE_DELETE mArchiveManager;
 #if OGRE_NO_ZIP_ARCHIVE == 0
         OGRE_DELETE mZipArchiveFactory;
+        OGRE_DELETE mEmbeddedZipArchiveFactory;
 #endif
         OGRE_DELETE mFileSystemArchiveFactory;
         OGRE_DELETE mSkeletonManager;

OgreMain/src/OgreZip.cpp

 #include "OgreRoot.h"
 
 #include <zzip/zzip.h>
+#include <zzip/plugin.h>
 
 
 namespace Ogre {
         return errorMsg;
     }
     //-----------------------------------------------------------------------
-    ZipArchive::ZipArchive(const String& name, const String& archType )
-        : Archive(name, archType), mZzipDir(0)
+    ZipArchive::ZipArchive(const String& name, const String& archType, zzip_plugin_io_handlers* pluginIo)
+        : Archive(name, archType), mZzipDir(0), mPluginIo(pluginIo)
     {
     }
     //-----------------------------------------------------------------------
         if (!mZzipDir)
         {
             zzip_error_t zzipError;
-            mZzipDir = zzip_dir_open(mName.c_str(), &zzipError);
+            mZzipDir = zzip_dir_open_ext_io(mName.c_str(), &zzipError, 0, mPluginIo);
             checkZzipError(zzipError, "opening archive");
 
             // Cache names
 		mCache.clear();
     }
     //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    //  ZipArchiveFactory
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
     const String& ZipArchiveFactory::getType(void) const
     {
         static String name = "Zip";
         return name;
     }
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    //  EmbeddedZipArchiveFactory
+    //-----------------------------------------------------------------------
+    //-----------------------------------------------------------------------
+    /// a struct to hold embedded file data
+    struct EmbeddedFileData
+    {
+        const uint8 * fileData;
+        zzip_size_t fileSize;
+        zzip_size_t curPos;
+        bool isFileOpened;
+        EmbeddedZipArchiveFactory::DecryptEmbeddedZipFileFunc decryptFunc;
+    };
+    //-----------------------------------------------------------------------
+    /// A type for a map between the file names to file index
+    typedef map<String, int>::type FileNameToIndexMap;
+    typedef FileNameToIndexMap::iterator FileNameToIndexMapIter;
+    /// A type to store the embedded files data
+    typedef vector<EmbeddedFileData>::type EmbbedFileDataList;
+    /// A static map between the file names to file index
+    FileNameToIndexMap * EmbeddedZipArchiveFactory_mFileNameToIndexMap;
+    /// A static list to store the embedded files data
+    EmbbedFileDataList * EmbeddedZipArchiveFactory_mEmbbedFileDataList;
+    /// A static pointer to file io alternative implementation for the embedded files
+    zzip_plugin_io_handlers* EmbeddedZipArchiveFactory::mPluginIo = NULL;
+    _zzip_plugin_io sEmbeddedZipArchiveFactory_PluginIo;
+    #define EMBBED_IO__BAD_FILE_HANDLE (-1)
+    #define EMBBED_IO__SUCCESS (0)
+    //-----------------------------------------------------------------------
+    /// functions for embedded zzip_plugin_io_handlers implementation 
+    /// The functions are here and not as static members because they 
+    /// use types that I don't want to define in the header like zzip_char_t,
+    //  zzip_ssize_t and such.
+    //-----------------------------------------------------------------------
+    // get file date by index
+    EmbeddedFileData & getEmbeddedFileDataByIndex(int fd)
+    {
+        return (*EmbeddedZipArchiveFactory_mEmbbedFileDataList)[fd-1];
+    }
+    //-----------------------------------------------------------------------
+    // opens the file
+    int EmbeddedZipArchiveFactory_open(zzip_char_t* name, int flags, ...)
+    {
+        String nameAsString = name;
+        FileNameToIndexMapIter foundIter = EmbeddedZipArchiveFactory_mFileNameToIndexMap->find(nameAsString);
+        if (foundIter != EmbeddedZipArchiveFactory_mFileNameToIndexMap->end())
+        {
+            int fd = foundIter->second;
+            EmbeddedFileData & curEmbeddedFileData = getEmbeddedFileDataByIndex(fd);
+            if(curEmbeddedFileData.isFileOpened)
+            {
+               // file is opened - return an error handle
+               return EMBBED_IO__BAD_FILE_HANDLE;
+            }
+            
+            curEmbeddedFileData.isFileOpened = true;
+            return fd;
+        }
+        else
+        {
+           // not found - return an error handle
+           return EMBBED_IO__BAD_FILE_HANDLE;
+        }
+    }
+    //-----------------------------------------------------------------------
+    // Closes a file.
+    // Return Value - On success, close returns 0. 
+    int EmbeddedZipArchiveFactory_close(int fd)
+    {
+        if (fd == EMBBED_IO__BAD_FILE_HANDLE)
+        {
+            // bad index - return an error
+            return -1;
+        }
 
+        EmbeddedFileData & curEmbeddedFileData = getEmbeddedFileDataByIndex(fd);
+
+        if(curEmbeddedFileData.isFileOpened == false)
+        {
+           // file is opened - return an error handle
+           return -1;
+        }
+        else
+        {
+            // success
+            curEmbeddedFileData.isFileOpened = false;
+            curEmbeddedFileData.curPos = 0;
+            return 0;
+        }
+
+    }
+       
+    //-----------------------------------------------------------------------
+    // reads data from the file
+    zzip_ssize_t EmbeddedZipArchiveFactory_read(int fd, void* buf, zzip_size_t len)
+    {
+        if (fd == EMBBED_IO__BAD_FILE_HANDLE)
+        {
+            // bad index - return an error size - negative
+            return -1;
+        }
+        // get the current buffer in file;
+        EmbeddedFileData & curEmbeddedFileData = getEmbeddedFileDataByIndex(fd);
+        const uint8 * curFileData = curEmbeddedFileData.fileData;
+        if (len + curEmbeddedFileData.curPos > curEmbeddedFileData.fileSize)
+        {
+            len = curEmbeddedFileData.fileSize - curEmbeddedFileData.curPos;
+        }
+        curFileData += curEmbeddedFileData.curPos;
+        
+        // copy to out buffer
+        memcpy(buf, curFileData, len);
+
+        if( curEmbeddedFileData.decryptFunc != NULL )
+        {
+            if (!curEmbeddedFileData.decryptFunc(curEmbeddedFileData.curPos, buf, len))
+            {
+                // decrypt failed - return an error size - negative
+                return -1;
+            }
+        }
+
+        // move the cursor to the new pos
+        curEmbeddedFileData.curPos += len;
+        
+        return len;
+    }
+    //-----------------------------------------------------------------------
+    // Moves file pointer.
+    zzip_off_t EmbeddedZipArchiveFactory_seeks(int fd, zzip_off_t offset, int whence)
+    {
+        if (fd == EMBBED_IO__BAD_FILE_HANDLE)
+        {
+            // bad index - return an error - nonzero value.
+            return -1;
+        }
+        
+        zzip_size_t newPos = -1;
+        // get the current buffer in file;
+        EmbeddedFileData & curEmbeddedFileData = getEmbeddedFileDataByIndex(fd);
+        switch(whence)
+        {
+            case SEEK_CUR:
+                newPos = curEmbeddedFileData.curPos + offset;
+                break;
+            case SEEK_END:
+                newPos = curEmbeddedFileData.fileSize - offset;
+                break;
+            case SEEK_SET:
+                newPos = offset;
+                break;
+            default:
+                // bad whence - return an error - nonzero value.
+                return -1;
+                break;
+        };
+        if (newPos >= curEmbeddedFileData.fileSize || 
+            newPos < 0 )
+        {
+            // bad whence - return an error - nonzero value.
+            return -1;
+        }
+
+        curEmbeddedFileData.curPos = newPos;
+        return newPos;
+    }
+    //-----------------------------------------------------------------------
+    // returns the file size
+    zzip_off_t EmbeddedZipArchiveFactory_filesize(int fd)
+    {
+        if (fd == EMBBED_IO__BAD_FILE_HANDLE)
+        {
+            // bad index - return an error - nonzero value.
+            return -1;
+        }
+                // get the current buffer in file;
+        EmbeddedFileData & curEmbeddedFileData = getEmbeddedFileDataByIndex(fd);
+        return curEmbeddedFileData.fileSize;
+    }
+    //-----------------------------------------------------------------------
+    // writes data to the file
+    zzip_ssize_t EmbeddedZipArchiveFactory_write(int fd, _zzip_const void* buf, zzip_size_t len)
+    {
+        // the files in this case are read only - return an error  - nonzero value.
+        return -1;
+    }
+    //-----------------------------------------------------------------------
+    EmbeddedZipArchiveFactory::EmbeddedZipArchiveFactory()
+    {
+        // init static member
+        if (mPluginIo == NULL)
+        {
+            mPluginIo = &sEmbeddedZipArchiveFactory_PluginIo;
+            mPluginIo->fd.open = EmbeddedZipArchiveFactory_open;    
+            mPluginIo->fd.close = EmbeddedZipArchiveFactory_close;    
+            mPluginIo->fd.read = EmbeddedZipArchiveFactory_read;    
+            mPluginIo->fd.seeks = EmbeddedZipArchiveFactory_seeks;    
+            mPluginIo->fd.filesize = EmbeddedZipArchiveFactory_filesize;    
+            mPluginIo->fd.write = EmbeddedZipArchiveFactory_write;    
+            mPluginIo->fd.sys = 1;    
+            mPluginIo->fd.type = 1;    
+        }
+    }
+    //-----------------------------------------------------------------------
+    EmbeddedZipArchiveFactory::~EmbeddedZipArchiveFactory()
+    {
+    }    
+    //-----------------------------------------------------------------------
+    const String& EmbeddedZipArchiveFactory::getType(void) const
+    {
+        static String name = "EmbeddedZip";
+        return name;
+    }
+    //-----------------------------------------------------------------------
+    void EmbeddedZipArchiveFactory::addEmbbeddedFile(const String& name, const uint8 * fileData, 
+                                        size_t fileSize, DecryptEmbeddedZipFileFunc decryptFunc)
+    {
+        static bool needToInit = true;
+        if(needToInit)
+        {
+            needToInit = false;
+
+            // we can't be sure when global variables get initialized
+            // meaning it is possible our list has not been init when this
+            // function is being called. The solution is to use local
+            // static members in this function an init the pointers for the
+            // global here. We know for use that the local static variables
+            // are create in this stage.
+            static FileNameToIndexMap sFileNameToIndexMap;
+            static EmbbedFileDataList sEmbbedFileDataList;
+            EmbeddedZipArchiveFactory_mFileNameToIndexMap = &sFileNameToIndexMap;
+            EmbeddedZipArchiveFactory_mEmbbedFileDataList = &sEmbbedFileDataList;
+        }
+
+        EmbeddedFileData newEmbeddedFileData;
+        newEmbeddedFileData.curPos = 0;
+        newEmbeddedFileData.isFileOpened = false;
+        newEmbeddedFileData.fileData = fileData;
+        newEmbeddedFileData.fileSize = fileSize;
+        newEmbeddedFileData.decryptFunc = decryptFunc;
+        EmbeddedZipArchiveFactory_mEmbbedFileDataList->push_back(newEmbeddedFileData);
+        (*EmbeddedZipArchiveFactory_mFileNameToIndexMap)[name] = EmbeddedZipArchiveFactory_mEmbbedFileDataList->size();
+    }
+    //-----------------------------------------------------------------------
+    void EmbeddedZipArchiveFactory::removeEmbbeddedFile( const String& name )
+    {
+        EmbeddedZipArchiveFactory_mFileNameToIndexMap->erase(name);
+    }
 }

Tools/OgreZip2Cpp/src/OgreZip2Cpp.cpp

+/*
+-----------------------------------------------------------------------------
+This source file is part of OGRE
+    (Object-oriented Graphics Rendering Engine)
+For the latest info, see http://www.ogre3d.org/
+
+Copyright (c) 2000-2009 Torus Knot Software Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+-----------------------------------------------------------------------------
+*/
+
+// this tool helps to embed zip files in programs.
+// if convert the zip file to a cpp file that should be added to the program build.
+// read more about it on this forum thread: http://www.ogre3d.org/forums/viewtopic.php?f=4&t=62181
+
+
+
+// _CRT_SECURE_NO_WARNINGS is defined to disable this compiler warning:
+// "
+//  warning C4996: 'sprintf': This function or variable may be unsafe. Consider using sprintf_s instead. 
+// To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
+// "
+#define _CRT_SECURE_NO_WARNINGS
+
+#include <fstream>
+#include <sys/stat.h>
+#include <string>
+
+
+void help(void)
+{
+    // Print help message
+    printf("OgreZip2Cpp: Convert a zip file into a cpp file.\n");
+    printf("Usage: OgreFile2Cpp [zip file name without extension (without .zip)]\n");
+    printf("The working folder needs to be the same as the one of the zip file.\n");
+}
+
+// the main function
+int main(int numargs, char** args)
+{
+    // check that we have only one parameter - the zip file name without extension 
+    if (numargs != 2)
+    {
+        printf("Error: Wrong number of parameters (the program uses one parameter)\n");
+        help();
+        return -1;
+    }
+    std::string fileNameWithoutExt = args[1];
+    std::string zipFileName = fileNameWithoutExt + ".zip";
+    std::string cppFileName = zipFileName + ".cpp";
+    std::string structName = fileNameWithoutExt;
+
+
+    // get the zip file size
+    std::streamsize fileSize = 0;
+    struct stat results;
+    if (stat(zipFileName.c_str(), &results) == 0)
+        // The size of the file in bytes is in
+        // results.st_size
+    {
+        fileSize = results.st_size;
+    }
+
+    // check that the file is valid
+    if (fileSize == 0)
+    {
+        printf("Error: Zip file was not found or its size is zero (make sure that you didn't add .zip at the end of the parameter)\n");
+        help();
+        return -2;
+    }
+    
+    // alloc memory for the in(zip) file - loading it all to memory is much fater then
+    // one char at a time
+    unsigned char * inFileData = new unsigned char[fileSize];
+
+    // open, read the data to memory and close the in file
+    std::ifstream roStream;
+	roStream.open(zipFileName.c_str(),  std::ifstream::in | std::ifstream::binary);
+    roStream.read((char *)inFileData, fileSize);
+    roStream.close();
+
+    // alloc the out file, the out file is a text file, meaning it is much bigger then the input file
+    // allocating a buffer ten time bigger then the size of the in file - just to be on the safe side.
+    // waste of memory, yes, but the most easy way to be safe without writing lots of code 
+    char * outFileData = new char[fileSize * 10];
+    char * outFileDataPos = outFileData;
+    
+
+    // start writing out the out cpp file 
+    // ----------------------------------
+
+    // add the include
+	outFileDataPos+= sprintf(outFileDataPos, "#include \"OgreZip.h\"\n\n");
+    
+    // start the names place
+	outFileDataPos+= sprintf(outFileDataPos, "namespace Ogre {\n");
+	
+    // declare the struct 
+    outFileDataPos+= sprintf(outFileDataPos, "struct %s{\n", structName.c_str());
+    
+    // add a data member for the file name
+	outFileDataPos+= sprintf(outFileDataPos, "\tchar * fileName;\n");
+
+    //  the struct contractor
+	outFileDataPos+= sprintf(outFileDataPos, "\t%s() : fileName(\"%s\")\n", structName.c_str(), zipFileName.c_str());
+	outFileDataPos+= sprintf(outFileDataPos, "\t{\n");
+
+    // declare and init the content of the zip file to a static buffer
+	outFileDataPos+= sprintf(outFileDataPos, "\t\tstatic uint8 fileData[] = \n");
+	outFileDataPos+= sprintf(outFileDataPos, "\t\t{\n\t\t\t");    
+	int posCurOutInLine = 0;
+	for( std::streamsize i = 0 ;  i < fileSize ; i++ ) 
+    {                
+        // get the current char        
+        unsigned char outChar = inFileData[i];
+        
+        // if you want to encrypt the data - encrypt the char here
+        // ....
+
+        // play with the formatting so the data will look nice
+        // add spaces before small number
+        if(outChar < 10) 
+            outFileDataPos+= sprintf(outFileDataPos, " ");
+        if(outChar < 100)
+		    outFileDataPos+= sprintf(outFileDataPos, " ");
+
+        // output the char to the out cpp file
+		outFileDataPos+= sprintf(outFileDataPos, "%d", outChar);
+	    posCurOutInLine+=3;
+
+        // add a comman after every char but the last
+		if(i + 1 != fileSize) {
+			outFileDataPos+= sprintf(outFileDataPos, ", ");
+			posCurOutInLine+=2;
+		}
+
+        // cut the line every 100 chars
+		if (posCurOutInLine > 100) {
+			outFileDataPos+= sprintf(outFileDataPos, "\n\t\t\t");
+			posCurOutInLine = 0;
+		}
+	}
+
+    // close the static buffer that holds the file data
+	outFileDataPos+= sprintf(outFileDataPos, "\n\t\t};\n");
+
+    // register the file buffer to tEmbeddedZipArchiveFactory
+	outFileDataPos+= sprintf(outFileDataPos, "\t\tEmbeddedZipArchiveFactory::addEmbbeddedFile(fileName, fileData, sizeof(fileData), NULL);\n");
+
+    // close the contractor 
+	outFileDataPos+= sprintf(outFileDataPos, "}\n");
+
+// This code may be risky in some case and is not relevant in most case for this tool so I am leaving it out. 
+//     // the distractor
+// 	outFileDataPos+= sprintf(outFileDataPos, "\t~%s()\n", structName.c_str());
+// 	outFileDataPos+= sprintf(outFileDataPos, "\t{\n");
+//     // remove the file from tEmbeddedZipArchiveFactory in the distractor (for the case of dynamic plugin unload?)
+// 	outFileDataPos+= sprintf(outFileDataPos, "\t\tEmbeddedZipArchiveFactory::removeEmbbeddedFile(fileName);\n");
+// 	outFileDataPos+= sprintf(outFileDataPos, "\t}\n");
+
+    // close the class
+	outFileDataPos+= sprintf(outFileDataPos, "};\n");
+	outFileDataPos+= sprintf(outFileDataPos, "\n");
+
+    // declare a global (static) variable so the struct contractor will be called when the program starts
+    // or the plugin loads.
+	outFileDataPos+= sprintf(outFileDataPos, "%s g_%s;\n", structName.c_str(), structName.c_str());
+	outFileDataPos+= sprintf(outFileDataPos, "\n");
+
+    // close the Ogre name space
+	outFileDataPos+= sprintf(outFileDataPos, "}\n");
+
+    // write out the cpp file
+    std::ofstream writeStream;
+	writeStream.open(cppFileName.c_str(),  std::ifstream::out | std::ofstream::binary);
+    writeStream.write(outFileData, ((std::streamsize)(outFileDataPos - outFileData)));
+    writeStream.close();
+
+    // done, let go home and drink some beers
+	return 0;
+}
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.