Game Development Community

FIX to Play OGGs in ZIP Archive

by CdnGater · 09/09/2004 (10:57 am) · 10 comments

TGE has built in the ability to use ZIP files to store your data. This works fine for all files except for the OGG (At least that I have found). When you try and play an zip archived OGG you get the "Not Implemented!" Messagebox. This will fix that problem.

First off it is not actualy a OGG problem, its the ZipSubRStream problem. It's just when using OGG you encounter it easier.

Ok, lets start.
What is in bold is what you add. The << ADD THIS HERE is me telling you what to add and where, so please don't type it in and they say it does not compile!

1) Open up the engine/core/ZipSubRStream.h file

class ZipSubRStream : public FilterStream
{
   typedef FilterStream Parent;
   static const U32 csm_streamCaps;
   static const U32 csm_inputBufferSize;

   Stream* m_pStream;
   U32     m_uncompressedSize;
   U32     m_currentPosition;
[b]   bool     m_EOS;  [/b]   << ADD THIS HERE

2) Open up the engine/core/ZipSubRStream.cc file

2.1) In the constructor add the following
ZipSubRStream::ZipSubRStream()
 : m_pStream(NULL),
   m_uncompressedSize(0),
   m_currentPosition(0),
[b]   m_EOS(false), [/b]  << ADD THIS HERE

   m_pZipStream(NULL),
   m_originalSlavePosition(0)


2.2) In ZipSubRStream::attachStream add the following
m_pStream          = io_pSlaveStream;
   m_originalSlavePosition = io_pSlaveStream->getPosition();
   m_uncompressedSize = 0;
   m_currentPosition  = 0;
[b]   m_EOS                 = 0; [/b] << ADD THIS HERE

   // Initialize zipStream state...
   m_pZipStream   = new z_stream_s;


2.3) In ZipSubRStream::detachStream add the following
m_pStream          = NULL;
   m_originalSlavePosition = 0;
   m_uncompressedSize = 0;
   m_currentPosition  = 0;
[b]   m_EOS                   = 0; [/b] << ADD THIS HERE
   setStatus(Closed);


2.4) The changes in ZipSubRStream::_read are a bit more extensive

Near the top of the function
AssertFatal(out_pBuffer != NULL, "NULL output buffer");
   if (getStatus() == Closed) {
      AssertFatal(false, "Attempted read from closed stream");
      return false;
   }

<< ADD FROM THIS LINE
[b]
   if (Ok != getStatus())
     return false;

   if (m_EOS)
   {
       setStatus(EOS);
       return true;
   };
[/b]
<< TO THIS LINE

   // Ok, we need to call inflate() until the output buffer is full.
   //  first, set up the output portion of the z_stream

Near the bottom of the function you will find the following piece of code
if (retVal == Z_STREAM_END) 
      {
         if (m_pZipStream->avail_out != 0)
            setStatus(EOS);
         else
            setStatus(Ok);
         m_currentPosition += m_pZipStream->total_out;
         return getStatus() == Ok;
      }

change it to look like the following

if (retVal == Z_STREAM_END) 
      {
         if (m_pZipStream->avail_out != 0)
	 m_EOS = true;

         setStatus(Ok);
         m_currentPosition += m_pZipStream->total_out;
         return true;
      }

The reason we do this is to conform the read function to act like the file stream read function. When the file stream hits EOS, it sets the flag and returns true. The next read throws the EOS status. I just make the zip reader do the same thing.


The last bit of code here, go down to the ZipSubRStream::setPosition function, see the
{
      AssertFatal(false, "Not implemented!");
      // Erk.  How do we do this.
      return false;
  }

change it to the following

{
[b]
      if (in_newPosition > m_uncompressedSize)
        return false;

      U32 newPosition = in_newPosition;
      if (newPosition < m_currentPosition)
      {
         Stream* pStream = getStream();
         U32 resetPosition = m_originalSlavePosition;
         U32 uncompressedSize = m_uncompressedSize;
         detachStream();
         pStream->setPosition(resetPosition);
         attachStream(pStream);
         setUncompressedSize(uncompressedSize);
      }
      else
      {
         newPosition -= m_currentPosition;
      }

      bool bRet = true;
      char *buffer = new char[2048];
      while (newPosition >= 2048)
      {
         newPosition -= 2048;
         if (!_read(2048,buffer))
         {
            bRet = false;
            break;
         }
      };
      if (bRet && newPosition > 0)
      {
         if (!_read(newPosition,buffer))
         {
            bRet = false;
         };
      };

      delete [] buffer;

      return bRet;
[/b]
   }

What this code does it puts the zip pointer back to the orginal start of the ziped archive, and reads forward the required number of bytes. It is not pritty BUT it works.

So between a not pritty solution and no solution, I think you will hear my point. If someone wants to come up with a better way, your welcome to. But for now, I will be listening my OGGs from my ZIP Files.

#1
09/09/2004 (11:24 am)
Nice fix Simon.

I do a similar thing in my container filter for my custom container format.
If the new position is > than the current position, read() the remaining bytes.
If the new position is < than the current position, go to the start, and read() to the position.

Although, i seperate the compressed data out into blocks of a specific decompressed size, since my code allows you to use multiple processing and compression techniques ;)
#2
09/09/2004 (12:10 pm)
This should be in HEAD.
#3
09/10/2004 (12:19 am)
Perhaps you should have a check in setPosition() such as this :

if (in_newPosition < getPosition())
           pStream->setPosition(resetPosition);

So instead of seeking to the beginning, it will keep on reading (if the position is after the current position).

And, you don't want it trying to seek to the exact same position, so add a check like this at the start :

if (in_newPosition == getPosition())
           return true;
#4
09/10/2004 (5:52 am)
James;

Good idea I'll update this tonight.

But there is one thing missing still from your code. If you want to continue from the current spot, you will need to adjust the in_newPosition.

eg:
If you want to seek to 1000 bytes and your at 500 already, you will only want to seek another 500, not the full 1000.

if (in_newPosition < getPosition())           
     pStream->setPosition(resetPosition);
else
   in_newPosition -= getPosition();


Anyways, I'l make the adjustments tonight and test them before positing them here.
#5
09/11/2004 (7:24 am)
I have modified the last code section to reflect the change. Thank you James, that was a good suggestion.
#6
09/12/2004 (7:40 am)
Hmm, I guess that works, but I took an entirely different method and included encryption in my zip routine sometime ago. All zip files are uncompressed once. This means it's attached once and never detached and my setPosition() look like this:

if (in_newPosition <= m_pZipStream->total_out)
   {
       m_currentPosition = in_newPosition;
       return true;
   }
   else
   {
       return false;
   }
#7
09/12/2004 (8:12 am)
This is on my todo list for HEAD improvements.
#8
09/13/2004 (1:43 pm)
I failed to mention that this small code is impressive because it only takes very little changes to make it work. Mine wasn't this small, not to mention I couldn't make it work with changes only to the setPosition() function.

Good work Simon.
#9
10/08/2004 (4:54 pm)
Wow, this is awesome! And now alxgetwavelen() even for a .ogg doesn't crash the engine anymore :)

Thanks Simon!
#10
03/14/2005 (11:39 pm)
Ok, the basic fixes are in. Thanks!
#11
01/23/2006 (10:25 pm)
is this still needed or did TGE 1.4 fix this issue?