[libromdata] CisoPspReader: Added initial support for JISO.

It has a unique header, but the index entries table is similar to the
other formats. It does *not* set the high bit to indicate NC; instead,
it uses the same method as CISOv2, where the compressed block size
matches the uncompressed block size.

Compression algorithm is lzo1x.

TODO:
- JISO supports NC areas. Figure this part out.
- Add LZO to extlib.
This commit is contained in:
David Korth 2020-09-05 05:03:29 -04:00
parent 85485f9d37
commit 640cb2bf8a
11 changed files with 363 additions and 19 deletions

View File

@ -125,6 +125,7 @@ INCLUDE(CheckJPEG)
INCLUDE(CheckTinyXML2)
INCLUDE(CheckZSTD)
INCLUDE(CheckLZ4)
INCLUDE(CheckLZO)
# Reference: https://cmake.org/Wiki/RecipeAddUninstallTarget
########### Add uninstall target ###############
@ -220,6 +221,16 @@ ELSE(ENABLE_LZ4)
SET(ENABLE_LZ4_MSG "Disabled")
ENDIF(ENABLE_LZ4)
IF(ENABLE_LZO)
IF(USE_INTERNAL_LZO)
SET(ENABLE_LZO_MSG "Enabled (bundled)")
ELSE(USE_INTERNAL_LZO)
SET(ENABLE_LZO_MSG "Enabled (system)")
ENDIF(USE_INTERNAL_LZO)
ELSE(ENABLE_LZO)
SET(ENABLE_LZO_MSG "Disabled")
ENDIF(ENABLE_LZO)
UNSET(EXTLIB_BUILD)
IF(USE_INTERNAL_ZLIB)
@ -237,6 +248,9 @@ ENDIF(USE_INTERNAL_ZSTD)
IF(USE_INTERNAL_LZ4)
SET(EXTLIB_BUILD "${EXTLIB_BUILD} - lz4\n")
ENDIF(USE_INTERNAL_LZ4)
IF(USE_INTERNAL_LZO)
SET(EXTLIB_BUILD "${EXTLIB_BUILD} - lzo\n")
ENDIF(USE_INTERNAL_LZO)
SET(EXTLIB_BUILD "${EXTLIB_BUILD} - minizip\n")
SET(EXTLIB_BUILD "${EXTLIB_BUILD} - inih\n")
IF(USE_INTERNAL_XML)
@ -258,6 +272,7 @@ MESSAGE("
- PVRTC decoder: ${ENABLE_PVRTC_MSG}
- ZSTD decompression: ${ENABLE_ZSTD_MSG}
- LZ4 decompression: ${ENABLE_LZ4_MSG}
- LZO decompression: ${ENABLE_LZO_MSG}
- Building these third-party libraries from extlib:
${EXTLIB_BUILD}")

53
cmake/libs/CheckLZO.cmake Normal file
View File

@ -0,0 +1,53 @@
# Check for LZO.
# If LZO isn't found, extlib/lzo/ will be used instead.
IF(ENABLE_LZO)
IF(NOT USE_INTERNAL_LZO)
IF(LZO_LIBRARY MATCHES "^lzo$" OR LZO_LIBRARY MATCHES "^lzo")
# Internal LZO was previously in use.
UNSET(LZO_FOUND)
UNSET(HAVE_LZO)
UNSET(LZO_LIBRARY CACHE)
UNSET(LZO_LIBRARIES CACHE)
ENDIF()
# Check for LZO.
FIND_PACKAGE(LZO)
IF(LZO_FOUND)
# Found system LZO.
SET(HAVE_LZO 1)
ELSE()
# System LZO was not found.
MESSAGE(STATUS "Using the internal copy of LZO since a system version was not found.")
# FIXME: Uncomment after adding extlib/lzo/.
#SET(USE_INTERNAL_LZO ON CACHE BOOL "Use the internal copy of LZO" FORCE)
SET(ENABLE_LZO OFF CACHE BOOL "Enable LZO decompression. (Required for some PSP disc formats.)" FORCE)
ENDIF()
ELSE()
MESSAGE(STATUS "Using the internal copy of LZO.")
ENDIF(NOT USE_INTERNAL_LZO)
IF(USE_INTERNAL_LZO)
# Using the internal LZO library.
SET(LZO_FOUND 1)
SET(HAVE_LZO 1)
# FIXME: When was it changed from LIBRARY to LIBRARIES?
IF(WIN32 OR APPLE)
# Using DLLs on Windows and Mac OS X.
SET(USE_INTERNAL_LZO_DLL ON)
SET(LZO_LIBRARY lzo_shared CACHE INTERNAL "LZO library" FORCE)
ELSE()
# Using static linking on other systems.
SET(USE_INTERNAL_LZO_DLL OFF)
SET(LZO_LIBRARY lzo_static CACHE INTERNAL "LZO library" FORCE)
ENDIF()
SET(LZO_LIBRARIES ${LZO_LIBRARY})
# FIXME: When was it changed from DIR to DIRS?
SET(LZO_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/extlib/lzo)
SET(LZO_INCLUDE_DIRS ${LZO_INCLUDE_DIR})
ELSE(USE_INTERNAL_LZO)
SET(USE_INTERNAL_LZO_DLL OFF)
ENDIF(USE_INTERNAL_LZO)
ENDIF(ENABLE_LZO)

22
cmake/libs/FindLZO.cmake Normal file
View File

@ -0,0 +1,22 @@
# Find LZO libraries and headers.
# If found, the following variables will be defined:
# - LZO_FOUND: System has LZO.
# - LZO_INCLUDE_DIRS: LZO include directories.
# - LZO_LIBRARIES: LZO libraries.
# - LZO_DEFINITIONS: Compiler switches required for using LZO.
#
# In addition, a target LZO::lzo will be created with all of
# these definitions.
#
# References:
# - https://cmake.org/Wiki/CMake:How_To_Find_Libraries
# - http://francesco-cek.com/cmake-and-gtk-3-the-easy-way/
#
INCLUDE(FindLibraryPkgConfig)
FIND_LIBRARY_PKG_CONFIG(LZO
lzo2 # pkgconfig
lzo/lzo1x.h # header
lzo2 # library
LZO::lzo # imported target
)

View File

@ -45,6 +45,9 @@ OPTION(BUILD_CLI "Build the `rpcli` command line program." ON)
OPTION(ENABLE_XML "Enable XML parsing for e.g. Windows manifests." ON)
OPTION(ENABLE_ZSTD "Enable ZSTD decompression. (Required for some unit tests.)" ON)
OPTION(ENABLE_LZ4 "Enable LZ4 decompression. (Required for some PSP disc formats.)" ON)
IF(NOT WIN32)
OPTION(ENABLE_LZO "Enable LZO decompression. (Required for some PSP disc formats.)" ON)
ENDIF(NOT WIN32)
IF(WIN32)
SET(USE_INTERNAL_ZLIB ON)
@ -52,12 +55,14 @@ IF(WIN32)
SET(USE_INTERNAL_XML ${ENABLE_XML})
SET(USE_INTERNAL_ZSTD ${ENABLE_ZSTD})
SET(USE_INTERNAL_LZ4 ${ENABLE_LZ4})
SET(USE_INTERNAL_LZO ${ENABLE_LZO})
ELSE(WIN32)
OPTION(USE_INTERNAL_ZLIB "Use the internal copy of zlib." OFF)
OPTION(USE_INTERNAL_PNG "Use the internal copy of libpng." OFF)
OPTION(USE_INTERNAL_XML "Use the internal copy of TinyXML2." OFF)
OPTION(USE_INTERNAL_ZSTD "Use the internal copy of zstd." OFF)
OPTION(USE_INTERNAL_LZ4 "Use the internal copy of LZ4." OFF)
OPTION(USE_INTERNAL_LZO "Use the internal copy of LZO." OFF)
ENDIF()
# TODO: If APNG export is added, verify that system libpng

View File

@ -455,6 +455,9 @@ ENDIF(ENABLE_LIBMSPACK)
IF(ENABLE_LZ4 AND LZ4_FOUND)
TARGET_LINK_LIBRARIES(romdata PRIVATE ${LZ4_LIBRARY})
ENDIF(ENABLE_LZ4 AND LZ4_FOUND)
IF(ENABLE_LZO AND LZO_FOUND)
TARGET_LINK_LIBRARIES(romdata PRIVATE ${LZO_LIBRARY})
ENDIF(ENABLE_LZO AND LZO_FOUND)
# Unix: Add -fpic/-fPIC in order to use this static library in plugins.
IF(UNIX AND NOT APPLE)

View File

@ -265,6 +265,9 @@ const RomDataFactoryPrivate::RomDataFns RomDataFactoryPrivate::romDataFns_magic[
#ifdef HAVE_LZ4
GetRomDataFns_addr(PSP, ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA, 0, 'ZISO'),
#endif /* HAVE_LZ4 */
#ifdef HAVE_LZO
GetRomDataFns_addr(PSP, ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA, 0, 'JISO'),
#endif /* HAVE_LZO */
GetRomDataFns_addr(PSP, ATTR_HAS_THUMBNAIL | ATTR_HAS_METADATA, 0, 0x44415800), // 'DAX\0'
// Audio

View File

@ -30,9 +30,23 @@
/* Define to 1 if we're using the internal copy of LZ4 as a DLL. */
#cmakedefine USE_INTERNAL_LZ4_DLL 1
/* Define to 1 if zlib is a DLL. */
/* Define to 1 if LZ4 is a DLL. */
#if !defined(USE_INTERNAL_LZ4) || defined(USE_INTERNAL_LZ4_DLL)
# define LZ4_IS_DLL 1
#endif
/* Define to 1 if you have LZO. */
#cmakedefine HAVE_LZO 1
/* Define to 1 if we're using the internal copy of LZO. */
#cmakedefine USE_INTERNAL_LZO 1
/* Define to 1 if we're using the internal copy of LZO as a DLL. */
#cmakedefine USE_INTERNAL_LZO_DLL 1
/* Define to 1 if LZO is a DLL. */
#if !defined(USE_INTERNAL_LZO) || defined(USE_INTERNAL_LZO_DLL)
# define LZO_IS_DLL 1
#endif
#endif /* __ROMPROPERTIES_LIBROMDATA_CONFIG_H__ */

View File

@ -29,6 +29,11 @@
# include <lz4.h>
#endif /* HAVE_LZ4 */
// LZO (JISO)
#ifdef HAVE_LZO
# include <lzo/lzo1x.h>
#endif /* HAVE_LZO */
// librpbase, librpfile
using namespace LibRpBase;
using LibRpFile::IRpFile;
@ -44,6 +49,9 @@ DELAYLOAD_TEST_FUNCTION_IMPL0(zlibVersion);
# ifdef HAVE_LZ4
DELAYLOAD_TEST_FUNCTION_IMPL0(LZ4_versionNumber);
# endif /* HAVE_LZ4 */
# ifdef HAVE_LZO
DELAYLOAD_TEST_FUNCTION_IMPL0(lzo_version);
# endif /* HAVE_LZO */
#endif /* _MSC_VER */
class CisoPspReaderPrivate : public SparseDiscReaderPrivate {
@ -58,11 +66,15 @@ class CisoPspReaderPrivate : public SparseDiscReaderPrivate {
enum class CisoType {
Unknown = -1,
CISO = 0, // CISO
CISO = 0,
#ifdef HAVE_LZ4
ZISO = 1, // ZISO
ZISO = 1,
#endif /* HAVE_LZ4 */
DAX = 2, // DAX
#ifdef HAVE_LZO
JISO = 2,
#endif /* HAVE_LZO */
DAX = 3,
Max
};
@ -71,6 +83,9 @@ class CisoPspReaderPrivate : public SparseDiscReaderPrivate {
// Header.
union {
CisoPspHeader cisoPsp;
#ifdef HAVE_LZO
JisoHeader jiso;
#endif /* HAVE_LZO */
DaxHeader dax;
} header;
@ -154,6 +169,22 @@ uint32_t CisoPspReaderPrivate::getBlockCompressedSize(uint32_t blockNum) const
}
break;
#ifdef HAVE_LZO
case CisoPspReaderPrivate::CisoType::JISO:
// Index entry table has an extra entry for the final block.
// Hence, we don't need the same workaround as GCZ.
// NOTE: JISO doesn't use the high bit for NC.
assert(blockNum != indexEntries.size()-1);
if (blockNum < indexEntries.size()-1) {
off64_t idxStart = le32_to_cpu(indexEntries[blockNum]);
off64_t idxEnd = le32_to_cpu(indexEntries[blockNum + 1]);
idxStart <<= index_shift;
idxEnd <<= index_shift;
size = static_cast<uint32_t>(idxEnd - idxStart);
}
break;
#endif /* HAVE_LZO */
case CisoPspReaderPrivate::CisoType::DAX:
// DAX uses a separate size table.
size = static_cast<uint32_t>(daxSizeTable[blockNum]);
@ -201,6 +232,9 @@ CisoPspReader::CisoPspReader(IRpFile *file)
# if defined(HAVE_LZ4) && defined(LZ4_IS_DLL)
bool isLZ4 = false;
# endif /* HAVE_LZ4 && LZ4_IS_DLL */
# if defined(HAVE_LZO) && defined(LZO_IS_DLL)
bool isLZO = false;
# endif /* HAVE_LZO && LZO_IS_DLL */
#endif /* MSC_VER */
switch (d->cisoType) {
default:
@ -241,6 +275,26 @@ CisoPspReader::CisoPspReader(IRpFile *file)
#endif /* _MSC_VER && HAVE_LZ4 */
break;
#ifdef HAVE_LZO
case CisoPspReaderPrivate::CisoType::JISO:
#if defined(_MSC_VER) && defined(LZO_IS_DLL)
isLZO = true;
#endif /* _MSC_VER && LZO_IS_DLL */
#if SYS_BYTEORDER != SYS_LIL_ENDIAN
// Byteswap the header.
d->header.jiso.magic = le32_to_cpu(d->header.jiso.magic);
d->header.jiso.block_size = le16_to_cpu(d->header.jiso.block_size);
d->header.jiso.uncompressed_size = le32_to_cpu(d->header.jiso.uncompressed_size);
d->header.jiso.header_size = le32_to_cpu(d->header.jiso.block_size);
#endif /* SYS_BYTEORDER != SYS_LIL_ENDIAN */
d->block_size = d->header.jiso.block_size;
d->disc_size = d->header.jiso.uncompressed_size;
// TODO: index_shift field?
indexEntryTblPos = static_cast<off64_t>(sizeof(d->header.jiso));
break;
#endif /* HAVE_LZO */
case CisoPspReaderPrivate::CisoType::DAX:
#if defined(_MSC_VER) && defined(ZLIB_IS_DLL)
isZlib = true;
@ -281,6 +335,15 @@ CisoPspReader::CisoPspReader(IRpFile *file)
}
}
# endif /* HAVE_LZ4 && LZ4_IS_DLL */
# if defined(HAVE_LZO) && defined(LZO_IS_DLL)
if (isLZO) {
if (DelayLoad_test_lzo_version() != 0) {
// Delay load for LZO failed.
UNREF_AND_NULL_NOCHK(m_file);
return;
}
}
# endif /* HAVE_LZ4 && LZ4_IS_DLL */
#endif /* _MSC_VER */
// Calculate the number of blocks.
@ -295,13 +358,19 @@ CisoPspReader::CisoPspReader(IRpFile *file)
// Read the index entries.
// NOTE: These are byteswapped on demand, not ahead of time.
uint32_t num_blocks_alloc = num_blocks;
if (d->cisoType == CisoPspReaderPrivate::CisoType::CISO
switch (d->cisoType) {
case CisoPspReaderPrivate::CisoType::CISO:
#ifdef HAVE_LZ4
|| d->cisoType == CisoPspReaderPrivate::CisoType::ZISO
case CisoPspReaderPrivate::CisoType::ZISO:
#endif /* HAVE_LZ4 */
)
{
num_blocks_alloc++;
#ifdef HAVE_LZO
case CisoPspReaderPrivate::CisoType::JISO:
#endif /* HAVE_LZO */
num_blocks_alloc++;
break;
default:
break;
}
d->indexEntries.resize(num_blocks_alloc);
size_t expected_size = num_blocks_alloc * sizeof(uint32_t);
@ -316,6 +385,12 @@ CisoPspReader::CisoPspReader(IRpFile *file)
return;
}
if (d->cisoType == CisoPspReaderPrivate::CisoType::JISO) {
// Initialize LZO.
lzo_init();
}
// TODO: NC areas for JISO.
if (d->cisoType == CisoPspReaderPrivate::CisoType::DAX) {
// Read the DAX size table.
d->daxSizeTable.resize(num_blocks);
@ -420,7 +495,7 @@ int CisoPspReader::isDiscSupported_static(const uint8_t *pHeader, size_t szHeade
#endif /* HAVE_LZ4 */
) && szHeader >= sizeof(CisoPspHeader))
{
// CISO magic.
// CISO/ZISO magic.
const CisoPspHeader *const cisoPspHeader = reinterpret_cast<const CisoPspHeader*>(pHeader);
// Header size:
@ -482,6 +557,39 @@ int CisoPspReader::isDiscSupported_static(const uint8_t *pHeader, size_t szHeade
}
#endif /* HAVE_LZ4 */
return (int)CisoPspReaderPrivate::CisoType::CISO;
} else if (*pu32 == be32_to_cpu(JISO_MAGIC) && szHeader >= sizeof(JisoHeader)) {
// JISO magic.
const JisoHeader *const jisoHeader = reinterpret_cast<const JisoHeader*>(pHeader);
// TODO: Version number field?
// Check if the block size is a supported power of two.
// - Minimum: JISO_BLOCK_SIZE_MIN ( 2 KB, 1 << 11)
// - Maximum: JISO_BLOCK_SIZE_MAX (64 KB, 1 << 16)
const uint32_t block_size = le32_to_cpu(jisoHeader->block_size);
if (!isPow2(block_size) ||
block_size < JISO_BLOCK_SIZE_MIN || block_size > JISO_BLOCK_SIZE_MAX)
{
// Block size is out of range.
return (int)CisoPspReaderPrivate::CisoType::Unknown;
}
// Must have at least one block and less than 4 GB of uncompressed data.
// NOTE: 4 GB is the implied maximum size due to use of uint32_t.
const uint32_t uncompressed_size = le32_to_cpu(jisoHeader->uncompressed_size);
if (uncompressed_size < DAX_BLOCK_SIZE) {
// Less than one block...
return (int)CisoPspReaderPrivate::CisoType::Unknown;
}
// Uncompressed data size must be a multiple of the block size.
if (uncompressed_size % block_size != 0) {
// Not a multiple.
return (int)CisoPspReaderPrivate::CisoType::Unknown;
}
// This is a valid JISO image.
return (int)CisoPspReaderPrivate::CisoType::JISO;
} else if (*pu32 == be32_to_cpu(DAX_MAGIC) && szHeader >= sizeof(DaxHeader)) {
// DAX magic.
const DaxHeader *const daxHeader = reinterpret_cast<const DaxHeader*>(pHeader);
@ -562,6 +670,14 @@ off64_t CisoPspReader::getPhysBlockAddr(uint32_t blockIdx) const
addr <<= d->index_shift;
break;
#ifdef HAVE_LZO
case CisoPspReaderPrivate::CisoType::JISO:
// TODO: Is index_shift actually supported by JISO?
addr = static_cast<off64_t>(d->indexEntries[blockIdx]);
addr <<= d->index_shift;
break;
#endif /* HAVE_LZO */
case CisoPspReaderPrivate::CisoType::DAX:
addr = static_cast<off64_t>(d->indexEntries[blockIdx]);
break;
@ -621,6 +737,7 @@ int CisoPspReader::readBlock(uint32_t blockIdx, void *ptr, int pos, size_t size)
None = 0,
Deflate = 1,
LZ4 = 2,
LZO = 3,
};
CompressionMode z_mode;
int windowBits = -15; // raw deflate (CISO)
@ -684,6 +801,23 @@ int CisoPspReader::readBlock(uint32_t blockIdx, void *ptr, int pos, size_t size)
break;
#endif /* HAVE_LZ4 */
#ifdef HAVE_LZO
case CisoPspReaderPrivate::CisoType::JISO:
// JISO uses LZO.
// TODO: Verify the rest of this.
// JISO does *not* indicate compression using the high bit.
// Instead, the compressed block size will match the uncompressed
// block size, similar to CISOv2.
physBlockAddr = static_cast<off64_t>(indexEntry);
physBlockAddr <<= d->index_shift;
z_mode = (z_block_size == d->block_size)
? CompressionMode::None
: CompressionMode::LZO;
break;
#endif /* HAVE_LZO */
case CisoPspReaderPrivate::CisoType::DAX:
physBlockAddr = static_cast<off64_t>(indexEntry);
if (d->header.dax.nc_areas > 0 && d->daxNCTable[blockIdx]) {
@ -815,6 +949,55 @@ int CisoPspReader::readBlock(uint32_t blockIdx, void *ptr, int pos, size_t size)
return 0;
#endif /* HAVE_LZ4 */
}
case CompressionMode::LZO: {
#ifdef HAVE_LZO
// Read compressed data into a temporary buffer,
// then decompress it.
uint32_t z_max_size = d->block_size;
if (unlikely(d->isDaxWithoutNCTable)) {
// DAX without NC table can end up compressing to larger
// than the uncompressed size.
z_max_size *= 2;
}
if (z_block_size > z_max_size) {
// Compressed data is larger than the uncompressed block size.
// This is only allowed for DAX without NC table.
m_lastError = EIO;
return 0;
}
size_t sz_read = m_file->seekAndRead(physBlockAddr, d->z_buffer.data(), z_block_size);
if (sz_read != z_block_size) {
// Seek and/or read error.
m_lastError = m_file->lastError();
if (m_lastError == 0) {
m_lastError = EIO;
}
return 0;
}
// Decompress the data.
// TODO: LZO in-place decompression?
lzo_uint dst_len = d->block_size;
int ret = lzo1x_decompress_safe(
d->z_buffer.data(), z_block_size,
d->blockCache.data(), &dst_len,
nullptr);
if (ret != LZO_E_OK || dst_len != d->block_size) {
// Decompression error.
// TODO: Print warnings and/or more comprehensive error codes.
d->blockCacheIdx = ~0U;
m_lastError = EIO;
return 0;
}
break;
#else /* !HAVE_LZO */
assert(!"LZO is not enabled in this build.");
m_lastError = EIO;
return 0;
#endif /* HAVE_LZO */
}
}
// Block has been loaded into the cache.

View File

@ -78,6 +78,39 @@ ASSERT_STRUCT(DaxNCArea, 2*sizeof(uint32_t));
// DAX has a fixed block size.
#define DAX_BLOCK_SIZE 0x2000
/**
* PlayStation Portable JISO header.
* NOTE: Based on reverse-engineered samples,
* so this may be incomplete.
*
* - An extra index entry is included to determine the size of the
* last compressed block, similar to CISO.
* - Index entries do NOT use the high bit to indicate uncompressed.
* - TODO: There might be an NC area.
* - If a block is uncompressed, the difference between index entries
* equals the block size. (Same as CISOv2.)
*
* All fields are in little-endian.
*/
#define JISO_MAGIC 'JISO' // JISO
typedef struct _JisoHeader {
uint32_t magic; // [0x000] 'JISO'
uint8_t unk3; // [0x004] 0x03?
uint8_t unk1; // [0x005] 0x01?
uint16_t block_size; // [0x006] Block size, usually 2048.
uint32_t unk_nc_area; // [0x008] Unknown (NC areas?)
uint32_t uncompressed_size; // [0x00C] Uncompressed data size.
uint8_t unknown_data[16]; // [0x010]
uint32_t header_size; // [0x020] Header size? (0x30)
uint8_t unknown[12]; // [0x024]
} JisoHeader;
ASSERT_STRUCT(JisoHeader, 0x30);
// 2 KB minimum block size (DVD sector)
// 64 KB maximum block size
#define JISO_BLOCK_SIZE_MIN (2048)
#define JISO_BLOCK_SIZE_MAX (64*1024)
#ifdef __cplusplus
}
#endif /* __cplusplus */

View File

@ -68,6 +68,7 @@ application/x-nintendo-ds-rom # NintendoDS
application/x-nintendo-dsi-rom # NintendoDS
application/x-psp-ciso-image # PSP
application/x-psp-dax-image # PSP
application/x-psp-jiso-image # PSP
application/x-psp-ziso-image # PSP
# Other

View File

@ -573,6 +573,27 @@
</match>
</magic>
</mime-type>
<mime-type type="application/x-psp-dax-image">
<comment>PlayStation Portable DAX disc image</comment>
<sub-class-of type="application/x-cd-image"/>
<generic-icon name="application-x-cd-image"/>
<glob pattern="*.dax"/>
<magic priority="50">
<match value="0x41445800" type="big32" offset="0"/>
</magic>
</mime-type>
<mime-type type="application/x-psp-jiso-image">
<comment>PlayStation Portable JISO disc image</comment>
<sub-class-of type="application/x-cd-image"/>
<generic-icon name="application-x-cd-image"/>
<glob pattern="*.jso"/>
<magic priority="50">
<match value="JISO" type="string" offset="0">
<!-- TODO: Verify this. -->
<match value="0x30" type="little32" offset="32"/>
</match>
</magic>
</mime-type>
<mime-type type="application/x-psp-ziso-image">
<comment>PlayStation Portable ZISO disc image</comment>
<sub-class-of type="application/x-cd-image"/>
@ -584,15 +605,6 @@
</match>
</magic>
</mime-type>
<mime-type type="application/x-psp-dax-image">
<comment>PlayStation Portable DAX disc image</comment>
<sub-class-of type="application/x-cd-image"/>
<generic-icon name="application-x-cd-image"/>
<glob pattern="*.dax"/>
<magic priority="50">
<match value="0x41445800" type="big32" offset="0"/>
</magic>
</mime-type>
<!-- *** Other *** -->