yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
7f04adbfb
master
1#include "slang-zip-file-system.h" 2 3#include "slang-blob.h" 4#include "slang-com-helper.h" 5#include "slang-com-ptr.h" 6#include "slang-implicit-directory-collector.h" 7#include "slang-io.h" 8#include "slang-riff.h" 9#include "slang-string-slice-pool.h" 10#include "slang-string-util.h" 11#include "slang-uint-set.h" 12 13#include <miniz.h> 14 15namespace Slang 16{ 17 18class ZipFileSystemImpl :public ComBaseObject , 19public ISlangMutableFileSystem , 20public IArchiveFileSystem 21{ 22public : 23// ISlangUnknown 24SLANG_COM_BASE_IUNKNOWN_ALL 25 26// ISlangCastable 27virtual SLANG_NO_THROW void * SLANG_MCALL castAs (const Guid & guid )SLANG_OVERRIDE ; 28 29// ISlangFileSystem 30virtual SLANG_NO_THROW SlangResult SLANG_MCALL loadFile (char const * path ,ISlangBlob ** outBlob ) 31SLANG_OVERRIDE ; 32 33// ISlangFileSystemExt 34virtual SLANG_NO_THROW SlangResult SLANG_MCALL 35getFileUniqueIdentity (const char * path ,ISlangBlob ** uniqueIdentityOut )SLANG_OVERRIDE ; 36virtual SLANG_NO_THROW SlangResult SLANG_MCALL calcCombinedPath ( 37SlangPathType fromPathType , 38const char * fromPath , 39const char * path , 40ISlangBlob ** pathOut )SLANG_OVERRIDE ; 41virtual SLANG_NO_THROW SlangResult SLANG_MCALL 42getPathType (const char * path ,SlangPathType * pathTypeOut )SLANG_OVERRIDE ; 43virtual SLANG_NO_THROW SlangResult SLANG_MCALL 44getPath (PathKind pathKind ,const char * path ,ISlangBlob ** outPath )SLANG_OVERRIDE ; 45virtual SLANG_NO_THROW void SLANG_MCALL clearCache ()SLANG_OVERRIDE {} 46virtual SLANG_NO_THROW SlangResult SLANG_MCALL enumeratePathContents ( 47const char * path , 48FileSystemContentsCallBack callback , 49void * userData )SLANG_OVERRIDE ; 50virtual SLANG_NO_THROW OSPathKind SLANG_MCALL getOSPathKind ()SLANG_OVERRIDE 51 { 52return OSPathKind ::None ; 53 } 54 55// ISlangModifyableFileSystem 56virtual SLANG_NO_THROW SlangResult SLANG_MCALL 57saveFile (const char * path ,const void * data ,size_t size )SLANG_OVERRIDE ; 58virtual SLANG_NO_THROW SlangResult SLANG_MCALL 59saveFileBlob (const char * path ,ISlangBlob * dataBlob )SLANG_OVERRIDE ; 60virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove (const char * path )SLANG_OVERRIDE ; 61virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory (const char * path )SLANG_OVERRIDE ; 62 63// IArchiveFileSystem 64virtual SLANG_NO_THROW SlangResult SLANG_MCALL 65loadArchive (const void * archive ,size_t archiveSizeInBytes )SLANG_OVERRIDE ; 66virtual SLANG_NO_THROW SlangResult SLANG_MCALL 67storeArchive (bool blobOwnsContent ,ISlangBlob ** outBlob )SLANG_OVERRIDE ; 68virtual SLANG_NO_THROW void SLANG_MCALL setCompressionStyle (const CompressionStyle & style ) 69SLANG_OVERRIDE ; 70 71ZipFileSystemImpl (); 72 ~ZipFileSystemImpl (); 73 74protected : 75enum class Mode 76 { 77None ,// m_archive is not initialized 78Read ,// m_archive is a reader 79ReadWrite ,// m_archive is a writer (that can be read from) 80 }; 81 82SlangResult _requireMode (Mode mode ); 83/// Do the mode change. 84SlangResult _requireModeImpl (Mode newMode ); 85 86bool _hasArchive () {return m_mode != Mode ::None ; } 87SlangResult _getFixedPath (const char * path ,String & outPath ); 88SlangResult _findEntryIndex (const char * path ,mz_uint & outIndex ); 89SlangResult _findEntryIndexFromFixedPath (const String & fixedPath ,mz_uint & outIndex ); 90 91SlangResult _copyToAndInitWriter (mz_zip_archive & outWriter ); 92 93/// Returns SLANG_E_NOT_FOUND if no directory or contents found 94/// terminationState controls when search terminates. If State::Undefined, will enumerate 95/// everything. If outContents not set, will just determine if the directory exists 96SlangResult _getPathContents ( 97ImplicitDirectoryCollector ::State terminationState , 98ImplicitDirectoryCollector * outCollector ); 99 100void _rebuildMap (); 101 102/// Returns true if the named item is at the index 103UnownedStringSlice _getPathAtIndex (Index index ); 104 105void * getInterface (const Guid & guid ); 106void * getObject (const Guid & guid ); 107 108void _initReadWrite (mz_zip_archive & outWriter ); 109 110// Maps from a path to an index in the m_archive 111StringSliceIndexMap m_pathMap ; 112// If bit is set (at the archive index) this index has been deleted. 113UIntSet m_removedSet ; 114 115ScopedAllocation m_data ; 116 117mz_uint m_compressionLevel = MZ_BEST_COMPRESSION ; 118Mode m_mode = Mode ::None ; 119 120mz_file_read_func m_readFunc ; 121 122mz_zip_archive m_archive ; 123}; 124 125void * ZipFileSystemImpl ::getInterface (const Guid & guid ) 126{ 127if (guid == ISlangUnknown ::getTypeGuid ()|| guid == ISlangCastable ::getTypeGuid ()) 128 { 129return static_cast < ISlangMutableFileSystem *> (this ); 130 } 131else if ( 132guid == ISlangFileSystem ::getTypeGuid ()|| guid == ISlangFileSystemExt ::getTypeGuid ()|| 133guid == ISlangMutableFileSystem ::getTypeGuid ()) 134 { 135return static_cast < ISlangMutableFileSystem *> (this ); 136 } 137else if (guid == IArchiveFileSystem ::getTypeGuid ()) 138 { 139return static_cast < IArchiveFileSystem *> (this ); 140 } 141return nullptr ; 142} 143 144void * ZipFileSystemImpl ::getObject (const Guid & guid ) 145{ 146SLANG_UNUSED (guid ); 147return nullptr ; 148} 149 150void * ZipFileSystemImpl ::castAs (const Guid & guid ) 151{ 152if (auto ptr = getInterface (guid )) 153 { 154return ptr ; 155 } 156return getObject (guid ); 157} 158 159// This is a very awkward hack to make it so we can get a read func, without having to implement all 160// of the tracking etc. All this does is create an empty zip, convert into a reader, and then grab 161// the read function 162static mz_file_read_func _calcReadFunc () 163{ 164mz_zip_archive archive ; 165mz_zip_zero_struct (& archive ); 166mz_zip_writer_init_heap (& archive ,0 ,0 ); 167// Convert to reader 168 169void * buf ; 170size_t size ; 171mz_zip_writer_finalize_heap_archive (& archive ,& buf ,& size ); 172ScopedAllocation alloc ; 173alloc .attach (buf ,size ); 174mz_zip_writer_end (& archive ); 175 176// Read 177mz_zip_zero_struct (& archive ); 178mz_zip_reader_init_mem (& archive ,alloc .getData (),alloc .getSizeInBytes (),0 ); 179 180auto readFunc = archive .m_pRead ; 181 182mz_zip_end (& archive ); 183return readFunc ; 184} 185 186static mz_file_read_func _getReadFunc () 187{ 188static const auto readFunc = _calcReadFunc (); 189return readFunc ; 190} 191 192ZipFileSystemImpl ::ZipFileSystemImpl () 193 :m_mode (Mode ::None ) 194{ 195m_readFunc = _getReadFunc (); 196} 197 198ZipFileSystemImpl ::~ZipFileSystemImpl () 199{ 200_requireMode (Mode ::None ); 201} 202 203void ZipFileSystemImpl ::_rebuildMap () 204{ 205m_pathMap .clear (); 206 207const mz_uint entryCount = mz_zip_reader_get_num_files (& m_archive ); 208 209m_removedSet .resizeAndClear (0 ); 210 211for (mz_uint i = 0 ;i < entryCount ;++ i ) 212 { 213mz_zip_archive_file_stat fileStat ; 214if (!mz_zip_reader_file_stat (& m_archive ,mz_uint (i ),& fileStat )) 215 { 216continue ; 217 } 218 219UnownedStringSlice currentName (fileStat .m_filename ); 220 221// Get rid of '/' 222currentName = currentName .trim ('/' ); 223 224m_pathMap .add (currentName ,Index (i )); 225 } 226} 227 228UnownedStringSlice ZipFileSystemImpl ::_getPathAtIndex (Index index ) 229{ 230SLANG_ASSERT (m_mode != Mode ::None ); 231 232mz_zip_archive_file_stat fileStat ; 233// Check it's added at the end 234if (!mz_zip_reader_file_stat (& m_archive ,mz_uint (index ),& fileStat )) 235 { 236return UnownedStringSlice (); 237 } 238 239return UnownedStringSlice (fileStat .m_filename ).trim ('/' ); 240} 241 242void ZipFileSystemImpl ::_initReadWrite (mz_zip_archive & outWriter ) 243{ 244mz_zip_zero_struct (& outWriter ); 245mz_zip_writer_init_heap (& outWriter ,0 ,0 ); 246outWriter .m_pRead = m_readFunc ; 247} 248 249SlangResult ZipFileSystemImpl ::_copyToAndInitWriter (mz_zip_archive & outWriter ) 250{ 251mz_zip_zero_struct (& outWriter ); 252switch (m_mode ) 253 { 254case Mode ::None : 255 { 256_initReadWrite (outWriter ); 257return SLANG_OK ; 258 } 259case Mode ::Read : 260case Mode ::ReadWrite : 261 { 262_initReadWrite (outWriter ); 263 264const mz_uint entryCount = mz_zip_reader_get_num_files (& m_archive ); 265 266for (mz_uint i = 0 ;i < entryCount ;++ i ) 267 { 268if (m_removedSet .contains (i )) 269 { 270continue ; 271 } 272 273// It's worth noting - it's not clear if this will work, because m_archive might not 274// be a reader, in the miniz docs. If it's a writer, it's not clear how to convert a 275// writer to a reader *selectively* which we require if we are going to lazily 276// handle removals. 277// 278// The fix to make this work is the hack that sets the m_reader, such that in effect 279// the writer is both read and write. That works because the default writer behavior 280// is a single block of memory for the archive, and that is compatible with the 281// reader. 282if (!mz_zip_writer_add_from_zip_reader (& outWriter ,& m_archive ,i )) 283 { 284mz_zip_end (& outWriter ); 285return SLANG_FAIL ; 286 } 287 } 288 289return SLANG_OK ; 290 } 291 292default : 293break ; 294 } 295return SLANG_FAIL ; 296} 297 298SlangResult ZipFileSystemImpl ::_requireModeImpl (Mode newMode ) 299{ 300SLANG_ASSERT (newMode != m_mode ); 301 302switch (m_mode ) 303 { 304case Mode ::None : 305 { 306switch (newMode ) 307 { 308case Mode ::Read : 309 { 310mz_uint flags = 0 ; 311mz_zip_zero_struct (& m_archive ); 312mz_zip_reader_init (& m_archive ,0 ,flags ); 313break ; 314 } 315case Mode ::ReadWrite : 316 { 317_initReadWrite (m_archive ); 318break ; 319 } 320default : 321break ; 322 } 323break ; 324 } 325case Mode ::Read : 326 { 327switch (newMode ) 328 { 329case Mode ::None : 330 { 331m_data .deallocate (); 332mz_zip_end (& m_archive ); 333break ; 334 } 335case Mode ::ReadWrite : 336 { 337// If nothing is removed, we can just convert 338if (m_removedSet .isEmpty ()) 339 { 340// Convert the reader into the writer 341if (!mz_zip_writer_init_from_reader (& m_archive ,nullptr )) 342 { 343return SLANG_FAIL ; 344 } 345// If it's now a writer the memory is owned by the m_archive 346m_data .detach (); 347 } 348else 349 { 350// Copy into a new writer 351mz_zip_archive writer ; 352SLANG_RETURN_ON_FAIL (_copyToAndInitWriter (writer )); 353 354// In the process we have removed anything that was deleted 355m_removedSet .clear (); 356// Don't need the read data anymore 357m_data .deallocate (); 358 359// Free the current archive 360mz_zip_end (& m_archive ); 361// Make the writer current 362m_archive = writer ; 363break ; 364 } 365break ; 366 } 367 } 368break ; 369 } 370case Mode ::ReadWrite : 371 { 372switch (newMode ) 373 { 374case Mode ::None : 375 { 376mz_zip_writer_end (& m_archive ); 377break ; 378 } 379case Mode ::Read : 380 { 381// If anything has been removed we copy selectively into a new writer, and then 382// convert that 383if (!m_removedSet .isEmpty ()) 384 { 385// There are entries that are deleted... so we need to copy selectively 386mz_zip_archive writer ; 387SLANG_RETURN_ON_FAIL (_copyToAndInitWriter (writer )); 388 389// In the process we have removed anything that was deleted 390m_removedSet .clear (); 391 392// Get rid of the old writer 393mz_zip_writer_end (& m_archive ); 394m_archive = writer ; 395 } 396 397void * buf ; 398size_t size ; 399mz_zip_writer_finalize_heap_archive (& m_archive ,& buf ,& size ); 400m_data .attach (buf ,size ); 401 402mz_zip_writer_end (& m_archive ); 403 404// Read 405mz_zip_zero_struct (& m_archive ); 406if (!mz_zip_reader_init_mem ( 407& m_archive , 408m_data .getData (), 409m_data .getSizeInBytes (), 4100 )) 411 { 412m_data .deallocate (); 413return SLANG_FAIL ; 414 } 415break ; 416 } 417default : 418break ; 419 } 420 } 421 } 422 423// Set the new mode 424m_mode = newMode ; 425return SLANG_OK ; 426} 427 428SlangResult ZipFileSystemImpl ::_requireMode (Mode newMode ) 429{ 430if (newMode == m_mode ) 431 { 432return SLANG_OK ; 433 } 434 435SlangResult res = _requireModeImpl (newMode ); 436if (SLANG_SUCCEEDED (res )) 437 { 438m_mode = newMode ; 439 } 440 441_rebuildMap (); 442return res ; 443} 444 445SlangResult ZipFileSystemImpl ::_getFixedPath (const char * path ,String & outPath ) 446{ 447StringBuilder simplifiedPath ; 448SLANG_RETURN_ON_FAIL ( 449Path ::simplify (path ,Path ::SimplifyStyle ::AbsoluteOnlyAndNoRoot ,simplifiedPath )); 450outPath = simplifiedPath ; 451return SLANG_OK ; 452} 453 454SlangResult ZipFileSystemImpl ::_findEntryIndexFromFixedPath ( 455const String & fixedPath , 456mz_uint & outIndex ) 457{ 458const Index index = m_pathMap .getValue (fixedPath .getUnownedSlice ()); 459 460// If not in list or deleted - it is removed 461if (index < 0 || m_removedSet .contains (index )) 462 { 463return SLANG_E_NOT_FOUND ; 464 } 465 466outIndex = mz_uint (index ); 467return SLANG_OK ; 468} 469 470SlangResult ZipFileSystemImpl ::_findEntryIndex (const char * path ,mz_uint & outIndex ) 471{ 472String fixedPath ; 473SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 474SLANG_RETURN_ON_FAIL (_findEntryIndexFromFixedPath (fixedPath ,outIndex )); 475return SLANG_OK ; 476} 477 478SlangResult ZipFileSystemImpl ::loadFile (char const * path ,ISlangBlob ** outBlob ) 479{ 480mz_uint index ; 481SLANG_RETURN_ON_FAIL (_findEntryIndex (path ,index )); 482 483// Check it's a file 484mz_zip_archive_file_stat fileStat ; 485if (!mz_zip_reader_file_stat (& m_archive ,index ,& fileStat )|| fileStat .m_is_directory ) 486 { 487return SLANG_E_NOT_FOUND ; 488 } 489 490ScopedAllocation alloc ; 491if (!alloc .allocateTerminated (size_t (fileStat .m_uncomp_size ))) 492 { 493return SLANG_E_OUT_OF_MEMORY ; 494 } 495 496const mz_uint flags = 0 ; 497 498// Extract to memory 499if (!mz_zip_reader_extract_to_mem ( 500& m_archive , 501index , 502alloc .getData (), 503alloc .getSizeInBytes (), 504flags )) 505 { 506return SLANG_FAIL ; 507 } 508 509* outBlob = RawBlob ::moveCreate (alloc ).detach (); 510return SLANG_OK ; 511} 512 513SlangResult ZipFileSystemImpl ::getPathType (const char * path ,SlangPathType * outPathType ) 514{ 515if (!_hasArchive ()) 516 { 517return SLANG_E_NOT_FOUND ; 518 } 519 520String fixedPath ; 521SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 522 523// First look if there is an *explicit* entry - either file or directory 524mz_uint index ; 525if (SLANG_SUCCEEDED (_findEntryIndexFromFixedPath (fixedPath ,index ))) 526 { 527mz_zip_archive_file_stat fileStat ; 528if (!mz_zip_reader_file_stat (& m_archive ,index ,& fileStat )) 529 { 530return SLANG_FAIL ; 531 } 532 533* outPathType = fileStat .m_is_directory ?SLANG_PATH_TYPE_DIRECTORY :SLANG_PATH_TYPE_FILE ; 534return SLANG_OK ; 535 } 536else 537 { 538// It could be an *implicit* directory (ie as part of a path). So lets look for that... 539ImplicitDirectoryCollector collector (fixedPath ); 540SLANG_RETURN_ON_FAIL ( 541_getPathContents (ImplicitDirectoryCollector ::State ::DirectoryExists ,& collector )); 542if (collector .getDirectoryExists ()) 543 { 544* outPathType = SLANG_PATH_TYPE_DIRECTORY ; 545return SLANG_OK ; 546 } 547 } 548 549return SLANG_E_NOT_FOUND ; 550} 551 552SlangResult ZipFileSystemImpl ::getPath (PathKind pathKind ,const char * path ,ISlangBlob ** outPath ) 553{ 554switch (pathKind ) 555 { 556case PathKind ::Display : 557case PathKind ::Canonical : 558 { 559// Get the fixed path 560String fixedPath ; 561SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 562 563// See if we can find in the zip explicitly 564mz_uint index ; 565if (SLANG_SUCCEEDED (_findEntryIndexFromFixedPath (fixedPath ,index ))) 566 { 567mz_zip_archive_file_stat fileStat ; 568if (!mz_zip_reader_file_stat (& m_archive ,index ,& fileStat )) 569 { 570return SLANG_FAIL ; 571 } 572 573// Use the path in the archive itself 574* outPath = StringUtil ::createStringBlob (fileStat .m_filename ).detach (); 575return SLANG_OK ; 576 } 577 578// Else output the fixed path 579* outPath = StringUtil ::createStringBlob (fixedPath ).detach (); 580return SLANG_OK ; 581 } 582case PathKind ::Simplified : 583 { 584* outPath = StringUtil ::createStringBlob (Path ::simplify (path )).detach (); 585return SLANG_OK ; 586 } 587default : 588break ; 589 } 590 591return SLANG_E_NOT_AVAILABLE ; 592} 593 594SlangResult ZipFileSystemImpl ::getFileUniqueIdentity ( 595const char * path , 596ISlangBlob ** outUniqueIdentity ) 597{ 598return getPath (PathKind ::Canonical ,path ,outUniqueIdentity ); 599} 600 601SlangResult ZipFileSystemImpl ::calcCombinedPath ( 602SlangPathType fromPathType , 603const char * fromPath , 604const char * path , 605ISlangBlob ** pathOut ) 606{ 607String relPath ; 608switch (fromPathType ) 609 { 610case SLANG_PATH_TYPE_FILE : 611 { 612relPath = Path ::combine (Path ::getParentDirectory (fromPath ),path ); 613break ; 614 } 615case SLANG_PATH_TYPE_DIRECTORY : 616 { 617relPath = Path ::combine (fromPath ,path ); 618break ; 619 } 620 } 621 622* pathOut = StringUtil ::createStringBlob (relPath ).detach (); 623return SLANG_OK ; 624} 625 626SlangResult ZipFileSystemImpl ::_getPathContents ( 627ImplicitDirectoryCollector ::State terminationState , 628ImplicitDirectoryCollector * outCollector ) 629{ 630if (!_hasArchive ()) 631 { 632return SLANG_E_NOT_FOUND ; 633 } 634 635// Okay - I want to iterate through all of the entries and look for the ones with this prefix 636const Index entryCount = Index (mz_zip_reader_get_num_files (& m_archive )); 637for (Index i = 0 ;i < entryCount ;++ i ) 638 { 639 640// Skip if it's been deleted. 641if (m_removedSet .contains (i )) 642 { 643continue ; 644 } 645 646mz_zip_archive_file_stat fileStat ; 647if (!mz_zip_reader_file_stat (& m_archive ,mz_uint (i ),& fileStat )) 648 { 649continue ; 650 } 651 652UnownedStringSlice currentPath (fileStat .m_filename ); 653SlangPathType pathType = 654fileStat .m_is_directory ?SLANG_PATH_TYPE_DIRECTORY :SLANG_PATH_TYPE_FILE ; 655outCollector -> addPath (pathType ,currentPath ); 656 657// If a termination state is defined, and we reach it, we are done 658if (terminationState != ImplicitDirectoryCollector ::State ::None && 659outCollector -> hasState (terminationState )) 660 { 661return SLANG_OK ; 662 } 663 } 664// Check we found the directory at all... 665return outCollector -> getDirectoryExists () ?SLANG_OK :SLANG_E_NOT_FOUND ; 666} 667 668SlangResult ZipFileSystemImpl ::enumeratePathContents ( 669const char * path , 670FileSystemContentsCallBack callback , 671void * userData ) 672{ 673if (!_hasArchive ()) 674 { 675return SLANG_E_NOT_FOUND ; 676 } 677 678String fixedPath ; 679SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 680ImplicitDirectoryCollector collector (fixedPath ); 681SLANG_RETURN_ON_FAIL (_getPathContents (ImplicitDirectoryCollector ::State ::None ,& collector )); 682return collector .enumerate (callback ,userData ); 683} 684 685SlangResult ZipFileSystemImpl ::saveFileBlob (const char * path ,ISlangBlob * dataBlob ) 686{ 687if (!dataBlob ) 688 { 689return SLANG_E_INVALID_ARG ; 690 } 691 692return saveFile (path ,dataBlob -> getBufferPointer (),dataBlob -> getBufferSize ()); 693} 694 695SlangResult ZipFileSystemImpl ::saveFile (const char * path ,const void * data ,size_t size ) 696{ 697String fixedPath ; 698SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 699 700mz_uint32 index ; 701if (SLANG_SUCCEEDED (_findEntryIndexFromFixedPath (fixedPath ,index ))) 702 { 703// Mark as removed 704m_removedSet .add (index ); 705 } 706 707// We need to be able to write to the archive 708_requireMode (Mode ::ReadWrite ); 709 710// TODO(JS): 711// We may want to check the directory exists that holds the path exists 712// Which is easy to do. Without this check it allows directories to come into existence 713// when the path to the file is used. 714// This behaviour *isn't* strictly the same as the file system, which requires the path 715// to a file to exist before it is written. 716// 717// Not enforcing this allows zips that don't explicitly specify paths - which saves space 718// and is simpler. 719// 720// NOTE! This also means that if a file that produces an implicit path is *removed* that 721// the implicit directories are also in effect removed. 722 723// Need to add to the end of the file 724const mz_uint32 entryCount = mz_zip_reader_get_num_files (& m_archive ); 725if (!mz_zip_writer_add_mem (& m_archive ,fixedPath .getBuffer (),data ,size ,m_compressionLevel )) 726 { 727return SLANG_FAIL ; 728 } 729 730// Make sure it is added at expended index 731SLANG_ASSERT (_getPathAtIndex (entryCount )== fixedPath .getUnownedSlice ()); 732 733// Set in the map 734m_pathMap .add (fixedPath .getUnownedSlice (),entryCount ); 735return SLANG_OK ; 736} 737 738SlangResult ZipFileSystemImpl ::remove (const char * path ) 739{ 740String fixedPath ; 741SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 742 743mz_uint32 index ; 744SLANG_RETURN_ON_FAIL (_findEntryIndexFromFixedPath (fixedPath ,index )); 745 746mz_zip_archive_file_stat fileStat ; 747if (!mz_zip_reader_file_stat (& m_archive ,index ,& fileStat )) 748 { 749return SLANG_FAIL ; 750 } 751 752if (fileStat .m_is_directory ) 753 { 754// Find the directory contents 755ImplicitDirectoryCollector collector (fixedPath ); 756SLANG_RETURN_ON_FAIL ( 757_getPathContents (ImplicitDirectoryCollector ::State ::HasContent ,& collector )); 758 759if (collector .hasContent ()) 760 { 761// If it contains children we can't remove it 762return SLANG_FAIL ; 763 } 764 } 765 766// Mark as removed 767m_removedSet .add (index ); 768return SLANG_OK ; 769} 770 771SlangResult ZipFileSystemImpl ::createDirectory (const char * path ) 772{ 773String fixedPath ; 774SLANG_RETURN_ON_FAIL (_getFixedPath (path ,fixedPath )); 775 776// If we find something with this name, we can't create it 777mz_uint32 index ; 778if (SLANG_SUCCEEDED (_findEntryIndexFromFixedPath (fixedPath ,index ))) 779 { 780return SLANG_FAIL ; 781 } 782 783// Make writable 784SLANG_RETURN_ON_FAIL (_requireMode (Mode ::ReadWrite )); 785 786const mz_uint entryCount = mz_zip_reader_get_num_files (& m_archive ); 787 788// The terminating / in the path indicates it's a directory 789 { 790String dirPath (fixedPath ); 791dirPath .appendChar ('/' ); 792if (!mz_zip_writer_add_mem (& m_archive ,dirPath .getBuffer (),nullptr ,0 ,m_compressionLevel )) 793 { 794return SLANG_FAIL ; 795 } 796 } 797 798SLANG_ASSERT (_getPathAtIndex (entryCount )== fixedPath .getUnownedSlice ()); 799 800// Set the index, that we added at end 801m_pathMap .add (fixedPath .getUnownedSlice (),entryCount ); 802return SLANG_OK ; 803} 804 805SlangResult ZipFileSystemImpl ::storeArchive (bool blobOwnsContent ,ISlangBlob ** outBlob ) 806{ 807// If we have anything deleted in 'Read', we need to convert to 'Write' and then back to read 808if (m_mode == Mode ::Read && !m_removedSet .isEmpty ()) 809 { 810_requireMode (Mode ::ReadWrite ); 811 } 812 813_requireMode (Mode ::Read ); 814 815ComPtr < ISlangBlob > blob ; 816 817if (blobOwnsContent ) 818 { 819// Takes a copy 820blob = RawBlob ::create (m_data .getData (),Index (m_data .getSizeInBytes ())); 821 } 822else 823 { 824// Doesn't take a copy... Must use with care(!) 825blob = UnownedRawBlob ::create (m_data .getData (),Index (m_data .getSizeInBytes ())); 826 } 827* outBlob = blob .detach (); 828return SLANG_OK ; 829} 830 831SlangResult ZipFileSystemImpl ::loadArchive (const void * archive ,size_t archiveSizeInBytes ) 832{ 833// Making the mode None empties the archive 834SLANG_RETURN_ON_FAIL (_requireMode (Mode ::None )); 835 836// Store a copy of the archive contents 837if (!m_data .set (archive ,archiveSizeInBytes )) 838 { 839return SLANG_E_OUT_OF_MEMORY ; 840 } 841 842// Initialize archive 843mz_zip_zero_struct (& m_archive ); 844 845// Read the contents of the archive, and make m_archive own it 846if (!mz_zip_reader_init_mem (& m_archive ,m_data .getData (),archiveSizeInBytes ,0 )) 847 { 848return SLANG_FAIL ; 849 } 850 851m_mode = Mode ::Read ; 852 853// Set up the mapping from paths to indices 854_rebuildMap (); 855 856return SLANG_OK ; 857} 858 859void ZipFileSystemImpl ::setCompressionStyle (const CompressionStyle & style ) 860{ 861switch (style .m_type ) 862 { 863case CompressionStyle ::Type ::BestSpeed : 864m_compressionLevel = MZ_BEST_SPEED ; 865break ; 866case CompressionStyle ::Type ::BestCompression : 867m_compressionLevel = MZ_BEST_COMPRESSION ; 868break ; 869case CompressionStyle ::Type ::Default : 870m_compressionLevel = MZ_DEFAULT_LEVEL ; 871break ; 872case CompressionStyle ::Type ::Level : 873 { 874int level = int (style .m_level * 10.0f + 0.5 ); 875level = (level < 0 ) ?0 :level ; 876level = (level > MZ_UBER_COMPRESSION ) ?MZ_UBER_COMPRESSION :level ; 877m_compressionLevel = level ; 878break ; 879 } 880 } 881} 882 883/* static */ SlangResult ZipFileSystem ::create (ComPtr < ISlangMutableFileSystem >& out ) 884{ 885out = new ZipFileSystemImpl ; 886return SLANG_OK ; 887} 888 889/* static */ bool ZipFileSystem ::isArchive (const void * data ,size_t dataSizeInBytes ) 890{ 891if (dataSizeInBytes < sizeof (FourCC )) 892 { 893return false; 894 } 895 896FourCC fourCC = 0 ; 897 ::memcpy (& fourCC ,data ,sizeof (FourCC )); 898 899// https://en.wikipedia.org/wiki/List_of_file_signatures 900switch (fourCC ) 901 { 902case SLANG_FOUR_CC (0x50 ,0x4B ,0x03 ,0x04 ): 903case SLANG_FOUR_CC (0x50 ,0x4B ,0x05 ,0x06 ): 904case SLANG_FOUR_CC (0x50 ,0x4B ,0x07 ,0x08 ): 905 { 906// It's a zip 907return true; 908 } 909 } 910return false; 911} 912 913}// namespace Slang