Skip to content

Backport changes from my ClownCD fork.#143

Open
Clownacy wants to merge 53 commits intortissera:masterfrom
Clownacy:clowncd
Open

Backport changes from my ClownCD fork.#143
Clownacy wants to merge 53 commits intortissera:masterfrom
Clownacy:clowncd

Conversation

@Clownacy
Copy link
Contributor

@Clownacy Clownacy commented Feb 5, 2026

This makes libchdr portable enough to compile for at least 32 libretro platforms. I've also cleaned the code up a bit, addressing a few bugs which I found along the way.

libchdr can now be compiled in a unity build, which makes the library as easy to integrate into a project as a single-file library. This is useful for projects which do not use CMake and cannot rely on pkg-config, such as libretro cores.

This contains my new file IO API, so it makes #142 redundant.

For some reason, the libretro fork has this, but upstream does not!
This new one avoids the need for memory allocation, streamlines some
library internals, and provides a simpler interface for the user:

Old API:

```c
core_file* const file_callbacks = (core_file*)malloc(sizeof(core_file));

if (file_callbacks == NULL)
{
	return NULL;
}
else
{
	chd_file *file;

	file_callbacks->argp = &disc->file;
	file_callbacks->fsize = ClownCD_Disc_CHDFileSize;
	file_callbacks->fread = ClownCD_Disc_CHDFileRead;
	file_callbacks->fclose = ClownCD_Disc_CHDFileClose;
	file_callbacks->fseek = ClownCD_Disc_CHDFileSeek;

	chd_open_core_file(file_callbacks, CHD_OPEN_READ, NULL, &file);
	return file;
}
```

New API:

```c
static const core_file_callbacks file_callbacks = {
	ClownCD_Disc_CHDFileSize,
	ClownCD_Disc_CHDFileRead,
	ClownCD_Disc_CHDFileClose,
	ClownCD_Disc_CHDFileSeek,
};

chd_file *file;

chd_open_core_file_callbacks(&file_callbacks, &disc->file, CHD_OPEN_READ, NULL, &file);
return file;
```

The old API has been preserved for backwards-compatibility; since
libchdr is packaged by Debian, I figured that API-breaking changes
would be undesirable.
Not a standard C type, and it conflicts with the rest of the code
anyway: it only gets set to, and compared to, regular `size_t`!
Nothing ever actually caches a chunk.
Also did some tidying, while I was at it.
Applied applied the 'FALSE' constant a few times.
The call operator can be used on a function pointer as-is.
Should shut-up some warnings when compiling as ANSI C.
- Does not erroneously refer to frames as samples.
- Correctly allocates a sector's worth of samples.
- No longer defines a one-word macro that could cause a name
  collision in unity builds.
`strncmp` is redundant, and the compiler complains about `rawheader`
not being guaranteed to be null-terminated.
This has better code locality, making it simpler to add new versions
in the future, and exposing the bug that the `flags` field is filled
with garbage when parsing a v5 header. That has now been fixed.
The reader already performs validation, and even returns different
error codes compared to the validator, so it is best if they are
merged.
They report failure for a reason, so we may as well act on it.
Now there's just a nice little function for doing that.
This should prevent a read-error being reported when reading a non-v5
CHD file with zero hunks, as it will not accidentally read past the
end of the header data.
Less complexity; more portability. Also much easier to perform a
unity build with. There is a slight difference in licensing
(zlib -> MIT), but that should not be a problem.
As with miniz, this version of the library is simpler, making it more
portable, and also easier to perform a unity build with.
It is easier to maintain code when you don't have to figure which
parts aren't used at all.
Forced include directories are incompatible with unity builds.
Unity builds cannot use forced include directories.
The only truly-portable build system!
Some libretro platforms do not support the `inline` keyword, so using
a macro makes it possible to work-around.
Static builds of libretro cores link against a static build of
RetroArch, which includes its own copy of LZMA, causing error due to
the same symbols being defined twice. To work around this, use macro
magic to namespace our copy of LZMA.

This is not too unusual: the SDL library does the same thing for its
copy of libusb:

libsdl-org/SDL@08b2176

It may be worth doing this to miniz and zstd as well.
Having everything tangled in a single file makes for bad code;
keeping them separate allows for encapsulation.

This will also make it easier to allow switching dr_flac for libFLAC,
if it ever comes to that.
All of them (besides CDFL) are identical aside from which
decompression functions are used. The vast majority of code can be
punted to a shared function.
@rtissera
Copy link
Owner

rtissera commented Feb 6, 2026

Ouch, this is big.... but very good work.
OK, let me close #142 first, do you think you could have a fix for failing CIs or shall I have a look ?

@rtissera
Copy link
Owner

rtissera commented Feb 6, 2026

@Clownacy Also the question is how "bad" it will break current libretro integrations ?
I'm all in for moving forward and cleaning up, don't get me wrong.

