Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ Information about configuration and supported features can be found on [**TheArc

Join us on our [**Discord server**](https://discord.gg/X8YWP8w) for a chat.

## Loading games from 7z archives

Dreamcast games can be loaded directly from `.7z` files containing exactly one `.gdi`, `.cue`, `.cdi`, or `.chd` image. Include all tracks referenced by a GDI or CUE file, keeping their relative folder paths intact. Archives containing multiple disc images must be split into one archive per disc.

Flycast extracts the required files to temporary disk storage for the duration of play, so enough free space for the uncompressed image is needed. Loading may take longer than opening an unpacked image, and solid archives can require enough memory to decompress an entire solid block. Existing arcade `.7z` ROM sets remain supported with their standard ROM set names.

## Downloads ![android](https://flyinghead.github.io/flycast-builds/android.jpg) ![windows](https://flyinghead.github.io/flycast-builds/windows.png) ![linux](https://flyinghead.github.io/flycast-builds/ubuntu.png) ![apple](https://flyinghead.github.io/flycast-builds/apple.png) ![switch](https://flyinghead.github.io/flycast-builds/switch.png) ![xbox](https://flyinghead.github.io/flycast-builds/xbox.png)

Get builds for your system from the [**builds page**](https://flyinghead.github.io/flycast-builds/) or [**GitHub Releases**](https://github.com/flyinghead/flycast/releases).
Expand Down
124 changes: 69 additions & 55 deletions core/archive/7zArchive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
#include "lzma/7z.h"
#include "lzma/7zCrc.h"
#include "lzma/Alloc.h"
#include "nowide/utf/convert.hpp"

#include <cstring>
#include <mutex>
#include <vector>

#define kInputBufSize ((size_t)1 << 18)

static bool crc_tables_generated;
static std::once_flag crc_tables_generated;

SRes SzArchive::ArchiveStream::Read(const ISeekInStream *p, void *buf, size_t *size)
{
Expand All @@ -50,95 +52,107 @@ SRes SzArchive::ArchiveStream::Seek(const ISeekInStream *p, Int64 *pos, ESzSeek

bool SzArchive::Open(hostfs::File *file)
{
SzArEx_Init(&szarchive);

if (archiveStream.file != nullptr)
delete archiveStream.file;
Close();
if (file == nullptr)
return false;
archiveStream.vt.Read = ArchiveStream::Read;
archiveStream.vt.Seek = ArchiveStream::Seek;
archiveStream.file = file;

LookToRead2_CreateVTable(&lookStream, 0);
lookStream.buf = (Byte *)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
if (lookStream.buf == NULL)
if (lookStream.buf == nullptr)
{
delete archiveStream.file;
Close();
return false;
}
lookStream.bufSize = kInputBufSize;
lookStream.realStream = &archiveStream.vt;
LookToRead2_Init(&lookStream);

if (!crc_tables_generated)
{
CrcGenerateTable();
crc_tables_generated = true;
}
std::call_once(crc_tables_generated, CrcGenerateTable);
SRes res = SzArEx_Open(&szarchive, &lookStream.vt, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
Close();

return (res == SZ_OK);
}

ArchiveFile* SzArchive::OpenFile(const char* name)
std::string SzArchive::GetFileName(size_t index) const
{
u16 fname[512];
for (UInt32 i = 0; i < szarchive.NumFiles; i++)
{
if (SzArEx_IsDir(&szarchive, i))
continue;
if (index >= szarchive.NumFiles)
return {};
size_t len = SzArEx_GetFileNameUtf16(&szarchive, index, nullptr);
if (len == 0)
return {};
std::vector<UInt16> name(len);
SzArEx_GetFileNameUtf16(&szarchive, index, name.data());
return nowide::utf::convert_string<char>(name.data(), name.data() + len - 1);
}

size_t len = SzArEx_GetFileNameUtf16(&szarchive, i, fname);
char szname[512];
size_t j = 0;
for (; j < len && j < sizeof(szname) - 1; j++)
szname[j] = fname[j];
szname[j] = 0;
if (strcmp(name, szname))
continue;
bool SzArchive::IsDirectory(size_t index) const
{
return index < szarchive.NumFiles && SzArEx_IsDir(&szarchive, index);
}

size_t offset = 0;
size_t out_size_processed = 0;
SRes res = SzArEx_Extract(&szarchive, &lookStream.vt, i, &block_idx, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
return NULL;
u64 SzArchive::GetFileSize(size_t index) const
{
return index < szarchive.NumFiles ? SzArEx_GetFileSize(&szarchive, index) : 0;
}

return new SzArchiveFile(out_buffer, offset, (u32)out_size_processed);
}
return NULL;
ArchiveFile* SzArchive::OpenFileByIndex(size_t index)
{
if (index >= szarchive.NumFiles || IsDirectory(index))
return nullptr;

size_t offset = 0;
size_t out_size_processed = 0;
SRes res = SzArEx_Extract(&szarchive, &lookStream.vt, static_cast<UInt32>(index), &block_idx, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
return nullptr;

return new SzArchiveFile(out_buffer, offset, out_size_processed);
}

ArchiveFile* SzArchive::OpenFile(const char* name)
{
for (size_t i = 0; i < GetFileCount(); i++)
if (!IsDirectory(i) && GetFileName(i) == name)
return OpenFileByIndex(i);
return nullptr;
}

ArchiveFile* SzArchive::OpenFileByCrc(u32 crc)
{
if (crc == 0)
return NULL;
for (UInt32 i = 0; i < szarchive.NumFiles; i++)
return nullptr;
for (size_t i = 0; i < GetFileCount(); i++)
{
unsigned isDir = SzArEx_IsDir(&szarchive, i);
if (isDir)
if (IsDirectory(i) || !SzBitWithVals_Check(&szarchive.CRCs, i))
continue;

if (crc != szarchive.CRCs.Vals[i])
continue;

size_t offset = 0;
size_t out_size_processed = 0;
SRes res = SzArEx_Extract(&szarchive, &lookStream.vt, i, &block_idx, &out_buffer, &out_buffer_size, &offset, &out_size_processed, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
return NULL;

return new SzArchiveFile(out_buffer, offset, (u32)out_size_processed);
return OpenFileByIndex(i);
}
return NULL;
return nullptr;
}

SzArchive::~SzArchive()
{
if (lookStream.buf != NULL)
{
delete archiveStream.file;
ISzAlloc_Free(&g_Alloc, lookStream.buf);
if (out_buffer != NULL)
ISzAlloc_Free(&g_Alloc, out_buffer);
SzArEx_Free(&szarchive, &g_Alloc);
}
Close();
}

void SzArchive::Close()
{
delete archiveStream.file;
archiveStream.file = nullptr;
ISzAlloc_Free(&g_Alloc, lookStream.buf);
lookStream.buf = nullptr;
ISzAlloc_Free(&g_Alloc, out_buffer);
out_buffer = nullptr;
out_buffer_size = 0;
block_idx = 0;
SzArEx_Free(&szarchive, &g_Alloc);
}
41 changes: 25 additions & 16 deletions core/archive/7zArchive.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,25 @@
class SzArchive : public Archive
{
public:
SzArchive() : out_buffer(NULL) {
memset(&archiveStream, 0, sizeof(archiveStream));
memset(&lookStream, 0, sizeof(lookStream));
SzArchive() {
SzArEx_Init(&szarchive);
}
~SzArchive() override;

// Takes ownership of file, including when opening fails.
bool Open(hostfs::File *file) override;
ArchiveFile* OpenFile(const char* name) override;
ArchiveFile *OpenFileByCrc(u32 crc) override;

protected:
bool Open(hostfs::File *file) override;
// Returned files share the extraction buffer and must be consumed before opening another file.
ArchiveFile *OpenFileByIndex(size_t index);
size_t GetFileCount() const { return szarchive.NumFiles; }
std::string GetFileName(size_t index) const;
bool IsDirectory(size_t index) const;
u64 GetFileSize(size_t index) const;

private:
void Close();

struct ArchiveStream
{
static SRes Read(const ISeekInStream *p, void *buf, size_t *size);
Expand All @@ -52,23 +58,25 @@ class SzArchive : public Archive
};

CSzArEx szarchive;
UInt32 block_idx; /* it can have any value before first call (if outBuffer = 0) */
Byte *out_buffer; /* it must be 0 before first call for each new archive. */
size_t out_buffer_size; /* it can have any value before first call (if outBuffer = 0) */
ArchiveStream archiveStream;
CLookToRead2 lookStream;
UInt32 block_idx = 0;
Byte *out_buffer = nullptr;
size_t out_buffer_size = 0;
ArchiveStream archiveStream {};
CLookToRead2 lookStream {};

};

class SzArchiveFile : public ArchiveFile
{
public:
SzArchiveFile(u8 *data, u32 offset, u32 length)
SzArchiveFile(u8 *data, size_t offset, size_t length)
: data(data), offset(offset), _length(length) {}
u32 Read(void *buffer, u32 length) override
{
length = std::min(length, this->_length);
memcpy(buffer, data + offset, length);
length = std::min<size_t>(length, _length - position);
if (length != 0)
memcpy(buffer, data + offset + position, length);
position += length;
return length;
}

Expand All @@ -78,6 +86,7 @@ class SzArchiveFile : public ArchiveFile

private:
u8 *data;
u32 offset;
u32 _length;
size_t offset;
size_t _length;
size_t position = 0;
};
2 changes: 2 additions & 0 deletions core/emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ int getGamePlatform(const std::string& filename)
std::string extension = get_file_extension(filename);
if (extension.empty())
return DC_PLATFORM_DREAMCAST; // unknown
if (extension == "7z" && !naomi_cart_IsKnownGame(filename.c_str()))
return DC_PLATFORM_DREAMCAST;
if (extension == "zip" || extension == "7z")
return naomi_cart_GetPlatform(filename.c_str());
if (extension == "bin" || extension == "dat" || extension == "lst")
Expand Down
6 changes: 6 additions & 0 deletions core/hw/naomi/naomi_cart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ static const Game *FindGame(const char *filename)
size_t folder_pos = get_last_slash_pos(gameName); // Only for standard path
if (folder_pos != std::string::npos)
gameName = gameName.substr(folder_pos + 1);
string_tolower(gameName);

for (int i = 0; Games[i].name != nullptr; i++)
if (gameName == Games[i].name)
Expand All @@ -164,6 +165,11 @@ static const Game *FindGame(const char *filename)
return nullptr;
}

bool naomi_cart_IsKnownGame(const char *filename)
{
return FindGame(filename) != nullptr;
}

void naomi_cart_LoadBios(const char *filename)
{
const Game *game = FindGame(filename);
Expand Down
1 change: 1 addition & 0 deletions core/hw/naomi/naomi_cart.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class NaomiCartException : public FlycastException
void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, LoadProgress *progress);
void naomi_cart_Close();
int naomi_cart_GetPlatform(const char *path);
bool naomi_cart_IsKnownGame(const char *filename);
void naomi_cart_LoadBios(const char *filename);
void naomi_cart_ConfigureEEPROM();
void naomi_cart_serialize(Serializer& ser);
Expand Down
Loading
Loading