/***************************************************************************** ** ** ** Neversoft Entertainment. ** ** ** ** Copyright (C) 2000 - All Rights Reserved ** ** ** ****************************************************************************** ** ** ** Project: skate3 ** ** ** ** Module: ** ** ** ** File name: ** ** ** ** Created by: rjm ** ** ** ** Description: ** ** ** *****************************************************************************/ // start autoduck documentation // @DOC pre // @module pre | None // @subindex Scripting Database // @index script | pre /***************************************************************************** ** Includes ** *****************************************************************************/ //#include #include #include #include #include // cd shared by the music streaming stuff... ASSERT if file access attempted // while music is streaming: #include // script stuff #include #include #include /***************************************************************************** ** DBG Information ** *****************************************************************************/ #define DEBUG_PRE 0 namespace File { /***************************************************************************** ** Externals ** *****************************************************************************/ /***************************************************************************** ** Defines ** *****************************************************************************/ #define CURRENT_PRE_VERSION 0xabcd0002 // as of 3/14/2001 //#define CURRENT_PRE_VERSION 0xabcd0001 // until 3/14/2001 #define RINGBUFFERSIZE 4096 /* N size of ring buffer */ #define MATCHLIMIT 18 /* F upper limit for match_length */ #define THRESHOLD 2 /* encode string into position and length */ #define WriteOut(x) {*pOut++ = x;} /***************************************************************************** ** Private Types ** *****************************************************************************/ /***************************************************************************** ** Private Data ** *****************************************************************************/ PreMgr *PreMgr::sp_mgr = NULL; bool PreMgr::s_lastExecuteSuccess = false; /***************************************************************************** ** Public Data ** *****************************************************************************/ /***************************************************************************** ** Private Prototypes ** *****************************************************************************/ /***************************************************************************** ** Private Functions ** *****************************************************************************/ #define USE_BUFFER 1 // we don't need no stinking buffer!!!! #if USE_BUFFER #ifdef __PLAT_NGPS__ // ring buffer is just over 4K 4096+17), // so fits nicely in the PS2's 8K scratchpad unsigned char text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1]; // Note: if we try to use the scratchpad, like this // then the code actually runs slower // if we want to optimize this, then it should // be hand crafted in assembly, using 128bit registers // const unsigned char * text_buf = (unsigned char*) 0x70000000; #else unsigned char text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1]; /* ring buffer of size N, with extra F-1 bytes to facilitate string comparison */ #endif #endif #define ReadInto(x) if (!Len) break; Len--; x = *pIn++ #define ReadInto2(x) Len--; x = *pIn++ // version that knows Len is Ok // Decode an LZSS encoded stream // Runs at approx 12MB/s on PS2 without scratchpad (which slows it down in C) // a 32x CD would run at 4.8MB/sec, although we seem to get a lot less than this // with our current file system, more like 600K per seconds..... // Need to write a fast streaming file system.... void DecodeLZSS(unsigned char *pIn, unsigned char *pOut, int Len) /* Just the reverse of Encode(). */ { int i, j, k, r, c; // uint64 LongWord; // int bytes = 0; // unsigned char *pScratch; unsigned int flags; // int basetime = (int) Tmr::ElapsedTime(0); // int len = Len; // int OutBytes = 4; // int OutWord = 0; #if USE_BUFFER for (i = 0; i < RINGBUFFERSIZE - MATCHLIMIT; i++) text_buf[i] = ' '; r = RINGBUFFERSIZE - MATCHLIMIT; #else r = RINGBUFFERSIZE - MATCHLIMIT; #endif flags = 0; for ( ; ; ) { if (((flags >>= 1) & 256) == 0) { ReadInto(c); flags = c | 0xff00; /* uses higher byte cleverly */ } /* to count eight */ if (flags & 1) { ReadInto(c); // putc(c, outfile); WriteOut(c); #if USE_BUFFER text_buf[r++] = c; r &= (RINGBUFFERSIZE - 1); #else r++; // r &= (RINGBUFFERSIZE - 1); // don't need to wrap r until it is used #endif } else { ReadInto(i); ReadInto2(j); // note, don't need to check len on this one.... i |= ((j & 0xf0) << 4); // i is 12 bit offset #if !USE_BUFFER j = (j & 0x0f) + THRESHOLD+1; // j is 4 bit length (above the threshold) unsigned char *pStream; r &= (RINGBUFFERSIZE - 1); // wrap r around before it is used pStream = pOut - r; // get base of block if (i>=r) // if offset > r, then pStream -= RINGBUFFERSIZE; // it's the previous block pStream += i; // add in the offset to the base r+=j; // add size to r while (j--) // copy j bytes WriteOut(*pStream++); #else j = (j & 0x0f) + THRESHOLD; // j is 4 bit length (above the threshold) for (k = 0; k <= j; k++) // just copy the bytes { c = text_buf[(i+k) & (RINGBUFFERSIZE - 1)]; WriteOut(c); text_buf[r++] = c; r &= (RINGBUFFERSIZE - 1); } #endif } } // int Time = (int) Tmr::ElapsedTime(basetime); // if (Time > 5) // { // printf("decomp time is %d ms, for %d bytes, %d bytes/second\n", Time,len, len * 1000 /Time ); // } } void PreFile::s_delete_file(_File *pFile, void *pData) { Dbg_Assert(pFile); delete pFile; } PreFile::PreFile(uint8 *p_file_buffer) { mp_table = new Lst::StringHashTable<_File>(4); Dbg_AssertPtr(p_file_buffer); mp_buffer = p_file_buffer; #ifdef __NOPT_ASSERT__ uint version = *((int *) (mp_buffer + 4)); Dbg_MsgAssert(version == CURRENT_PRE_VERSION,( "PRE file version (%x) not current (%x)",version,CURRENT_PRE_VERSION)); #endif m_numEntries = *((int *)(mp_buffer + 8)); uint8 *pEntry = mp_buffer + 12; for (int i = 0; i < m_numEntries; i++) { int data_size = *((int *) pEntry); int compressed_data_size = *((int *) (pEntry + 4)); int text_size = *((int *) (pEntry + 8)); int actual_data_size = (compressed_data_size != 0) ? compressed_data_size : data_size; char *pName = (char *) pEntry + 12; uint8 *pCompressedData = pEntry + 12 + text_size; _File *pFile = new (Mem::Manager::sHandle().TopDownHeap()) _File; if (!mp_table->GetItem(pName)) { // file is not in table, safe to add mp_table->PutItem(pName, pFile); pFile->compressedDataSize = compressed_data_size; pFile->pCompressedData = pCompressedData; pFile->pData = NULL; pFile->sky.POS = 0; pFile->sky.SOF = data_size; } else // Somehow, file is already in table, just kill it // Later, I'll want to add an assert delete pFile; # if DEBUG_PRE printf(" %s, size %d\n", pName, data_size); # endif pEntry += 12 + text_size + ((actual_data_size + 3) & (~3)); } # if DEBUG_PRE printf("Done loading PRE\n"); # endif mp_activeFile = NULL; } PreFile::~PreFile() { delete mp_buffer; mp_table->HandleCallback(s_delete_file, NULL); mp_table->FlushAllItems(); delete mp_table; } // returns handle pointer void *PreFile::GetContainedFile(const char *pName) { _File *pFile = mp_table->GetItem(pName, false); if (!pFile) return NULL; dumbSkyFile *pHandle = &pFile->sky; // kinda roundabout, but sets mp_activeFile GetContainedFileByHandle(pHandle); // do we need to fetch file data? if (!mp_activeFile->pData) { if (mp_activeFile->compressedDataSize) { mp_activeFile->pData = new (Mem::Manager::sHandle().TopDownHeap()) uint8[mp_activeFile->sky.SOF]; // need to uncompress data DecodeLZSS(mp_activeFile->pCompressedData, mp_activeFile->pData, mp_activeFile->compressedDataSize); } } return pHandle; } uint8 *PreFile::GetContainedFileByHandle(void *pHandle) { mp_table->IterateStart(); _File *pFile = mp_table->IterateNext(); while(pFile) { uint8 *pCompressedData = pFile->pCompressedData; if (pCompressedData && &pFile->sky == pHandle) { mp_activeFile = pFile; if (mp_activeFile->compressedDataSize) return mp_activeFile->pData; else return mp_activeFile->pCompressedData; } pFile = mp_table->IterateNext(); } return NULL; } void PreFile::Reset() { Dbg_AssertPtr(mp_activeFile); mp_activeFile->sky.POS = 0; } uint32 PreFile::Read(void *addr, uint32 count) { Dbg_AssertPtr(mp_activeFile); int seek_offs = mp_activeFile->sky.POS; unsigned int limit = mp_activeFile->sky.SOF - seek_offs; int copy_number = (count <= limit) ? count : limit; if (mp_activeFile->compressedDataSize) { Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed")); memcpy(addr, mp_activeFile->pData + mp_activeFile->sky.POS, copy_number); } else { memcpy(addr, mp_activeFile->pCompressedData + mp_activeFile->sky.POS, copy_number); } mp_activeFile->sky.POS += copy_number; #if DEBUG_PRE printf("PRE: read %d bytes from file, handle 0x%x\n", copy_number, (int) mp_activeFile->pData); #endif return copy_number; } int PreFile::Eof() { Dbg_AssertPtr(mp_activeFile); if (mp_activeFile->sky.POS >= mp_activeFile->sky.SOF) { #if DEBUG_PRE printf("PRE: at end of file\n"); #endif return 1; } #if DEBUG_PRE printf("PRE: not at end of file\n"); #endif return 0; } void PreFile::Close() { //Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed")); if (mp_activeFile->pData) delete mp_activeFile->pData; mp_activeFile->pData = NULL; } int PreFile::Seek(long offset, int origin) { int32 old_pos = mp_activeFile->sky.POS; // SEEK_CUR, SEEK_END, SEEK_SET switch(origin) { case SEEK_CUR: mp_activeFile->sky.POS += offset; break; case SEEK_END: mp_activeFile->sky.POS = mp_activeFile->sky.SOF - offset; break; case SEEK_SET: mp_activeFile->sky.POS = offset; break; default: return -1; } if (mp_activeFile->sky.POS < 0 || mp_activeFile->sky.POS > mp_activeFile->sky.SOF) { mp_activeFile->sky.POS = old_pos; return -1; } return 0; } PreMgr::PreMgr() { mp_table = new Lst::StringHashTable(4); sp_mgr = this; mp_activeHandle = NULL; mp_activeData = NULL; mp_activeNonPreHandle = NULL; } PreMgr::~PreMgr() { delete mp_table; } // Returns handle // Not frequently called void *PreMgr::getContainedFile(const char *pName) { Dbg_AssertPtr(pName); // replace all '/' with '\' char cleaned_name[128]; const char *pCharIn = pName; char *pCharOut = cleaned_name; while (1) { *pCharOut = *pCharIn; if (*pCharIn == '\0') break; if (*pCharOut == '/') *pCharOut = '\\'; pCharIn++; pCharOut++; } void *pHandle = NULL; mp_table->IterateStart(); PreFile *pPre = mp_table->IterateNext(); while(pPre) { pHandle = pPre->GetContainedFile(cleaned_name); if (pHandle) { mp_activePre = pPre; mp_activeHandle = pHandle; mp_activeData = pPre->GetContainedFileByHandle(pHandle); # ifdef __PLAT_NGPS__ scePrintf("+++ %s is in PRE\n", cleaned_name); # endif return pHandle; } pPre = mp_table->IterateNext(); } # ifdef __PLAT_NGPS__ scePrintf("--- %s not found in PRE\n", cleaned_name); # endif return NULL; } // returns pointer to data uint8 *PreMgr::getContainedFileByHandle(void *pHandle) { Dbg_AssertPtr(pHandle); // if we know that the file in question is not in the PRE system, // then it's a regular file, don't waste time looking for it if (mp_activeNonPreHandle == pHandle) return NULL; if (mp_activeHandle == pHandle) // mp_activePre will be unchanged return mp_activeData; uint8 *pData = NULL; mp_table->IterateStart(); PreFile *pPre = mp_table->IterateNext(); while(pPre) { pData = pPre->GetContainedFileByHandle(pHandle); if (pData) { mp_activePre = pPre; mp_activeHandle = pHandle; mp_activeData = pData; return pData; } pPre = mp_table->IterateNext(); } // obviously this file is not in the PRE system, mark as such mp_activeNonPreHandle = pHandle; return NULL; } // there's a wrapper around this now, so that we can do // some memory-context switching void PreMgr::loadPre(const char *pFilename, bool dont_assert) { # ifdef __PLAT_XBOX__ // Do nothing. return; # endif m_blockPreLoading = (bool) Script::GetInt("block_pre_loading", false); if (m_blockPreLoading) return; if( Pcm::UsingCD() ) { Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." )); return; } // Moved this to below the Pcm::UsingCD() call as that is used (bad!!) to turn off // music and streams. # ifdef __PLAT_NGPS__ // scePrintf("Loading PRE file %s...\n", pFilename); # endif char fullname[256]; sprintf(fullname, "pre\\%s", pFilename); Tmr::Time basetime = Tmr::ElapsedTime(0); int file_size; uint8 *pFile = NULL; file_size = CanFileBeLoadedQuickly( fullname ); if ( file_size ) { pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size]; bool fileLoaded= LoadFileQuicklyPlease( fullname, pFile ); if ( !fileLoaded ) { printf( "pre file %s failed to load quickly.\n", fullname ); Dbg_MsgAssert( 0,( "Fire Matt - pre file didn't load quickly." )); } } else { void *fp = File::Open(fullname, "rb"); if (!fp) { // always run the code below if CD build if (dont_assert || Config::CD()) { printf("couldn't open %s\n", fullname); return; } Dbg_MsgAssert(0,( "couldn't open %s\n", fullname)); } File::Read(&file_size, 4, 1, fp); Dbg_MsgAssert(file_size > 0,( "%s has incorrect file size\n", fullname)); if (Config::CD()) { if (file_size <= 0) printf("%s has incorrect file size\n", fullname); } // Now allocates the .PRE file from the top of the heap, to avoid fragmentation. pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size]; //uint8 *pFile = new uint8[file_size]; File::Read(pFile + 4, 1, file_size, fp); File::Close(fp); } printf("load time for file size %d is %d ms\n", file_size, (int) Tmr::ElapsedTime(basetime)); // the PRE file object winds up at the top of the heap, too. This is fine because // it will be unloaded at the same time as the big file buffer if (!mp_table->PutItem(pFilename, new (Mem::Manager::sHandle().TopDownHeap()) PreFile(pFile))) Dbg_MsgAssert(0,( "PRE %s loaded twice", pFilename)); } /***************************************************************************** ** Public Functions ** *****************************************************************************/ DefineSingletonClass(PreMgr, "PRE Manager"); bool PreMgr::InPre(const char *pFilename) { return mp_table->GetItem( pFilename ); } void PreMgr::LoadPre(const char *pFilename, bool dont_assert) { // GJ: This function is a wrapper around loadPRE(), to // make sure that all allocations go on the top-down heap Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap()); loadPre(pFilename, dont_assert); Mem::Manager::sHandle().PopContext(); } void PreMgr::LoadPrePermanently(const char *pFilename, bool dont_assert) { // GJ: This function is a wrapper around LoadPRE(), // used for loading PRE files which will reside in memory // permanently. Essentially, we need to make sure that the // data is above Renderware's resource arena. If you didn't // use this function, you'd get fragmentation when the resource // arena resized itself. // Mick: Removed reference to renderware, the old "arena" concept is gone // Load the pre file... // This will go on the top-down heap by default LoadPre(pFilename, dont_assert); } void PreMgr::UnloadPre(const char *pFilename, bool dont_assert) { # ifdef __PLAT_NGPS__ // scePrintf("Unloading PRE file %s\n", pFilename); # endif if (m_blockPreLoading) return; PreFile *pThePre = mp_table->GetItem(pFilename); if (!pThePre) { if (dont_assert) return; Dbg_MsgAssert(0,( "PRE file %s not in PRE manager", pFilename)); } mp_table->FlushItem(pFilename); delete pThePre; } bool PreMgr::sPreEnabled() { return sp_mgr != NULL; } bool PreMgr::sPreExecuteSuccess() { return s_lastExecuteSuccess; } bool PreMgr::pre_fexist(const char *name) { Dbg_MsgAssert(name,( "requesting file NULL")); if (sp_mgr->getContainedFile(name)) { # if DEBUG_PRE printf("PRE: file %s exists\n", name); # endif s_lastExecuteSuccess = true; return true; } if ( Pcm::UsingCD( ) ) { Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." )); return false; } return s_lastExecuteSuccess = false; } void *PreMgr::pre_fopen(const char *name, const char *access) { Dbg_MsgAssert(name,( "trying to open file NULL")); void *pHandle = sp_mgr->getContainedFile(name); if (pHandle) { // if we are going to write the file, we want to use the regular file system const char *pChar = access; bool am_writing = false; while(*pChar) { if (*pChar != 'r' && *pChar != 'b') am_writing = true; pChar++; } if (am_writing) { # ifdef __PLAT_NGPS__ // scePrintf(" writing file %s\n", name); # endif // am writing, so we don't need file open in PRE system sp_mgr->mp_activePre->Close(); } else { // we're reading the file from the PRE system # if DEBUG_PRE printf("PRE: opened file %s, handle is 0x%x\n", name, (int) pHandle); # endif sp_mgr->mp_activePre->Reset(); s_lastExecuteSuccess = true; return pHandle; } } // if we get here, we are using the regular file system if ( Pcm::UsingCD( ) ) { Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." )); return NULL; } s_lastExecuteSuccess = false; return NULL; //return pHandle; } int PreMgr::pre_fclose(void *fptr) { Dbg_MsgAssert(fptr,( "calling fclose with invalid file ptr")); uint8 *pData = sp_mgr->getContainedFileByHandle(fptr); if (pData) { #if DEBUG_PRE printf("PRE: closed file, handle 0x%x\n", (int) fptr); #endif sp_mgr->mp_activePre->Close(); s_lastExecuteSuccess = true; return 0; } s_lastExecuteSuccess = false; return 0; } size_t PreMgr::pre_fread(void *addr, size_t size, size_t count, void *fptr) { Dbg_MsgAssert(fptr,( "calling fread with invalid file ptr")); uint8 *pData = sp_mgr->getContainedFileByHandle(fptr); if (pData) { // read from a simulated file stream in PRE file Dbg_AssertPtr(sp_mgr->mp_activePre); s_lastExecuteSuccess = true; return sp_mgr->mp_activePre->Read(addr, size * count); } if ( Pcm::UsingCD( ) ) { Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." )); return 0; } s_lastExecuteSuccess = false; return 0; } size_t PreMgr::pre_fwrite(const void *addr, size_t size, size_t count, void *fptr) { #ifdef __NOPT_ASSERT__ uint8 *pData = #endif sp_mgr->getContainedFileByHandle(fptr); Dbg_MsgAssert(!pData,( "can't write to a PRE file")); if ( Pcm::UsingCD( ) ) { Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." )); return 0; } s_lastExecuteSuccess = false; return 0; } char *PreMgr::pre_fgets(char *buffer, int maxLen, void *fptr) { #ifdef __NOPT_ASSERT__ uint8 *pData = #endif sp_mgr->getContainedFileByHandle(fptr); Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file")); s_lastExecuteSuccess = false; return NULL; } int PreMgr::pre_fputs(const char *buffer, void *fptr) { #ifdef __NOPT_ASSERT__ uint8 *pData = #endif sp_mgr->getContainedFileByHandle(fptr); Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file")); s_lastExecuteSuccess = false; return 0; } int PreMgr::pre_feof(void *fptr) { Dbg_MsgAssert(fptr,( "calling feof with invalid file ptr")); uint8 *pData = sp_mgr->getContainedFileByHandle(fptr); if (pData) { Dbg_AssertPtr(sp_mgr->mp_activePre); s_lastExecuteSuccess = true; return sp_mgr->mp_activePre->Eof(); } s_lastExecuteSuccess = false; return 0; } int PreMgr::pre_fseek(void *fptr, long offset, int origin) { uint8 *pData = sp_mgr->getContainedFileByHandle(fptr); if (pData) { s_lastExecuteSuccess = true; return sp_mgr->mp_activePre->Seek(offset, origin); } Dbg_MsgAssert(!pData,( "seek not supported for PRE file")); s_lastExecuteSuccess = false; return 0; } int PreMgr::pre_fflush(void *fptr) { #ifdef __NOPT_ASSERT__ uint8 *pData = #endif sp_mgr->getContainedFileByHandle(fptr); Dbg_MsgAssert(!pData,( "flush not supported for PRE file")); s_lastExecuteSuccess = false; return 0; } int PreMgr::pre_ftell(void *fptr) { #ifdef __NOPT_ASSERT__ uint8 *pData = #endif sp_mgr->getContainedFileByHandle(fptr); Dbg_MsgAssert(!pData,( "tell supported for PRE file")); s_lastExecuteSuccess = false; return 0; } // @script | InPreFile | // @uparm "string" | filename bool ScriptInPreFile(Script::CStruct *pParams, Script::CScript *pScript) { const char *pFilename; pParams->GetText(NONAME, &pFilename, true); Spt::SingletonPtr pre_mgr; return pre_mgr->InPre(pFilename); } // @script | LoadPreFile | // @uparm "string" | filename // @flag dont_assert | bool ScriptLoadPreFile(Script::CStruct *pParams, Script::CScript *pScript) { const char *pFilename; pParams->GetText(NONAME, &pFilename, true); Spt::SingletonPtr pre_mgr; pre_mgr->LoadPre(pFilename, pParams->ContainsFlag("dont_assert")); return true; } // @script | UnloadPreFile | // @flag BoardsPre | // @flag dont_assert | // @uparm "string" | filename bool ScriptUnloadPreFile(Script::CStruct *pParams, Script::CScript *pScript) { Spt::SingletonPtr pre_mgr; if (pParams->ContainsFlag("BoardsPre")) { // Spt::SingletonPtr< Mdl::Skate > pSkate; // pSkate->UnloadBoardPreIfPresent(pParams->ContainsFlag("dont_assert")); printf ("STUBBED: Unload BoardsPre, in ScriptUnloadPreFile\n"); return true; } const char *pFilename; pParams->GetText(NONAME, &pFilename, true); pre_mgr->UnloadPre(pFilename, pParams->ContainsFlag("dont_assert")); return true; } } // namespace File