@Clownacy
Copy link
Contributor Author

Clownacy commented Feb 6, 2026

The CI seems to be failing because of a timeout when fetching ftp://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/10.1/All/pkg_summary.gz. The file does exist at https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/10.1/All/pkg_summary.gz, but the URL gets redirected to https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/x86_64/10.0_2025Q4/All/pkg_summary.gz. Maybe FTP isn't handling that redirect properly? Either way, it does not seem to be caused by any of my changes.

Backwards-Compatibility

This update should be mostly backwards-compatible with existing projects, though there are some edge-cases where it is not:

Libretro cores tend to bypass libchdr's build system and compile its C files manually. Cores that do this will now encounter linker errors due to libchdr having additional C files which the core needs to be updated to reference. If these projects relied on libchdr's CMake script instead, then this would not have been a problem. For projects that cannot use CMake, the unity.c file provides a simple way to use libchdr in a future-proof manner, as it will always be updated to account for new C files. Either way, this is an easy problem for users to address: they can use CMake, the unity file, or just update their Makefile to reference the new C files like they always have.

Projects which rely on chd_codec_config or chd_get_codec_name will encounter linker errors due to the removal of those functions. Given that they only return an error code (or an error message), I figure that either no project actually uses them, or no project bothers checking their return values. If need be, these stubbed functions can easily be restored.

The new file IO API implements the old API as a layer atop the new one, maintaining backwards-compatibility. There is one slight change in behaviour though: if a file were opened with chd_open or chd_open_file, then chd_core_file could be used to obtain its core_file struct; this is no longer the case, as those functions do not create a core_file struct. chd_core_file can now only obtain the core_file struct of a CHD file that was opened with the chd_open_core_file function, which I imagine was always the intended use-case for this function anyway. If preserving the old behaviour is a must, then chd_open or chd_open_file can be implemented atop chd_open_core_file (though this would make the functions slightly less efficient, requiring a call to malloc).

core_fclose, core_fread, core_fseek, and core_fsize have been changed to target the new file IO API. From what I can tell, these are internal libchdr functions, but they are technically visible to users due to chd.h including coretypes.h, so any projects which are using these functions (for some reason) will not compile. If needed, I can restore the old version of these functions, and provide new functions (which target the new file IO API) for libchdr to use internally.

Other than these edge-cases, this update should not cause any breakage for libretro cores.

SwanStation Test

As a test, I modified a copy of SwanStation to use this version of libchdr, and, while there were a couple of issues that needed sorting, it was mostly a smooth upgrade. The issues that I encountered were...

  • SwanStation needs to link against chdr-static instead of libchdr.
    • SwanStation uses a custom CMake build script for libchdr, so switching to this repository's CMake script necessitates this change.
  • SwanStation needs to define USE_LIBRETRO_VFS and include libretro-common's include directory for the chdr-static target, as these are necessary for integration with libretro's file API to work.
    • SwanStation's copy of libchdr works differently to the upstream version, making USE_LIBRETRO_VFS unnecessary, and its custom CMake script handles libretro-common's include directory manually. Using upstream libchdr voids these, requiring the aforementioned changes.
    • We cannot do these in libchdr itself because we do not know where libretro-common is (it is provided by the libretro core, not us).
    • Alternatively, libretro integration can be removed from libchdr, and users can be expected to use the core_file or core_file_callbacks API to make libchdr use libretro's file IO (this is what ClownMDEmu/ClownCD does). In its current state, libretro integration is very much a giant hack.
  • SwanStation needs to stop building its own copy of the LZMA library, as this conflicts with libchdr's copy.
    • Previously this worked because of this change, but now that LZMA's functions are namespaced to prevent name-collisions in static libretro builds, these namespaced functions cannot be found in SwanStation's copy of LZMA, causing linker errors.
    • This can be worked around by renaming libchdr's LZMA CMake target from lzma to chdr-lzma, and removing the check for if it has already been defined (because projects should not be defining internal libchdr libraries).
    • Alternatively, LZMA namespacing can be disabled if a user-provided lzma CMake target is detected, though, given the lack of a standard LZMA CMake package, this seems like a very specific-to-SwanStation hack.
  • add_subdirectory(libchdr) needs to be changed to add_subdirectory(libchdr EXCLUDE_FROM_ALL), to prevent CMake from trying to build the benchmark program, which fails due to linker errors caused by the libretro integration.
    • This can also be addressed by linking chdr-static against libretro-common in SwanStation's CMake script, but that requires policy CMP0079 be set to NEW.

Once these changes were made, SwanStation compiled and ran correctly.

SwanStation defines its own `lzma` target, which lacks the
namespacing that libchdr's copy has, so linking against it will
result in linker errors. To avoid this, give libchdr's copy a
different name.
…RIFY_BLOCK_CRC`.

Better than forcing the user to modify a libchdr header. It may be
worth namespacing these to prevent name collisions with user code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants